[インデックス 15044] ファイルの概要
このコミットは、Go言語のドキュメンテーションツールである godoc
コマンドの src/cmd/godoc/godoc.go
ファイルにおけるバグ修正です。godoc
は、Goのソースコードからドキュメントを生成し、HTTPサーバーとして提供するツールです。このファイルは、godoc
サーバーがHTTPリクエストを処理する際のURLリダイレクトロジックを実装しています。具体的には、URLの正規化(末尾のスラッシュの有無の調整)に関する処理が含まれています。
コミット
cmd/godoc: fix buggy use of strings.HasSuffix
This code never worked. Maybe it's not necessary?
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7225070
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7e9f00c80c56fe325d0c0e11c4a8ce4bc368b06e
元コミット内容
cmd/godoc: fix buggy use of strings.HasSuffix
このコミットは、godoc
コマンド内で strings.HasSuffix
関数の誤った使用方法を修正するものです。コミットメッセージには「このコードは一度も機能しなかった。もしかしたら不要なのかもしれない?」とあり、既存のコードが意図した動作をしていなかったことを示唆しています。
変更の背景
godoc
サーバーは、リクエストされたURLのパスを正規化し、必要に応じてリダイレクトを行う機能を持っています。例えば、ディレクトリを示すURLには末尾にスラッシュを付与したり、ファイルを示すURLからは末尾のスラッシュを除去したりすることが一般的です。
このコミット以前のコードでは、このURL正規化のロジックにおいて strings.HasSuffix
関数が誤った引数で呼び出されていました。strings.HasSuffix(s, suffix)
は文字列 s
が suffix
で終わるかを判定する関数ですが、元のコードでは strings.HasSuffix("/", canonical)
のように、定数文字列である "/"
が s
の位置に、動的に変化するパスである canonical
が suffix
の位置に渡されていました。
これにより、"/"
が canonical
で終わるかという、ほとんど常に false
となる条件が評価され、結果として末尾のスラッシュの追加や削除を行うべきロジックが全く機能していませんでした。コミットメッセージにある「このコードは一度も機能しなかった」という記述は、この引数の誤用によるものです。この修正は、godoc
がURLの正規化を正しく行い、ユーザーエクスペリエンスを向上させるために不可欠でした。
前提知識の解説
1. strings.HasSuffix
関数
Go言語の標準ライブラリ strings
パッケージに含まれる関数です。
func HasSuffix(s, suffix string) bool
この関数は、文字列 s
が指定された suffix
で終わる場合に true
を返します。そうでない場合は false
を返します。
例:
strings.HasSuffix("filename.go", ".go")
は true
を返します。
strings.HasSuffix("/path/to/dir/", "/")
は true
を返します。
strings.HasSuffix("/", "/path/to/dir/")
は false
を返します("/"
が /path/to/dir/
で終わることはないため)。
2. pathpkg.Clean
(実際には path.Clean
または filepath.Clean
)
Go言語の標準ライブラリ path
または path/filepath
パッケージに含まれる関数です。
func Clean(path string) string
この関数は、パスを「クリーン」な形式に変換します。具体的には、以下のような処理を行います。
/a/b/../c
のような.
や..
を解決します。/a//b
のような連続するスラッシュを単一のスラッシュに置き換えます。- 末尾のスラッシュを削除します(ルートパス
/
を除く)。 - 空のパスは
.
に変換します。 例:path.Clean("/a/b/../c")
は/a/c
を返します。path.Clean("/a//b/")
は/a/b
を返します。
godoc
の文脈では、http.Request.URL.Path
から取得したパスを正規化するために使用されます。
3. strings.TrimRight
関数
Go言語の標準ライブラリ strings
パッケージに含まれる関数です。
func TrimRight(s, cutset string) string
この関数は、文字列 s
の末尾から cutset
に含まれる文字をすべて削除した新しい文字列を返します。
例:
strings.TrimRight("banana", "an")
は "b"
を返します(末尾から 'a', 'n', 'a', 'n' が削除される)。
strings.TrimRight("/path/to/dir///", "/")
は /path/to/dir
を返します。
4. URLの正規化とリダイレクト
Webサーバーでは、同じリソースに対して複数のURLでアクセスできると、SEO上の問題やキャッシュの非効率性につながることがあります。そのため、URLを統一された形式(正規化された形式)に変換し、元のURLから正規化されたURLへHTTPリダイレクト(通常は301 Moved Permanently)を行うことが一般的です。
godoc
の場合、ディレクトリを示すパスには末尾にスラッシュを付ける(例: /pkg/fmt
を /pkg/fmt/
に)、ファイルを示すパスからは末尾のスラッシュを削除する(例: /pkg/fmt/doc.go/
を /pkg/fmt/doc.go
に)といったルールが適用されます。
5. http.ResponseWriter
と http.Request
Go言語の net/http
パッケージにおけるHTTPハンドラの基本的なインターフェースと構造体です。
http.ResponseWriter
: HTTPレスポンスを書き込むためのインターフェース。ステータスコードの設定、ヘッダーの追加、レスポンスボディの書き込みなどを行います。http.Request
: 受信したHTTPリクエストに関する情報(URL、メソッド、ヘッダー、ボディなど)を保持する構造体。
技術的詳細
このコミットは、godoc
の godoc.go
ファイル内の2つの関数 redirect
と redirectFile
における strings.HasSuffix
の誤用を修正しています。
redirect
関数における修正
redirect
関数は、主にディレクトリパスの正規化を担当しています。pathpkg.Clean
はパスの末尾のスラッシュを削除する特性があるため、ディレクトリを示すパスの場合、pathpkg.Clean
の結果に末尾のスラッシュを再付与する必要があります。
元のコード:
if !strings.HasSuffix("/", canonical) {
canonical += "/"
}
この if
文は、canonical
パスが末尾にスラッシュを持たない場合にスラッシュを追加しようとしています。しかし、strings.HasSuffix("/", canonical)
は、文字列 "/"
が canonical
で終わるかをチェックします。これは、canonical
が正確に "/"
である場合を除き、常に false
を返します。例えば canonical
が "/pkg/fmt"
であっても、strings.HasSuffix("/", "/pkg/fmt")
は false
です。結果として、この条件は常に真となり、canonical
が既にスラッシュで終わっているかどうかにかかわらず、常にスラッシュが追加されてしまうか、あるいは意図したチェックが全く行われない状態でした。
修正後のコード:
if !strings.HasSuffix(canonical, "/") {
canonical += "/"
}
修正後は、strings.HasSuffix(canonical, "/")
となり、canonical
パスが文字列 "/"
で終わるかを正しくチェックします。これにより、canonical
が末尾にスラッシュを持たない場合にのみスラッシュが追加されるようになり、意図したディレクトリパスの正規化が正しく機能するようになりました。
redirectFile
関数における修正
redirectFile
関数は、主にファイルパスの正規化を担当しており、末尾のスラッシュを削除することを目的としています。
元のコード:
for strings.HasSuffix("/", c) {
c = c[:len(c)-1]
}
この for
ループは、パス c
の末尾からスラッシュを繰り返し削除しようとしています。しかし、ここでも strings.HasSuffix("/", c)
という誤った引数順序が使われています。前述の通り、この条件は c
が正確に "/"
である場合を除き、常に false
を返します。したがって、このループはほとんどの場合実行されず、ファイルパスの末尾のスラッシュが削除されることはありませんでした。
修正後のコード:
c = strings.TrimRight(c, "/")
修正後は、strings.TrimRight(c, "/")
を使用しています。この関数は、文字列 c
の末尾から指定された cutset
(この場合は "/"
)に含まれる文字をすべて削除します。これにより、パス c
の末尾に存在するすべてのスラッシュが効率的かつ正確に削除されるようになり、ファイルパスの正規化が正しく機能するようになりました。
この修正により、godoc
サーバーはURLの正規化を意図通りに行い、ユーザーがアクセスするURLが統一され、より予測可能な動作をするようになりました。
コアとなるコードの変更箇所
--- a/src/cmd/godoc/godoc.go
+++ b/src/cmd/godoc/godoc.go
@@ -652,7 +652,7 @@ func applyTemplate(t *template.Template, name string, data interface{}) []byte {
func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) {
canonical := pathpkg.Clean(r.URL.Path)
- if !strings.HasSuffix("/", canonical) {
+ if !strings.HasSuffix(canonical, "/") {
canonical += "/"
}
if r.URL.Path != canonical {
@@ -666,9 +666,7 @@ func redirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) {
func redirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) {
c := pathpkg.Clean(r.URL.Path)
- for strings.HasSuffix("/", c) {
- \tc = c[:len(c)-1]\
- }
+ c = strings.TrimRight(c, "/")
if r.URL.Path != c {
url := *r.URL
url.Path = c
コアとなるコードの解説
redirect
関数内の変更
-
変更前 (
-
行):if !strings.HasSuffix("/", canonical) {
この行は、
canonical
というパス文字列が末尾にスラッシュを持っているかどうかを判定しようとしていました。しかし、strings.HasSuffix(s, suffix)
の引数が逆になっており、s
には定数文字列"/"
が、suffix
には動的なパスcanonical
が渡されていました。これは「"/"
という文字列がcanonical
で終わるか?」という、ほとんど常にfalse
となる無意味なチェックでした。結果として、このif
文の条件は常に真となり、次の行で無条件にスラッシュが追加されるか、あるいは意図したチェックが全く行われない状態でした。 -
変更後 (
+
行):if !strings.HasSuffix(canonical, "/") {
この行では、引数の順序が修正されました。
s
にはパス文字列canonical
が、suffix
には定数文字列"/"
が渡されています。これにより、「canonical
というパス文字列が"/"
で終わらない場合」という正しい条件が評価されるようになりました。この修正により、ディレクトリを示すパスにのみ末尾のスラッシュが適切に追加されるようになります。
redirectFile
関数内の変更
-
変更前 (
-
行):for strings.HasSuffix("/", c) { c = c[:len(c)-1] }
このコードブロックは、パス文字列
c
の末尾からスラッシュを削除しようとしていました。しかし、ここでもstrings.HasSuffix("/", c)
という誤った引数順序が使われていました。この条件は、c
が正確に"/"
である場合を除き、常にfalse
を返します。そのため、このfor
ループの本体(c = c[:len(c)-1]
)はほとんど実行されず、ファイルパスの末尾のスラッシュが削除されることはありませんでした。 -
変更後 (
+
行):c = strings.TrimRight(c, "/")
この行では、
strings.TrimRight
関数が導入されました。strings.TrimRight(c, "/")
は、文字列c
の末尾から、引数cutset
で指定された文字(この場合は"/"
)をすべて削除した新しい文字列を返します。これにより、for
ループと手動での文字列操作を行うことなく、パスの末尾にある不要なスラッシュを効率的かつ正確に削除できるようになりました。これは、コードの簡潔さと正確性を向上させる優れた変更です。
これらの変更により、godoc
はURLの正規化を正しく行い、ユーザーがアクセスするURLが統一され、より予測可能な動作をするようになりました。
関連リンク
- Go言語
strings
パッケージ: https://pkg.go.dev/strings - Go言語
path
パッケージ: https://pkg.go.dev/path - Go言語
net/http
パッケージ: https://pkg.go.dev/net/http
参考にした情報源リンク
- 特になし(Go標準ライブラリのドキュメントとコミットの差分から直接分析しました)。