1
1

More than 1 year has passed since last update.

http.ListenAndServeTLSでサーバが起動できずハマったが、エラー出力を自分で書いたらすぐに解決した

Posted at

概要

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が返ってきてしまう。

そもそも実行した後にすぐターミナルがコマンド待ち状態になってしまうのでおそらくサーバが起動できていない。

sever.go
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.pemkey.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.pemkey.pem を削除し、下記のコマンドを実行するとカレントディレクトリにcert.pemkey.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サーバとして正常に動作するのでしょうか。

server.go
    http2.ConfigureServer(&server, &http2.Server{})

    // server.ListenAndServeTLS("cert.pem", "key.pem")
    server.ListenAndServe()
}
% go run server.go

ブラウザでlocalhost:8080 にアクセスすると、
動作しています。
スクリーンショット 2021-10-30 18.59.31.png

つまり、怪しいのはserver.ListenAndServeTLS("cert.pem", "key.pem") の行だということになりました。

エラー出力で調査

ここで公式ドキュメントを確認。
https://pkg.go.dev/net/http#Server.ListenAndServeTLS

Sever.ListenAndServeTLS はエラーを返してくれる、ということに気付きます。

server.go
    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のヘッダが返ってきました!
ブラウザでもサーバが正常に動作していることを確認できます。
スクリーンショット 2021-10-30 18.48.57.png

解決。

結論

当然といえば当然かもしれませんが、Sever.ListenAndServeTLS は証明書と鍵のファイルを読めないと動きません。
そして何より、解決の糸口になったのはエラーを標準出力したことだったので、今後もGoのコードが思うように動作しなくなったら「まずエラーを可視化」してみようと思います。
err にエラーを格納するのを癖にすることでこのような事故は減らせそうです。

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1