[インデックス 13521] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージにおけるHTTPリクエストメソッドの処理に関する修正です。具体的には、Request.Method
フィールドが意図せず大文字に変換される挙動を停止し、HTTP/1.1の仕様であるRFC 2616 Section 5.1.1に準拠するように変更しています。これにより、データ競合の解消と、HTTPメソッドのケースセンシティブな扱いが保証されます。
コミット
commit f8c6514a1cb11034e96588ddfafdbbba5b0cf27b
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Mon Jul 30 10:05:24 2012 +1000
net/http: don't modify Request Method's case
This fixes a data race (usually just harmlessly updating
"GET" to "GET"), but also follows RFC 2616 Sec 5.1.1 which
says that the request method is case-sensitive.
Fixes #3881
R=golang-dev, rsc, dsymonds
CC=golang-dev
https://golang.org/cl/6446063
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f8c6514a1cb11034e96588ddfafdbbba5b0cf27b
元コミット内容
net/http: don't modify Request Method's case
This fixes a data race (usually just harmlessly updating
"GET" to "GET"), but also follows RFC 2616 Sec 5.1.1 which
says that the request method is case-sensitive.
Fixes #3881
変更の背景
このコミットが行われた背景には、主に以下の2つの問題がありました。
-
データ競合 (Data Race) の存在: 以前の
net/http
パッケージの実装では、HTTPリクエストのメソッド(例: "GET", "POST")を処理する際に、Request.Method
フィールドを常に大文字に変換していました。この変換処理が、複数のゴルーチン(Goの軽量スレッド)から同時に同じRequest
オブジェクトにアクセスするシナリオでデータ競合を引き起こす可能性がありました。コミットメッセージにあるように、多くの場合「"GET"を"GET"に更新する」といった無害な競合でしたが、潜在的な問題として認識されていました。 -
RFC 2616 Section 5.1.1 への不準拠: HTTP/1.1の仕様を定義するRFC 2616のSection 5.1.1では、HTTPリクエストメソッドはケースセンシティブ(大文字と小文字を区別する)であると明記されています。しかし、Goの
net/http
パッケージは、受信したリクエストメソッドを強制的に大文字に変換していたため、この仕様に準拠していませんでした。例えば、クライアントが "get" という小文字のメソッドでリクエストを送信した場合でも、サーバー側では "GET" として処理されてしまい、仕様との乖離が生じていました。これは、特に厳密なプロトコル準拠が求められる場面や、将来的なHTTPバージョンの拡張性を考慮する上で問題となり得ました。
これらの問題を解決するため、Request.Method
の自動的な大文字変換を削除し、受信したメソッドのケースをそのまま保持するように変更されました。
前提知識の解説
HTTPリクエストメソッド
HTTPリクエストメソッドは、クライアントがサーバーに対してどのような操作を要求しているかを示すものです。代表的なものには GET
(リソースの取得), POST
(データの送信), PUT
(リソースの更新/作成), DELETE
(リソースの削除) などがあります。
RFC 2616 (Hypertext Transfer Protocol -- HTTP/1.1)
RFC 2616は、HTTP/1.1プロトコルの主要な仕様を定義した文書です。この文書は、HTTPメッセージのフォーマット、メソッドのセマンティクス、ヘッダーフィールド、ステータスコードなど、HTTPのあらゆる側面について詳細に記述しています。
RFC 2616 Section 5.1.1 (Method)
このセクションは、HTTPリクエストの「メソッド」について記述しています。重要な点は、メソッド名が「ケースセンシティブ」であると明記されていることです。つまり、GET
と get
は異なるメソッドとして扱われるべきであり、プロトコル実装はこれらを区別する必要があります。
データ競合 (Data Race)
データ競合は、複数の並行実行されるスレッド(Goではゴルーチン)が、同期メカニズムなしに同じメモリ位置にアクセスし、少なくとも1つのアクセスが書き込みである場合に発生するプログラミング上のバグです。データ競合が発生すると、プログラムの動作が予測不能になったり、クラッシュしたりする可能性があります。Go言語では、go run -race
コマンドでデータ競合を検出するツールが提供されています。
Go言語の net/http
パッケージ
net/http
パッケージは、Go言語でHTTPクライアントおよびサーバーを実装するための標準ライブラリです。このパッケージは、HTTPリクエストの解析、レスポンスの生成、ルーティング、ミドルウェアのサポートなど、HTTP通信に必要な基本的な機能を提供します。
http.Request
構造体: 受信したHTTPリクエストの情報を保持する構造体です。Method
フィールドは、リクエストのHTTPメソッド(例: "GET", "POST")を文字列として格納します。http.ResponseWriter
インターフェース: HTTPレスポンスをクライアントに書き込むためのインターフェースです。
技術的詳細
このコミットの技術的な核心は、net/http
パッケージ内部でHTTPリクエストメソッドの文字列を強制的に大文字に変換していた処理を削除した点にあります。
以前の実装では、HTTPリクエストがサーバーに到達し、http.Request
オブジェクトが構築される際に、resp.Request.Method = strings.ToUpper(resp.Request.Method)
のようなコードが実行されていました。この処理は、受信したメソッド文字列(例: "get")を常に大文字("GET")に変換していました。
この自動変換は、以下の問題を引き起こしていました。
- RFC不準拠: RFC 2616 Section 5.1.1がメソッドをケースセンシティブと規定しているにもかかわらず、Goのサーバーはこれを無視し、常に大文字として扱っていました。これにより、プロトコルレベルでの厳密な準拠が損なわれていました。
- データ競合の可能性:
Request.Method
フィールドはhttp.Request
構造体の一部であり、この構造体はリクエスト処理中に複数の場所で参照される可能性があります。strings.ToUpper
による文字列の再割り当て(またはインプレースでの変更)は、複数のゴルーチンが同時に同じRequest
オブジェクトのMethod
フィールドにアクセスし、その値を変更しようとした場合にデータ競合を引き起こす可能性がありました。たとえ値が変更されなくても、書き込み操作自体が競合状態を作り出す原因となります。
このコミットでは、これらの strings.ToUpper
の呼び出しを削除することで、受信したHTTPメソッドの文字列がそのまま Request.Method
フィールドに格納されるようになります。これにより、RFC 2616の仕様に厳密に準拠し、かつ潜在的なデータ競合のリスクを排除することができました。
テストケース TestCaseSensitiveMethod
が追加され、小文字の "get" メソッドでリクエストを送信し、サーバー側で r.Method
が "get" のままであることを確認することで、この変更が正しく機能していることを検証しています。
コアとなるコードの変更箇所
このコミットでは、主に2つのファイルが変更されています。
src/pkg/net/http/response.go
src/pkg/net/http/serve_test.go
src/pkg/net/http/response.go
の変更
--- a/src/pkg/net/http/response.go
+++ b/src/pkg/net/http/response.go
@@ -107,7 +107,6 @@ func ReadResponse(r *bufio.Reader, req *Request) (resp *Response, err error) {
resp = new(Response)
resp.Request = req
- resp.Request.Method = strings.ToUpper(resp.Request.Method)
// Parse the first line of the response.
line, err := tp.ReadLine()
@@ -188,11 +187,6 @@ func (r *Response) ProtoAtLeast(major, minor int) bool {
//
func (r *Response) Write(w io.Writer) error {
- // RequestMethod should be upper-case
- if r.Request != nil {
- r.Request.Method = strings.ToUpper(r.Request.Method)
- }
-
// Status line
text := r.Status
if text == "" {
このファイルでは、strings.ToUpper(resp.Request.Method)
の呼び出しが2箇所から削除されています。
ReadResponse
関数内: HTTPレスポンスを読み込む際に、関連するリクエストのメソッドを大文字に変換していた行が削除されました。(*Response).Write
メソッド内: レスポンスを書き出す際に、関連するリクエストのメソッドを大文字に変換していた行が削除されました。これは、レスポンスを書き出す際にリクエストメソッドのケースを修正しようとする、冗長かつ誤ったロジックでした。
src/pkg/net/http/serve_test.go
の変更
--- a/src/pkg/net/http/serve_test.go
+++ b/src/pkg/net/http/serve_test.go
@@ -1188,6 +1188,22 @@ func TestServerGracefulClose(t *testing.T) {
<-writeErr
}
+func TestCaseSensitiveMethod(t *testing.T) {
+ ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ if r.Method != "get" {
+ t.Errorf(`Got method %q; want "get"`, r.Method)
+ }
+ }))
+ defer ts.Close()
+ req, _ := NewRequest("get", ts.URL, nil)
+ res, err := DefaultClient.Do(req)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ res.Body.Close()
+}
+
// goTimeout runs f, failing t if f takes more than ns to complete.
func goTimeout(t *testing.T, d time.Duration, f func()) {
ch := make(chan bool, 2)
このファイルには、TestCaseSensitiveMethod
という新しいテスト関数が追加されています。
コアとなるコードの解説
src/pkg/net/http/response.go
の変更点
削除された2行のコードは、HTTPリクエストのメソッド文字列を強制的に大文字に変換していました。
-
resp.Request.Method = strings.ToUpper(resp.Request.Method)
(inReadResponse
): この行は、サーバーがクライアントからのHTTPリクエストを読み込み、http.Request
オブジェクトを構築する過程で実行されていました。本来、リクエストメソッドはクライアントが送信した通りのケースで保持されるべきですが、この行によって強制的に大文字に変換されていました。これにより、RFC 2616のケースセンシティブな要件に違反していました。また、複数のゴルーチンが同時に同じRequest
オブジェクトのMethod
フィールドを更新しようとすると、データ競合が発生する可能性がありました。 -
r.Request.Method = strings.ToUpper(r.Request.Method)
(in(*Response).Write
): この行は、HTTPレスポンスをクライアントに書き出す際に、関連するリクエストのメソッドを再度大文字に変換しようとしていました。これは、レスポンスの書き出しとは直接関係のない処理であり、冗長であるだけでなく、もしr.Request
がnilでない場合に、不必要にRequest.Method
を変更しようとするものでした。この処理もまた、潜在的なデータ競合の原因となり得ました。
これらの行を削除することで、net/http
パッケージは、受信したHTTPリクエストメソッドのケースをそのまま保持するようになり、RFC 2616の仕様に準拠するようになりました。また、Request.Method
フィールドへの不必要な書き込み操作がなくなることで、関連するデータ競合のリスクも解消されました。
src/pkg/net/http/serve_test.go
の追加テスト
TestCaseSensitiveMethod
テスト関数は、この変更が正しく機能していることを検証するために追加されました。
httptest.NewServer
: テスト用のHTTPサーバーを起動します。このサーバーは、リクエストを受け取ると、そのメソッドが期待通り "get" (小文字) であるかをチェックします。HandlerFunc(func(w ResponseWriter, r *Request) { ... })
: サーバーがリクエストを受け取った際に実行されるハンドラ関数を定義しています。このハンドラ内で、r.Method
が "get" と等しいかを確認しています。もし大文字に変換されていれば、"GET"
となり、テストは失敗します。NewRequest("get", ts.URL, nil)
: クライアント側から、小文字の "get" メソッドでHTTPリクエストを作成しています。DefaultClient.Do(req)
: 作成したリクエストをテストサーバーに送信します。
このテストが成功するということは、サーバーが受信したリクエストメソッドのケースをそのまま保持していることを意味し、コミットの目的が達成されたことを示します。
関連リンク
- Go Issue #3881: net/http: don't modify Request Method's case
- Go Code Review: https://golang.org/cl/6446063
参考にした情報源リンク
- RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1: https://www.w3.org/Protocols/rfc2616/rfc2616.html
- 特に Section 5.1.1 Method: https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1
- Go言語
net/http
パッケージ公式ドキュメント: https://pkg.go.dev/net/http - Go言語におけるデータ競合の検出: https://go.dev/doc/articles/race_detector
- Go言語
strings
パッケージ公式ドキュメント: https://pkg.go.dev/strings - Go言語
httptest
パッケージ公式ドキュメント: https://pkg.go.dev/net/http/httptest