diff --git a/README.md b/README.md index 6ae4989..ef33097 100644 --- a/README.md +++ b/README.md @@ -27,18 +27,18 @@ go get git.sharkk.net/Go/Router r := router.New() // Static routes -r.Get("/", func(w http.ResponseWriter, r *http.Request, params []string) { +r.Get("/", func(w router.Res, r router.Req, params []string) { fmt.Fprintf(w, "Root handler") }) // Parameter routes -r.Get("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) { +r.Get("/users/[id]", func(w router.Res, r router.Req, params []string) { userID := params[0] fmt.Fprintf(w, "User ID: %s", userID) }) // Wildcard routes -r.Get("/files/*path", func(w http.ResponseWriter, r *http.Request, params []string) { +r.Get("/files/*path", func(w router.Res, r router.Req, params []string) { filePath := params[0] fmt.Fprintf(w, "File path: %s", filePath) }) @@ -53,7 +53,7 @@ if handler, params, ok := r.Lookup("GET", "/users/123"); ok { handler.Serve(params) } -// Use listen and serve directly +// Or simply serve them http.ListenAndServe(":8080", r) ``` @@ -105,7 +105,7 @@ http.ListenAndServe(":8080", r) ## Benchmarks -Benchmark results comparing Router to the standard `http.ServeMux`: +Benchmark comparing Router to the standard `http.ServeMux`: ``` cpu: AMD Ryzen 9 7950X 16-Core Processor diff --git a/router.go b/router.go index 9e27946..5181eda 100644 --- a/router.go +++ b/router.go @@ -3,9 +3,14 @@ package router import ( "fmt" "net/http" - "slices" ) +// Res is an alias for http.ResponseWriter for shorter, cleaner code +type Res = http.ResponseWriter + +// Req is an alias for *http.Request for shorter, cleaner code +type Req = *http.Request + // Handler is an interface for handling HTTP requests with path parameters. type Handler interface { Serve(params []string) @@ -76,9 +81,9 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { // httpHandler adapts net/http handlers to the router. type httpHandler struct { - w http.ResponseWriter - r *http.Request - h func(w http.ResponseWriter, r *http.Request, params []string) + w Res + r Req + h func(w Res, r Req, params []string) } // Serve executes the http handler with parameters. @@ -112,7 +117,7 @@ func (g *Group) Group(prefix string) *Group { return &Group{ router: g.router, prefix: g.prefix + prefix, - middleware: slices.Clone(g.middleware), + middleware: append([]Middleware{}, g.middleware...), } } @@ -182,7 +187,7 @@ func (r *Router) Delete(path string, handler HandlerFunc) error { // buildGroupMiddleware returns combined middleware for the group func (g *Group) buildGroupMiddleware() []Middleware { - middleware := slices.Clone(g.router.middleware) + middleware := append([]Middleware{}, g.router.middleware...) return append(middleware, g.middleware...) } @@ -252,7 +257,7 @@ type MiddlewareGroup struct { // buildMiddleware returns combined middleware for the middleware router func (mr *MiddlewareRouter) buildMiddleware() []Middleware { - middleware := slices.Clone(mr.router.middleware) + middleware := append([]Middleware{}, mr.router.middleware...) return append(middleware, mr.middleware...) } @@ -293,7 +298,7 @@ func (mr *MiddlewareRouter) Delete(path string, handler HandlerFunc) error { // buildMiddleware returns combined middleware for the middleware group func (mg *MiddlewareGroup) buildMiddleware() []Middleware { - middleware := slices.Clone(mg.group.router.middleware) + middleware := append([]Middleware{}, mg.group.router.middleware...) middleware = append(middleware, mg.group.middleware...) return append(middleware, mg.middleware...) } diff --git a/router_test.go b/router_test.go index 1829f6f..4290ade 100644 --- a/router_test.go +++ b/router_test.go @@ -24,7 +24,7 @@ func newHandler(fn func(params []string)) Handler { func TestRootPath(t *testing.T) { r := New() - r.Get("/", func(w http.ResponseWriter, r *http.Request, params []string) { + r.Get("/", func(w Res, r Req, params []string) { // No-op for testing }) @@ -35,7 +35,7 @@ func TestRootPath(t *testing.T) { func TestStaticPath(t *testing.T) { r := New() - r.Get("/users/all", func(w http.ResponseWriter, r *http.Request, params []string) { + r.Get("/users/all", func(w Res, r Req, params []string) { // No-op for testing }) @@ -48,7 +48,7 @@ func TestSingleParameter(t *testing.T) { r := New() called := false - r.Get("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) { + r.Get("/users/[id]", func(w Res, r Req, params []string) { called = true assert.Equal(t, params[0], "123") }) @@ -63,7 +63,7 @@ func TestMultipleParameters(t *testing.T) { r := New() called := false - r.Get("/users/[id]/posts/[postId]", func(w http.ResponseWriter, r *http.Request, params []string) { + r.Get("/users/[id]/posts/[postId]", func(w Res, r Req, params []string) { called = true assert.Equal(t, params[0], "123") assert.Equal(t, params[1], "456") @@ -77,7 +77,7 @@ func TestMultipleParameters(t *testing.T) { func TestNonExistentPath(t *testing.T) { r := New() - r.Get("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) { + r.Get("/users/[id]", func(w Res, r Req, params []string) { // No-op for testing }) @@ -87,7 +87,7 @@ func TestNonExistentPath(t *testing.T) { func TestWrongMethod(t *testing.T) { r := New() - r.Get("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) { + r.Get("/users/[id]", func(w Res, r Req, params []string) { // No-op for testing }) @@ -99,7 +99,7 @@ func TestTrailingSlash(t *testing.T) { r := New() called := false - r.Get("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) { + r.Get("/users/[id]", func(w Res, r Req, params []string) { called = true assert.Equal(t, params[0], "123") }) @@ -113,7 +113,7 @@ func TestTrailingSlash(t *testing.T) { func TestDifferentMethods(t *testing.T) { r := New() - handler := func(w http.ResponseWriter, r *http.Request, params []string) {} + handler := func(w Res, r Req, params []string) {} r.Get("/test", handler) r.Post("/test", handler) @@ -135,7 +135,7 @@ func TestWildcardPath(t *testing.T) { t.Run("simple wildcard", func(t *testing.T) { called := false - err := r.Get("/files/*path", func(w http.ResponseWriter, r *http.Request, params []string) { + err := r.Get("/files/*path", func(w Res, r Req, params []string) { called = true assert.Equal(t, params[0], "docs/report.pdf") }) @@ -149,7 +149,7 @@ func TestWildcardPath(t *testing.T) { t.Run("wildcard with empty path", func(t *testing.T) { called := false - err := r.Get("/download/*filepath", func(w http.ResponseWriter, r *http.Request, params []string) { + err := r.Get("/download/*filepath", func(w Res, r Req, params []string) { called = true assert.Equal(t, params[0], "") }) @@ -163,7 +163,7 @@ func TestWildcardPath(t *testing.T) { t.Run("wildcard with parameter", func(t *testing.T) { called := false - err := r.Get("/users/[id]/*action", func(w http.ResponseWriter, r *http.Request, params []string) { + err := r.Get("/users/[id]/*action", func(w Res, r Req, params []string) { called = true assert.Equal(t, params[0], "123") assert.Equal(t, params[1], "settings/profile/avatar") @@ -177,12 +177,12 @@ func TestWildcardPath(t *testing.T) { }) t.Run("multiple wildcards not allowed", func(t *testing.T) { - err := r.Get("/api/*version/*path", func(w http.ResponseWriter, r *http.Request, params []string) {}) + err := r.Get("/api/*version/*path", func(w Res, r Req, params []string) {}) assert.NotNil(t, err) }) t.Run("non-last wildcard not allowed", func(t *testing.T) { - err := r.Get("/api/*version/users", func(w http.ResponseWriter, r *http.Request, params []string) {}) + err := r.Get("/api/*version/users", func(w Res, r Req, params []string) {}) assert.NotNil(t, err) }) } @@ -202,7 +202,7 @@ func TestMiddleware(t *testing.T) { }) }) - r.Get("/test", func(w http.ResponseWriter, r *http.Request, params []string) {}) + r.Get("/test", func(w Res, r Req, params []string) {}) h, params, found := r.Lookup("GET", "/test") assert.True(t, found) @@ -232,7 +232,7 @@ func TestMiddleware(t *testing.T) { }) }) - r.Get("/test", func(w http.ResponseWriter, r *http.Request, params []string) { + r.Get("/test", func(w Res, r Req, params []string) { order = append(order, 0) }) @@ -261,7 +261,7 @@ func TestMiddleware(t *testing.T) { }) } - r.WithMiddleware(middleware).Get("/test", func(w http.ResponseWriter, r *http.Request, params []string) {}) + r.WithMiddleware(middleware).Get("/test", func(w Res, r Req, params []string) {}) h, params, found := r.Lookup("GET", "/test") assert.True(t, found) @@ -277,7 +277,7 @@ func TestGroup(t *testing.T) { // Create API group api := r.Group("/api") - api.Get("/users", func(w http.ResponseWriter, r *http.Request, params []string) {}) + api.Get("/users", func(w Res, r Req, params []string) {}) h, params, found := r.Lookup("GET", "/api/users") assert.True(t, found) @@ -290,7 +290,7 @@ func TestGroup(t *testing.T) { // Create nested groups api := r.Group("/api") v1 := api.Group("/v1") - v1.Get("/users", func(w http.ResponseWriter, r *http.Request, params []string) {}) + v1.Get("/users", func(w Res, r Req, params []string) {}) h, params, found := r.Lookup("GET", "/api/v1/users") assert.True(t, found) @@ -310,7 +310,7 @@ func TestGroup(t *testing.T) { }) }) - api.Get("/users", func(w http.ResponseWriter, r *http.Request, params []string) {}) + api.Get("/users", func(w Res, r Req, params []string) {}) h, params, found := r.Lookup("GET", "/api/users") assert.True(t, found) @@ -340,7 +340,7 @@ func TestGroup(t *testing.T) { }) }) - v1.Get("/users", func(w http.ResponseWriter, r *http.Request, params []string) { + v1.Get("/users", func(w Res, r Req, params []string) { order = append(order, 3) }) @@ -374,7 +374,7 @@ func TestGroup(t *testing.T) { order = append(order, 2) next.Serve(params) }) - }).Get("/users", func(w http.ResponseWriter, r *http.Request, params []string) { + }).Get("/users", func(w Res, r Req, params []string) { order = append(order, 3) }) @@ -393,7 +393,7 @@ func TestGroup(t *testing.T) { // Benchmarks func BenchmarkRouterLookup(b *testing.B) { r := New() - handler := func(w http.ResponseWriter, r *http.Request, params []string) {} + handler := func(w Res, r Req, params []string) {} // Setup routes for benchmarking r.Get("/", handler) @@ -434,7 +434,7 @@ func BenchmarkRouterLookup(b *testing.B) { func BenchmarkParallelLookup(b *testing.B) { r := New() - handler := func(w http.ResponseWriter, r *http.Request, params []string) {} + handler := func(w Res, r Req, params []string) {} r.Get("/users/[id]", handler) r.Get("/posts/[id]/comments", handler) @@ -458,7 +458,7 @@ func BenchmarkParallelLookup(b *testing.B) { func BenchmarkWildcardLookup(b *testing.B) { r := New() - handler := func(w http.ResponseWriter, r *http.Request, params []string) {} + handler := func(w Res, r Req, params []string) {} // Setup routes for benchmarking r.Get("/files/*path", handler) @@ -486,7 +486,7 @@ func BenchmarkMiddleware(b *testing.B) { b.Run("no_middleware", func(b *testing.B) { r := New() - r.Get("/test", func(w http.ResponseWriter, r *http.Request, params []string) {}) + r.Get("/test", func(w Res, r Req, params []string) {}) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -498,7 +498,7 @@ func BenchmarkMiddleware(b *testing.B) { b.Run("one_middleware", func(b *testing.B) { r := New() r.Use(passthrough) - r.Get("/test", func(w http.ResponseWriter, r *http.Request, params []string) {}) + r.Get("/test", func(w Res, r Req, params []string) {}) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -512,7 +512,7 @@ func BenchmarkMiddleware(b *testing.B) { for i := 0; i < 5; i++ { r.Use(passthrough) } - r.Get("/test", func(w http.ResponseWriter, r *http.Request, params []string) {}) + r.Get("/test", func(w Res, r Req, params []string) {}) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -523,7 +523,7 @@ func BenchmarkMiddleware(b *testing.B) { } func BenchmarkGroups(b *testing.B) { - handler := func(w http.ResponseWriter, r *http.Request, params []string) {} + handler := func(w Res, r Req, params []string) {} b.Run("flat_route", func(b *testing.B) { r := New() @@ -570,13 +570,13 @@ func BenchmarkNetHttpServer(b *testing.B) { r := New() // Set up test routes - r.Get("/", func(w http.ResponseWriter, r *http.Request, params []string) { + r.Get("/", func(w Res, r Req, params []string) { // No-op for benchmarking }) - r.Get("/users/all", func(w http.ResponseWriter, r *http.Request, params []string) { + r.Get("/users/all", func(w Res, r Req, params []string) { // No-op for benchmarking }) - r.Get("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) { + r.Get("/users/[id]", func(w Res, r Req, params []string) { // No-op for benchmarking }) @@ -631,7 +631,7 @@ func BenchmarkNetHttpServer(b *testing.B) { func BenchmarkComparison(b *testing.B) { // Custom router setup customRouter := New() - handler := func(w http.ResponseWriter, r *http.Request, params []string) {} + handler := func(w Res, r Req, params []string) {} customRouter.Get("/", handler) customRouter.Get("/users/all", handler) @@ -640,9 +640,9 @@ func BenchmarkComparison(b *testing.B) { // Standard mux setup mux := http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {}) - mux.HandleFunc("/users/all", func(w http.ResponseWriter, r *http.Request) {}) - mux.HandleFunc("/users/", func(w http.ResponseWriter, r *http.Request) {}) // Best equivalent for dynamic routes + mux.HandleFunc("/", func(w Res, r Req) {}) + mux.HandleFunc("/users/all", func(w Res, r Req) {}) + mux.HandleFunc("/users/", func(w Res, r Req) {}) // Best equivalent for dynamic routes // Root path b.Run("root_path", func(b *testing.B) {