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

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

このコミットは、Go言語の標準ライブラリ net/http パッケージにおける Client.Post メソッドの挙動、特にリクエストボディが io.Reader かつ io.Closer の両方を実装している場合の扱いについて、ドキュメントの明確化とテストの追加を行ったものです。これにより、net/http クライアントがリクエストボディを適切にクローズする責任があることが明示され、その動作が保証されるようになりました。

コミット

commit b2b15d1230f3b4f669b5dd171c31e1b081bffede
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Fri Jul 19 12:02:54 2013 +1000

    net/http: document and test Client.Post treating Reader body as ReadCloser
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/11542044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/b2b15d1230f3b4f669b5dd171c31e1b081bffede

元コミット内容

net/http: document and test Client.Post treating Reader body as ReadCloser

このコミットは、net/http パッケージの Client.Post 関数が、io.Reader として渡されたリクエストボディが io.Closer インターフェースも実装している場合に、そのボディを ReadCloser として扱い、適切にクローズすることについて、ドキュメントを整備し、テストを追加するものです。

変更の背景

Goの net/http パッケージでは、HTTPリクエストのボディとして io.Reader インターフェースを実装する任意の型を受け入れることができます。しかし、ネットワーク操作においては、リソースのリークを防ぐために、読み取りが完了したストリームは適切にクローズされる必要があります。特に、リクエストボディがファイルやネットワーク接続など、クローズが必要なリソースを表す場合、そのクローズの責任がどこにあるのかが不明確であると、リソースリークや予期せぬ動作につながる可能性があります。

このコミット以前は、Client.Post に渡された io.Readerio.Closer も実装している場合に、net/http パッケージがその Close メソッドを呼び出すかどうかの挙動が明示的にドキュメント化されていませんでした。また、RoundTripper インターフェースのドキュメントも、リクエストボディの「消費」については言及していましたが、「クローズ」については明確ではありませんでした。

この曖昧さを解消し、開発者が安心して net/http クライアントを使用できるようにするため、以下の点が課題となっていました。

  1. ドキュメントの不足: Client.Postio.Closer を実装するボディをどのように扱うかについての公式な説明がなかった。
  2. 責任の不明確さ: RoundTripper がリクエストボディをクローズする責任を負うべきかどうかが不明確だった。
  3. テストの不足: この重要な挙動を保証するための自動テストが存在しなかった。

このコミットは、これらの課題に対処し、net/http パッケージの堅牢性と使いやすさを向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的なインターフェースと net/http パッケージの概念を理解しておく必要があります。

  • io.Reader インターフェース: Read(p []byte) (n int, err error) メソッドを持つインターフェースです。データを読み出すための一般的な抽象化を提供します。ファイル、ネットワーク接続、メモリ上のバイト列など、様々なデータソースからデータを読み出す際に使用されます。

  • io.Closer インターフェース: Close() error メソッドを持つインターフェースです。リソース(ファイルハンドル、ネットワークソケットなど)を解放するための一般的な抽象化を提供します。io.Readerio.Writer と組み合わせて使用されることが多く、リソースのリークを防ぐために重要です。

  • io.ReadCloser インターフェース: io.Readerio.Closer の両方を埋め込んだインターフェースです。読み取りとクローズの両方の機能を持つリソースを表します。

  • net/http パッケージ: Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。WebアプリケーションやAPIクライアントを構築する上で不可欠なパッケージです。

  • net/http.Client: HTTPリクエストを送信するためのクライアント構造体です。Get, Post, Do などのメソッドを提供し、HTTP通信を行います。

  • net/http.Request: HTTPリクエストを表す構造体です。Body フィールドは io.ReadCloser 型であり、リクエストボディのデータを保持します。

  • net/http.RoundTripper インターフェース: RoundTrip(*Request) (*Response, error) メソッドを持つインターフェースです。HTTPリクエストを送信し、HTTPレスポンスを受信する単一のHTTPトランザクションを表します。net/http.Client は内部的に RoundTripper を使用してリクエストを処理します。カスタムの RoundTripper を実装することで、リクエストの変更、認証、プロキシ設定などの高度な処理を行うことができます。

