42

Go语言中间件框架 Negroni 的静态文件处理源码分析

 5 years ago
source link: https://studygolang.com/articles/15269?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Negroni是一个非常棒的中间件,尤其是其中间件调用链优雅的设计,以及对GO HTTP 原生处理器的兼容。我以前写过两篇文章,对Negroni进行了专门的分析,没有看过的朋友可以在看下。

Go语言经典库使用分析(五)| Negroni 中间件(一) http://www.flysnow.org/2017/08/20/go-classic-libs-negroni-one.html

Go语言经典库使用分析(六)| Negroni 中间件(二) http://www.flysnow.org/2017/09/02/go-classic-libs-negroni-two.html

Negroni Static中间件

Negroni设计的时候,内置了3个中间件,它们分别是Logger、Recovery和Static,用于日志处理,故障恢复以及静态文件处理,今天来分析下静态文件处理的源码实现。

Negroni的静态文件处理中间件,主要是为了把我们电脑(服务器)上的文件托管到Web服务上,可以通过HTTP的方式访问查看对应的电脑上的文件。如果请求的文件不存在,那么请求将会转给下一个中间件;如果文件存在,那么就会显示静态文件的内容,请求到此终止。

使用Negroni实现静态处理非常简单,我们看一个示例。

package main

import (
    "github.com/urfave/negroni"
    "net/http"
)

func main(){
    n := negroni.New()
    n.Use(negroni.NewLogger())
    n.Use(negroni.NewStatic(http.Dir("/tmp")))
    n.Run(":8080")
}

示例中,把 /tmp 目录下的文件,托管在了Web服务器上,可以通过HTTP的方式访问到。假如 /tmp 下有一个 1.txt 文件,那么我们打开浏览器,输入地址 http://127.0.0.1:8080/1.txt 即可访问对应的 1.txt 文件的内容。

示例中,我添加了一个 n.Use(negroni.NewLogger()) 中间件,用于打印访问日志,比如请求的URL等。

从示例代码中,可以看到,Negroni主要通过 negroni.NewStatic 函数,生成一个静态文件处理的中间件。

// NewStatic returns a new instance of Static
func NewStatic(directory http.FileSystem) *Static {
    return &Static{
        Dir:       directory,
        Prefix:    "",
        IndexFile: "index.html",
    }
}

该函数接受一个 http.FileSystem 类型的参数,用于指定要处理的目录。从函数的源代码可以看出,返回的其实是一个 *Static ,这个 Static 结构体有三个字段。

Negroni Static 结构体分析

type Static struct {
    // 静态服务要处理目录
    Dir http.FileSystem
    // 给这些静态文件添加的URL前缀,主要用于对处理的静态文件分类
    Prefix string
    // 索引文件,访问Dir目录的时候,显示这个索引文件的内容
    IndexFile string
}

第一个字段我们已经在示例中演示了,下面看看另外两个字段的用法,我通过示例说明,更容易理解。

Negroni Static Prefix 分析

现在我要托管 /tmp 目录下的文件,但是我又想通过 http://hostname/tmp/1.txt 这样的方式进行归类,让访问的人知道,这些文件是在 tmp/ 下的,这就可以用到 Prefix 字段了。我们把原来的示例修改下看看。

func main(){
    n := negroni.New()
    n.Use(negroni.NewLogger())
    n.Use(&negroni.Static{
        Dir:       http.Dir("/tmp"),
        Prefix:    "/tmp",
        IndexFile: "index.html",
    })


    n.Run(":8080")
}

我们不再使用 NewStatic 方法,而是直接通过 &negroni.Static{} 构建一个静态文件处理的中间件,在构建的时候,指定 Prefix 的值为 /tmp ,现在我们再访问 /tmp 下的 1.txt 文件,就只能通过 http://127.0.0.1:8080/tmp/1.txt ,而不是原来的 http://127.0.0.1:8080/1.txt ,这样我们通过给URL加前缀,达到文件分类的目的,其实就是增加一个URL path。

本文为原创文章,转载注明出处&&,欢迎扫码关注公众号 flysnow_org 或者网站 http://www.flysnow.org/ ,&&第一时间看后续精彩文章。觉得好的话,&&顺手分享到朋友圈吧,感谢支持。

以上这些是如何实现的呢?我们看下静态文件处理的源代码,结合分析。

file := r.URL.Path
    // if we have a prefix, filter requests by stripping the prefix
    if s.Prefix != "" {
        if !strings.HasPrefix(file, s.Prefix) {
            next(rw, r)
            return
        }
        file = file[len(s.Prefix):]
        if file != "" && file[0] != '/' {
            next(rw, r)
            return
        }
    }
    f, err := s.Dir.Open(file)

以上这段代码,是通过URL路径,查找对应的静态文件的核心代码。从源代码中可以看到,对 Prefix 不为空的情况进行了特殊处理,如果 Prefix 不为空,那么我们就要从URL Path中去掉这个 Prefix ,因为 Prefix 是我们自己强加入的,不属于文件路径中的部分,所以要剥离掉,这样才可以得到要处理文件的真实路径,也就是源代码中的 file 变量,最后通过 s.Dir.Open(file) 打开文件,得到其内容显示即可。

Negroni Static IndexFile 分析

最后一个字段是 IndexFile ,用于指定索引文件。对于我们使用过Apache、Nginx的都比较熟悉,我们指定了 index.html 作为索引文件后,那么我们再访问 http://127.0.0.1:8080/tmp/ 这个目录,显示的其实是 index.html 文件的内容,因为我们访问的其实是 http://127.0.0.1:8080/tmp/index.html ,这就是 IndexFile 的作用。

Negroni的静态处理器中间件源代码中,对于 IndexFile 也非常简洁,容易理解。

// try to serve index file
    if fi.IsDir() {
        file = path.Join(file, s.IndexFile)
        f, err = s.Dir.Open(file)
    }
    //省略了无关代码

这个源代码处理很简单,如果是一个目录的话,就把目录和 IndexFile 拼接成一个新的文件路径,进行二次打开。

如何在请求页面上显示文件内容

在一系列的打开、判断中,如果最后可以找到正确的文件,拿到内容,那么就可以把内容展示到浏览器的页面上了。

http.ServeContent(rw, r, file, fi.ModTime(), f)

非常简洁的一段代码,即达到了我们的目的,该函数可以把 ReadSeeker 中的内容,显示到请求的页面上。

func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) {
    sizeFunc := func() (int64, error) {
        size, err := content.Seek(0, io.SeekEnd)
        if err != nil {
            return 0, errSeeker
        }
        _, err = content.Seek(0, io.SeekStart)
        if err != nil {
            return 0, errSeeker
        }
        return size, nil
    }
    serveContent(w, req, name, modtime, sizeFunc, content)
}

相比 io.Copyhttp.ServeContent 可以自动计算响应的内容长度、可以自动识别内容的MIME类型,还可以处理If-Match,If-Unmodified-Since,If-None-Match,If-Modified-Since和If-Range的要求。

因为 * os.File 实现了 io.ReadSeeker 接口,所以我们可以直接使用文件的内容。

小结

好了,到了这里,我们已经分析完了Negroni中静态文件处理中间件的实现,看完后相信你也可以写自己的静态文件处理服务了,可以自己试试,写一个和 http.FileServer 类似功能的静态文件处理服务。

本文为原创文章,转载注明出处,欢迎扫码关注公众号 flysnow_org 或者网站 http://www.flysnow.org/ ,第一时间看后续精彩文章。觉得好的话,顺手分享到朋友圈吧,感谢支持。

veyEvev.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK