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

[インデックス 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つです。

  1. absoluteURI: 完全なURI(例: http://www.example.com/path/to/resource)。プロキシリクエストなどで使用されます。
  2. 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は、パスベースのマッチングに基づいてリクエストを適切なハンドラにルーティングするマルチプレクサです。

技術的詳細

このコミットは、主に以下の技術的な変更を導入しています。

  1. net/urlパッケージの変更: url.Parse関数が、Request-URIとして「*」を正しくパースできるように修正されました。これにより、url.URL構造体のPathフィールドに「*」が設定されるようになります。
  2. net/httpパッケージの変更:
    • Request-URIのパース: HTTPリクエストのパース時に、Request-URIが「*」である場合を適切に処理できるようになりました。
    • グローバルOPTIONS *ハンドラ: http.ServerOPTIONS *リクエストを受け取った際に、globalOptionsHandlerという特別なハンドラにディスパッチするロジックが追加されました。このハンドラは、Content-Length: 0のレスポンスを返し、サーバーがOPTIONS *リクエストをサポートしていることを示します。また、将来の拡張のために、リクエストボディが最大4KBまで読み捨てられるようになっています。
    • ServeMuxの挙動: http.ServeMuxは、パスベースのルーティングを行うため、RequestURIが「*」であるリクエスト(ただしOPTIONSメソッドを除く)を受け取った場合、400 Bad Requestを返すように変更されました。これは、ServeMuxが「*」のようなサーバー全体を指すURIを処理するようには設計されていないためです。これにより、一般的なHTTPリクエストの処理には影響を与えず、特殊な「*」リクエストは適切なハンドラにルーティングされるようになります。
    • テストの追加: net/http/readrequest_test.gonet/http/serve_test.goに、NOTIFY *OPTIONS *リクエストのパースと処理に関するテストケースが追加され、変更が正しく機能することを確認しています。

これらの変更により、GoのHTTPサーバーはHTTP/1.1の仕様にさらに厳密に準拠し、SSDPやSIPのようなプロトコルが利用する特殊なリクエスト形式にも対応できるようになりました。

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

src/pkg/net/http/readrequest_test.go

  • reqTestsスライスに、SSDPのNOTIFY *リクエストとOPTIONS *リクエストのテストケースが追加されました。これにより、これらの特殊なRequest-URIが正しくパースされ、Request構造体のURL.PathRequestURIフィールドに「*」が設定されることが検証されます。

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」である場合に、handlerglobalOptionsHandler{}に設定されるロジックが追加されました。
  • 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スタック全体で適切に処理できるようにすることです。

  1. net/urlの変更: net/url/url.goparse関数は、URL文字列をurl.URL構造体に変換する役割を担います。この変更により、入力されたrawurlが単一の「*」である場合、url.URLPathフィールドに「*」が直接設定されるようになりました。これは、OPTIONS *のようなリクエストが、通常のパスとしてではなく、特殊なURIとして認識されるための基盤となります。

    // src/pkg/net/url/url.go
    if rawurl == "*" {
        url.Path = "*"
        return
    }
    
  2. net/httpOPTIONS *ハンドリング: net/http/server.goconn.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)
        }
    }
    
  3. 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などのプロトコルが利用する特殊な「*」形式のリクエストを安全かつ効率的に処理できるようになりました。

関連リンク

参考にした情報源リンク