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

[インデックス 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つの関連する問題を解決するために行われました。

  1. http.ServeFileがカレントワーキングディレクトリ (CWD) からファイルを正しく提供できない問題: 以前のhttp.ServeFileの実装では、ファイルパスの解決において、カレントワーキングディレクトリからの相対パスが期待通りに機能しないケースがありました。特に、http.FileServerhttp.Dir("")http.Dir(".")で初期化された場合に、CWD内のファイルを正しく見つけられない、または提供できないというバグが存在していました。これは、Webサーバーが提供する静的ファイルが、サーバー起動時のディレクトリに存在する場合に問題となります。

  2. 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.FileSystemOpenメソッドを呼び出すため、このOpenメソッドの修正が直接的な解決策となります。

テストの追加

変更の正しさを検証するために、src/pkg/net/http/fs_test.goに2つの新しいテスト関数が追加されています。

  1. TestEmptyDirOpenCWD: このテストは、http.Dir("")http.Dir(".")http.Dir("./")のそれぞれが、カレントワーキングディレクトリ内のファイル(この場合はfs_test.go自身)を正しく開けることを検証します。これは、http.Dir("")http.Dir(".")と等価に扱われるという新しい挙動を直接的にテストするものです。

  2. 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からのファイル提供問題」が解決されたことを直接的に示します。

これらのテストは、修正が期待通りに動作し、将来的なリグレッションを防ぐための重要なセーフティネットとなります。

関連リンク

参考にした情報源リンク