[インデックス 14783] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/http
パッケージにおいて、http.NewRequest
関数がリクエストボディの Content-Length
を自動的に設定する際の挙動を改善するものです。具体的には、*bytes.Reader
型のボディが渡された場合にも、そのサイズから Content-Length
を正しく推測し、設定するように変更されました。これにより、*strings.Reader
や *bytes.Buffer
と同様に、*bytes.Reader
を使用した場合でも、開発者が手動で Content-Length
を設定する手間が省かれ、より利便性が向上します。
コミット
commit 5e8ca201d10f7cc5bd555a49aa5202cfc670b4a4
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Jan 2 14:40:27 2013 -0800
net/http: make NewRequest pick a ContentLength from a *bytes.Reader too
It already did so for its sibling, *strings.Reader, as well as *bytes.Buffer.
R=edsrzf, dave, adg, kevlar, remyoudompheng, adg, rsc
CC=golang-dev
https://golang.org/cl/7031045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5e8ca201d10f7cc5bd555a49aa5202cfc670b4a4
元コミット内容
net/http: make NewRequest pick a ContentLength from a *bytes.Reader too
It already did so for its sibling, *strings.Reader, as well as *bytes.Buffer.
R=edsrzf, dave, adg, kevlar, remyoudompheng, adg, rsc
CC=golang-dev
https://golang.org/cl/7031045
変更の背景
http.NewRequest
関数は、HTTPリクエストを作成する際に、リクエストボディの io.Reader
から Content-Length
ヘッダを自動的に推測し、設定する機能を持っています。これは、特にPOSTリクエストなどでボディを送信する際に、開発者が明示的にボディの長さを計算してヘッダに設定する手間を省くための便利な機能です。
しかし、この機能は *strings.Reader
や *bytes.Buffer
といった特定の io.Reader
実装に対しては既に有効でしたが、*bytes.Reader
に対しては適用されていませんでした。*bytes.Reader
はバイトスライスをラップして io.Reader
インターフェースを提供するものであり、その内部にはデータの長さ情報が含まれています。そのため、*bytes.Reader
を使用した場合でも、Content-Length
を自動的に設定できるはずでした。
このコミットは、この既存の機能の抜け穴を埋め、*bytes.Reader
も Content-Length
の自動推測の対象に含めることで、APIの一貫性と利便性を向上させることを目的としています。
前提知識の解説
net/http
パッケージ
net/http
パッケージは、Go言語におけるHTTPクライアントおよびサーバーの実装を提供する標準ライブラリです。HTTPリクエストの送信、HTTPサーバーの構築、ルーティング、ミドルウェアの適用など、HTTP通信に関する幅広い機能を提供します。
io.Reader
インターフェース
io.Reader
はGo言語の io
パッケージで定義されている基本的なインターフェースの一つです。データを読み出すための単一のメソッド Read(p []byte) (n int, err error)
を持ちます。このインターフェースを実装する型は、ファイル、ネットワーク接続、メモリ上のデータなど、様々なデータソースからデータを読み出すことができます。HTTPリクエストのボディは、この io.Reader
インターフェースを満たす型として渡されます。
Content-Length
ヘッダ
Content-Length
はHTTPヘッダの一つで、HTTPメッセージのボディの長さをオクテット(バイト)単位で示します。これは、受信側がボディの終わりを正確に判断し、メッセージ全体を受信したことを確認するために重要です。特にPOSTやPUTリクエストなど、ボディを伴うリクエストでは、このヘッダが正しく設定されていることが通信の成功に不可欠です。
*bytes.Reader
bytes
パッケージの Reader
型は、バイトスライス ([]byte
) を io.Reader
、io.Seeker
、io.ByteReader
、io.ByteScanner
、io.RuneReader
、io.RuneScanner
インターフェースとしてラップする構造体です。これにより、メモリ上のバイトデータをファイルのように読み出すことができます。Len()
メソッドを持ち、残りの読み取り可能なバイト数を返します。
*strings.Reader
strings
パッケージの Reader
型は、文字列 (string
) を io.Reader
、io.Seeker
、io.ByteReader
、io.ByteScanner
、io.RuneReader
、io.RuneScanner
インターフェースとしてラップする構造体です。*bytes.Reader
と同様に、Len()
メソッドを持ち、残りの読み取り可能な文字数を返します。
*bytes.Buffer
bytes
パッケージの Buffer
型は、可変長のバイトバッファを実装する構造体です。io.Reader
および io.Writer
インターフェースを実装しており、データの読み書きが可能です。Len()
メソッドを持ち、バッファに現在格納されているバイト数を返します。
技術的詳細
http.NewRequest
関数は、リクエストボディとして io.Reader
を受け取ります。この関数内部では、渡された io.Reader
の具体的な型をチェックし、もしその型が Content-Length
を効率的に取得できる特定の型(例えば、Len()
メソッドを持つ型)であれば、その長さを使って req.ContentLength
フィールドを自動的に設定します。
変更前のコードでは、この型チェックの switch
ステートメントにおいて、*strings.Reader
と *bytes.Buffer
は含まれていましたが、*bytes.Reader
が含まれていませんでした。
// 変更前
switch v := body.(type) {
case *strings.Reader:
req.ContentLength = int64(v.Len())
case *bytes.Buffer:
req.ContentLength = int64(v.Len())
}
*bytes.Reader
も Len()
メソッドを持っており、そのメソッドは内部のバイトスライスの長さを正確に返すことができます。したがって、*bytes.Reader
も Content-Length
の自動推測の対象とすることが可能であり、かつ望ましい挙動です。
このコミットでは、switch
ステートメントに *bytes.Reader
のケースを追加することで、この不足を解消しています。
// 変更後
switch v := body.(type) {
case *bytes.Buffer: // 順序が変更されたが、機能に影響なし
req.ContentLength = int64(v.Len())
case *bytes.Reader: // 新しく追加されたケース
req.ContentLength = int64(v.Len())
case *strings.Reader: // 順序が変更されたが、機能に影響なし
req.ContentLength = int64(v.Len())
}
これにより、http.NewRequest
を呼び出す際に bytes.NewReader
で作成した io.Reader
を渡した場合でも、Content-Length
ヘッダが自動的に正しく設定されるようになります。
また、この変更に伴い、request_test.go
に TestNewRequestContentLength
という新しいテスト関数が追加されました。このテストは、*bytes.Reader
、*bytes.Buffer
、*strings.Reader
のそれぞれについて、NewRequest
が正しい ContentLength
を設定するかどうかを検証します。さらに、io.Reader
インターフェースを満たすが Len()
メソッドを持たないカスタム型や、io.NewSectionReader
のような Content-Length
を推測できないケースについてもテストし、それらの場合には ContentLength
が0になることを確認しています。これにより、変更が意図した通りに機能し、かつ他のケースに悪影響を与えないことが保証されます。
コアとなるコードの変更箇所
src/pkg/net/http/request.go
の NewRequest
関数内の switch
ステートメントに、*bytes.Reader
のケースが追加されました。
--- a/src/pkg/net/http/request.go
+++ b/src/pkg/net/http/request.go
@@ -433,10 +433,12 @@ func NewRequest(method, urlStr string, body io.Reader) (*Request, error) {
}\n \tif body != nil {\n \t\tswitch v := body.(type) {\n-\t\tcase *strings.Reader:\n-\t\t\treq.ContentLength = int64(v.Len())\n \t\tcase *bytes.Buffer:\n \t\t\treq.ContentLength = int64(v.Len())\n+\t\tcase *bytes.Reader:\n+\t\t\treq.ContentLength = int64(v.Len())\n+\t\tcase *strings.Reader:\n+\t\t\treq.ContentLength = int64(v.Len())\n \t\t}\n \t}\n \n```
また、`src/pkg/net/http/request_test.go` に新しいテスト関数 `TestNewRequestContentLength` が追加されました。
```diff
--- a/src/pkg/net/http/request_test.go
+++ b/src/pkg/net/http/request_test.go
@@ -238,6 +238,35 @@ func TestNewRequestHost(t *testing.T) {\n \t}\n }\n \n+func TestNewRequestContentLength(t *testing.T) {\n+\treadByte := func(r io.Reader) io.Reader {\n+\t\tvar b [1]byte\n+\t\tr.Read(b[:])\n+\t\treturn r\n+\t}\n+\ttests := []struct {\n+\t\tr io.Reader\n+\t\twant int64\n+\t}{\n+\t\t{bytes.NewReader([]byte(\"123\")), 3},\n+\t\t{bytes.NewBuffer([]byte(\"1234\")), 4},\n+\t\t{strings.NewReader(\"12345\"), 5},\n+\t\t// Not detected:\n+\t\t{struct{ io.Reader }{strings.NewReader(\"xyz\")}, 0},\n+\t\t{io.NewSectionReader(strings.NewReader(\"x\"), 0, 6), 0},\n+\t\t{readByte(io.NewSectionReader(strings.NewReader(\"xy\"), 0, 6)), 0},\n+\t}\n+\tfor _, tt := range tests {\n+\t\treq, err := NewRequest(\"POST\", \"http://localhost/\", tt.r)\n+\t\tif err != nil {\n+\t\t\tt.Fatal(err)\n+\t\t}\n+\t\tif req.ContentLength != tt.want {\n+\t\t\tt.Errorf(\"ContentLength(%#T) = %d; want %d\", tt.r, req.ContentLength, tt.want)\n+\t\t}\n+\t}\n+}\n+\n func testMissingFile(t *testing.T, req *Request) {\n \tf, fh, err := req.FormFile(\"missing\")\n \tif f != nil {\n```
## コアとなるコードの解説
`src/pkg/net/http/request.go` の変更は、`NewRequest` 関数内の以下の部分にあります。
```go
if body != nil {
switch v := body.(type) {
case *bytes.Buffer:
req.ContentLength = int64(v.Len())
case *bytes.Reader: // この行が新しく追加された
req.ContentLength = int64(v.Len())
case *strings.Reader:
req.ContentLength = int64(v.Len())
}
}
このコードブロックは、body
が nil
でない場合に実行されます。body
は io.Reader
型ですが、Goの型アサーション switch v := body.(type)
を使用して、body
の具体的な動的型をチェックしています。
case *bytes.Buffer:
: もしbody
が*bytes.Buffer
型であれば、そのLen()
メソッドを呼び出してバッファの現在の長さを取得し、req.ContentLength
に設定します。case *bytes.Reader:
: このコミットで追加された部分です。 もしbody
が*bytes.Reader
型であれば、同様にそのLen()
メソッドを呼び出してリーダーがラップしているバイトスライスの長さを取得し、req.ContentLength
に設定します。case *strings.Reader:
: もしbody
が*strings.Reader
型であれば、そのLen()
メソッドを呼び出して文字列の長さを取得し、req.ContentLength
に設定します。
この変更により、*bytes.Reader
を使用してリクエストボディを構築した場合でも、Content-Length
ヘッダが自動的に正しく設定されるようになり、開発者は手動でこのヘッダを設定する手間が省けます。これは、io.Reader
インターフェースの背後にある具体的な型が持つ長さ情報を活用することで、よりスマートなAPI挙動を実現しています。
src/pkg/net/http/request_test.go
に追加された TestNewRequestContentLength
テスト関数は、この変更の正しさを検証します。様々な io.Reader
の実装(bytes.NewReader
, bytes.NewBuffer
, strings.NewReader
)に対して NewRequest
を呼び出し、req.ContentLength
が期待される値と一致するかを確認します。また、Content-Length
を推測できないケース(例えば、Len()
メソッドを持たないカスタム io.Reader
や、部分的に読み取られた io.NewSectionReader
)についてもテストし、それらの場合には ContentLength
が0になることを確認することで、意図しない副作用がないことを保証しています。
関連リンク
- Go CL 7031045: https://golang.org/cl/7031045
参考にした情報源リンク
- Go言語の公式ドキュメント:
net/http
パッケージ: https://pkg.go.dev/net/httpio
パッケージ: https://pkg.go.dev/iobytes
パッケージ: https://pkg.go.dev/bytesstrings
パッケージ: https://pkg.go.dev/strings
- HTTP/1.1 RFC 2616 - Content-Length: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13