概要
Go初学者(私)が『Go プログラミング実践入門』の3.4 HTTP2の使用 でハマりました。
以下は解決した時のログと、得た教訓を記したものです。
結論、http.ListenAndServeTLS
の引数として渡すために生成した証明書・鍵ファイルの権限が、何らかの原因で足りていなかったことが原因でした。
このハマりで得たもの・教訓は以下の通りです。
- Goの標準ライブラリで動作テスト用の証明書が作成できるということ
- ハマったらファイルの権限も確認すること
- 怪しい箇所のエラー出力をちゃんと自分で書くこと
詳細
何が起こったか
http.ListenAndServeTLS(certFile, keyFile string)
でGoサーバーが起動できない。
正確には、以下のファイルをgo run した後、別ターミナルでcurlコマンドを叩いても curl: (7) Failed to connect to localhost port 8080: Connection refused
が返ってきてしまう。
そもそも実行した後にすぐターミナルがコマンド待ち状態になってしまうのでおそらくサーバが起動できていない。
package main
import (
"fmt"
"golang.org/x/net/http2"
"net/http"
)
type MyHandler struct{}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
handler := MyHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: &handler,
}
http2.ConfigureServer(&server, &http2.Server{})
server.ListenAndServeTLS("cert.pem", "key.pem")
}
// サーバがHTTP/2で動作しているかどうかはcURLで確認する
// curl -I --http2 --insecure https://localhost:8080/
実行してGoサーバを起動しようとした時の様子です。
% go run server.go
%
状況
以下のようにディレクトリを切っていた。(他にも章・リストごとにディレクトリがある)
cert.pem
とkey.pem
はこちら から中身をコピーしている。
http2 % tree
.
├── cert.pem
├── server.go
└── key.pem
試したこと
/crypto/tls/generate_cert.go で証明書を新たに作成
「自分の環境で作成した証明書でないといけないのかも」と思い、このパッケージの存在を知りました。
参考: https://hodalog.com/generate-self-signed-certificate-using-by-golang/
一旦元あったcert.pem
とkey.pem
を削除し、下記のコマンドを実行するとカレントディレクトリにcert.pem
とkey.pem
が生成されます。
% go run /usr/local/go/src/crypto/tls/generate_cert.go -rsa-bits 2048 -host localhost
2021/10/30 17:41:11 wrote cert.pem
2021/10/30 17:41:11 wrote key.pem
もう一度先ほどのファイルでGoサーバを起動し、同様のcurlコマンドを実行してみるものの、これだけでは解決しませんでした。
(結果論ですが、これはほとんど関係なかったと言えるでしょう。人様から拝借した証明書・鍵でも組み合わせが合っているなら動作するはずです)
httpなら動作するのか確認
そもそもTLSの設定を適用しない場合にGoサーバとして正常に動作するのでしょうか。
http2.ConfigureServer(&server, &http2.Server{})
// server.ListenAndServeTLS("cert.pem", "key.pem")
server.ListenAndServe()
}
% go run server.go
ブラウザでlocalhost:8080 にアクセスすると、
動作しています。
つまり、怪しいのはserver.ListenAndServeTLS("cert.pem", "key.pem")
の行だということになりました。
エラー出力で調査
ここで公式ドキュメントを確認。
https://pkg.go.dev/net/http#Server.ListenAndServeTLS
Sever.ListenAndServeTLS
はエラーを返してくれる、ということに気付きます。
http2.ConfigureServer(&server, &http2.Server{})
// エラーが返ってきたら`err`に格納、そして標準出力
err := server.ListenAndServeTLS("cert.pem", "key.pem")
if err != nil {
fmt.Printf("ERROR: %s", err)
}
}
これで実行。
% go run server.go
ERROR: open cert.pem: no such file or directory%
強制終了した。しかし正常にエラーが出力できています。
ん?cert.pem
ならさっき作成したのに読み込めていない…もしかして権限がおかしい?と思いls -l
で確認。
% ls -l
total 24
-rw-r--r-- 1 daikideal staff 1090 10 30 17:41 cert.pem
-rw-r--r-- 1 daikideal staff 610 10 30 18:13 server.go
-rw------- 1 daikideal staff 1704 10 30 17:41 key.pem
…そんなことある?と思いましたがそんなことになっているのだから仕方ないと思い、chmodで読み込み権限を付与。
(というかcert.pem
には権限があるのにcert.pem
が読めていないとは一体…?)
% chmod 644 key.pem
これでもう一度Goサーバを起動。
% go run server.go
エラーは出ていないし強制終了もしていません。
curlコマンドで確認してみます。
% curl -I --http2 --insecure -v https://localhost:8080
* Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (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 change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
* subject: O=Acme Co
* start date: Oct 30 08:41:11 2021 GMT
* expire date: Oct 30 08:41:11 2022 GMT
* issuer: O=Acme Co
* SSL certificate verify result: unable to get local issuer certificate (20), 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 0x7f8f3900dc00)
> HEAD / HTTP/2
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 200
HTTP/2 200
< content-type: text/plain; charset=utf-8
content-type: text/plain; charset=utf-8
< content-length: 13
content-length: 13
< date: Sat, 30 Oct 2021 09:47:19 GMT
date: Sat, 30 Oct 2021 09:47:19 GMT
<
* Connection #0 to host localhost left intact
* Closing connection 0
HTTP/2のヘッダが返ってきました!
ブラウザでもサーバが正常に動作していることを確認できます。
解決。
結論
当然といえば当然かもしれませんが、Sever.ListenAndServeTLS
は証明書と鍵のファイルを読めないと動きません。
そして何より、解決の糸口になったのはエラーを標準出力したことだったので、今後もGoのコードが思うように動作しなくなったら「まずエラーを可視化」してみようと思います。
err
にエラーを格納するのを癖にすることでこのような事故は減らせそうです。