Search code examples
godesign-patternsmiddlewaremux

Reducing boilerplate in a mux middlware and custom context implementation


I'm fairly new to Go and trying to create a middleware implementation in a gateway library that I'm building on top of mux. So far, I've come up up with this:

type (
    MyContext struct {
        // ...
    }

    APIGateway struct {
        mux *mux.Router
        ctx *MyContext
    }
)

type MiddlewareFunc func(*MyContext) func(http.Handler) http.Handler

func (a APIGateway) Use(mw MiddlewareFunc) {
    a.mux.Use(mw(ctx))
}

This works, but then I have to deal with a lot of boilerplate code. The simplest middleware implemented on this pattern has to have a minimum of two return statements and lot of incline function declaration.

func someMiddleware(ctx *MyContext) func(http.Handler) http.Handler {
    return func(handler http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Application Logic here
            handler.ServeHTTP(w, r)
        })
    }
}

This quickly becomes a bigger problem when the middlware has to take some extra parameters since now it has to deal with three return statements:

func LogginMiddleware(c LoggerConfig) MiddlewareFunc {
    return func(ctx *MyContext) func(http.Handler) http.Handler {
        return func(handler http.Handler) http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                // ...
                handler.ServeHTTP(w, r)
                // ...
            })
        }
    }
}

Is there a way I can possibly reduce the involvement of boilerplate code?


Solution

  • You have this problem because the middleware functions are implemented as closures. Note that middleware function is defined as:

    type MiddlewareFunc func(http.Handler) http.Handler
    

    where http.Handler is an interface. So you can write something like this for each middleware:

    type MyMiddleware struct {
       // Fields your middleware func needs
       Next http.Handler
    }
    
    // This is the middleware func
    func MW(next http.Handler) http.Handler {
       m:=MyMiddleware{Next:next}
       // Initialize other fields
       return m
    }
    
    // Implement http.Handler.
    func (m MyMiddleware) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
       // Do  middleware stuff
       m.Next.ServeHTTP(w,req)
    }