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

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

このコミットは、Go言語の net/http パッケージにおける Response.TLS フィールドに関するマイナーな修正と最適化を目的としています。具体的には、TLS接続の状態情報 (tls.ConnectionState) の取り扱いを改善し、効率性を高め、関連するドキュメントを更新しています。

コミット

commit 6433bff205f24c0f527f87284b8c09c6476e5812
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Wed Mar 5 12:40:13 2014 -0800

    net/http: minor fixes and optimization for Response.TLS
    
    Also add it to doc/go1.3.txt.
    
    Update #7289
    
    LGTM=rsc
    R=rsc
    CC=golang-codereviews
    https://golang.org/cl/71740043

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

https://github.com/golang/go/commit/6433bff205f24c0f527f87284b8c09c6476e5812

元コミット内容

net/http: Response.TLS のマイナーな修正と最適化。 doc/go1.3.txt にも追加。 Issue #7289 を更新。

変更の背景

このコミットの主な背景には、net/http パッケージがTLS (Transport Layer Security) を使用して安全なHTTP通信を行う際に、レスポンスオブジェクト (http.Response) に含まれるTLS接続の状態情報 (Response.TLS) の取り扱いに関する改善の必要性がありました。

以前の実装では、TLS接続の状態がレスポンスごとに新しくコピーされて Response.TLS フィールドに割り当てられていました。これは、特に多数のHTTPリクエストを処理するようなシナリオにおいて、不要なメモリ割り当てとコピーのオーバーヘッドを引き起こす可能性がありました。このコミットは、このオーバーヘッドを削減し、パフォーマンスを向上させることを目的としています。

また、Response.TLS フィールドのドキュメントが不明瞭であったり、そのポインタが共有される性質が明記されていなかったりしたため、開発者が誤解する可能性がありました。このコミットでは、コメントを更新し、このフィールドが暗号化されていないレスポンスでは nil になること、およびポインタが共有されるため変更すべきではないことを明確にしています。

Update #7289 という記述から、この変更が特定のバグ報告や機能改善要求に対応するものであることが示唆されます。具体的なIssueの内容は不明ですが、上記のパフォーマンスとドキュメントの改善がその要求の一部であったと考えられます。

前提知識の解説

このコミットを理解するためには、以下の知識が役立ちます。

  1. Go言語の net/http パッケージ: Go言語でHTTPクライアントおよびサーバーを構築するための標準ライブラリです。HTTPリクエストの送信、レスポンスの受信、ルーティング、ミドルウェアの適用など、HTTP通信の基本的な機能を提供します。
  2. TLS (Transport Layer Security): インターネット上で安全な通信を行うための暗号化プロトコルです。HTTP通信をTLSで保護するとHTTPSとなります。TLSは、通信の盗聴、改ざん、なりすましを防ぐための暗号化、認証、データ整合性を提供します。
  3. TLSハンドシェイク: クライアントとサーバーがTLS接続を確立する際に実行される一連のプロトコルです。このプロセス中に、暗号スイートのネゴシエーション、証明書の交換と検証、セッションキーの生成などが行われます。
  4. tls.ConnectionState: Go言語の crypto/tls パッケージで定義されている構造体で、確立されたTLS接続に関する詳細な情報(使用されたTLSバージョン、暗号スイート、ピア証明書、セッション再開情報など)を保持します。
  5. http.Response 構造体: net/http パッケージで定義されており、HTTPレスポンスを表します。この構造体には、ステータスコード、ヘッダー、ボディなどの情報が含まれます。Response.TLS フィールドは、このレスポンスがTLS接続を介して受信された場合に、そのTLS接続の状態情報を提供します。
  6. http.Transport 構造体: net/http パッケージのHTTPクライアントが実際にネットワーク接続を確立し、リクエストを送信し、レスポンスを受信するメカニズムを実装する構造体です。接続の再利用(Keep-Alive)、プロキシ、TLS設定などを管理します。
  7. persistConn 構造体: http.Transport の内部で使用される構造体で、永続的なネットワーク接続(Keep-Alive接続など)を管理します。これにより、複数のHTTPリクエストで同じTCP/TLS接続を再利用し、パフォーマンスを向上させることができます。

