[原]Golang FileServer

轉載請註明出處html

 

今天咱們用go來搭建一個文件服務器FileServer,而且咱們簡單分析一下,它到底是如何工做的。知其然,並知其因此然!web

首先搭建一個最簡單的,資源就掛載在服務器的根目錄下,而且路由路徑爲根路徑:127.0.0.1:8080/瀏覽器

    
    http.Handle("/", http.FileServer(http.Dir("sourse")))

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

服務器程序和資源結構以下:服務器

 

打開源碼,咱們定位到net/http/fs.go文件中,看看http.FileServer是如何定義的函數

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

原來FileServer函數是返回一個Handler,接下來咱們再看看fileHandler是怎麼定義的字體

type fileHandler struct {
    root FileSystem
}

原來是個結構體,既然是個Handler,那麼它必定實現了ServeHttp函數,找找看網站

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) //看來關鍵在這裏
}

進入到關鍵函數serveFile看看,它的函數聲明以下:url

func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) //最後一個參數表示是否從新定向,在web服務中,它老是true

這裏最後一個參數很重要,咱們下面會揭示爲何,好啦,看看源碼,無關部分我都砍掉:spa

func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
    const indexPage = "/index.html"

    // redirect .../index.html to .../
    // can't use Redirect() because that would make the path absolute,
    // which would be a problem running under StripPrefix
    if strings.HasSuffix(r.URL.Path, indexPage) {
        localRedirect(w, r, "./")
        return
    }

    f, err := fs.Open(name)
    if err != nil {
        msg, code := toHTTPError(err)
        Error(w, msg, code)
        return
    }
    defer f.Close()

    d, err := f.Stat()
    if err != nil {
        msg, code := toHTTPError(err)
        Error(w, msg, code)
        return
    }

    if redirect {
        // redirect to canonical path: / at end of directory url
        // r.URL.Path always begins with /
        url := r.URL.Path
        if d.IsDir() {
            if url[len(url)-1] != '/' {
                localRedirect(w, r, path.Base(url)+"/")     ---------------------------- 1 return
            }
        } else {
            if url[len(url)-1] == '/' {
                localRedirect(w, r, "../"+path.Base(url))   ---------------------------- 2 return
            }
        }
    }

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

重點看到紅色標註部分,如今咱們假設咱們請求是http://127.0.0.1/abc/d.jpg。那麼咱們 r.URL.Path的值就是/abc/d.jpg,因而乎,程序進入到1部分(看我藍色字體標註),path.Base()函數是取函數最後/部分,也就是/d.jpg。如今請求變成了/d.jpg,而後進行重定向,這時瀏覽器根據重定向內容再次發送請求,此次請求的url.Path是咱們上一次處理好的/d.jpg,最後,程序便順利的進入到了第3部分(見我藍色字體標註)。serveContent 這個函數是最終向瀏覽器發送資源文件的code

 

大概的一個處理文件資源請求的流程就是這樣子,如今咱們來解釋一下,爲何

func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) 函數的第四個參數那麼重要

由於在web服務中,咱們發現它永遠都是true,這就致使了咱們的url不管是什麼,都將會被它cut成只剩最後一部分/xxx.jpg相似的樣子。換句話說,假設咱們爲文本服務器設置的路由格式是/xxx/xxx/xxx/x.jpg的話。
那麼文本服務器根本無法正常工做,由於它只認識/xx.jpg的路由格式。

這或許也正是你在網上找相關資料的時候,發現你們轉發的內容都是將文本服務器掛載在根節點上。

"/"路由咱們一般會將它拿來作網站的入口,這樣豈不是很不爽了?那麼有沒有解決的辦法呢? 固然是有的啦,在net/http/server.go文件中,有這麼一個函數:
// StripPrefix returns a handler that serves HTTP requests
// by removing the given prefix from the request URL's Path
// and invoking the handler h. StripPrefix handles a
// request for a path that doesn't begin with prefix by
// replying with an HTTP 404 not found error.
func StripPrefix(prefix string, h Handler) Handler {
    if prefix == "" {
        return h
    }
    return HandlerFunc(func(w ResponseWriter, r *Request) {
        if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
            r.URL.Path = p
            h.ServeHTTP(w, r)
        } else {
            NotFound(w, r)
        }
    })
}

根據註釋以及代碼來看,它的做用是返回一個Handler,可是這個Handler呢,有點點不同,不同在哪裏呢,它會過濾掉一部分路由前綴。

好比咱們有以下路由:/aaa/bbb/ccc.jpg,那麼執行StripPrefix("/aaa/bbb", ..handler)以後,咱們將會獲得一個新的Handler,這個新Handler的執行函數和原來的handler是同樣的,可是這個新Handler在處理路由請求的時候,會自動將/aaa/bbb/ccc.jpg理解爲/aaa.jpg

 

好啦,分析到這裏,咱們如今再來搭建一個路由路徑爲/s/下的文件服務器,代碼以下:

func main() {

    http.Handle("/s/", http.StripPrefix("/s/", http.FileServer(http.Dir("sourse"))))  

    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        fmt.Println(err)
    }
}
相關文章
相關標籤/搜索