😸

nghttpd で TLS なしの HTTP/2(h2c)を利用する

2024/07/10に公開

nghttpd に --no-tls を指定すると TLS なしの h2c (HTTP/2 cleartext) を利用できる

/usr/sbin/nghttpd 8000 --no-tls -v -d ./web

この状態での nghttpd は HTTP/1 をサポートしないので、コネクションプリフェイス (PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n) を送信できる HTTP クライアントが必要になる。curl の場合、--http2-prior-knowledge オプションを指定する

curl -v --http2-prior-knowledge http://localhost:8000
*   Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
* h2h3 [:method: GET]
* h2h3 [:path: /]
* h2h3 [:scheme: http]
* h2h3 [:authority: localhost:8000]
* h2h3 [user-agent: curl/7.88.1]
* h2h3 [accept: */*]
* Using Stream ID: 1 (easy handle 0x55a9bf4ddce0)
> GET / HTTP/2
> Host: localhost:8000
> user-agent: curl/7.88.1
> accept: */*
> 
< HTTP/2 200 
< server: nghttpd nghttp2/1.52.0
< cache-control: max-age=3600
< date: Wed, 10 Jul 2024 04:09:08 GMT
< content-length: 6
< last-modified: Wed, 10 Jul 2024 03:26:27 GMT
< content-type: text/html
< 
hello
* Connection #0 to host localhost left intact

HTTP/1.1 GET リクエストを送信すると次のようなレスポンスが返される

curl -v --http1.1 http://localhost:8000
*   Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.88.1
> Accept: */*
> 
* Received HTTP/0.9 when not allowed
* Closing connection 0
curl: (1) Received HTTP/0.9 when not allowed

返されるレスポンスはバイナリデータの HTTP/2 フレームであり、冒頭の行には HTTP のバージョンが含まれないため、HTTP/0.9 と誤解されている

nghttpd のログを見ると最初に次のような Settings フレームが送信されている。Settings フレームはクライアントとサーバーがデータのやりとりをするための取り決めである。

[id=9] [256.282] send SETTINGS frame <length=6, flags=0x00, stream_id=0>
          (niv=1)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]

フレームの構造はヘッダーとペイロードから構成される。ヘッダーは固定長の9バイト、ペイロードは任意の長さである。ヘッダーはペイロードの長さ(3バイト)、フレームの種類(1バイト)、フラグ(1バイト)、ストリームの id (4バイト)で構成される。

SETTINGS フレームを表す値は「0x04」である。ログの記録では長さは0x06、フラグは 0x00、ストリーム id は 0x00 である。したがってヘッダーは次のようにあらわされる

0x00 0x00 0x06 # ペイロードの長さ
0x04 # フレームの種類(Settings)
0x00 # フラグ
0x00 0x00 0x00 0x00 # ストリーム id 

わかりやすいように改行で示したが、実際のデータは9バイトのバイト列である。

次にSettings フレームのペイロードを調べる。設定項目ごとに識別子(2バイト)と値(4バイト)で構成される。ペイロードの長さは6バイトなので、設定項目は1つということになる。

上記のログから SETTINGS_MAX_CONCURRENT_STREAMS の識別子は 0x03 で値は 100 (16進数で 0x64) であることがわかる。Settings フレーム全体のバイト列(15 = 9 + 6)は次のようになる。

0x00 0x00 0x06 # ペイロードの長さ
0x04 # フレームの種類(Settings)
0x00 # フラグ
0x00 0x00 0x00 0x00 # ストリーム id
0x00 0x03 # 識別子 SETTINGS_MAX_CONCURRENT_STREAMS
0x00 0x00 0x00 0x64  # 値

Discussion