I have a fairly quick-and-dirty error handler in my Go web app that raises a HTTP error, logs the important parts of the response and serves an error template. I'd like to remove the repetition where I'm writing something like this a few too many times in a handler:
err := doSomething()
if err != nil {
serverError(w, r, err, code)
}
I've had a good read of the Error Handling and Go article which covers defining a custom HTTP handler type that returns a error type/struct like this (or even returning int, err instead):
type appHandler func(http.ResponseWriter, *http.Request) *appError
type appError struct {
code int
Err error
}
// Ensures appHandler satisfies the http.Handler interface
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
switch err.Code {
case http.StatusNotFound:
http.NotFound(w, r)
case http.StatusInternalServerError:
http.Error(w, "message", http.StatusInternalServerError)
default:
http.Error(w, "message", err.Code)
}
}
}
But I'm not sure how to retain my existing middleware functionality/wrapper that allows me to chain middleware like this: r.HandleFunc("/route", use(myHandler, middleware1, middleware2))
where use
and my middleware look like this:
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
for _, m := range middleware {
h = m(h)
}
return h
}
func AntiCSRF(h http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// do something
// h.ServeHTTP(w,r)
}
}
From what I can figure, it'd be something like the below (which doesn't work). I'm getting an error saying cannot use m(h) (type http.Handler) as type appHandler in assignment: need type assertion
. How do I resolve this whilst still keeping the middleware itself "as is"?
You can find a (simplified) playground example here: http://play.golang.org/p/Cmmo-wK2Af
r.Handle("/route", use(myHandler, middleware.NoCache)) // Contrived example!
func use(h myHandlerType?, middleware ...func(http.Handler) http.Handler) http.Handler {
for _, m := range middleware {
h = m(h)
}
return h
}
func myHandler(w http.ResponseWriter, r *http.Request) *appError {
// Extremely contrived example
name := "Matt"
_, err := fmt.Fprintf(w, "Hi %s", name)
if err != nil {
return &appError{500, err}
}
return nil
}
func contrivedMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age=0, private, must-revalidate")
w.Header().Set("X-Accel-Expires", "0")
h.ServeHTTP(w, r)
})
}
What am I missing and is there a better way to do this?
I've managed to solve this thanks to the help of 'cronos' on #go-nuts.
The solution allows me to use a custom handler type, chain middleware and avoid the repetition of having to wrap handlers (i.e. appHandler(myHandler)), middleware...):
type appHandler func(http.ResponseWriter, *http.Request) *appError
type appError struct {
Code int
Error error
}
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil {
switch e.Code {
case http.StatusNotFound:
notFound(w, r)
case http.StatusInternalServerError:
serverError(w, r, e.Error, e.Code)
default:
serverError(w, r, e.Error, e.Code)
}
}
}
func use(h appHandler, middleware ...func(http.Handler) http.Handler) http.Handler {
var res http.Handler = h
for _, m := range middleware {
res = m(res)
}
return res
}
func someMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age=0, private, must-revalidate")
w.Header().Set("X-Accel-Expires", "0")
h.ServeHTTP(w, r)
})
}
func myHandler(w http.ResponseWriter, r *http.Request) *appError {
err := doSomething()
if err != nil {
return &appError{500, err}
}
// render your template, etc.
return nil
}
With routes looking like this: r.Handle("/route", use(myHandler, someMiddleware))
You can obviously modify appHandler
to return whatever you like, add additional fields to appError
and so on. Your middleware is also able to wrap your router if you want to apply it to all routes - i.e. http.Handle("/", someMiddleware(r))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With