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

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

このコミットは、Go言語の net/http パッケージにおける Client 型のクッキー(Cookie)処理に関するバグ修正です。具体的には、(*Client) Do() メソッドを通じて GET または HEAD 以外のHTTPリクエスト(例: POSTPUTなど)を送信する際に、クッキーが適切に処理されない問題を解決します。この修正により、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 のクッキー管理の不整合がありました。元々、ClientDo メソッドは、GET および HEAD リクエストの場合にのみ、リダイレクト処理の一部としてクッキーの送受信を行っていました。しかし、POSTPUT といった他のHTTPメソッドを使用するリクエストでは、Do メソッドが直接 send 関数(当時の内部関数)を呼び出しており、このパスでは CookieJar を介したクッキーの送受信ロジックが欠落していました。

この問題は、Go Issue 3985 (https://code.google.com/p/go/issues/detail?id=3985) として報告されました。ユーザーは、Client.PostClient.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/httpClient は、デフォルトでリダイレクトを自動的に追跡します。

技術的詳細

このコミットの核心的な変更は、http.Client の内部でクッキー処理を一元化するための新しいプライベートメソッド (*Client) send(req *Request) (*Response, error) を導入した点です。

以前の net/http パッケージでは、Client.Do メソッドはHTTPメソッドの種類によって異なるパスを辿っていました。

  • GET または HEAD リクエストの場合: doFollowingRedirects メソッドが呼び出され、このメソッド内でリダイレクト処理と同時にクッキーの送受信が行われていました。
  • それ以外のメソッド(POSTPUTなど)の場合: send 関数(当時の内部関数で、Client のメソッドではない)が直接呼び出されていました。この send 関数はトランスポート層へのリクエスト送信のみを担当し、クッキーの送受信ロジックは含まれていませんでした。

この設計の欠陥により、POST などのリクエストでは Client.Jar が設定されていてもクッキーが送受信されず、Go Issue 3985 の問題が発生していました。

新しい (*Client) send メソッドは、このクッキー処理のロジックをカプセル化します。

  1. リクエストクッキーの追加: c.Jar != nil であれば、c.Jar.Cookies(req.URL) を呼び出して、現在のリクエストURLに関連するクッキーを取得し、それらを req.AddCookie(cookie) を使ってリクエストのヘッダーに追加します。
  2. リクエストの送信: 実際のHTTPリクエストは、既存の send(req, c.Transport) 関数(これはトランスポート層への実際の送信を行う)を呼び出すことで行われます。
  3. レスポンスクッキーの処理: リクエスト送信が成功し、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.Jarnil の場合にクッキーを「破棄」するためのダミーの実装でしたが、クッキー処理が (*Client) send に集約されたことで、このダミー実装の必要性がなくなりました。Client.Jarnil の場合は、(*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行削除)

具体的な変更点:

  1. 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 フィールドのコメントが修正されました。
  2. src/pkg/net/http/client_test.go:
    • TestClientSendsCookieFromJar テストケースに、POST リクエストの場合のクッキー送信を検証するアサーションが追加されました。
  3. 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() メソッドに一元化されました。これにより、DodoFollowingRedirectsPost といったメソッドが、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.Jarnil の場合にクッキーを無視するためのダミー実装でした。しかし、クッキー処理が (*Client) send メソッドに集約され、そのメソッド内で c.Jar != nil のチェックが行われるようになったため、このダミー実装は不要になりました。これにより、コードの複雑性が軽減されました。

関連リンク

参考にした情報源リンク

  • Go Issue 3985 の詳細な議論
  • Go言語の net/http パッケージのドキュメント (当時のバージョン)
  • HTTP クッキーに関する一般的な情報 (RFC 6265など)