[インデックス 14236] ファイルの概要
このコミットは、Go言語の net/http パッケージにおける Client 型のクッキー(Cookie)処理に関するバグ修正です。具体的には、(*Client) Do() メソッドを通じて GET または HEAD 以外のHTTPリクエスト(例: POST、PUTなど)を送信する際に、クッキーが適切に処理されない問題を解決します。この修正により、Client が持つ CookieJar が、すべてのHTTPメソッドタイプのリクエストに対してクッキーを送信し、レスポンスからクッキーを受け取って更新するようになります。
コミット
commit 4918e3a960c382e673f632c57e155373c73f0c1c
Author: Pawel Szczur <filemon@google.com>
Date: Mon Oct 29 17:56:31 2012 +0100
net/http/client.go: fix cookie handling on (*Client) Do()
Fix the problem with no cookie handling when sending
other than GET or HEAD request through
(*Client) Do(*Request) (*Resposne, error).
https://code.google.com/p/go/issues/detail?id=3985
Adds a function (*Client) send(*Request) (*Reponse, error):
- sets cookies from CookieJar to request,
- sends request
- parses a reply cookies and updates CookieJar
Fixes #3985
R=bradfitz
CC=gobot, golang-dev
https://golang.org/cl/6653049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4918e3a960c382e673f632c57e155373c73f0c1c
元コミット内容
net/http/client.go: (*Client) Do() におけるクッキー処理の修正。
(*Client) Do(*Request) (*Response, error) を通じて GET または HEAD 以外のリクエストを送信する際に、クッキーが処理されない問題を修正します。
この修正は、(*Client) send(*Request) (*Response, error) という新しい関数を追加します。この関数は以下の処理を行います。
CookieJarからリクエストにクッキーを設定する。- リクエストを送信する。
- レスポンスからクッキーを解析し、
CookieJarを更新する。
Go Issue 3985 を修正します。
変更の背景
この変更の背景には、Go言語の net/http パッケージにおける Client のクッキー管理の不整合がありました。元々、Client の Do メソッドは、GET および HEAD リクエストの場合にのみ、リダイレクト処理の一部としてクッキーの送受信を行っていました。しかし、POST や PUT といった他のHTTPメソッドを使用するリクエストでは、Do メソッドが直接 send 関数(当時の内部関数)を呼び出しており、このパスでは CookieJar を介したクッキーの送受信ロジックが欠落していました。
この問題は、Go Issue 3985 (https://code.google.com/p/go/issues/detail?id=3985) として報告されました。ユーザーは、Client.Post や Client.Do を使って POST リクエストを送信した際に、セッションクッキーがサーバーに送信されず、またサーバーからの Set-Cookie ヘッダーがクライアントの CookieJar に保存されないという挙動に直面していました。これは、Webアプリケーションでセッション管理や認証にクッキーを使用する場合に深刻な問題となります。
このコミットは、この不整合を解消し、すべてのHTTPメソッドタイプのリクエストに対して一貫したクッキー処理を提供することを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の net/http パッケージに関する知識が必要です。
net/httpパッケージ: Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。Webアプリケーション開発において中心的な役割を果たします。http.Client: HTTPリクエストを送信し、HTTPレスポンスを受信する高レベルのクライアント構造体です。リダイレクトの自動処理、クッキー管理、認証などのポリシーを設定できます。Do(req *Request) (*Response, error):Clientの主要なメソッドで、任意のRequestを送信し、そのResponseを返します。Post(url string, bodyType string, body io.Reader) (resp *Response, err error): 指定されたURLにPOSTリクエストを送信するための便利なメソッドです。Jar CookieJar:Clientのフィールドで、クッキーを保存および取得するためのインターフェースです。このフィールドがnilでない場合、ClientはこのCookieJarを使用してクッキーを管理します。
http.Request: HTTPリクエストを表す構造体です。URL、メソッド(GET, POSTなど)、ヘッダー、ボディなどの情報を含みます。AddCookie(c *Cookie): リクエストにクッキーを追加するメソッドです。
http.Response: HTTPレスポンスを表す構造体です。ステータスコード、ヘッダー、ボディなどの情報を含みます。Cookies() []*Cookie: レスポンスに含まれるクッキーを*Cookieのスライスとして返すメソッドです。
http.CookieJarインターフェース: クッキーの保存と取得のロジックを抽象化するためのインターフェースです。SetCookies(u *url.URL, cookies []*Cookie): 指定されたURLに対するクッキーを受け取り、保存するメソッドです。Cookies(u *url.URL) []*Cookie: 指定されたURLに対して送信すべきクッキーを返すメソッドです。
- HTTPクッキー: Webサーバーがユーザーのブラウザに保存させる小さなデータです。セッション管理、ユーザーの追跡、パーソナライズなどに使用されます。HTTPリクエストの
Cookieヘッダーで送信され、HTTPレスポンスのSet-Cookieヘッダーで設定されます。 - リダイレクト: HTTPレスポンスのステータスコード(例: 301, 302, 303, 307)によって、クライアントが別のURLにリクエストを再送信するよう指示されることです。
net/httpのClientは、デフォルトでリダイレクトを自動的に追跡します。
技術的詳細
このコミットの核心的な変更は、http.Client の内部でクッキー処理を一元化するための新しいプライベートメソッド (*Client) send(req *Request) (*Response, error) を導入した点です。
以前の net/http パッケージでは、Client.Do メソッドはHTTPメソッドの種類によって異なるパスを辿っていました。
GETまたはHEADリクエストの場合:doFollowingRedirectsメソッドが呼び出され、このメソッド内でリダイレクト処理と同時にクッキーの送受信が行われていました。- それ以外のメソッド(
POST、PUTなど)の場合:send関数(当時の内部関数で、Clientのメソッドではない)が直接呼び出されていました。このsend関数はトランスポート層へのリクエスト送信のみを担当し、クッキーの送受信ロジックは含まれていませんでした。
この設計の欠陥により、POST などのリクエストでは Client.Jar が設定されていてもクッキーが送受信されず、Go Issue 3985 の問題が発生していました。
新しい (*Client) send メソッドは、このクッキー処理のロジックをカプセル化します。
- リクエストクッキーの追加:
c.Jar != nilであれば、c.Jar.Cookies(req.URL)を呼び出して、現在のリクエストURLに関連するクッキーを取得し、それらをreq.AddCookie(cookie)を使ってリクエストのヘッダーに追加します。 - リクエストの送信: 実際のHTTPリクエストは、既存の
send(req, c.Transport)関数(これはトランスポート層への実際の送信を行う)を呼び出すことで行われます。 - レスポンスクッキーの処理: リクエスト送信が成功し、
c.Jar != nilであれば、resp.Cookies()を呼び出してレスポンスに含まれるSet-Cookieヘッダーからクッキーを抽出し、それらをc.Jar.SetCookies(req.URL, resp.Cookies())を使ってCookieJarに保存します。
この新しい (*Client) send メソッドが導入されたことで、Client.Do メソッドは、GET または HEAD 以外のリクエストの場合に、直接この新しい c.send(req) を呼び出すように変更されました。また、Client.Post メソッドも同様に c.send(req) を利用するように修正されました。
さらに、doFollowingRedirects メソッド内で行われていたクッキー処理の重複ロジックも、新しい c.send(req) メソッドの呼び出しに置き換えられました。これにより、クッキー処理のロジックが (*Client) send に一元化され、コードの重複が排除され、保守性が向上しました。
また、src/pkg/net/http/jar.go から blackHoleJar という内部構造体が削除されました。これは、Client.Jar が nil の場合にクッキーを「破棄」するためのダミーの実装でしたが、クッキー処理が (*Client) send に集約されたことで、このダミー実装の必要性がなくなりました。Client.Jar が nil の場合は、(*Client) send メソッド内の if c.Jar != nil チェックによってクッキー処理がスキップされるため、明示的な「ブラックホール」実装は不要になりました。
コアとなるコードの変更箇所
このコミットによって変更された主要なファイルと行数は以下の通りです。
src/pkg/net/http/client.go: 50行変更 (33行追加, 17行削除)src/pkg/net/http/client_test.go: 4行変更 (4行追加, 0行削除)src/pkg/net/http/jar.go: 17行変更 (5行追加, 12行削除)
具体的な変更点:
src/pkg/net/http/client.go:(*Client) send(req *Request) (*Response, error)メソッドが追加されました。(*Client) Do()メソッド内で、GETまたはHEAD以外のリクエストの場合に、直接send(req, c.Transport)を呼び出す代わりに、新しく追加されたc.send(req)を呼び出すように変更されました。(*Client) doFollowingRedirects()メソッド内で、リダイレクト時のクッキー送受信ロジックがc.send(req)の呼び出しに置き換えられました。(*Client) Post()メソッド内で、クッキー送受信ロジックがc.send(req)の呼び出しに置き換えられました。Client構造体のJarフィールドのコメントが修正されました。
src/pkg/net/http/client_test.go:TestClientSendsCookieFromJarテストケースに、POSTリクエストの場合のクッキー送信を検証するアサーションが追加されました。
src/pkg/net/http/jar.go:blackHoleJar構造体とその関連メソッド (SetCookies,Cookies) が削除されました。CookieJarインターフェースのコメントが修正されました。
コアとなるコードの解説
src/pkg/net/http/client.go の変更
// 新しく追加された (*Client) send メソッド
func (c *Client) send(req *Request) (*Response, error) {
if c.Jar != nil { // CookieJarが設定されている場合のみクッキー処理を行う
for _, cookie := range c.Jar.Cookies(req.URL) { // JarからURLに対応するクッキーを取得
req.AddCookie(cookie) // リクエストにクッキーを追加
}
}
resp, err := send(req, c.Transport) // 実際のHTTPリクエストを送信
if err != nil {
return nil, err
}
if c.Jar != nil { // CookieJarが設定されている場合のみクッキー処理を行う
c.Jar.SetCookies(req.URL, resp.Cookies()) // レスポンスからクッキーを解析し、Jarを更新
}
return resp, err
}
// (*Client) Do() メソッドの変更
func (c *Client) Do(req *Request) (resp *Response, err error) {
// ... (既存のコード) ...
if req.Method == "GET" || req.Method == "HEAD" {
return c.doFollowingRedirects(req) // GET/HEADはリダイレクト処理を含む既存のパス
}
// GET/HEAD以外のリクエストは、新しく追加された c.send() を通る
return c.send(req)
}
// (*Client) doFollowingRedirects() メソッドの変更
func (c *Client) doFollowingRedirects(ireq *Request) (resp *Response, err error) {
// ... (既存のコード) ...
// 以前はここでクッキーの取得と設定を行っていたが、c.send() に集約された
// for _, cookie := range jar.Cookies(req.URL) { req.AddCookie(cookie) }
urlStr := req.URL.String()
// 以前は send() を直接呼び出していたが、c.send() を通るように変更
if resp, err = c.send(req); err != nil {
break
}
// 以前はここでレスポンスクッキーの処理を行っていたが、c.send() に集約された
// if c := resp.Cookies(); len(c) > 0 { jar.SetCookies(req.URL, c) }
// ... (既存のコード) ...
}
// (*Client) Post() メソッドの変更
func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error) {
// ... (既存のコード) ...
req.Header.Set("Content-Type", bodyType)
// 以前はここでクッキーの取得と設定、send() の呼び出しを行っていたが、c.send() に集約された
// if c.Jar != nil { ... }
// resp, err = send(req, c.Transport)
// if err == nil && c.Jar != nil { ... }
return c.send(req) // 新しく追加された c.send() を通る
}
この変更により、Client のクッキー処理ロジックが c.send() メソッドに一元化されました。これにより、Do、doFollowingRedirects、Post といったメソッドが、HTTPメソッドの種類に関わらず、一貫してクッキーを処理するようになりました。
src/pkg/net/http/client_test.go の変更
func TestClientSendsCookieFromJar(t *testing.T) {
// ... (既存のGETリクエストのテスト) ...
// POSTリクエストの場合もクッキーが送信されることを確認するテストが追加された
req, _ = NewRequest("POST", us, nil)
client.Do(req) // Note: doesn't hit network
matchReturnedCookies(t, expectedCookies, tr.req.Cookies())
}
このテストケースの追加により、POST リクエストでも CookieJar からクッキーが正しく送信されることが保証されるようになりました。
src/pkg/net/http/jar.go の変更
// blackHoleJar 構造体とその関連メソッドが削除された
// type blackHoleJar struct{}
// func (blackHoleJar) SetCookies(u *url.URL, cookies []*Cookie) {}
// func (blackHoleJar) Cookies(u *url.URL) []*Cookie { return nil }
blackHoleJar は、Client.Jar が nil の場合にクッキーを無視するためのダミー実装でした。しかし、クッキー処理が (*Client) send メソッドに集約され、そのメソッド内で c.Jar != nil のチェックが行われるようになったため、このダミー実装は不要になりました。これにより、コードの複雑性が軽減されました。
関連リンク
- Go Issue 3985: https://code.google.com/p/go/issues/detail?id=3985
- Go CL 6653049: https://golang.org/cl/6653049
参考にした情報源リンク
- Go Issue 3985 の詳細な議論
- Go言語の
net/httpパッケージのドキュメント (当時のバージョン) - HTTP クッキーに関する一般的な情報 (RFC 6265など)