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

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

このコミットは、Go言語の net/http パッケージにおけるクライアントの戻り値のドキュメントを明確にし、特に http.ClientDo メソッドやリダイレクト処理において、Responseerror の両方が非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 テストの失敗時)で Responseerror の両方が非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.Requesthttp.Response: http.Request は送信されるHTTPリクエストを表し、http.Response は受信されるHTTPレスポンスを表します。
  • http.RoundTripper: http.Request を受け取り、http.Response を返すインターフェースです。http.Client は内部で RoundTripper を使用して実際のHTTP通信を行います。http.DefaultTransport がデフォルトの RoundTripper です。
  • Go言語のエラーハンドリング: Goでは、エラーは通常、関数の最後の戻り値として error 型で返されます。慣習として、errornil でない場合はエラーが発生したことを意味し、その場合、他の戻り値は通常、その型のゼロ値または nil であるべきです。
  • HTTPリダイレクト: HTTPステータスコード3xx(例: 301 Moved Permanently, 302 Found, 307 Temporary Redirect)は、クライアントが別のURLにリクエストを再送信する必要があることを示します。http.Client はデフォルトでこれらのリダイレクトを自動的に追跡します。
  • resp.Body.Close(): http.ResponseBody フィールドは io.ReadCloser インターフェースを実装しており、HTTPレスポンスのボディを読み取るためのストリームです。このボディは、読み取りが完了した後、またはエラーが発生した場合でも、必ず Close() メソッドを呼び出して閉じる必要があります。これを怠ると、基盤となるTCP接続が解放されず、接続リークやリソース枯渇の原因となる可能性があります。特に、http.Client が持続的なTCP接続(Keep-Alive)を再利用できるようにするためには、Body を適切に閉じることが重要です。

技術的詳細

このコミットの技術的な核心は、Go言語におけるエラーハンドリングの慣習 ((result, error) のペアで、エラー時は result がゼロ値/nilであるべき) に net/http クライアントの挙動を合わせることにあります。

具体的には、http.ClientDo メソッドや、その内部で呼び出される 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の慣習に沿ったものとなります(エラー時は respnil)。

また、doFollowingRedirects 関数では、リダイレクト処理中にエラーが発生した場合に、それまでに取得した respBody を確実に閉じるように修正されました。これは、エラーパスにおいてもリソースリークを防ぐための重要な変更です。

さらに、Client.Do, Get, Post, PostForm, Head といった主要なクライアントメソッドのドキュメントコメントが更新され、以下の点が明確化されました。

  • 「エラーはクライアントポリシー(CheckRedirectなど)またはHTTPプロトコルエラーによって返される。」
  • 「2xx以外のレスポンスはエラーを引き起こさない。」
  • errnil の場合、resp は常に非nilの resp.Body を含む。」
  • 「呼び出し元は resp.Body の読み取りが完了したら必ず resp.Body.Close() を呼び出すべきである。」

これらのドキュメントの明確化は、開発者が net/http クライアントの戻り値を正しく理解し、適切にエラーハンドリングとリソース管理を行う上で非常に重要です。

コアとなるコードの変更箇所

変更は主に src/pkg/net/http/client.go に集中しています。

  1. 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
       }
      
  2. src/pkg/net/http/client_test.go:

    • TestRedirects 関数から不要な finalUrl = res.Request.URL.String() の行が削除されました。これは、リダイレクトが禁止された場合に resnil になるため、その後のアクセスがパニックを引き起こす可能性があったためと考えられます。

コアとなるコードの解説

  • send 関数の変更: send 関数は、http.ClientDo メソッドやリダイレクト処理の内部で、実際のHTTPリクエストを RoundTripper に送信する役割を担っています。 変更前は、t.RoundTrip(req) の結果をそのまま返していました。しかし、RoundTripper の実装によっては、エラーが発生した場合でも部分的な Response オブジェクト(例えば、ヘッダーのみが受信されたがボディの読み取り中にエラーが発生した場合など)を返すことがありました。この場合、resperr の両方が非nilとなり、Goの慣習に反していました。 変更後は、resp, err = t.RoundTrip(req) で結果を受け取った後、if err != nil でエラーの有無をチェックします。もしエラーがあれば、resp が非nilであっても、その resp は破棄され、return nil, err となります。これにより、send 関数は常に「エラーがあれば respnil」というGoの慣習に従うようになります。log.Printf の行は、このような稀なケース(RoundTripperresperr の両方を返す)が実際に発生した際に、デバッグ情報としてログに出力するためのものです。

  • doFollowingRedirects 関数の変更: この関数は、HTTPリダイレクトを自動的に追跡するロジックを実装しています。リダイレクトチェーンの途中でエラーが発生した場合、以前のバージョンでは、エラーを返す前に最後に取得した respBody が閉じられない可能性がありました。 追加された if resp != nil { resp.Body.Close() } のブロックは、リダイレクト処理中にエラーが発生し、かつ resp が非nilである場合に、その respBody を確実に閉じることを保証します。これにより、リソースリークを防ぎ、TCP接続が適切に解放されるようになります。また、return nil, &url.Error{...} の形式に変更されたことで、エラー発生時には respnil であることが保証されます。

  • ドキュメントコメントの更新: Client.DoGet, Post などの主要なクライアントメソッドのドキュメントコメントは、戻り値のセマンティクスを明確にする上で非常に重要です。特に、「errnil の場合、resp は常に非nilの resp.Body を含む」という記述は、Goのエラーハンドリングの慣習を明示的に示しており、開発者が net/http クライアントをより安全かつ正確に使用するための指針となります。また、resp.Body.Close() の呼び出しの重要性を繰り返し強調することで、リソース管理のベストプラクティスを促しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント: net/http パッケージ
  • Go言語のエラーハンドリングに関する慣習とベストプラクティス
  • HTTPプロトコルのリダイレクトに関する仕様 (RFC 7231など)