技術的詳細

このコミットの技術的な核心は、net/http パッケージがリクエストボディの io.Closer インターフェースをどのように尊重し、いつ Close() メソッドを呼び出すかという点にあります。

HTTPリクエストのボディは、net/http.Client.Post メソッドに io.Reader として渡されます。しかし、この io.Reader が同時に io.Closer インターフェースも実装している場合(つまり io.ReadCloser である場合)、net/http パッケージは、ボディのデータがサーバーに正常に書き込まれた後に、その Close() メソッドを呼び出す責任を負います。これは、ファイルやネットワークストリームなど、基になるリソースを適切に解放するために不可欠です。

この挙動は、net/http.Client が内部で使用する net/http.RoundTripper インターフェースの実装によって処理されます。RoundTripper の役割は、リクエストを送信し、レスポンスを受け取るだけでなく、リクエストボディの消費とクローズも含まれるべきであると、このコミットによって明確にされました。

具体的には、以下の変更が行われました。

  1. RoundTripper インターフェースのドキュメント更新: RoundTripperRoundTrip メソッドのドキュメントに、「リクエストのBodyを消費し、クローズする」という記述が追加されました。これにより、RoundTripper の実装者がリクエストボディのクローズ責任を負うことが明確になりました。

  2. Client.Post メソッドのドキュメント更新: Client.Post メソッドのドキュメントに、「提供されたボディが io.Closer でもある場合、ボディがサーバーに正常に書き込まれた後にクローズされる」という記述が追加されました。これは、Client.Post を使用する開発者に対して、この自動クローズ挙動を明示的に通知するものです。

  3. テストケースの追加: TestTransportClosesRequestBody という新しいテスト関数が src/pkg/net/http/transport_test.go に追加されました。このテストは、カスタムの countCloseReader 型(io.Readerio.Closer を実装し、Close() が呼ばれるとカウンタをインクリメントする)を使用して、Client.Post が実際にボディの Close() メソッドを呼び出すことを検証します。これにより、将来の変更によってこの重要な挙動が損なわれることを防ぎます。

これらの変更により、net/http パッケージは、リクエストボディのライフサイクル管理においてより予測可能で堅牢な挙動を提供するようになりました。開発者は、io.Closer を実装するリクエストボディを Client.Post に渡す際に、明示的に Close() を呼び出す必要がなくなり、リソースリークのリスクが低減されます。

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

このコミットにおけるコアとなるコードの変更箇所は以下の2つのファイルです。

  1. src/pkg/net/http/client.go

    • RoundTripper インターフェースのコメントが変更されました。
    • Client.Post メソッドのコメントが追加されました。
  2. src/pkg/net/http/transport_test.go

    • TestTransportClosesRequestBody という新しいテスト関数が追加されました。
    • countCloseReader というヘルパー型が追加されました。

src/pkg/net/http/client.go の変更点

--- a/src/pkg/net/http/client.go
+++ b/src/pkg/net/http/client.go
@@ -74,8 +74,8 @@ type RoundTripper interface {
  	// authentication, or cookies.
  	//
  	// RoundTrip should not modify the request, except for
- 	// consuming the Body.  The request's URL and Header fields
- 	// are guaranteed to be initialized.
+ 	// consuming and closing the Body. The request's URL and
+ 	// Header fields are guaranteed to be initialized.
  	RoundTrip(*Request) (*Response, error)
  }
  
