HTTP/2の学習シリーズ。本当はKanazawa.rb #87のもくもく会でやっていたことなんですが参加エントリーを書かない内に次のKanazawa.rbが来そうなので学んだことだけ抜き出してメモっておこうというモチベーションです。
前回はクライアント-サーバー間のHTTP/2通信についてGoでサーバーを実装し確認するという内容でした。今回は間にリバースプロキシを挟んでTLS終端を担ってもらいます。よくありがちな構成をシンプルにしたものですね。
構成
- Client: curl
brew install curl-openssl
- ReverseProxy: nghttpx
- 今回はシンプルな設定(に見える)nghttpxを利用する
- Backend: Go(net/http)
- 前回実装したものを引き続き利用し、h2cで受け付けるように修正する
nghttpx
まずは nghttpx の設定です。基本的な項目のみになっています。注目すべきは2行目の backend で proto=h2
で HTTP/2 プロトコルで接続するようになります。
frontend=0.0.0.0,8443 backend=server,8080;;proto=h2 private-key-file=/etc/nghttpx/cert/server.key certificate-file=/etc/nghttpx/cert/server.crt
backend
h2cでリクエストを受け付けるように修正したBackendの実装は以下になります。h2c というパッケージが用意されているのでこれを利用します。
package main import ( "io" "log" "net/http" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" ) func main() { handler := http.HandlerFunc(index) h2s := &http2.Server{} srv := &http.Server{ Addr: ":8080", Handler: h2c.NewHandler(handler, h2s), } log.Fatal(srv.ListenAndServe()) } func index(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "Hello World! by HTTP/2") }
オーケストレーション・リクエストの実行
前回サーバーはdocker化したので、docker-composeでリバースプロキシを含めて構成したいと思います。
version: "3.7" services: server: build: context: server reverse-proxy: image: dit4c/nghttpx ports: - "8443:8443" volumes: - "./nghttpx:/etc/nghttpx" - "./cert:/etc/nghttpx/cert" depends_on: - server
プロジェクトは以下の構成です。
. ├── cert │ ├── server.crt │ └── server.key ├── docker-compose.yml ├── nghttpx │ └── nghttpx.conf └── server ├── Dockerfile └── main.go
これで全ての構成の準備ができたので docker-compose を起動します。
$ docker-compose up
$ curl -v -k https://localhost:8443 * Trying ::1:8443... * TCP_NODELAY set * Connected to localhost (::1) port 8443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /usr/local/etc/openssl@1.1/cert.pem CApath: /usr/local/etc/openssl@1.1/certs * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (IN), TLS handshake, Server key exchange (12): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384 * ALPN, server accepted to use h2 * Server certificate: * subject: C=JP * start date: Dec 17 23:13:26 2019 GMT * expire date: Dec 16 23:13:26 2020 GMT * issuer: C=JP * SSL certificate verify result: self signed certificate (18), continuing anyway. * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * Using Stream ID: 1 (easy handle 0x7fe729822a00) > GET / HTTP/2 > Host: localhost:8443 > user-agent: curl/7.67.0 > accept: */* > * Connection state changed (MAX_CONCURRENT_STREAMS == 100)! < HTTP/2 200 < content-type: text/plain; charset=utf-8 < content-length: 22 < date: Thu, 19 Dec 2019 07:40:30 GMT < server: nghttpx < via: 2 nghttpx < * Connection #0 to host localhost left intact Hello World! by HTTP/2
リバースプロキシを挟んでHTTP/2サーバーにリクエストし正常にレスポンスすることを確認できました。
また、dockerネットワークに接続して tcpdump し Wireshark で確認してみます。(tcpdumpイメージは前回参照)
$ docker run \ --net=container:$(docker ps -f "name=reverse-proxy" --format "{{.ID}}") \ -v $(pwd)/tcpdump:/var/dump-data \ tcpdump tcpdump -i eth0 -w /var/dump-data/eth0-2.pcap
172.0.0.3
が reverse-proxy で 172.0.0.2
が backend server です。これらの間の通信もHTTP/2で行われていますね。よかったよかった。
まとめ
今回はnghttpxを利用してTLS終端およびバックエンドサーバーへのリバースプロキシを行いました。また、HTTP/2でのリクエスト・レスポンスを確認できました。余談ですが docker-compose.yml をさくっと書けたのは日頃の作業が身に付いてる感じがあって良かったです。
今回のソースコードはこちらにアップロードしています。 github.com