(2018.6) 実装をやり直しました。だいぶスッキリ。
HTTP/2 を理解するために, 簡単に実装してみました。実用性を求めるわけではありません。
このページでは, 非TLS (平文) で接続します。
ソースコードの全部, Makefile はこちら; network · master · netsphere / my-cpp-lib · GitLab
Linux と Windows (mingw32/64) でテストしています。
出発点として, こちらを利用しました; HTTP2最速実装をmain()関数だけで簡単に説明する(非SSL編)
HTTP/2 は, 滅多矢鱈に複雑なため, main()
だけで実装するのは無理筋だと思います。ある程度関数を分けて, 処理を纏めています。
また, 現状では HTTP/1.1 サーバのほうが多いと思われるので, [2022-06] RFC 9113 (June 2022) で, HTTP/1.1 からの upgrade は廃止された (obsolete). HTTP/1.1 101 Switching Protocols
で, HTTP/2 に upgrade するようにしました。h2c
upgrade token も廃止。
標準C++の流儀のために, ソケット通信は, basic_streambuf
を拡張したものを使います; streambuf を拡張し, ソケット対応 [C++]
以下で説明するコードで実行すると, 次のようになります。はい, できました!!
決め打ちで, http://nghttp2.org/
にアクセスしています。二つ目のリソースとして, /httpbin/
を取得します。
後述しますが, HEADERS
フレームと DATA
フレームを使って, リソースを取得します.
status_code = 101: HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: h2c main:260 S payload length:0 bytes, type:SETTINGS, flags:00, stream_id:0 main:272 R payload length:18 bytes, type:SETTINGS, flags:00, stream_id:0 main:285 S payload length:0 bytes, type:SETTINGS, flags:01, stream_id:0 send_HEADERS_frame:197 S payload length:68 bytes, type:HEADERS, flags:04, stream_id:3 :authority: nghttp2.org :method: GET :path: /httpbin/ :scheme: http recv_streams:79 R payload length:198 bytes, type:HEADERS, flags:04, stream_id:1 :status: 200 date: Sat, 30 Jun 2018 12:26:14 GMT content-type: text/html last-modified: Tue, 08 May 2018 13:53:22 GMT etag: "5af1abd2-19d8" accept-ranges: bytes content-length: 6616 x-backend-header-rtt: 0.001403 server: nghttpx via: 2 nghttpx x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block x-content-type-options: nosniff recv_streams:79 R payload length:6616 bytes, type:DATA, flags:01, stream_id:1 ====DATA==== <!DOCTYPE html> <!--[if IEMobile 7 ]><html class="no-js iem7"><![endif]--> <!--[if lt IE 9]><html c====/DATA==== recv_streams:79 R payload length:0 bytes, type:SETTINGS, flags:01, stream_id:0 recv_streams:79 R payload length:83 bytes, type:HEADERS, flags:04, stream_id:3 :status: 200 date: Sat, 30 Jun 2018 12:26:14 GMT content-type: text/html; charset=utf-8 content-length: 11299 access-control-allow-origin: * access-control-allow-credentials: true x-backend-header-rtt: 0.006455 server: nghttpx via: 1.1 nghttpx x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block x-content-type-options: nosniff recv_streams:79 R payload length:11299 bytes, type:DATA, flags:01, stream_id:3 ====DATA==== <!DOCTYPE html> <html> <head> <meta http-equiv='content-type' value='text/html;charset=utf8'> <m====/DATA==== main:321 S payload length:0 bytes, type:GOAWAY, flags:00, stream_id:0
まず, main()
関数から。
main()
関数の最初から, 接続するところまで.
Http2Reader
クラスは, 共通ルーチンのページを参照ください. (2018.6) SocketStreamBuf
を使うよう, 書き直しました。
HTTP/2 では, TCP_NODELAY
オプションを有効にします。
HTTP/2 は, 過去との互換性のため, 80番ポートを使います。次のいずれかでHTTP/2として接続します。
Connection: Upgrade, HTTP2-Settings
を含める。サーバが対応していれば, HTTP/2 にアップグレード.現状では, (1) の方法の方がいいと思う。
[2022-06] RFC 9113 (June 2022) で, HTTP/1.1 からの upgrade は廃止された (obsolete). h2c
upgrade token も廃止。かならず (2) で実装せよ.
接続を確立すると, まず, クライアントから, client connection preface として, 決まった文字列を送信します。
次に, 相互に SETTINGS フレームやりとりし, 通信条件を確認します。お互いに, ACK で受け取ったことを知らせます。
一つ目のリソースの要求は, 最初の HTTP/1.1 リクエストに含まれます。下の例では, 二つ目のリクエストを送っています。
send_HEADERS_frame()
は, 共通ルーチンのページを見てください。
recv_streams()
については, 共通ルーチンのページをご覧ください。
送受信が終わったら, 後始末.