[インデックス 13363] ファイルの概要
このコミットは、Go言語の net/http パッケージにおけるクライアントの戻り値のドキュメントを明確にし、特に http.Client の Do メソッドやリダイレクト処理において、Response と error の両方が非nilとなる可能性があった一貫性のない挙動を修正することを目的としています。Goの慣習的なエラーハンドリングに則り、エラーが発生した場合には Response がnilとなるように変更されています。
コミット
commit e1d9fcd267e1a827e9841dce4c91def0777a90ee
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Tue Jun 19 09:10:14 2012 -0700
net/http: clarify client return values in docs
Also, fixes one violation found during testing where both
response and error could be non-nil when a CheckRedirect test
failed. This is arguably a minor API (behavior, not
signature) change, but it wasn't documented either way and was
inconsistent & non-Go like. Any code depending on the old
behavior was wrong anyway.
R=adg, rsc
CC=golang-dev
https://golang.org/cl/6307088
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e1d9fcd267e1a827e9841dce4c91def0777a90ee
元コミット内容
このコミットの主な内容は、net/http パッケージのクライアント関数(Do, Get, Post, Head など)の戻り値に関するドキュメントの明確化と、テスト中に発見された特定のケース(CheckRedirect テストの失敗時)で Response と error の両方が非nilで返されるというGoの慣習に反する挙動の修正です。コミットメッセージでは、この変更はAPIの振る舞いに関するものであり、シグネチャの変更ではないと述べられています。また、以前の挙動に依存していたコードはそもそも誤っていたと指摘されています。
変更の背景
Go言語では、関数がエラーを返す場合、通常は (result, error) の形式で値を返します。この際、error が非nilであれば、result はその型のゼロ値(ポインタ型の場合は nil)であることが慣習とされています。これは、呼び出し側が error が非nilであるかどうかを確認するだけで、処理が成功したか失敗したかを明確に判断できるようにするためです。
しかし、net/http パッケージのクライアントの一部では、リダイレクト処理や CheckRedirect 関数の失敗時に、Response オブジェクトと error オブジェクトの両方が非nilで返されるという、この慣習に反する挙動が見られました。これは、呼び出し側が Response オブジェクトを処理すべきか、それともエラーハンドリングに専念すべきかという点で混乱を招く可能性がありました。
このコミットは、この一貫性のない、"non-Go like" な挙動を修正し、ドキュメントを更新することで、net/http クライアントの戻り値のセマンティクスを明確にし、Goのエラーハンドリングの慣習に準拠させることを目的としています。これにより、クライアントコードの堅牢性と予測可能性が向上します。
前提知識の解説
- Go言語の
net/httpパッケージ: Go言語の標準ライブラリの一部で、HTTPクライアントとサーバーの実装を提供します。ウェブアプリケーションやAPIクライアントを構築する上で不可欠なパッケージです。 http.Client: HTTPリクエストを送信し、HTTPレスポンスを受信するクライアントを表す構造体です。リダイレクトの処理、クッキーの管理、認証などのポリシーを設定できます。http.Requestとhttp.Response:http.Requestは送信されるHTTPリクエストを表し、http.Responseは受信されるHTTPレスポンスを表します。http.RoundTripper:http.Requestを受け取り、http.Responseを返すインターフェースです。http.Clientは内部でRoundTripperを使用して実際のHTTP通信を行います。http.DefaultTransportがデフォルトのRoundTripperです。- Go言語のエラーハンドリング: Goでは、エラーは通常、関数の最後の戻り値として
error型で返されます。慣習として、errorがnilでない場合はエラーが発生したことを意味し、その場合、他の戻り値は通常、その型のゼロ値またはnilであるべきです。 - HTTPリダイレクト: HTTPステータスコード3xx(例: 301 Moved Permanently, 302 Found, 307 Temporary Redirect)は、クライアントが別のURLにリクエストを再送信する必要があることを示します。
http.Clientはデフォルトでこれらのリダイレクトを自動的に追跡します。 resp.Body.Close():http.ResponseのBodyフィールドはio.ReadCloserインターフェースを実装しており、HTTPレスポンスのボディを読み取るためのストリームです。このボディは、読み取りが完了した後、またはエラーが発生した場合でも、必ずClose()メソッドを呼び出して閉じる必要があります。これを怠ると、基盤となるTCP接続が解放されず、接続リークやリソース枯渇の原因となる可能性があります。特に、http.Clientが持続的なTCP接続(Keep-Alive)を再利用できるようにするためには、Bodyを適切に閉じることが重要です。
技術的詳細
このコミットの技術的な核心は、Go言語におけるエラーハンドリングの慣習 ((result, error) のペアで、エラー時は result がゼロ値/nilであるべき) に net/http クライアントの挙動を合わせることにあります。
具体的には、http.Client の Do メソッドや、その内部で呼び出される send 関数、そしてリダイレクトを処理する doFollowingRedirects 関数において、RoundTripper が (resp, err) の両方を非nilで返した場合の処理が変更されました。
変更前は、RoundTripper がエラーを返しても resp が非nilである場合、その resp がそのまま呼び出し元に返される可能性がありました。これは、呼び出し元が if err != nil でエラーをチェックした際に、同時に resp も有効であると誤解する可能性を生み出していました。
このコミットでは、send 関数内で t.RoundTrip(req) の結果を受け取った後、if err != nil の条件が真であれば、たとえ resp が非nilであっても、resp を破棄して return nil, err とするように修正されました。これにより、send 関数から返される (resp, err) のペアは、常にGoの慣習に沿ったものとなります(エラー時は resp が nil)。
また、doFollowingRedirects 関数では、リダイレクト処理中にエラーが発生した場合に、それまでに取得した resp の Body を確実に閉じるように修正されました。これは、エラーパスにおいてもリソースリークを防ぐための重要な変更です。
さらに、Client.Do, Get, Post, PostForm, Head といった主要なクライアントメソッドのドキュメントコメントが更新され、以下の点が明確化されました。
- 「エラーはクライアントポリシー(
CheckRedirectなど)またはHTTPプロトコルエラーによって返される。」 - 「2xx以外のレスポンスはエラーを引き起こさない。」
- 「
errがnilの場合、respは常に非nilのresp.Bodyを含む。」 - 「呼び出し元は
resp.Bodyの読み取りが完了したら必ずresp.Body.Close()を呼び出すべきである。」
これらのドキュメントの明確化は、開発者が net/http クライアントの戻り値を正しく理解し、適切にエラーハンドリングとリソース管理を行う上で非常に重要です。
コアとなるコードの変更箇所
変更は主に src/pkg/net/http/client.go に集中しています。
-
src/pkg/net/http/client.go:import文にlogパッケージが追加されました。Client.Doメソッドのドキュメントコメントが大幅に修正され、戻り値のセマンティクスが明確化されました。send関数内でt.RoundTrip(req)の結果を処理するロジックが変更されました。--- a/src/pkg/net/http/client.go +++ b/src/pkg/net/http/client.go @@ -130,7 +136,14 @@ func send(req *Request, t RoundTripper) (resp *Response, err error) { if u := req.URL.User; u != nil { req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(u.String())))) } - return t.RoundTrip(req) + resp, err = t.RoundTrip(req) + if err != nil { + if resp != nil { + log.Printf("RoundTripper returned a response & error; ignoring response") + } + return nil, err + } + return resp, nil }Get,Client.Get,Post,Client.Post,PostForm,Client.PostForm,Head,Client.Headメソッドのドキュメントコメントが更新され、戻り値のセマンティクスとresp.Body.Close()の必要性が明確化されました。Client.doFollowingRedirects関数内で、リダイレクト処理中にエラーが発生した場合にresp.Body.Close()を呼び出すロジックが追加されました。--- a/src/pkg/net/http/client.go +++ b/src/pkg/net/http/client.go @@ -244,13 +267,16 @@ func (c *Client) doFollowingRedirects(ireq *Request) (r *Response, err error) { \treturn\n \t}\n \n +\tif resp != nil { +\t\tresp.Body.Close() +\t} +\n \tmethod := ireq.Method -\terr = &url.Error{ +\treturn nil, &url.Error{ \tOp: method[0:1] + strings.ToLower(method[1:]), \tURL: urlStr, \tErr: err, } -\treturn +\t// return is now implicit due to the above line }
-
src/pkg/net/http/client_test.go:TestRedirects関数から不要なfinalUrl = res.Request.URL.String()の行が削除されました。これは、リダイレクトが禁止された場合にresがnilになるため、その後のアクセスがパニックを引き起こす可能性があったためと考えられます。
コアとなるコードの解説
-
send関数の変更:send関数は、http.ClientのDoメソッドやリダイレクト処理の内部で、実際のHTTPリクエストをRoundTripperに送信する役割を担っています。 変更前は、t.RoundTrip(req)の結果をそのまま返していました。しかし、RoundTripperの実装によっては、エラーが発生した場合でも部分的なResponseオブジェクト(例えば、ヘッダーのみが受信されたがボディの読み取り中にエラーが発生した場合など)を返すことがありました。この場合、respとerrの両方が非nilとなり、Goの慣習に反していました。 変更後は、resp, err = t.RoundTrip(req)で結果を受け取った後、if err != nilでエラーの有無をチェックします。もしエラーがあれば、respが非nilであっても、そのrespは破棄され、return nil, errとなります。これにより、send関数は常に「エラーがあればrespはnil」というGoの慣習に従うようになります。log.Printfの行は、このような稀なケース(RoundTripperがrespとerrの両方を返す)が実際に発生した際に、デバッグ情報としてログに出力するためのものです。 -
doFollowingRedirects関数の変更: この関数は、HTTPリダイレクトを自動的に追跡するロジックを実装しています。リダイレクトチェーンの途中でエラーが発生した場合、以前のバージョンでは、エラーを返す前に最後に取得したrespのBodyが閉じられない可能性がありました。 追加されたif resp != nil { resp.Body.Close() }のブロックは、リダイレクト処理中にエラーが発生し、かつrespが非nilである場合に、そのrespのBodyを確実に閉じることを保証します。これにより、リソースリークを防ぎ、TCP接続が適切に解放されるようになります。また、return nil, &url.Error{...}の形式に変更されたことで、エラー発生時にはrespがnilであることが保証されます。 -
ドキュメントコメントの更新:
Client.DoやGet,Postなどの主要なクライアントメソッドのドキュメントコメントは、戻り値のセマンティクスを明確にする上で非常に重要です。特に、「errがnilの場合、respは常に非nilのresp.Bodyを含む」という記述は、Goのエラーハンドリングの慣習を明示的に示しており、開発者がnet/httpクライアントをより安全かつ正確に使用するための指針となります。また、resp.Body.Close()の呼び出しの重要性を繰り返し強調することで、リソース管理のベストプラクティスを促しています。
関連リンク
- Go CL 6307088: https://golang.org/cl/6307088
参考にした情報源リンク
- Go言語の公式ドキュメント:
net/httpパッケージ - Go言語のエラーハンドリングに関する慣習とベストプラクティス
- HTTPプロトコルのリダイレクトに関する仕様 (RFC 7231など)