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) {
  err := view404.Execute(w, nil)

func main() {
  router := mux.NewRouter()
  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?


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

1 answer

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 {
    ignore bool

func (hrw *hookedResponseWriter) WriteHeader(status int) {
    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.



All Articles