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

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

このコミットは、Go言語の標準ライブラリであるnet/httpパッケージにおけるクライアントのクッキー処理に関する最適化を導入しています。具体的には、HTTPレスポンスにクッキーが含まれていない場合に、不要なSetCookiesメソッドの呼び出しを避けるように変更されています。これにより、パフォーマンスの向上と、クッキーが設定されない場合の不必要な処理の削減が期待されます。

コミット

commit d0283ab62ab692ee4c28e31d85740a5804b5540a
Author: Joakim Sernbrant <serbaut@gmail.com>
Date:   Wed Dec 19 16:24:38 2012 -0800

    net/http: only call client SetCookie when needed
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/6968049

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

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

元コミット内容

このコミットの元の内容は、net/httpパッケージのクライアントが、HTTPレスポンスからクッキーを取得し、それをクッキージャー(http.CookieJar)に設定する際に、レスポンスにクッキーが含まれていない場合でもSetCookiesメソッドを呼び出していた挙動を修正するものです。変更の目的は、「必要な場合にのみclient.SetCookieを呼び出す」ことで、無駄な処理を省くことにあります。

変更の背景

Goのnet/httpパッケージのClientは、HTTPリクエストを送信し、レスポンスを受け取った後に、そのレスポンスに含まれるクッキーを管理するためにhttp.CookieJarインターフェースの実装を利用します。以前の実装では、レスポンスからクッキーを取得するresp.Cookies()メソッドが空のスライス(クッキーがない場合)を返した場合でも、常にc.Jar.SetCookies(req.URL, resp.Cookies())が呼び出されていました。

この挙動は、クッキーが全く含まれていないレスポンスに対しても、CookieJarの実装がSetCookiesメソッドを呼び出されることになり、その内部で不要な処理(例えば、空のスライスに対するループ処理や、クッキーの存在チェックなど)が発生する可能性がありました。特に、クッキーを頻繁に利用しない、またはクッキーを全く設定しないAPIエンドポイントとの通信が多いアプリケーションでは、この小さなオーバーヘッドが積み重なり、わずかながらもパフォーマンスに影響を与える可能性がありました。

このコミットは、このような不必要なSetCookies呼び出しを排除し、クッキーが実際にレスポンスに含まれている場合にのみSetCookiesを呼び出すようにすることで、効率性を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびHTTPプロトコルに関する知識が役立ちます。

  • HTTPクッキー (HTTP Cookies): ウェブサーバーがユーザーのウェブブラウザに送信する小さなデータ片です。ブラウザは通常、同じサーバーへの後続のリクエストでこれらのクッキーを保存し、返送します。これにより、セッション管理、パーソナライゼーション、トラッキングなどが可能になります。
  • Go言語の net/http パッケージ: Go言語でHTTPクライアントおよびサーバーを構築するための標準ライブラリです。
    • http.Client: HTTPリクエストを送信するための構造体です。この構造体は、リクエストのタイムアウト、リダイレクトポリシー、そしてクッキー管理などの設定をカプセル化します。
    • http.CookieJar インターフェース: クッキーを保存および取得するためのインターフェースです。このインターフェースを実装することで、カスタムのクッキー管理ロジックをhttp.Clientに組み込むことができます。主なメソッドは以下の通りです。
      • SetCookies(u *url.URL, cookies []*Cookie): 指定されたURLに関連するクッキーを設定(追加または更新)します。
      • Cookies(u *url.URL) []*Cookie: 指定されたURLに関連するクッキーを取得します。
    • http.Response: HTTPレスポンスを表す構造体です。
    • resp.Cookies() メソッド: http.Response構造体のメソッドで、レスポンスのSet-Cookieヘッダーから解析された*http.Cookieのスライスを返します。Set-Cookieヘッダーが存在しない場合は、空のスライスを返します。
  • Go言語の if ステートメントにおける短い変数宣言: Goでは、ifステートメントの条件式の前に、そのスコープ内でのみ有効な変数を宣言し、初期化することができます。例: if val := someFunc(); val != nil { ... }。このコミットではif rc := resp.Cookies(); len(rc) > 0という形式が使われています。

