HTTP/2の学習シリーズ。本当はKanazawa.rb #87のもくもく会でやっていたことなんですが参加エントリーを書かない内に次のKanazawa.rbが来そうなので学んだことだけ抜き出してメモっておこうというモチベーションです。
www.blogaomu.com
前回はクライアント-サーバー間の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
別のターミナルでHTTPSリクエストを行います。
$ curl -v -k https://localhost:8443
* Trying ::1:8443...
* TCP_NODELAY set
* Connected to localhost (::1) port 8443 (
* 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
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