(久しぶりにSwift以外の記事…)
はじめに
結論はタイトルの通り: 「Fetch APIでFormData
をPOST
するときにContent-Typeを指定しないようにしよう。」
その理由は…?
Content-Typeの指定あり/なしで比較
次のコードを実行してみてください。Content-typeをmultipart/form-data
に設定しています。
const formData = new FormData();
formData.append('name', 'value');
const request = new Request('/do-something', {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
},
body: formData,
});
const response = fetch(request);
そうすると次のようなリクエストが送られます(例: Safariの場合; 一部のヘッダは省略)。
POST /do-something HTTP/1.1
Accept: */*
Content-Type: multipart/form-data
Content-Length: 140
Host: example.com
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
------WebKitFormBoundaryHwUCy8Y0kR36DASN
Content-Disposition: form-data; name="name"
value
------WebKitFormBoundaryHwUCy8Y0kR36DASN--
これの何が問題なのでしょう?
既にお気づきの方もいらっしゃるかとは思いますが、次は前出のコードの'Content-Type': 'multipart/form-data',
をコメントアウトして実行してみてください。その場合のリクエストは次のようになります。
POST /do-something HTTP/1.1
Accept: */*
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRAycGNoAYaQ0xKA0
Content-Length: 140
Host: example.com
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
------WebKitFormBoundaryRAycGNoAYaQ0xKA0
Content-Disposition: form-data; name="name"
value
------WebKitFormBoundaryRAycGNoAYaQ0xKA0--
さて、Content-Typeを指定する場合と指定しない場合のHTTPリクエストの違いが見えましたか?
Content-Typeを指定しない場合、Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRAycGNoAYaQ0xKA0
のようにクライアントが適切なContent-Typeを設定します。そして大切なのは、FormData
をPOST
するときにはContent-Typeのパラメータに"boundary"を含めなければいけないということです1。
しかし、JavaScriptでRequest
を生成する際にContent-Typeを指定してしまうと、適切なboundaryを指定することができません。すると、サーバ側でmultipartの境界を判別できずにデータの受け取りに失敗することになります。…リクエストボディの1行目をみればboundaryは一目瞭然じゃないかって?もしかしたら「優しいプログラム」は1行目をみてboundaryを判断することがあるかもしれません。しかし、サーバ側のすべてのプログラムがそのように「優しい」ことを期待してはいけません。サーバ側のプログラムを書く人間からすると、リクエストのContent-Typeにboundaryが指定されていない場合に1行目の文字列からそれが勝手にboundaryと判断することはデータを誤って解釈することになりかねず(入れ子になったmultipartとか…実は別のデータだったとか…)、基本的にはboundaryの指定がなければエラーとするでしょう。
もう一度結論
Fetch APIでFormData
をPOST
するときにContent-Typeを指定しないようにしよう。
-
"boundary"は基本的にクライアントがランダムな文字列を含めることが一般的です(実際Safariもリクエストごとに文字列が異なる) ↩