From 1be26a465b6447d62f6e6b9092dcc7df9c19f024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20de=20Jong?= <5366568-cornedejong@users.noreply.gitlab.com> Date: Wed, 15 May 2024 18:41:30 +0200 Subject: [PATCH 1/4] Added middleware support for individual routes --- middleware.go | 13 ++++++++++ middleware_test.go | 61 +++++++++++++++++++++++++++++++++++++++++++--- route.go | 19 ++++++++++++++- 3 files changed, 89 insertions(+), 4 deletions(-) diff --git a/middleware.go b/middleware.go index cb51c565..e1900d0f 100644 --- a/middleware.go +++ b/middleware.go @@ -32,6 +32,19 @@ func (r *Router) useInterface(mw middleware) { r.middlewares = append(r.middlewares, mw) } +// RouteMiddleware ------------------------------------------------------------- + +func (r *Route) Use(mwf ...MiddlewareFunc) { + for _, fn := range mwf { + r.middlewares = append(r.middlewares, fn) + } +} + +// useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. +func (r *Route) useInterface(mw middleware) { + r.middlewares = append(r.middlewares, mw) +} + // CORSMethodMiddleware automatically sets the Access-Control-Allow-Methods response header // on requests for routes that have an OPTIONS method matcher to all the method matchers on // the route. Routes that do not explicitly handle OPTIONS requests will not be processed diff --git a/middleware_test.go b/middleware_test.go index 4963b66f..8835b56c 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -2,6 +2,7 @@ package mux import ( "bytes" + "fmt" "net/http" "testing" ) @@ -42,6 +43,17 @@ func TestMiddlewareAdd(t *testing.T) { if len(router.middlewares) != 3 { t.Fatal("Middleware function was not added correctly") } + + route := router.HandleFunc("/route", dummyHandler) + route.useInterface(mw) + if len(route.middlewares) != 1 { + t.Fatal("Route middleware function was not added correctly") + } + + route.Use(banalMw) + if len(route.middlewares) != 2 { + t.Fatal("Route middleware function was not added correctly") + } } func TestMiddleware(t *testing.T) { @@ -85,6 +97,24 @@ func TestMiddleware(t *testing.T) { t.Fatalf("Expected %d calls, but got only %d", 3, mw.timesCalled) } }) + + t.Run("regular call using route middleware func", func(t *testing.T) { + router.HandleFunc("/route", dummyHandler).Use(mw.Middleware) + req = newRequest("GET", "/route") + router.ServeHTTP(rw, req) + if mw.timesCalled != 6 { + t.Fatalf("Expected %d calls, but got only %d", 6, mw.timesCalled) + } + }) + + t.Run("regular call using route middleware interface", func(t *testing.T) { + router.HandleFunc("/route", dummyHandler).useInterface(mw) + req = newRequest("GET", "/route") + router.ServeHTTP(rw, req) + if mw.timesCalled != 9 { + t.Fatalf("Expected %d calls, but got only %d", 9, mw.timesCalled) + } + }) } func TestMiddlewareSubrouter(t *testing.T) { @@ -156,13 +186,15 @@ func TestMiddlewareExecution(t *testing.T) { mwStr := []byte("Middleware\n") handlerStr := []byte("Logic\n") - router := NewRouter() - router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { + handlerFunc := func(w http.ResponseWriter, e *http.Request) { _, err := w.Write(handlerStr) if err != nil { t.Fatalf("Failed writing HTTP response: %v", err) } - }) + } + + router := NewRouter() + router.HandleFunc("/", handlerFunc) t.Run("responds normally without middleware", func(t *testing.T) { rw := NewRecorder() @@ -194,6 +226,29 @@ func TestMiddlewareExecution(t *testing.T) { t.Fatal("Middleware + handler response is not what it should be") } }) + + t.Run("responds with handler, middleware and route middleware response", func(t *testing.T) { + routeMwStr := []byte("Route Middleware\n") + rw := NewRecorder() + req := newRequest("GET", "/route") + + router.HandleFunc("/route", handlerFunc).Use(func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write(routeMwStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } + h.ServeHTTP(w, r) + }) + }) + + router.ServeHTTP(rw, req) + expectedString := append(append(mwStr, routeMwStr...), handlerStr...) + if !bytes.Equal(rw.Body.Bytes(), expectedString) { + fmt.Println(rw.Body.String()) + t.Fatal("Middleware + handler response is not what it should be") + } + }) } func TestMiddlewareNotFound(t *testing.T) { diff --git a/route.go b/route.go index b6582dae..6525f47a 100644 --- a/route.go +++ b/route.go @@ -27,6 +27,9 @@ type Route struct { // "global" reference to all named routes namedRoutes map[string]*Route + // route specific middleware + middlewares []middleware + // config possibly passed in from `Router` routeConf } @@ -99,7 +102,8 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { match.Route = r } if match.Handler == nil { - match.Handler = r.handler + // for the matched handler, wrap it in the assigned middleware + match.Handler = r.WrapHandlerInMiddleware(r.handler) } // Set variables. @@ -142,6 +146,19 @@ func (r *Route) GetHandler() http.Handler { return r.handler } +// WrapHandlerInMiddleware wraps the supplied handler in the middleware +// that were assigned to this route. If no middleware specified +// the handler is returned +func (r *Route) WrapHandlerInMiddleware(handler http.Handler) http.Handler { + if len(r.middlewares) > 0 { + for i := len(r.middlewares) - 1; i >= 0; i-- { + handler = r.middlewares[i].Middleware(handler) + } + } + + return handler +} + // Name ----------------------------------------------------------------------- // Name sets the name for the route, used to build URLs. From 883a014499e5911fcad5eccc708066bd94fd86ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20de=20Jong?= <5366568-cornedejong@users.noreply.gitlab.com> Date: Wed, 15 May 2024 18:48:06 +0200 Subject: [PATCH 2/4] Added route return to route.use method --- middleware.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/middleware.go b/middleware.go index e1900d0f..d63995ce 100644 --- a/middleware.go +++ b/middleware.go @@ -34,10 +34,12 @@ func (r *Router) useInterface(mw middleware) { // RouteMiddleware ------------------------------------------------------------- -func (r *Route) Use(mwf ...MiddlewareFunc) { +func (r *Route) Use(mwf ...MiddlewareFunc) *Route { for _, fn := range mwf { r.middlewares = append(r.middlewares, fn) } + + return r } // useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. From f88dac8d12d7461e80be7d51caccb1b1b301f802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20de=20Jong?= <5366568-cornedejong@users.noreply.gitlab.com> Date: Wed, 15 May 2024 19:03:14 +0200 Subject: [PATCH 3/4] Updated comments and some consistency fixes --- middleware.go | 3 ++- route.go | 11 +++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/middleware.go b/middleware.go index d63995ce..a79815ca 100644 --- a/middleware.go +++ b/middleware.go @@ -34,6 +34,7 @@ func (r *Router) useInterface(mw middleware) { // RouteMiddleware ------------------------------------------------------------- +// Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Route. Route middleware are executed after the Router middleware but before the Route handler. func (r *Route) Use(mwf ...MiddlewareFunc) *Route { for _, fn := range mwf { r.middlewares = append(r.middlewares, fn) @@ -42,7 +43,7 @@ func (r *Route) Use(mwf ...MiddlewareFunc) *Route { return r } -// useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. +// useInterface appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Route. Route middleware are executed after the Router middleware but before the Route handler. func (r *Route) useInterface(mw middleware) { r.middlewares = append(r.middlewares, mw) } diff --git a/route.go b/route.go index 6525f47a..47eb0b34 100644 --- a/route.go +++ b/route.go @@ -102,8 +102,8 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { match.Route = r } if match.Handler == nil { - // for the matched handler, wrap it in the assigned middleware - match.Handler = r.WrapHandlerInMiddleware(r.handler) + // for the matched handler, wrap it in the assigned middlewares + match.Handler = r.WrapHandlerInMiddlewares(r.handler) } // Set variables. @@ -146,10 +146,9 @@ func (r *Route) GetHandler() http.Handler { return r.handler } -// WrapHandlerInMiddleware wraps the supplied handler in the middleware -// that were assigned to this route. If no middleware specified -// the handler is returned -func (r *Route) WrapHandlerInMiddleware(handler http.Handler) http.Handler { +// WrapHandlerInMiddleware wraps the route handler in the assigned middlewares. +// If no middlewares are specified, just the handler is returned. +func (r *Route) WrapHandlerInMiddlewares(handler http.Handler) http.Handler { if len(r.middlewares) > 0 { for i := len(r.middlewares) - 1; i >= 0; i-- { handler = r.middlewares[i].Middleware(handler) From f041d4281c623e6c7ae278cbf1e3437dbe229dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20de=20Jong?= <5366568-cornedejong@users.noreply.gitlab.com> Date: Thu, 16 May 2024 12:47:53 +0200 Subject: [PATCH 4/4] wrapper to getter --- route.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/route.go b/route.go index 47eb0b34..3480419a 100644 --- a/route.go +++ b/route.go @@ -102,8 +102,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { match.Route = r } if match.Handler == nil { - // for the matched handler, wrap it in the assigned middlewares - match.Handler = r.WrapHandlerInMiddlewares(r.handler) + match.Handler = r.GetHandlerWithMiddlewares() } // Set variables. @@ -146,10 +145,12 @@ func (r *Route) GetHandler() http.Handler { return r.handler } -// WrapHandlerInMiddleware wraps the route handler in the assigned middlewares. -// If no middlewares are specified, just the handler is returned. -func (r *Route) WrapHandlerInMiddlewares(handler http.Handler) http.Handler { - if len(r.middlewares) > 0 { +// GetHandlerWithMiddleware returns the route handler wrapped in the assigned middlewares. +// If no middlewares are specified, just the handler, if any, is returned. +func (r *Route) GetHandlerWithMiddlewares() http.Handler { + handler := r.handler + + if handler != nil && len(r.middlewares) > 0 { for i := len(r.middlewares) - 1; i >= 0; i-- { handler = r.middlewares[i].Middleware(handler) }