[インデックス 11425] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/http
パッケージにおいて、Request
構造体に RequestURI
フィールドを追加するものです。これにより、HTTPリクエストラインでクライアントから送信されたオリジナルのURI文字列を、URLパッケージによる正規化(canonicalization)を経ずにそのまま保持できるようになります。
コミット
commit 899cd04e214435ee09483231fc3fa03ad270c5e6
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Thu Jan 26 14:37:14 2012 -0800
net/http: add Request.RequestURI field
The new url.URL's parsing can be too canonicalizing for
certain applications. By keeping the original request URI
around, we give applications a gross escape hatch while
keeping the URL package clean and simple for normal uses.
(From a discussion with Gary Burd, Gustavo Niemeyer,
and Russ Cox.)
Fixes #2782
R=golang-dev, rsc, dsymonds
CC=golang-dev
https://golang.org/cl/5580044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/899cd04e214435ee09483231fc3fa03ad270c5e6
元コミット内容
このコミットは、net/http
パッケージの Request
構造体に RequestURI
という新しいフィールドを追加します。このフィールドは、クライアントからサーバーに送信されたHTTPリクエストライン(RFC 2616, Section 5.1)に含まれる、未変更のRequest-URIを保持することを目的としています。
コミットメッセージによると、url.URL
パッケージの新しいパース処理が、特定のアプリケーションにとって「正規化しすぎている(too canonicalizing)」場合があることが問題視されていました。この正規化されたURLでは対応できないケースのために、オリジナルのRequest-URIを保持することで、アプリケーションに「大雑把な脱出ハッチ(gross escape hatch)」を提供しつつ、url
パッケージ自体は通常の用途でクリーンかつシンプルに保つという意図があります。
この変更は、Gary Burd、Gustavo Niemeyer、Russ Cox との議論から生まれたものであり、Issue #2782 を修正するものです。
変更の背景
Go言語の net/url
パッケージは、URLをパースし、その構成要素(スキーム、ホスト、パス、クエリなど)を構造化された形式で提供します。このパース処理には、URLの正規化(canonicalization)が含まれることが一般的です。正規化とは、URLの異なる表記を標準的な形式に統一するプロセスです。例えば、パスの正規化(//
を /
に変換する、.
や ..
を解決する)、ポート番号の省略、スキームの小文字化などが含まれます。
しかし、特定のHTTPアプリケーション、特にプロキシサーバー、リバースプロキシ、ルーティングロジックがURIの厳密な文字列マッチングに依存する場合、あるいはレガシーシステムとの互換性を保つ必要がある場合などでは、この自動的な正規化が問題となることがあります。例えば、クライアントが GET /foo/../bar
のようなリクエストを送信した場合、正規化されると /bar
になるかもしれませんが、アプリケーションは ../
のような非正規化されたパス情報に基づいて特定の処理を行いたい場合があります。
このコミットは、このような正規化によって失われる可能性のあるオリジナルのURI情報を保持するためのメカニズムを提供することで、開発者がより低レベルの、未加工のURIデータにアクセスできるようにすることを目的としています。これにより、url.URL
パッケージの設計思想である「クリーンでシンプル」さを維持しつつ、特殊な要件を持つアプリケーションにも対応できるようになります。
前提知識の解説
HTTP Request-URI (RFC 2616, Section 5.1)
HTTP/1.1の仕様であるRFC 2616のセクション5.1「Request-Line」では、HTTPリクエストの最初の行(Request-Line)の形式が定義されています。これは Method SP Request-URI SP HTTP-Version CRLF
の形式を取ります。
ここでいう Request-URI
は、リクエストの対象となるリソースを識別するURIです。これは、絶対URI(例: http://www.example.com/path/to/resource
)、絶対パス(例: /path/to/resource?query=string
)、またはアスタリスク(*
、OPTIONSメソッドなどで使用)など、いくつかの形式を取り得ます。
このコミットで追加される RequestURI
フィールドは、この Request-Line
から直接取得される、未加工の Request-URI
文字列を指します。
URLの正規化 (Canonicalization)
URLの正規化とは、同じリソースを指す複数のURL表記を、一貫した単一の標準形式に変換するプロセスです。これには以下のような操作が含まれます。
- スキームの小文字化:
HTTP
をhttp
に。 - ホスト名の小文字化:
EXAMPLE.COM
をexample.com
に。 - デフォルトポートの削除:
http://example.com:80/
をhttp://example.com/
に。 - パスの正規化:
%XX
エンコーディングのデコード(必要に応じて)。..
や.
の解決:http://example.com/a/../b
をhttp://example.com/b
に。- 連続するスラッシュの圧縮:
http://example.com//path
をhttp://example.com/path
に。
- クエリパラメータの順序付け:
?b=2&a=1
を?a=1&b=2
に。
net/url
パッケージの url.Parse()
関数は、これらの正規化の一部を内部的に行い、パースされたURLを url.URL
構造体として返します。この構造体は、正規化された形式でURLの各コンポーネントを提供します。
net/http
パッケージと Request
構造体
net/http
パッケージは、Go言語でHTTPクライアントとサーバーを実装するための主要なパッケージです。
http.Request
構造体は、受信したHTTPリクエスト(サーバー側)または送信するHTTPリクエスト(クライアント側)を表します。この構造体には、メソッド、URL、ヘッダー、ボディなどの情報が含まれます。
これまで、リクエストのURI情報は Request.URL
フィールド(*url.URL
型)を通じてアクセスされていました。これは net/url
パッケージによってパースされ、正規化されたURLオブジェクトです。
技術的詳細
このコミットの主要な変更点は、src/pkg/net/http/request.go
にある http.Request
構造体に RequestURI
という新しい string
型のフィールドが追加されたことです。
type Request struct {
// ... 既存のフィールド ...
// RequestURI is the unmodified Request-URI of the
// Request-Line (RFC 2616, Section 5.1) as sent by the client
// to a server. Usually the URL field should be used instead.
// It is an error to set this field in an HTTP client request.
RequestURI string
// ... 既存のフィールド ...
}
このフィールドのコメントには、以下の重要な情報が含まれています。
unmodified Request-URI
: クライアントから送信された未変更のRequest-URIであること。Usually the URL field should be used instead
: 通常はRequest.URL
フィールドを使用すべきであること。これは、Request.URL
が正規化されており、ほとんどのアプリケーションにとってより扱いやすい形式であるためです。It is an error to set this field in an HTTP client request
: HTTPクライアントのリクエストではこのフィールドを設定してはならないこと。これは、クライアントがリクエストを送信する際には、Request.URL
フィールドからURIが構築されるため、RequestURI
を手動で設定すると矛盾が生じる可能性があるためです。
ReadRequest
関数の変更
src/pkg/net/http/request.go
内の ReadRequest
関数は、HTTPリクエストの生データを読み込み、http.Request
構造体にパースする役割を担っています。この関数が変更され、リクエストラインから直接 RequestURI
フィールドを読み込むようになりました。
変更前:
var rawurl string
req.Method, rawurl, req.Proto = f[0], f[1], f[2]
変更後:
req.Method, req.RequestURI, req.Proto = f[0], f[1], f[2]
rawurl := req.RequestURI
この変更により、HTTPリクエストラインの2番目の要素(Request-URI部分)が直接 req.RequestURI
に代入されるようになりました。そして、rawurl
変数も req.RequestURI
の値で初期化されるため、後続の url.Parse()
呼び出しは引き続き rawurl
を使用して req.URL
フィールドをパースします。これにより、RequestURI
は未加工のまま保持され、URL
フィールドは正規化されたURLオブジェクトを保持するという二重の目的が達成されます。
クライアント側の制約
src/pkg/net/http/client.go
の send
関数(HTTPクライアントがリクエストを送信する際に呼び出される内部関数)には、Request.RequestURI
が設定されている場合にエラーを返すチェックが追加されました。
if req.RequestURI != "" {
return nil, errors.New("http: Request.RequestURI can't be set in client requests.")
}
この制約は、クライアントがリクエストを送信する際には、Request.URL
フィールドに基づいてリクエストラインが構築されるため、RequestURI
を手動で設定することは意図しない動作や矛盾を引き起こす可能性があるためです。RequestURI
は、サーバーが受信したリクエストの「生」のURIを保持するためのものであり、クライアントが送信するリクエストの構築には使用されません。
テストの追加
この変更に伴い、src/pkg/net/http/client_test.go
と src/pkg/net/http/readrequest_test.go
にテストが追加・修正されています。
TestClientErrorWithRequestURI
: クライアント側でRequestURI
を設定しようとするとエラーになることを確認するテスト。readrequest_test.go
のreqTests
変数に、RequestURI
フィールドの期待値が追加され、サーバーがリクエストを読み込む際に正しくパースされることを検証しています。
コアとなるコードの変更箇所
src/pkg/net/http/request.go
Request
構造体に RequestURI
フィールドが追加されました。
ReadRequest
関数内で、HTTPリクエストラインのURI部分が直接 req.RequestURI
に代入されるようになりました。
--- a/src/pkg/net/http/request.go
+++ b/src/pkg/net/http/request.go
@@ -153,6 +153,12 @@ type Request struct {
// This field is ignored by the HTTP client.
RemoteAddr string
+ // RequestURI is the unmodified Request-URI of the
+ // Request-Line (RFC 2616, Section 5.1) as sent by the client
+ // to a server. Usually the URL field should be used instead.
+ // It is an error to set this field in an HTTP client request.
+ RequestURI string
+
// TLS allows HTTP servers and other software to record
// information about the TLS connection on which the request
// was received. This field is not filled in by ReadRequest.
@@ -459,8 +465,8 @@ func ReadRequest(b *bufio.Reader) (req *Request, err error) {
if f = strings.SplitN(s, " ", 3); len(f) < 3 {
return nil, &badStringError{"malformed HTTP request", s}
}
- var rawurl string
- req.Method, rawurl, req.Proto = f[0], f[1], f[2]
+ req.Method, req.RequestURI, req.Proto = f[0], f[1], f[2]
+ rawurl := req.RequestURI
var ok bool
if req.ProtoMajor, req.ProtoMinor, ok = ParseHTTPVersion(req.Proto); !ok {
return nil, &badStringError{"malformed HTTP version", req.Proto}
src/pkg/net/http/client.go
クライアントがリクエストを送信する際に、RequestURI
フィールドが設定されている場合にエラーを返すチェックが追加されました。
--- a/src/pkg/net/http/client.go
+++ b/src/pkg/net/http/client.go
@@ -116,6 +116,10 @@ func send(req *Request, t RoundTripper) (resp *Response, err error) {
return nil, errors.New("http: nil Request.URL")
}
+ if req.RequestURI != "" {
+ return nil, errors.New("http: Request.RequestURI can't be set in client requests.")
+ }
+
// Most the callers of send (Get, Post, et al) don't need
// Headers, leaving it uninitialized. We guarantee to the
// Transport that this has been initialized, though.
src/pkg/net/http/client_test.go
クライアント側での RequestURI
設定に関するエラーをテストする新しいテストケースが追加されました。
--- a/src/pkg/net/http/client_test.go
+++ b/src/pkg/net/http/client_test.go
@@ -428,3 +428,15 @@ func TestClientInsecureTransport(t *testing.T) {
}
}
}
+
+func TestClientErrorWithRequestURI(t *testing.T) {
+ req, _ := NewRequest("GET", "http://localhost:1234/", nil)
+ req.RequestURI = "/this/field/is/illegal/and/should/error/"
+ _, err := DefaultClient.Do(req)
+ if err == nil {
+ t.Fatalf("expected an error")
+ }
+ if !strings.Contains(err.Error(), "RequestURI") {
+ t.Errorf("wanted error mentioning RequestURI; got error: %v", err)
+ }
+}
src/pkg/net/http/readrequest_test.go
reqTests
変数内のテストデータに RequestURI
フィールドの期待値が追加されました。
--- a/src/pkg/net/http/readrequest_test.go
+++ b/src/pkg/net/http/readrequest_test.go
@@ -64,6 +64,7 @@ var reqTests = []reqTest{\n Close: false,\n ContentLength: 7,\n Host: "www.techcrunch.com",
+ RequestURI: "http://www.techcrunch.com/",
},\n
"abcdef\n",
@@ -89,6 +90,7 @@ var reqTests = []reqTest{\n Close: false,\n ContentLength: 0,\n Host: "foo.com",
+ RequestURI: "/",
},\n
noBody,\n@@ -114,6 +116,7 @@ var reqTests = []reqTest{\n Close: false,\n ContentLength: 0,\n Host: "test",
+ RequestURI: "//user@host/is/actually/a/path/",
},\n
noBody,\n@@ -163,6 +166,7 @@ var reqTests = []reqTest{\n Header: Header{},\n ContentLength: -1,\n Host: "foo.com",
+ RequestURI: "/",
},\n
"foobar",\n@@ -188,6 +192,7 @@ var reqTests = []reqTest{\n Close: false,\n ContentLength: 0,\n Host: "www.google.com:443",
+ RequestURI: "www.google.com:443",
},\n
noBody,\n@@ -211,6 +216,7 @@ var reqTests = []reqTest{\n Close: false,\n ContentLength: 0,\n Host: "127.0.0.1:6060",
+ RequestURI: "127.0.0.1:6060",
},\n
noBody,\n@@ -234,6 +240,7 @@ var reqTests = []reqTest{\n Close: false,\n ContentLength: 0,\n Host: "",
+ RequestURI: "/_goRPC_",
},\n
noBody,\n```
## コアとなるコードの解説
このコミットの核心は、`http.Request` 構造体に `RequestURI` フィールドを追加し、HTTPサーバーが受信したリクエストの「生」のURIをそのまま保持できるようにした点です。
1. **`RequestURI` フィールドの追加**:
`Request` 構造体に追加された `RequestURI` は `string` 型であり、HTTPリクエストラインの `Request-URI` 部分を、`net/url` パッケージによるパースや正規化を一切行わずに、そのままの文字列として格納します。これにより、例えば `GET /foo/../bar?q=1` のようなリクエストが来た場合、`RequestURI` は `/foo/../bar?q=1` となり、`URL.Path` は `/bar`、`URL.RawQuery` は `q=1` といった形で、正規化された情報と未加工の情報が両方利用可能になります。
2. **`ReadRequest` での `RequestURI` の設定**:
`ReadRequest` 関数は、ネットワークから読み込んだHTTPリクエストのバイト列をパースし、`http.Request` オブジェクトを構築します。この関数内で、リクエストラインをスペースで分割した際に得られる2番目の要素(これがRequest-URIです)が、直接 `req.RequestURI` に代入されるようになりました。その後、この `req.RequestURI` の値が `rawurl` 変数にコピーされ、既存の `url.Parse()` 呼び出しに渡されて `req.URL` フィールドが設定されます。この順序により、`RequestURI` は常にオリジナルの値を含み、`URL` は正規化された値を含むことが保証されます。
3. **クライアント側での `RequestURI` の使用禁止**:
`net/http` クライアントがリクエストを送信する際には、`Request.URL` フィールドの情報に基づいてリクエストラインが構築されます。`RequestURI` フィールドは、サーバーが受信したリクエストの生データを保持するためのものであり、クライアントが送信するリクエストの構築には関与しません。そのため、もしクライアント側で `RequestURI` が設定されていた場合、それは意図しない動作や混乱を招く可能性があるため、`send` 関数内で明示的にエラーを返すように変更されました。これは、APIの誤用を防ぐためのガードレールとして機能します。
この変更は、Goの `net/http` パッケージが、一般的な用途では正規化されたURLを推奨しつつも、特定の高度なユースケース(例えば、プロキシ、リバースプロキシ、特定のルーティングロジック、またはレガシーシステムとの互換性など)において、未加工のURIデータへのアクセスが必要となる開発者のニーズに応えるためのものです。これにより、ライブラリの柔軟性が向上し、より幅広いシナリオに対応できるようになりました。
## 関連リンク
* Go Issue 2782: `net/http: add Request.RequestURI field` - [https://github.com/golang/go/issues/2782](https://github.com/golang/go/issues/2782)
* Go CL 5580044: `net/http: add Request.RequestURI field` - [https://golang.org/cl/5580044](https://golang.org/cl/5580044)
* RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1 (Section 5.1 Request-Line) - [https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1](https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1)
## 参考にした情報源リンク
* RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1
* Go言語の `net/http` パッケージのドキュメント
* Go言語の `net/url` パッケージのドキュメント
* GitHub上のGoリポジトリのコミット履歴と関連Issue
* URL正規化に関する一般的な情報源 (例: Wikipedia, MDN Web Docs)