fetch を使って multipart/form-data ヘッダーと FormData を POST する方法

これは問題なく動作するCURLの例である:

curl -X POST \
  <url> \
  -H 'authorization: Bearer <token>' \
  -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
  -F file=@algorithm.jpg \
  -F userId=<userId>

isomorphic-fetch]1を使ってこのリクエストを再現しようとしています。

以下のコードを試してみました:

const formData = new FormData();
formData.append('file', file);
formData.append('userId', userId);

return fetch(`<url>`, {      
  method: 'POST',
  headers: {
    'Content-Length': file.length
    'Authorization: Bearer <authorization token>',
    'Content-Type': 'multipart/form-data'
  },
  body: formData
})`

FormDataに渡されたfileを生成するためにfs.readFileSync` を使用しています。

先ほどの例では、トークンに埋め込まれた userIdformData から渡された userId と一致しないというエラーメッセージとともに 401 HTTP ステータスコード (unauthorized) が返されます。

つまり、REST API に届く FormData は適切に形成されていないのではないだろうか。

この問題は Content-Length ヘッダーに関連しているのかもしれないが、それを計算する良い方法は見つからなかった(Content-Length ヘッダーを使用しないと、411 HTTP ステータスコード Content-Length ヘッダーが見つからない)。

Content-Lengthヘッダーの値が正しくないために失敗しているのでしょうか?

これが失敗する理由や、より良いデバッグ方法について、他に何か提案はありますか?

この問題を明確にするためにさらに情報が必要であれば、質問してください。

**アップデート

formData.getLengthSync()メソッドを使って正しいContent-Length`値を取得するために、form-dataモジュールを試してみました。

しかし、問題は変わりません(HTTPステータスコードのレスポンスが401エラー)。

質問へのコメント (5)
ソリューション

ネットワーク・インスペクタを開き、このコード・スニペットを実行し、フォームを送信すると、Content-Lengthが正しく設定されていることが確認できるはずです:

解説 (4)

同じような壁に頭をぶつけた。具体的には、nodeでisomorphic-fetchを使ってマルチパートフォームをPOSTした。私にとっての鍵は .getHeaders() を見つけることだった。 NPM description for form-data](https://www.npmjs.com/package/form-data)は、これがなくても動作することを示唆しているが、少なくともnodeでは動作しないようだ

// image is a Buffer containing a PNG image
// auth is the authorization token

const form_data  = new FormData();
form_data.append("image", png, {
    filename: `image.png`,
    contentType: 'application/octet-stream',
    mimeType: 'application/octet-stream'
});

const headers = Object.assign({
    'Accept': 'application/json',
    'Authorization': auth,
}, form_data.getHeaders());

try {
    const image_res = await fetch(url, {
        method: 'POST',
        headers: headers,
        body: form_data
    });

    if (!image_res.ok) {
        const out = await image_res.json();
        console.dir(out);
        return;
    }
}
catch (e) {
    console.error(`Chart image generation exception: ${e}`);
}
解説 (2)