Compare commits

..

No commits in common. "master" and "1.0.0" have entirely different histories.

5 changed files with 708 additions and 879 deletions

155
DOCS.md
View File

@ -1,155 +0,0 @@
# Router Package Documentation
A fast, lightweight HTTP router for Go with support for middleware, route groups, and path parameters.
## Core Types
### Router
Main router that implements `http.Handler`.
```go
router := router.New()
```
### Handler
Request handler function type.
```go
type Handler func(w http.ResponseWriter, r *http.Request, params []string)
```
### Middleware
Function type for middleware.
```go
type Middleware func(Handler) Handler
```
### Group
Route group with a prefix.
```go
group := router.Group("/api")
```
## Router Methods
### New()
Creates a new router.
```go
router := router.New()
```
### ServeHTTP(w, r)
Implements `http.Handler` interface.
### Use(mw ...Middleware)
Adds global middleware.
```go
router.Use(loggingMiddleware, authMiddleware)
```
### Handle(method, path, handler)
Registers a handler for the given method and path.
```go
router.Handle("GET", "/users", listUsersHandler)
```
### HTTP Method Shortcuts
```go
router.Get("/users", listUsersHandler)
router.Post("/users", createUserHandler)
router.Put("/users/[id]", updateUserHandler)
router.Patch("/users/[id]", patchUserHandler)
router.Delete("/users/[id]", deleteUserHandler)
```
### Group(prefix)
Creates a route group with prefix.
```go
api := router.Group("/api")
```
### WithMiddleware(mw ...Middleware)
Applies middleware to the next route registration.
```go
router.WithMiddleware(authMiddleware).Get("/admin", adminHandler)
```
## Group Methods
### Use(mw ...Middleware)
Adds middleware to the group.
```go
api.Use(apiKeyMiddleware)
```
### Group(prefix)
Creates a nested group.
```go
v1 := api.Group("/v1")
```
### HTTP Method Shortcuts
```go
api.Get("/users", listUsersHandler)
api.Post("/users", createUserHandler)
api.Put("/users/[id]", updateUserHandler)
api.Patch("/users/[id]", patchUserHandler)
api.Delete("/users/[id]", deleteUserHandler)
```
### WithMiddleware(mw ...Middleware)
Applies middleware to the next route registration in this group.
```go
api.WithMiddleware(authMiddleware).Get("/admin", adminHandler)
```
## Path Parameters
Dynamic segments in paths are defined using square brackets.
```go
router.Get("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
id := params[0]
// ...
})
```
## Wildcards
Wildcard segments capture all remaining path segments.
```go
router.Get("/files/*path", func(w http.ResponseWriter, r *http.Request, params []string) {
path := params[0]
// ...
})
```
Notes:
- Wildcards must be the last segment in a path
- Only one wildcard is allowed per path

View File

@ -1,276 +0,0 @@
# Examples
## Basic Usage
```go
package main
import (
"fmt"
"net/http"
"github.com/yourusername/router"
)
func main() {
r := router.New()
r.Get("/", func(w http.ResponseWriter, r *http.Request, _ []string) {
fmt.Fprintf(w, "Hello World!")
})
r.Get("/about", func(w http.ResponseWriter, r *http.Request, _ []string) {
fmt.Fprintf(w, "About page")
})
http.ListenAndServe(":8080", r)
}
```
## Path Parameters
```go
r := router.New()
// Single parameter
r.Get("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
id := params[0]
fmt.Fprintf(w, "User ID: %s", id)
})
// Multiple parameters
r.Get("/posts/[category]/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
category := params[0]
id := params[1]
fmt.Fprintf(w, "Category: %s, Post ID: %s", category, id)
})
// Wildcard
r.Get("/files/*path", func(w http.ResponseWriter, r *http.Request, params []string) {
path := params[0]
fmt.Fprintf(w, "File path: %s", path)
})
```
## Middleware
```go
// Logging middleware
func LoggingMiddleware(next router.Handler) router.Handler {
return router.Handler(func(w http.ResponseWriter, r *http.Request, params []string) {
fmt.Printf("[%s] %s\n", r.Method, r.URL.Path)
next(w, r, params)
})
}
// Auth middleware
func AuthMiddleware(next router.Handler) router.Handler {
return router.Handler(func(w http.ResponseWriter, r *http.Request, params []string) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next(w, r, params)
})
}
// Global middleware
r := router.New()
r.Use(LoggingMiddleware)
// Route-specific middleware
r.WithMiddleware(AuthMiddleware).Get("/admin", adminHandler)
```
## Route Groups
```go
r := router.New()
// API group
api := r.Group("/api")
api.Get("/status", statusHandler)
// Versioned API
v1 := api.Group("/v1")
v1.Get("/users", listUsersHandler)
v1.Post("/users", createUserHandler)
v2 := api.Group("/v2")
v2.Get("/users", listUsersV2Handler)
```
## Combining Features
```go
r := router.New()
// Global middleware
r.Use(LoggingMiddleware)
// API group with middleware
api := r.Group("/api")
api.Use(ApiKeyMiddleware)
// Admin group with auth middleware
admin := r.Group("/admin")
admin.Use(AuthMiddleware)
// Users endpoints with versioning
users := api.Group("/v1/users")
users.Get("/", listUsersHandler)
users.Post("/", createUserHandler)
users.Get("/[id]", getUserHandler)
users.Put("/[id]", updateUserHandler)
users.Delete("/[id]", deleteUserHandler)
// Special case with route-specific middleware
api.WithMiddleware(CacheMiddleware).Get("/cached-resource", cachedResourceHandler)
```
## Error Handling
```go
r := router.New()
err := r.Get("/users/[id]", getUserHandler)
if err != nil {
// Handle error
}
// Custom NotFound handler
oldServeHTTP := r.ServeHTTP
r.ServeHTTP = func(w http.ResponseWriter, req *http.Request) {
h, params, ok := r.Lookup(req.Method, req.URL.Path)
if !ok {
// Custom 404 handler
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Custom 404: %s not found", req.URL.Path)
return
}
h(w, req, params)
}
```
## Complete Application Example
```go
package main
import (
"fmt"
"log"
"net/http"
"github.com/yourusername/router"
)
func main() {
r := router.New()
// Global middleware
r.Use(LoggingMiddleware)
// Basic routes
r.Get("/", homeHandler)
r.Get("/about", aboutHandler)
// API routes
api := r.Group("/api")
api.Use(ApiKeyMiddleware)
// Users API
users := api.Group("/users")
users.Get("/", listUsersHandler)
users.Post("/", createUserHandler)
users.Get("/[id]", getUserHandler)
users.Put("/[id]", updateUserHandler)
users.Delete("/[id]", deleteUserHandler)
// Admin routes with auth
admin := r.Group("/admin")
admin.Use(AuthMiddleware)
admin.Get("/", adminDashboardHandler)
admin.Get("/users", adminUsersHandler)
// Start server
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
// Handlers
func homeHandler(w http.ResponseWriter, r *http.Request, _ []string) {
fmt.Fprintf(w, "Welcome to the home page")
}
func aboutHandler(w http.ResponseWriter, r *http.Request, _ []string) {
fmt.Fprintf(w, "About us")
}
func listUsersHandler(w http.ResponseWriter, r *http.Request, _ []string) {
fmt.Fprintf(w, "List of users")
}
func getUserHandler(w http.ResponseWriter, r *http.Request, params []string) {
id := params[0]
fmt.Fprintf(w, "User details for ID: %s", id)
}
func createUserHandler(w http.ResponseWriter, r *http.Request, _ []string) {
// Parse form data or JSON body
fmt.Fprintf(w, "User created")
}
func updateUserHandler(w http.ResponseWriter, r *http.Request, params []string) {
id := params[0]
fmt.Fprintf(w, "User updated: %s", id)
}
func deleteUserHandler(w http.ResponseWriter, r *http.Request, params []string) {
id := params[0]
fmt.Fprintf(w, "User deleted: %s", id)
}
func adminDashboardHandler(w http.ResponseWriter, r *http.Request, _ []string) {
fmt.Fprintf(w, "Admin Dashboard")
}
func adminUsersHandler(w http.ResponseWriter, r *http.Request, _ []string) {
fmt.Fprintf(w, "Admin Users Management")
}
// Middleware
func LoggingMiddleware(next router.Handler) router.Handler {
return router.Handler(func(w http.ResponseWriter, r *http.Request, params []string) {
log.Printf("[%s] %s", r.Method, r.URL.Path)
next(w, r, params)
})
}
func ApiKeyMiddleware(next router.Handler) router.Handler {
return router.Handler(func(w http.ResponseWriter, r *http.Request, params []string) {
apiKey := r.Header.Get("X-API-Key")
if apiKey == "" {
http.Error(w, "API key required", http.StatusUnauthorized)
return
}
next(w, r, params)
})
}
func AuthMiddleware(next router.Handler) router.Handler {
return router.Handler(func(w http.ResponseWriter, r *http.Request, params []string) {
// Check session or JWT
authorized := checkUserAuth(r)
if !authorized {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
next(w, r, params)
})
}
func checkUserAuth(r *http.Request) bool {
// Implementation of auth check
return r.Header.Get("Authorization") != ""
}
```

