2017/12/04更新

[フロントエンド] multipart/form-dataを理解してみよう

このエントリーをはてなブックマークに追加            

こんにちは、@yoheiMuneです。
ファイルアップロードを実装する時はいつも苦戦するのですが、multipart/form-dataというデータ形式を理解できたらすんなりと実装できるようになりました。今日は、ファイルアップロードでは必須とも言えるmultipart/form-dataについて、ブログにまとめました。



目次




サンプルコード

本記事のサンプルをGithubに置いてあります。Node.JSのサーバーで実装しています。実際に動かすと理解の助けになると思いますので、必要あればご参照ください。



コンテンツの種類とは

multipart/form-dataを扱う前に、まずは「コンテンツの種類」について理解する必要があります。

フロントエンドとバックエンドとのデータのやり取りでは、htmljavascriptpngjsonなど様々な種類のデータを扱います。その際に、どんなデータを扱っているのかを指定する必要があります。

そのコンテンツの種類を指定するために、MIME Typeという形式が利用されています。それをHTTPのヘッダ(Content-Typeヘッダ)に付与して、送信先にコンテンツの種類を伝えます。例えば本サイトからHTMLを受け取ると、以下のヘッダが付与されます。
// htmlファイルを受け取る場合のHTTPヘッダ
Content-Type:text/html; charset=UTF-8
上記はMIME Typetext/htmlで、送信内容がhtmlであることを表現しています。

MIME Typeにはhtml以外にも様々な種類が存在します。
text/plain       : テキストファイル
image/png        : PNG画像
application/pdf  : PDFファイル
application/json : JSONファイル
など(その他の形式はこちら

そして、MIME Typeの中の1つが、今回扱いたいmultipart/form-dataという形式です。これは複合データ型(=multipart)であることを表現します。これは1回のHTTP通信で、複数の種類(テキストやファイルなど)を一度に扱うことができます。

以降では、multipart/form-dataの中身に踏み込みます。



multipart/form-dataとは

multipart/form-dataは、前述の通り複数の種類のデータを一度に扱える形式で、主な利用シーンはHTMLフォームです。特にファイルアップロードでよく利用されます。

HTMLフォームで利用する

HTMLフォームで複合型を使うには、formタグの属性でenctype="multipart/form-data"を指定します。
<!--multipart/form-data形式でPOSTする例-->

<form method="POST" action="/upload" enctype="multipart/form-data">
    <input type="text" name="message" value="Hello"/><br>
    <input type="file" name="file"/><br>
    <input type="submit" value="SUBMIT"/>
</form>
これを実際にPOSTすると、multipart/form-data形式でサーバーにデータが送信されます。
なお、JavaScriptでの実装方法はこちらをご参照ください。


HTTPの送信データ

上記のフォームでファイルにa.txtを指定した場合について、具体的に見ていきます。

以降ではHTTPリクエストの中身を扱いますが、それの理解は「HTTPリクエストの中身を学ぶ」が役立ちます。

HTTPヘッダ

この記事の最初で説明した通りHTTPヘッダには、Content-Typeが付与されます。
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryO5quBRiT4G7Vm3R7
このデータ形式がmultipart/form-dataであることが分かります。

また、boundary=----WebKitFormBoundaryO5quBRiT4G7Vm3R7という値が指定されていることがわかります。これはコンテンツの境界を示す文字列です。データ形式が複合型なので、1回の送信データでいくつかの種類のデータが含まれますが、それらを分けるために使います(具体的にはボディ部で使います)。この境界を示す文字列はリクエストごとに変わるため、手元で試した場合には別の文字列になります。


HTTPボディ

続いて送信内容(HTTPボディ)です。以下の内容が指定されました。
------WebKitFormBoundaryO5quBRiT4G7Vm3R7
Content-Disposition: form-data; name="message"

Hello
------WebKitFormBoundaryO5quBRiT4G7Vm3R7
Content-Disposition: form-data; name="file"; filename="a.txt"
Content-Type: text/plain

aaa
------WebKitFormBoundaryO5quBRiT4G7Vm3R7--
ボディは境界を示す文字列ごとに、複数のデータが存在することがわかります。
1つ目のデータはname="message"というキーで、値がHelloであることがわかります。
2つ目のデータはname="file"というキーで、ファイル名がfilename="a.txt"で、ファイルの種類がContent-Type: text/plainで、ファイルの中身がaaaであることがわかります。

このように、複数のデータ型をまとめて扱うことができる形式がmultipart/form-data形式です。中身まで見ると難しくないですね!


参考までに

今回は説明のためにファイルにはテキスト形式を用いましたが、もちろん画像などのバイナリーデータも指定可能です。しかしその場合には、ファイルの中身(上記の場合はaaa)がバイナリを表現する形式になるため、人の目では理解することができません(サンプルはこちら)。

また、サーバーサイドでは上記の形式を読み取って、適切にデータを受け取る必要があります。なかなか面倒な処理なので、この辺りは言語本体やライブラリに任せたいところですね。



参考文献

以下が参考になりました。ありがとうございます。

MIME タイプ - HTTP | MDN

フォームよるファイルアップロードの仕様 - Jakarta Commons FileUploadの利用手順

いまさら聞けないHTTPマルチパートフォームデータ送信 - SATOXのシテオク日記

Content-Disposition - HTTP | MDN

フォームデータを送信する - ウェブ開発を学ぶ | MDN



最後に

中身を理解できるとなんてことないですね。こんな感じのベーシックスキル、今後も定期的に身につけていきたい今日この頃です。

最後になりますが本ブログでは、フロントエンド、Node.js、Python、Swift、Go言語、Linux、インフラ、Java、機械学習、などの技術トピックを発信をしていきます。「プログラミングで困ったその時に、解決の糸口を見つけられる」そんな目標でブログを書き続けています。今後も役立つネタを書いていきますので、ぜひ本ブログのRSSTwitterをフォローして貰えたら嬉しいです ^ ^

最後までご覧頂きましてありがとうございました!





こんな記事もいかがですか?

[取り組み] フロントエンドでコーディングスピードをアップさせる6つの方法!と思って書いてたら30個も書いちゃった。
[フロントエンド] フロントエンドの入社試験99問!難しいですよ〜w。
[フロントエンド] Webページを表示するテストの際に、通信速度を3Gに制限して表示してみよう
[フロントエンド] スマホ実機でのデバッグ手段を増やす!Macのプロキシを利用して、通信内容を確認する。
[フロントエンド] Chrome 35 Beta の変更点。Touch制御、新しいJavaScript機能、プレフィックスなしのShadowDOM
[フロントエンド]複数アカウントでのテストには、Chromeのユーザー管理を使って、Cookieを切り替えると便利
[フロントエンド] Chrome36βが出た。変更点など。element.animate、HTML Imports、Object.observe、他。
RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。