rewrite 4

This commit is contained in:
Sky Johnson 2025-04-26 15:11:28 -05:00
parent de416933e8
commit 2212ea757c
3 changed files with 260 additions and 410 deletions

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
- Adapters for net/http and fasthttp (optional) - Implements `http.Handler` for direct integration
- 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,26 +27,34 @@ go get git.sharkk.net/Go/Router
r := router.New() r := router.New()
// Static routes // Static routes
r.Get("/", router.FuncHandler(func(params []string) { r.Get("/", func(w http.ResponseWriter, r *http.Request, params []string) {
fmt.Println("Root handler") fmt.Fprintf(w, "Root handler")
})) })
// Parameter routes // Parameter routes
r.Get("/users/[id]", router.FuncHandler(func(params []string) { r.Get("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
userID := params[0] userID := params[0]
fmt.Printf("User ID: %s\n", userID) fmt.Fprintf(w, "User ID: %s", userID)
})) })
// Wildcard routes // Wildcard routes
r.Get("/files/*path", router.FuncHandler(func(params []string) { r.Get("/files/*path", func(w http.ResponseWriter, r *http.Request, params []string) {
filePath := params[0] filePath := params[0]
fmt.Printf("File path: %s\n", filePath) fmt.Fprintf(w, "File path: %s", filePath)
})
// Standard http.HandlerFunc adapter
r.Get("/simple", router.StandardHandler(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Simple handler without params")
})) }))
// Lookup routes // Lookup routes manually
if handler, params, ok := r.Lookup("GET", "/users/123"); ok { if handler, params, ok := r.Lookup("GET", "/users/123"); ok {
handler.Serve(params) handler.Serve(params)
} }
// Use listen and serve directly
http.ListenAndServe(":8080", r)
``` ```
### Middleware ### Middleware
@ -54,11 +62,13 @@ if handler, params, ok := r.Lookup("GET", "/users/123"); ok {
```go ```go
// Create logging middleware // Create logging middleware
func LoggingMiddleware(next router.Handler) router.Handler { func LoggingMiddleware(next router.Handler) router.Handler {
return router.FuncHandler(func(params []string) { return &router.simpleHandler{
fn: func(params []string) {
fmt.Println("Request started") fmt.Println("Request started")
next.Serve(params) next.Serve(params)
fmt.Println("Request completed") fmt.Println("Request completed")
}) },
}
} }
// Apply middleware globally // Apply middleware globally
@ -67,6 +77,8 @@ 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
@ -87,59 +99,13 @@ 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 Integration http.ListenAndServe(":8080", r)
```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 results comparing our router to the standard `http.ServeMux` on AMD Ryzen 9 7950X: Benchmark results comparing Router to the standard `http.ServeMux`:
``` ```
cpu: AMD Ryzen 9 7950X 16-Core Processor cpu: AMD Ryzen 9 7950X 16-Core Processor
@ -161,7 +127,6 @@ Router: 10.580 ns/op 0 B/op 0 allocs/op
ServeMux: 178.100 ns/op 56 B/op 3 allocs/op ServeMux: 178.100 ns/op 56 B/op 3 allocs/op
``` ```
Key Performance Points:
- Root path lookups are 15x faster - Root path lookups are 15x faster
- Static paths are 4x faster with zero allocations - Static paths are 4x faster with zero allocations
- Dynamic paths are 4.4x faster with fewer allocations - Dynamic paths are 4.4x faster with fewer allocations

418
router.go
View File

@ -3,6 +3,7 @@ package router
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"slices"
) )
// Handler is an interface for handling HTTP requests with path parameters. // Handler is an interface for handling HTTP requests with path parameters.
@ -111,7 +112,7 @@ func (g *Group) Group(prefix string) *Group {
return &Group{ return &Group{
router: g.router, router: g.router,
prefix: g.prefix + prefix, prefix: g.prefix + prefix,
middleware: append([]Middleware{}, g.middleware...), middleware: slices.Clone(g.middleware),
} }
} }
@ -124,166 +125,101 @@ func applyMiddleware(handler Handler, middleware []Middleware) Handler {
return h return h
} }
// HandlerFunc is a function that handles HTTP requests with parameters.
type HandlerFunc func(w http.ResponseWriter, r *http.Request, params []string)
// Handle registers a handler for the given method and path.
func (r *Router) Handle(method, path string, handler HandlerFunc) error {
root := r.methodNode(method)
if root == nil {
return fmt.Errorf("unsupported method: %s", method)
}
return r.addRoute(root, path, &httpHandler{h: handler}, r.middleware)
}
// methodNode returns the root node for the given HTTP method.
func (r *Router) methodNode(method string) *node {
switch method {
case "GET":
return r.get
case "POST":
return r.post
case "PUT":
return r.put
case "PATCH":
return r.patch
case "DELETE":
return r.delete
default:
return nil
}
}
// Get registers a handler for GET requests at the given path. // Get registers a handler for GET requests at the given path.
func (r *Router) Get(path string, handler http.HandlerFunc) error { func (r *Router) Get(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return r.Handle("GET", path, handler)
handler(w, r)
}
return r.addRoute(r.get, path, &httpHandler{h: httpHandlerFunc}, r.middleware)
}
// Get registers a handler with parameters for GET requests at the given path.
func (r *Router) GetParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
return r.addRoute(r.get, path, &httpHandler{h: handler}, r.middleware)
} }
// Post registers a handler for POST requests at the given path. // Post registers a handler for POST requests at the given path.
func (r *Router) Post(path string, handler http.HandlerFunc) error { func (r *Router) Post(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return r.Handle("POST", path, handler)
handler(w, r)
}
return r.addRoute(r.post, path, &httpHandler{h: httpHandlerFunc}, r.middleware)
}
// 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. // Put registers a handler for PUT requests at the given path.
func (r *Router) Put(path string, handler http.HandlerFunc) error { func (r *Router) Put(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return r.Handle("PUT", path, handler)
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. // Patch registers a handler for PATCH requests at the given path.
func (r *Router) Patch(path string, handler http.HandlerFunc) error { func (r *Router) Patch(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return r.Handle("PATCH", path, handler)
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. // Delete registers a handler for DELETE requests at the given path.
func (r *Router) Delete(path string, handler http.HandlerFunc) error { func (r *Router) Delete(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return r.Handle("DELETE", path, handler)
handler(w, r)
} }
return r.addRoute(r.delete, path, &httpHandler{h: httpHandlerFunc}, r.middleware) // buildGroupMiddleware returns combined middleware for the group
func (g *Group) buildGroupMiddleware() []Middleware {
middleware := slices.Clone(g.router.middleware)
return append(middleware, g.middleware...)
} }
// Delete registers a handler with parameters for DELETE requests at the given path. // Handle registers a handler for the given method and path.
func (r *Router) DeleteParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error { func (g *Group) Handle(method, path string, handler HandlerFunc) error {
return r.addRoute(r.delete, path, &httpHandler{h: handler}, r.middleware) root := g.router.methodNode(method)
if root == nil {
return fmt.Errorf("unsupported method: %s", method)
} }
// Group HTTP handler registration methods fullPath := g.prefix + path
return g.router.addRoute(root, fullPath, &httpHandler{h: handler}, g.buildGroupMiddleware())
}
// Get registers a handler for GET requests at the given path. // Get registers a handler for GET requests at the given path.
func (g *Group) Get(path string, handler http.HandlerFunc) error { func (g *Group) Get(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return g.Handle("GET", path, handler)
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. // Post registers a handler for POST requests at the given path.
func (g *Group) Post(path string, handler http.HandlerFunc) error { func (g *Group) Post(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return g.Handle("POST", path, handler)
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. // Put registers a handler for PUT requests at the given path.
func (g *Group) Put(path string, handler http.HandlerFunc) error { func (g *Group) Put(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return g.Handle("PUT", path, handler)
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. // Patch registers a handler for PATCH requests at the given path.
func (g *Group) Patch(path string, handler http.HandlerFunc) error { func (g *Group) Patch(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return g.Handle("PATCH", path, handler)
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. // Delete registers a handler for DELETE requests at the given path.
func (g *Group) Delete(path string, handler http.HandlerFunc) error { func (g *Group) Delete(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return g.Handle("DELETE", path, handler)
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. // WithMiddleware applies specific middleware to the next route registration.
@ -314,198 +250,95 @@ type MiddlewareGroup struct {
middleware []Middleware middleware []Middleware
} }
// HTTP method registration functions for MiddlewareRouter // buildMiddleware returns combined middleware for the middleware router
func (mr *MiddlewareRouter) buildMiddleware() []Middleware {
middleware := slices.Clone(mr.router.middleware)
return append(middleware, mr.middleware...)
}
// Handle registers a handler for the given method and path.
func (mr *MiddlewareRouter) Handle(method, path string, handler HandlerFunc) error {
root := mr.router.methodNode(method)
if root == nil {
return fmt.Errorf("unsupported method: %s", method)
}
return mr.router.addRoute(root, path, &httpHandler{h: handler}, mr.buildMiddleware())
}
// Get registers a handler for GET requests with specific middleware. // Get registers a handler for GET requests with specific middleware.
func (mr *MiddlewareRouter) Get(path string, handler http.HandlerFunc) error { func (mr *MiddlewareRouter) Get(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return mr.Handle("GET", path, handler)
handler(w, r)
}
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 handler with parameters for GET requests with specific middleware.
func (mr *MiddlewareRouter) GetParam(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.get, path, &httpHandler{h: handler}, middleware)
} }
// Post registers a handler for POST requests with specific middleware. // Post registers a handler for POST requests with specific middleware.
func (mr *MiddlewareRouter) Post(path string, handler http.HandlerFunc) error { func (mr *MiddlewareRouter) Post(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return mr.Handle("POST", path, handler)
handler(w, r)
}
middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.post, path, &httpHandler{h: httpHandlerFunc}, middleware)
}
// Post registers a handler with parameters for POST requests with specific middleware.
func (mr *MiddlewareRouter) PostParam(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.post, path, &httpHandler{h: handler}, middleware)
} }
// Put registers a handler for PUT requests with specific middleware. // Put registers a handler for PUT requests with specific middleware.
func (mr *MiddlewareRouter) Put(path string, handler http.HandlerFunc) error { func (mr *MiddlewareRouter) Put(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return mr.Handle("PUT", path, handler)
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 handler with parameters for PUT requests with specific middleware.
func (mr *MiddlewareRouter) PutParam(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.put, path, &httpHandler{h: handler}, middleware)
} }
// Patch registers a handler for PATCH requests with specific middleware. // Patch registers a handler for PATCH requests with specific middleware.
func (mr *MiddlewareRouter) Patch(path string, handler http.HandlerFunc) error { func (mr *MiddlewareRouter) Patch(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return mr.Handle("PATCH", path, handler)
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)
}
// Patch registers a handler with parameters for PATCH requests with specific middleware.
func (mr *MiddlewareRouter) PatchParam(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.patch, path, &httpHandler{h: handler}, middleware)
} }
// Delete registers a handler for DELETE requests with specific middleware. // Delete registers a handler for DELETE requests with specific middleware.
func (mr *MiddlewareRouter) Delete(path string, handler http.HandlerFunc) error { func (mr *MiddlewareRouter) Delete(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return mr.Handle("DELETE", path, handler)
handler(w, r)
} }
middleware := append([]Middleware{}, mr.router.middleware...) // buildMiddleware returns combined middleware for the middleware group
middleware = append(middleware, mr.middleware...) func (mg *MiddlewareGroup) buildMiddleware() []Middleware {
return mr.router.addRoute(mr.router.delete, path, &httpHandler{h: httpHandlerFunc}, middleware) middleware := slices.Clone(mg.group.router.middleware)
middleware = append(middleware, mg.group.middleware...)
return append(middleware, mg.middleware...)
} }
// Delete registers a handler with parameters for DELETE requests with specific middleware. // Handle registers a handler for the given method and path.
func (mr *MiddlewareRouter) DeleteParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error { func (mg *MiddlewareGroup) Handle(method, path string, handler HandlerFunc) error {
middleware := append([]Middleware{}, mr.router.middleware...) root := mg.group.router.methodNode(method)
middleware = append(middleware, mr.middleware...) if root == nil {
return mr.router.addRoute(mr.router.delete, path, &httpHandler{h: handler}, middleware) return fmt.Errorf("unsupported method: %s", method)
} }
// HTTP method registration functions for MiddlewareGroup fullPath := mg.group.prefix + path
return mg.group.router.addRoute(root, fullPath, &httpHandler{h: handler}, mg.buildMiddleware())
}
// Get registers a handler for GET requests with specific middleware. // Get registers a handler for GET requests with specific middleware.
func (mg *MiddlewareGroup) Get(path string, handler http.HandlerFunc) error { func (mg *MiddlewareGroup) Get(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return mg.Handle("GET", path, handler)
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. // Post registers a handler for POST requests with specific middleware.
func (mg *MiddlewareGroup) Post(path string, handler http.HandlerFunc) error { func (mg *MiddlewareGroup) Post(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return mg.Handle("POST", path, handler)
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. // Put registers a handler for PUT requests with specific middleware.
func (mg *MiddlewareGroup) Put(path string, handler http.HandlerFunc) error { func (mg *MiddlewareGroup) Put(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return mg.Handle("PUT", path, handler)
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. // Patch registers a handler for PATCH requests with specific middleware.
func (mg *MiddlewareGroup) Patch(path string, handler http.HandlerFunc) error { func (mg *MiddlewareGroup) Patch(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return mg.Handle("PATCH", path, handler)
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. // Delete registers a handler for DELETE requests with specific middleware.
func (mg *MiddlewareGroup) Delete(path string, handler http.HandlerFunc) error { func (mg *MiddlewareGroup) Delete(path string, handler HandlerFunc) error {
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) { return mg.Handle("DELETE", path, handler)
}
// Adapter for standard http.HandlerFunc
func StandardHandler(handler http.HandlerFunc) HandlerFunc {
return func(w http.ResponseWriter, r *http.Request, _ []string) {
handler(w, r) 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. // readSegment extracts the next path segment starting at the given position.
@ -599,24 +432,13 @@ func (r *Router) addRoute(root *node, path string, handler Handler, middleware [
// Lookup finds a handler matching the given 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. // 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) {
var root *node root := r.methodNode(method)
switch method { if root == nil {
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, []string{}, root.handler != nil
} }
params := make([]string, 0, root.maxParams) params := make([]string, 0, root.maxParams)

View File

@ -2,6 +2,7 @@ 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"
@ -23,7 +24,7 @@ func newHandler(fn func(params []string)) Handler {
func TestRootPath(t *testing.T) { func TestRootPath(t *testing.T) {
r := New() r := New()
r.GetParam("/", func(w http.ResponseWriter, r *http.Request, params []string) { r.Get("/", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for testing // No-op for testing
}) })
@ -34,7 +35,7 @@ func TestRootPath(t *testing.T) {
func TestStaticPath(t *testing.T) { func TestStaticPath(t *testing.T) {
r := New() r := New()
r.GetParam("/users/all", func(w http.ResponseWriter, r *http.Request, params []string) { r.Get("/users/all", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for testing // No-op for testing
}) })
@ -47,7 +48,7 @@ func TestSingleParameter(t *testing.T) {
r := New() r := New()
called := false called := false
r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) { r.Get("/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")
}) })
@ -62,7 +63,7 @@ func TestMultipleParameters(t *testing.T) {
r := New() r := New()
called := false called := false
r.GetParam("/users/[id]/posts/[postId]", func(w http.ResponseWriter, r *http.Request, params []string) { r.Get("/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")
@ -76,7 +77,7 @@ func TestMultipleParameters(t *testing.T) {
func TestNonExistentPath(t *testing.T) { func TestNonExistentPath(t *testing.T) {
r := New() r := New()
r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) { r.Get("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for testing // No-op for testing
}) })
@ -86,7 +87,7 @@ func TestNonExistentPath(t *testing.T) {
func TestWrongMethod(t *testing.T) { func TestWrongMethod(t *testing.T) {
r := New() r := New()
r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) { r.Get("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for testing // No-op for testing
}) })
@ -98,7 +99,7 @@ func TestTrailingSlash(t *testing.T) {
r := New() r := New()
called := false called := false
r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) { r.Get("/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")
}) })
@ -114,11 +115,11 @@ func TestDifferentMethods(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request, params []string) {} handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
r.GetParam("/test", handler) r.Get("/test", handler)
r.PostParam("/test", handler) r.Post("/test", handler)
r.PutParam("/test", handler) r.Put("/test", handler)
r.PatchParam("/test", handler) r.Patch("/test", handler)
r.DeleteParam("/test", handler) r.Delete("/test", handler)
methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"} methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"}
for _, method := range methods { for _, method := range methods {
@ -134,7 +135,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.GetParam("/files/*path", func(w http.ResponseWriter, r *http.Request, params []string) { err := r.Get("/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")
}) })
@ -148,7 +149,7 @@ func TestWildcardPath(t *testing.T) {
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.GetParam("/download/*filepath", func(w http.ResponseWriter, r *http.Request, params []string) { err := r.Get("/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], "")
}) })
@ -162,7 +163,7 @@ func TestWildcardPath(t *testing.T) {
t.Run("wildcard with parameter", func(t *testing.T) { t.Run("wildcard with parameter", func(t *testing.T) {
called := false called := false
err := r.GetParam("/users/[id]/*action", func(w http.ResponseWriter, r *http.Request, params []string) { err := r.Get("/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")
@ -176,12 +177,12 @@ func TestWildcardPath(t *testing.T) {
}) })
t.Run("multiple wildcards not allowed", func(t *testing.T) { t.Run("multiple wildcards not allowed", func(t *testing.T) {
err := r.GetParam("/api/*version/*path", func(w http.ResponseWriter, r *http.Request, params []string) {}) err := r.Get("/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.GetParam("/api/*version/users", func(w http.ResponseWriter, r *http.Request, params []string) {}) err := r.Get("/api/*version/users", func(w http.ResponseWriter, r *http.Request, params []string) {})
assert.NotNil(t, err) assert.NotNil(t, err)
}) })
} }
@ -201,7 +202,7 @@ func TestMiddleware(t *testing.T) {
}) })
}) })
r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {}) r.Get("/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)
@ -231,7 +232,7 @@ func TestMiddleware(t *testing.T) {
}) })
}) })
r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) { r.Get("/test", func(w http.ResponseWriter, r *http.Request, params []string) {
order = append(order, 0) order = append(order, 0)
}) })
@ -260,7 +261,7 @@ func TestMiddleware(t *testing.T) {
}) })
} }
r.WithMiddleware(middleware).GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {}) r.WithMiddleware(middleware).Get("/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)
@ -276,7 +277,7 @@ func TestGroup(t *testing.T) {
// Create API group // Create API group
api := r.Group("/api") api := r.Group("/api")
api.GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) {}) api.Get("/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)
@ -289,7 +290,7 @@ 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.GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) {}) v1.Get("/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)
@ -309,7 +310,7 @@ func TestGroup(t *testing.T) {
}) })
}) })
api.GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) {}) api.Get("/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)
@ -339,7 +340,7 @@ func TestGroup(t *testing.T) {
}) })
}) })
v1.GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) { v1.Get("/users", func(w http.ResponseWriter, r *http.Request, params []string) {
order = append(order, 3) order = append(order, 3)
}) })
@ -373,7 +374,7 @@ func TestGroup(t *testing.T) {
order = append(order, 2) order = append(order, 2)
next.Serve(params) next.Serve(params)
}) })
}).GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) { }).Get("/users", func(w http.ResponseWriter, r *http.Request, params []string) {
order = append(order, 3) order = append(order, 3)
}) })
@ -395,10 +396,10 @@ func BenchmarkRouterLookup(b *testing.B) {
handler := func(w http.ResponseWriter, r *http.Request, params []string) {} handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
// Setup routes for benchmarking // Setup routes for benchmarking
r.GetParam("/", handler) r.Get("/", handler)
r.GetParam("/users/all", handler) r.Get("/users/all", handler)
r.GetParam("/users/[id]", handler) r.Get("/users/[id]", handler)
r.GetParam("/users/[id]/posts/[postId]", handler) r.Get("/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++ {
@ -435,9 +436,9 @@ func BenchmarkParallelLookup(b *testing.B) {
r := New() r := New()
handler := func(w http.ResponseWriter, r *http.Request, params []string) {} handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
r.GetParam("/users/[id]", handler) r.Get("/users/[id]", handler)
r.GetParam("/posts/[id]/comments", handler) r.Get("/posts/[id]/comments", handler)
r.GetParam("/products/[category]/[id]", handler) r.Get("/products/[category]/[id]", handler)
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
i := 0 i := 0
@ -460,8 +461,8 @@ func BenchmarkWildcardLookup(b *testing.B) {
handler := func(w http.ResponseWriter, r *http.Request, params []string) {} handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
// Setup routes for benchmarking // Setup routes for benchmarking
r.GetParam("/files/*path", handler) r.Get("/files/*path", handler)
r.GetParam("/users/[id]/*action", handler) r.Get("/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++ {
@ -485,7 +486,7 @@ func BenchmarkMiddleware(b *testing.B) {
b.Run("no_middleware", func(b *testing.B) { b.Run("no_middleware", func(b *testing.B) {
r := New() r := New()
r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {}) r.Get("/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++ {
@ -497,7 +498,7 @@ func BenchmarkMiddleware(b *testing.B) {
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.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {}) r.Get("/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++ {
@ -511,7 +512,7 @@ func BenchmarkMiddleware(b *testing.B) {
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
r.Use(passthrough) r.Use(passthrough)
} }
r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {}) r.Get("/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++ {
@ -526,7 +527,7 @@ func BenchmarkGroups(b *testing.B) {
b.Run("flat_route", func(b *testing.B) { b.Run("flat_route", func(b *testing.B) {
r := New() r := New()
r.GetParam("/api/v1/users", handler) r.Get("/api/v1/users", handler)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -538,7 +539,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.GetParam("/users", handler) v1.Get("/users", handler)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -555,7 +556,7 @@ func BenchmarkGroups(b *testing.B) {
}) })
}) })
v1 := api.Group("/v1") v1 := api.Group("/v1")
v1.GetParam("/users", handler) v1.Get("/users", handler)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -565,15 +566,77 @@ func BenchmarkGroups(b *testing.B) {
}) })
} }
func BenchmarkNetHttpServer(b *testing.B) {
r := New()
// Set up test routes
r.Get("/", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for benchmarking
})
r.Get("/users/all", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for benchmarking
})
r.Get("/users/[id]", func(w http.ResponseWriter, r *http.Request, 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()
}
})
}
func BenchmarkComparison(b *testing.B) { func BenchmarkComparison(b *testing.B) {
// Custom router setup // Custom router setup
customRouter := New() customRouter := New()
handler := func(w http.ResponseWriter, r *http.Request, params []string) {} handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
customRouter.GetParam("/", handler) customRouter.Get("/", handler)
customRouter.GetParam("/users/all", handler) customRouter.Get("/users/all", handler)
customRouter.GetParam("/users/[id]", handler) customRouter.Get("/users/[id]", handler)
customRouter.GetParam("/users/[id]/posts/[postId]", handler) customRouter.Get("/users/[id]/posts/[postId]", handler)
// Standard mux setup // Standard mux setup
mux := http.NewServeMux() mux := http.NewServeMux()