110
README.md
View File

@ -8,7 +8,7 @@ A high-performance router for Go with support for path parameters, wildcards, mi
- Support for path parameters (`[id]`) and wildcards (`*path`) - Support for path parameters (`[id]`) and wildcards (`*path`)
- Middleware support for request processing pipelines - Middleware support for request processing pipelines
- Route grouping with shared prefixes and middleware - Route grouping with shared prefixes and middleware
- Implements `http.Handler` for direct integration - Adapters for net/http and fasthttp (optional)
- Up to 15x faster than `http.ServeMux` for dynamic routes - Up to 15x faster than `http.ServeMux` for dynamic routes
- Zero allocations for static routes - Zero allocations for static routes
@ -27,29 +27,26 @@ go get git.sharkk.net/Go/Router
r := router.New() r := router.New()
// Static routes // Static routes
r.Get("/", func(w router.Res, r router.Req, params []string) { r.Get("/", router.FuncHandler(func(params []string) {
fmt.Fprintf(w, "Root handler") fmt.Println("Root handler")
}) }))
// Parameter routes // Parameter routes
r.Get("/users/[id]", func(w router.Res, r router.Req, params []string) { r.Get("/users/[id]", router.FuncHandler(func(params []string) {
userID := params[0] userID := params[0]
fmt.Fprintf(w, "User ID: %s", userID) fmt.Printf("User ID: %s\n", userID)
}) }))
// Wildcard routes // Wildcard routes
r.Get("/files/*path", func(w router.Res, r router.Req, params []string) { r.Get("/files/*path", router.FuncHandler(func(params []string) {
filePath := params[0] filePath := params[0]
fmt.Fprintf(w, "File path: %s", filePath) fmt.Printf("File path: %s\n", filePath)
}) }))
// Lookup routes manually // Lookup routes
if handler, params, ok := r.Lookup("GET", "/users/123"); ok { if handler, params, ok := r.Lookup("GET", "/users/123"); ok {
handler(nil, nil, params) handler.Serve(params)
} }
// Or simply serve them
http.ListenAndServe(":8080", r)
``` ```
### Middleware ### Middleware
@ -57,9 +54,9 @@ http.ListenAndServe(":8080", r)
```go ```go
// Create logging middleware // Create logging middleware
func LoggingMiddleware(next router.Handler) router.Handler { func LoggingMiddleware(next router.Handler) router.Handler {
return router.Handler(func(w router.Res, r router.Req, params []string) { return router.FuncHandler(func(params []string) {
fmt.Println("Request started") fmt.Println("Request started")
next(w, r, params) next.Serve(params)
fmt.Println("Request completed") fmt.Println("Request completed")
}) })
} }
@ -70,8 +67,6 @@ r.Use(LoggingMiddleware)
// Apply middleware to specific routes // Apply middleware to specific routes
r.WithMiddleware(AuthMiddleware).Get("/admin", adminHandler) r.WithMiddleware(AuthMiddleware).Get("/admin", adminHandler)
http.ListenAndServe(":8080", r)
``` ```
### Route Groups ### Route Groups
@ -92,38 +87,85 @@ v1.Get("/products", listProductsHandler) // matches /api/v1/products
admin := api.Group("/admin") admin := api.Group("/admin")
admin.Use(AuthMiddleware) admin.Use(AuthMiddleware)
admin.Get("/stats", statsHandler) // matches /api/admin/stats admin.Get("/stats", statsHandler) // matches /api/admin/stats
```
http.ListenAndServe(":8080", r) ### HTTP Integration
```go
// Standard net/http integration
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Find the matching handler
handler, params, found := router.Lookup(r.Method, r.URL.Path)
if !found {
http.NotFound(w, r)
return
}
// Create an HTTP-compatible handler
httpHandler := router.NewHTTP(w, r, func(w http.ResponseWriter, r *http.Request, params []string) {
fmt.Fprintf(w, "Hello, user %s", params[0])
})
// Execute the handler
httpHandler.Serve(params)
})
```
### FastHTTP Integration (Optional)
Build with `-tags fasthttp` to enable FastHTTP support:
```go
// FastHTTP integration
fastHandler := func(ctx *fasthttp.RequestCtx) {
path := string(ctx.Path())
method := string(ctx.Method())
handler, params, found := router.Lookup(method, path)
if !found {
ctx.Error("Not found", fasthttp.StatusNotFound)
return
}
// Create a FastHTTP-compatible handler
fastHandler := router.NewFastHTTP(ctx, func(ctx *fasthttp.RequestCtx, params []string) {
fmt.Fprintf(ctx, "Hello, user %s", params[0])
})
// Execute the handler
fastHandler.Serve(params)
}
``` ```
## Benchmarks ## Benchmarks
Benchmark comparing Router to the standard `http.ServeMux`: Benchmark results comparing our router to the standard `http.ServeMux` on AMD Ryzen 9 7950X:
``` ```
cpu: 13th Gen Intel(R) Core(TM) i7-1370P cpu: AMD Ryzen 9 7950X 16-Core Processor
BenchmarkComparison/root_path BenchmarkComparison/root_path
Router: 1.798 ns/op 0 B/op 0 allocs/op Router: 2.098 ns/op 0 B/op 0 allocs/op
ServeMux: 40.98 ns/op 0 B/op 0 allocs/op ServeMux: 32.010 ns/op 0 B/op 0 allocs/op
BenchmarkComparison/static_path BenchmarkComparison/static_path
Router: 18.41 ns/op 0 B/op 0 allocs/op Router: 16.050 ns/op 0 B/op 0 allocs/op
ServeMux: 86.04 ns/op 0 B/op 0 allocs/op ServeMux: 67.980 ns/op 0 B/op 0 allocs/op
BenchmarkComparison/dynamic_path BenchmarkComparison/dynamic_path
Router: 24.23 ns/op 0 B/op 0 allocs/op Router: 39.170 ns/op 16 B/op 1 allocs/op
ServeMux: 221.9 ns/op 48 B/op 3 allocs/op ServeMux: 174.000 ns/op 48 B/op 3 allocs/op
BenchmarkComparison/not_found BenchmarkComparison/not_found
Router: 10.76 ns/op 0 B/op 0 allocs/op Router: 10.580 ns/op 0 B/op 0 allocs/op
ServeMux: 210.2 ns/op 56 B/op 3 allocs/op ServeMux: 178.100 ns/op 56 B/op 3 allocs/op
``` ```
- Root path lookups are 22x faster Key Performance Points:
- Static paths are 4.7x faster with zero allocations - Root path lookups are 15x faster
- Dynamic paths are 9x faster with zero allocations - Static paths are 4x faster with zero allocations
- Not found paths are 19.5x faster with zero allocations - Dynamic paths are 4.4x faster with fewer allocations
- Not found paths are 16.8x faster with zero allocations
## License ## License

