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.
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:
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.