How to Use Specific middleware for specific routes in a Get Subrouter in gorilla mux

Viewed 6928

I have a specific requirement with Gorilla mux routing where i want to add different Middlewares for different routes that are under one subrouter( GET subrouter in my case). Below is my code for routing:

    // create a serve mux
    sm := mux.NewRouter()

    // register handlers
    postR := sm.Methods(http.MethodPost).Subrouter()
    postR.HandleFunc("/signup", uh.Signup)
    postR.HandleFunc("/login", uh.Login)
    postR.Use(uh.MiddlewareValidateUser)

    getR := sm.Methods(http.MethodGet).Subrouter()
    getR.HandleFunc("/refresh-token", uh.RefreshToken)
    getR.HandleFunc("/user-profile", uh.GetUserProfile)

In the above router logic both my /refresh-token and /user-profile token come under getR router. Also i have two middleware functions called ValidateAccessToken and ValidateRefreshToken. I want to use the ValidateRefreshToken middleware function for "/refresh-token" route and ValidateAccessToken for all other routes under GET subrouter. I want to do it with Gorilla mux routing itself. Please suggest me the appropriate approach to accomplish the above scenario. Thanks for your time and effort.

3 Answers

The solution provided by notorious.no also works perfectly for the given requirement. Also i came across another solution which uses PathPrefix and solves the same issue and would like to get suggestions for the same.

    // create a serve mux
    sm := mux.NewRouter()

    // register handlers
    postR := sm.Methods(http.MethodPost).Subrouter()
    postR.HandleFunc("/signup", uh.Signup)
    postR.HandleFunc("/login", uh.Login)
    postR.Use(uh.MiddlewareValidateUser)

    refToken := sm.PathPrefix("/refresh-token").Subrouter()
    refToken.HandleFunc("", uh.RefreshToken)
    refToken.Use(uh.MiddlewareValidateRefreshToken)

    getR := sm.Methods(http.MethodGet).Subrouter()
    getR.HandleFunc("/greet", uh.Greet)
    getR.Use(uh.MiddlewareValidateAccessToken)

Came to this solution using the reference : https://github.com/gorilla/mux/issues/360

I had a similar use case and this is an example of how I solved it:

package main

import (
    "log"
    "net/http"
    "time"
)

import (
    "github.com/gorilla/mux"
)

// Adapter is an alias so I dont have to type so much.
type Adapter func(http.Handler) http.Handler

// Adapt takes Handler funcs and chains them to the main handler.
func Adapt(handler http.Handler, adapters ...Adapter) http.Handler {
    // The loop is reversed so the adapters/middleware gets executed in the same
    // order as provided in the array.
    for i := len(adapters); i > 0; i-- {
        handler = adapters[i-1](handler)
    }
    return handler
}

// RefreshToken is the main handler.
func RefreshToken(res http.ResponseWriter, req *http.Request) {
    res.Write([]byte("hello world"))
}

// ValidateRefreshToken is the middleware.
func ValidateRefreshToken(hKey string) Adapter {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
            // Check if a header key exists and has a value
            if value := req.Header.Get(hKey); value == "" {
                res.WriteHeader(http.StatusForbidden)
                res.Write([]byte("invalid request token"))
                return
            }

            // Serve the next handler
            next.ServeHTTP(res, req)
        })
    }
}

// MethodLogger logs the method of the request.
func MethodLogger() Adapter {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
            log.Printf("method=%s uri=%s\n", req.Method, req.RequestURI)
            next.ServeHTTP(res, req)
        })
    }
}

func main() {
    sm := mux.NewRouter()
    getR := sm.Methods(http.MethodGet).Subrouter()
    getR.HandleFunc("/refresh-token", Adapt(
        http.HandlerFunc(RefreshToken),
        MethodLogger(),
        ValidateRefreshToken("Vikee-Request-Token"),
    ).ServeHTTP)

    srv := &http.Server{
        Handler:      sm,
        Addr:         "localhost:8888",
        WriteTimeout: 30 * time.Second,
        ReadTimeout:  30 * time.Second,
    }
    log.Fatalln(srv.ListenAndServe())
}

The Adapt function lets you chain multiple handlers together. I've demonstrated 2 middleware funcs (ValidateRefreshToken and MethodLogger). The middleware are basically closures. You can use this method with any framework that accepts http.Handler such as mux and chi.

And another

Usage example

mux := mux.NewRouter()
healthRoute := mux.Path("/health").Handler(healthHandler)
mux.PathPrefix("/").Handler(defaultHandler)

// Run logging middleware except on the health route because health is spammy
mux.Use(libHttp.MiddlewareExcept(libHttp.LoggingMiddlewareWith404(logger), healthRoute))

And impl

// MiddlewareExcept returns a new middleware that calls the provided middleware except on the provided routes
func MiddlewareExcept(middleware mux.MiddlewareFunc, routes ...*mux.Route) mux.MiddlewareFunc {
    routeMatch := mux.RouteMatch{}
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
            for _, route := range routes {
                if route.Match(r, &routeMatch) {
                    if next != nil {
                        next.ServeHTTP(rw, r)
                    }
                } else {
                    middleware(next).ServeHTTP(rw, r)
                }
            }
        })
    }
}
Related