[インデックス 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など)