Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 13142] ファイルの概要

このコミットは、Go言語の標準ライブラリ net/http パッケージにおけるHTTPコネクション管理の改善を目的としています。具体的には、HTTPレスポンスヘッダに Connection: close が含まれる場合のクライアント側のコネクションクローズ処理の修正と、関連するテストの改善が含まれています。

コミット

commit e4ed9494e5a9a6f2c05f84f9279c8062d8a9427d
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Wed May 23 11:19:38 2012 -0700

    net/http: fix response Connection: close, close client connections
    
    Fixes #3663
    Updates #3540 (fixes it more)
    Updates #1967 (fixes it more, re-enables a test)
    
    R=golang-dev, n13m3y3r
    CC=golang-dev
    https://golang.org/cl/6213064

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/e4ed9494e5a9a6f2c05f84f9279c8062d8a9427d

元コミット内容

net/http: fix response Connection: close, close client connections

このコミットは、HTTPレスポンスに Connection: close ヘッダが含まれている場合に、クライアント側で適切にコネクションを閉じるように修正します。また、関連する既存のバグ(Issue #3663, #3540, #1967)を修正し、一部のテストを再有効化しています。

変更の背景

このコミットは、主に以下のGitHub Issueで報告された問題に対処するために行われました。

  • Issue #3663: net/http: Transport doesn't close connection when server sends Connection: close
    • サーバーが Connection: close ヘッダを送信した場合に、net/httpTransport がクライアント側のTCPコネクションを適切に閉じないという問題。これにより、リソースリークや予期せぬ動作が発生する可能性がありました。
  • Issue #3540: net/http: Transport doesn't close connection when server sends Connection: close (again)
    • Issue #3663 と同様の問題で、以前の修正が不十分であったか、別のシナリオで再発した可能性を示唆しています。このコミットは、この問題をより完全に修正することを目指しています。
  • Issue #1967: net/http: Transport doesn't close connection when server sends Connection: close
    • これも同様のコネクションクローズに関する問題で、このコミットによってテストが再有効化されることから、以前の修正で一時的に無効化されていたテストケースが、今回の修正によって再びパスするようになったことを示しています。

これらの問題は、HTTP/1.0およびHTTP/1.1におけるコネクション管理の複雑さに起因しています。特に、Connection: close ヘッダは、現在のリクエスト/レスポンスの処理が完了した後にTCPコネクションを閉じるべきであることを示しますが、net/http パッケージがこれを常に正しく処理していなかったため、コネクションが意図せず開いたままになることがありました。

前提知識の解説

HTTP/1.0とHTTP/1.1におけるコネクション管理

  • HTTP/1.0: デフォルトでは、各リクエスト/レスポンスのペアごとに新しいTCPコネクションが確立され、レスポンスが完了するとコネクションは閉じられます。持続的なコネクション(Keep-Alive)を使用するには、Connection: Keep-Alive ヘッダを明示的に含める必要がありました。
  • HTTP/1.1: デフォルトでは、持続的なコネクション(Persistent Connection)が使用されます。これにより、複数のリクエスト/レスポンスを同じTCPコネクション上で送受信でき、コネクション確立のオーバーヘッドを削減できます。コネクションを閉じるには、Connection: close ヘッダを明示的に含める必要があります。

Connection ヘッダ

Connection ヘッダは、現在のコネクションに固有のオプションを指定するために使用されます。最も一般的な値は closekeep-alive です。

  • Connection: close: 送信側が、現在のリクエスト/レスポンスの処理が完了した後にコネクションを閉じることを意図していることを示します。
  • Connection: keep-alive: HTTP/1.0で持続的なコネクションを要求するために使用されました。HTTP/1.1ではデフォルトで持続的なコネクションが使用されるため、通常は不要ですが、互換性のために使用されることがあります。

net/http パッケージの役割

Go言語の net/http パッケージは、HTTPクライアントとサーバーの実装を提供します。

  • http.Server: HTTPリクエストを受け付け、レスポンスを送信するサーバーサイドの機能を提供します。
  • http.Client: HTTPリクエストを送信し、レスポンスを受信するクライアントサイドの機能を提供します。内部的には http.Transport を使用して実際のネットワーク通信を行います。
  • http.Transport: HTTPリクエストの送信、レスポンスの受信、コネクションの再利用(コネクションプーリング)、プロキシの処理など、低レベルのHTTP通信を管理します。

コネクションリーク

コネクションリークとは、プログラムがネットワークコネクションを確立した後、そのコネクションを適切に閉じないために、リソースが解放されずに残ってしまう状態を指します。これにより、利用可能なコネクション数が枯渇したり、システムリソースが消費され続けたりする問題が発生します。HTTPにおいては、Connection: close ヘッダが適切に処理されない場合にコネクションリークが発生する可能性があります。

技術的詳細

このコミットは、net/http パッケージ内の以下のファイルに影響を与えています。

  1. src/pkg/net/http/serve_test.go: サーバー側のテストファイル。testTCPConnectionCloses 関数が修正され、レスポンスボディの読み込み完了を待つためのチャネルが success から didReadAll に変更され、より堅牢なテストロジックになっています。また、res.Close フラグの検証が追加されています。
  2. src/pkg/net/http/server.go: HTTPサーバーの実装ファイル。response 構造体の WriteHeader メソッドに、w.closeAfterReplytrue であり、かつ Connection: close ヘッダがまだ設定されていない場合に、明示的に Connection: close ヘッダを追加するロジックが追加されました。これは、サーバーがコネクションを閉じる意図がある場合に、クライアントにその旨を正しく通知するためのものです。
  3. src/pkg/net/http/transport.go: HTTPクライアントのトランスポート層の実装ファイル。
    • persistConn 構造体に closed bool フィールドが追加され、コネクションが既に閉じられているかどうかを追跡できるようになりました。これにより、二重クローズを防ぎます。
    • readLoop メソッド内で、コネクションが alive でない場合に pc.close() を呼び出すロジックが追加されました。これは、サーバーがコネクションを閉じることを示した場合に、クライアント側でもコネクションを閉じることを保証します。
    • closeLocked メソッド内で、pc.closed フラグを確認し、コネクションがまだ閉じられていない場合にのみ pc.conn.Close() を呼び出すようになりました。これにより、net.ConnClose() メソッドが複数回呼び出されることによる潜在的な問題を回避します。
  4. src/pkg/net/http/transport_test.go: クライアント側のトランスポートテストファイル。
    • testCloseConn 構造体と testConnSet 構造体が修正され、テストの堅牢性が向上しました。特に、testConnSet*testing.T が追加され、テスト中にエラーを報告できるようになりました。
    • makeTestDial 関数が *testing.T を引数として受け取るようになり、テストヘルパーがよりテストフレームワークと統合されました。
    • testConnSet.check メソッド内のエラー報告が t.Logf から t.Errorf に変更され、テスト失敗時に明確なエラーとして報告されるようになりました。これは、以前のIssue #3540の修正が不十分であったために一時的に Logf に変更されていたものを、今回の修正で Errorf に戻せるようになったことを示しています。
    • TestTransportConnectionCloseOnResponseTestTransportConnectionCloseOnRequest のテスト関数で、makeTestDial の呼び出しに t が渡されるようになりました。

これらの変更により、net/http パッケージは、サーバーからの Connection: close ヘッダをより正確に解釈し、クライアント側のコネクションを適切に閉じるようになりました。これにより、コネクションリークの防止と、より信頼性の高いHTTP通信が実現されます。

コアとなるコードの変更箇所

src/pkg/net/http/server.go の変更

--- a/src/pkg/net/http/server.go
+++ b/src/pkg/net/http/server.go
@@ -389,6 +389,11 @@ func (w *response) WriteHeader(code int) {
 	if !w.req.ProtoAtLeast(1, 0) {
 		return
 	}
+
+	if w.closeAfterReply && !hasToken(w.header.Get("Connection"), "close") {
+		w.header.Set("Connection", "close")
+	}
+
 	proto := "HTTP/1.0"
 	if w.req.ProtoAtLeast(1, 1) {
 		proto = "HTTP/1.1"

src/pkg/net/http/transport.go の変更

--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -480,6 +480,7 @@ type persistConn struct {
 	t        *Transport
 	cacheKey string // its connectMethod.String()
 	conn     net.Conn
+	closed   bool                // whether conn has been closed
 	br       *bufio.Reader       // from conn
 	bw       *bufio.Writer       // to conn
 	reqch    chan requestAndChan // written by roundTrip(); read by readLoop()
@@ -574,6 +575,9 @@ func (pc *persistConn) readLoop() {
 			if alive && !pc.t.putIdleConn(pc) {
 				alive = false
 			}
+			if !alive {
+				pc.close()
+			}
 			waitForBodyRead <- true
 		}
 	}
@@ -669,7 +673,10 @@ func (pc *persistConn) close() {
 
 func (pc *persistConn) closeLocked() {
 	pc.broken = true
-	pc.conn.Close()
+	if !pc.closed {
+		pc.conn.Close()
+		pc.closed = true
+	}
 	pc.mutateHeaderFunc = nil
 }

コアとなるコードの解説

src/pkg/net/http/server.go の変更点

response.WriteHeader メソッドは、HTTPレスポンスヘッダを書き込む直前に呼び出されます。追加されたロジックは以下の条件をチェックします。

  • w.closeAfterReply: サーバーが現在のリクエストの処理後にコネクションを閉じる意図があるかどうかを示す内部フラグ。
  • !hasToken(w.header.Get("Connection"), "close"): レスポンスヘッダにまだ Connection: close が含まれていないかどうか。

もしこれらの条件が両方とも真であれば、サーバーは明示的に Connection: close ヘッダをレスポンスに追加します。これにより、クライアントはサーバーがコネクションを閉じようとしていることを認識し、適切に対応できるようになります。これは、特にHTTP/1.1において、サーバーがコネクションを再利用しないことをクライアントに通知するために重要です。

src/pkg/net/http/transport.go の変更点

  1. persistConn.closed フィールドの追加: persistConn は、クライアント側で持続的なコネクションを管理するための構造体です。closed ブール値フィールドが追加されたことで、このコネクションが既に閉じられているかどうかを追跡できるようになりました。これは、net.Conn.Close() が複数回呼び出されるのを防ぐためのガードとして機能します。

  2. readLoop メソッド内の pc.close() 呼び出し: readLoop は、persistConn がサーバーからのレスポンスを読み取るためのゴルーチンです。このループ内で、alive フラグ(コネクションが再利用可能かどうかを示す)が false になった場合、つまりサーバーがコネクションを閉じると示した場合に、pc.close() が呼び出されるようになりました。これにより、クライアント側でもコネクションを積極的に閉じ、リソースの早期解放を促します。

  3. closeLocked メソッド内の二重クローズ防止: closeLocked メソッドは、persistConn のコネクションを閉じるための内部ヘルパー関数です。このメソッド内で、if !pc.closed というチェックが追加されました。これにより、pc.conn.Close() が呼び出される前に、コネクションがまだ閉じられていないことを確認します。コネクションが閉じられた後には pc.closed = true が設定され、以降の closeLocked 呼び出しでは実際の Close() 呼び出しがスキップされます。これは、net.Conn.Close() が複数回呼び出された場合にエラーを発生させたり、予期せぬ動作を引き起こしたりする可能性のある低レベルのネットワークAPIの特性に対応するための堅牢な設計です。

これらの変更は、HTTPコネクションのライフサイクル管理を改善し、特に Connection: close ヘッダが使用されるシナリオでの信頼性を高めることを目的としています。

関連リンク

参考にした情報源リンク