FormDataはフォームの文字列とファイルを組み立てXMLHttpRequestで送る
公開日: 執筆:佐藤
FormDataは、フォームにある文字列とファイルを、名前と値の組としてまとめるオブジェクトです。既存のform要素からnew FormData(form)で作るか、空のFormDataへappend()で項目を追加します。
作ったFormDataは、XMLHttpRequestのsend()へそのまま渡します。Content-Typeをmultipart/form-dataへ手作業で固定せず、送信内容と一致するboundary(境界文字列)をブラウザーに作らせてください。
FormData(form)はフォームの項目から作る
W3CのXMLHttpRequest Level 2仕様は、FormDataを、名前、値、種別、必要に応じてファイル名を持つ項目の順序付き集合として定義しています。
FormData(form)へHTMLFormElementを渡すと、そのフォームから構築したform data set(フォームデータセット)を項目として持つ、新しいFormDataが返ります。
<form id="upload-form" action="/upload" method="post">
<label for="company">会社名</label>
<input id="company" name="company" type="text" />
<label for="document">添付ファイル</label>
<input id="document" name="document" type="file" />
<button type="submit">送信</button>
</form>
var form = document.getElementById('upload-form');
var data = new FormData(form);
この例では、companyとdocumentという名前を持つフォーム項目から、送信用の集合を作ります。idはlabelとの関連付け、nameは送信する項目名に使います。どの項目がform data setへ入るかは、フォームの構造と各control(フォームコントロール)の状態で決まります。
append()は文字列またはBlobを末尾へ追加する
FormDataを空で作り、必要な値を一つずつ追加することもできます。
var data = new FormData();
data.append('company', 'サンプル商事');
data.append('message', '資料を送ります');
W3C仕様は、append(name, value)のvalueとして、文字列またはBlobを受け取る形を定めています。Blobを追加するときは、三つ目の引数でファイル名も指定できます。
var input = document.getElementById('document');
if (input.files.length > 0) {
var file = input.files[0];
data.append('document', file, file.name);
}
Fileに空でないnameがあれば、その名前がファイル名に使われます。三つ目のfilenameを渡した場合は、その指定が使われます。
append()は同じ名前の項目を置き換える命令ではありません。新しい名前と値の組を集合の末尾へ追加します。同じnameを複数回使う設計では、受信側が複数の値として扱えるかも確認してください。
send(FormData)はmultipart/form-dataのリクエスト本文を作る
FormDataを送るときは、XMLHttpRequestを開き、send()の引数へ渡します。
var request = new XMLHttpRequest();
request.open('POST', form.action, true);
request.onload = function () {
if (request.status >= 200 && request.status < 300) {
// サーバーが返した成功時の処理
} else {
// HTTP statusに応じた処理
}
};
request.onerror = function () {
// 通信に失敗した場合の処理
};
request.send(data);
send(data)はリクエストを開始し、引数をリクエスト本文にします。ただし、リクエストメソッドがGETまたはHEADなら、仕様上、引数はnullとして扱われます。フォームの文字列とファイルを本文として送る例では、受信側が受け付けるPOSTなどのメソッドを使います。
FormDataが渡された場合、ブラウザーは、内容をUTF-8のmultipart/form-dataとして符号化し、boundaryを生成します。各文字列とファイルは、そのboundaryで区切られたリクエスト本文になります。
Content-Typeのboundaryを手入力しない
W3C仕様では、作成者がContent-Typeを設定しておらず、送信内容からMIME typeが決まる場合、ブラウザーがContent-Typeヘッダーを追加します。FormDataでは、生成したboundaryを含む次のような値です。
Content-Type: multipart/form-data; boundary=...
次のように、multipart/form-dataだけを手作業で設定しないでください。
// FormDataを送る場合は、この行を書かない
request.setRequestHeader('Content-Type', 'multipart/form-data');
boundaryはリクエスト本文の区切りと一致する必要があります。作成者がContent-Typeを先に設定すると、仕様の「ヘッダーがなければブラウザーが追加する」という条件から外れます。send(data)へFormDataを渡し、本文とヘッダーを同じ処理で組み立てます。
FormDataの作成とサーバーの受付を分ける
new FormData(form)が成功しても、サーバーがファイルを保存したことまでは分かりません。ブラウザー側は、名前、値、ファイル、ファイル名をリクエストへ組み立てます。受信側は、メソッド、項目名、必須項目、ファイル種別、容量、保存先を検査します。
HTMLのrequired属性やJavaScriptの確認だけを、サーバー側の検査の代わりにはできません。また、HTTP通信が完了したことと、入力内容が受理されたことも別です。loadだけで成功と決めず、HTTP statusとサーバーのresponse(応答)を確認します。
送信先の仕様を、次の対応で残します。
| フォームのname | FormDataの値 | 受信側で確認すること |
|---|---|---|
company |
文字列 | 必須か、許容する長さ |
document |
ファイル | 種別、容量、ファイル名、保存方法 |
message |
追加した文字列 | 任意か、許容する長さ |
表内の項目名は説明用です。実際のendpointが受け付ける名前と条件へ合わせます。
一つのフォームでリクエストと応答を確認する
最初に、文字列一項目とファイル一項目だけのフォームを用意します。new FormData(form)で作り、追加項目が必要ならappend()し、send(data)へ渡します。
送信時は、リクエストメソッド、送信先URL、Content-Typeのboundary、各partの項目名とファイル名を確認します。受信後は、HTTP statusとresponseを確認し、サーバー側の記録と対応させます。
フォームを組み立てられたこと、requestを送れたこと、サーバーが内容を受理したことは、三つの別の結果です。一つのフォームでこの三段階を確認してから、項目数、複数ファイル、進捗表示などを追加してください。
関連記事

