[インデックス 13481] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージにおける Client
のリダイレクト処理の挙動を、Go 1の初期の動作に戻すものです。具体的には、Client.CheckRedirect
関数がエラーを返した場合に、以前の *http.Response
オブジェクトとエラーの両方を返すように修正し、その動作を検証するテストを追加しています。
コミット
commit dfd7f18130e538c53a2974988caecacd53d473f1
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Jul 18 13:48:39 2012 -0700
net/http: revert back to (and test) Go 1 CheckRedirect behavior
If a Client's CheckRedirect function returns an error, we
again return both a non-nil *Response and a non-nil error.
Fixes #3795
R=golang-dev, n13m3y3r
CC=golang-dev
https://golang.org/cl/6429044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dfd7f18130e538c53a2974988caecacd53d473f1
元コミット内容
このコミットは、net/http
パッケージの Client
におけるリダイレクト処理の挙動を、Go 1の初期の動作に戻すものです。具体的には、Client
の CheckRedirect
関数がエラーを返した場合に、nil
でない *http.Response
と nil
でないエラーの両方を返すように変更しています。これは、以前の変更によって失われたGo 1の互換性のある動作を回復させることを目的としています。また、この修正された動作を検証するためのテストケースも追加されています。
変更の背景
この変更は、Go言語のIssue #3795 (https://golang.org/issue/3795) を修正するために行われました。
Go 1の初期の net/http
パッケージでは、Client.CheckRedirect
関数がエラーを返した場合、Client
の Get
メソッド(やその他のリクエスト送信メソッド)は、リダイレクトが失敗する直前に受け取った *http.Response
オブジェクトと、CheckRedirect
が返したエラーの両方を呼び出し元に返していました。この挙動は、リダイレクトチェーンの途中で何らかの問題が発生した場合でも、直前のレスポンスヘッダ(特に Location
ヘッダなど)を検査したいというユースケースにおいて有用でした。
しかし、その後の変更(おそらくパフォーマンス最適化やコードの簡素化のため)により、CheckRedirect
がエラーを返した場合に *http.Response
が nil
になってしまうという回帰バグが発生しました。これにより、ユーザーはリダイレクト失敗時の詳細なコンテキスト(例えば、どのURLへのリダイレクトが拒否されたかを示す Location
ヘッダなど)にアクセスできなくなり、デバッグやエラーハンドリングが困難になりました。
このコミットは、この回帰バグを修正し、Go 1の初期の期待される動作、すなわち CheckRedirect
がエラーを返した場合に *http.Response
とエラーの両方を返す挙動を回復させるものです。
前提知識の解説
Go言語の net/http
パッケージ
net/http
パッケージは、Go言語でHTTPクライアントおよびサーバーを実装するための標準ライブラリです。
http.Client
: HTTPリクエストを送信し、レスポンスを受信するクライアントを表します。通常、http.DefaultClient
を使用するか、カスタム設定を持つhttp.Client
インスタンスを作成します。http.Request
: 送信するHTTPリクエストを表します。メソッド(GET, POSTなど)、URL、ヘッダ、ボディなどの情報を含みます。http.Response
: 受信したHTTPレスポンスを表します。ステータスコード、ヘッダ、ボディなどの情報を含みます。- リダイレクト (Redirect): HTTPプロトコルにおいて、サーバーがクライアントに対して別のURLへリクエストを再送信するよう指示する仕組みです。通常、3xx系のステータスコード(例: 301 Moved Permanently, 302 Found, 307 Temporary Redirect)で応答されます。
Client.CheckRedirect
フィールド:http.Client
構造体のフィールドの一つで、リダイレクトを処理する際のポリシーをカスタマイズするために使用されます。これはfunc(req *Request, via []*Request) error
型の関数ポインタです。req
: 次にリダイレクトされるリクエスト(まだ送信されていないもの)。via
: これまでに経由してきたリクエストのリスト(古いものから順)。- この関数が
nil
でない場合、Client
はリダイレクトを追跡する前にこの関数を呼び出します。 - この関数がエラーを返すと、
Client
はそのエラーを返してリダイレクトの追跡を中止します。 - この関数が
nil
を返すと、Client
はリダイレクトを追跡します。 - このフィールドが
nil
の場合、Client
はデフォルトのリダイレクトポリシー(最大10回のリダイレクトを自動的に追跡し、異なるホストへのPOSTリクエストはGETに変換する)を使用します。
url.Error
url.Error
は、net/url
パッケージで定義されているエラー型で、URLの処理中に発生したエラーをラップするために使用されます。これには、エラーが発生した操作 (Op
)、関連するURL (URL
)、および元のエラー (Err
) の情報が含まれます。net/http
パッケージでは、HTTPリクエストの送信やリダイレクト処理中に発生したエラーを url.Error
でラップして返すことがよくあります。
技術的詳細
このコミットの技術的な核心は、net/http
パッケージの Client
がリダイレクトを処理する内部関数 doFollowingRedirects
の挙動変更にあります。
以前のバグのある実装では、Client.CheckRedirect
がエラーを返してリダイレクトが中止された場合、doFollowingRedirects
関数は最終的に nil
の *http.Response
と url.Error
を返していました。これは、リダイレクトが失敗した際に、その直前の有効なレスポンスオブジェクト(特に、リダイレクト先のURLを示す Location
ヘッダを含むレスポンス)が失われることを意味しました。
このコミットでは、この問題を解決するために以下の変更が加えられています。
redirectFailed
フラグの導入:doFollowingRedirects
関数内にredirectFailed := false
という新しいブール変数が導入されました。- フラグの設定:
CheckRedirect
関数(内部的にはredirectChecker
を通じて呼び出される)がエラーを返した場合、このredirectFailed
フラグがtrue
に設定されます。 - 条件付きの戻り値:
doFollowingRedirects
関数のエラー処理部分で、redirectFailed
フラグがtrue
であるかどうかをチェックする条件分岐が追加されました。- もし
redirectFailed
がtrue
であれば、関数はresp
(リダイレクト失敗直前の*http.Response
オブジェクト) とurl.Error
の両方を返します。この際、resp.Body.Close()
は行われません。これにより、呼び出し元はresp
オブジェクトにアクセスし、そのヘッダ(例:Location
)を読み取ることができます。 - もし
redirectFailed
がfalse
であれば(つまり、エラーがCheckRedirect
以外の原因で発生した場合)、以前と同様にresp.Body.Close()
を呼び出し、nil
の*http.Response
とurl.Error
を返します。
- もし
この変更により、CheckRedirect
がエラーを返した場合でも、リダイレクトが中止された時点での最後の有効なレスポンスオブジェクトが呼び出し元に提供されるようになり、Go 1の初期の互換性のある動作が回復されました。
また、この修正された動作を検証するために、client_test.go
に新しいテストアサーションが追加されています。これにより、CheckRedirect
がエラーを返した際に *http.Response
が nil
でないこと、およびそのレスポンスに Location
ヘッダが含まれていることが保証されます。
コアとなるコードの変更箇所
src/pkg/net/http/client.go
--- a/src/pkg/net/http/client.go
+++ b/src/pkg/net/http/client.go
@@ -33,10 +33,11 @@ type Client struct {
// CheckRedirect specifies the policy for handling redirects.
// If CheckRedirect is not nil, the client calls it before
- // following an HTTP redirect. The arguments req and via
- // are the upcoming request and the requests made already,
- // oldest first. If CheckRedirect returns an error, the client
- // returns that error (wrapped in a url.Error) instead of
+ // following an HTTP redirect. The arguments req and via are
+ // the upcoming request and the requests made already, oldest
+ // first. If CheckRedirect returns an error, the Client's Get
+ // method returns both the previous Response and
+ // CheckRedirect's error (wrapped in a url.Error) instead of
// issuing the Request req.
//
// If CheckRedirect is nil, the Client uses its default policy,
@@ -221,6 +222,7 @@ func (c *Client) doFollowingRedirects(ireq *Request) (resp *Response, err error)
req := ireq
urlStr := "" // next relative or absolute URL to fetch (after first request)
+ redirectFailed := false
for redirect := 0; ; redirect++ {
if redirect != 0 {
req = new(Request)
@@ -239,6 +241,7 @@ func (c *Client) doFollowingRedirects(ireq *Request) (resp *Response, err error)
err = redirectChecker(req, via)
if err != nil {
+ redirectFailed = true
break
}
}
@@ -268,16 +271,24 @@ func (c *Client) doFollowingRedirects(ireq *Request) (resp *Response, err error)
return
}
- if resp != nil {
- resp.Body.Close()
- }
-
method := ireq.Method
- return nil, &url.Error{
+ urlErr := &url.Error{
Op: method[0:1] + strings.ToLower(method[1:]),
URL: urlStr,
Err: err,
}
+
+ if redirectFailed {
+ // Special case for Go 1 compatibility: return both the response
+ // and an error if the CheckRedirect function failed.
+ // See http://golang.org/issue/3795
+ return resp, urlErr
+ }
+
+ if resp != nil {
+ resp.Body.Close()
+ }
+ return nil, urlErr
}
func defaultCheckRedirect(req *Request, via []*Request) error {
src/pkg/net/http/client_test.go
--- a/src/pkg/net/http/client_test.go
+++ b/src/pkg/net/http/client_test.go
@@ -234,6 +234,12 @@ func TestRedirects(t *testing.T) {
if urlError, ok := err.(*url.Error); !ok || urlError.Err != checkErr {
t.Errorf("with redirects forbidden, expected a *url.Error with our 'no redirects allowed' error inside; got %#v (%q)", err, err)
}
+ if res == nil {
+ t.Fatalf("Expected a non-nil Response on CheckRedirect failure (http://golang.org/issue/3795)")
+ }
+ if res.Header.Get("Location") == "" {
+ t.Errorf("no Location header in Response")
+ }
}
var expectedCookies = []*Cookie{
コアとなるコードの解説
src/pkg/net/http/client.go
の変更点
-
Client.CheckRedirect
のコメント更新:- 変更前:
If CheckRedirect returns an error, the client returns that error (wrapped in a url.Error) instead of issuing the Request req.
- 変更後:
If CheckRedirect returns an error, the Client's Get method returns both the previous Response and CheckRedirect's error (wrapped in a url.Error) instead of issuing the Request req.
- このコメントの変更は、このコミットの意図を明確に示しています。
CheckRedirect
がエラーを返した場合に、*Response
とエラーの両方が返されるという新しい(Go 1の初期の)挙動を明記しています。
- 変更前:
-
doFollowingRedirects
関数内の変更:redirectFailed := false
の追加: リダイレクトがCheckRedirect
のエラーによって失敗したかどうかを追跡するためのフラグが導入されました。if err != nil { redirectFailed = true; break }
の追加:redirectChecker
(内部でCheckRedirect
を呼び出す) がエラーを返した場合、redirectFailed
フラグをtrue
に設定し、リダイレクトループを中断します。- エラーハンドリングロジックの変更:
- 変更前は、
resp
がnil
でない場合にresp.Body.Close()
を呼び出し、常にnil
の*Response
とurl.Error
を返していました。 - 変更後、
urlErr
という変数にurl.Error
を格納した後、if redirectFailed
の条件分岐が追加されました。 if redirectFailed
がtrue
の場合、resp
(リダイレクト失敗直前のレスポンス) とurlErr
をそのまま返します。このパスではresp.Body.Close()
は呼び出されません。これにより、呼び出し元はresp
オブジェクトにアクセスできます。if redirectFailed
がfalse
の場合(つまり、CheckRedirect
以外でエラーが発生した場合)、以前と同様にresp.Body.Close()
を呼び出し、nil
の*Response
とurlErr
を返します。
- 変更前は、
src/pkg/net/http/client_test.go
の変更点
TestRedirects
関数内に、CheckRedirect
がエラーを返した際の挙動を検証する新しいアサーションが追加されました。
if res == nil { t.Fatalf("Expected a non-nil Response on CheckRedirect failure (http://golang.org/issue/3795)") }
:- このアサーションは、
CheckRedirect
がエラーを返した場合でも、doFollowingRedirects
関数がnil
でない*http.Response
オブジェクトを返すことを保証します。これは、Issue #3795 で報告された回帰バグの直接的な修正を検証するものです。
- このアサーションは、
if res.Header.Get("Location") == "" { t.Errorf("no Location header in Response") }
:- このアサーションは、返された
*http.Response
オブジェクトに、リダイレクト先のURLを示すLocation
ヘッダが含まれていることを確認します。これは、ユーザーがリダイレクト失敗時にどのURLへのリダイレクトが拒否されたかを特定できるようにするために重要です。
- このアサーションは、返された
これらの変更により、net/http
クライアントのリダイレクト処理は、Go 1の初期の期待される動作に戻り、より堅牢なエラーハンドリングと情報提供が可能になりました。
関連リンク
- Go Issue #3795:
net/http: CheckRedirect error returns nil Response
- https://golang.org/issue/3795 - Go Change-List 6429044:
net/http: revert back to (and test) Go 1 CheckRedirect behavior
- https://golang.org/cl/6429044
参考にした情報源リンク
- 上記のGo IssueおよびChange-Listのページ
- Go言語の
net/http
パッケージのドキュメント (Go 1.0当時のものを含む) - HTTPリダイレクトに関する一般的な知識
- Go言語のテストフレームワーク
testing
に関する知識