[インデックス 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メソッド内でレスポンスを受け取った後、クライアントにJar(CookieJarインターフェースの実装)が設定されている場合、常に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.Jarがnilでない(つまり、クッキー管理が有効になっている)場合、常に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というテスト関数が拡張され、新しい動作が検証されています。
-
新しいハンドラロジックの追加:
httptest.NewServerで作成されるテストサーバーのハンドラに、新しい条件が追加されました。if r.RequestURI == "/nosetcookie" { return // dont set cookies for this path }これにより、
/nosetcookieというパスへのリクエストに対しては、サーバーはSetCookieヘッダーを一切設定せずにレスポンスを返します。 -
新しいリクエストの追加:
TestJarCalls関数内で、c.Get("http://firsthost.fake/nosetcookie")という新しいHTTP GETリクエストが追加されました。このリクエストは、クッキーが設定されないシナリオをシミュレートします。 -
期待されるログ出力の更新:
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へのリクエスト後も、CookieJarのCookiesメソッド(既存のクッキーを取得するため)は呼び出されることを示しています。しかし、SetCookieメソッドの呼び出しは追加されていません。これは、サーバーがクッキーを返さなかったため、新しいロジックによってSetCookieがスキップされたことを正確に検証しています。
これらの変更により、net/httpクライアントのクッキー処理がより効率的になり、クッキーが不要な場合のオーバーヘッドが削減されます。
関連リンク
- Go言語の
net/httpパッケージのドキュメント: https://pkg.go.dev/net/http - Go言語の
http.CookieJarインターフェースのドキュメント: https://pkg.go.dev/net/http#CookieJar - Go言語の
http.Client構造体のドキュメント: https://pkg.go.dev/net/http#Client
参考にした情報源リンク
- 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クッキーの理解のため)