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

[インデックス 13200] ファイルの概要

このコミットは、Go言語の標準ライブラリである net/http パッケージ内の header.go ファイルに対する変更です。具体的には、hasToken 関数内のコメントが改善され、コードの意図がより明確に説明されています。

コミット

commit 253d7f0460e6547788d707a2f4e5e5b0c0301b2d
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Tue May 29 14:27:07 2012 -0700

    net/http: better comment in hasToken
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6249065

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/253d7f0460e6547788d707a2f4e5e5b0c0301b2d

元コミット内容

net/http: better comment in hasToken

このコミットの目的は、net/http パッケージ内の hasToken 関数に、より良いコメントを追加することです。

変更の背景

Go言語の net/http パッケージは、HTTPクライアントとサーバーの実装を提供します。HTTPヘッダーの解析は、このパッケージの重要な機能の一つです。hasToken 関数は、HTTPヘッダーの値が特定のトークンを含んでいるかどうかを効率的にチェックするために内部的に使用されます。例えば、Connection ヘッダーが closekeep-alive といったトークンを含むか、あるいは Expect ヘッダーが 100-continue を含むかなどを判断する際に利用されます。

この関数は、パフォーマンスを考慮して最適化されており、特に文字列の比較において工夫が凝らされています。元のコードは機能的には正しかったものの、その最適化されたロジック、特にASCII文字の比較と大文字・小文字を区別しない比較(EqualFold)の組み合わせに関する説明が不足していました。

このコミットは、コードの可読性と保守性を向上させることを目的としています。将来の開発者がこのコードを理解し、必要に応じて修正する際に、コメントがその助けとなるように、より詳細な説明が追加されました。これは、Go言語のプロジェクトにおけるコード品質とドキュメンテーションの重視を示す典型的な例です。

前提知識の解説

HTTPヘッダーとトークン

HTTP(Hypertext Transfer Protocol)は、Web上でデータを交換するためのプロトコルです。HTTPリクエストとレスポンスは、ヘッダーと呼ばれるメタデータを含んでいます。ヘッダーは Key: Value の形式で構成され、例えば Content-Type: text/htmlConnection: keep-alive のようになります。

HTTPヘッダーの値には、しばしば「トークン」と呼ばれる特定の文字列が含まれます。RFC 7230(HTTP/1.1 Message Syntax and Routing)では、トークンは特定の文字セット(ASCII文字、数字、一部の記号)で構成されると定義されています。hasToken 関数は、このようなトークンがヘッダー値の中に存在するかどうかを効率的に検出するために設計されています。

Go言語の文字列処理とバイトスライス

Go言語では、文字列はUTF-8エンコードされたバイトのシーケンスとして扱われます。しかし、ASCII文字は1バイトで表現されるため、ASCII文字の比較はバイト単位で行うことができます。

hasToken 関数では、v[sp] のようにバイトスライスとして文字列にアクセスしています。これは、文字列全体をデコードすることなく、個々のバイトを直接操作することでパフォーマンスを向上させるための一般的な手法です。

大文字・小文字を区別しない比較 (EqualFold)

HTTPヘッダーのフィールド名や一部のヘッダー値は、大文字・小文字を区別しない(case-insensitive)で扱われることがあります。例えば、Connection: Keep-AliveConnection: keep-alive は同じ意味として解釈されます。

Go言語の strings パッケージには EqualFold 関数があり、これは2つの文字列が大文字・小文字を区別せずに等しいかどうかをチェックします。hasToken 関数では、この EqualFold を利用して、トークンが大文字・小文字を区別せずにヘッダー値内に存在するかを判断しています。

ビット演算 b|0x20

このコミットで追加されたコメントで説明されている b|0x20 は、ビット演算子 | (OR) と16進数の 0x20 を使用しています。

  • ASCII文字において、大文字のアルファベット(A-Z)と小文字のアルファベット(a-z)は、対応する文字のASCII値の5ビット目が異なります。具体的には、小文字のASCII値は対応する大文字のASCII値に 0x20 を加算した値になります。
    • 例: 'A' (0x41) | 0x20 = 0x61 ('a')
    • 例: 'B' (0x42) | 0x20 = 0x62 ('b')
  • したがって、b|0x20 は、b が大文字のASCIIアルファベットであれば対応する小文字に変換し、小文字のASCIIアルファベットであればそのまま小文字を維持します。非アルファベット文字の場合は、通常は元の値が維持されるか、意図しない値になる可能性がありますが、この文脈ではトークンがASCII文字であることを前提としているため、アルファベット文字の変換に利用されています。

この最適化は、hasToken 関数がトークンの最初の文字を効率的にチェックするために使用されています。これにより、EqualFold を呼び出す前に、大文字・小文字を区別せずに最初の文字が一致するかどうかを高速に判断し、不要な EqualFold の呼び出しをスキップすることができます。

