はじめに
APIサーバを設計する際に、データの送信方法、特にファイルの送信方式で悩むことがある。 ここでは、HTTPのPOSTでデータを送信する時のパターンを整理してみた。
送りたいデータ: なし
言うまでもないが、この場合は単純にPOSTのリクエストを送信すればよい。
curlの実行例は以下の通りで、明示的にオプション-X
または--request
でPOSTであることを指定して実行する。
$ curl \ --request POST \ http://localhost:8080/
この時のHTTPリクエストは次のようになる。
POST / HTTP/1.1 Host: localhost:8080 User-Agent: curl/7.81.0 Accept: */*
送りたいデータ: JSON
普通にAPIを設計するならば、JSONの送受信となる。 curlコマンドの実行例を次に示す。
$ curl \ --header "Content-Type: application/json" \ --data '{"name":"Jaybanuan", "age":10, "favorite": "チョコ棒"}' \ http://localhost:8080/
この時のHTTPリクエストは次のようになる。
POST / HTTP/1.1 Host: localhost:8080 User-Agent: curl/7.81.0 Accept: */* Content-Type: application/json Content-Length: 58 {"name":"Jaybanuan", "age":10, "favorite": "チョコ棒"}
送りたいデータ: YAML
JSONの場合とおおよそ同じやり方で送信できると思うが、メディアタイプapplication/yaml
がまだ正式に登録されていない。
なのでContent-Type
は、ドラフトであることを承知でapplication/yaml
を使うか、適当にアプリ固有のものを定義して使うことになる。
YAMLはJSONと互換があるので、現時点ではYAMLを直接転送するよりは、YAMLをJSONに変換して、それをContent-Type: application/json
で送信するのが無難かと思う。
送りたいデータ: Key-Value
送りたいデータが単純なKey-Valueの場合は、HTTPの仕様に頼って送るのも一つの手である。 メリットは、クライアント側としてはcurlコマンド等で送りやすいことと、サーバ側としてはWebアプリのフレームワークがある程度処理を肩代わりしてくれること。 デメリットは、利用者が通信プロトコル(HTTP)をある程度理解する必要があること。
(1) Content-Type: application/x-www-form-urlencoded
を利用
この場合は、Key-Valueの個々のデータを&
で連結したものが、HTTPボディとして送信される。
curlコマンドでは、以下のオプションを利用することでKey-Valueのデータを送付できる。
-d
または--data
--data-urlencode
--data-raw
これらのオプションの利用時は、自動的に以下が設定される。
- HTTPメソッドはPOSTに設定
- HTTPヘッダの
Content-Type
はapplication/x-www-form-urlencoded
に設定
curlコマンドの実行例を次に示す。
$ curl \ --data name=jaybanuan \ --data-raw age=10 \ --data-urlencode "favorite=チョコ棒" \ http://localhost:8080/
この時のHTTPリクエストは次のようになる。
POST / HTTP/1.1 Host: localhost:8080 User-Agent: curl/7.81.0 Accept: */* Content-Length: 67 Content-Type: application/x-www-form-urlencoded name=jaybanuan&age=10&favorite=%E3%83%81%E3%83%A7%E3%82%B3%E6%A3%92
(2) Content-Type: multipart/form-data
を利用
この場合は、Key-Valueの個々のデータは、マルチパートとして送信される。 curlコマンドでは、以下のオプションを利用することでKey-Valueのデータを送付できる。
-F
または--form
これらのオプションの利用時は、自動的に以下が設定される。
- HTTPメソッドはPOSTに設定
- HTTPヘッダの
Content-Type
はmultipart/form-data
に設定
curlコマンドの実行例を次に示す。
$ curl \ --form name=jaybanuan \ --form age=10 \ --form "favorite=チョコ棒" \ http://localhost:8080/
この時のHTTPリクエストは次のようになる。
POST / HTTP/1.1 Host: localhost:8080 User-Agent: curl/7.81.0 Accept: */* Content-Length: 351 Content-Type: multipart/form-data; boundary=------------------------e297819c8d0c28ba --------------------------e297819c8d0c28ba Content-Disposition: form-data; name="name" jaybanuan --------------------------e297819c8d0c28ba Content-Disposition: form-data; name="age" 10 --------------------------e297819c8d0c28ba Content-Disposition: form-data; name="favorite" チョコ棒 --------------------------e297819c8d0c28ba--
application/x-www-form-urlencoded
の場合と比べるとHTTPボディが重くなっている。
しかし、Key-Valueの送信に加えて、後述のようにファイルも送信する場合は、こちらを利用する必要がある。
送りたいデータ: ファイル
ここで、以下の内容の2つのファイルをサーバに送りたいとする。
1つ目はgreeting.txt
というファイル名のテキストファイルで、内容は次のとおり。
Hello, World!
2つ目はgreeting.json
というファイル名のJSONで、内容は次のとおり。
{ "greeting": "Hello, World!" }
(1) Content-Type: multipart/form-data
を利用
HTTPのマルチパートにより、ファイルを転送する。 HTTPでファイル送信を行う際の一般的な方法であり、広く使われている。 curlコマンドの実行例を次に示す。
$ curl \ --form greeting-en=@greeting.txt \ --form "greeting-json=@greeting.json;type=application/json" \ http://localhost:8080/
各パートのContent-Type
を指定したい場合は、ファイル名の後ろにtype
で指定する。
また、各パートにその他のヘッダを付与したい場合はheaders
で指定する。
この時のHTTPリクエストは次のようになる。
POST / HTTP/1.1 Host: localhost:8080 User-Agent: curl/7.81.0 Accept: */* Content-Length: 405 Content-Type: multipart/form-data; boundary=------------------------7aa5ee307312820c --------------------------7aa5ee307312820c Content-Disposition: form-data; name="greeting-en"; filename="greeting.txt" Content-Type: text/plain Hello, World! --------------------------7aa5ee307312820c Content-Disposition: form-data; name="greeting-json"; filename="greeting.json" Content-Type: application/json { "greeting": "Hello, World!" } --------------------------7aa5ee307312820c--
この方式のメリットは、大抵のHTTPクライアントツールやWebアプリのフレームワークはマルチパートをサポートしているため、それを活用することで自力での実装部分を減らせること。 一方でデメリットは、HTTPの都合が色濃く出るので、JSONベースのAPIでのファイルアップロードに利用すると、統一感が出ないこと。
(2) ファイルをJSONに詰め込んで送信
ファイル送信用のJSONベースのAPIを設計して、ファイル名やファイルの内容をJSONに詰め込んで送信する。 以下は簡素なJSONベースのファイル送信APIの例。 この例では、ファイルの内容はJSONの文字列として扱えるようにBASE64でエンコードしている。
[ { "filename": "greeting.txt", "content": "SGVsbG8sIFdvcmxkIQ==" }, { "filename": "greeting.json", "content": "ewogICAgImdyZWV0aW5nIjogIkhlbGxvLCBXb3JsZCEiCn0=" } ]
この時のHTTPリクエストは次のようになる。
POST / HTTP/1.1 Host: localhost:8080 User-Agent: curl/7.81.0 Accept: */* Content-Type: application/json Content-Length: 204 [ { "filename": "greeting.txt", "content": "SGVsbG8sIFdvcmxkIQ==" }, { "filename": "greeting.json", "content": "ewogICAgImdyZWV0aW5nIjogIkhlbGxvLCBXb3JsZCEiCn0=" }]
上記のcurlの実行例では、オプション--data
を利用しているので、JSONから改行がなくなっている。
JSONの場合は改行に依存しないので問題ないが、余計な変換をしたくない場合はオプション--data-raw
を利用すればよい。
マルチパートの時の裏返しになるが、この方式のメリットは、JSONベースのAPIでの実装になり統一感が出ること。 一方でデメリットは、サーバ側ではAPIの設計から実装まで自力で行う必要があることと、クライアント側ではJSONの組み立てが面倒でcurlコマンドで手軽にファイル送信ができないこと。