自定义404与Gorilla Mux和std http.FileServer

时间:2014-10-01 12:58:49

标签: go

我有以下代码,一切正常。

var view404 = template.Must(template.ParseFiles("views/404.html"))

func NotFound(w http.ResponseWriter, r *http.Request) {
  w.WriteHeader(404)
  err := view404.Execute(w, nil)
  check(err)
}

func main() {
  router := mux.NewRouter()
  router.StrictSlash(true)
  router.NotFoundHandler = http.HandlerFunc(NotFound)
  router.Handle("/", IndexHandler).Methods("GET")
  router.PathPrefix("/public/").Handler(http.StripPrefix("/public/", http.FileServer(http.Dir("public"))))
  http.Handle("/", router)
  http.ListenAndServe(":8000", nil)
}

/cannot/find等路线的请求显示了我的自定义404模板。我的/public/目录中的所有静态文件也都已正确提供。

我在处理不存在的静态文件并为它们显示我的自定义NotFound处理程序时遇到问题。对/public/cannot/find的请求调用标准的http.NotFoundHandler,其回复

  

未找到404页

如何为普通路由和静态文件使用相同的自定义NotFoundHandler?


更新

我最后通过将FileHandler包装为@Dewy Broto建议来实现我自己的http.ServeFile

type FileHandler struct {
  Path string
}

func (f FileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  prefix := "public"
  http.ServeFile(w, r, path.Join(prefix, f.Path))
}

// ...

router.Handle("/css/styles.css", FileHandler{"/css/styles.css"}).Methods("GET")

现在我的NotFound处理程序捕获所有丢失的路由,甚至丢失文件。

1 个答案:

答案 0 :(得分:6)

FileServer正在生成404响应。 FileServer处理多路复用器传递给它的所有请求,包括丢失文件的请求。有几种方法可以使用自定义404页面提供静态文件:

  • 使用ServeContent编写自己的文件处理程序。此处理程序可以以您想要的任何方式生成错误响应。如果您不生成索引页面,那么代码不是很多。
  • 用另一个挂钩传递给FileHandler的FileServer的处理程序包装ResponseWriter处理程序。当调用WriteHeader(404)时,钩子会写一个不同的主体。
  • 使用多路复用器注册每个静态资源,以便多路复用器中的catchall处理未找到的错误。这种方法需要一个简单的包装ServeFile

这是第二种方法中描述的包装的草图:

type hookedResponseWriter struct {
    http.ResponseWriter
    ignore bool
}

func (hrw *hookedResponseWriter) WriteHeader(status int) {
    hrw.ResponseWriter.WriteHeader(status)
    if status == 404 {
        hrw.ignore = true
        // Write custom error here to hrw.ResponseWriter
    }
}

func (hrw *hookedResponseWriter) Write(p []byte) (int, error) {
    if hrw.ignore {
        return len(p), nil
    }
    return hrw.ResponseWriter.Write(p)
}

type NotFoundHook struct {
    h http.Handler
}

func (nfh NotFoundHook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    nfh.h.ServeHTTP(&hookedResponseWriter{ResponseWriter: w}, r)
}

通过包装FileServer:

来使用钩子
 router.PathPrefix("/public/").Handler(NotFoundHook{http.StripPrefix("/public/", http.FileServer(http.Dir("public")))})

这个简单钩子的一个警告是它阻止了服务器中的优化,以便从文件复制到套接字。