765
router.go
View File

@ -3,36 +3,38 @@ package router
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"slices"
) )
type Res = http.ResponseWriter // Handler is an interface for handling HTTP requests with path parameters.
type Req = *http.Request type Handler interface {
Serve(params []string)
// Handler is a request handler with parameters.
type Handler func(w Res, r Req, params []string)
func (h Handler) Serve(w Res, r Req, params []string) {
h(w, r, params)
} }
// Middleware wraps a handler with additional functionality.
type Middleware func(Handler) Handler type Middleware func(Handler) Handler
// node represents a segment in the URL path and its handling logic.
type node struct { type node struct {
segment string segment string // the path segment this node matches
handler Handler handler Handler // handler for this path, if it's an endpoint
children []*node children []*node // child nodes for subsequent path segments
isDynamic bool isDynamic bool // true for param segments like [id]
isWildcard bool isWildcard bool // true for catch-all segments like *filepath
maxParams uint8 maxParams uint8 // maximum number of parameters in paths under this node
} }
// Router routes HTTP requests by method and path.
// It supports static paths, path parameters, wildcards, and middleware.
type Router struct { type Router struct {
get, post, put, patch, delete *node get *node
middleware []Middleware post *node
paramsBuffer []string put *node
patch *node
delete *node
middleware []Middleware // Global middleware
} }
// Group represents a route group with a path prefix and shared middleware.
type Group struct { type Group struct {
router *Router router *Router
prefix string prefix string
@ -42,371 +44,624 @@ type Group struct {
// New creates a new Router instance. // New creates a new Router instance.
func New() *Router { func New() *Router {
return &Router{ return &Router{
get: &node{}, get: &node{},
post: &node{}, post: &node{},
put: &node{}, put: &node{},
patch: &node{}, patch: &node{},
delete: &node{}, delete: &node{},
middleware: []Middleware{}, middleware: []Middleware{},
paramsBuffer: make([]string, 64),
} }
} }
// ServeHTTP implements http.Handler. // ServeHTTP implements http.Handler interface
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
h, params, ok := r.Lookup(req.Method, req.URL.Path) handler, params, found := r.Lookup(req.Method, req.URL.Path)
if !ok { if !found {
http.NotFound(w, req) http.NotFound(w, req)
return return
} }
h(w, req, params)
// Create an HTTP-specific handler wrapper
httpHandler := &httpHandler{
w: w,
r: req,
h: func(w http.ResponseWriter, r *http.Request, params []string) {
handler.Serve(params)
},
}
httpHandler.Serve(params)
} }
// Use adds middleware to the router. // httpHandler adapts net/http handlers to the router.
func (r *Router) Use(mw ...Middleware) *Router { type httpHandler struct {
r.middleware = append(r.middleware, mw...) w http.ResponseWriter
r *http.Request
h func(w http.ResponseWriter, r *http.Request, params []string)
}
// Serve executes the http handler with parameters.
func (h *httpHandler) Serve(params []string) {
h.h(h.w, h.r, params)
}
// Use adds middleware to the router's global middleware stack.
func (r *Router) Use(middleware ...Middleware) *Router {
r.middleware = append(r.middleware, middleware...)
return r return r
} }
// Group creates a new route group. // Group creates a new route group with the given path prefix.
func (r *Router) Group(prefix string) *Group { func (r *Router) Group(prefix string) *Group {
return &Group{router: r, prefix: prefix, middleware: []Middleware{}} return &Group{
router: r,
prefix: prefix,
middleware: []Middleware{},
}
} }
// Use adds middleware to the group. // Use adds middleware to the group's middleware stack.
func (g *Group) Use(mw ...Middleware) *Group { func (g *Group) Use(middleware ...Middleware) *Group {
g.middleware = append(g.middleware, mw...) g.middleware = append(g.middleware, middleware...)
return g return g
} }
// Group creates a nested group. // Group creates a nested group with an additional prefix.
func (g *Group) Group(prefix string) *Group { func (g *Group) Group(prefix string) *Group {
return &Group{router: g.router, prefix: g.prefix + prefix, middleware: slices.Clone(g.middleware)} return &Group{
router: g.router,
prefix: g.prefix + prefix,
middleware: append([]Middleware{}, g.middleware...),
}
} }
// applyMiddleware applies middleware in reverse order. // applyMiddleware wraps a handler with middleware in reverse order.
func applyMiddleware(h Handler, mw []Middleware) Handler { func applyMiddleware(handler Handler, middleware []Middleware) Handler {
for i := len(mw) - 1; i >= 0; i-- { h := handler
h = mw[i](h) for i := len(middleware) - 1; i >= 0; i-- {
h = middleware[i](h)
} }
return h return h
} }
// Handle registers a handler for the given method and path. // Get registers a handler for GET requests at the given path.
func (r *Router) Handle(method, path string, h Handler) error { func (r *Router) Get(path string, handler http.HandlerFunc) error {
root := r.methodNode(method) httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
if root == nil { handler(w, r)
return fmt.Errorf("unsupported method: %s", method)
} }
return r.addRoute(root, path, h, r.middleware)
return r.addRoute(r.get, path, &httpHandler{h: httpHandlerFunc}, r.middleware)
} }
func (r *Router) methodNode(method string) *node { // Get registers a handler with parameters for GET requests at the given path.
switch method { func (r *Router) GetParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
case "GET": return r.addRoute(r.get, path, &httpHandler{h: handler}, r.middleware)
return r.get }
case "POST":
return r.post // Post registers a handler for POST requests at the given path.
case "PUT": func (r *Router) Post(path string, handler http.HandlerFunc) error {
return r.put httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
case "PATCH": handler(w, r)
return r.patch }
case "DELETE":
return r.delete return r.addRoute(r.post, path, &httpHandler{h: httpHandlerFunc}, r.middleware)
default: }
return nil
// Post registers a handler with parameters for POST requests at the given path.
func (r *Router) PostParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
return r.addRoute(r.post, path, &httpHandler{h: handler}, r.middleware)
}
// Put registers a handler for PUT requests at the given path.
func (r *Router) Put(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
return r.addRoute(r.put, path, &httpHandler{h: httpHandlerFunc}, r.middleware)
}
// Put registers a handler with parameters for PUT requests at the given path.
func (r *Router) PutParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
return r.addRoute(r.put, path, &httpHandler{h: handler}, r.middleware)
}
// Patch registers a handler for PATCH requests at the given path.
func (r *Router) Patch(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
return r.addRoute(r.patch, path, &httpHandler{h: httpHandlerFunc}, r.middleware)
}
// Patch registers a handler with parameters for PATCH requests at the given path.
func (r *Router) PatchParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
return r.addRoute(r.patch, path, &httpHandler{h: handler}, r.middleware)
}
// Delete registers a handler for DELETE requests at the given path.
func (r *Router) Delete(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
return r.addRoute(r.delete, path, &httpHandler{h: httpHandlerFunc}, r.middleware)
}
// Delete registers a handler with parameters for DELETE requests at the given path.
func (r *Router) DeleteParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
return r.addRoute(r.delete, path, &httpHandler{h: handler}, r.middleware)
}
// Group HTTP handler registration methods
// Get registers a handler for GET requests at the given path.
func (g *Group) Get(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.get, g.prefix+path, &httpHandler{h: httpHandlerFunc}, middleware)
}
// Get registers a handler with parameters for GET requests at the given path.
func (g *Group) GetParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.get, g.prefix+path, &httpHandler{h: handler}, middleware)
}
// Post registers a handler for POST requests at the given path.
func (g *Group) Post(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.post, g.prefix+path, &httpHandler{h: httpHandlerFunc}, middleware)
}
// Post registers a handler with parameters for POST requests at the given path.
func (g *Group) PostParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.post, g.prefix+path, &httpHandler{h: handler}, middleware)
}
// Put registers a handler for PUT requests at the given path.
func (g *Group) Put(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.put, g.prefix+path, &httpHandler{h: httpHandlerFunc}, middleware)
}
// Put registers a handler with parameters for PUT requests at the given path.
func (g *Group) PutParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.put, g.prefix+path, &httpHandler{h: handler}, middleware)
}
// Patch registers a handler for PATCH requests at the given path.
func (g *Group) Patch(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.patch, g.prefix+path, &httpHandler{h: httpHandlerFunc}, middleware)
}
// Patch registers a handler with parameters for PATCH requests at the given path.
func (g *Group) PatchParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.patch, g.prefix+path, &httpHandler{h: handler}, middleware)
}
// Delete registers a handler for DELETE requests at the given path.
func (g *Group) Delete(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.delete, g.prefix+path, &httpHandler{h: httpHandlerFunc}, middleware)
}
// Delete registers a handler with parameters for DELETE requests at the given path.
func (g *Group) DeleteParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.delete, g.prefix+path, &httpHandler{h: handler}, middleware)
}
// WithMiddleware applies specific middleware to the next route registration.
func (r *Router) WithMiddleware(middleware ...Middleware) *MiddlewareRouter {
return &MiddlewareRouter{
router: r,
middleware: middleware,
} }
} }
// Get registers a GET handler. // WithMiddleware applies specific middleware to the next route registration.
func (r *Router) Get(path string, h Handler) error { func (g *Group) WithMiddleware(middleware ...Middleware) *MiddlewareGroup {
return r.Handle("GET", path, h) return &MiddlewareGroup{
} group: g,
middleware: middleware,
// Post registers a POST handler.
func (r *Router) Post(path string, h Handler) error {
return r.Handle("POST", path, h)
}
// Put registers a PUT handler.
func (r *Router) Put(path string, h Handler) error {
return r.Handle("PUT", path, h)
}
// Patch registers a PATCH handler.
func (r *Router) Patch(path string, h Handler) error {
return r.Handle("PATCH", path, h)
}
// Delete registers a DELETE handler.
func (r *Router) Delete(path string, h Handler) error {
return r.Handle("DELETE", path, h)
}
func (g *Group) buildGroupMiddleware() []Middleware {
mw := slices.Clone(g.router.middleware)
return append(mw, g.middleware...)
}
// Handle registers a handler in the group.
func (g *Group) Handle(method, path string, h Handler) error {
root := g.router.methodNode(method)
if root == nil {
return fmt.Errorf("unsupported method: %s", method)
} }
return g.router.addRoute(root, g.prefix+path, h, g.buildGroupMiddleware())
}
// Get registers a GET handler in the group.
func (g *Group) Get(path string, h Handler) error {
return g.Handle("GET", path, h)
}
// Post registers a POST handler in the group.
func (g *Group) Post(path string, h Handler) error {
return g.Handle("POST", path, h)
}
// Put registers a PUT handler in the group.
func (g *Group) Put(path string, h Handler) error {
return g.Handle("PUT", path, h)
}
// Patch registers a PATCH handler in the group.
func (g *Group) Patch(path string, h Handler) error {
return g.Handle("PATCH", path, h)
}
// Delete registers a DELETE handler in the group.
func (g *Group) Delete(path string, h Handler) error {
return g.Handle("DELETE", path, h)
}
// WithMiddleware applies specific middleware for next registration.
func (r *Router) WithMiddleware(mw ...Middleware) *MiddlewareRouter {
return &MiddlewareRouter{router: r, middleware: mw}
}
// WithMiddleware applies specific middleware for next group route.
func (g *Group) WithMiddleware(mw ...Middleware) *MiddlewareGroup {
return &MiddlewareGroup{group: g, middleware: mw}
} }
// MiddlewareRouter handles route registration with specific middleware.
type MiddlewareRouter struct { type MiddlewareRouter struct {
router *Router router *Router
middleware []Middleware middleware []Middleware
} }
// MiddlewareGroup handles group route registration with specific middleware.
type MiddlewareGroup struct { type MiddlewareGroup struct {
group *Group group *Group
middleware []Middleware middleware []Middleware
} }
func (mr *MiddlewareRouter) buildMiddleware() []Middleware { // HTTP method registration functions for MiddlewareRouter
mw := slices.Clone(mr.router.middleware)
return append(mw, mr.middleware...)
}
// Handle registers a handler with middleware router. // Get registers a handler for GET requests with specific middleware.
func (mr *MiddlewareRouter) Handle(method, path string, h Handler) error { func (mr *MiddlewareRouter) Get(path string, handler http.HandlerFunc) error {
root := mr.router.methodNode(method) httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
if root == nil { handler(w, r)
return fmt.Errorf("unsupported method: %s", method)
} }
return mr.router.addRoute(root, path, h, mr.buildMiddleware())
middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.get, path, &httpHandler{h: httpHandlerFunc}, middleware)
} }
// Get registers a GET handler with middleware router. // Get registers a handler with parameters for GET requests with specific middleware.
func (mr *MiddlewareRouter) Get(path string, h Handler) error { func (mr *MiddlewareRouter) GetParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
return mr.Handle("GET", path, h) middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.get, path, &httpHandler{h: handler}, middleware)
} }
// Post registers a POST handler with middleware router. // Post registers a handler for POST requests with specific middleware.
func (mr *MiddlewareRouter) Post(path string, h Handler) error { func (mr *MiddlewareRouter) Post(path string, handler http.HandlerFunc) error {
return mr.Handle("POST", path, h) httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
} handler(w, r)
// Put registers a PUT handler with middleware router.
func (mr *MiddlewareRouter) Put(path string, h Handler) error {
return mr.Handle("PUT", path, h)
}
// Patch registers a PATCH handler with middleware router.
func (mr *MiddlewareRouter) Patch(path string, h Handler) error {
return mr.Handle("PATCH", path, h)
}
// Delete registers a DELETE handler with middleware router.
func (mr *MiddlewareRouter) Delete(path string, h Handler) error {
return mr.Handle("DELETE", path, h)
}
func (mg *MiddlewareGroup) buildMiddleware() []Middleware {
mw := slices.Clone(mg.group.router.middleware)
mw = append(mw, mg.group.middleware...)
return append(mw, mg.middleware...)
}
// Handle registers a handler with middleware group.
func (mg *MiddlewareGroup) Handle(method, path string, h Handler) error {
root := mg.group.router.methodNode(method)
if root == nil {
return fmt.Errorf("unsupported method: %s", method)
} }
return mg.group.router.addRoute(root, mg.group.prefix+path, h, mg.buildMiddleware())
middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.post, path, &httpHandler{h: httpHandlerFunc}, middleware)
} }
// Get registers a GET handler with middleware group. // Post registers a handler with parameters for POST requests with specific middleware.
func (mg *MiddlewareGroup) Get(path string, h Handler) error { func (mr *MiddlewareRouter) PostParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
return mg.Handle("GET", path, h) middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.post, path, &httpHandler{h: handler}, middleware)
} }
// Post registers a POST handler with middleware group. // Put registers a handler for PUT requests with specific middleware.
func (mg *MiddlewareGroup) Post(path string, h Handler) error { func (mr *MiddlewareRouter) Put(path string, handler http.HandlerFunc) error {
return mg.Handle("POST", path, h) httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.put, path, &httpHandler{h: httpHandlerFunc}, middleware)
} }
// Put registers a PUT handler with middleware group. // Put registers a handler with parameters for PUT requests with specific middleware.
func (mg *MiddlewareGroup) Put(path string, h Handler) error { func (mr *MiddlewareRouter) PutParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
return mg.Handle("PUT", path, h) middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.put, path, &httpHandler{h: handler}, middleware)
} }
// Patch registers a PATCH handler with middleware group. // Patch registers a handler for PATCH requests with specific middleware.
func (mg *MiddlewareGroup) Patch(path string, h Handler) error { func (mr *MiddlewareRouter) Patch(path string, handler http.HandlerFunc) error {
return mg.Handle("PATCH", path, h) httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.patch, path, &httpHandler{h: httpHandlerFunc}, middleware)
} }
// Delete registers a DELETE handler with middleware group. // Patch registers a handler with parameters for PATCH requests with specific middleware.
func (mg *MiddlewareGroup) Delete(path string, h Handler) error { func (mr *MiddlewareRouter) PatchParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
return mg.Handle("DELETE", path, h) middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.patch, path, &httpHandler{h: handler}, middleware)
} }
// readSegment extracts the next path segment. // Delete registers a handler for DELETE requests with specific middleware.
func (mr *MiddlewareRouter) Delete(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.delete, path, &httpHandler{h: httpHandlerFunc}, middleware)
}
// Delete registers a handler with parameters for DELETE requests with specific middleware.
func (mr *MiddlewareRouter) DeleteParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.delete, path, &httpHandler{h: handler}, middleware)
}
// HTTP method registration functions for MiddlewareGroup
// Get registers a handler for GET requests with specific middleware.
func (mg *MiddlewareGroup) Get(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.get, mg.group.prefix+path, &httpHandler{h: httpHandlerFunc}, middleware)
}
// Get registers a handler with parameters for GET requests with specific middleware.
func (mg *MiddlewareGroup) GetParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.get, mg.group.prefix+path, &httpHandler{h: handler}, middleware)
}
// Post registers a handler for POST requests with specific middleware.
func (mg *MiddlewareGroup) Post(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.post, mg.group.prefix+path, &httpHandler{h: httpHandlerFunc}, middleware)
}
// Post registers a handler with parameters for POST requests with specific middleware.
func (mg *MiddlewareGroup) PostParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.post, mg.group.prefix+path, &httpHandler{h: handler}, middleware)
}
// Put registers a handler for PUT requests with specific middleware.
func (mg *MiddlewareGroup) Put(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.put, mg.group.prefix+path, &httpHandler{h: httpHandlerFunc}, middleware)
}
// Put registers a handler with parameters for PUT requests with specific middleware.
func (mg *MiddlewareGroup) PutParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.put, mg.group.prefix+path, &httpHandler{h: handler}, middleware)
}
// Patch registers a handler for PATCH requests with specific middleware.
func (mg *MiddlewareGroup) Patch(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.patch, mg.group.prefix+path, &httpHandler{h: httpHandlerFunc}, middleware)
}
// Patch registers a handler with parameters for PATCH requests with specific middleware.
func (mg *MiddlewareGroup) PatchParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.patch, mg.group.prefix+path, &httpHandler{h: handler}, middleware)
}
// Delete registers a handler for DELETE requests with specific middleware.
func (mg *MiddlewareGroup) Delete(path string, handler http.HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r)
}
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.delete, mg.group.prefix+path, &httpHandler{h: httpHandlerFunc}, middleware)
}
// Delete registers a handler with parameters for DELETE requests with specific middleware.
func (mg *MiddlewareGroup) DeleteParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.delete, mg.group.prefix+path, &httpHandler{h: handler}, middleware)
}
// readSegment extracts the next path segment starting at the given position.
// Returns the segment, the position after it, and whether there are more segments.
func readSegment(path string, start int) (segment string, end int, hasMore bool) { func readSegment(path string, start int) (segment string, end int, hasMore bool) {
if start >= len(path) { if start >= len(path) {
return "", start, false return "", start, false
} }
if path[start] == '/' { if path[start] == '/' {
start++ start++
} }
if start >= len(path) { if start >= len(path) {
return "", start, false return "", start, false
} }
end = start end = start
for end < len(path) && path[end] != '/' { for end < len(path) && path[end] != '/' {
end++ end++
} }
return path[start:end], end, end < len(path) return path[start:end], end, end < len(path)
} }
// addRoute adds a new route to the trie. // addRoute adds a new route to the prefix tree with middleware.
func (r *Router) addRoute(root *node, path string, h Handler, mw []Middleware) error { func (r *Router) addRoute(root *node, path string, handler Handler, middleware []Middleware) error {
h = applyMiddleware(h, mw) wrappedHandler := applyMiddleware(handler, middleware)
if path == "/" { if path == "/" {
root.handler = h root.handler = wrappedHandler
return nil return nil
} }
current := root current := root
pos := 0 pos := 0
lastWC := false var lastWildcard bool
count := uint8(0) paramsCount := uint8(0)
for { for {
seg, newPos, more := readSegment(path, pos) segment, newPos, hasMore := readSegment(path, pos)
if seg == "" { if segment == "" {
break break
} }
isDyn := len(seg) > 2 && seg[0] == '[' && seg[len(seg)-1] == ']'
isWC := len(seg) > 0 && seg[0] == '*' isDynamic := len(segment) > 2 && segment[0] == '[' && segment[len(segment)-1] == ']'
if isWC { isWildcard := len(segment) > 0 && segment[0] == '*'
if lastWC || more {
if isWildcard {
if lastWildcard {
return fmt.Errorf("wildcard must be the last segment in the path") return fmt.Errorf("wildcard must be the last segment in the path")
} }
lastWC = true if hasMore {
return fmt.Errorf("wildcard must be the last segment in the path")
}
lastWildcard = true
} }
if isDyn || isWC {
count++ if isDynamic || isWildcard {
paramsCount++
} }
var child *node var child *node
for _, c := range current.children { for _, n := range current.children {
if c.segment == seg { if n.segment == segment {
child = c child = n
break break
} }
} }
if child == nil { if child == nil {
child = &node{segment: seg, isDynamic: isDyn, isWildcard: isWC} child = &node{
segment: segment,
isDynamic: isDynamic,
isWildcard: isWildcard,
}
current.children = append(current.children, child) current.children = append(current.children, child)
} }
if child.maxParams < count {
child.maxParams = count if child.maxParams < paramsCount {
child.maxParams = paramsCount
} }
current = child current = child
pos = newPos pos = newPos
} }
current.handler = h
current.handler = wrappedHandler
return nil return nil
} }
// Lookup finds a handler matching method and path. // Lookup finds a handler matching the given method and path.
// Returns the handler, any captured parameters, and whether a match was found.
func (r *Router) Lookup(method, path string) (Handler, []string, bool) { func (r *Router) Lookup(method, path string) (Handler, []string, bool) {
root := r.methodNode(method) var root *node
if root == nil { switch method {
case "GET":
root = r.get
case "POST":
root = r.post
case "PUT":
root = r.put
case "PATCH":
root = r.patch
case "DELETE":
root = r.delete
default:
return nil, nil, false return nil, nil, false
} }
if path == "/" { if path == "/" {
return root.handler, nil, root.handler != nil return root.handler, nil, root.handler != nil
} }
buffer := r.paramsBuffer params := make([]string, 0, root.maxParams)
if cap(buffer) < int(root.maxParams) { h, found := match(root, path, 0, &params)
buffer = make([]string, root.maxParams)
r.paramsBuffer = buffer
}
buffer = buffer[:0]
h, paramCount, found := match(root, path, 0, &buffer)
if !found { if !found {
return nil, nil, false return nil, nil, false
} }
return h, params, true
return h, buffer[:paramCount], true
} }
// match traverses the trie to find a handler. // match recursively traverses the prefix tree to find a matching handler.
func match(current *node, path string, start int, params *[]string) (Handler, int, bool) { // It populates params with any captured path parameters or wildcard matches.
paramCount := 0 func match(current *node, path string, start int, params *[]string) (Handler, bool) {
// Check for wildcard children first
for _, c := range current.children { for _, child := range current.children {
if c.isWildcard { if child.isWildcard {
rem := path[start:] remaining := path[start:]
if len(rem) > 0 && rem[0] == '/' { if len(remaining) > 0 && remaining[0] == '/' {
rem = rem[1:] remaining = remaining[1:]
} }
*params = append(*params, rem) *params = append(*params, remaining)
return c.handler, 1, c.handler != nil return child.handler, child.handler != nil
} }
} }
seg, pos, more := readSegment(path, start) // Read current segment
if seg == "" { segment, pos, hasMore := readSegment(path, start)
return current.handler, 0, current.handler != nil if segment == "" {
return current.handler, current.handler != nil
} }
for _, c := range current.children { // Try to match children
if c.segment == seg || c.isDynamic { for _, child := range current.children {
if c.isDynamic { if child.segment == segment || child.isDynamic {
*params = append(*params, seg) if child.isDynamic {
paramCount++ *params = append(*params, segment)
} }
if !more { if !hasMore {
return c.handler, paramCount, c.handler != nil return child.handler, child.handler != nil
} }
h, nestedCount, ok := match(c, path, pos, params) if h, found := match(child, path, pos, params); found {
if ok { return h, true
return h, paramCount + nestedCount, true
} }
} }
} }
return nil, 0, false return nil, false
} }