技術的詳細

このコミットの技術的な詳細は、主に net/http パッケージ内の ResponseTransport、および persistConn 構造体における tls.ConnectionState の取り扱い方法の変更に集約されます。

  1. http.Response 構造体の TLS フィールドのコメント更新:

    • 変更前: Response.TLS フィールドのコメントは、Transport がTLS対応接続に対してこのフィールドを設定し、それ以外の場合は nil のままにすることを説明していました。
    • 変更後: コメントがより明確になり、TLS フィールドが暗号化されていないレスポンスでは nil になること、そしてポインタがレスポンス間で共有されるため、変更すべきではないことが明記されました。これは、後述の最適化と密接に関連しています。
  2. http.Transport および persistConn における tls.ConnectionState の管理:

    • 変更前: TransportreadLoop メソッド内で、TLS接続 (*tls.Conn) から ConnectionState() を取得し、その値を新しい tls.ConnectionState 構造体にコピーして resp.TLS に割り当てていました。
    • 変更後:
      • persistConn 構造体に新しいフィールド tlsState *tls.ConnectionState が追加されました。これは、永続的な接続が確立された際に、そのTLS接続の状態を保持するためのものです。
      • TransportdialConn メソッド内で、TLS接続が確立された直後に tlsConn.ConnectionState() を取得し、そのポインタを pconn.tlsState に保存するようになりました。
      • persistConnreadLoop メソッド内で、レスポンスが生成される際に、resp.TLSpc.tlsState を直接割り当てるようになりました。これにより、tls.ConnectionState の不要なコピーが回避され、代わりに既存のポインタが共有される形になります。

この変更により、TLS接続の状態情報が persistConn オブジェクトに一度だけ保存され、その後の同じ永続接続を介したすべてのレスポンスでそのポインタが再利用されるため、メモリ割り当てとコピーのオーバーヘッドが削減されます。これは、特に多数のHTTPリクエストが同じTLS接続を介して行われる場合に、顕著なパフォーマンス向上をもたらします。

  1. テストコードの改善 (src/pkg/net/http/client_test.go):

    • テスト TestResponseSetsTLSConnectionState において、res.Body.Close()defer ステートメントとして追加され、レスポンスボディが確実にクローズされるようになりました。
    • エラーメッセージのフォーマットが改善され、t.Errorfgotwant の値がより明確に表示されるようになりました。
  2. ドキュメントの更新 (doc/go1.3.txt):

    • Go 1.3 の変更点リストに「net/http: Request.TLS を追加 (CL 52660047)」という項目が追加されました。ただし、このコミットの変更内容は Response.TLS に焦点を当てているため、この記述は少し混乱を招く可能性があります。おそらく、関連する別の変更(Request.TLS の追加)が同じリリースサイクルで計画されていたか、または記述ミスである可能性があります。このコミット自体は Response.TLS の最適化と修正です。

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

src/pkg/net/http/response.go

--- a/src/pkg/net/http/response.go
+++ b/src/pkg/net/http/response.go
@@ -76,10 +76,10 @@ type Response struct {
 	// This is only populated for Client requests.
 	Request *Request
 
-	// TLS allows information about the TLS connection on which the
-	// response was received. The Transport in this package sets the field
-	// for TLS-enabled connections before returning the Response otherwise
-	// it leaves the field nil.
+	// TLS contains information about the TLS connection on which the
+	// response was received. It is nil for unencrypted responses.
+	// The pointer is shared between responses and should not be
+	// modified.
 	TLS *tls.ConnectionState
 }

src/pkg/net/http/transport.go

--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -583,6 +583,8 @@ func (t *Transport) dialConn(cm connectMethod) (*persistConn, error) {
 			return nil, err
 		}
 	}
+	cs := tlsConn.ConnectionState()
+	pconn.tlsState = &cs
 	pconn.conn = tlsConn
 }
 
