Custom 404 with Gorilla Mux and std http.FileServer

huangapple go评论83阅读模式
英文:

Custom 404 with Gorilla Mux and std http.FileServer

问题

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

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 page not found

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


更新

我最终按照@Dewy Broto的建议,通过包装http.ServeFile来实现了自己的FileHandler

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处理程序可以捕获所有缺失的路由,甚至是缺失的文件。

英文:

I have the following code and everything works fine.

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)
}

A request to a route like /cannot/find shows my custom 404 template. All static files inside my /public/ directory are also properly served.

I have a problem handling non-existent static files and showing my custom NotFound handler for them. A request to /public/cannot/find calls the standard http.NotFoundHandler which replies with

> 404 page not found

How can I have the same custom NotFoundHandler for normal routes and static files?


Update

I ended up implementing my own FileHandler by wrapping http.ServeFile as @Dewy Broto suggested.

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")

Now my NotFound handler catches all missing routes and even missing files.

答案1

得分: 10

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

  • 使用ServeContent编写自己的文件处理程序。该处理程序可以以任何你想要的方式生成错误响应。如果不生成索引页面,代码量不多。
  • 使用另一个处理程序包装FileServer处理程序,该处理程序钩住传递给FileHandler的ResponseWriter。当调用WriteHeader(404)时,该钩子会写入不同的响应体。
  • 将每个静态资源在mux中注册,以便由mux中的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
        // 在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")))})

这个简单钩子的一个注意事项是它阻止了服务器在从文件复制到套接字时的优化。

英文:

The FileServer is generating the 404 response. The FileServer handles all requests passed to it by the mux including requests for missing files. There are a few ways to to serve static files with a custom 404 page:

  • Write your own file handler using ServeContent. This handler can generate error responses in whatever way you want. It's not a lot of code if you don't generate index pages.
  • Wrap the FileServer handler with another handler that hooks the ResponseWriter passed to the FileHandler. The hook writes a different body when WriteHeader(404) is called.
  • Register each static resource with the mux so that not found errors are handled by the catchall in the mux. This approach requires a simple wrapper around ServeFile.

Here's a sketch of the wrapper described in the second approach:

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)
}

Use the hook by wrapping the FileServer:

 router.PathPrefix("/public/").Handler(NotFoundHook{http.StripPrefix("/public/", http.FileServer(http.Dir("public")))})

One caveat of this simple hook is that it blocks an optimization in the server for copying from a file to a socket.

huangapple
  • 本文由 发表于 2014年10月1日 20:58:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/26141953.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定