技術的詳細

この変更は、net/httpパッケージ内のClient構造体のsendメソッドに焦点を当てています。sendメソッドは、HTTPリクエストを実際に送信し、レスポンスを受け取る主要なロジックを含んでいます。

変更前は、sendメソッド内でレスポンスを受け取った後、クライアントにJarCookieJarインターフェースの実装)が設定されている場合、常にresp.Cookies()を呼び出し、その結果をc.Jar.SetCookiesに渡していました。

// 変更前 (client.go)
if c.Jar != nil {
    c.Jar.SetCookies(req.URL, resp.Cookies())
}

resp.Cookies()は、レスポンスにSet-Cookieヘッダーが存在しない場合でも、nilではなく空の[]*Cookieスライスを返します。したがって、クッキーが全く設定されていないレスポンスに対しても、c.Jar.SetCookiesメソッドは常に呼び出されていました。

このコミットでは、この部分に条件分岐が追加されました。

// 変更後 (client.go)
if c.Jar != nil {
    if rc := resp.Cookies(); len(rc) > 0 { // ここが変更点
        c.Jar.SetCookies(req.URL, rc)
    }
}

新しいコードでは、まずresp.Cookies()の結果をrcという変数に代入し、そのrcスライスの長さが0より大きい(つまり、1つ以上のクッキーが含まれている)場合にのみ、c.Jar.SetCookies(req.URL, rc)が呼び出されるようになります。

この変更により、クッキーが全く含まれていないHTTPレスポンスの場合、len(rc)は0となり、c.Jar.SetCookiesの呼び出しはスキップされます。これにより、CookieJarの実装が不要な処理を実行することを防ぎ、全体的な効率が向上します。

テストケースの追加も重要です。client_test.goには、クッキーを設定しない特別なパス/nosetcookieが追加されました。このパスへのリクエストでは、サーバーはSetCookieヘッダーを返しません。テストでは、このパスへのリクエスト後にCookieJarのログを検証し、SetCookieメソッドが呼び出されていないことを確認しています。ただし、Cookiesメソッド(既存のクッキーを取得するため)は呼び出されることが期待されており、これはJarがリクエストURLに関連する既存のクッキーをチェックする必要があるためです。

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

src/pkg/net/http/client.go

--- a/src/pkg/net/http/client.go
+++ b/src/pkg/net/http/client.go
@@ -98,7 +98,9 @@ func (c *Client) send(req *Request) (*Response, error) {
 		return nil, err
 	}
 	if c.Jar != nil {
-		c.Jar.SetCookies(req.URL, resp.Cookies())
+		if rc := resp.Cookies(); len(rc) > 0 {
+			c.Jar.SetCookies(req.URL, rc)
+		}
 	}
 	return resp, err
 }

src/pkg/net/http/client_test.go

