[インデックス 15485] ファイルの概要
このコミットは、Go言語の実験的なcookiejar
パッケージにおいて、文字列操作の効率と可読性を向上させるための変更です。具体的には、"." + str
のような文字列結合とstrings.HasSuffix
の組み合わせを、専用のヘルパー関数hasDotSuffix
に置き換えることで、不要な文字列アロケーションを削減し、コードをより明確にしています。この変更は機能的な振る舞いを変更するものではなく、主に内部的な改善を目的としています。
コミット
exp/cookiejar: eliminate some "."+str garbage.
It's not a big deal, but it's easy to fix.
R=dsymonds CC=dr.volker.dobler, golang-dev https://golang.org/cl/7425043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3b69efb010259b681cfd00b5dec9fe61d5be55a3
元コミット内容
commit 3b69efb010259b681cfd00b5dec9fe61d5be55a3
Author: Nigel Tao <nigeltao@golang.org>
Date: Thu Feb 28 13:46:26 2013 +1100
exp/cookiejar: eliminate some "."+str garbage.
It's not a big deal, but it's easy to fix.
R=dsymonds
CC=dr.volker.dobler, golang-dev
https://golang.org/cl/7425043
変更の背景
この変更の背景には、Go言語のexp/cookiejar
パッケージ内で、特定のドメインマッチングやパブリックサフィックスのチェックにおいて、"." + suffix
という形式で文字列を結合し、その結果に対してstrings.HasSuffix
関数を使用している箇所が複数存在したことがあります。
コミットメッセージにある「eliminate some "."+str garbage. It's not a big deal, but it's easy to fix.」という記述が示すように、これは大きなパフォーマンス問題を引き起こすものではありませんでしたが、"." + suffix
という文字列結合は、その都度新しい文字列を生成し、メモリをアロケートするオーバーヘッドを伴います。特に、これらの操作が頻繁に実行される場合、小さなアロケーションが積み重なり、ガベージコレクションの負荷を増やす可能性があります。
このコミットは、このような冗長な文字列結合とサフィックスチェックのパターンを、より効率的で直接的なhasDotSuffix
というヘルパー関数に置き換えることで、コードのクリーンアップとわずかなパフォーマンス改善を図ることを目的としています。これにより、不必要な文字列アロケーションを避け、コードの意図をより明確にすることができます。
前提知識の解説
Go言語のexp/cookiejar
パッケージ
exp/cookiejar
は、Go言語の標準ライブラリの一部として提供されているnet/http/cookiejar
パッケージの実験的な前身、または関連するパッケージです。HTTPクライアントがWebサーバーとの間でクッキーを管理するための機能を提供します。Webブラウザがクッキーを保存し、後続のリクエストで送信するのと同様に、GoのHTTPクライアントもこのcookiejar
を使用してクッキーの状態を維持します。
クッキーの管理には、ドメインマッチング(どのドメインのクッキーがどのリクエストに適用されるか)やパスマッチング(どのパスのクッキーが適用されるか)といった複雑なロジックが含まれます。これらのロジックは、RFC 6265などの標準仕様に基づいて実装されます。
RFC 6265: HTTP State Management Mechanism
RFC 6265は、HTTPクッキーの動作を定義する標準仕様です。このRFCには、クッキーの生成、保存、送信に関する詳細なルールが記述されており、特に「ドメインマッチング」と「パスマッチング」のアルゴリズムが重要です。
- ドメインマッチング (Domain Matching): クッキーがどのドメインに対して有効であるかを判断するルールです。例えば、
example.com
のクッキーはwww.example.com
にも適用される場合がありますが、other.com
には適用されません。RFC 6265のセクション5.1.3で定義されています。このコミットで変更されたdomainMatch
関数は、このルールの一部を実装しています。 - パブリックサフィックス (Public Suffix):
com
,org
,co.uk
などのドメイン名の末尾部分で、個人や組織が直接登録できない部分を指します。パブリックサフィックスリスト(Public Suffix List)は、クッキーのセキュリティを強化するために使用されます。例えば、example.com
のクッキーがcom
全体に設定されることを防ぎます。RFC 6265のセクション5.3のポイント5で言及されており、psList
(Public Suffix List)が関連します。
Go言語の文字列操作とstrings.HasSuffix
Go言語では、文字列は不変(immutable)です。文字列を結合したり、部分文字列を抽出したりする操作は、新しい文字列を生成し、そのためのメモリをアロケートします。
strings.HasSuffix(s, suffix string) bool
: この関数は、文字列s
が指定されたsuffix
で終わるかどうかをチェックします。例えば、strings.HasSuffix("filename.txt", ".txt")
はtrue
を返します。- 文字列結合 (
+
演算子):s1 + s2
のように+
演算子を使って文字列を結合すると、新しい文字列が作成されます。例えば、"." + "example.com"
は".example.com"
という新しい文字列を生成します。
このコミットの変更は、"." + suffix
という結合によって一時的な文字列が生成されることを避け、直接バイト列を比較することで効率を向上させています。
技術的詳細
このコミットの主要な技術的変更は、strings.HasSuffix(host, "."+e.Domain)
というパターンを、新しく導入されたヘルパー関数hasDotSuffix(s, suffix string)
に置き換えたことです。
元のコードでは、host
文字列が"."
とe.Domain
を結合した文字列で終わるかどうかをチェックしていました。例えば、host
が"www.example.com"
でe.Domain
が"example.com"
の場合、"." + e.Domain
は".example.com"
という新しい文字列を生成し、その後strings.HasSuffix("www.example.com", ".example.com")
が呼び出されます。この".example.com"
という一時的な文字列の生成が、メモリのアロケーションとガベージコレクションの対象となります。
新しいhasDotSuffix
関数は、この一時的な文字列生成を回避します。その実装は以下の通りです。
func hasDotSuffix(s, suffix string) bool {
return len(s) > len(suffix) && s[len(s)-len(suffix)-1] == '.' && s[len(s)-len(suffix):] == suffix
}
この関数は、以下の3つの条件をチェックします。
len(s) > len(suffix)
:s
の長さがsuffix
の長さよりも長いことを確認します。これは、s
がsuffix
の前に少なくとも1文字(この場合は.
)を持つことを保証するためです。s[len(s)-len(suffix)-1] == '.'
:s
の末尾からsuffix
の長さ分を遡った直前の文字が.
であることをチェックします。これにより、s
が... .suffix
という形式であることを確認します。s[len(s)-len(suffix):] == suffix
:s
の末尾len(suffix)
文字がsuffix
と完全に一致することをチェックします。これは、strings.HasSuffix
が内部で行うのと同様の比較です。
これらの条件を組み合わせることで、s
が"."
に続いてsuffix
で終わることを、新しい文字列をアロケートすることなく効率的に判断できます。これは、文字列のインデックスアクセスとスライス操作を直接利用することで実現されています。
この変更は、特にdomainMatch
やdomainAndType
のような頻繁に呼び出される可能性のある関数において、わずかながらもパフォーマンスの改善とメモリ使用量の削減に貢献します。また、コードの意図がhasDotSuffix
という関数名によってより明確になります。
コアとなるコードの変更箇所
src/pkg/exp/cookiejar/jar.go
--- a/src/pkg/exp/cookiejar/jar.go
+++ b/src/pkg/exp/cookiejar/jar.go
@@ -121,7 +121,7 @@ func (e *entry) domainMatch(host string) bool {
if e.Domain == host {
return true
}
- return !e.HostOnly && strings.HasSuffix(host, "."+e.Domain)
+ return !e.HostOnly && hasDotSuffix(host, e.Domain)
}
// pathMatch implements "path-match" according to RFC 6265 section 5.1.4.
@@ -139,6 +139,11 @@ func (e *entry) pathMatch(requestPath string) bool {
return false
}
+// hasDotSuffix returns whether s ends in "."+suffix.
+func hasDotSuffix(s, suffix string) bool {
+ return len(s) > len(suffix) && s[len(s)-len(suffix)-1] == '.' && s[len(s)-len(suffix):] == suffix
+}
+
// byPathLength is a []entry sort.Interface that sorts according to RFC 6265
// section 5.4 point 2: by longest path and then by earliest creation time.
type byPathLength []entry
@@ -469,7 +474,7 @@ func (j *Jar) domainAndType(host, domain string) (string, bool, error) {
// See RFC 6265 section 5.3 #5.
if j.psList != nil {
- if ps := j.psList.PublicSuffix(domain); ps != "" && !strings.HasSuffix(domain, "."+ps) {
+ if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) {
if host == domain {
// This is the one exception in which a cookie
// with a domain attribute is a host cookie.
@@ -481,7 +486,7 @@ func (j *Jar) domainAndType(host, domain string) (string, bool, error) {
// The domain must domain-match host: www.mycompany.com cannot
// set cookies for .ourcompetitors.com.
- if host != domain && !strings.HasSuffix(host, "."+domain) {
+ if host != domain && !hasDotSuffix(host, domain) {
return "", false, errIllegalDomain
}
src/pkg/exp/cookiejar/jar_test.go
--- a/src/pkg/exp/cookiejar/jar_test.go
+++ b/src/pkg/exp/cookiejar/jar_test.go
@@ -40,6 +40,80 @@ func newTestJar() *Jar {
return jar
}
+var hasDotSuffixTests = [...]struct {
+ s, suffix string
+}{
+ {"", ""},
+ {"", "."},
+ {"", "x"},
+ {".", ""},
+ {".", "."},
+ {".", ".."},
+ {".", "x"},
+ {".", "x."},
+ {".", ".x"},
+ {".", ".x."},
+ {"x", ""},
+ {"x", "."},
+ {"x", ".."},
+ {"x", "x"},
+ {"x", "x."},
+ {"x", ".x"},
+ {"x", ".x."},
+ {".x", ""},
+ {".x", "."},
+ {".x", ".."},
+ {".x", "x"},
+ {".x", "x."},
+ {".x", ".x"},
+ {".x", ".x."},
+ {"x.", ""},
+ {"x.", "."},
+ {"x.", ".."},
+ {"x.", "x"},
+ {"x.", "x."},
+ {"x.", ".x"},
+ {"x.", ".x."},
+ {"com", ""},
+ {"com", "m"},
+ {"com", "om"},
+ {"com", "com"},
+ {"com", ".com"},
+ {"com", "x.com"},
+ {"com", "xcom"},
+ {"com", "xorg"},
+ {"com", "org"},
+ {"com", "rg"},
+ {"foo.com", ""},
+ {"foo.com", "m"},
+ {"foo.com", "om"},
+ {"foo.com", "com"},
+ {"foo.com", ".com"},
+ {"foo.com", "o.com"},
+ {"foo.com", "oo.com"},
+ {"foo.com", "foo.com"},
+ {"foo.com", ".foo.com"},
+ {"foo.com", "x.foo.com"},
+ {"foo.com", "xfoo.com"},
+ {"foo.com", "xfoo.org"},
+ {"foo.com", "foo.org"},
+ {"foo.com", "oo.org"},
+ {"foo.com", "o.org"},
+ {"foo.com", ".org"},
+ {"foo.com", "org"},
+ {"foo.com", "rg"},
+}
+
+func TestHasDotSuffix(t *testing.T) {
+ for _, tc := range hasDotSuffixTests {
+ got := hasDotSuffix(tc.s, tc.suffix)
+ want := strings.HasSuffix(tc.s, "."+tc.suffix)
+ if got != want {
+ t.Errorf("s=%q, suffix=%q: got %v, want %v", tc.s, tc.suffix, got, want)
+ }
+ }
+}
+
var canonicalHostTests = map[string]string{
"www.example.com": "www.example.com",
"WWW.EXAMPLE.COM": "www.example.com",
コアとなるコードの解説
src/pkg/exp/cookiejar/jar.go
の変更
-
hasDotSuffix
関数の追加:jar.go
ファイルに、新しいヘルパー関数hasDotSuffix(s, suffix string) bool
が追加されました。この関数は、文字列s
が"."
に続いてsuffix
で終わるかどうかを効率的に判定します。前述の「技術的詳細」セクションで説明したように、文字列の長さチェックとインデックスアクセスを組み合わせることで、一時的な文字列アロケーションを避けています。 -
既存コードの
hasDotSuffix
への置き換え:domainMatch
関数内のstrings.HasSuffix(host, "."+e.Domain)
がhasDotSuffix(host, e.Domain)
に置き換えられました。domainMatch
は、クッキーのドメインがリクエストのホストと一致するか、またはホストがクッキーのドメインのサブドメインであるかを判断するために使用されます。domainAndType
関数内の2箇所で、同様にstrings.HasSuffix
と文字列結合のパターンがhasDotSuffix
に置き換えられました。この関数は、クッキーのドメイン属性が有効であるか、およびホストとドメインの関係を検証するために使用されます。特に、パブリックサフィックスのチェック(RFC 6265 section 5.3 #5)と、ドメインがホストのドメインマッチングルールに従っているかのチェックに関連します。
これらの変更により、cookiejar
パッケージの内部で発生していた小さな文字列アロケーションが削減され、コードの意図がより明確になりました。
src/pkg/exp/cookiejar/jar_test.go
の変更
-
hasDotSuffixTests
変数の追加:hasDotSuffix
関数の動作を検証するための広範なテストケースが定義された構造体スライスhasDotSuffixTests
が追加されました。このテストケースには、空文字列、単一文字、ドットを含む文字列、一般的なドメイン名など、様々なs
とsuffix
の組み合わせが含まれています。これにより、hasDotSuffix
関数が期待通りに動作することを確認できます。 -
TestHasDotSuffix
関数の追加:hasDotSuffix
関数をテストするためのTestHasDotSuffix
関数が追加されました。このテスト関数は、hasDotSuffixTests
内の各テストケースをループし、hasDotSuffix(tc.s, tc.suffix)
の戻り値と、元のstrings.HasSuffix(tc.s, "."+tc.suffix)
の戻り値を比較します。両者が一致しない場合にエラーを報告します。これにより、新しいhasDotSuffix
関数が、置き換え前のロジックと完全に同じ結果を返すことが保証されます。
テストの追加は、リファクタリングにおいて非常に重要です。新しい関数が既存の振る舞いを壊していないことを確認するための安全網となります。
関連リンク
- Go CL 7425043: https://golang.org/cl/7425043
参考にした情報源リンク
- RFC 6265 - HTTP State Management Mechanism: https://datatracker.ietf.org/doc/html/rfc6265
- Public Suffix List: https://publicsuffix.org/
- Go言語
strings
パッケージ: https://pkg.go.dev/strings - Go言語
net/http/cookiejar
パッケージ: https://pkg.go.dev/net/http/cookiejar