Custom 404 with Gorilla Mux and std http.FileServer
I have the following code and everything is working 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 route request like this /cannot/find
shows my own 404 template. All static files inside my directory /public/
are served properly as well.
I have a problem handling non-existent static files and showing my custom handler NotFound
for them. The request /public/cannot/find
calls the standard http.NotFoundHandler which responds
404 pages not found
How can I use 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 handler NotFound
catches all missing routes and even missing files.
source to share
The FileServer generates a 404 response. The FileServer handles all requests sent to it by the multiplexer, including requests for no files. There are several ways to serve static files with a custom 404 page:
- Write your own file handler using ServeContent . This handler can generate error responses in any way. This is not a lot of code unless you create index pages.
- Wrap the FileServer handler with another handler that intercepts the ResponseWriter passed to the FileHandler. The hook writes another body when WriteHeader (404) is called.
- Register each static resource with the multiplexer so that errors are not caught in the multiplex. This approach requires a simple ServeFile wrapper .
Here's a description 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 a hook wrapping FileServer:
router.PathPrefix("/public/").Handler(NotFoundHook{http.StripPrefix("/public/", http.FileServer(http.Dir("public")))})
One of the caveats of this simple hook is that it blocks server optimization to copy from file to socket.
source to share