2023-9-19

HTTP/1.1のHeaderの仕様とNginxにおけるパーサーの実装

HTTP

Nginx

自作サーバー

前回に引き続きHTTP/1.1の仕様とNginxの実装を読んでいく。 今回はHeaderのパース周りについて。RFC9110と9112に関連する記載がある。

Headerの仕様

RFC9110ではHeader, Trailerで使われるName-Value型の形式をFieldと呼んでいる。 5章がまるまるFieldの説明になっていて、Field Name, Field Valueで利用可能な文字や細則について記載がある。 一方RFC9112の5章にもHTTP/1.1におけるFieldの記法について記載がある。 RFC9112→RFC9110という順で読むと概観→細則という流れで読めるので読みやすいかも知れない。 あまり英語が得意でなく誤訳があるかもしれないので、この記事をなにかの参考にする場合には原典を合わせて確認してほしい。

RFC9112における記載

text
Copied!
field-line   = field-name ":" OWS field-value OWS

冒頭にfield-lineの定義がある。field-nameとfield-valueを:で繋げたものをfield-lineと読んでいて、ブラウザの開発者ツールなどで見慣れた形式をしている。 OWSはOptional White Spaceの略でRFC9110の5,6,3で定義されていて0個以上の空白(スペースか水平タブ)を指す。 field-lineにおけるOWSはfield-valueではないので、冒頭と末尾の空白は除去してあげる必要がある。

逆にfield-nameとコロンの間には空白が許可されていない。もしそのようなリクエストが送られてきた場合にはサーバーは400で拒否する必要がある。

過去field-nameの前に1つ以上のスペースを挟むことで、複数行のヘッダを記載することが出来たようだがHTTP/1.1では基本的に非推奨になっている。 クライアントがそのようなヘッダを生成することは禁止されているし、サーバー側も特定の場合を覗いて400で拒否する必要がある。

RFC9110における記載

field-nameやfield-valueの詳細についてはこちらに記載がある。

text
Copied!
field-name     = token

これだけではどの文字の利用が許可されているかわからないが、細かいことはAppendix Aに大体書いてある。 tokenは1*tcharと定義されていて、tcharは以下の通り。

text
Copied!
tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
"^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA

次にfield-valueだが以下のように定義されている。直感的には空白混じりの文字列という理解。 VCHARは視認可能なUS-ASCII文字を指す。 過去US-ASCII以外の文字を仕様としてサポートしていた名残でobs-textという文字も許可されている。 サーバーはobs-textを不透明なデータとして処理するべきという記載があるが、具体的にどう扱うべきかよくわからない。

text
Copied!
field-value    = *field-content
field-content  = field-vchar
                [ 1*( SP / HTAB / field-vchar ) field-vchar ]
field-vchar    = VCHAR / obs-text
obs-text       = %x80-FF

あとはfield-valueに関する細かい細則などが記載されている。

  • 複数の値を表現する場合にはカンマ+OWSで区切る。
  • 同一のfield-nameの行が複数存在した場合は、順番を保ったまま最初の行にカンマ+OWSで結合する。
  • field-value中に出現するCRLFやNUL文字が現れた場合拒否するか、SPで置き換える。
  • 二重引用符で囲まれた文字はただのテキストとして扱う。
  • \はエスケープ文字として扱う。

Nginxの実装

ngx_http_parse_header_lineという関数がfield-lineのパースを担っている。 あまり処理内容は前回と差がない。状態を表す変数を定義して、1文字ずつ読み込んで状態を遷移させていく。

  • field-nameにアルファベット、数字、ハイフン、アンダースコアしか許可してなさそう。
  • field-valueの細かいバリデーションはこの段階ではしていなさそう。
  • field-valueが存在していないケースを考慮していそう?

雑感

まだRustでNginxライクなHeaderパーサーを作成してみたが、NginxはEAGAINを考慮してリエントラントに書かれていて偉い。

参考