Golang。用什么? http.ServeFile(..)或http.FileServer(..)?

时间:2015-03-01 12:38:51

标签: go server

我有点困惑。大多数示例都显示了两者的用法:http.ServeFile(..)http.FileServer(..),但似乎它们具有非常接近的功能。此外,我没有找到有关如何设置自定义NotFound处理程序的信息。

// This works and strip "/static/" fragment from path
fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))

// This works too, but "/static2/" fragment remains and need to be striped manually
http.HandleFunc("/static2/", func(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, r.URL.Path[1:])
})

http.ListenAndServe(":8080", nil)

我试过阅读源代码,它们都使用serveFile(ResponseWriter, *Request, FileSystem, string, bool)底层函数。但http.FileServer使用自己的fileHandler方法返回ServeHTTP()并在提供文件之前进行一些准备工作(例如path.Clean())。

那为什么需要这种分离呢?哪种方法更好用?如何设置自定义NotFound处理程序,例如找不到请求的文件?

2 个答案:

答案 0 :(得分:58)

主要区别在于http.FileServer有效地将HTTP前缀与文件系统的1:1映射。简单来说,它提供了整个目录路径。和它的所有孩子。

假设您有一个名为/home/bob/static的目录,并且您已进行此设置:

fs := http.FileServer(http.Dir("/home/bob/static"))
http.Handle("/static/", http.StripPrefix("/static", fs))

您的服务器会接受例如/static/foo/bar并提供/home/bob/static/foo/bar(或404)

的任何内容

相比之下,ServeFile是一个较低级别的帮助程序,可以用来实现类似于FileServer的东西,或者实现你自己的路径,以及任何数量的东西。它只需要获取指定的本地文件并通过HTTP连接发送它。它本身不会提供整个目录前缀(除非你编写了一个类似于FileServer的查找处理程序)

注意天真地提供文件系统是一个潜在危险的事情(有可能突破root树的方法)因此我建议除非你真的知道什么您正在使用http.FileServerhttp.Dir,因为它们包括检查以确保人们无法突破FS,ServeFile没有。

附录 你的第二个问题,遗憾的是,你如何做一个自定义的NotFound处理程序并不容易回答。因为你注意到这是从内部函数serveFile调用的,所以没有超级容易的地方可以进入。可能会有一些偷偷摸摸的事情,比如用自己的ResponseWriter拦截响应来拦截404响应代码,但我会把这个练习留给你。

答案 1 :(得分:1)

这里是一个处理程序,如果找不到文件,该处理程序会将重定向发送到“ /”。如建议的here(这是在golang服务中提供的)那样,当为Angular应用程序添加后备时,这非常方便。

注意:该代码尚未投入生产。仅用于说明(最多:-)

    package main

    import "net/http"

    type (
        // FallbackResponseWriter wraps an http.Requesthandler and surpresses
        // a 404 status code. In such case a given local file will be served.
        FallbackResponseWriter struct {
            WrappedResponseWriter http.ResponseWriter
            FileNotFound          bool
        }
    )

    // Header returns the header of the wrapped response writer
    func (frw *FallbackResponseWriter) Header() http.Header {
        return frw.WrappedResponseWriter.Header()
    }

    // Write sends bytes to wrapped response writer, in case of FileNotFound
    // It surpresses further writes (concealing the fact though)
    func (frw *FallbackResponseWriter) Write(b []byte) (int, error) {
        if frw.FileNotFound {
            return len(b), nil
        }
        return frw.WrappedResponseWriter.Write(b)
    }

    // WriteHeader sends statusCode to wrapped response writer
    func (frw *FallbackResponseWriter) WriteHeader(statusCode int) {
        Log.Printf("INFO: WriteHeader called with code %d\n", statusCode)
        if statusCode == http.StatusNotFound {
            Log.Printf("INFO: Setting FileNotFound flag\n")
            frw.FileNotFound = true
            return
        }
        frw.WrappedResponseWriter.WriteHeader(statusCode)
    }

    // AddFallbackHandler wraps the handler func in another handler func covering authentication
    func AddFallbackHandler(handler http.HandlerFunc, filename string) http.HandlerFunc {
        Log.Printf("INFO: Creating fallback handler")
        return func(w http.ResponseWriter, r *http.Request) {
            Log.Printf("INFO: Wrapping response writer in fallback response writer")
            frw := FallbackResponseWriter{
                WrappedResponseWriter: w,
                FileNotFound:          false,
            }
            handler(&frw, r)
            if frw.FileNotFound {
                Log.Printf("INFO: Serving fallback")
                http.Redirect(w, r, "/", http.StatusSeeOther)
            }
        }
    }

可以在此示例中添加它(使用goji作为多路复用器):

    mux.Handle(pat.Get("/*"),
        AddFallbackHandler(http.FileServer(http.Dir("./html")).ServeHTTP, "/"))