[原]Golang FileServer


轉載請注明出處

 

今天我們用go來搭建一個文件服務器FileServer,並且我們簡單分析一下,它究竟是如何工作的。知其然,並知其所以然!

首先搭建一個最簡單的,資源就掛載在服務器的根目錄下,並且路由路徑為根路徑: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看看,它的函數聲明如下:

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

這里最后一個參數很重要,我們下面會揭示為什么,好啦,看看源碼,無關部分我都砍掉:

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 這個函數是最終向瀏覽器發送資源文件的

 

大概的一個處理文件資源請求的流程就是這樣子,現在我們來解釋一下,為什么

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)
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM