Node.jsからほかのサーバーにファイルを送るには、いろいろな方法がある。
- ファイルのURLを送り、対向側で別途ダウンロードしてもらう。
- ファイルをbase64形式にしてJSONのプロパティで送る
- multipart/form-data形式で送る
- JSONを使わない、HTTPリクエストを使わない
など?
form-data を使って multipart/form-data で送信する
multipart/form-data形式で送る。これは、Webブラウザのformタグが伝統的にやっている方法と同じだ。Node.jsを使うなら、JSONなどを使うほうが楽だが、どうしても multipart/form-data 形式を使いたいケースもある。
ブラウザ側でmultipart/form-dataを使うのは簡単だ。ビルトインクラスのFormDataがある。
async onFileChanged(event) {
const file = event.target.files[0]
:
var form = new FormData();
form.append("file", file);
await axios.post(`/upload`, form);
:
Node.jsでは、form-data パッケージを使うと同じことができる。
ブラウザのFormDataと同じインターフェイスを目指して作られている。
また、ブラウザには無いヘルパーがある。簡単に送信できるsubmit
メソッドなど。
const FormData = require('form-data');
const fs = require('fs');
const form = new FormData();
form.append('my_file', fs.createReadStream('/foo/bar.jpg'));
form.submit('http://example.com/upload', function(err, res) {
console.log(res.statusCode);
});
テストするためにミニマルなサーバーコードを用意する。これには multerを使う。
:
const multer = require("multer");
const upload = multer({
storage: multer.memoryStorage()
});
app.post("/upload", upload.single("my_file"), async (req, res, next) => {
const { file } = req;
if (!file) {
res.status(500).send("NG");
return;
}
console.log(file.mimetype);
console.log(file.originalname);
res.send("OK");
});
:
これは動く。
Bufferを送ろうとすると動かない?
form-dataを使うときは、入力するデータの型に注意が必要。↑のサンプルのように、inputStreamを使う場合は問題無いが、Bufferなどを使うと、なぜか、送信できない。
正確には、送信できるが、サーバー側のmulterで処理できずに止まってしまう。
// サーバー側でエラーになり、うごかない
const FormData = require('form-data');
const form = new FormData();
:
const buffer = fs.readFileSync('image.jpg'); // Bufferに読み込む
form.append('my_file', buffer); // Bufferを渡す
form.submit('http://example.com/upload', function(err, res) {
console.log(res.statusCode);
});
:
form-dataは、入力クラス次第で、ファイル名などの情報を自動的に推測してくれるが、ファイルのメタ情報が失われている場合、form.appendの第三引数で明示的に指定する必要がある。
:
const buffer = fs.readFileSync('image.jpg');
form.append('my_file', buffer, {
filename: 'image.jpg'
contentType: 'image/jpeg',
knownLength: buffer.length
});
:
ブラウザがどのように multipart/form-data でデータを送信しているのかを、dev tool などで観察すると、理解が深まる。ブラウザがファイルを送信するときには、HTTPリクエスト内の各サブパートに、Content-Dispositionヘッダをつけて、ファイル名などのメタ情報を書き込む。これが無いと、サーバー側では、ファイルとして認識できない。
Content-Disposition: form-data; name="file"; filename="my-image.jpg"
Content-Type: image/png
{ここに本文}
MDN の Content-Disposition のドキュメント
axiosでFormDataを送る
form-dataパッケージは送信するための関数 .submit()を持っているが、ほかのHTTPリクエストクライアントでも送信することができる。axiosを例にすると、次のようなコードでできる。
const form = new FormData();
:
:
const response = await axios.post(`http://example.com/upload`, form, {
headers: form.getHeaders()
});
form.getHeaders()メソッドでHTTPヘッダを取得して、axiosに渡す必要がある点に注意が必要。これの中身は、次のようなContent-Typeヘッダの情報になっている。この boundaryの識別子を、HTTPヘッダに含める必要があるということ。
Content-Type: multipart/form-data; boundary=--------------------------709506743092431454880062