Jaybanuan's Blog

どうせまた調べるハメになることをメモしていくブログ

APIサーバでのデータの送信方式の整理

はじめに

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を使うか、適当にアプリ固有のものを定義して使うことになる。

redj.hatenablog.com

YAMLJSONと互換があるので、現時点ではYAMLを直接転送するよりは、YAMLJSONに変換して、それを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-Typeapplication/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-Typemultipart/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コマンドで手軽にファイル送信ができないこと。

参考

weblabo.oscasierra.net