[インデックス 13129] ファイルの概要
このコミット 3d03ec88963e93d15a9dec53b4ba61fda75c603b
は、Go言語の標準ライブラリ net/http
パッケージにおける、HTTP接続のKeep-Alive動作に関する変更です。具体的には、以前のコミット 2eec2501961c
(CL 6112054) で行われた変更を元に戻す(undoする)ものです。この 2eec2501961c
は、さらにその前のコミット 97d027b3aa68
の内容をRevert(元に戻す)していました。
つまり、このコミットは「RevertのRevert」であり、結果としてクライアントがHTTP接続のKeep-Aliveを無効にできる機能(Connection: close
ヘッダの適切な処理)を再度有効にするものです。この変更は、以前のRevertの原因となっていた「Expect: 100-continue」ヘッダに関するテストの問題が解決されたため、安全に行えるようになりました。また、Issue 3540「Connection header in request is ignored by the http server」の修正にも関連しています。
コミット
commit 3d03ec88963e93d15a9dec53b4ba61fda75c603b
Author: Russ Cox <rsc@golang.org>
Date: Tue May 22 13:56:40 2012 -0400
undo CL 6112054 / 2eec2501961c
Now that we've fixed the Expect: test, this CL should be okay.
««« original CL description
net/http: revert 97d027b3aa68
Revert the following change set:
changeset: 13018:97d027b3aa68
user: Gustavo Niemeyer <gustavo@niemeyer.net>
date: Mon Apr 23 22:00:16 2012 -0300
summary: net/http: allow clients to disable keep-alive
This broke a test on Windows 64 and somebody else
will have to check.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6112054
»»»
Fixes #3540.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6228046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3d03ec88963e93d15a9dec53b4ba61fda75c603b
元コミット内容
このコミットは、2eec2501961c
(CL 6112054) というコミットを元に戻すものです。
その 2eec2501961c
コミットの元の説明には、97d027b3aa68
というコミットをRevertすると記載されています。
97d027b3aa68
のコミット内容は以下の通りです。
- コミットハッシュ:
97d027b3aa68
- 概要:
net/http: allow clients to disable keep-alive
(クライアントがKeep-Aliveを無効にできるようにする) - 日付: 2012年4月23日
- 作者: Gustavo Niemeyer
この 97d027b3aa68
の変更は、クライアントがHTTPリクエストヘッダに Connection: close
を含めることで、サーバーに対して接続を閉じるように要求できる機能を追加するものでした。しかし、この変更はWindows 64環境でのテストを壊したため、一時的に 2eec2501961c
によってRevertされていました。
今回のコミット 3d03ec88963e93d15a9dec53b4ba61fda75c603b
は、この 2eec2501961c
のRevertをさらに元に戻すことで、結果的に 97d027b3aa68
で導入された「クライアントがKeep-Aliveを無効にできる機能」を再度有効にしています。
変更の背景
このコミットの背景には、Go言語の net/http
パッケージにおけるHTTP接続の管理、特にKeep-Aliveと接続終了のメカニズムに関する課題がありました。
-
初期の機能追加 (
97d027b3aa68
): 当初、Gustavo Niemeyer氏によって、クライアントがHTTPリクエストヘッダにConnection: close
を含めることで、サーバーに対して接続を閉じるように明示的に要求できる機能が追加されました。これは、HTTPプロトコルにおける標準的な動作であり、クライアントがリソースを解放したり、特定のシナリオで接続の再利用を避けたい場合に重要です。この変更は、GoのIssue 3540「Connection header in request is ignored by the http server」の修正を意図していました。 -
一時的なRevert (
2eec2501961c
): しかし、この機能追加はWindows 64環境でのテストを壊すという予期せぬ副作用をもたらしました。具体的なテストの失敗内容はコミットメッセージからは不明ですが、安定性を優先するため、Russ Cox氏によってこの変更は一時的にRevertされました。これにより、クライアントがConnection: close
を指定しても、サーバーがそれを適切に処理しない状態に戻されました。 -
RevertのRevert(今回のコミット
3d03ec88963e93d15a9dec53b4ba61fda75c603b
): 今回のコミットは、以前のRevert (2eec2501961c
) を元に戻すものです。この「RevertのRevert」が可能になったのは、コミットメッセージに「Now that we've fixed the Expect: test, this CL should be okay.」とあるように、Expect: 100-continue
ヘッダに関連するテストの問題が解決されたためです。この「Expect: test」の修正が、以前のWindows 64でのテスト失敗の根本原因であった可能性が高いです。この修正により、クライアントがConnection: close
を指定した場合の動作が再び正しくなり、Issue 3540が最終的に解決されることになります。
要するに、このコミットは、一時的に無効化されていた「クライアントによる接続終了要求」の機能を、関連するバグが修正されたことで安全に再有効化し、HTTPプロトコル仕様への準拠と、より柔軟な接続管理を実現することを目的としています。
前提知識の解説
このコミットを理解するためには、以下のHTTPプロトコルとGo言語の net/http
パッケージに関する知識が必要です。
-
HTTP Keep-Alive (持続的接続): HTTP/1.1では、デフォルトでKeep-Alive(持続的接続)が有効になっています。これは、一つのTCP接続上で複数のHTTPリクエストとレスポンスをやり取りできる機能です。これにより、リクエストごとに新しいTCP接続を確立・切断するオーバーヘッドが削減され、Webページのロード時間短縮やサーバー負荷軽減に貢献します。 クライアントやサーバーは、
Connection: keep-alive
ヘッダを送信することで、接続を維持したい意図を示します。 -
HTTP
Connection: close
ヘッダ: HTTP/1.0では、デフォルトで接続はリクエストごとに閉じられます。HTTP/1.1でKeep-Aliveがデフォルトになった後も、クライアントやサーバーはConnection: close
ヘッダを送信することで、現在のリクエスト/レスポンスの送受信が完了した後にTCP接続を閉じるように明示的に要求できます。これは、リソースの解放、サーバーの過負荷回避、または特定のプロトコルハンドシェイクの完了など、様々なシナリオで利用されます。 -
HTTP
Expect: 100-continue
ヘッダ: これは、主に大きなリクエストボディ(例: ファイルアップロード)を送信する際にクライアントが使用するヘッダです。クライアントはまず、リクエストヘッダにExpect: 100-continue
を含めてサーバーに送信します。サーバーはこれを受け取ると、リクエストボディを受け入れる準備ができている場合に100 Continue
という中間レスポンスを返します。クライアントはこの100 Continue
を受け取ってから、初めてリクエストボディの送信を開始します。 もしサーバーが100 Continue
以外のレスポンス(例:401 Unauthorized
や403 Forbidden
)を返した場合、クライアントはリクエストボディを送信せずに接続を閉じることができます。これにより、不要なデータ転送を避け、帯域幅を節約できます。 このメカニズムは、サーバーがリクエストボディを処理する前に認証や認可のチェックを行いたい場合などに有用です。しかし、実装が複雑であり、特にエラーハンドリングやタイムアウト処理が不適切だと、接続がハングアップしたり、予期せぬ動作を引き起こす可能性があります。今回のコミットの背景にある「Expect: test」の問題も、このような複雑さに起因していたと考えられます。 -
Go言語の
net/http
パッケージ: Go言語のnet/http
パッケージは、HTTPクライアントとサーバーを構築するための基本的な機能を提供します。このパッケージは、HTTPプロトコルの詳細を抽象化し、開発者が簡単にWebアプリケーションを構築できるように設計されています。http.Request
: 受信したHTTPリクエストを表す構造体。ヘッダ、メソッド、URL、ボディなどの情報を含みます。http.ResponseWriter
: HTTPレスポンスを書き込むためのインターフェース。ヘッダの設定やボディの書き込みに使用します。http.Handler
: HTTPリクエストを処理するためのインターフェース。ServeHTTP
メソッドを実装します。
-
HTTPヘッダのトークン解析: HTTPヘッダの値は、しばしばカンマ区切りで複数の「トークン」を含むことがあります(例:
Connection: keep-alive, Upgrade
)。これらのトークンを正しく解析するには、単にstrings.Contains
で部分文字列を検索するだけでは不十分な場合があります。RFC 7230などのHTTP仕様では、トークンは特定の文字セットで構成され、空白やカンマで区切られると定義されています。今回のコミットで導入されたhasToken
関数は、このトークン解析の必要性を示唆しています。コミット内のTODO
コメントにあるように、strings.Contains
を使用している点は、まだRFCに完全に準拠した実装ではないことを示しています。
技術的詳細
このコミットで行われた技術的な変更は、主に net/http
パッケージ内のHTTPヘッダの解析と、それに基づく接続管理のロジックに焦点を当てています。
-
hasToken
ヘルパー関数の導入:src/pkg/net/http/request.go
に新しいプライベート関数hasToken(s, token string) bool
が追加されました。この関数は、与えられた文字列s
(HTTPヘッダの値)が特定のtoken
を含んでいるかどうかをチェックします。 以前はstrings.Contains(strings.ToLower(r.Header.Get("Expect")), "100-continue")
のように直接strings.Contains
を使用していましたが、hasToken
を導入することで、ヘッダ値の正規化(小文字化)とトークンチェックのロジックをカプセル化しています。 ただし、この関数には// TODO This is a poor implementation of the RFC. See http://golang.org/issue/3535
というコメントが付いています。これは、HTTPヘッダのトークン解析がRFCの仕様(例: カンマ区切りの複数のトークン、空白の扱いなど)に厳密には準拠しておらず、より堅牢な実装が必要であることを示唆しています。Issue 3535は、このhasToken
の実装が不十分であるという課題を追跡しているものと思われます。 -
Request
構造体へのwantsClose()
メソッドの追加:src/pkg/net/http/request.go
にfunc (r *Request) wantsClose() bool
メソッドが追加されました。このメソッドは、リクエストのConnection
ヘッダにclose
トークンが含まれているかどうかをチェックします。これにより、クライアントが明示的に接続の終了を要求しているかどうかを簡単に判断できるようになります。このメソッドも内部でhasToken
を利用しています。 -
expectsContinue()
とwantsHttp10KeepAlive()
の変更:src/pkg/net/http/request.go
内の既存のメソッドexpectsContinue()
とwantsHttp10KeepAlive()
も、内部でhasToken
関数を使用するように変更されました。これにより、HTTPヘッダのトークンチェックが一貫した方法で行われるようになります。 -
サーバー側の接続終了ロジックの修正 (
src/pkg/net/http/server.go
):response
構造体のWriteHeader
メソッド(HTTPレスポンスヘッダを書き込む際に呼び出される)内のロジックが変更されました。 変更前:} else if !w.req.ProtoAtLeast(1, 1) { // Client did not ask to keep connection alive. w.closeAfterReply = true }
変更後:
} else if !w.req.ProtoAtLeast(1, 1) || w.req.wantsClose() { w.closeAfterReply = true }
この変更により、サーバーは以下のいずれかの条件が満たされた場合に、レスポンス送信後に接続を閉じるように設定されます。
- リクエストがHTTP/1.1以上ではない場合(つまりHTTP/1.0の場合)。HTTP/1.0ではデフォルトで接続は閉じられます。
- または、クライアントが
Connection: close
ヘッダを送信して接続の終了を要求している場合(w.req.wantsClose()
がtrue
を返す場合)。 この修正により、サーバーはクライアントのConnection: close
要求を適切に尊重し、接続を終了するようになります。
-
テストケースの追加と修正 (
src/pkg/net/http/serve_test.go
):testTcpConnectionCloses
関数がtestTCPConnectionCloses
にリネームされました(Goの命名規約に合わせた変更)。- 新しいテストケース
TestClientCanClose
が追加されました。このテストは、クライアントがConnection: close
ヘッダを送信した場合に、サーバーが実際に接続を閉じることを検証します。これは、今回のコミットで再有効化された機能が正しく動作することを確認するための重要なテストです。
これらの変更は、HTTPヘッダの解析をより堅牢にし、特に Connection
ヘッダに基づく接続管理のロジックを改善することで、HTTPプロトコル仕様への準拠を高め、クライアントが接続のライフサイクルをより細かく制御できるようにすることを目的としています。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下の3つのファイルにわたります。
-
src/pkg/net/http/request.go
:expectsContinue()
関数がstrings.ToLower(r.Header.Get("Expect")) == "100-continue"
からhasToken(r.Header.Get("Expect"), "100-continue")
に変更。wantsHttp10KeepAlive()
関数がstrings.Contains(strings.ToLower(r.Header.Get("Connection")), "keep-alive")
からhasToken(r.Header.Get("Connection"), "keep-alive")
に変更。- 新しい関数
wantsClose() bool
が追加。これはhasToken(r.Header.Get("Connection"), "close")
を呼び出す。 - 新しいヘルパー関数
hasToken(s, token string) bool
が追加。
-
src/pkg/net/http/serve_test.go
:testTcpConnectionCloses
関数がtestTCPConnectionCloses
にリネーム。TestServeHTTP10Close
,TestHandlersCanSetConnectionClose11
,TestHandlersCanSetConnectionClose10
内で呼び出される関数名もtestTCPConnectionCloses
に変更。- 新しいテスト関数
TestClientCanClose()
が追加。このテストはtestTCPConnectionCloses
を使用して、クライアントがConnection: close
を指定した場合の動作を検証。
-
src/pkg/net/http/server.go
:response
構造体のWriteHeader
メソッド内の接続終了ロジックが変更。!w.req.ProtoAtLeast(1, 1)
の条件に|| w.req.wantsClose()
が追加。
コアとなるコードの解説
src/pkg/net/http/request.go
hasToken(s, token string) bool
func hasToken(s, token string) bool {
if s == "" {
return false
}
// TODO This is a poor implementation of the RFC. See http://golang.org/issue/3535
return strings.Contains(strings.ToLower(s), token)
}
この関数は、HTTPヘッダの値 s
の中に特定の token
が含まれているかをチェックするためのヘルパーです。HTTPヘッダは通常、大文字小文字を区別しないため、strings.ToLower(s)
で小文字に変換してから strings.Contains
で部分文字列を検索しています。
しかし、TODO
コメントにあるように、これはRFC(Request For Comments、インターネット標準を定義する文書)に準拠した厳密なトークン解析ではありません。HTTPヘッダのトークンはカンマで区切られたり、空白が含まれたりすることがあり、単なる部分文字列検索では誤った結果を返す可能性があります。例えば、Connection: keep-alive, close
のようなヘッダで hasToken("keep-alive, close", "close")
は true
を返しますが、Connection: not-close
のようなヘッダでも hasToken("not-close", "close")
が true
を返してしまう可能性があります。Issue 3535は、この hasToken
のより堅牢な実装を求めるものです。
func (r *Request) expectsContinue() bool
func (r *Request) expectsContinue() bool {
return hasToken(r.Header.Get("Expect"), "100-continue")
}
このメソッドは、リクエストの Expect
ヘッダが 100-continue
を含んでいるかどうかをチェックします。以前は strings.ToLower
と strings.Contains
を直接使用していましたが、hasToken
を利用することでコードの重複を避け、意図を明確にしています。
func (r *Request) wantsHttp10KeepAlive() bool
func (r *Request) wantsHttp10KeepAlive() bool {
if r.ProtoMajor != 1 || r.ProtoMinor != 0 {
return false
}
return hasToken(r.Header.Get("Connection"), "keep-alive")
}
このメソッドは、HTTP/1.0のリクエストが Connection: keep-alive
ヘッダを送信しているかどうかをチェックします。HTTP/1.0ではKeep-Aliveはデフォルトではないため、明示的に keep-alive
が指定されているかを確認します。ここでも hasToken
が使用されています。
func (r *Request) wantsClose() bool
func (r *Request) wantsClose() bool {
return hasToken(r.Header.Get("Connection"), "close")
}
新しく追加されたこのメソッドは、リクエストの Connection
ヘッダが close
トークンを含んでいるかどうかをチェックします。これにより、クライアントが接続の終了を明示的に要求しているかを簡単に判断できるようになります。
src/pkg/net/http/serve_test.go
func testTCPConnectionCloses(t *testing.T, req string, h Handler)
この関数は、HTTPリクエスト req
を送信し、その後にTCP接続が閉じられることを検証するためのヘルパーテスト関数です。元の名前 testTcpConnectionCloses
から testTCPConnectionCloses
に変更されました。これはGoの命名規約(アクロニムは大文字で統一する)に合わせたものです。
func TestClientCanClose(t *testing.T)
// TestClientCanClose verifies that clients can also force a connection to close.
func TestClientCanClose(t *testing.T) {
testTCPConnectionCloses(t, "GET / HTTP/1.1\r\nConnection: close\r\n\r\n", HandlerFunc(func(w ResponseWriter, r *Request) {
// Nothing.
}))
}
この新しいテストケースは、クライアントがHTTP/1.1リクエストで Connection: close
ヘッダを送信した場合に、サーバーがその要求を尊重して接続を閉じることを検証します。testTCPConnectionCloses
を呼び出し、Connection: close
ヘッダを含むリクエストを送信し、接続が閉じられることを確認します。これは、今回のコミットで再有効化された機能の動作保証となります。
src/pkg/net/http/server.go
func (w *response) WriteHeader(code int)
// ...
} else if !w.req.ProtoAtLeast(1, 1) || w.req.wantsClose() {
w.closeAfterReply = true
}
// ...
この変更は、サーバーがレスポンスヘッダを書き込む際に、接続を閉じるべきかどうかを判断するロジックに影響します。
変更前は、リクエストがHTTP/1.1未満(つまりHTTP/1.0)の場合にのみ w.closeAfterReply = true
と設定されていました。
変更後は、以下のいずれかの条件が満たされた場合に接続を閉じるように設定されます。
- リクエストがHTTP/1.1未満である(
!w.req.ProtoAtLeast(1, 1)
)。 - または、クライアントが
Connection: close
ヘッダを送信して接続の終了を要求している(w.req.wantsClose()
)。
この || w.req.wantsClose()
の追加により、サーバーはクライアントの明示的な接続終了要求を適切に処理し、HTTPプロトコル仕様に準拠した動作を実現します。
関連リンク
- Go Issue 3540: Connection header in request is ignored by the http server
- Go Issue 3535: net/http: hasToken is a poor implementation of the RFC
- Change List 6112054 (Revert): net/http: revert 97d027b3aa68
- Change List 6228046 (Current Commit): undo CL 6112054 / 2eec2501961c
- Original Commit 97d027b3aa68: net/http: allow clients to disable keep-alive
参考にした情報源リンク
- https://github.com/golang/go/commit/3d03ec88963e93d15a9dec53b4ba61fda75c603b
- https://go.googlesource.com/go/+/2eec2501961c
- https://go.googlesource.com/go/+/97d027b3aa68
- https://go.dev/issue/3540
- https://go.dev/issue/3535
- https://golang.org/cl/6112054
- https://golang.org/cl/6228046
- Web search results for "golang net/http Expect: 100-continue test fix"
- Web search results for "golang net/http allow clients to disable keep-alive"
- RFC 7230 (Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing) - 特にヘッダフィールドの構文に関するセクション