--- a/src/pkg/net/http/client_test.go
+++ b/src/pkg/net/http/client_test.go
@@ -418,6 +418,9 @@ func matchReturnedCookies(t *testing.T, expected, given []*Cookie) {
 func TestJarCalls(t *testing.T) {
 	ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
 		pathSuffix := r.RequestURI[1:]
+		if r.RequestURI == "/nosetcookie" {
+			return // dont set cookies for this path
+		}
 		SetCookie(w, &Cookie{Name: "name" + pathSuffix, Value: "val" + pathSuffix})
 		if r.RequestURI == "/" {
 			Redirect(w, r, "http://secondhost.fake/secondpath", 302)
@@ -437,11 +440,16 @@ func TestJarCalls(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+	_, err = c.Get("http://firsthost.fake/nosetcookie")
+	if err != nil {
+		t.Fatal(err)
+	}
 	got := jar.log.String()
 	want := `Cookies("http://firsthost.fake/")
 SetCookie("http://firsthost.fake/", [name=val])
 Cookies("http://secondhost.fake/secondpath")
 SetCookie("http://secondhost.fake/secondpath", [namesecondpath=valsecondpath])
+Cookies("http://firsthost.fake/nosetcookie")
 `
 	if got != want {
 		t.Errorf("Got Jar calls:\n%s\nWant:\n%s", got, want)

コアとなるコードの解説

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

このファイルでは、Client構造体のsendメソッド内のクッキー処理ロジックが変更されています。

元のコード:

if c.Jar != nil {
    c.Jar.SetCookies(req.URL, resp.Cookies())
}

この行は、c.Jarnilでない(つまり、クッキー管理が有効になっている)場合、常にresp.Cookies()から取得したクッキーをc.Jar.SetCookiesに渡していました。resp.Cookies()は、レスポンスにSet-Cookieヘッダーがない場合でも空のスライスを返すため、SetCookiesは常に呼び出されていました。

変更後のコード:

if c.Jar != nil {
    if rc := resp.Cookies(); len(rc) > 0 {
        c.Jar.SetCookies(req.URL, rc)
    }
}

この変更では、resp.Cookies()の戻り値(rc)の長さをチェックするif文が追加されています。len(rc) > 0という条件は、「レスポンスに1つ以上のクッキーが含まれている場合のみ」を意味します。この条件が真である場合にのみ、c.Jar.SetCookiesが呼び出されます。これにより、クッキーが全く含まれていないレスポンスに対しては、SetCookiesメソッドの呼び出しが完全にスキップされ、不必要な処理が回避されます。

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

このファイルでは、TestJarCallsというテスト関数が拡張され、新しい動作が検証されています。

  1. 新しいハンドラロジックの追加: httptest.NewServerで作成されるテストサーバーのハンドラに、新しい条件が追加されました。

    if r.RequestURI == "/nosetcookie" {
        return // dont set cookies for this path
    }
    

    これにより、/nosetcookieというパスへのリクエストに対しては、サーバーはSetCookieヘッダーを一切設定せずにレスポンスを返します。

  2. 新しいリクエストの追加: TestJarCalls関数内で、c.Get("http://firsthost.fake/nosetcookie")という新しいHTTP GETリクエストが追加されました。このリクエストは、クッキーが設定されないシナリオをシミュレートします。

  3. 期待されるログ出力の更新: jar.log.String()の期待される出力wantが更新されました。

    - `Cookies("http://firsthost.fake/")
    - SetCookie("http://firsthost.fake/", [name=val])
    - Cookies("http://secondhost.fake/secondpath")
    - SetCookie("http://secondhost.fake/secondpath", [namesecondpath=valsecondpath])
    + `Cookies("http://firsthost.fake/")
    + SetCookie("http://firsthost.fake/", [name=val])
    + Cookies("http://secondhost.fake/secondpath")
    + SetCookie("http://secondhost.fake/secondpath", [namesecondpath=valsecondpath])
    + Cookies("http://firsthost.fake/nosetcookie")
    `
    

    注目すべきは、Cookies("http://firsthost.fake/nosetcookie")という行が追加されている点です。これは、/nosetcookieへのリクエスト後も、CookieJarCookiesメソッド(既存のクッキーを取得するため)は呼び出されることを示しています。しかし、SetCookieメソッドの呼び出しは追加されていません。これは、サーバーがクッキーを返さなかったため、新しいロジックによってSetCookieがスキップされたことを正確に検証しています。

これらの変更により、net/httpクライアントのクッキー処理がより効率的になり、クッキーが不要な場合のオーバーヘッドが削減されます。

関連リンク

参考にした情報源リンク

  • Goのコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/6968049 (コミットメッセージに記載されているリンク)
  • Go言語の公式ドキュメント
  • HTTP/1.1 RFC 2616 (特にセクション 14.34 "Set-Cookie" と 15.1 "Cookies"): https://www.rfc-editor.org/rfc/rfc2616 (一般的なHTTPクッキーの理解のため)