View File

@ -2,40 +2,59 @@ package router
import ( import (
"net/http" "net/http"
"net/http/httptest"
"testing" "testing"
assert "git.sharkk.net/Go/Assert" assert "git.sharkk.net/Go/Assert"
) )
// simpleHandler implements the Handler interface
type simpleHandler struct {
fn func(params []string)
}
func (h *simpleHandler) Serve(params []string) {
h.fn(params)
}
// newHandler creates a simple Handler from a function
func newHandler(fn func(params []string)) Handler {
return &simpleHandler{fn: fn}
}
func TestRootPath(t *testing.T) { func TestRootPath(t *testing.T) {
r := New() r := New()
r.Get("/", func(w Res, r Req, params []string) {}) r.GetParam("/", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for testing
})
_, _, found := r.Lookup("GET", "/") h, params, found := r.Lookup("GET", "/")
assert.True(t, found) assert.True(t, found)
h.Serve(params)
} }
func TestStaticPath(t *testing.T) { func TestStaticPath(t *testing.T) {
r := New() r := New()
r.Get("/users/all", func(w Res, r Req, params []string) {}) r.GetParam("/users/all", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for testing
})
_, _, found := r.Lookup("GET", "/users/all") h, params, found := r.Lookup("GET", "/users/all")
assert.True(t, found) assert.True(t, found)
h.Serve(params)
} }
func TestSingleParameter(t *testing.T) { func TestSingleParameter(t *testing.T) {
r := New() r := New()
called := false called := false
r.Get("/users/[id]", func(w Res, r Req, params []string) { r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
called = true called = true
assert.Equal(t, params[0], "123") assert.Equal(t, params[0], "123")
}) })
h, params, found := r.Lookup("GET", "/users/123") h, params, found := r.Lookup("GET", "/users/123")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
assert.True(t, called) assert.True(t, called)
} }
@ -43,7 +62,7 @@ func TestMultipleParameters(t *testing.T) {
r := New() r := New()
called := false called := false
r.Get("/users/[id]/posts/[postId]", func(w Res, r Req, params []string) { r.GetParam("/users/[id]/posts/[postId]", func(w http.ResponseWriter, r *http.Request, params []string) {
called = true called = true
assert.Equal(t, params[0], "123") assert.Equal(t, params[0], "123")
assert.Equal(t, params[1], "456") assert.Equal(t, params[1], "456")
@ -51,13 +70,15 @@ func TestMultipleParameters(t *testing.T) {
h, params, found := r.Lookup("GET", "/users/123/posts/456") h, params, found := r.Lookup("GET", "/users/123/posts/456")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
assert.True(t, called) assert.True(t, called)
} }
func TestNonExistentPath(t *testing.T) { func TestNonExistentPath(t *testing.T) {
r := New() r := New()
r.Get("/users/[id]", func(w Res, r Req, params []string) {}) r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for testing
})
_, _, found := r.Lookup("GET", "/posts/123") _, _, found := r.Lookup("GET", "/posts/123")
assert.False(t, found) assert.False(t, found)
@ -65,7 +86,9 @@ func TestNonExistentPath(t *testing.T) {
func TestWrongMethod(t *testing.T) { func TestWrongMethod(t *testing.T) {
r := New() r := New()
r.Get("/users/[id]", func(w Res, r Req, params []string) {}) r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for testing
})
_, _, found := r.Lookup("POST", "/users/123") _, _, found := r.Lookup("POST", "/users/123")
assert.False(t, found) assert.False(t, found)
@ -75,27 +98,27 @@ func TestTrailingSlash(t *testing.T) {
r := New() r := New()
called := false called := false
r.Get("/users/[id]", func(w Res, r Req, params []string) { r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
called = true called = true
assert.Equal(t, params[0], "123") assert.Equal(t, params[0], "123")
}) })
h, params, found := r.Lookup("GET", "/users/123/") h, params, found := r.Lookup("GET", "/users/123/")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
assert.True(t, called) assert.True(t, called)
} }
func TestDifferentMethods(t *testing.T) { func TestDifferentMethods(t *testing.T) {
r := New() r := New()
handler := func(w Res, r Req, params []string) {} handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
r.Get("/test", handler) r.GetParam("/test", handler)
r.Post("/test", handler) r.PostParam("/test", handler)
r.Put("/test", handler) r.PutParam("/test", handler)
r.Patch("/test", handler) r.PatchParam("/test", handler)
r.Delete("/test", handler) r.DeleteParam("/test", handler)
methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"} methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"}
for _, method := range methods { for _, method := range methods {
@ -111,7 +134,7 @@ func TestWildcardPath(t *testing.T) {
t.Run("simple wildcard", func(t *testing.T) { t.Run("simple wildcard", func(t *testing.T) {
called := false called := false
err := r.Get("/files/*path", func(w Res, r Req, params []string) { err := r.GetParam("/files/*path", func(w http.ResponseWriter, r *http.Request, params []string) {
called = true called = true
assert.Equal(t, params[0], "docs/report.pdf") assert.Equal(t, params[0], "docs/report.pdf")
}) })
@ -119,13 +142,13 @@ func TestWildcardPath(t *testing.T) {
h, params, found := r.Lookup("GET", "/files/docs/report.pdf") h, params, found := r.Lookup("GET", "/files/docs/report.pdf")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
assert.True(t, called) assert.True(t, called)
}) })
t.Run("wildcard with empty path", func(t *testing.T) { t.Run("wildcard with empty path", func(t *testing.T) {
called := false called := false
err := r.Get("/download/*filepath", func(w Res, r Req, params []string) { err := r.GetParam("/download/*filepath", func(w http.ResponseWriter, r *http.Request, params []string) {
called = true called = true
assert.Equal(t, params[0], "") assert.Equal(t, params[0], "")
}) })
@ -133,13 +156,13 @@ func TestWildcardPath(t *testing.T) {
h, params, found := r.Lookup("GET", "/download/") h, params, found := r.Lookup("GET", "/download/")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
assert.True(t, called) assert.True(t, called)
}) })
t.Run("wildcard with parameter", func(t *testing.T) { t.Run("wildcard with parameter", func(t *testing.T) {
called := false called := false
err := r.Get("/users/[id]/*action", func(w Res, r Req, params []string) { err := r.GetParam("/users/[id]/*action", func(w http.ResponseWriter, r *http.Request, params []string) {
called = true called = true
assert.Equal(t, params[0], "123") assert.Equal(t, params[0], "123")
assert.Equal(t, params[1], "settings/profile/avatar") assert.Equal(t, params[1], "settings/profile/avatar")
@ -148,17 +171,17 @@ func TestWildcardPath(t *testing.T) {
h, params, found := r.Lookup("GET", "/users/123/settings/profile/avatar") h, params, found := r.Lookup("GET", "/users/123/settings/profile/avatar")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
assert.True(t, called) assert.True(t, called)
}) })
t.Run("multiple wildcards not allowed", func(t *testing.T) { t.Run("multiple wildcards not allowed", func(t *testing.T) {
err := r.Get("/api/*version/*path", func(w Res, r Req, params []string) {}) err := r.GetParam("/api/*version/*path", func(w http.ResponseWriter, r *http.Request, params []string) {})
assert.NotNil(t, err) assert.NotNil(t, err)
}) })
t.Run("non-last wildcard not allowed", func(t *testing.T) { t.Run("non-last wildcard not allowed", func(t *testing.T) {
err := r.Get("/api/*version/users", func(w Res, r Req, params []string) {}) err := r.GetParam("/api/*version/users", func(w http.ResponseWriter, r *http.Request, params []string) {})
assert.NotNil(t, err) assert.NotNil(t, err)
}) })
} }
@ -168,19 +191,21 @@ func TestMiddleware(t *testing.T) {
t.Run("global middleware", func(t *testing.T) { t.Run("global middleware", func(t *testing.T) {
r := New() r := New()
// Track middleware execution
executed := false executed := false
r.Use(func(next Handler) Handler { r.Use(func(next Handler) Handler {
return Handler(func(w Res, r Req, params []string) { return newHandler(func(params []string) {
executed = true executed = true
next(w, r, params) next.Serve(params)
}) })
}) })
r.Get("/test", func(w Res, r Req, params []string) {}) r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {})
h, params, found := r.Lookup("GET", "/test") h, params, found := r.Lookup("GET", "/test")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
assert.True(t, executed) assert.True(t, executed)
}) })
@ -191,28 +216,28 @@ func TestMiddleware(t *testing.T) {
order := []int{} order := []int{}
r.Use(func(next Handler) Handler { r.Use(func(next Handler) Handler {
return Handler(func(w Res, r Req, params []string) { return newHandler(func(params []string) {
order = append(order, 1) order = append(order, 1)
next.Serve(nil, nil, params) next.Serve(params)
order = append(order, 4) order = append(order, 4)
}) })
}) })
r.Use(func(next Handler) Handler { r.Use(func(next Handler) Handler {
return Handler(func(w Res, r Req, params []string) { return newHandler(func(params []string) {
order = append(order, 2) order = append(order, 2)
next.Serve(nil, nil, params) next.Serve(params)
order = append(order, 3) order = append(order, 3)
}) })
}) })
r.Get("/test", func(w Res, r Req, params []string) { r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {
order = append(order, 0) order = append(order, 0)
}) })
h, params, found := r.Lookup("GET", "/test") h, params, found := r.Lookup("GET", "/test")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
// Check middleware execution order (first middleware wraps second) // Check middleware execution order (first middleware wraps second)
assert.Equal(t, len(order), 5) assert.Equal(t, len(order), 5)
@ -229,17 +254,17 @@ func TestMiddleware(t *testing.T) {
executed := false executed := false
middleware := func(next Handler) Handler { middleware := func(next Handler) Handler {
return Handler(func(w Res, r Req, params []string) { return newHandler(func(params []string) {
executed = true executed = true
next.Serve(nil, nil, params) next.Serve(params)
}) })
} }
r.WithMiddleware(middleware).Get("/test", func(w Res, r Req, params []string) {}) r.WithMiddleware(middleware).GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {})
h, params, found := r.Lookup("GET", "/test") h, params, found := r.Lookup("GET", "/test")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
assert.True(t, executed) assert.True(t, executed)
}) })
} }
@ -251,11 +276,11 @@ func TestGroup(t *testing.T) {
// Create API group // Create API group
api := r.Group("/api") api := r.Group("/api")
api.Get("/users", func(w Res, r Req, params []string) {}) api.GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) {})
h, params, found := r.Lookup("GET", "/api/users") h, params, found := r.Lookup("GET", "/api/users")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
}) })
t.Run("nested groups", func(t *testing.T) { t.Run("nested groups", func(t *testing.T) {
@ -264,11 +289,11 @@ func TestGroup(t *testing.T) {
// Create nested groups // Create nested groups
api := r.Group("/api") api := r.Group("/api")
v1 := api.Group("/v1") v1 := api.Group("/v1")
v1.Get("/users", func(w Res, r Req, params []string) {}) v1.GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) {})
h, params, found := r.Lookup("GET", "/api/v1/users") h, params, found := r.Lookup("GET", "/api/v1/users")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
}) })
t.Run("group middleware", func(t *testing.T) { t.Run("group middleware", func(t *testing.T) {
@ -278,17 +303,17 @@ func TestGroup(t *testing.T) {
// Create group with middleware // Create group with middleware
api := r.Group("/api") api := r.Group("/api")
api.Use(func(next Handler) Handler { api.Use(func(next Handler) Handler {
return Handler(func(w Res, r Req, params []string) { return newHandler(func(params []string) {
executed = true executed = true
next.Serve(nil, nil, params) next.Serve(params)
}) })
}) })
api.Get("/users", func(w Res, r Req, params []string) {}) api.GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) {})
h, params, found := r.Lookup("GET", "/api/users") h, params, found := r.Lookup("GET", "/api/users")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
assert.True(t, executed) assert.True(t, executed)
}) })
@ -299,28 +324,28 @@ func TestGroup(t *testing.T) {
// Create group with middleware // Create group with middleware
api := r.Group("/api") api := r.Group("/api")
api.Use(func(next Handler) Handler { api.Use(func(next Handler) Handler {
return Handler(func(w Res, r Req, params []string) { return newHandler(func(params []string) {
order = append(order, 1) order = append(order, 1)
next.Serve(nil, nil, params) next.Serve(params)
}) })
}) })
// Create nested group with additional middleware // Create nested group with additional middleware
v1 := api.Group("/v1") v1 := api.Group("/v1")
v1.Use(func(next Handler) Handler { v1.Use(func(next Handler) Handler {
return Handler(func(w Res, r Req, params []string) { return newHandler(func(params []string) {
order = append(order, 2) order = append(order, 2)
next.Serve(nil, nil, params) next.Serve(params)
}) })
}) })
v1.Get("/users", func(w Res, r Req, params []string) { v1.GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) {
order = append(order, 3) order = append(order, 3)
}) })
h, params, found := r.Lookup("GET", "/api/v1/users") h, params, found := r.Lookup("GET", "/api/v1/users")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
// Check middleware execution order // Check middleware execution order
assert.Equal(t, len(order), 3) assert.Equal(t, len(order), 3)
@ -336,25 +361,25 @@ func TestGroup(t *testing.T) {
// Create group with middleware // Create group with middleware
api := r.Group("/api") api := r.Group("/api")
api.Use(func(next Handler) Handler { api.Use(func(next Handler) Handler {
return Handler(func(w Res, r Req, params []string) { return newHandler(func(params []string) {
order = append(order, 1) order = append(order, 1)
next.Serve(nil, nil, params) next.Serve(params)
}) })
}) })
// Add route with specific middleware // Add route with specific middleware
api.WithMiddleware(func(next Handler) Handler { api.WithMiddleware(func(next Handler) Handler {
return Handler(func(w Res, r Req, params []string) { return newHandler(func(params []string) {
order = append(order, 2) order = append(order, 2)
next.Serve(nil, nil, params) next.Serve(params)
}) })
}).Get("/users", func(w Res, r Req, params []string) { }).GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) {
order = append(order, 3) order = append(order, 3)
}) })
h, params, found := r.Lookup("GET", "/api/users") h, params, found := r.Lookup("GET", "/api/users")
assert.True(t, found) assert.True(t, found)
h.Serve(nil, nil, params) h.Serve(params)
// Check middleware execution order // Check middleware execution order
assert.Equal(t, len(order), 3) assert.Equal(t, len(order), 3)
@ -367,13 +392,13 @@ func TestGroup(t *testing.T) {
// Benchmarks // Benchmarks
func BenchmarkRouterLookup(b *testing.B) { func BenchmarkRouterLookup(b *testing.B) {
r := New() r := New()
handler := func(w Res, r Req, params []string) {} handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
// Setup routes for benchmarking // Setup routes for benchmarking
r.Get("/", handler) r.GetParam("/", handler)
r.Get("/users/all", handler) r.GetParam("/users/all", handler)
r.Get("/users/[id]", handler) r.GetParam("/users/[id]", handler)
r.Get("/users/[id]/posts/[postId]", handler) r.GetParam("/users/[id]/posts/[postId]", handler)
b.Run("root", func(b *testing.B) { b.Run("root", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -408,11 +433,11 @@ func BenchmarkRouterLookup(b *testing.B) {
func BenchmarkParallelLookup(b *testing.B) { func BenchmarkParallelLookup(b *testing.B) {
r := New() r := New()
handler := func(w Res, r Req, params []string) {} handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
r.Get("/users/[id]", handler) r.GetParam("/users/[id]", handler)
r.Get("/posts/[id]/comments", handler) r.GetParam("/posts/[id]/comments", handler)
r.Get("/products/[category]/[id]", handler) r.GetParam("/products/[category]/[id]", handler)
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
i := 0 i := 0
@ -432,11 +457,11 @@ func BenchmarkParallelLookup(b *testing.B) {
func BenchmarkWildcardLookup(b *testing.B) { func BenchmarkWildcardLookup(b *testing.B) {
r := New() r := New()
handler := func(w Res, r Req, params []string) {} handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
// Setup routes for benchmarking // Setup routes for benchmarking
r.Get("/files/*path", handler) r.GetParam("/files/*path", handler)
r.Get("/users/[id]/*action", handler) r.GetParam("/users/[id]/*action", handler)
b.Run("simple_wildcard", func(b *testing.B) { b.Run("simple_wildcard", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -453,55 +478,55 @@ func BenchmarkWildcardLookup(b *testing.B) {
func BenchmarkMiddleware(b *testing.B) { func BenchmarkMiddleware(b *testing.B) {
passthrough := func(next Handler) Handler { passthrough := func(next Handler) Handler {
return Handler(func(w Res, r Req, params []string) { return newHandler(func(params []string) {
next.Serve(nil, nil, params) next.Serve(params)
}) })
} }
b.Run("no_middleware", func(b *testing.B) { b.Run("no_middleware", func(b *testing.B) {
r := New() r := New()
r.Get("/test", func(w Res, r Req, params []string) {}) r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {})
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
h, params, _ := r.Lookup("GET", "/test") h, params, _ := r.Lookup("GET", "/test")
h.Serve(nil, nil, params) h.Serve(params)
} }
}) })
b.Run("one_middleware", func(b *testing.B) { b.Run("one_middleware", func(b *testing.B) {
r := New() r := New()
r.Use(passthrough) r.Use(passthrough)
r.Get("/test", func(w Res, r Req, params []string) {}) r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {})
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
h, params, _ := r.Lookup("GET", "/test") h, params, _ := r.Lookup("GET", "/test")
h.Serve(nil, nil, params) h.Serve(params)
} }
}) })
b.Run("five_middleware", func(b *testing.B) { b.Run("five_middleware", func(b *testing.B) {
r := New() r := New()
for range 5 { for i := 0; i < 5; i++ {
r.Use(passthrough) r.Use(passthrough)
} }
r.Get("/test", func(w Res, r Req, params []string) {}) r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {})
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
h, params, _ := r.Lookup("GET", "/test") h, params, _ := r.Lookup("GET", "/test")
h.Serve(nil, nil, params) h.Serve(params)
} }
}) })
} }
func BenchmarkGroups(b *testing.B) { func BenchmarkGroups(b *testing.B) {
handler := func(w Res, r Req, params []string) {} handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
b.Run("flat_route", func(b *testing.B) { b.Run("flat_route", func(b *testing.B) {
r := New() r := New()
r.Get("/api/v1/users", handler) r.GetParam("/api/v1/users", handler)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -513,7 +538,7 @@ func BenchmarkGroups(b *testing.B) {
r := New() r := New()
api := r.Group("/api") api := r.Group("/api")
v1 := api.Group("/v1") v1 := api.Group("/v1")
v1.Get("/users", handler) v1.GetParam("/users", handler)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -525,79 +550,17 @@ func BenchmarkGroups(b *testing.B) {
r := New() r := New()
api := r.Group("/api") api := r.Group("/api")
api.Use(func(next Handler) Handler { api.Use(func(next Handler) Handler {
return Handler(func(w Res, r Req, params []string) { return newHandler(func(params []string) {
next.Serve(nil, nil, params) next.Serve(params)
}) })
}) })
v1 := api.Group("/v1") v1 := api.Group("/v1")
v1.Get("/users", handler) v1.GetParam("/users", handler)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
h, params, _ := r.Lookup("GET", "/api/v1/users") h, params, _ := r.Lookup("GET", "/api/v1/users")
h.Serve(nil, nil, params) h.Serve(params)
}
})
}
func BenchmarkNetHttpServer(b *testing.B) {
r := New()
// Set up test routes
r.Get("/", func(w Res, r Req, params []string) {
// No-op for benchmarking
})
r.Get("/users/all", func(w Res, r Req, params []string) {
// No-op for benchmarking
})
r.Get("/users/[id]", func(w Res, r Req, params []string) {
// No-op for benchmarking
})
// Create test server
server := httptest.NewServer(r)
defer server.Close()
// Create HTTP client
client := &http.Client{
Transport: &http.Transport{
DisableKeepAlives: false,
},
}
b.Run("root", func(b *testing.B) {
url := server.URL + "/"
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := client.Get(url)
if err != nil {
b.Fatal(err)
}
resp.Body.Close()
}
})
b.Run("static_path", func(b *testing.B) {
url := server.URL + "/users/all"
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := client.Get(url)
if err != nil {
b.Fatal(err)
}
resp.Body.Close()
}
})
b.Run("param_path", func(b *testing.B) {
url := server.URL + "/users/123"
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := client.Get(url)
if err != nil {
b.Fatal(err)
}
resp.Body.Close()
} }
}) })
} }
@ -605,18 +568,18 @@ func BenchmarkNetHttpServer(b *testing.B) {
func BenchmarkComparison(b *testing.B) { func BenchmarkComparison(b *testing.B) {
// Custom router setup // Custom router setup
customRouter := New() customRouter := New()
handler := func(w Res, r Req, params []string) {} handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
customRouter.Get("/", handler) customRouter.GetParam("/", handler)
customRouter.Get("/users/all", handler) customRouter.GetParam("/users/all", handler)
customRouter.Get("/users/[id]", handler) customRouter.GetParam("/users/[id]", handler)
customRouter.Get("/users/[id]/posts/[postId]", handler) customRouter.GetParam("/users/[id]/posts/[postId]", handler)
// Standard mux setup // Standard mux setup
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/", func(w Res, r Req) {}) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
mux.HandleFunc("/users/all", func(w Res, r Req) {}) mux.HandleFunc("/users/all", func(w http.ResponseWriter, r *http.Request) {})
mux.HandleFunc("/users/", func(w Res, r Req) {}) // Best equivalent for dynamic routes mux.HandleFunc("/users/", func(w http.ResponseWriter, r *http.Request) {}) // Best equivalent for dynamic routes
// Root path // Root path
b.Run("root_path", func(b *testing.B) { b.Run("root_path", func(b *testing.B) {