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

[インデックス 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つの問題がありました。

  1. データ競合 (Data Race) の存在: 以前の net/http パッケージの実装では、HTTPリクエストのメソッド(例: "GET", "POST")を処理する際に、Request.Method フィールドを常に大文字に変換していました。この変換処理が、複数のゴルーチン(Goの軽量スレッド)から同時に同じ Request オブジェクトにアクセスするシナリオでデータ競合を引き起こす可能性がありました。コミットメッセージにあるように、多くの場合「"GET"を"GET"に更新する」といった無害な競合でしたが、潜在的な問題として認識されていました。

  2. 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リクエストの「メソッド」について記述しています。重要な点は、メソッド名が「ケースセンシティブ」であると明記されていることです。つまり、GETget は異なるメソッドとして扱われるべきであり、プロトコル実装はこれらを区別する必要があります。

データ競合 (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")に変換していました。

この自動変換は、以下の問題を引き起こしていました。

  1. RFC不準拠: RFC 2616 Section 5.1.1がメソッドをケースセンシティブと規定しているにもかかわらず、Goのサーバーはこれを無視し、常に大文字として扱っていました。これにより、プロトコルレベルでの厳密な準拠が損なわれていました。
  2. データ競合の可能性: Request.Method フィールドは http.Request 構造体の一部であり、この構造体はリクエスト処理中に複数の場所で参照される可能性があります。strings.ToUpper による文字列の再割り当て(またはインプレースでの変更)は、複数のゴルーチンが同時に同じ Request オブジェクトの Method フィールドにアクセスし、その値を変更しようとした場合にデータ競合を引き起こす可能性がありました。たとえ値が変更されなくても、書き込み操作自体が競合状態を作り出す原因となります。

このコミットでは、これらの strings.ToUpper の呼び出しを削除することで、受信したHTTPメソッドの文字列がそのまま Request.Method フィールドに格納されるようになります。これにより、RFC 2616の仕様に厳密に準拠し、かつ潜在的なデータ競合のリスクを排除することができました。

テストケース TestCaseSensitiveMethod が追加され、小文字の "get" メソッドでリクエストを送信し、サーバー側で r.Method が "get" のままであることを確認することで、この変更が正しく機能していることを検証しています。

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

このコミットでは、主に2つのファイルが変更されています。

  1. src/pkg/net/http/response.go
  2. 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リクエストのメソッド文字列を強制的に大文字に変換していました。

  1. resp.Request.Method = strings.ToUpper(resp.Request.Method) (in ReadResponse): この行は、サーバーがクライアントからのHTTPリクエストを読み込み、http.Request オブジェクトを構築する過程で実行されていました。本来、リクエストメソッドはクライアントが送信した通りのケースで保持されるべきですが、この行によって強制的に大文字に変換されていました。これにより、RFC 2616のケースセンシティブな要件に違反していました。また、複数のゴルーチンが同時に同じ Request オブジェクトの Method フィールドを更新しようとすると、データ競合が発生する可能性がありました。

  2. 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): 作成したリクエストをテストサーバーに送信します。

このテストが成功するということは、サーバーが受信したリクエストメソッドのケースをそのまま保持していることを意味し、コミットの目的が達成されたことを示します。

関連リンク

参考にした情報源リンク