zoukankan      html  css  js  c++  java
  • [原]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)
        }
    }
  • 相关阅读:
    【POJ3613 Cow Relays】(广义矩阵乘法)
    【洛谷 P2483】 【模板】k短路([SDOI2010]魔法猪学院)(A*)
    【UVA1505】 Flood-it!(IDA*)
    【CF1095F】 Make It Connected(最小生成树)
    【SP1716】GSS3
    【洛谷 P1641】 [SCOI2010]生成字符串(Catalan数)
    【BZOJ 2351】Matrix(Hash)
    【CH1809】匹配统计(KMP)
    【洛谷 P2633】 Count on a tree(主席树,树上差分)
    【洛谷 P5341】 [TJOI2019]甲苯先生和大中锋的字符串(后缀自动机)
  • 原文地址:https://www.cnblogs.com/ka200812/p/5909094.html
Copyright © 2011-2022 走看看