@@ -346,6 +346,9 @@ func Post(url string, bodyType string, body io.Reader) (resp *Response, err erro
  // Post issues a POST to the specified URL.
  //
  // Caller should close resp.Body when done reading from it.
+//
+// If the provided body is also an io.Closer, it is closed after the
+// body is successfully written to the server.
  func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error) {
  	req, err := NewRequest("POST", url, body)
  	if err != nil {

src/pkg/net/http/transport_test.go の変更点

--- a/src/pkg/net/http/transport_test.go
+++ b/src/pkg/net/http/transport_test.go
@@ -15,6 +15,7 @@ import (
  	"io"
  	"io/ioutil"
  	"net"
+ 	"net/http"
  	. "net/http"
  	"net/http/httptest"
  	"net/url"
@@ -1610,6 +1611,42 @@ func TestIdleConnChannelLeak(t *testing.T) {
  	}
  }
  
+// Verify the status quo: that the Client.Post function coerces its
+// body into a ReadCloser if it's a Closer, and that the Transport
+// then closes it.
+func TestTransportClosesRequestBody(t *testing.T) {
+	defer afterTest(t)
+	ts := httptest.NewServer(http.HandlerFunc(func(w ResponseWriter, r *Request) {
+		io.Copy(ioutil.Discard, r.Body)
+	}))
+	defer ts.Close()
+
+	tr := &Transport{}
+	defer tr.CloseIdleConnections()
+	cl := &Client{Transport: tr}
+
+	closes := 0
+
+	res, err := cl.Post(ts.URL, "text/plain", countCloseReader{&closes, strings.NewReader("hello")})
+	if err != nil {
+		t.Fatal(err)
+	}
+	res.Body.Close()
+	if closes != 1 {
+		t.Errorf("closes = %d; want 1", closes)
+	}
+}
+
+type countCloseReader struct {
+	n *int
+	io.Reader
+}
+
+func (cr countCloseReader) Close() error {
+	(*cr.n)++
+	return nil
+}
+
  // rgz is a gzip quine that uncompresses to itself.
  var rgz = []byte{
  	0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,

コアとなるコードの解説

src/pkg/net/http/client.go の変更点

  1. RoundTripper インターフェースのコメント修正: RoundTripper インターフェースの RoundTrip メソッドのドキュメントが更新されました。 変更前: // consuming the Body. 変更後: // consuming and closing the Body. この変更は、RoundTripper の実装がリクエストボディを単に「消費する」だけでなく、「クローズする」責任も負うことを明確にしています。これは、リソースリークを防ぐ上で非常に重要な指針となります。net/http パッケージのデフォルトの Transport 実装は、この責任を適切に果たします。

  2. Client.Post メソッドのコメント追加: Client.Post メソッドのドキュメントに以下の新しい行が追加されました。

    // If the provided body is also an io.Closer, it is closed after the
    // body is successfully written to the server.
    

    このコメントは、Client.Post に渡される io.Readerio.Closer インターフェースも実装している場合、net/http パッケージがその Close() メソッドを自動的に呼び出すことを明示しています。これは、開発者がリクエストボディとして os.Filebytes.Buffer のような io.Closer を渡した場合に、明示的に defer file.Close() などと書く必要がないことを示唆しています(ただし、エラーハンドリングによっては必要になる場合もあります)。この自動クローズは、ボディのデータがサーバーに正常に書き込まれた後に実行されます。

src/pkg/net/http/transport_test.go の変更点

  1. countCloseReader 型の追加: このコミットでは、テストのために countCloseReader という新しい型が定義されました。

    type countCloseReader struct {
    	n *int
    	io.Reader
    }
    
    func (cr countCloseReader) Close() error {
    	(*cr.n)++
    	return nil
    }
    

    この型は、既存の io.Reader をラップし、io.Closer インターフェースも実装しています。Close() メソッドが呼び出されるたびに、ポインタ n が指す整数カウンタをインクリメントします。これにより、テスト内で Close() メソッドが何回呼び出されたかを追跡できます。

  2. TestTransportClosesRequestBody テスト関数の追加: この新しいテスト関数は、Client.Postio.Closer を実装するリクエストボディを適切にクローズすることを確認します。

    • httptest.NewServer を使用して、テスト用のHTTPサーバーを起動します。このサーバーは、受信したリクエストボディを単に読み捨てます。
    • net/http.Transportnet/http.Client のインスタンスを作成します。
    • closes という整数変数を定義し、そのアドレスを countCloseReader に渡します。
    • cl.Post を呼び出し、リクエストボディとして countCloseReader のインスタンスを渡します。
    • res.Body.Close() を呼び出してレスポンスボディをクローズします(これはリクエストボディのクローズとは別です)。
    • 最後に、closes 変数の値が 1 であることをアサートします。これは、countCloseReaderClose() メソッドが正確に1回呼び出されたことを意味し、net/http パッケージがリクエストボディを適切にクローズしたことを証明します。

これらの変更は、net/http パッケージの内部的な挙動を明確にし、その堅牢性を高めるための重要なステップです。特に、リソース管理の側面において、開発者がより安全にHTTPクライアントを使用できるようになります。

関連リンク

  • Go CL (Code Review) リンク: https://golang.org/cl/11542044 このコミットの元のコードレビューページです。詳細な議論や変更履歴を確認できます。

参考にした情報源リンク

  • Go言語公式ドキュメント - io パッケージ: https://pkg.go.dev/io io.Reader, io.Closer, io.ReadCloser インターフェースに関する公式ドキュメントです。

  • Go言語公式ドキュメント - net/http パッケージ: https://pkg.go.dev/net/http net/http.Client, net/http.Request, net/http.RoundTripper など、HTTPクライアントとサーバーに関する公式ドキュメントです。

  • Go言語公式ドキュメント - net/http/httptest パッケージ: https://pkg.go.dev/net/http/httptest HTTPテストサーバーの作成に使用される httptest.NewServer に関する公式ドキュメントです。

  • Go言語のソースコード: この解説は、Go言語の公式リポジトリにある実際のソースコード(src/pkg/net/http/client.go および src/pkg/net/http/transport_test.go)を直接参照して作成されました。

このコミットは、Go言語の標準ライブラリ net/http パッケージにおける Client.Post メソッドの挙動、特にリクエストボディが io.Reader かつ io.Closer の両方を実装している場合の扱いについて、ドキュメントの明確化とテストの追加を行ったものです。これにより、net/http クライアントがリクエストボディを適切にクローズする責任があることが明示され、その動作が保証されるようになりました。

コミット

commit b2b15d1230f3b4f669b5dd171c31e1b081bffede
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Fri Jul 19 12:02:54 2013 +1000

    net/http: document and test Client.Post treating Reader body as ReadCloser
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/11542044

GitHub上でのコミットページへのリンク

https://github.com/golang.com/go/commit/b2b15d1230f3b4f669b5dd171c31e1b081bffede

元コミット内容

net/http: document and test Client.Post treating Reader body as ReadCloser

このコミットは、net/http パッケージの Client.Post 関数が、io.Reader として渡されたリクエストボディが io.Closer インターフェースも実装している場合に、そのボディを ReadCloser として扱い、適切にクローズすることについて、ドキュメントを整備し、テストを追加するものです。

変更の背景

Goの net/http パッケージでは、HTTPリクエストのボディとして io.Reader インターフェースを実装する任意の型を受け入れることができます。しかし、ネットワーク操作においては、リソースのリークを防ぐために、読み取りが完了したストリームは適切にクローズされる必要があります。特に、リクエストボディがファイルやネットワーク接続など、クローズが必要なリソースを表す場合、そのクローズの責任がどこにあるのかが不明確であると、リソースリークや予期せぬ動作につながる可能性があります。

このコミット以前は、Client.Post に渡された io.Readerio.Closer も実装している場合に、net/http パッケージがその Close() メソッドを呼び出すかどうかの挙動が明示的にドキュメント化されていませんでした。また、RoundTripper インターフェースのドキュメントも、リクエストボディの「消費」については言及していましたが、「クローズ」については明確ではありませんでした。

この曖昧さを解消し、開発者が安心して net/http クライアントを使用できるようにするため、以下の点が課題となっていました。

  1. ドキュメントの不足: Client.Postio.Closer を実装するボディをどのように扱うかについての公式な説明がなかった。
  2. 責任の不明確さ: RoundTripper がリクエストボディをクローズする責任を負うべきかどうかが不明確だった。
  3. テストの不足: この重要な挙動を保証するための自動テストが存在しなかった。

このコミットは、これらの課題に対処し、net/http パッケージの堅牢性と使いやすさを向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的なインターフェースと net/http パッケージの概念を理解しておく必要があります。

  • io.Reader インターフェース: Read(p []byte) (n int, err error) メソッドを持つインターフェースです。データを読み出すための一般的な抽象化を提供します。ファイル、ネットワーク接続、メモリ上のバイト列など、様々なデータソースからデータを読み出す際に使用されます。

  • io.Closer インターフェース: Close() error メソッドを持つインターフェースです。リソース(ファイルハンドル、ネットワークソケットなど)を解放するための一般的な抽象化を提供します。io.Readerio.Writer と組み合わせて使用されることが多く、リソースのリークを防ぐために重要です。

  • io.ReadCloser インターフェース: io.Readerio.Closer の両方を埋め込んだインターフェースです。読み取りとクローズの両方の機能を持つリソースを表します。

  • net/http パッケージ: Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。WebアプリケーションやAPIクライアントを構築する上で不可欠なパッケージです。

  • net/http.Client: HTTPリクエストを送信するためのクライアント構造体です。Get, Post, Do などのメソッドを提供し、HTTP通信を行います。

  • net/http.Request: HTTPリクエストを表す構造体です。Body フィールドは io.ReadCloser 型であり、リクエストボディのデータを保持します。

  • net/http.RoundTripper インターフェース: RoundTrip(*Request) (*Response, error) メソッドを持つインターフェースです。HTTPリクエストを送信し、HTTPレスポンスを受信する単一のHTTPトランザクションを表します。net/http.Client は内部的に RoundTripper を使用してリクエストを処理します。カスタムの RoundTripper を実装することで、リクエストの変更、認証、プロキシ設定などの高度な処理を行うことができます。

技術的詳細

このコミットの技術的な核心は、net/http パッケージがリクエストボディの io.Closer インターフェースをどのように尊重し、いつ Close() メソッドを呼び出すかという点にあります。

HTTPリクエストのボディは、net/http.Client.Post メソッドに io.Reader として渡されます。しかし、この io.Reader が同時に io.Closer インターフェースも実装している場合(つまり io.ReadCloser である場合)、net/http パッケージは、ボディのデータがサーバーに正常に書き込まれた後に、その Close() メソッドを呼び出す責任を負います。これは、ファイルやネットワークストリームなど、基になるリソースを適切に解放するために不可欠です。

この挙動は、net/http.Client が内部で使用する net/http.RoundTripper インターフェースの実装によって処理されます。RoundTripper の役割は、リクエストを送信し、レスポンスを受け取るだけでなく、リクエストボディの消費とクローズも含まれるべきであると、このコミットによって明確にされました。

具体的には、以下の変更が行われました。

  1. RoundTripper インターフェースのドキュメント更新: RoundTripperRoundTrip メソッドのドキュメントに、「リクエストのBodyを消費し、クローズする」という記述が追加されました。これにより、RoundTripper の実装者がリクエストボディのクローズ責任を負うことが明確になりました。

  2. Client.Post メソッドのドキュメント更新: Client.Post メソッドのドキュメントに、「提供されたボディが io.Closer でもある場合、ボディがサーバーに正常に書き込まれた後にクローズされる」という記述が追加されました。これは、Client.Post を使用する開発者に対して、この自動クローズ挙動を明示的に通知するものです。

  3. テストケースの追加: TestTransportClosesRequestBody という新しいテスト関数が src/pkg/net/http/transport_test.go に追加されました。このテストは、カスタムの countCloseReader 型(io.Readerio.Closer を実装し、Close() が呼ばれるとカウンタをインクリメントする)を使用して、Client.Post が実際にボディの Close() メソッドを呼び出すことを検証します。これにより、将来の変更によってこの重要な挙動が損なわれることを防ぎます。

これらの変更により、net/http パッケージは、リクエストボディのライフサイクル管理においてより予測可能で堅牢な挙動を提供するようになりました。開発者は、io.Closer を実装するリクエストボディを Client.Post に渡す際に、明示的に Close() を呼び出す必要がなくなり、リソースリークのリスクが低減されます。

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

このコミットにおけるコアとなるコードの変更箇所は以下の2つのファイルです。

  1. src/pkg/net/http/client.go

    • RoundTripper インターフェースのコメントが変更されました。
    • Client.Post メソッドのコメントが追加されました。
  2. src/pkg/net/http/transport_test.go

    • TestTransportClosesRequestBody という新しいテスト関数が追加されました。
    • countCloseReader というヘルパー型が追加されました。

src/pkg/net/http/client.go の変更点

--- a/src/pkg/net/http/client.go
+++ b/src/pkg/net/http/client.go
@@ -74,8 +74,8 @@ type RoundTripper interface {
  	// authentication, or cookies.
  	//
  	// RoundTrip should not modify the request, except for
- 	// consuming the Body.  The request's URL and Header fields
- 	// are guaranteed to be initialized.
+ 	// consuming and closing the Body. The request's URL and
+ 	// Header fields are guaranteed to be initialized.
  	RoundTrip(*Request) (*Response, error)
  }
  
@@ -346,6 +346,9 @@ func Post(url string, bodyType string, body io.Reader) (resp *Response, err erro
  // Post issues a POST to the specified URL.
  //
  // Caller should close resp.Body when done reading from it.
+//
+// If the provided body is also an io.Closer, it is closed after the
+// body is successfully written to the server.
  func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error) {
  	req, err := NewRequest("POST", url, body)
  	if err != nil {

src/pkg/net/http/transport_test.go の変更点

--- a/src/pkg/net/http/transport_test.go
+++ b/src/pkg/net/http/transport_test.go
@@ -15,6 +15,7 @@ import (
  	"io"
  	"io/ioutil"
  	"net"
+ 	"net/http"
  	. "net/http"
  	"net/http/httptest"
  	"net/url"
@@ -1610,6 +1611,42 @@ func TestIdleConnChannelLeak(t *testing.T) {
  	}
  }
  
+// Verify the status quo: that the Client.Post function coerces its
+// body into a ReadCloser if it's a Closer, and that the Transport
+// then closes it.
+func TestTransportClosesRequestBody(t *testing.T) {
+	defer afterTest(t)
+	ts := httptest.NewServer(http.HandlerFunc(func(w ResponseWriter, r *Request) {
+		io.Copy(ioutil.Discard, r.Body)
+	}))
+	defer ts.Close()
+
+	tr := &Transport{}
+	defer tr.CloseIdleConnections()
+	cl := &Client{Transport: tr}
+
+	closes := 0
+
+	res, err := cl.Post(ts.URL, "text/plain", countCloseReader{&closes, strings.NewReader("hello")})
+	if err != nil {
+		t.Fatal(err)
+	}
+	res.Body.Close()
+	if closes != 1 {
+		t.Errorf("closes = %d; want 1", closes)
+	}
+}
+
+type countCloseReader struct {
+	n *int
+	io.Reader
+}
+
+func (cr countCloseReader) Close() error {
+	(*cr.n)++
+	return nil
+}
+
  // rgz is a gzip quine that uncompresses to itself.
  var rgz = []byte{
  	0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,

コアとなるコードの解説

src/pkg/net/http/client.go の変更点

  1. RoundTripper インターフェースのコメント修正: RoundTripper インターフェースの RoundTrip メソッドのドキュメントが更新されました。 変更前: // consuming the Body. 変更後: // consuming and closing the Body. この変更は、RoundTripper の実装がリクエストボディを単に「消費する」だけでなく、「クローズする」責任も負うことを明確にしています。これは、リソースリークを防ぐ上で非常に重要な指針となります。net/http パッケージのデフォルトの Transport 実装は、この責任を適切に果たします。

  2. Client.Post メソッドのコメント追加: Client.Post メソッドのドキュメントに以下の新しい行が追加されました。

    // If the provided body is also an io.Closer, it is closed after the
    // body is successfully written to the server.
    

    このコメントは、Client.Post に渡される io.Readerio.Closer インターフェースも実装している場合、net/http パッケージがその Close() メソッドを自動的に呼び出すことを明示しています。これは、開発者がリクエストボディとして os.Filebytes.Buffer のような io.Closer を渡した場合に、明示的に defer file.Close() などと書く必要がないことを示唆しています(ただし、エラーハンドリングによっては必要になる場合もあります)。この自動クローズは、ボディのデータがサーバーに正常に書き込まれた後に実行されます。

src/pkg/net/http/transport_test.go の変更点

  1. countCloseReader 型の追加: このコミットでは、テストのために countCloseReader という新しい型が定義されました。

    type countCloseReader struct {
    	n *int
    	io.Reader
    }
    
    func (cr countCloseReader) Close() error {
    	(*cr.n)++
    	return nil
    }
    

    この型は、既存の io.Reader をラップし、io.Closer インターフェースも実装しています。Close() メソッドが呼び出されるたびに、ポインタ n が指す整数カウンタをインクリメントします。これにより、テスト内で Close() メソッドが何回呼び出されたかを追跡できます。

  2. TestTransportClosesRequestBody テスト関数の追加: この新しいテスト関数は、Client.Postio.Closer を実装するリクエストボディを適切にクローズすることを確認します。

    • httptest.NewServer を使用して、テスト用のHTTPサーバーを起動します。このサーバーは、受信したリクエストボディを単に読み捨てます。
    • net/http.Transportnet/http.Client のインスタンスを作成します。
    • closes という整数変数を定義し、そのアドレスを countCloseReader に渡します。
    • cl.Post を呼び出し、リクエストボディとして countCloseReader のインスタンスを渡します。
    • res.Body.Close() を呼び出してレスポンスボディをクローズします(これはリクエストボディのクローズとは別です)。
    • 最後に、closes 変数の値が 1 であることをアサートします。これは、countCloseReaderClose() メソッドが正確に1回呼び出されたことを意味し、net/http パッケージがリクエストボディを適切にクローズしたことを証明します。

これらの変更は、net/http パッケージの内部的な挙動を明確にし、その堅牢性を高めるための重要なステップです。特に、リソース管理の側面において、開発者がより安全にHTTPクライアントを使用できるようになります。

関連リンク

  • Go CL (Code Review) リンク: https://golang.org/cl/11542044 このコミットの元のコードレビューページです。詳細な議論や変更履歴を確認できます。

参考にした情報源リンク

  • Go言語公式ドキュメント - io パッケージ: https://pkg.go.dev/io io.Reader, io.Closer, io.ReadCloser インターフェースに関する公式ドキュメントです。

  • Go言語公式ドキュメント - net/http パッケージ: https://pkg.go.dev/net/http net/http.Client, net/http.Request, net/http.RoundTripper など、HTTPクライアントとサーバーに関する公式ドキュメントです。

  • Go言語公式ドキュメント - net/http/httptest パッケージ: https://pkg.go.dev/net/http/httptest HTTPテストサーバーの作成に使用される httptest.NewServer に関する公式ドキュメントです。

  • Go言語のソースコード: この解説は、Go言語の公式リポジトリにある実際のソースコード(src/pkg/net/http/client.go および src/pkg/net/http/transport_test.go)を直接参照して作成されました。