nginx1.9.3 HTTP/2 パッチを読むメモ(day1

patch v2が公開されました、すでに内容は古くなっています(2015/08/16)



前回の「nginx1.9.3 HTTP/2 パッチを試す」に引き続き、nginxのhttp/2パッチを読んでみる。


ただし、nginx初心者なのでnginx的な部分については全然理解していない...基本的には自分用メモなので内容に関しては間違ってる部分も多いと思う


今回はフレームの受信周りの全体の流れをざっと眺めた。ヘッダ処理らへんはチョット複雑なので次の機会に整理したい。

ファイル

Nginxのhttp2関連のファイルは以下のとおりである。コレ以外にもパッチが当たるファイルもある。

./src/http/v2/ngx_http_v2.c
./src/http/v2/ngx_http_v2_module.c
./src/http/v2/ngx_http_v2_filter_module.c
./src/http/v2/ngx_http_v2_huff_encode.c
./src/http/v2/ngx_http_v2_huff_decode.c
./src/http/v2/ngx_http_v2_table.c


大体こんな感じ

ファイル
ngx_http_v2.c http2の接続を受けて、フレームを解釈したり
ngx_http_v2_filter_module.c フィルター。HTTPレスポンスをフレームに落としこむ
ngx_http_v2_module.c モジュール定義。いつもの
ngx_http_v2_huff_decode.c HPACKのハフマンコードのデコード
ngx_http_v2_huff_encode.c 中身は空っぽ
ngx_http_v2_table.c HPACKのテーブル定義、及び処理

ngx_http_v2.c

ALPNでhttp2のネゴシエーションが終了すると、それ以降データを取り出してフレームとして処理していく。
ngx_http_v2_state_で始まる関数があり、順々に呼ばれていく。


処理に必要なデータがない場合は、ngx_http_v2_state_saveに移り、状態を保存し、現在の関数をハンドラに登録して抜ける。
プロトコル的にエラーが生じた場合は、ngx_http_v2_connection_errorに移る

(青矢印はハンドラの登録、グレー矢印は関数コール、グレー破線矢印はTODO部分、)
(ngx_http_v2_state_skip_paddedは図の都合上省略)

ngx_http_v2_init

接続を受け付けて、諸々の処理を行う。

  • バッファプールの確保
  • 送信windowと受信windowの初期化
  • サーバーコネクションプリフェイスの送信
  • WINDOW_UPDATEフレームの送信
  • 各キューの初期化
  • ハンドラの登録
ngx_http_v2_state_preface
  • クライアントコネクションプリフェイスの確認("PRI * HTTP/2.0\r\n")
  • 問題なければ ngx_http_v2_state_preface_end へ
ngx_http_v2_state_preface_end
  • クライアントコネクションプリフェイスの確認("\r\nSM\r\n\r\n")
  • 問題なければ ngx_http_v2_state_head へ
ngx_http_v2_state_head
  • フレームのtype,length,flagを取り出す
  • 各情報とともに、typeに対応する関数を呼び出す
    • 未知のフレームの場合はそのままngx_http_v2_state_skipへ

ngx_http_v2_state_data

DATAフレームのフラグ周り、受信window周りの処理。実際にデータの取り出しは行わない

  • paddingフラグがアレば、padding長を計算
  • コネクション・ストリームレベルの受信windowの確認と、減算
    • 受信window以上に送られてきていたらエラー
    • 現在の受信windowがNGX_HTTP_V2_MAX_WINDOWの4分の1以下だったら、NGX_HTTP_V2_MAX_WINDOWまでwindow_update
  • 該当streamの状態を確認。
    • half-closeだったらエラー
  • 問題なければ ngx_http_v2_state_read_data へ

ngx_http_v2_state_read_data

実際にrequest bodyを取り出す。

  • 保存してある、そのストリームのhttp requestを取り出す
  • データを読み出すためのバッファなどの確保
  • request bodyのサイズ確認
    • 上限を超えてないか
    • dataフレームの長さと、content_lengthが一致しているか
  • データを一次領域に書き込む
  • end_streamフラグの処理
  • 残りのpaddingを実際に読み飛ばす
  • ngx_http_v2_state_completeへ

ngx_http_v2_state_headers

HEADERSフレームのエラーチェックと、priorityの処理。
実際にheader blockの処理はしない

  • 諸々確認
    • フレーム長の確認
    • padding長の確認
    • ストリームidが0でないことの確認
    • ストリーム並列数の確認
  • 内部的にストリームの作成
  • priorityの処理
    • フラグがアレば、weightとdependencyを取り出してセット
    • フラグがなければ、デフォルト値をセット
  • ngx_http_v2_state_header_block へ

ngx_http_v2_state_header_block

Header Block Fragment の処理。一度に一つのヘッダが処理される
ヘッダーフィールド表現の識別する

  • 先頭1バイトを取り出す
    • 各ヘッダーフィールド表現を識別しする
      • indexed header field
      • literal header field with incremental indexing
      • dynamic table size update
      • literal header field never indexed
      • literal header field without indexing
    • index値があれば取り出す、header名・header値をparseする必要があるか確認する
  • Indexed Header Fieldであれば、ngx_http_v2_state_process_header へ
  • dynamic table size update であれば、hpackの動的テーブルサイズを伸長し、ngx_http_v2_state_header_complete へ
  • ngx_http_v2_state_header_len へ

ngx_http_v2_state_header_len

headerを処理するための長さを決定する。

  • ハフマン符号化されているか確認
    • ngx_http_v2_state_header_huffへ
  • ヘッダサイズが上限を超えてないか確認
  • ngx_http_v2_state_header_raw へ

ngx_http_v2_state_header_raw

多分、データの長さの分だけバッファのポジションを変更

ngx_http_v2_state_header_huff

ハフマン符号のデコード

  • ハフマン符号のデコード
  • ngx_http_v2_state_process_headerへ

ngx_http_v2_state_process_header

ヘッダ一つを取り出す

  • ヘッダ名をパースする必要があれば ngx_http_v2_state_header_len へ
  • psedoヘッダの処理
  • headerの名前、値を取り出す
  • リクエストのヘッダーリストに取り出したヘッダを加える

ngx_http_v2_state_header_complete

ヘッダをパースし終わった時の処理。
まだパースするヘッダがアレば、ハンドラにngx_http_v2_state_header_blockを登録して抜ける

取り出したHTTPリクエストヘッダをngx_http_process_requestに渡して、ヘッダーの処理を終わる

  • lengthがまだ残って入れば、handleにngx_http_v2_state_header_blockを登録して抜ける
  • NGX_HTTP_V2_END_HEADERS_FLAGが立ってれば
    • ngx_http_v2_run_requestでHTTPリクエストをngx_http_process_requestに渡す
  • paddingがあれば読み飛ばして
  • ngx_http_v2_state_completeへ

ngx_http_v2_state_priority

PRIORITYフレームの処理

  • フレームのlengthがた正しいか確認
  • WeightとDependencyを取り出して、そのストリームに適応する。
  • ngx_http_v2_state_header_completeへ

ngx_http_v2_state_window_update

WINDOWS_UPDATEフレームの処理
ストリーム・コネクションレベルの送信windowサイズを更新し、waitingになっていたストリームの状態を更新する。

  • フレームのlengthがた正しいか確認
  • ストリームidが指定されていれば
    • 未知のストリームだったらngx_http_v2_state_completeへ
    • 送信windowサイズを増やす
    • ngx_http_v2_state_complete へ
  • コネクションレベルの送信windowサイズを増やす
    • キューのwaitingだったストリームの更新
  • ngx_http_v2_state_completeへ

ngx_http_v2_state_ping

PINGフレームの処理

  • フレームのlengthがた正しいか確認
  • ACKフラグ付きだったら、ngx_http_v2_state_skipへ
  • PINGに対する応答フレームを作成し、キューに詰む
  • ngx_http_v2_state_completeへ

ngx_http_v2_state_goaway

GOAWAYフレームの処理。
ログに吐いてngx_http_v2_state_skipへ

  • フレームのlengthがた正しいか確認
  • 最後のストリームidとエラーコードを取り出して、ログに履く
  • ngx_http_v2_state_skip へ

ngx_http_v2_state_rst_stream

RST_STREAMフレームの処理
中身は実装されていない
ngx_http_v2_state_skip へ

ngx_http_v2_state_push_promise

PUSH_PROMISEフレームの処理
中身は実装されていない
ngx_http_v2_state_skip へ

ngx_http_v2_state_complete

フレーム処理の終了。

  • stateの初期化
  • ハンドラにngx_http_v2_state_headを登録して抜ける

ngx_http_v2_state_skip_padded

padddingの分lengthを変更し、ngx_http_v2_state_skipへ

ngx_http_v2_state_skip

フレームの処理をskipしてポジションを進める

ngx_http_v2_state_save

フレームを処理するのに必要なデータがまだないとき、状態を保存しハンドラに現在の関数を登録し抜ける