[インデックス 14608] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet/http
およびnet/url
パッケージにおいて、HTTP/1.1のRequest-URIとして「*
」を許可する変更を導入しています。これにより、特定のプロトコル(SSDPやSIPなど)で利用される「*
」形式のリクエストURIをGoのHTTPサーバーが適切に処理できるようになります。また、Apache HTTP Serverのように、サーバー全体に対するOPTIONS *
リクエストを処理するためのグローバルなハンドラも実装されています。
コミット
commit a6701f2699d328ab2bbff0130a6a553451d68f0d
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Tue Dec 11 12:07:27 2012 -0500
net/http, net/url: permit Request-URI "*"
Also, implement a global OPTIONS * handler, like Apache.
Permit sending "*" requests to handlers, but not path-based
(ServeMux) handlers. That means people can go out of their
way to support SSDP or SIP or whatever, but most users will be
unaffected.
See RFC 2616 Section 5.1.2 (Request-URI)
See RFC 2616 Section 9.2 (OPTIONS)
Fixes #3692
R=rsc
CC=golang-dev
https://golang.org/cl/6868095
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a6701f2699d328ab2bbff0130a6a553451d68f0d
元コミット内容
このコミットは、net/http
およびnet/url
パッケージがHTTPリクエストのRequest-URIとして「*
」を許可するように変更します。これに伴い、ApacheのようなグローバルなOPTIONS *
ハンドラも実装されます。
「*
」リクエストは、パスベースの(ServeMux
のような)ハンドラではなく、より低レベルのハンドラに送信されることが許可されます。これにより、開発者はSSDPやSIPなどのプロトコルをサポートするために、この機能を利用できるようになりますが、一般的なHTTPサーバーの利用には影響を与えません。
この変更は、RFC 2616のセクション5.1.2(Request-URI)とセクション9.2(OPTIONS)に準拠しており、GoのIssue #3692を修正します。
変更の背景
この変更の背景には、HTTP/1.1の仕様(RFC 2616)で定義されているRequest-URIの特殊な形式「*
」をGoのHTTPサーバーが適切に扱えないという問題がありました。特に、サーバー全体に対するOPTIONS
リクエスト(OPTIONS *
)や、SSDP(Simple Service Discovery Protocol)やSIP(Session Initiation Protocol)のような特定のプロトコルが利用する「*
」形式のリクエストを処理する必要がありました。
GoのIssue #3692では、SSDPのNOTIFY *
リクエストがGoのHTTPサーバーで正しくパースされないことが報告されており、このコミットはその問題を解決するために行われました。この変更により、GoのHTTPサーバーはより広範なHTTP/1.1のユースケースに対応できるようになります。
前提知識の解説
HTTP/1.1 Request-URI
HTTP/1.1(RFC 2616)では、リクエストラインの一部としてRequest-URIが定義されています。Request-URIは、リクエストの対象となるリソースを識別します。Request-URIにはいくつかの形式がありますが、このコミットに関連するのは以下の2つです。
- absoluteURI: 完全なURI(例:
http://www.example.com/path/to/resource
)。プロキシリクエストなどで使用されます。 - asterisk ("*"): アスタリスク1文字のみ。これは、
OPTIONS
メソッドでのみ使用され、リクエストが特定のURIではなく、サーバー全体に適用されることを示します。例えば、OPTIONS * HTTP/1.1
は、サーバー全体の通信オプションを問い合わせるリクエストです。
HTTP OPTIONSメソッド
OPTIONS
メソッドは、Webサーバーまたは特定のWebサーバー上のリソースがサポートしている通信オプションを記述するために使用されます。クライアントはOPTIONS
リクエストを送信することで、サーバーがサポートするHTTPメソッド、ヘッダー、その他の機能に関する情報を取得できます。
OPTIONS *
形式のリクエストは、特定のURIではなく、サーバー全体がサポートする通信オプションを問い合わせるために使用されます。これは、クライアントがサーバーの一般的な機能を事前に把握するために役立ちます。
SSDP (Simple Service Discovery Protocol)
SSDPは、UPnP(Universal Plug and Play)などのネットワークプロトコルで使用される、ローカルネットワーク上のサービスやデバイスを検出するためのプロトコルです。SSDPでは、デバイスがネットワークに参加した際に、マルチキャストアドレスに対してNOTIFY * HTTP/1.1
のような形式で通知を送信することがあります。この「*
」は、特定のURIではなく、デバイス自体が提供するサービス全体を通知する意味合いを持ちます。
SIP (Session Initiation Protocol)
SIPは、VoIP(Voice over IP)などのリアルタイム通信セッションを確立、変更、終了するためのシグナリングプロトコルです。SIPもHTTPに似たメッセージ形式を使用し、特定の状況で「*
」形式のRequest-URIを使用することがあります。
Go net/http
パッケージ
Goのnet/http
パッケージは、HTTPクライアントとサーバーの実装を提供します。http.Server
はHTTPリクエストを受け付け、http.Handler
インターフェースを実装するオブジェクトにリクエストをディスパッチします。http.ServeMux
は、パスベースのマッチングに基づいてリクエストを適切なハンドラにルーティングするマルチプレクサです。
技術的詳細
このコミットは、主に以下の技術的な変更を導入しています。
net/url
パッケージの変更:url.Parse
関数が、Request-URIとして「*
」を正しくパースできるように修正されました。これにより、url.URL
構造体のPath
フィールドに「*
」が設定されるようになります。net/http
パッケージの変更:- Request-URIのパース: HTTPリクエストのパース時に、Request-URIが「
*
」である場合を適切に処理できるようになりました。 - グローバル
OPTIONS *
ハンドラ:http.Server
がOPTIONS *
リクエストを受け取った際に、globalOptionsHandler
という特別なハンドラにディスパッチするロジックが追加されました。このハンドラは、Content-Length: 0
のレスポンスを返し、サーバーがOPTIONS *
リクエストをサポートしていることを示します。また、将来の拡張のために、リクエストボディが最大4KBまで読み捨てられるようになっています。 ServeMux
の挙動:http.ServeMux
は、パスベースのルーティングを行うため、RequestURI
が「*
」であるリクエスト(ただしOPTIONS
メソッドを除く)を受け取った場合、400 Bad Request
を返すように変更されました。これは、ServeMux
が「*
」のようなサーバー全体を指すURIを処理するようには設計されていないためです。これにより、一般的なHTTPリクエストの処理には影響を与えず、特殊な「*
」リクエストは適切なハンドラにルーティングされるようになります。- テストの追加:
net/http/readrequest_test.go
とnet/http/serve_test.go
に、NOTIFY *
とOPTIONS *
リクエストのパースと処理に関するテストケースが追加され、変更が正しく機能することを確認しています。
- Request-URIのパース: HTTPリクエストのパース時に、Request-URIが「
これらの変更により、GoのHTTPサーバーはHTTP/1.1の仕様にさらに厳密に準拠し、SSDPやSIPのようなプロトコルが利用する特殊なリクエスト形式にも対応できるようになりました。
コアとなるコードの変更箇所
src/pkg/net/http/readrequest_test.go
reqTests
スライスに、SSDPのNOTIFY *
リクエストとOPTIONS *
リクエストのテストケースが追加されました。これにより、これらの特殊なRequest-URIが正しくパースされ、Request
構造体のURL.Path
とRequestURI
フィールドに「*
」が設定されることが検証されます。
src/pkg/net/http/serve_test.go
TestOptions
関数が追加されました。このテストは、OPTIONS *
リクエストが200 OK
で成功することを確認します。- また、
GET *
リクエストがServeMux
によって400 Bad Request
で拒否されることも検証し、ServeMux
がパスベースのリクエストのみを処理するという意図された挙動を保証します。
src/pkg/net/http/server.go
conn.serve()
メソッド内で、リクエストのRequestURI
が「*
」かつMethod
が「OPTIONS
」である場合に、handler
がglobalOptionsHandler{}
に設定されるロジックが追加されました。ServeMux.ServeHTTP
メソッド内で、r.RequestURI == "*"
である場合に、400 Bad Request
を返すロジックが追加されました。ただし、これはOPTIONS *
リクエストには適用されず、ServeMux
がパスベースのリクエストのみを処理するという原則を維持します。globalOptionsHandler
という新しい型と、そのServeHTTP
メソッドが定義されました。このハンドラは、OPTIONS *
リクエストに対してContent-Length: 0
のレスポンスを返し、リクエストボディを最大4KBまで読み捨てます。
src/pkg/net/url/url.go
parse
関数内で、rawurl == "*"
である場合に、url.Path
に「*
」を設定して早期リターンするロジックが追加されました。これにより、net/url
パッケージがRequest-URI「*
」を正しくパースできるようになります。
src/pkg/net/url/url_test.go
parseRequestUrlTests
変数の名前がparseRequestURLTests
に変更されました。parseRequestURLTests
スライスに、"*"
が有効なRequest-URIとして追加されました。これにより、ParseRequestURI
関数が「*
」を正しく処理できることが検証されます。
コアとなるコードの解説
このコミットの核心は、HTTP/1.1のRequest-URIの特殊な形式である「*
」をGoのHTTPスタック全体で適切に処理できるようにすることです。
-
net/url
の変更:net/url/url.go
のparse
関数は、URL文字列をurl.URL
構造体に変換する役割を担います。この変更により、入力されたrawurl
が単一の「*
」である場合、url.URL
のPath
フィールドに「*
」が直接設定されるようになりました。これは、OPTIONS *
のようなリクエストが、通常のパスとしてではなく、特殊なURIとして認識されるための基盤となります。// src/pkg/net/url/url.go if rawurl == "*" { url.Path = "*" return }
-
net/http
のOPTIONS *
ハンドリング:net/http/server.go
のconn.serve()
は、個々のHTTP接続を処理し、リクエストを適切なハンドラにディスパッチする中心的なロジックです。このコミットでは、リクエストのRequestURI
が「*
」であり、かつMethod
が「OPTIONS
」である場合に、globalOptionsHandler{}
という特別なハンドラが使用されるように変更されました。// src/pkg/net/http/server.go if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} }
globalOptionsHandler
は、ServeHTTP
メソッドを実装しており、OPTIONS *
リクエストに対してContent-Length: 0
の空のレスポンスを返します。これは、サーバーがOPTIONS *
リクエストをサポートしていることを示す標準的な挙動です。また、将来の拡張のために、リクエストボディが存在する場合でも最大4KBまで読み捨てられるようになっています。// src/pkg/net/http/server.go type globalOptionsHandler struct{} func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) { w.Header().Set("Content-Length", "0") if r.ContentLength != 0 { // Read up to 4KB of OPTIONS body (as mentioned in the // spec as being reserved for future use), but anything // over that is considered a waste of server resources // (or an attack) and we abort and close the connection, // courtesy of MaxBytesReader's EOF behavior. mb := MaxBytesReader(w, r.Body, 4<<10) io.Copy(ioutil.Discard, mb) } }
-
ServeMux
の特殊な挙動:http.ServeMux
は、URLパスに基づいてリクエストをルーティングする一般的なHTTPマルチプレクサです。ServeMux
はパスベースのルーティングに特化しているため、RequestURI
が「*
」であるリクエスト(ただし、前述のOPTIONS *
はconn.serve()
で先に処理されるため、ここには到達しない)を受け取った場合、400 Bad Request
を返すように変更されました。これにより、ServeMux
が意図しない「*
」リクエストを処理することを防ぎ、一般的なHTTPサーバーの挙動に影響を与えずに、特殊な「*
」リクエストの処理をより低レベルのハンドラに委ねる設計が実現されています。// src/pkg/net/http/server.go func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { if r.RequestURI == "*" { w.Header().Set("Connection", "close") w.WriteHeader(StatusBadRequest) return } h, _ := mux.Handler(r) h.ServeHTTP(w, r) }
これらの変更と、それらを検証するための包括的なテストの追加により、GoのHTTPサーバーはHTTP/1.1の仕様に準拠し、SSDPやSIPなどのプロトコルが利用する特殊な「*
」形式のリクエストを安全かつ効率的に処理できるようになりました。
関連リンク
- Go Issue #3692: https://github.com/golang/go/issues/3692
- Go CL 6868095: https://golang.org/cl/6868095 (このコミットに対応するGoの変更リスト)
参考にした情報源リンク
- RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1:
- Section 5.1.2 Request-URI: https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
- Section 9.2 OPTIONS Method: https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2
- Simple Service Discovery Protocol (SSDP): https://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol
- Session Initiation Protocol (SIP): https://en.wikipedia.org/wiki/Session_Initiation_Protocol