goroutesgorilla

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


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.


Solution

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