Compare commits
No commits in common. "master" and "1.0.0" have entirely different histories.
155
DOCS.md
155
DOCS.md
@ -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
|
|
276
EXAMPLES.md
276
EXAMPLES.md
@ -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
110
README.md
@ -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
765
router.go
@ -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, ¶ms)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
281
router_test.go
281
router_test.go
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user