[インデックス 13263] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http パッケージ内の request.go ファイルに対する変更です。具体的には、HTTPリクエストのヘッダーから "Host" フィールドを削除する際の処理が req.Header.Del("Host") から delete(req.Header, "Host") へと変更されています。
コミット
commit 290115fdf4609c1fb0ba87aa1940a24308213543
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Sun Jun 3 22:40:16 2012 -0700
net/http: change a Del to delete
No need to fix case of "Host" string literal.
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/6278049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/290115fdf4609c1fb0ba87aa1940a24308213543
元コミット内容
このコミットの元のメッセージは以下の通りです。
net/http: change a Del to delete
No need to fix case of "Host" string literal.
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/6278049
このメッセージは、net/http パッケージにおいて、Del メソッドを delete 関数に置き換える変更が行われたことを示しています。その理由として、「"Host" 文字列リテラルのケースを修正する必要がない」と述べられています。
変更の背景
Go言語の net/http パッケージは、HTTPクライアントとサーバーの実装を提供します。HTTPヘッダーはキーと値のペアで構成され、キーは大文字・小文字を区別しないとされていますが、実際の実装では大文字・小文字の扱いが重要になる場合があります。
Goの http.Header 型は map[string][]string のエイリアスであり、マップのキーは文字列です。HTTPヘッダーの仕様(RFC 7230など)では、ヘッダーフィールド名は大文字・小文字を区別しないとされていますが、Goのマップはキーの大文字・小文字を厳密に区別します。このため、http.Header にアクセスする際には、正規化されたキー(通常は textproto.CanonicalMIMEHeaderKey を使用して大文字・小文字を正規化された形式)を使用することが推奨されます。
req.Header.Del("Host") のようなメソッドは、内部でキーの正規化を行ってからマップから要素を削除します。しかし、delete(req.Header, "Host") のように直接マップ操作を行う場合、指定されたキーと完全に一致するキーのみが削除されます。
このコミットの背景には、net/http パッケージが "Host" ヘッダーを特別に扱っているという事情があります。HTTPリクエストが受信されると、Host ヘッダーの値は http.Request 構造体の Host フィールドにパースされ、格納されます。その後、元のヘッダーマップから "Host" ヘッダーを削除することで、重複や不整合を防ぐ意図があります。
コミットメッセージにある「No need to fix case of "Host" string literal.」という記述は、http.Request のパース処理において、"Host" ヘッダーが既に正規化された形で処理されているか、あるいはこの特定のコンテキストでは大文字・小文字を区別しない削除が不要であると判断されたことを示唆しています。つまり、req.Header.get("Host") で取得した時点で、Host ヘッダーは既に正規化されたキーでアクセスされており、その後に削除する際には、正規化されたキー "Host" を使って直接 delete 関数を呼び出しても問題ない、という判断があったと考えられます。これにより、Del メソッドが持つ正規化のオーバーヘッドを避けることができます。
前提知識の解説
Go言語の net/http パッケージ
net/http パッケージは、Go言語でHTTPクライアントおよびサーバーを構築するための基本的な機能を提供します。このパッケージは、HTTP/1.1の仕様に準拠しており、リクエストのパース、レスポンスの生成、ルーティング、ミドルウェアのサポートなど、Webアプリケーション開発に必要な多くの機能を含んでいます。
http.Request 構造体
http.Request は、受信したHTTPリクエストを表す構造体です。この構造体には、リクエストメソッド(GET, POSTなど)、URL、ヘッダー、ボディなどの情報が含まれます。
req.Hostフィールド: HTTPリクエストのHostヘッダーの値を表す文字列フィールドです。このフィールドは、リクエストのターゲットホストを識別するために使用されます。req.Headerフィールド:http.Header型のマップであり、HTTPリクエストのヘッダーを表します。http.Headerはmap[string][]stringのエイリアスで、キーはヘッダー名(例: "Content-Type", "User-Agent")、値はそのヘッダーの値のリストです。
http.Header 型とヘッダーの正規化
http.Header は map[string][]string のエイリアスですが、HTTPヘッダーフィールド名は大文字・小文字を区別しないというHTTPの仕様があります。しかし、Goのマップはキーの大文字・小文字を厳密に区別します。このため、http.Header にヘッダーを追加したり、ヘッダーを取得したりする際には、ヘッダー名を正規化する必要があります。
Goの net/textproto パッケージには CanonicalMIMEHeaderKey という関数があり、これを使ってヘッダー名を正規化します。例えば、"content-type" は "Content-Type" に正規化されます。
map の delete 関数と http.Header.Del メソッド
-
delete(m map[K]V, key K): Go言語の組み込み関数で、マップmから指定されたkeyに対応する要素を削除します。この関数は、キーの大文字・小文字を厳密に区別します。つまり、delete(myMap, "Key")は"Key"というキーを持つ要素のみを削除し、"key"や"KEY"というキーを持つ要素は削除しません。 -
http.Header.Del(key string):http.Header型に定義されているメソッドで、指定されたkeyに対応するヘッダーを削除します。このメソッドは、内部でtextproto.CanonicalMIMEHeaderKeyを使用してkeyを正規化し、正規化されたキーに対応するヘッダーをマップから削除します。これにより、ユーザーが指定したキーの大文字・小文字に関わらず、正しいヘッダーが削除されることが保証されます。
技術的詳細
このコミットの技術的なポイントは、http.Header.Del("Host") から delete(req.Header, "Host") への変更が、Host ヘッダーの特殊な扱いに基づいている点です。
通常、http.Header.Del メソッドを使用する理由は、HTTPヘッダー名が大文字・小文字を区別しないという仕様に対応するためです。Del メソッドは内部でキーを正規化するため、例えば req.Header.Del("host") と呼び出しても、マップ内の "Host" キーに対応する値が削除されます。
しかし、このコミットでは delete(req.Header, "Host") と、組み込みの delete 関数を直接使用しています。これは、以下のいずれかの理由が考えられます。
Hostヘッダーの特殊なパース処理:http.Requestのパース処理において、Hostヘッダーは特別に扱われ、その値はreq.Hostフィールドに格納されます。この際、元のヘッダーマップからHostヘッダーを削除する目的は、req.Hostフィールドに値が移された後に、ヘッダーマップ内に重複するHostエントリが存在しないようにするためです。- 正規化の不要性:
req.Header.get("Host")の呼び出しが示唆するように、このコードパスではHostヘッダーが常に正規化されたキー"Host"でアクセスされることが保証されている可能性があります。つまり、req.Header.get("Host")が成功した場合、マップ内には"Host"というキーが存在することが確実であり、その後に削除する際も"Host"というキーを直接指定すれば十分である、という判断です。 - パフォーマンスの最適化:
http.Header.Delメソッドは、キーの正規化処理(textproto.CanonicalMIMEHeaderKeyの呼び出し)を伴います。この正規化処理は、文字列操作やマップルックアップを伴うため、わずかながらオーバーヘッドが発生します。もし、削除対象のキーが常に正規化された形式であることが保証されている場合、直接delete関数を呼び出すことで、このオーバーヘッドを削減し、パフォーマンスをわずかに向上させることができます。
コミットメッセージの「No need to fix case of "Host" string literal.」という記述は、上記の2番目と3番目の理由を強く裏付けています。つまり、このコンテキストでは "Host" という文字列リテラルが常に正しいケース(正規化された形式)であるため、Del メソッドが提供する大文字・小文字の正規化機能は不要であり、直接 delete を使用することでコードを簡潔にし、潜在的なパフォーマンス向上を図っていると考えられます。
この変更は、net/http パッケージの内部実装の最適化であり、外部からこのパッケージを利用するユーザーのコードに直接的な影響を与えるものではありません。しかし、Go言語の標準ライブラリがどのように細かな最適化を行っているかを示す良い例と言えます。
コアとなるコードの変更箇所
変更は src/pkg/net/http/request.go ファイルの以下の部分です。
--- a/src/pkg/net/http/request.go
+++ b/src/pkg/net/http/request.go
@@ -515,7 +515,7 @@ func ReadRequest(b *bufio.Reader) (req *Request, err error) {
if req.Host == "" {
req.Host = req.Header.get("Host")
}
- req.Header.Del("Host")
+ delete(req.Header, "Host")
fixPragmaCacheControl(req.Header)
コアとなるコードの解説
変更されたコードブロックは、ReadRequest 関数内にあります。この関数は、bufio.Reader からHTTPリクエストを読み込み、http.Request 構造体を生成する役割を担っています。
-
if req.Host == "": この条件文は、req.Hostフィールドがまだ設定されていない場合に実行されます。HTTPリクエストのパース中にHostヘッダーが見つからなかった場合や、他の方法でreq.Hostが初期化されていない場合に該当します。 -
req.Host = req.Header.get("Host"):req.Header.get("Host")は、req.Headerマップから "Host" ヘッダーの値を取得します。getメソッドは、内部でヘッダー名を正規化して検索するため、"Host"、"host"、"HOST"など、どのようなケースでヘッダーが送られてきても正しく値を取得できます。取得した値はreq.Hostフィールドに格納されます。 -
- req.Header.Del("Host"): 変更前のコードでは、req.Header.Del("Host")が呼び出されていました。これは、http.Header型のDelメソッドを使用して、ヘッダーマップから "Host" ヘッダーを削除するものです。前述の通り、Delメソッドはキーを正規化してから削除を行います。 -
+ delete(req.Header, "Host"): 変更後のコードでは、Goの組み込み関数であるdeleteが直接使用されています。これにより、req.Headerマップから"Host"というキーを持つエントリが直接削除されます。この変更は、req.Host = req.Header.get("Host")の行で既にHostヘッダーが正規化された形で処理され、req.Hostフィールドに格納されているため、その後の削除においては、正規化されたキー"Host"を使って直接削除しても問題ないという判断に基づいています。これにより、Delメソッドの正規化処理のオーバーヘッドを避けることができます。
この変更は、Host ヘッダーが req.Host フィールドに抽出された後、ヘッダーマップからそのエントリを削除するというロジックの効率化を目的としています。
関連リンク
- Go言語の
net/httpパッケージドキュメント: https://pkg.go.dev/net/http - Go言語の
net/textprotoパッケージドキュメント: https://pkg.go.dev/net/textproto - Goのコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/6278049
参考にした情報源リンク
- RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing: https://datatracker.ietf.org/doc/html/rfc7230 (特にヘッダーフィールド名の大文字・小文字の区別に関するセクション)
- Go言語のマップに関するドキュメント: https://go.dev/blog/maps
- Go言語の
delete組み込み関数に関するドキュメント: https://pkg.go.dev/builtin#delete - Go言語の
http.Header型のソースコード (変更当時のバージョンに近いもの): https://github.com/golang/go/blob/release-branch.go1.0/src/pkg/net/http/header.go (当時のDelメソッドの実装を確認するため) - Go言語の
http.Request型のソースコード (変更当時のバージョンに近いもの): https://github.com/golang/go/blob/release-branch.go1.0/src/pkg/net/http/request.go (当時のReadRequest関数の実装を確認するため) - Go言語の
textproto.CanonicalMIMEHeaderKeyのソースコード: https://github.com/golang/go/blob/master/src/net/textproto/reader.go (現在の実装)
I have generated the commit explanation as requested.