Conditionally Applying Middlware in Go

I’ve been prototyping some stuff with Huma, and, while it lets your bring your own router, that doesn’t mean that all features of the router are available to you.

My problem was that I wanted API routes to be protected by an authentication middleware, but the actual open api and schema URLs to be open. Using something like chi’s groups would make this easy, but the Huma API instance would not be associated with the group the right way. Enter the need to conditionally apply middleware.

import (
	"net/http"
	"slices"
)

type Middleware func(http.Handler) http.Handler

type Matcher func(r *http.Request) bool

// / Only apply the fillter passed in if the matcher accepts the request.
func FilterMiddleware(matcher Matcher, m Middleware, others ...Middleware) Middleware {
	middleware := append([]Middleware{m}, others...)

	slices.Reverse(middleware)

	return func(next http.Handler) http.Handler {
		withMiddleware := next
		for _, m := range middleware {
			withMiddleware = m(withMiddleware)
		}

		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if !matcher(r) {
				next.ServeHTTP(w, r)
				return
			}

			withMiddleware.ServeHTTP(w, r)
		})
	}
}

If the matcher matches the request middleware is applied, if not, no middleware applied.

The key bits here are:

middleware := append([]Middleware{m}, others...)

slices.Reverse(middleware)

This ensure that the middleware is applied in the order it’s passed in, eg:

filtered = FilterMiddleware(matcherHere, one, two, three)
// becomes

three(two(one(handler)))

The other interesting bit is:

withMiddleware := next
for _, m := range middleware {
	withMiddleware = m(withMiddleware)
}

This ensure we keep a copy of next around (presumably the real HTTP handler) and next wrapped with middleware (withMiddleware). We can then conditionally apply it based on the matcher function.

My actual “don’t apply auth middlware to open API endpoints,” looked somethign like this:

const OpenAPIPath = "/openapi"

func isNotOpenAPIRequest(r *http.Request) bool {
	return !strings.HasPrefix(r.URL.Path, OpenAPIPath)
}

// elsewhere...
router := chi.NewMux()
router.Use(FilterMiddleware(
	isNotOpenAPIRequest,
	authenticationMiddleware(authConfig),
))