Search code examples
gohandlergorillamux

How to apply the same handler for all routes with gorilla mmux


I'm using a valuable rootHandler to deal with error generated. I'd like to know how I could use this handler for every endpoint my api may serve.

r := mux.NewRouter()

r.Handle("/api/endpoint0", rootHandler(function0)).Methods("POST")
r.HandleFunc("/api/endpoint1", function1).Methods("POST")
r.HandleFunc("/api/endpoint2", function2).Methods("POST")

    s := &http.Server{
        Handler:      r,
        Addr:         "127.0.0.1:8080",
        WriteTimeout: 15 * time.Second,
        ReadTimeout:  15 * time.Second,
    }

My error handler is working as follow :

type rootHandler func(http.ResponseWriter, *http.Request) error

func (fn rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    err := fn(w, r) // Call handler function
    if err == nil {
        return
    }
    //log error or w/e
}

func NewHTTPError(err error, status int, detail string) error {
    return &HTTPError{
        Cause:  err,
        Detail: detail,
        Status: status,
    }
}

And one of the exemple function I was using :

func function0(w http.ResponseWriter, r *http.Request) (err error) {
    if err = notLogged(); err != nil {
        return NewHTTPError(err, 400, "not authorized")
    }

}

Do I have to wrap every route functions with rootHanlder(functionName) or is there a way to apply it to every routes ?


Solution

  • Depending on the exact structure of your rootHandler, you might be able to set it as a middleware:

    func GetRootHandlerMW(authUser SomeType) func(http.Handler) http.Handler {
      return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Do what needs to be done?
            next.ServeHTTP(w, r)
        })
      }
    }
    
    ...
    r.Use(GetRootHandlerMW(authUser))
    

    A simple logging middleware looks like this:

    func LogMW(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            logRequest(...)
            next.ServeHTTP(w, r)
        })
    }
    
    ...
    r.Use(LogMW)
    

    From your added code, it looks like you want to change the HTTP handler function to return error. That changes the signature of the function and you cannot use it as a handler. You can either:

    • Use context to pass errors back to the middleware. That is, set some error value in the request context in your handler so the logger middleware can log it after the handler returns, or
    • Wrap every handler with the error logger like you are apparently doing now.

    Adding values to a context will create a new context, so if you use context to pass values back from the handler, you have to set a placeholder first. Something like this:

    type RequestError struct {
       Err error
    }
    
    type requestErrorKeyType int
    const requestErrorKey requestErrorKeyType=iota
    
    func SetRequestError(req *http.Request, err error) {
       if r:=req.Context().Value(requestErrorKey); r!=nil {
           r.(*RequestError).Err=err
       }
    }
    
    // In the handler...
    requestError:=RequestError{}
    newReq:=request.WithContext(request.Context().WithValue(requestErrorKey,&requestError))
    next.ServeHTTP(w,newReq)
    if requestError.Err!=nil {
      ...
    }