【问题标题】:How to custom handle a file not being found when using go static file server?使用 go 静态文件服务器时如何自定义处理未找到的文件?
【发布时间】:2017-11-14 23:52:29
【问题描述】:

所以我使用 go 服务器来提供单页 Web 应用程序。

这适用于为根路由上的所有资产提供服务。所有的 CSS 和 HTML 都正确提供。

fs := http.FileServer(http.Dir("build"))
http.Handle("/", fs)

所以当 URL 为http://myserverurl/index.htmlhttp://myserverurl/styles.css 时,它会提供相应的文件。

但是对于像 http://myserverurl/myCustompage 这样的 URL,如果 myCustompage 不是构建文件夹中的文件,它会抛出 404

如何使文件不存在的所有路由都服务于index.html

它是一个单页网络应用程序,一旦提供了 html 和 js,它将呈现适当的屏幕。但它需要index.html 服务于没有文件的路由。

如何做到这一点?

【问题讨论】:

    标签: go webserver go-server


    【解决方案1】:

    http.FileServer() 返回的处理程序不支持自定义,不支持提供自定义 404 页面或操作。

    我们可以做的是包装http.FileServer()返回的处理程序,在我们的处理程序中我们当然可以做任何我们想做的事情。在我们的包装处理程序中,我们将调用文件服务器处理程序,如果这会发送一个 404 未找到响应,我们将不会将其发送给客户端,而是将其替换为重定向响应。

    为了实现这一点,在我们的包装器中,我们创建了一个包装器http.ResponseWriter,我们将把它传递给http.FileServer()返回的处理程序,在这个包装器响应编写器中,我们可以检查状态码,如果它是404,我们可能会将响应发送给客户端,而是将重定向发送到/index.html

    这是这个包装器http.ResponseWriter 的示例:

    type NotFoundRedirectRespWr struct {
        http.ResponseWriter // We embed http.ResponseWriter
        status              int
    }
    
    func (w *NotFoundRedirectRespWr) WriteHeader(status int) {
        w.status = status // Store the status for our own use
        if status != http.StatusNotFound {
            w.ResponseWriter.WriteHeader(status)
        }
    }
    
    func (w *NotFoundRedirectRespWr) Write(p []byte) (int, error) {
        if w.status != http.StatusNotFound {
            return w.ResponseWriter.Write(p)
        }
        return len(p), nil // Lie that we successfully written it
    }
    

    包装 http.FileServer() 返回的处理程序可能如下所示:

    func wrapHandler(h http.Handler) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            nfrw := &NotFoundRedirectRespWr{ResponseWriter: w}
            h.ServeHTTP(nfrw, r)
            if nfrw.status == 404 {
                log.Printf("Redirecting %s to index.html.", r.RequestURI)
                http.Redirect(w, r, "/index.html", http.StatusFound)
            }
        }
    }
    

    请注意,我使用http.StatusFound 重定向状态代码而不是http.StatusMovedPermanently,因为后者可能会被浏览器缓存,因此如果稍后创建具有该名称的文件,浏览器不会请求它,而是显示index.html马上。

    现在使用 main() 函数:

    func main() {
        fs := wrapHandler(http.FileServer(http.Dir(".")))
        http.HandleFunc("/", fs)
        panic(http.ListenAndServe(":8080", nil))
    }
    

    尝试查询不存在的文件,我们将在日志中看到:

    2017/11/14 14:10:21 Redirecting /a.txt3 to /index.html.
    2017/11/14 14:10:21 Redirecting /favicon.ico to /index.html.
    

    请注意,我们的自定义处理程序(行为良好)还将请求重定向到 /favico.icoindex.html,因为我的文件系统中没有 favico.ico 文件。如果您也没有它,您可能希望将其添加为例外。

    Go Playground 上提供了完整示例。你不能在那里运行它,把它保存到你本地的 Go 工作区并在本地运行它。

    同时检查这个相关问题:Log 404 on http.FileServer

    【讨论】:

    • 这很有趣。我想出了另一种使用http.ServeFile 的方法。我首先使用os.Stat 检查文件是否存在,然后提供相应的文件或 index.html。你能告诉我哪种方法更好吗?为什么?
    • @JeffPChacko http.FileServer() 返回的处理程序也在后台使用http.ServeFile()(或其部分实现),但还提供其他好处,如目录列表和安全性,以确保不提供文件在给定文件夹之外。如果不使用http.FileServer(),则必须注意这些(例如,您必须规范化/解析包含.. 的棘手请求路径)。
    猜你喜欢
    • 2020-05-19
    • 2016-04-20
    • 2014-12-20
    • 2019-08-29
    • 2022-12-18
    • 2015-02-11
    • 2022-01-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多