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

3

9

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.

Xochitlxp answered 10/11, 2020 at 12:28 Comment(0)
C
8

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.

Canonicity answered 10/11, 2020 at 13:45 Comment(0)
X
15

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

Xochitlxp answered 11/11, 2020 at 5:12 Comment(0)
C
8

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.

Canonicity answered 10/11, 2020 at 13:45 Comment(0)
H
0

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 implementation

    // 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)
                    }
                }
            })
        }
    }

Hilburn answered 28/4, 2021 at 1:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.