@@ -718,6 +720,7 @@ type persistConn struct {
 	t        *Transport
 	cacheKey connectMethodKey
 	conn     net.Conn
+	tlsState *tls.ConnectionState
 	closed   bool                // whether conn has been closed
 	br       *bufio.Reader       // from conn
 	bw       *bufio.Writer       // to conn
@@ -792,9 +795,8 @@ func (pc *persistConn) readLoop() {
 			}
 		}
 
-		if tlsConn, ok := pc.conn.(*tls.Conn); resp != nil && ok {
-			resp.TLS = new(tls.ConnectionState)
-			*resp.TLS = tlsConn.ConnectionState()
+		if resp != nil {
+			resp.TLS = pc.tlsState
 		}
 
 		hasBody := resp != nil && rc.req.Method != "HEAD" && resp.ContentLength != 0

コアとなるコードの解説

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

  • Response.TLS フィールドのコメント更新:
    • 変更前は、Response.TLSTransport によって設定されることのみを述べていました。
    • 変更後は、「暗号化されていないレスポンスでは nil になる」という条件と、「ポインタがレスポンス間で共有されるため、変更すべきではない」という重要な注意点が追加されました。これは、このコミットで行われた最適化(tls.ConnectionState のコピーを避け、ポインタを共有する)と整合性を保つためのドキュメント上の修正です。開発者がこのフィールドを安全かつ正しく利用するためのガイドラインを提供します。

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

  • persistConn 構造体への tlsState フィールドの追加:

    • type persistConn struct { ... tlsState *tls.ConnectionState ... }
    • persistConn は永続的なネットワーク接続(TCP/TLS接続)を管理する内部構造体です。この新しいフィールドは、確立されたTLS接続の tls.ConnectionState をポインタとして保持するために導入されました。これにより、接続が確立された時点で一度だけ ConnectionState を取得し、それを接続のライフサイクル全体で再利用できるようになります。
  • dialConn メソッドでの tlsState の保存:

    • cs := tlsConn.ConnectionState()
    • pconn.tlsState = &cs
    • dialConn は、新しいネットワーク接続(TLS接続を含む)を確立する役割を担います。TLS接続 (tlsConn) が正常に確立された後、その ConnectionState() を取得し、そのアドレスを persistConn オブジェクト (pconn) の新しく追加された tlsState フィールドに保存します。これにより、TLS接続の状態情報が persistConn に紐付けられ、後で簡単に参照できるようになります。
  • readLoop メソッドでの resp.TLS への tlsState の直接割り当て:

    • 変更前: if tlsConn, ok := pc.conn.(*tls.Conn); resp != nil && ok { resp.TLS = new(tls.ConnectionState); *resp.TLS = tlsConn.ConnectionState() }
      • 以前は、レスポンスが読み取られるたびに、tls.Conn から ConnectionState() を取得し、新しい tls.ConnectionState オブジェクトを割り当てて、その中に値をコピーしていました。これは、レスポンスごとにメモリ割り当てとデータコピーが発生することを意味します。
    • 変更後: if resp != nil { resp.TLS = pc.tlsState }
      • readLoop は、永続接続を介して受信したレスポンスを処理します。この変更により、レスポンスの TLS フィールドに、persistConn に保存されている tlsState ポインタが直接割り当てられるようになりました。
      • この変更がこのコミットの「最適化」の核心です。tls.ConnectionState の不要なコピーが排除され、代わりに既存のポインタが共有されるため、特に多数のレスポンスが同じTLS接続を介して処理される場合のパフォーマンスが向上します。メモリ割り当ての削減は、ガベージコレクションの負荷軽減にも寄与します。

これらの変更は、Goの net/http クライアントがTLS接続をより効率的に管理し、Response.TLS フィールドの利用方法をより明確にするための重要な改善です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (net/http パッケージ, crypto/tls パッケージ)
  • TLS (Transport Layer Security) の一般的な概念に関する情報
  • Go言語のコミット履歴と差分情報