Dev Study
← Rust で小さなブラウザを作る コース

7. リクエストの形 — 行末は CRLF、Host は必須、Connection: close で終わりが分かる

のリクエストは、ただのテキストです。1 行目が (メソッド・・バージョンを空白で区切る)、続けての行、空行、。 には本文がありません。 9112 の では HTTP-message = start-line *( field-line CRLF ) CRLF [ message-body ] です。

行末は CRLF(\r\n)です。 だけで送る実装も見かけますが、送る側は CRLF と決まっています(受ける側に LF 単独を許す余地があるのは次のレッスン)。は HTTP/1.1 では必須で、RFC 9112 §3.2 は「クライアントは全ての HTTP/1.1 リクエストで Host を送らなければならない」と定めています。1 つの IP に複数のサイトが同居する(名前ベースのバーチャル)ため、どのサイト宛かをここで伝えます。

参照実装の build_request は、request-target に前の章の Url::request_target()( と ?)、Host に host_header()(既定の 80 以外なら : 付き)を入れ、 を付けます。Connection: close は「この 1 往復で接続を閉じる」というです。これを付けると、受け取る側は「サーバーが接続を閉じるまで読む」だけで本文の終わりが分かります。付けないと Keep-Alive で接続が残り、 が返ってきません。

下のコードは通信をしません。リクエストの文字列を組み立てて CRLF を見える形で表示し、続けてサーバーから来たと仮定した文字列を分解します(分解の中身は次のレッスン)。

本物のブラウザでは: の では、HttpNetworkTransaction が HttpStreamFactory に接続を頼み、は TransportClientSocketPool から借り、空きが無ければ TransportConnectJob が新しくつなぎます。は HostResolverImpl です。要求行とヘッダーを文字列にして送るのは HttpBasicStream と HttpStreamParser の仕事で、この回の build_request() にあたります。使い終わったソケットは「再利用可能なら」プールに戻されます(net/docs/life-of-a-url-request.md)。Firefox は nsHttpChannel → nsHttpTransaction → nsHttpConnectionMgr → nsSocketTransport の順です。この実装に無いのは接続の再利用と並列取得で、1 回つないで 1 回送って閉じます。

開発のどこに活かすか: curl -v で見えるリクエストの全行が、この 5 行で説明がつきます。Host を付け忘れて 400 が返る、Connection の扱いでクライアントが固まる、といった不具合は「 はテキストで、行末と空行が区切り」という事実から追えます。社内のプロキシやロードバランサのログを読むときも、request-line と field-line の名前で話せると早いです。

点線の付いた語はタップすると説明が出ます。用語集を開く →

① リクエストは 3 つの部分 + 空行

GET /index.html?a=1 HTTP/1.1 を送るときのバイト列
request-lineGET SP /index.html?a=1 SP HTTP/1.1 CRLF
field-line × nHost: example.com CRLF / Connection: close CRLF …
空行CRLF だけ。ここでヘッダーが終わる
message-bodyGET には無い
  • ・SP は空白 1 つ、CRLF は \r\n。RFC 9112 §2.1 の HTTP-message の形そのまま

空行がヘッダーの終わりの印なので、ヘッダーの値に改行は入れられません。

▶ サンプルコードを実行してみよう(自由に書き換えてOK)

Rust

手元で動かす(配布物)

図やコードに出てくる用語(タップで説明)

サンプルコードで身につける(3本) →

学んだ概念を、素朴な例から実務寄りの例まで3つの具体例で確認します。

公式ドキュメントで詳しく ↗