技術向上

プログラミングの学び、気になるテクノロジーやビジネストレンドを発信

http.FileServer()で階層ごと複数ファイルをサーブする【Go】

http.FileServer()関数を使って、階層ごと複数ファイルをサーブすることができます。

返り値の型はHandlerです。
よって、指定されたURLにアクセスされた際にHandlerを実行する、
http.Handle()関数の第2引数に指定することができます。

func http.FileServe(root FileSystem) Handler

引数はFileSystemであり、これは次のようなinterfaceです。

type FileSystem interface {
        Open(name string) (File, error)
}

したがって、Open(name string) (File, error)が実装されているものはFileSystemと見なすことができます。 これに該当するのが、type Dirです。下記メソッドが定義されています。

func (d Dir) Open(name string) (File, error)

よって、http.Dirをhttp.FileServe()の引数とすることができます。
パスを指定するときは、次のように文字列をhttp.Dirにキャストします。

http.Dir(".")    // 同階層のすべてのファイル、ディレクトリが対象

このように「.」を用いた、gitライクな階層指定が可能です。


以上を踏まえて、main.goを次のようにします。

func main() {
    http.Handle("/", http.FileServer(http.Dir("../sample")))    // 一つ上の階層にあるsampleディレクトリ以下すべてが対象
    http.HandleFunc("/pict", pict)     // HandleFuncで他のhandlerを追加
    http.ListenAndServe(":8080", nil)
}

func pict(w http.ResponseWriter, req *http.Request) {
    f, err := os.Open("pict.png")
    if err != nil {
        http.Error(w, "file not found", 404)
    }
    defer f.Close()
    fi, err := f.Stat()
    if err != nil {
        http.Error(w, "file not found", 404)
    }
    http.ServeContent(w, req, fi.Name(), fi.ModTime(), f)
}


一度成功したコードを修正して、他の階層を試そうとしても変わらない場合は、
キャッシュをクリアすると解決するかもしれません。

単一ファイルには、http.ServeFile()かhttp.ServeContent()を使います。

http.ServeContent()【Go】 - 技術向上


http.FileServer()には、一つ問題があります。

http.Handle("/abc", http.FileServer(http.Dir("../sample")))

というように、http.Handle()の第1引数で階層を特定した場合に、
http.FileServer()は「http.Dirにキャストしたパス + http.Handle()の第1引数」を捜します。
つまり、sampleの下にabcという階層を用意する必要があります。これでは不便です。

この問題を解決する方法を下記で紹介しています。

http.StripPrefix()でフォルダ階層とURLの指定を分離する【Go】 - 技術向上