35
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Node.js上からmultipart/form-data形式でHTTPリクエストをする

Posted at

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がある。

ブラウザ.js
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メソッドなど。

form-data.js
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を使う。

サーバー(express).js
:
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で処理できずに止まってしまう。

.js
// サーバー側でエラーになり、うごかない
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の第三引数で明示的に指定する必要がある。

.js
:
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
35
32
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?