首頁 > 軟體

Go 標準庫 http.FileServer 實現靜態檔案服務

2020-06-16 16:41:00

http.FileServer 方法屬於標準庫 net/http,返回一個使用 FileSystem 介面 root 提供檔案存取服務的 HTTP 處理器。可以方便的實現靜態檔案伺服器。

http.ListenAndServe(":8080", http.FileServer(http.Dir("/files/path")))

存取 http://127.0.0.1:8080,即可看到類似 Nginx 中 autoindex 目錄瀏覽功能。

原始碼解析

我們現在開始將上述的那僅有的一行程式碼進行剖析,看看到底是如何實現的。原始碼中英文注釋也比較詳細,可以參考。

我們先看 http.Dir(),再看 http.FileServer(),而 http.ListenAndServe() 監聽 TCP 埠並提供路由服務,此處不贅述。

http.Dir()

從以下原始碼我們可以看出,type Dir string 實現了 type FileSystem interface 的介面函數 Openhttp.Dir("/") 實際返回的是 http.Dir 型別,將字串路徑轉換成檔案系統。

// 所屬檔案: src/net/http/fs.go, 26-87行

type Dir string

func (d Dir) Open(name string) (File, error) {
    // ...
}

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

http.FileServer()

http.FileServer() 方法返回的是 fileHandler 範例,而 fileHandler 結構體實現了 Handler 介面的方法 ServeHTTP()ServeHTTP 方法內的核心是 serveFile() 方法。

// 所屬檔案: src/net/http/fs.go, 690-716行

type fileHandler struct {
    root FileSystem
}

func FileServer(root FileSystem) Handler {
    return &fileHandler{root}
}

func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
    upath := r.URL.Path
    if !strings.HasPrefix(upath, "/") {
        upath = "/" + upath
        r.URL.Path = upath
    }
    serveFile(w, r, f.root, path.Clean(upath), true)
}
// 所屬檔案: src/net/http/server.go, 82行

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

serveFile() 方法判斷,如果存取路徑是目錄,則列出目錄內容,如果是檔案則使用 serveContent() 方法輸出檔案內容。serveContent() 方法則是個讀取檔案內容並輸出的方法,此處不再貼程式碼。

// 所屬檔案: src/net/http/fs.go, 540行

// name is '/'-separated, not filepath.Separator.
func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {

    // 中間程式碼已省略

    if d.IsDir() {
        if checkIfModifiedSince(r, d.ModTime()) == condFalse {
            writeNotModified(w)
            return
        }
        w.Header().Set("Last-Modified", d.ModTime().UTC().Format(TimeFormat))
        dirList(w, r, f)
        return
    }

    // serveContent will check modification time
    sizeFunc := func() (int64, error) { return d.Size(), nil }
    serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f)
}

支援子目錄路徑

http.StripPrefix() 方法配合 http.Handle()http.HandleFunc() 可以實現帶路由字首的檔案服務。

package main

import (
    "net/http"
    "fmt"
)

func main() {

    http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))

    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        fmt.Println(err)
    }

}

IT145.com E-mail:sddin#qq.com