[インデックス 18697] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージにおける Server
構造体に ErrorLog
フィールドを追加し、TLSハンドシェイクエラーを適切にログに記録するように変更するものです。これにより、HTTPサーバーがTLSハンドシェイク中に発生するエラーを捕捉し、開発者や運用者が問題を特定しやすくなります。
コミット
commit 281088b1f087aff44c4a928734871fe94f82b88b
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Fri Feb 28 12:12:51 2014 -0800
net/http: add Server.ErrorLog; log and test TLS handshake errors
Fixes #7291
LGTM=agl
R=golang-codereviews, agl
CC=agl, golang-codereviews
https://golang.org/cl/70250044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/281088b1f087aff44c4a928734871fe94f82b88b
元コミット内容
net/http: add Server.ErrorLog; log and test TLS handshake errors
このコミットは、net/http
パッケージの Server
に ErrorLog
フィールドを追加し、TLSハンドシェイクエラーをログに記録し、そのテストを追加するものです。
変更の背景
この変更は、Go issue #7291「net/http: server TLS handshake failures are silent」を修正するために行われました。以前の net/http
パッケージでは、HTTPサーバーがTLSハンドシェイク中にエラーに遭遇した場合、そのエラーが適切にログに記録されず、サイレントに失敗していました。これにより、開発者や運用者はTLS接続の問題をデバッグすることが困難でした。
具体的には、serve()
関数内で tlsConn.Handshake()
が呼び出されていましたが、そこで発生したエラーが外部に公開されず、デバッグに役立つ情報が得られませんでした。このコミットは、この問題を解決し、TLSハンドシェイクエラーを Server.ErrorLog
を通じてログに出力することで、可視性を向上させることを目的としています。
前提知識の解説
Go言語の net/http
パッケージ
net/http
パッケージは、Go言語でHTTPクライアントとサーバーを実装するための標準ライブラリです。このパッケージを使用することで、WebアプリケーションやAPIサーバーを簡単に構築できます。
http.Server
構造体
http.Server
は、HTTPサーバーの設定をカプセル化する構造体です。ポート、ハンドラー、タイムアウトなどの設定を保持します。このコミット以前は、エラーロギングのための専用フィールドはありませんでした。
log.Logger
log
パッケージは、Go言語の標準ロギングライブラリです。log.Logger
は、ログメッセージの出力先(io.Writer
)、プレフィックス、フラグなどを設定できるロガーです。
TLSハンドシェイク
TLS (Transport Layer Security) ハンドシェイクは、クライアントとサーバーが安全な通信チャネルを確立するために行う一連のプロトコルです。このプロセスには、証明書の交換、暗号スイートのネゴシエーション、鍵の生成などが含まれます。ハンドシェイク中にエラーが発生すると、安全な接続を確立できません。
httptest
パッケージ
httptest
パッケージは、HTTPテストを容易にするためのユーティリティを提供します。httptest.NewTLSServer
は、テスト用のTLSサーバーを簡単に起動できる関数です。
技術的詳細
このコミットの主要な変更点は以下の通りです。
-
http.Server
へのErrorLog
フィールドの追加:http.Server
構造体にErrorLog *log.Logger
フィールドが追加されました。これにより、サーバー固有のエラーロガーを設定できるようになります。ErrorLog
がnil
の場合、デフォルトでlog
パッケージの標準ロガー(os.Stderr
に出力)が使用されます。 -
Server.logf
ヘルパーメソッドの導入:Server
構造体にlogf
というプライベートなヘルパーメソッドが追加されました。このメソッドは、Server.ErrorLog
が設定されていればそれを使用し、そうでなければlog.Printf
を使用してログメッセージを出力します。これにより、サーバー内の様々な場所からのエラーロギングを一元的に管理できるようになりました。 -
TLSハンドシェイクエラーのロギング:
conn.serve()
メソッド内で、TLSハンドシェイク (tlsConn.Handshake()
) が失敗した場合に、そのエラーをc.server.logf
を使用してログに記録するようになりました。これにより、TLS接続の問題が発生した際に、具体的なエラーメッセージがログに出力されるようになります。 -
既存のロギング箇所の
logf
への移行:response
構造体内のWriteHeader
やWrite
メソッド、conn.serve()
内のパニック処理など、既存のlog.Print
やlog.Printf
を使用していたエラーロギング箇所が、新しく導入されたc.conn.server.logf
またはc.server.logf
を使用するように変更されました。これにより、サーバー全体のエラーロギングがErrorLog
フィールドによって制御されるようになりました。 -
テストの追加:
client_test.go
とserve_test.go
に、TLSハンドシェイクエラーがErrorLog
に適切に記録されることを検証するテストが追加されました。TestClientInsecureTransport
とTestClientWithIncorrectTLSServerName
では、不正な証明書やサーバー名によるTLSハンドシェイクエラーがErrorLog
に出力されることを確認しています。TestTLSHandshakeTimeout
では、TLSハンドシェイクのタイムアウトエラーがErrorLog
に出力されることを確認しています。 これらのテストでは、カスタムのio.Writer
をErrorLog
に設定し、そのライターに書き込まれたログメッセージの内容を検証しています。
コアとなるコードの変更箇所
src/pkg/net/http/server.go
--- a/src/pkg/net/http/server.go
+++ b/src/pkg/net/http/server.go
@@ -615,11 +615,11 @@ const maxPostHandlerReadBytes = 256 << 10
func (w *response) WriteHeader(code int) {
if w.conn.hijacked() {
- log.Print("http: response.WriteHeader on hijacked connection")
+ w.conn.server.logf("http: response.WriteHeader on hijacked connection")
return
}
if w.wroteHeader {
- log.Print("http: multiple response.WriteHeader calls")
+ w.conn.server.logf("http: multiple response.WriteHeader calls")
return
}
w.wroteHeader = true
@@ -634,7 +634,7 @@ func (w *response) WriteHeader(code int) {
if err == nil && v >= 0 {
w.contentLength = v
} else {
- log.Printf("http: invalid Content-Length of %q", cl)
+ w.conn.server.logf("http: invalid Content-Length of %q", cl)
w.handlerHeader.Del("Content-Length")
}
}
@@ -817,7 +817,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
if hasCL && hasTE && te != "identity" {
// TODO: return an error if WriteHeader gets a return parameter
// For now just ignore the Content-Length.
- log.Printf("http: WriteHeader called with both Transfer-Encoding of %q and a Content-Length of %d",
+ w.conn.server.logf("http: WriteHeader called with both Transfer-Encoding of %q and a Content-Length of %d",
\tte, w.contentLength)
delHeader("Content-Length")
hasCL = false
@@ -963,7 +963,7 @@ func (w *response) WriteString(data string) (n int, err error) {
// either dataB or dataS is non-zero.
func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error) {
if w.conn.hijacked() {
- log.Print("http: response.Write on hijacked connection")
+ w.conn.server.logf("http: response.Write on hijacked connection")
return 0, ErrHijacked
}
if !w.wroteHeader {
@@ -1096,7 +1096,7 @@ func (c *conn) serve() {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
- log.Printf("http: panic serving %v: %v\\n%s", c.remoteAddr, err, buf)
+ c.server.logf("http: panic serving %v: %v\\n%s", c.remoteAddr, err, buf)
}
if !c.hijacked() {
c.close()
@@ -1112,6 +1112,7 @@ func (c *conn) serve() {\n c.rwc.SetWriteDeadline(time.Now().Add(d))\n }\n if err := tlsConn.Handshake(); err != nil {\n+ c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)\n return\n }\n c.tlsState = new(tls.ConnectionState)\
@@ -1604,6 +1605,12 @@ type Server struct {\n // ConnState type and associated constants for details.\n ConnState func(net.Conn, ConnState)\n \n+\t// ErrorLog specifies an optional logger for errors accepting\n+\t// connections and unexpected behavior from handlers.\n+\t// If nil, logging goes to os.Stderr via the log package's\n+\t// standard logger.\n+\tErrorLog *log.Logger\n+\n \tdisableKeepAlives int32 // accessed atomically.\n }\n \n@@ -1704,7 +1711,7 @@ func (srv *Server) Serve(l net.Listener) error {\n if max := 1 * time.Second; tempDelay > max {\n tempDelay = max\n }\n- log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)\n+ srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)\n time.Sleep(tempDelay)\n continue\n }\n@@ -1735,6 +1742,14 @@ func (s *Server) SetKeepAlivesEnabled(v bool) {\n }\n }\n \n+func (s *Server) logf(format string, args ...interface{}) {\n+\tif s.ErrorLog != nil {\n+\t\ts.ErrorLog.Printf(format, args...)\n+\t} else {\n+\t\tlog.Printf(format, args...)\n+\t}\n+}\n+\n // ListenAndServe listens on the TCP network address addr\n // and then calls Serve with handler to handle requests\n // on incoming connections. Handler is typically nil,\n```
### `src/pkg/net/http/client_test.go` および `src/pkg/net/http/serve_test.go`
これらのファイルには、`Server.ErrorLog` を設定し、TLSハンドシェイクエラーがログに記録されることを検証するテストコードが追加されています。具体的には、`httptest.NewTLSServer` や `httptest.NewUnstartedServer` で作成したテストサーバーの `Config.ErrorLog` にカスタムの `log.Logger` を設定し、そのロガーが期待するエラーメッセージを受け取ったかどうかを `chanWriter` を使って確認しています。
## コアとなるコードの解説
### `Server` 構造体への `ErrorLog` フィールド追加
```go
type Server struct {
// ... 既存のフィールド ...
// ErrorLog specifies an optional logger for errors accepting
// connections and unexpected behavior from handlers.
// If nil, logging goes to os.Stderr via the log package's
// standard logger.
ErrorLog *log.Logger
disableKeepAlives int32 // accessed atomically.
}
この変更により、http.Server
のインスタンスを作成する際に、カスタムのロガーを ErrorLog
フィールドに設定できるようになりました。これにより、アプリケーションのロギングシステムとHTTPサーバーのエラーログを統合したり、特定のエラーログを別のファイルやサービスにルーティングしたりすることが可能になります。
logf
ヘルパーメソッド
func (s *Server) logf(format string, args ...interface{}) {
if s.ErrorLog != nil {
s.ErrorLog.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}
logf
メソッドは、Server
内部でエラーをログに記録する際に使用される統一されたインターフェースを提供します。このメソッドは、Server.ErrorLog
が設定されているかどうかをチェックし、設定されていればそのロガーを使用し、そうでなければ標準の log.Printf
を使用します。これにより、ロギングの動作を外部から簡単にカスタマイズできるようになりました。
TLSハンドシェイクエラーのロギング
// src/pkg/net/http/server.go の conn.serve() メソッド内
// ...
if err := tlsConn.Handshake(); err != nil {
c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
return
}
// ...
このコードスニペットは、TLSハンドシェイクが失敗した場合に、そのエラーを Server.ErrorLog
に記録する部分です。tlsConn.Handshake()
がエラーを返した場合、c.server.logf
を呼び出して、エラーメッセージとリモートアドレスをログに出力します。これにより、TLS接続の問題が発生した際に、どのクライアントからの接続で、どのようなエラーが発生したのかを明確に把握できるようになりました。
既存のロギング箇所の変更
コミットの差分を見ると、response.WriteHeader
、response.Write
、conn.serve
内のパニック処理、Server.Serve
内の Accept
エラー処理など、既存の log.Print
や log.Printf
を使用していた箇所が、すべて c.conn.server.logf
または srv.logf
を使用するように変更されています。これにより、HTTPサーバーからのすべてのエラーログが Server.ErrorLog
の設定に従って出力されるようになり、ロギングの一貫性と制御性が向上しました。
関連リンク
- Go issue #7291: https://github.com/golang/go/issues/7291
参考にした情報源リンク
- Go issue #7291: https://github.com/golang/go/issues/7291
- Go issue #38877: https://github.com/golang/go/issues/38877 (関連する後続の議論)
- Go言語
net/http
パッケージドキュメント: https://pkg.go.dev/net/http - Go言語
log
パッケージドキュメント: https://pkg.go.dev/log - Go言語
crypto/tls
パッケージドキュメント: https://pkg.go.dev/crypto/tls - Go言語
httptest
パッケージドキュメント: https://pkg.go.dev/net/http/httptest