[インデックス 10431] ファイルの概要
このコミットは、Go言語の標準ライブラリnet/http
パッケージにおけるファイルサービングの挙動に関する修正と改善を目的としています。具体的には、http.ServeFile
関数がカレントワーキングディレクトリ (CWD) からファイルを正しく提供できない問題と、http.Dir("")
がhttp.Dir(".")
と等価に扱われるようにする改善が含まれています。
コミット
commit 0b1bcf8f94620b34396b3549ea959646e830c7c8
Author: Andrew Gerrand <adg@golang.org>
Date: Thu Nov 17 11:42:25 2011 +1100
http: fix serving from CWD with http.ServeFile
http: make Dir("") equivalent to Dir(".")
Fixes #2471.
R=golang-dev, bradfitz, rsc
CC=golang-dev
https://golang.org/cl/5370061
---
src/pkg/net/http/fs.go | 8 +++++++-
src/pkg/net/http/fs_test.go | 28 ++++++++++++++++++++++++++++
2 files changed, 35 insertions(+), 1 deletion(-)
diff --git a/src/pkg/net/http/fs.go b/src/pkg/net/http/fs.go
index 5f91ff5cbf..5aadac17a2 100644
--- a/src/pkg/net/http/fs.go
+++ b/src/pkg/net/http/fs.go
@@ -22,13 +22,19 @@ import (
// A Dir implements http.FileSystem using the native file
// system restricted to a specific directory tree.
+//
+// An empty Dir is treated as ".".
type Dir string
func (d Dir) Open(name string) (File, error) {
if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 {
return nil, errors.New("http: invalid character in file path")
}
- f, err := os.Open(filepath.Join(string(d), filepath.FromSlash(path.Clean("/"+name))))
+ dir := string(d)
+ if dir == "" {
+ dir = "."
+ }
+ f, err := os.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))))
if err != nil {
return nil, err
}
diff --git a/src/pkg/net/http/fs_test.go b/src/pkg/net/http/fs_test.go
index e1a784c1f6..6697189900 100644
--- a/src/pkg/net/http/fs_test.go
+++ b/src/pkg/net/http/fs_test.go
@@ -208,6 +208,20 @@ func TestDirJoin(t *testing.T) {
test(Dir("/etc/hosts"), "../")
}
+func TestEmptyDirOpenCWD(t *testing.T) {
+ test := func(d Dir) {
+ name := "fs_test.go"
+ f, err := d.Open(name)
+ if err != nil {
+ t.Fatalf("open of %s: %v", name, err)
+ }
+ defer f.Close()
+ }
+ test(Dir(""))
+ test(Dir("."))
+ test(Dir("./"))
+}
+
func TestServeFileContentType(t *testing.T) {
const ctype = "icecream/chocolate"
override := false
@@ -247,6 +261,20 @@ func TestServeFileMimeType(t *testing.T) {
}
}
+func TestServeFileFromCWD(t *testing.T) {
+ ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ ServeFile(w, r, "fs_test.go")
+ }))
+ defer ts.Close()
+ r, err := Get(ts.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if r.StatusCode != 200 {
+ t.Fatalf("expected 200 OK, got %s", r.Status)
+ }
+}
+
func TestServeFileWithContentEncoding(t *testing.T) {
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
w.Header().Set("Content-Encoding", "foo")
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0b1bcf8f94620b34396b3549ea959646e830c7c8
元コミット内容
http: fix serving from CWD with http.ServeFile
http: make Dir("") equivalent to Dir(".")
Fixes #2471.
変更の背景
このコミットは、Go言語のnet/http
パッケージにおける2つの関連する問題を解決するために行われました。
-
http.ServeFile
がカレントワーキングディレクトリ (CWD) からファイルを正しく提供できない問題: 以前のhttp.ServeFile
の実装では、ファイルパスの解決において、カレントワーキングディレクトリからの相対パスが期待通りに機能しないケースがありました。特に、http.FileServer
がhttp.Dir("")
やhttp.Dir(".")
で初期化された場合に、CWD内のファイルを正しく見つけられない、または提供できないというバグが存在していました。これは、Webサーバーが提供する静的ファイルが、サーバー起動時のディレクトリに存在する場合に問題となります。 -
http.Dir("")
とhttp.Dir(".")
の挙動の不一致:http.Dir
型は、ファイルシステム上の特定のディレクトリツリーに制限されたhttp.FileSystem
を実装するために使用されます。直感的には、空文字列""
で初期化されたDir
は、カレントワーキングディレクトリを表す"."
と同じように振る舞うべきです。しかし、以前の実装ではこの等価性が保証されておらず、異なる挙動を示す可能性がありました。これにより、開発者が意図しないファイルパス解決の問題に直面する可能性がありました。
これらの問題は、GoのWebサーバーが静的ファイルを提供するための基本的な機能に影響を与えるため、修正が必要でした。特に、Fixes #2471
という記述から、GitHubのIssue #2471で報告されたバグを修正するものであることがわかります。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびWebの基本的な概念を理解しておく必要があります。
net/http
パッケージ: Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。Webアプリケーションを構築する上で中心的な役割を果たします。http.Handler
インターフェース: HTTPリクエストを処理するためのインターフェースで、ServeHTTP(ResponseWriter, *Request)
メソッドを定義します。http.FileServer
:http.FileSystem
インターフェースを実装するオブジェクトを受け取り、そのファイルシステムからHTTPリクエストに応じてファイルを配信するhttp.Handler
を返します。http.FileSystem
インターフェース: ファイルシステムを抽象化するためのインターフェースで、Open(name string) (File, error)
メソッドを定義します。これにより、実際のファイルシステムだけでなく、メモリ上のファイルシステムやアーカイブ内のファイルシステムなど、様々なソースからファイルを読み込むことができます。http.Dir
型:string
型を基盤とした型で、http.FileSystem
インターフェースを実装します。これにより、特定のディレクトリをルートとするファイルシステムとして扱うことができます。http.ServeFile
関数: 特定のファイルパスに基づいてHTTPレスポンスとしてファイルを送信するヘルパー関数です。- カレントワーキングディレクトリ (CWD): プロセスが現在作業しているディレクトリです。相対パスが指定された場合、このディレクトリを基準にファイルが検索されます。
filepath.Join
: 複数のパス要素を結合して、プラットフォーム固有のパス区切り文字を使用して単一のパスを生成する関数です。filepath.FromSlash
: スラッシュ区切りのパスを、現在のオペレーティングシステムのパス区切り文字に変換する関数です。path.Clean
: パスを簡略化し、冗長な要素(例:.
、..
)を削除し、正規化する関数です。
技術的詳細
このコミットの技術的な核心は、http.Dir
型のOpen
メソッドにおけるファイルパスの解決ロジックの変更と、それに関連するテストの追加にあります。
http.Dir.Open
メソッドの変更
以前のhttp.Dir.Open
メソッドでは、os.Open(filepath.Join(string(d), filepath.FromSlash(path.Clean("/"+name))))
という形でファイルパスを構築していました。ここで問題となるのは、string(d)
が空文字列""
の場合です。filepath.Join("", "some/path")
は通常"some/path"
を返しますが、これはカレントワーキングディレクトリからの相対パスとして解釈されるべきです。しかし、特定の条件下やOSの挙動によっては、この解釈が期待通りに行われないことがありました。
修正後のコードでは、dir := string(d)
としてDir
の値を文字列に変換した後、if dir == "" { dir = "." }
という条件分岐を追加しています。これにより、Dir
が空文字列で初期化された場合、明示的に"."
(カレントワーキングディレクトリ)として扱われるようになります。この変更により、filepath.Join
に渡される最初の引数が常に有効なディレクトリパス(空文字列ではない)となり、ファイルパスの解決がより堅牢になります。
この変更は、http.ServeFile
が内部的にhttp.FileSystem
を使用しているため、http.ServeFile
がCWDからファイルを正しく提供できない問題も同時に解決します。http.ServeFile
は、提供するファイルパスを解決する際に、http.FileServer
が使用するhttp.FileSystem
のOpen
メソッドを呼び出すため、このOpen
メソッドの修正が直接的な解決策となります。
テストの追加
変更の正しさを検証するために、src/pkg/net/http/fs_test.go
に2つの新しいテスト関数が追加されています。
-
TestEmptyDirOpenCWD
: このテストは、http.Dir("")
、http.Dir(".")
、http.Dir("./")
のそれぞれが、カレントワーキングディレクトリ内のファイル(この場合はfs_test.go
自身)を正しく開けることを検証します。これは、http.Dir("")
がhttp.Dir(".")
と等価に扱われるという新しい挙動を直接的にテストするものです。 -
TestServeFileFromCWD
: このテストは、http.ServeFile
がカレントワーキングディレクトリからファイルを正しく提供できることを検証します。httptest.NewServer
を使用してテスト用のHTTPサーバーを起動し、そのハンドラ内でServeFile(w, r, "fs_test.go")
を呼び出しています。その後、サーバーにリクエストを送信し、ステータスコードが200 OKであることを確認することで、ファイルが正常に提供されたことを検証します。これは、http.ServeFile
がCWDからファイルを正しく提供できないというバグが修正されたことを確認するためのものです。
これらのテストは、修正が意図した通りに機能し、将来のリグレッションを防ぐための重要な役割を果たします。
コアとなるコードの変更箇所
src/pkg/net/http/fs.go
--- a/src/pkg/net/http/fs.go
+++ b/src/pkg/net/http/fs.go
@@ -22,13 +22,19 @@ import (
// A Dir implements http.FileSystem using the native file
// system restricted to a specific directory tree.
+//
+// An empty Dir is treated as ".".
type Dir string
func (d Dir) Open(name string) (File, error) {
if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 {
return nil, errors.New("http: invalid character in file path")
}
- f, err := os.Open(filepath.Join(string(d), filepath.FromSlash(path.Clean("/"+name))))
+ dir := string(d)
+ if dir == "" {
+ dir = "."
+ }
+ f, err := os.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))))
if err != nil {
return nil, err
}
src/pkg/net/http/fs_test.go
--- a/src/pkg/net/http/fs_test.go
+++ b/src/pkg/net/http/fs_test.go
@@ -208,6 +208,20 @@ func TestDirJoin(t *testing.T) {
test(Dir("/etc/hosts"), "../")
}
+func TestEmptyDirOpenCWD(t *testing.T) {
+ test := func(d Dir) {
+ name := "fs_test.go"
+ f, err := d.Open(name)
+ if err != nil {
+ t.Fatalf("open of %s: %v", name, err)
+ }
+ defer f.Close()
+ }
+ test(Dir(""))
+ test(Dir("."))
+ test(Dir("./"))
+}
+
func TestServeFileContentType(t *testing.T) {
const ctype = "icecream/chocolate"
override := false
@@ -247,6 +261,20 @@ func TestServeFileMimeType(t *testing.T) {
}
}
+func TestServeFileFromCWD(t *testing.T) {
+ ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ ServeFile(w, r, "fs_test.go")
+ }))
+ defer ts.Close()
+ r, err := Get(ts.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if r.StatusCode != 200 {
+ t.Fatalf("expected 200 OK, got %s", r.Status)
+ }
+}
+
func TestServeFileWithContentEncoding(t *testing.T) {
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
w.Header().Set("Content-Encoding", "foo")
コアとなるコードの解説
src/pkg/net/http/fs.go
の変更点
Dir
型のOpen
メソッドは、http.FileSystem
インターフェースの一部として、指定された名前のファイルを開く責任を負います。
変更前:
f, err := os.Open(filepath.Join(string(d), filepath.FromSlash(path.Clean("/"+name))))
この行では、Dir
の基となる文字列string(d)
と、リクエストされたファイル名name
を結合して、ファイルシステム上の絶対パスを構築しようとしていました。しかし、string(d)
が空文字列""
の場合、filepath.Join("", "filename")
は"filename"
を返します。これは、os.Open
がカレントワーキングディレクトリからの相対パスとして解釈しようとしますが、特定のシナリオやOSの挙動によっては、期待通りに動作しないことがありました。
変更後:
dir := string(d)
if dir == "" {
dir = "."
}
f, err := os.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))))
この修正では、まずDir
の値をdir
変数に代入します。次に、dir
が空文字列であるかどうかをチェックし、もし空であればdir
を明示的に"."
に設定します。"."
は、ファイルシステムにおいて「カレントワーキングディレクトリ」を意味する標準的な表記です。これにより、filepath.Join
に渡される最初の引数が常に有効なディレクトリパスとなり、os.Open
がファイルを正しく見つけられるようになります。このシンプルな変更により、http.Dir("")
がhttp.Dir(".")
と等価に振る舞うようになり、http.ServeFile
がCWDからファイルを正しく提供できるようになります。
src/pkg/net/http/fs_test.go
の変更点
追加されたテストは、上記の修正が正しく機能することを保証します。
TestEmptyDirOpenCWD
func TestEmptyDirOpenCWD(t *testing.T) {
test := func(d Dir) {
name := "fs_test.go"
f, err := d.Open(name)
if err != nil {
t.Fatalf("open of %s: %v", name, err)
}
defer f.Close()
}
test(Dir(""))
test(Dir("."))
test(Dir("./"))
}
このテストは、Dir("")
、Dir(".")
、Dir("./")
の3つのケースで、Dir.Open
メソッドがfs_test.go
というファイルをエラーなく開けることを検証します。これにより、空文字列やカレントディレクトリを示すパスが正しく処理されることが確認されます。
TestServeFileFromCWD
func TestServeFileFromCWD(t *testing.T) {
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
ServeFile(w, r, "fs_test.go")
}))
defer ts.Close()
r, err := Get(ts.URL)
if err != nil {
t.Fatal(err)
}
if r.StatusCode != 200 {
t.Fatalf("expected 200 OK, got %s", r.Status)
}
}
このテストは、http.ServeFile
関数がカレントワーキングディレクトリからファイルを正しく提供できることをエンドツーエンドで検証します。httptest.NewServer
を使ってテスト用のHTTPサーバーを立ち上げ、そのハンドラ内でServeFile
を呼び出してfs_test.go
ファイルを配信します。クライアントがこのサーバーにリクエストを送信し、HTTPステータスコードが200 (OK) であることを確認することで、ファイルが正常に提供されたことを検証します。これは、ユーザーが直面していた「CWDからのファイル提供問題」が解決されたことを直接的に示します。
これらのテストは、修正が期待通りに動作し、将来的なリグレッションを防ぐための重要なセーフティネットとなります。
関連リンク
- Go言語
net/http
パッケージのドキュメント: https://pkg.go.dev/net/http - Go言語
os
パッケージのドキュメント: https://pkg.go.dev/os - Go言語
path/filepath
パッケージのドキュメント: https://pkg.go.dev/path/filepath - Go言語
path
パッケージのドキュメント: https://pkg.go.dev/path
参考にした情報源リンク
- コミット情報:
/home/violet/Project/comemo/commit_data/10431.txt
- GitHubコミットページ: https://github.com/golang/go/commit/0b1bcf8f94620b34396b3549ea959646e830c7c8
- Go言語の公式ドキュメント (pkg.go.dev)
- Go言語のソースコード (GitHub)
- 一般的なWebサーバーの概念とファイルサービングのメカニズムに関する知識