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.

+3


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

+5


source







All Articles