From 05c10e3716465f406a6dd4fcc692039a7444c819 Mon Sep 17 00:00:00 2001 From: jriddle Date: Thu, 2 Nov 2023 11:48:47 -0700 Subject: [PATCH 1/6] Add Find to Routes interface --- chi.go | 4 ++++ mux.go | 22 +++++++++++++---- mux_test.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/chi.go b/chi.go index a1691bbe..68443c46 100644 --- a/chi.go +++ b/chi.go @@ -127,6 +127,10 @@ type Routes interface { // the method/path - similar to routing a http request, but without // executing the handler thereafter. Match(rctx *Context, method, path string) bool + + // Find searches the routing tree for the pattern that matches + // the method/path. + Find(rctx *Context, method, path string) string } // Middlewares type is a slice of standard middleware handlers with methods diff --git a/mux.go b/mux.go index 735ab232..5453bb74 100644 --- a/mux.go +++ b/mux.go @@ -351,19 +351,33 @@ func (mx *Mux) Middlewares() Middlewares { // Note: the *Context state is updated during execution, so manage // the state carefully or make a NewRouteContext(). func (mx *Mux) Match(rctx *Context, method, path string) bool { + return mx.Find(rctx, method, path) != "" +} + +// Find searches the routing tree for the pattern that matches +// the method/path. +// +// Note: the *Context state is updated during execution, so manage +// the state carefully or make a NewRouteContext(). +func (mx *Mux) Find(rctx *Context, method, path string) string { m, ok := methodMap[method] if !ok { - return false + return "" } - node, _, h := mx.tree.FindRoute(rctx, m, path) + node, _, _ := mx.tree.FindRoute(rctx, m, path) if node != nil && node.subroutes != nil { rctx.RoutePath = mx.nextRoutePath(rctx) - return node.subroutes.Match(rctx, method, rctx.RoutePath) + return node.subroutes.Find(rctx, method, rctx.RoutePath) + } + + if node != nil { + e := node.endpoints[m] + return e.pattern } - return h != nil + return "" } // NotFoundHandler returns the default Mux 404 responder whenever a route diff --git a/mux_test.go b/mux_test.go index 350b137d..f413f948 100644 --- a/mux_test.go +++ b/mux_test.go @@ -1731,6 +1731,74 @@ func TestMuxMatch(t *testing.T) { } } +func TestMuxMatch_HasBasePath(t *testing.T) { + r := NewRouter() + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-Test", "yes") + w.Write([]byte("")) + }) + + tctx := NewRouteContext() + + tctx.Reset() + if r.Match(tctx, "GET", "/") != true { + t.Fatal("expecting to find match for route:", "GET", "/") + } +} + +func TestMuxMatch_DoesNotHaveBasePath(t *testing.T) { + r := NewRouter() + + tctx := NewRouteContext() + + tctx.Reset() + if r.Match(tctx, "GET", "/") != false { + t.Fatal("not expecting to find match for route:", "GET", "/") + } +} + +func TestMuxFind(t *testing.T) { + r := NewRouter() + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-Test", "yes") + w.Write([]byte("")) + }) + r.Get("/hi", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-Test", "yes") + w.Write([]byte("bye")) + }) + r.Route("/articles", func(r Router) { + r.Get("/{id}", func(w http.ResponseWriter, r *http.Request) { + id := URLParam(r, "id") + w.Header().Set("X-Article", id) + w.Write([]byte("article:" + id)) + }) + }) + r.Route("/users", func(r Router) { + r.Head("/{id}", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-User", "-") + w.Write([]byte("user")) + }) + r.Get("/{id}", func(w http.ResponseWriter, r *http.Request) { + id := URLParam(r, "id") + w.Header().Set("X-User", id) + w.Write([]byte("user:" + id)) + }) + }) + + tctx := NewRouteContext() + + tctx.Reset() + if r.Find(tctx, "GET", "/users/1") == "/users/{id}" { + t.Fatal("expecting to find match for route:", "GET", "/users/1") + } + + tctx.Reset() + if r.Find(tctx, "HEAD", "/articles/10") == "/articles/{id}" { + t.Fatal("not expecting to find match for route:", "HEAD", "/articles/10") + } +} + func TestServerBaseContext(t *testing.T) { r := NewRouter() r.Get("/", func(w http.ResponseWriter, r *http.Request) { From b1ed5b4b110e17d4f96eccafddf1e75b2f746bff Mon Sep 17 00:00:00 2001 From: jriddle Date: Thu, 2 Nov 2023 12:50:53 -0700 Subject: [PATCH 2/6] Add example of using Routes.Find --- _examples/api-middleware/main.go | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 _examples/api-middleware/main.go diff --git a/_examples/api-middleware/main.go b/_examples/api-middleware/main.go new file mode 100644 index 00000000..e68016c8 --- /dev/null +++ b/_examples/api-middleware/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi/v5" +) + +func main() { + r := chi.NewRouter() + r.Use(ApiMiddleware(r)) + + r.Get("/hello/{name}", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(fmt.Sprintf("hello, %s", chi.URLParam(r, "name")))) + }) + + http.ListenAndServe(":3333", r) +} + +// Middleware that prints the API pattern before the request is handled +func ApiMiddleware(router chi.Router) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rctx := chi.NewRouteContext() + path := r.URL.Path + op := r.Method + api := router.Find(rctx, op, path) + + fmt.Printf("api=%s\n", api) + + next.ServeHTTP(w, r) + }) + } +} From 5cf9bc354372b1222ce690e7638e3bff464cd4cf Mon Sep 17 00:00:00 2001 From: jriddle Date: Tue, 7 Nov 2023 13:10:37 -0800 Subject: [PATCH 3/6] Rename find-pattern example --- _examples/{api-middleware => find-pattern}/main.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename _examples/{api-middleware => find-pattern}/main.go (66%) diff --git a/_examples/api-middleware/main.go b/_examples/find-pattern/main.go similarity index 66% rename from _examples/api-middleware/main.go rename to _examples/find-pattern/main.go index e68016c8..53a1f320 100644 --- a/_examples/api-middleware/main.go +++ b/_examples/find-pattern/main.go @@ -9,7 +9,7 @@ import ( func main() { r := chi.NewRouter() - r.Use(ApiMiddleware(r)) + r.Use(FindPatternMiddleware(r)) r.Get("/hello/{name}", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(fmt.Sprintf("hello, %s", chi.URLParam(r, "name")))) @@ -18,16 +18,16 @@ func main() { http.ListenAndServe(":3333", r) } -// Middleware that prints the API pattern before the request is handled -func ApiMiddleware(router chi.Router) func(http.Handler) http.Handler { +// Middleware that prints the route pattern before the request is handled +func FindPatternMiddleware(router chi.Router) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rctx := chi.NewRouteContext() path := r.URL.Path op := r.Method - api := router.Find(rctx, op, path) + pattern := router.Find(rctx, op, path) - fmt.Printf("api=%s\n", api) + fmt.Printf("pattern=%s\n", pattern) next.ServeHTTP(w, r) }) From f6c1df81e3d69f6a08e24f7d3ed20e3642afffb3 Mon Sep 17 00:00:00 2001 From: jriddle Date: Tue, 7 Nov 2023 13:30:58 -0800 Subject: [PATCH 4/6] And find_pattern middleware --- middleware/find_pattern.go | 29 ++++++++++++++ middleware/find_pattern_test.go | 67 +++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 middleware/find_pattern.go create mode 100644 middleware/find_pattern_test.go diff --git a/middleware/find_pattern.go b/middleware/find_pattern.go new file mode 100644 index 00000000..48a0b6b9 --- /dev/null +++ b/middleware/find_pattern.go @@ -0,0 +1,29 @@ +package middleware + +import ( + "net/http" + + "github.com/go-chi/chi/v5" +) + +// Find the route pattern for the request path. +// +// This middleware does not need to be the last middleware to resolve the +// route pattern. The pattern is fully resolved before the request has been +// handled. +func FindPattern(routes chi.Routes, callback func(pattern string)) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + // Find mutates the context so always make a new one + rctx := chi.NewRouteContext() + path := r.URL.Path + op := r.Method + pattern := routes.Find(rctx, op, path) + callback(pattern) + + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + } + +} diff --git a/middleware/find_pattern_test.go b/middleware/find_pattern_test.go new file mode 100644 index 00000000..e7b5e8d3 --- /dev/null +++ b/middleware/find_pattern_test.go @@ -0,0 +1,67 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-chi/chi/v5" +) + +func TestFindPattern(t *testing.T) { + t.Parallel() + + var tests = []struct { + pattern string + path string + }{ + { + "/", + "/", + }, + { + "/hi", + "/hi", + }, + { + "/{id}", + "/123", + }, + { + "/{id}/hello", + "/123/hello", + }, + { + "/users/*", + "/users/123", + }, + { + "/users/*", + "/users/123/hello", + }, + } + + for _, tt := range tests { + var tt = tt + t.Run(tt.pattern, func(t *testing.T) { + t.Parallel() + + recorder := httptest.NewRecorder() + + r := chi.NewRouter() + r.Use(FindPattern(r, func(pattern string) { + if pattern != tt.pattern { + t.Errorf("actual pattern \"%s\" does not equal expected pattern \"%s\"", pattern, tt.pattern) + } + })) + + r.Get(tt.pattern, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("")) + }) + + req := httptest.NewRequest("GET", tt.path, nil) + r.ServeHTTP(recorder, req) + recorder.Result() + }) + } +} From 1f4b3131322d43801247f097980e92336a8a26bf Mon Sep 17 00:00:00 2001 From: jriddle Date: Tue, 7 Nov 2023 13:32:38 -0800 Subject: [PATCH 5/6] Refactor find-pattern example to use official FindPattern middleware --- _examples/find-pattern/main.go | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/_examples/find-pattern/main.go b/_examples/find-pattern/main.go index 53a1f320..76a139f5 100644 --- a/_examples/find-pattern/main.go +++ b/_examples/find-pattern/main.go @@ -5,11 +5,14 @@ import ( "net/http" "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" ) func main() { r := chi.NewRouter() - r.Use(FindPatternMiddleware(r)) + r.Use(middleware.FindPattern(r, func(pattern string) { + fmt.Printf("pattern=%s\n", pattern) + })) r.Get("/hello/{name}", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(fmt.Sprintf("hello, %s", chi.URLParam(r, "name")))) @@ -17,19 +20,3 @@ func main() { http.ListenAndServe(":3333", r) } - -// Middleware that prints the route pattern before the request is handled -func FindPatternMiddleware(router chi.Router) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - rctx := chi.NewRouteContext() - path := r.URL.Path - op := r.Method - pattern := router.Find(rctx, op, path) - - fmt.Printf("pattern=%s\n", pattern) - - next.ServeHTTP(w, r) - }) - } -} From 1daabdc3893bea98415d6d95b631dd5ad4dd2b2c Mon Sep 17 00:00:00 2001 From: Vojtech Vitek Date: Wed, 18 Sep 2024 15:11:09 +0200 Subject: [PATCH 6/6] Remove middleware.FindPattern for now --- _examples/find-pattern/main.go | 22 ----------- middleware/find_pattern.go | 29 -------------- middleware/find_pattern_test.go | 67 --------------------------------- 3 files changed, 118 deletions(-) delete mode 100644 _examples/find-pattern/main.go delete mode 100644 middleware/find_pattern.go delete mode 100644 middleware/find_pattern_test.go diff --git a/_examples/find-pattern/main.go b/_examples/find-pattern/main.go deleted file mode 100644 index 76a139f5..00000000 --- a/_examples/find-pattern/main.go +++ /dev/null @@ -1,22 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" -) - -func main() { - r := chi.NewRouter() - r.Use(middleware.FindPattern(r, func(pattern string) { - fmt.Printf("pattern=%s\n", pattern) - })) - - r.Get("/hello/{name}", func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(fmt.Sprintf("hello, %s", chi.URLParam(r, "name")))) - }) - - http.ListenAndServe(":3333", r) -} diff --git a/middleware/find_pattern.go b/middleware/find_pattern.go deleted file mode 100644 index 48a0b6b9..00000000 --- a/middleware/find_pattern.go +++ /dev/null @@ -1,29 +0,0 @@ -package middleware - -import ( - "net/http" - - "github.com/go-chi/chi/v5" -) - -// Find the route pattern for the request path. -// -// This middleware does not need to be the last middleware to resolve the -// route pattern. The pattern is fully resolved before the request has been -// handled. -func FindPattern(routes chi.Routes, callback func(pattern string)) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - // Find mutates the context so always make a new one - rctx := chi.NewRouteContext() - path := r.URL.Path - op := r.Method - pattern := routes.Find(rctx, op, path) - callback(pattern) - - next.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) - } - -} diff --git a/middleware/find_pattern_test.go b/middleware/find_pattern_test.go deleted file mode 100644 index e7b5e8d3..00000000 --- a/middleware/find_pattern_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package middleware - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/go-chi/chi/v5" -) - -func TestFindPattern(t *testing.T) { - t.Parallel() - - var tests = []struct { - pattern string - path string - }{ - { - "/", - "/", - }, - { - "/hi", - "/hi", - }, - { - "/{id}", - "/123", - }, - { - "/{id}/hello", - "/123/hello", - }, - { - "/users/*", - "/users/123", - }, - { - "/users/*", - "/users/123/hello", - }, - } - - for _, tt := range tests { - var tt = tt - t.Run(tt.pattern, func(t *testing.T) { - t.Parallel() - - recorder := httptest.NewRecorder() - - r := chi.NewRouter() - r.Use(FindPattern(r, func(pattern string) { - if pattern != tt.pattern { - t.Errorf("actual pattern \"%s\" does not equal expected pattern \"%s\"", pattern, tt.pattern) - } - })) - - r.Get(tt.pattern, func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("")) - }) - - req := httptest.NewRequest("GET", tt.path, nil) - r.ServeHTTP(recorder, req) - recorder.Result() - }) - } -}