[インデックス 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クッキーの理解のため)