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