リクエストの構造
メソッド、URL、ヘッダー、ボディ、そしてサーバーが実際に受け取るもの。
前のレッスンでは、ブラウザに URL を入力してリクエストを送りました。残りはすべて ブラウザが埋めてくれました。このレッスンでは、リクエストを分解して各部分が何なのかを確かめます。これから書くテストは どれも、これらの部分のいくつかを意図して設定し、サーバーの反応を確かめるものだからです。
HTTP リクエストは 4 つの部分からなります。
- メソッド:どんな種類の操作をしたいか
- URL:どのサーバーの、どの対象に対して操作するか
- ヘッダー:リクエストについての追加情報
- ボディ(任意):送るデータ
通信路の上のリクエストの姿
HTTP/1.1 はテキストのプロトコルです。暗号化される前のリクエストは、数行のプレーンテキストにすぎません。次は curl が https://api.testingapis.com/api/echo?color=red に送るリクエストです。curl を -v(詳細表示)付きで実行すれば 自分でも見られます。> で始まる行が送信された内容です。
GET /api/echo?color=red HTTP/1.1
Host: api.testingapis.com
User-Agent: curl/8.18.0
Accept: */*1 行目はリクエスト行で、メソッド、クエリ文字列付きのパス、プロトコルのバージョンが並びます。その後の各行はヘッダーで、 名前と値がコロンで区切られています。空行でヘッダーが終わり、ボディがあればその空行の後に続きます。新しい HTTP(HTTP/2 と HTTP/3)は同じ情報をバイナリ形式で送りますが、部分の構成は同じで、どのツールも今でもこのテキストの形で見せてくれます。
サーバーが受け取ったものを見る
リクエストを知るいちばん簡単な方法は、受け取ったものを教えてくれるサーバーに送ってみることです。/api/echo はまさにそれをします。どのメソッドでも受け付け、リクエストの説明を返します。次を送ってみてください。
/api/echo?color=red&size=10curl から送ったときの返事を、興味深いフィールドだけに絞ったものが次です。このページから送ると、ヘッダーはブラウザのものに なります。
{
"method": "GET",
"url": "http://api.testingapis.com/api/echo?color=red&size=10",
"path": "/api/echo",
"queryString": "?color=red&size=10",
"query": { "color": "red", "size": "10" },
"headers": {
"Accept": "*/*",
"Host": "api.testingapis.com",
"User-Agent": "curl/8.18.0",
"Accept-Encoding": "gzip, br",
"Cf-Ipcountry": "IN",
"X-Forwarded-For": "203.0.113.7, 172.71.198.178, 10.30.107.46",
"X-Forwarded-Proto": "https"
},
"contentType": null,
"contentLength": 0,
"body": null
}ここで 2 つのことをよく見てください。サーバーは URL を https:// ではなく http:// として見ています。そして、あなたが送っていない Cf-Ipcountry や X-Forwarded-For のようなヘッダーを受け取っています。リクエストはアプリケーションに届く前に CDN とロードバランサーを通り、そのそれぞれが 何かを書き換えたり付け加えたりしたのです。X-Forwarded-For の最初のアドレスはあなたのものです(上のものは例です)。本番のシステムではこれがふつうで、エコーのエンドポイントが役立つ 理由でもあります。送ったつもりのものではなく、実際に届いたものを見せてくれるからです。
メソッド
メソッドは動詞です。GET はデータを求め、POST は何かを作るためにデータを送ります。PUT、PATCH、DELETE は変更や削除をします。ブラウザのアドレスバーは GET しか送れないので、それ以外には別のツールが必要です。 メソッドには HTTP メソッド という専用のレッスンがあるので、今は、同じ URL でもメソッドによって違うことができる、とだけ覚えておけば十分です。
URL
この URL を部分に分けてみましょう。
https://api.testingapis.com/api/Products?category=books&limit=5| 部分 | 値 | 意味 |
|---|---|---|
| スキーム | https | どう接続するか。https は暗号化された接続の中の HTTP。 |
| ホスト | api.testingapis.com | どのサーバーか。Host ヘッダーとしても送られる。 |
| パス | /api/Products | そのサーバー上のどのリソースか。 |
| クエリ文字列 | category=books&limit=5 | ? の後に & で区切って付ける追加のパラメーター。 |
URL にはポート(ホストの後の :8080)やフラグメント(末尾の #section)が付くこともあります。ポートは テスト環境やローカル環境でよく見かけます。フラグメントはサーバーには届かず、ブラウザが持ったままです。
パスパラメーターとクエリパラメーター
値はパスでもクエリ文字列でも運べます。/api/Products/1 の 1 はパスパラメーターで、特定の 1 つの商品を指します。ドキュメントではパスを /api/Products/{id} と書きます。/api/Products?limit=5 の limit はクエリパラメーターです。クエリパラメーターはたいてい結果の絞り込み、並べ替え、ページ分けに使われ、たいてい省略できます。 この使い分けは慣習でありルールではないので、API ごとにドキュメントを確認してください。
パスは大文字と小文字を区別することが多く、クエリパラメーターの名前もたいてい区別します。これはサーバーのフレームワーク 次第なので、新しい API では手早く試してみましょう。/api/Products の代わりに /api/products を送って、どうなるかを見ます。
パーセントエンコーディング
URL の中で役割を持つ文字があります。? はクエリ文字列の始まり、& はパラメーターの区切り、# はフラグメントの始まり、/ はパスの区切りで、スペースはそもそも使えません。これらをデータとして送るには、% とそのバイトの 16 進数の値に置き換えます。スペースは %20 に、& は %26 になります。これをパーセントエンコーディング、または URL エンコーディングと呼びます。
「red shoes」を検索し、値がそのまま a&b という文字列のタグで絞り込みたいとします。エンコードすればうまくいきます。
/api/echo?q=red%20shoes&tag=a%26b{
"queryString": "?q=red%20shoes&tag=a%26b",
"query": { "q": "red shoes", "tag": "a&b" }
}今度はアンパサンドをエンコードせずにタグを送ってみます。
/api/echo?tag=a&b{
"queryString": "?tag=a&b",
"query": { "tag": "a", "b": "" }
}サーバーには 2 つのパラメーターが見えています。値が a の tag と、b という名前の空のパラメーターです。エラーも警告もありません。リクエストは「成功」し、データは間違っています。文字列を つなげて URL を組み立てるクライアントで本当によくある種類のバグで、テストのときに検索欄へアンパサンド、プラス記号、 スラッシュ、英語以外の文字を入れてみるとよい理由でもあります。手で変換したいときは、このサイトのURL エンコーダーが使えます。
ヘッダー
ヘッダーはリクエストを説明する名前と値の組です。名前は大文字と小文字を区別しないので、content-type と Content-Type は同じ意味です。ほとんどのクライアントはいくつかのヘッダーを自動で付けます。自分で設定したり確認したりすることが多いのは 次のものです。
| ヘッダー | 意味 | 例 |
|---|---|---|
Content-Type | 送るボディの形式。 | application/json |
Accept | 受け取ってもよい形式。 | application/json |
User-Agent | リクエストを送ったクライアント。 | curl/8.18.0 |
Authorization | トークンなどの認証情報。 | Bearer abc123 |
Host | サーバー上のどのサイトか。クライアントが URL から埋める。 | api.testingapis.com |
Accept は、API が複数の形式で応えられるときに意味を持ちます。/api/formats/negotiateサインインして表示 は同じユーザーを、既定では JSON で、求めれば XML で返します。
/api/formats/negotiateヘッダーなしで受け取る JSON と XML を比べてみてください。どちらも同じ 7 つのフィールドを、それぞれの形式なりの書き方で 持っています。roles のリストは繰り返される role 要素になり、null の manager は xsi:nil の付いた空の要素になります。API が複数の形式を提供しているなら、それぞれをテストし、内容が一致すると思い込まないでください。 このレッスンを最初に書いたとき、このユーザーの XML 版には 3 つのフィールドが欠けていました。
Authorization は 認証と認可 でしっかり扱います。ひとつだけ今のうちに知っておいてください。本物のトークンをスクリーンショット、バグ報告、共有コレクション に貼り付けてはいけません。トークンはパスワードです。
ボディ
GET リクエストには通常ボディがありません。POST、PUT、PATCH はサーバーにデータを送るので、たいていボディがあります。 ボディの形式は自由で、Content-Type ヘッダーがサーバーに読み方を伝えます。今日のほとんどの API ではボディは JSON です。次は curl で小さな JSON オブジェクトをエコーのエンドポイントに送ります。
curl -X POST https://api.testingapis.com/api/echo \
-H "Content-Type: application/json" \
-d '{"title":"Dune","year":1965}'{
"method": "POST",
"path": "/api/echo",
"query": {},
"contentType": "application/json",
"contentLength": 28,
"body": "{\"title\":\"Dune\",\"year\":1965}",
"json": { "title": "Dune", "year": 1965 }
}サーバーはヘッダーを読んで application/json を確認し、28 バイトをオブジェクトとして解析しました。同じリクエストを ここから送ることもできます。
/api/echo{"title":"Dune","year":1965}Content-Type とボディが食い違うとき
今度は同じようなボディを curl で、ヘッダーを付けずに送ってみます。
curl -X POST https://api.testingapis.com/api/echo \
-d '{"title":"Dune"}'{
"contentType": "application/x-www-form-urlencoded",
"contentLength": 16,
"body": "{\"title\":\"Dune\"}",
"json": null,
"form": { "{\"title\":\"Dune\"}": "" }
}curl の -d オプションの既定値は、HTML フォームが使う形式 application/x-www-form-urlencoded です。サーバーはヘッダーを信じてボディをフォームとして読もうとし、JSON のテキスト全体を名前にしたフォームフィールドが 1 つできあがりました。エラーは返ってきません。実際のサーバーにも同じように振る舞うものは多く、415 Unsupported Media Type を返すものもあります。Content-Type がない、または間違っているのは、リクエストが「正しく見える」のに失敗するいちばんよくある原因の 1 つです。サーバーがデータを 無視するときに最初に確認すべきところです。
ヘッダーが正しくても JSON が壊れていれば、よいサーバーはそう言います。末尾にカンマがあるボディ {"title":"Dune",} に対するエコーのレスポンスの一部が次です。
"json": null,
"jsonError": "The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. LineNumber: 0 | BytePositionInLine: 16."演習
/api/echo を使って、それぞれの問いに答えてください。推測せず、毎回レスポンスを見ること。
- ブラウザのアドレスバーから
/api/echo?name=Zoë&city=São Pauloを送ってください。送る前にブラウザは URL の何を変えましたか。サーバーはそれを何にデコードしましたか。 /api/echo?id=1&id=2を送ってください。2 回出てくるパラメーターを、サーバーはどう表しますか。フレームワークによって扱いが違うので、 テストするどの API にとってもよい問いです。- JSON のボディと
Content-Type: text/plainで POST を送ってください。サーバーはそれでも JSON を解析しましたか。 - ブラウザと curl の両方から
/api/echo/user-agentを試してください。どちらを使ったかを API が気にするのはなぜでしょうか。