HTTPリクエストの構造
同じ接続先へcurlで要求し、HostとAcceptを変更して応答を比較します。最後に日本語を含むPOST本文を送り、実際の要求バイト列でヘッダー終端と本文の長さを確認します。
説明
HTTP/1.1のメッセージは、開始行、ヘッダー、空行、必要な本文の順で構成されます。要求は何を求めるかを伝え、応答は状態コードと本文の形式・内容を返します。
同じ接続先へcurlで要求し、HostとAcceptを変更して応答を比較します。最後に日本語を含むPOST本文を送り、実際の要求バイト列でヘッダー終端と本文の長さを確認します。
1HTMLを要求して応答を表示
接続先は127.0.0.1:18080、要求するホストはa.testです。AcceptでHTMLを希望し、-iで応答ヘッダーも表示します。
curl -sS -i進行表示を省きエラーとヘッダーを表示 -H "Host: a.test"要求するホスト -H "Accept: text/html"希望する表現 http://127.0.0.1:18080/representation実際の接続先と要求パスHTTP/1.1 200 OKが状態行です。Content-Typeはtext/html、Content-Lengthは本文のバイト数で、空行の後が<p>A: 120</p>という本文です。Vary: Acceptは表現の選択にAcceptが使われることを示します。
2受信した要求の実バイト列
配信側で記録した要求を、改行が見える表現で開きます。
python3 inspect_http.py last最後に受信した要求バイト列先頭はGET /representation HTTP/1.1です。ヘッダー各行は\r\nで終わり、\r\n\r\nがヘッダーと本文の区切りです。このGET要求には本文がありません。User-Agentは使用したcurlの版を示します。
3同じ接続先でHostだけを変更
IPアドレスとポートは同じまま、Hostをb.testへ変えます。
curl -sS -i -H "Host: b.test"同じIPへ別のホストを要求 -H "Accept: text/html" http://127.0.0.1:18080/representation接続先は同じ本文がB: 120へ変わります。IPアドレス・ポートは配送先、Hostはその接続先で要求するホスト名を伝える情報です。同じ接続先が複数のホストを受け持つ構成に使われます。
4同じ資源をJSONで要求
Hostをa.testへ戻し、希望する表現をJSONへ変更します。
curl -sS -i -H "Host: a.test" -H "Accept: application/json"JSONを希望 http://127.0.0.1:18080/representation同じ資源を要求同じパスでもJSONが返り、Content-Typeはapplication/jsonになります。Acceptは要求側の希望、Content-Typeは実際の本文の形式です。両者は別のヘッダーです。
5複数の表現に優先度を付ける
JSONとHTMLを両方許容し、qでHTMLの優先度を高くします。
curl -sS -i -H "Host: a.test" -H "Accept: application/json;q=0.1, text/html;q=0.9"受け入れる表現と優先度 http://127.0.0.1:18080/representationこの配信側は対応する二形式から優先度の高いHTMLを選びます。qは0〜1で、0は受け入れ不可です。Acceptに書いた形式が必ず返るという約束ではなく、配信側が提供できる表現も選択へ影響します。
6提供できない形式を要求
この資源が用意していないPNGだけを要求します。
curl -sS -i -H "Host: a.test" -H "Accept: image/png"未提供の形式だけを要求 http://127.0.0.1:18080/representationこの配信側は406 Not Acceptableを返します。状態コードは要求の処理結果で、説明本文のContent-Typeはtext/plainです。要求の希望形式と、失敗応答の説明形式も同じとは限りません。
7同じ本文でも型の宣言は異なる
同じ<b>hello</b>という本文を、text/plainとtext/htmlの二種類の宣言で取得します。
curl -sS -i http://127.0.0.1:18080/same?type=plainプレーンテキストとして宣言
curl -sS -i http://127.0.0.1:18080/same?type=htmlHTMLとして宣言本文のバイト列とContent-Lengthは同じですが、Content-Typeが異なります。curlはどちらも文字列として表示します。ブラウザで文書として扱う場合、宣言されたメディア型は本文をどう解釈するかに関わります。
8日本語を含むPOST本文
JSONを本文にしてPOSTします。このecho資源は受け取った本文をそのまま返します。
curl -sS -i -H "Host: a.test" -H "Content-Type: application/json"送る本文をJSONとして宣言 --data-binary本文のバイトを送る '{"label":"あ"}' http://127.0.0.1:18080/echo受け取った本文を返す資源--data-binaryによりPOSTとなり、JSONの本文が送られます。要求のContent-Typeは送る本文の形式です。応答側にもContent-TypeとContent-Lengthがあり、要求と応答それぞれの本文を説明します。
9POSTのヘッダー終端と本文長
受信したPOSTのバイト列を開き、空行より後の実バイト数を確認します。
python3 inspect_http.py lastPOSTの開始行・ヘッダー・本文を照合要求行はPOST /echo HTTP/1.1です。Content-Lengthは本文のバイト数で、"あ"は\xe3\x81\x82の3バイトです。ヘッダー終端のCRLF二組は本文長に含まれません。表示上の文字数ではなく、符号化後のバイト数で区切ります。
まとめ
要求行・状態行、ヘッダー、空行、本文を実バイト列で対応させます。
Hostは要求先のホスト、Acceptは希望する表現、Content-Typeは実際の本文形式です。
Content-Lengthは符号化後の本文バイト数で、ヘッダーや区切りの空行は含みません。
操作履歴と解説を振り返れます。
シェル / curl・HTTP/1.1
curl -sS -i -H "Host: a.test" -H "Accept: text/html" http://127.0.0.1:18080/representationHTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 14 Vary: Accept <p>A: 120</p>
python3 inspect_http.py lastb'GET /representation HTTP/1.1\r\nHost: a.test\r\nUser-Agent: curl/8.7.1\r\nAccept: text/html\r\n\r\n' header bytes: 89 body bytes: 0 CRLF separator: 85 .. 88
curl -sS -i -H "Host: b.test" -H "Accept: text/html" http://127.0.0.1:18080/representationHTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 14 Vary: Accept <p>B: 120</p>
curl -sS -i -H "Host: a.test" -H "Accept: application/json" http://127.0.0.1:18080/representationHTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 24
Vary: Accept
{"site":"A","price":120}curl -sS -i -H "Host: a.test" -H "Accept: application/json;q=0.1, text/html;q=0.9" http://127.0.0.1:18080/representationHTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 14 Vary: Accept <p>A: 120</p>
curl -sS -i -H "Host: a.test" -H "Accept: image/png" http://127.0.0.1:18080/representationHTTP/1.1 406 Not Acceptable Content-Type: text/plain; charset=utf-8 Content-Length: 15 Vary: Accept not acceptable
curl -sS -i http://127.0.0.1:18080/same?type=plain
curl -sS -i http://127.0.0.1:18080/same?type=htmlHTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Content-Length: 13 <b>hello</b> HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 13 <b>hello</b>
curl -sS -i -H "Host: a.test" -H "Content-Type: application/json" --data-binary '{"label":"あ"}' http://127.0.0.1:18080/echoHTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 15
{"label":"あ"}python3 inspect_http.py lastb'POST /echo HTTP/1.1\r\nHost: a.test\r\nUser-Agent: curl/8.7.1\r\nAccept: */*\r\nContent-Type: application/json\r\nContent-Length: 15\r\n\r\n{"label":"\xe3\x81\x82"}'
header bytes: 126 body bytes: 15
CRLF separator: 122 .. 125演習完了