技術的詳細

hasToken 関数は、v (ヘッダー値の文字列) の中に token (検索対象のトークン) が存在するかどうかをチェックします。この関数は、トークンがASCII文字であることを前提としています。

変更前のコードでは、ループ内で v の各位置 sp からトークンが始まる可能性をチェックしていました。最初の文字のチェックは以下の行で行われていました。

if b := v[sp]; b != token[0] && b|0x20 != token[0] {
    continue
}

この行の意図は、v[sp](ヘッダー値の現在のバイト)が token[0](トークンの最初のバイト)と一致しない、かつ、v[sp] を小文字に変換したものが token[0] とも一致しない場合に、この位置 sp からの比較をスキップするというものです。

追加されたコメントは、このロジックの背後にある理由を明確にしています。

  1. The token is ASCII, so checking only a single byte is sufficient.: トークンがASCII文字であるため、UTF-8のマルチバイト文字を考慮する必要がなく、バイト単位の比較で十分であることを示しています。
  2. We skip this potential starting position if both the first byte and its potential ASCII uppercase equivalent (b|0x20) don't match.: b|0x20 の操作が、b が大文字の場合に小文字に変換する目的であることを説明しています。これにより、大文字・小文字を区別せずに最初の文字が一致するかどうかを効率的にチェックしています。
  3. False positives ('^' => '~') are caught by EqualFold.: b|0x20 のようなビット演算は、アルファベット以外の文字に対しては意図しない変換を行う可能性があります(例: '^' が '~' になる)。しかし、このような「誤検出」は、その後の EqualFold 関数による厳密な大文字・小文字を区別しない比較によって適切に処理されるため、問題ないことを保証しています。つまり、b|0x20 はあくまで高速な事前フィルタリングであり、最終的な正確性は EqualFold に委ねられているという設計思想が示されています。

このコメントの追加により、コードの意図、特にパフォーマンス最適化のためのビット演算の使用と、その後の EqualFold による正確性の保証という二段階のチェックメカニズムが、より明確に理解できるようになりました。

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

変更は src/pkg/net/http/header.go ファイルの hasToken 関数内で行われました。

--- a/src/pkg/net/http/header.go
+++ b/src/pkg/net/http/header.go
@@ -99,6 +99,11 @@ func hasToken(v, token string) bool {
 	}\n 	for sp := 0; sp <= len(v)-len(token); sp++ {\n \t\t// Check that first character is good.\n+\t\t// The token is ASCII, so checking only a single byte\n+\t\t// is sufficient.  We skip this potential starting\n+\t\t// position if both the first byte and its potential\n+\t\t// ASCII uppercase equivalent (b|0x20) don\'t match.\n+\t\t// False positives (\'^\' => \'~\') are caught by EqualFold.\n \t\tif b := v[sp]; b != token[0] && b|0x20 != token[0] {\n \t\t\tcontinue\n \t\t}\n```

追加されたのは、`// Check that first character is good.` の下に続く5行のコメントです。

## コアとなるコードの解説

追加されたコメントは、`hasToken` 関数内の以下の `if` 文のロジックを詳細に説明しています。

```go
		// Check that first character is good.
		// The token is ASCII, so checking only a single byte
		// is sufficient.  We skip this potential starting
		// position if both the first byte and its potential
		// ASCII uppercase equivalent (b|0x20) don't match.
		// False positives ('^' => '~') are caught by EqualFold.
		if b := v[sp]; b != token[0] && b|0x20 != token[0] {
			continue
		}

このコードブロックは、v (ヘッダー値) の現在の位置 sp から token が始まる可能性があるかどうかを、最初の文字(バイト)をチェックすることで高速に判断しています。

  1. b := v[sp]:ヘッダー値 v の現在の位置 sp のバイトを b に代入します。
  2. b != token[0]btoken の最初のバイトと直接一致しない場合。
  3. b|0x20 != token[0]b を小文字に変換したものが token の最初のバイトと一致しない場合。

これら2つの条件が両方とも真である場合(&& 論理AND)、つまり、現在のバイト b が大文字・小文字を区別しても token の最初のバイトと一致しない場合、この位置 sp から token が始まる可能性はないと判断し、continue で次の sp の位置にスキップします。

この事前チェックは、strings.EqualFold の呼び出しを避けることでパフォーマンスを向上させるための最適化です。EqualFold はよりコストの高い操作であるため、最初のバイトの簡単なチェックで多くの不一致を排除できると、全体の処理速度が向上します。

コメントは、この最適化がASCIIトークンに依存していること、そして b|0x20 のビット演算がどのように大文字・小文字を区別しない比較を効率的に行っているかを明確にしています。また、この高速なチェックが「誤検出」を引き起こす可能性があるものの、最終的な正確性は EqualFold によって保証されるという重要な点も説明しています。

関連リンク

参考にした情報源リンク