Multipart Uploadは、テキストフィールドやバイナリファイルを含む複数の異種データを1つのリクエストで転送できるHTTPメカニズムです。各パートは一意の境界文字列で区切られ、独自のContent-Typeヘッダーを持ちます。MDN Web Docs, 2025によると、multipart/form-dataはHTMLフォームを介したファイルアップロードの標準形式であり、画像、ドキュメント、その他のファイルをサーバーに送信するためにWebアプリケーションやモバイルアプリケーションで広く使用されています。
重要ポイント
Multipart Uploadは、リクエストボディが複数の論理的に分離されたパートで構成されるHTTPプロトコルを介したデータ転送方法です。各パートには、テキストフォームフィールド、バイナリファイル、JSONオブジェクト、画像など、異なるタイプのデータを含めることができます。すべてのパートは1つのPOSTリクエストにパッケージ化され、N回の個別HTTP呼び出しを送信する必要がなくなります。Multipart Uploadは、WebフォームとファイルアップロードAPIの必須部分です。
マルチパート形式は、電子メールメッセージのMIME標準の一部としてRFC 2046仕様で定義され、その後RFC 1867でHTTPに適応されました。今日、Web開発では、ファイルを含むフォーム用に設計されたマルチパートサブタイプの1つであるmultipart/form-dataがほぼ独占的に使用されています。他のサブタイプ(multipart/mixed(任意の添付ファイル用)やmultipart/byteranges(ファイルの部分ダウンロード用))は、はるかに頻繁に使用されません。
マルチパートと単純なapplication/x-www-form-urlencodedの根本的な違いは、後者がすべてのデータをURI互換の文字列にエンコードし、バイナリファイルをサポートしないことです。一方、Multipart/form-dataは、エンコードせずに各ファイルを元のバイナリ形式で送信するため、より効率的で精度が失われません。リクエストサイズは、パートヘッダーと境界のオーバーヘッドにより、マルチパートの場合、常にファイルサイズの合計より5〜15%大きくなります。
Multipart Uploadは、ファイルアップロードが必要なあらゆる場所で使用されます。ソーシャルネットワークのアバターやプロフィール写真、メッセンジャーの添付ファイル、CRMシステムのドキュメント、オンラインストアの商品画像などです。モバイルアプリケーションでは、デバイスのカメラからの写真、音声録音、ビデオクリップなどのメディアファイルをサーバーに送信するためにMultipart Uploadが使用されます。Cloudflare Researchによると、Web上の全POSTリクエストの約15%がmultipart/form-dataを使用しています。
Multipart UploadとChunked Transferは異なるメカニズムです。マルチパートはリクエストを意味のあるパート(フィールドとファイル)に分割しますが、チャンク転送は総サイズを知らなくても送信できるようにデータストリームを断片に分割します。マルチパートはチャンク転送内で送信できます。サーバーは完全なサイズを知らなくてもマルチパートレスポンスを部分的に送信します。これらのメカニズムは競合せず、異なるレベルで異なる問題を解決します。
ブラウザがenctype="multipart/form-data"属性を持つフォームを送信すると、マルチパート形式でリクエストボディを構築します。各フォームフィールドは個別のブロックになり、境界文字列(boundary)によって他のブロックと区切られます。境界は自動生成され、データ内に出現しないことが保証された一意の文字シーケンスです。クライアントはこの境界をContent-Typeヘッダーに追加します: multipart/form-data; boundary=----WebKitFormBoundaryX7K。
各ブロックは--boundaryで始まり、フィールド名(name)と、ファイルの場合は元のファイル名(filename)を含むContent-Dispositionヘッダーを持ちます。空白行の後に、実際のフィールドデータまたはバイナリ形式のファイルコンテンツが続きます。リクエストは文字列--boundary--で終了します。サーバーは受信したストリームを解析します。最初に境界を見つけ、次に各パートのヘッダーを抽出し、データタイプを判別して、フォームハンドラーまたはAPIコントローラーに渡します。
IETF RFC 7578によると、multipart/form-dataは各パートにcharsetを指定する必要はありません。テキストフィールドはUTF-8と見なされ、バイナリパートには元のエンコーディングのファイルが含まれます。1つのパートのサイズはプロトコルによって制限されません。制限はサーバーレベルで設定されます。たとえば、Nginxではclient_max_body_size、Spring Bootではspring.servlet.multipart.max-file-sizeで設定します。
Boundaryは、送信データに出現してはならない一意の文字列です。通常はプレフィックス(----WebKitFormBoundaryや----Boundaryなど)で始まり、ランダムな文字を含みます。ブラウザとHTTPクライアントは自動的にboundaryを生成します。RFC 2046に従い、boundaryの長さは70文字を超えてはなりません。各パートは文字列--boundary\r\nで区切られ、リクエストの終わりは--boundary--\r\nで示されます。
マルチパートリクエストは、MIMEおよびHTTP標準によって定義された厳格な構造を持ちます。リクエストヘッダーは、boundaryパラメータを持つContent-Type: multipart/form-dataを設定します。リクエストボディは一連のパートで構成され、各パートには独自のヘッダーとボディが含まれます。パートヘッダーには、Content-Disposition(必須)とContent-Type(オプション、ファイル用)が含まれます。パートヘッダーとそのデータの間に空白行が必要です。
| 要素 | 例 | 必須 |
|---|---|---|
| Content-Type | multipart/form-data; boundary=---Bnd123 | はい |
| パート区切り | ---Bnd123 | はい(各パートの前) |
| Content-Disposition | form-data; name="avatar"; filename="photo.jpg" | はい |
| パートContent-Type | image/jpeg | ファイル用 |
| パートボディ | [バイナリ画像データ] | はい |
| 終了境界 | ---Bnd123-- | はい(リクエスト終了) |
テキストフィールドと画像ファイルを送信するマルチパートリクエストの実際の例を考えてみましょう。クライアントは一意のboundaryを持つContent-Typeヘッダーを形成します。リクエストボディにはすべてのフォームフィールドが順に含まれます。受信すると、サーバーはこれらのパートを解析し、各フィールドに個別のオブジェクトとしてアクセスできるようにします。このアプローチにより、1回のHTTP呼び出しでファイルを含む複雑なフォームを処理できます。
import okhttp3.*
import java.io.File
fun uploadFile() {
val client = OkHttpClient()
val imageFile = File("/path/to/photo.jpg")
val requestBody = MultipartBody.Builder()
.setType(MediaType.parse("multipart/form-data"))
.addFormDataPart("username", "john_doe")
.addFormDataPart(
"avatar", "photo.jpg",
RequestBody.create(
MediaType.parse("image/jpeg"), imageFile
)
)
.build()
val request = Request.Builder()
.url("https://api.example.com/upload")
.post(requestBody)
.build()
client.newCall(request).execute().use { response ->
println("アップロード済み: ${response.isSuccessful}")
}
}
サーバー側では、マルチパートリクエストはフレームワークによって解析されるか、手動で解析されます。Spring Bootでは、@RequestParam("avatar") MultipartFile fileアノテーションで十分であり、フレームワークが自動的にマルチパートリクエストからファイルを抽出します。KotlinのKtorではreceiveMultipart()が使用され、Express.jsではmulterミドルウェアが使用されます。サーバーは各フォームフィールドと各アップロードファイルに個別にアクセスでき、ファイルをディスクまたはクラウドストレージに保存し、URLまたは識別子をクライアントに返します。
Multipart Uploadは、代替のデータ転送方法に比べていくつかの重要な利点を提供します。多数の代わりに1つのリクエスト — すべてのフォームフィールドとファイルが1回のHTTP呼び出しで送信されるため、ネットワークとサーバーの負荷が軽減されます。N個のファイルをアップロードするためにN個の接続を開く必要はありません。すべてが1つのPOSTにパッケージ化されます。これは、各HTTP接続が遅延とバッテリー消耗を意味するモバイルアプリケーションにとって特に重要です。
エンコードなしのバイナリ転送 — バイナリデータがbase64でエンコードされる(サイズが33%増加する)application/x-www-form-urlencodedとは異なり、multipart/form-dataはファイルを元のバイナリ形式で送信します。これはサイズと速度の両方でより効率的です。10MBを超える大きなファイルの場合、その差は重要になります。同じファイルでのURLエンコードされたリクエストよりも、マルチパートリクエストの方が30%小さくなります。
任意の構造 — マルチパートは異なるタイプのフィールドを任意の順序で組み合わせることができます。フォームには、テキストフィールド、複数のファイル、JSONデータ、非表示フィールドを同時に含めることができます。各パートは独自のContent-Typeを持ち、テキストデータとバイナリデータを混在させることができます。比較すると、base64エンコーディングはサイズが33%増加しますが、マルチパートはサービスヘッダーに約5〜15%しか追加しません。
HTTP Archive, 2025の調査によると、Web上のファイルアップロードの94%のケースでmultipart/form-dataが使用されています。代替手段は、JSONでのbase64(4%)とWebSocketを介した直接転送(2%)です。JSONでのbase64は、他のすべてのデータもJSONであるAPIには便利ですが、大きなファイルには非効率的です。WebSocketはリアルタイムデータに適していますが、すべてのHTTPインフラストラクチャでサポートされているわけではありません。マルチパートは、そのシンプルさと効率性により、ファイルアップロードの標準であり続けています。
モバイルアプリケーションでは、Multipart Uploadを使用してユーザーデバイスからメディアコンテンツ(ギャラリーの写真、カメラ撮影、音声録音、ドキュメントファイル)を送信します。Androidでは、標準的なアプローチはMultipartBody.Builderを使用したOkHttpで、マルチパートリクエストを簡単に作成できます。Retrofitも@Multipartおよび@Partアノテーションを介してマルチパートをサポートしています。開発者が各パートのデータタイプを指定すると、HTTPクライアントが自動的に正しいヘッダーを生成します。
iOSでは、同じタスクはカスタムHTTPBodyStreamを使用したURLSessionまたはAlamofireとmultipartFormDataを使用して解決されます。Alamofireは、マルチパートリクエストを送信するための便利なupload(multipartFormData:)メソッドを提供します。両方のプラットフォームで、アップロードするファイルのサイズを考慮することが重要です。大きなファイル(10〜20MB以上)の場合は、アプリケーションが最小化されても終了しないように、バックグラウンドアップロードを使用することをお勧めします。AndroidではDownloadManagerまたはWorkManagerを介して、iOSではバックグラウンド構成のURLSessionを介して行われます。
モバイルアプリケーションでファイルをアップロードする際は、ネットワーク状態を考慮する必要があります。AndroidのConnectivity Managerは、Wi-Fiまたはモバイルデータが利用可能かどうかを判断し、アップロードに最適なタイミングを選択するのに役立ちます。動画などの大きなファイルの場合は、ユーザーのモバイルデータを消費しないように、Wi-Fiに接続するまでアップロードを延期することをお勧めします。AndroidのWorkManagerでは、NetworkType.UNMETEREDを介してこのような制約を設定できます。
Multipart Uploadを介してファイルを送信する前に、モバイルアプリケーションは多くの場合、画像を圧縮およびリサイズします。JPEG圧縮(品質85%)は、画面表示で目立った品質低下なしにファイルサイズを3〜5倍削減します。長辺を1920pxにリサイズすると、さらにサイズが削減されます。AndroidではBitmap.compress()を使用し、iOSでは圧縮パラメータ0.85のUIImageJPEGRepresentationを使用します。このような最適化により、アップロードが高速化され、モバイルデータが節約されます。
Multipart Uploadで最も一般的なエラーは、サーバーのリクエストサイズ制限を超えることです。デフォルトでは、Nginxはリクエストボディサイズを1MB(client_max_body_size)、Tomcatは2MB(maxSwallowSize)に制限しています。開発者がこれらの制限を増やさないと、サーバーは413 Request Entity Too Largeエラーを返します。解決策は、サーバーで最大アップロードサイズを明示的に設定し、ファイルが許可されたサイズを超える場合にクライアントに警告を表示することです。
2番目の問題は、ボディストリーミング中のマルチパートリクエストの誤った処理です。一部のサーバーは、解析する前にマルチパートリクエスト全体をメモリにロードしようとし、大きなファイルでOutOfMemoryErrorを引き起こします。最新のサーバー(Nginx、Spring Boot、Ktor)は、各パートが到着時に処理されるストリーミングマルチパート解析をサポートしています。開発者は、サーバーがマルチパートリクエストのストリーミング処理用に設定されていることを確認する必要があります。
3番目の問題カテゴリは、大きなファイルアップロード時のタイムアウトです。HTTPクライアントにはreadTimeoutとconnectTimeoutの設定があり、50〜100MBを超えるファイルの長時間のアップロード中にトリガーされる可能性があります。解決策は、アップロードエンドポイントのタイムアウトを増やすか、マルチパート内でチャンク転送エンコーディングを使用することです。モバイルデバイスでは、アップロードの中断を処理し、接続が切れた場合の再開(resume)を実装することも重要です。
ファイルアップロード(マルチパート経由)は、Webアプリケーションの中で最も脆弱なエンドポイントの1つです。攻撃者は、実行可能スクリプトをimage.jpgに名前を変更してアップロードする可能性があります。サーバーは、アップロードされたファイルのMIMEタイプを拡張子ではなく内容(マジックバイト)で確認し、許可するタイプを制限し、ウイルス対策ソフトでファイルをスキャンする必要があります。アップロードされたファイルはWebサーバーのdocument-rootの外部に保存し、アクセス権限の確認を行う個別のコントローラーを介して提供することをお勧めします。
よくある質問
multipart/form-dataは各フォームフィールドを独自のヘッダーを持つ個別のブロックとして送信し、エンコードなしでバイナリファイルをサポートします。application/x-www-form-urlencodedはすべてのデータをURI互換の文字列(キー=値&キー2=値2)にエンコードし、ファイルを直接サポートしません — base64でエンコードする必要があります。
HTTPプロトコルはマルチパートリクエストのサイズを制限しませんが、実際には制限はサーバーによって設定されます。Nginxはデフォルトで1MB、Apacheは2MB、Spring Bootは1MBに制限します。大きなファイルをアップロードするには、client_max_body_size(Nginx)またはspring.servlet.multipart.max-file-size(Spring Boot)を希望の値(例:100MB)に設定してください。
はい、multipart/form-dataは1つのリクエストで複数のファイルをサポートします。各ファイルは独自のContent-DispositionとContent-Typeを持つ個別のパートとして送信されます。HTMLフォームはinput type="file"にmultiple属性を使用します。OkHttpでは各ファイルに対してaddFormDataPartが呼び出され、Alamofireでは各ファイルに対してappendが呼び出されます。
Boundaryは、複合リクエストのパートを区切り、サーバーが1つのパートの終わりと別のパートの始まりを判断できるようにする一意の文字列です。クライアントによって生成され、Content-Typeヘッダーで指定されます。boundaryがないと、サーバーはマルチコンポーネントリクエストを個別のフィールドとファイルに解析できません。
ファイルの拡張子やリクエストのContent-Typeを信頼しないでください。攻撃者はこれらを偽装する可能性があります。マジックバイト(ファイルの最初の数バイト)を介してMIMEタイプを確認してください。JavaのApache Tika、C/C++のlibmagic、Linuxのfileコマンド、またはフレームワークの組み込みツール(JavaのFiles.probeContentType()、Pythonのmimetypes)を使用します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。