Compare commits
No commits in common. "de416933e803191572e46d1c5122aae612f9875c" and "5791042454d85089cfd91efdb23b6f1ed63c8828" have entirely different histories.
de416933e8
...
5791042454
18
LICENSE
18
LICENSE
@ -1,17 +1,9 @@
|
||||
# Sharkk Minimal License
|
||||
MIT License
|
||||
|
||||
Copyright © 2025 Sharkk, sharkk.net
|
||||
Copyright (c) 2024 Sharkk
|
||||
|
||||
You can freely use and modify this software if you follow these simple rules:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
1. **Share-Alike**: When sharing modified versions, use this same license.
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
2. **Share Source Code**: When distributing this software, make the source code available.
|
||||
|
||||
3. **No Attribution Needed**: You don't need to credit the original authors publicly, but all copyright notices within the source code must remain intact.
|
||||
|
||||
4. **Patent Protection**: Contributors won't sue you for patent infringement on this software.
|
||||
|
||||
5. **No Warranty**: This software has no guarantees. You use it at your own risk.
|
||||
|
||||
6. **No Liability**: The authors aren't responsible for any damages or problems that might happen when you use this software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
128
README.md
128
README.md
@ -1,14 +1,11 @@
|
||||
# Router
|
||||
|
||||
A high-performance router for Go with support for path parameters, wildcards, middleware, and route grouping. Sports an incredibly simple API over the top of a prefix tree data structure. Minimal allocations and, dare we say, 🔥*blazingly* fast🔥.
|
||||
A high-performance router for Go with support for path parameters and wildcards. Sports an incredibly simple API over the top of a prefix tree data structure. Minimal allocations and, dare we say, 🔥*blazingly* fast🔥.
|
||||
|
||||
## Features
|
||||
- Zero dependencies
|
||||
- Fast path matching with radix tree structure
|
||||
- Support for path parameters (`[id]`) and wildcards (`*path`)
|
||||
- Middleware support for request processing pipelines
|
||||
- Route grouping with shared prefixes and middleware
|
||||
- Adapters for net/http and fasthttp (optional)
|
||||
- Up to 15x faster than `http.ServeMux` for dynamic routes
|
||||
- Zero allocations for static routes
|
||||
|
||||
@ -20,120 +17,41 @@ go get git.sharkk.net/Go/Router
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Routing
|
||||
|
||||
```go
|
||||
// Create a new router
|
||||
r := router.New()
|
||||
type Handler func(params []string)
|
||||
|
||||
router := router.New()
|
||||
|
||||
// Static routes
|
||||
r.Get("/", router.FuncHandler(func(params []string) {
|
||||
router.Get("/", func(params []string) {
|
||||
fmt.Println("Root handler")
|
||||
}))
|
||||
})
|
||||
router.Get("/users/all", func(params []string) {
|
||||
fmt.Println("All users")
|
||||
})
|
||||
|
||||
// Parameter routes
|
||||
r.Get("/users/[id]", router.FuncHandler(func(params []string) {
|
||||
router.Get("/users/[id]", func(params []string) {
|
||||
userID := params[0]
|
||||
fmt.Printf("User ID: %s\n", userID)
|
||||
}))
|
||||
})
|
||||
|
||||
// Nested parameters
|
||||
router.Get("/users/[id]/posts/[postId]", func(params []string) {
|
||||
userID := params[0]
|
||||
postID := params[1]
|
||||
fmt.Printf("User %s, Post %s\n", userID, postID)
|
||||
})
|
||||
|
||||
// Wildcard routes
|
||||
r.Get("/files/*path", router.FuncHandler(func(params []string) {
|
||||
router.Get("/files/*path", func(params []string) {
|
||||
filePath := params[0]
|
||||
fmt.Printf("File path: %s\n", filePath)
|
||||
}))
|
||||
})
|
||||
|
||||
// Lookup routes
|
||||
if handler, params, ok := r.Lookup("GET", "/users/123"); ok {
|
||||
handler.Serve(params)
|
||||
}
|
||||
```
|
||||
|
||||
### Middleware
|
||||
|
||||
```go
|
||||
// Create logging middleware
|
||||
func LoggingMiddleware(next router.Handler) router.Handler {
|
||||
return router.FuncHandler(func(params []string) {
|
||||
fmt.Println("Request started")
|
||||
next.Serve(params)
|
||||
fmt.Println("Request completed")
|
||||
})
|
||||
}
|
||||
|
||||
// Apply middleware globally
|
||||
r := router.New()
|
||||
r.Use(LoggingMiddleware)
|
||||
|
||||
// Apply middleware to specific routes
|
||||
r.WithMiddleware(AuthMiddleware).Get("/admin", adminHandler)
|
||||
```
|
||||
|
||||
### Route Groups
|
||||
|
||||
```go
|
||||
// Create a router
|
||||
r := router.New()
|
||||
|
||||
// Create an API group
|
||||
api := r.Group("/api")
|
||||
api.Get("/users", listUsersHandler) // matches /api/users
|
||||
|
||||
// Nested groups
|
||||
v1 := api.Group("/v1")
|
||||
v1.Get("/products", listProductsHandler) // matches /api/v1/products
|
||||
|
||||
// Group with middleware
|
||||
admin := api.Group("/admin")
|
||||
admin.Use(AuthMiddleware)
|
||||
admin.Get("/stats", statsHandler) // matches /api/admin/stats
|
||||
```
|
||||
|
||||
### 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)
|
||||
if handler, params, ok := router.Lookup("GET", "/users/123"); ok {
|
||||
handler(params)
|
||||
}
|
||||
```
|
||||
|
||||
@ -169,4 +87,4 @@ Key Performance Points:
|
||||
|
||||
## License
|
||||
|
||||
[Sharkk Minimal License](LICENSE); do what you like!
|
||||
Licensed under MIT. [Take a look!](LICENSE)
|
483
router.go
483
router.go
@ -2,16 +2,10 @@ package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Handler is an interface for handling HTTP requests with path parameters.
|
||||
type Handler interface {
|
||||
Serve(params []string)
|
||||
}
|
||||
|
||||
// Middleware wraps a handler with additional functionality.
|
||||
type Middleware func(Handler) Handler
|
||||
// Handler processes HTTP requests with optional path parameters.
|
||||
type Handler func(params []string)
|
||||
|
||||
// node represents a segment in the URL path and its handling logic.
|
||||
type node struct {
|
||||
@ -24,21 +18,13 @@ type node struct {
|
||||
}
|
||||
|
||||
// Router routes HTTP requests by method and path.
|
||||
// It supports static paths, path parameters, wildcards, and middleware.
|
||||
// It supports static paths, path parameters, and wildcards.
|
||||
type Router struct {
|
||||
get *node
|
||||
post *node
|
||||
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 {
|
||||
router *Router
|
||||
prefix string
|
||||
middleware []Middleware
|
||||
}
|
||||
|
||||
// New creates a new Router instance.
|
||||
@ -49,463 +35,35 @@ func New() *Router {
|
||||
put: &node{},
|
||||
patch: &node{},
|
||||
delete: &node{},
|
||||
middleware: []Middleware{},
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler interface
|
||||
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
handler, params, found := r.Lookup(req.Method, req.URL.Path)
|
||||
if !found {
|
||||
http.NotFound(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// httpHandler adapts net/http handlers to the router.
|
||||
type httpHandler struct {
|
||||
w http.ResponseWriter
|
||||
r *http.Request
|
||||
h func(w http.ResponseWriter, r *http.Request, params []string)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Group creates a new route group with the given path prefix.
|
||||
func (r *Router) Group(prefix string) *Group {
|
||||
return &Group{
|
||||
router: r,
|
||||
prefix: prefix,
|
||||
middleware: []Middleware{},
|
||||
}
|
||||
}
|
||||
|
||||
// Use adds middleware to the group's middleware stack.
|
||||
func (g *Group) Use(middleware ...Middleware) *Group {
|
||||
g.middleware = append(g.middleware, middleware...)
|
||||
return g
|
||||
}
|
||||
|
||||
// Group creates a nested group with an additional prefix.
|
||||
func (g *Group) Group(prefix string) *Group {
|
||||
return &Group{
|
||||
router: g.router,
|
||||
prefix: g.prefix + prefix,
|
||||
middleware: append([]Middleware{}, g.middleware...),
|
||||
}
|
||||
}
|
||||
|
||||
// applyMiddleware wraps a handler with middleware in reverse order.
|
||||
func applyMiddleware(handler Handler, middleware []Middleware) Handler {
|
||||
h := handler
|
||||
for i := len(middleware) - 1; i >= 0; i-- {
|
||||
h = middleware[i](h)
|
||||
}
|
||||
return h
|
||||
}
|
||||
// HTTP method registration functions
|
||||
// Routes can contain static segments (/users), parameters ([id]), and wildcards (*filepath)
|
||||
|
||||
// Get registers a handler for GET requests at the given path.
|
||||
func (r *Router) Get(path string, handler http.HandlerFunc) error {
|
||||
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
|
||||
handler(w, r)
|
||||
}
|
||||
|
||||
return r.addRoute(r.get, path, &httpHandler{h: httpHandlerFunc}, r.middleware)
|
||||
}
|
||||
|
||||
// Get registers a handler with parameters for GET requests at the given path.
|
||||
func (r *Router) GetParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
|
||||
return r.addRoute(r.get, path, &httpHandler{h: handler}, r.middleware)
|
||||
func (r *Router) Get(path string, handler Handler) error {
|
||||
return r.addRoute(r.get, path, handler)
|
||||
}
|
||||
|
||||
// Post registers a handler for POST requests at the given path.
|
||||
func (r *Router) Post(path string, handler http.HandlerFunc) error {
|
||||
httpHandlerFunc := func(w http.ResponseWriter, r *http.Request, _ []string) {
|
||||
handler(w, r)
|
||||
}
|
||||
|
||||
return r.addRoute(r.post, path, &httpHandler{h: httpHandlerFunc}, r.middleware)
|
||||
}
|
||||
|
||||
// Post registers a handler with parameters for POST requests at the given path.
|
||||
func (r *Router) PostParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
|
||||
return r.addRoute(r.post, path, &httpHandler{h: handler}, r.middleware)
|
||||
func (r *Router) Post(path string, handler Handler) error {
|
||||
return r.addRoute(r.post, path, handler)
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (r *Router) Put(path string, handler Handler) error {
|
||||
return r.addRoute(r.put, path, handler)
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (r *Router) Patch(path string, handler Handler) error {
|
||||
return r.addRoute(r.patch, path, handler)
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
// WithMiddleware applies specific middleware to the next route registration.
|
||||
func (g *Group) WithMiddleware(middleware ...Middleware) *MiddlewareGroup {
|
||||
return &MiddlewareGroup{
|
||||
group: g,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
// MiddlewareRouter handles route registration with specific middleware.
|
||||
type MiddlewareRouter struct {
|
||||
router *Router
|
||||
middleware []Middleware
|
||||
}
|
||||
|
||||
// MiddlewareGroup handles group route registration with specific middleware.
|
||||
type MiddlewareGroup struct {
|
||||
group *Group
|
||||
middleware []Middleware
|
||||
}
|
||||
|
||||
// HTTP method registration functions for MiddlewareRouter
|
||||
|
||||
// Get registers a handler for GET requests with specific middleware.
|
||||
func (mr *MiddlewareRouter) Get(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.get, path, &httpHandler{h: httpHandlerFunc}, middleware)
|
||||
}
|
||||
|
||||
// Get registers a handler with parameters for GET requests with specific middleware.
|
||||
func (mr *MiddlewareRouter) GetParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
|
||||
middleware := append([]Middleware{}, mr.router.middleware...)
|
||||
middleware = append(middleware, mr.middleware...)
|
||||
return mr.router.addRoute(mr.router.get, path, &httpHandler{h: handler}, middleware)
|
||||
}
|
||||
|
||||
// Post registers a handler for POST requests with specific middleware.
|
||||
func (mr *MiddlewareRouter) Post(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.post, path, &httpHandler{h: httpHandlerFunc}, middleware)
|
||||
}
|
||||
|
||||
// Post registers a handler with parameters for POST requests with specific middleware.
|
||||
func (mr *MiddlewareRouter) PostParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
|
||||
middleware := append([]Middleware{}, mr.router.middleware...)
|
||||
middleware = append(middleware, mr.middleware...)
|
||||
return mr.router.addRoute(mr.router.post, path, &httpHandler{h: handler}, middleware)
|
||||
}
|
||||
|
||||
// Put registers a handler for PUT requests with specific middleware.
|
||||
func (mr *MiddlewareRouter) Put(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.put, path, &httpHandler{h: httpHandlerFunc}, middleware)
|
||||
}
|
||||
|
||||
// Put registers a handler with parameters for PUT requests with specific middleware.
|
||||
func (mr *MiddlewareRouter) PutParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
|
||||
middleware := append([]Middleware{}, mr.router.middleware...)
|
||||
middleware = append(middleware, mr.middleware...)
|
||||
return mr.router.addRoute(mr.router.put, path, &httpHandler{h: handler}, middleware)
|
||||
}
|
||||
|
||||
// Patch registers a handler for PATCH requests with specific middleware.
|
||||
func (mr *MiddlewareRouter) Patch(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.patch, path, &httpHandler{h: httpHandlerFunc}, middleware)
|
||||
}
|
||||
|
||||
// Patch registers a handler with parameters for PATCH requests with specific middleware.
|
||||
func (mr *MiddlewareRouter) PatchParam(path string, handler func(w http.ResponseWriter, r *http.Request, params []string)) error {
|
||||
middleware := append([]Middleware{}, mr.router.middleware...)
|
||||
middleware = append(middleware, mr.middleware...)
|
||||
return mr.router.addRoute(mr.router.patch, path, &httpHandler{h: handler}, middleware)
|
||||
}
|
||||
|
||||
// Delete registers a handler for DELETE requests with specific middleware.
|
||||
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)
|
||||
func (r *Router) Delete(path string, handler Handler) error {
|
||||
return r.addRoute(r.delete, path, handler)
|
||||
}
|
||||
|
||||
// readSegment extracts the next path segment starting at the given position.
|
||||
@ -531,12 +89,11 @@ func readSegment(path string, start int) (segment string, end int, hasMore bool)
|
||||
return path[start:end], end, end < len(path)
|
||||
}
|
||||
|
||||
// addRoute adds a new route to the prefix tree with middleware.
|
||||
func (r *Router) addRoute(root *node, path string, handler Handler, middleware []Middleware) error {
|
||||
wrappedHandler := applyMiddleware(handler, middleware)
|
||||
|
||||
// addRoute adds a new route to the prefix tree.
|
||||
// It validates wildcard positions and tracks parameter counts.
|
||||
func (r *Router) addRoute(root *node, path string, handler Handler) error {
|
||||
if path == "/" {
|
||||
root.handler = wrappedHandler
|
||||
root.handler = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -592,7 +149,7 @@ func (r *Router) addRoute(root *node, path string, handler Handler, middleware [
|
||||
pos = newPos
|
||||
}
|
||||
|
||||
current.handler = wrappedHandler
|
||||
current.handler = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
|
436
router_test.go
436
router_test.go
@ -7,78 +7,54 @@ import (
|
||||
assert "git.sharkk.net/Go/Assert"
|
||||
)
|
||||
|
||||
// simpleHandler implements the Handler interface
|
||||
type simpleHandler struct {
|
||||
fn func(params []string)
|
||||
func handler(t *testing.T, expectedParams []string) Handler {
|
||||
return func(params []string) {
|
||||
assert.Equal(t, len(params), len(expectedParams))
|
||||
for i, expected := range expectedParams {
|
||||
assert.Equal(t, params[i], expected)
|
||||
}
|
||||
|
||||
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) {
|
||||
r := New()
|
||||
r.GetParam("/", func(w http.ResponseWriter, r *http.Request, params []string) {
|
||||
// No-op for testing
|
||||
})
|
||||
r.Get("/", handler(t, nil))
|
||||
|
||||
h, params, found := r.Lookup("GET", "/")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
h(params)
|
||||
}
|
||||
|
||||
func TestStaticPath(t *testing.T) {
|
||||
r := New()
|
||||
r.GetParam("/users/all", func(w http.ResponseWriter, r *http.Request, params []string) {
|
||||
// No-op for testing
|
||||
})
|
||||
r.Get("/users/all", handler(t, nil))
|
||||
|
||||
h, params, found := r.Lookup("GET", "/users/all")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
h(params)
|
||||
}
|
||||
|
||||
func TestSingleParameter(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
called := false
|
||||
r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
|
||||
called = true
|
||||
assert.Equal(t, params[0], "123")
|
||||
})
|
||||
r.Get("/users/[id]", handler(t, []string{"123"}))
|
||||
|
||||
h, params, found := r.Lookup("GET", "/users/123")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
assert.True(t, called)
|
||||
h(params)
|
||||
}
|
||||
|
||||
func TestMultipleParameters(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
called := false
|
||||
r.GetParam("/users/[id]/posts/[postId]", func(w http.ResponseWriter, r *http.Request, params []string) {
|
||||
called = true
|
||||
assert.Equal(t, params[0], "123")
|
||||
assert.Equal(t, params[1], "456")
|
||||
})
|
||||
r.Get("/users/[id]/posts/[postId]", handler(t, []string{"123", "456"}))
|
||||
|
||||
h, params, found := r.Lookup("GET", "/users/123/posts/456")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
assert.True(t, called)
|
||||
h(params)
|
||||
}
|
||||
|
||||
func TestNonExistentPath(t *testing.T) {
|
||||
r := New()
|
||||
r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
|
||||
// No-op for testing
|
||||
})
|
||||
r.Get("/users/[id]", handler(t, nil))
|
||||
|
||||
_, _, found := r.Lookup("GET", "/posts/123")
|
||||
assert.False(t, found)
|
||||
@ -86,9 +62,7 @@ func TestNonExistentPath(t *testing.T) {
|
||||
|
||||
func TestWrongMethod(t *testing.T) {
|
||||
r := New()
|
||||
r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
|
||||
// No-op for testing
|
||||
})
|
||||
r.Get("/users/[id]", handler(t, nil))
|
||||
|
||||
_, _, found := r.Lookup("POST", "/users/123")
|
||||
assert.False(t, found)
|
||||
@ -96,29 +70,21 @@ func TestWrongMethod(t *testing.T) {
|
||||
|
||||
func TestTrailingSlash(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
called := false
|
||||
r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
|
||||
called = true
|
||||
assert.Equal(t, params[0], "123")
|
||||
})
|
||||
r.Get("/users/[id]", handler(t, []string{"123"}))
|
||||
|
||||
h, params, found := r.Lookup("GET", "/users/123/")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
assert.True(t, called)
|
||||
h(params)
|
||||
}
|
||||
|
||||
func TestDifferentMethods(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
|
||||
|
||||
r.GetParam("/test", handler)
|
||||
r.PostParam("/test", handler)
|
||||
r.PutParam("/test", handler)
|
||||
r.PatchParam("/test", handler)
|
||||
r.DeleteParam("/test", handler)
|
||||
r.Get("/test", handler(t, nil))
|
||||
r.Post("/test", handler(t, nil))
|
||||
r.Put("/test", handler(t, nil))
|
||||
r.Patch("/test", handler(t, nil))
|
||||
r.Delete("/test", handler(t, nil))
|
||||
|
||||
methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"}
|
||||
for _, method := range methods {
|
||||
@ -133,272 +99,53 @@ func TestWildcardPath(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
t.Run("simple wildcard", func(t *testing.T) {
|
||||
called := false
|
||||
err := r.GetParam("/files/*path", func(w http.ResponseWriter, r *http.Request, params []string) {
|
||||
called = true
|
||||
assert.Equal(t, params[0], "docs/report.pdf")
|
||||
})
|
||||
err := r.Get("/files/*path", handler(t, []string{"docs/report.pdf"}))
|
||||
assert.Nil(t, err)
|
||||
|
||||
h, params, found := r.Lookup("GET", "/files/docs/report.pdf")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
assert.True(t, called)
|
||||
h(params)
|
||||
})
|
||||
|
||||
t.Run("wildcard with empty path", func(t *testing.T) {
|
||||
called := false
|
||||
err := r.GetParam("/download/*filepath", func(w http.ResponseWriter, r *http.Request, params []string) {
|
||||
called = true
|
||||
assert.Equal(t, params[0], "")
|
||||
})
|
||||
err := r.Get("/download/*filepath", handler(t, []string{""}))
|
||||
assert.Nil(t, err)
|
||||
|
||||
h, params, found := r.Lookup("GET", "/download/")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
assert.True(t, called)
|
||||
h(params)
|
||||
})
|
||||
|
||||
t.Run("wildcard with parameter", func(t *testing.T) {
|
||||
called := false
|
||||
err := r.GetParam("/users/[id]/*action", func(w http.ResponseWriter, r *http.Request, params []string) {
|
||||
called = true
|
||||
assert.Equal(t, params[0], "123")
|
||||
assert.Equal(t, params[1], "settings/profile/avatar")
|
||||
})
|
||||
err := r.Get("/users/[id]/*action", handler(t, []string{"123", "settings/profile/avatar"}))
|
||||
assert.Nil(t, err)
|
||||
|
||||
h, params, found := r.Lookup("GET", "/users/123/settings/profile/avatar")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
assert.True(t, called)
|
||||
h(params)
|
||||
})
|
||||
|
||||
t.Run("multiple wildcards not allowed", func(t *testing.T) {
|
||||
err := r.GetParam("/api/*version/*path", func(w http.ResponseWriter, r *http.Request, params []string) {})
|
||||
err := r.Get("/api/*version/*path", handler(t, nil))
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
|
||||
t.Run("non-last wildcard not allowed", func(t *testing.T) {
|
||||
err := r.GetParam("/api/*version/users", func(w http.ResponseWriter, r *http.Request, params []string) {})
|
||||
err := r.Get("/api/*version/users", handler(t, nil))
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// Middleware Tests
|
||||
func TestMiddleware(t *testing.T) {
|
||||
t.Run("global middleware", func(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
// Track middleware execution
|
||||
executed := false
|
||||
|
||||
r.Use(func(next Handler) Handler {
|
||||
return newHandler(func(params []string) {
|
||||
executed = true
|
||||
next.Serve(params)
|
||||
})
|
||||
})
|
||||
|
||||
r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {})
|
||||
|
||||
h, params, found := r.Lookup("GET", "/test")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
assert.True(t, executed)
|
||||
})
|
||||
|
||||
t.Run("multiple middleware", func(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
// Track middleware execution order
|
||||
order := []int{}
|
||||
|
||||
r.Use(func(next Handler) Handler {
|
||||
return newHandler(func(params []string) {
|
||||
order = append(order, 1)
|
||||
next.Serve(params)
|
||||
order = append(order, 4)
|
||||
})
|
||||
})
|
||||
|
||||
r.Use(func(next Handler) Handler {
|
||||
return newHandler(func(params []string) {
|
||||
order = append(order, 2)
|
||||
next.Serve(params)
|
||||
order = append(order, 3)
|
||||
})
|
||||
})
|
||||
|
||||
r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {
|
||||
order = append(order, 0)
|
||||
})
|
||||
|
||||
h, params, found := r.Lookup("GET", "/test")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
|
||||
// Check middleware execution order (first middleware wraps second)
|
||||
assert.Equal(t, len(order), 5)
|
||||
assert.Equal(t, order[0], 1) // First middleware enter
|
||||
assert.Equal(t, order[1], 2) // Second middleware enter
|
||||
assert.Equal(t, order[2], 0) // Handler
|
||||
assert.Equal(t, order[3], 3) // Second middleware exit
|
||||
assert.Equal(t, order[4], 4) // First middleware exit
|
||||
})
|
||||
|
||||
t.Run("route-specific middleware", func(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
executed := false
|
||||
|
||||
middleware := func(next Handler) Handler {
|
||||
return newHandler(func(params []string) {
|
||||
executed = true
|
||||
next.Serve(params)
|
||||
})
|
||||
}
|
||||
|
||||
r.WithMiddleware(middleware).GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {})
|
||||
|
||||
h, params, found := r.Lookup("GET", "/test")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
assert.True(t, executed)
|
||||
})
|
||||
}
|
||||
|
||||
// Group Tests
|
||||
func TestGroup(t *testing.T) {
|
||||
t.Run("simple group", func(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
// Create API group
|
||||
api := r.Group("/api")
|
||||
api.GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) {})
|
||||
|
||||
h, params, found := r.Lookup("GET", "/api/users")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
})
|
||||
|
||||
t.Run("nested groups", func(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
// Create nested groups
|
||||
api := r.Group("/api")
|
||||
v1 := api.Group("/v1")
|
||||
v1.GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) {})
|
||||
|
||||
h, params, found := r.Lookup("GET", "/api/v1/users")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
})
|
||||
|
||||
t.Run("group middleware", func(t *testing.T) {
|
||||
r := New()
|
||||
executed := false
|
||||
|
||||
// Create group with middleware
|
||||
api := r.Group("/api")
|
||||
api.Use(func(next Handler) Handler {
|
||||
return newHandler(func(params []string) {
|
||||
executed = true
|
||||
next.Serve(params)
|
||||
})
|
||||
})
|
||||
|
||||
api.GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) {})
|
||||
|
||||
h, params, found := r.Lookup("GET", "/api/users")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
assert.True(t, executed)
|
||||
})
|
||||
|
||||
t.Run("nested group middleware", func(t *testing.T) {
|
||||
r := New()
|
||||
order := []int{}
|
||||
|
||||
// Create group with middleware
|
||||
api := r.Group("/api")
|
||||
api.Use(func(next Handler) Handler {
|
||||
return newHandler(func(params []string) {
|
||||
order = append(order, 1)
|
||||
next.Serve(params)
|
||||
})
|
||||
})
|
||||
|
||||
// Create nested group with additional middleware
|
||||
v1 := api.Group("/v1")
|
||||
v1.Use(func(next Handler) Handler {
|
||||
return newHandler(func(params []string) {
|
||||
order = append(order, 2)
|
||||
next.Serve(params)
|
||||
})
|
||||
})
|
||||
|
||||
v1.GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) {
|
||||
order = append(order, 3)
|
||||
})
|
||||
|
||||
h, params, found := r.Lookup("GET", "/api/v1/users")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
|
||||
// Check middleware execution order
|
||||
assert.Equal(t, len(order), 3)
|
||||
assert.Equal(t, order[0], 1) // First middleware (from api group)
|
||||
assert.Equal(t, order[1], 2) // Second middleware (from v1 group)
|
||||
assert.Equal(t, order[2], 3) // Handler
|
||||
})
|
||||
|
||||
t.Run("route-specific middleware in group", func(t *testing.T) {
|
||||
r := New()
|
||||
order := []int{}
|
||||
|
||||
// Create group with middleware
|
||||
api := r.Group("/api")
|
||||
api.Use(func(next Handler) Handler {
|
||||
return newHandler(func(params []string) {
|
||||
order = append(order, 1)
|
||||
next.Serve(params)
|
||||
})
|
||||
})
|
||||
|
||||
// Add route with specific middleware
|
||||
api.WithMiddleware(func(next Handler) Handler {
|
||||
return newHandler(func(params []string) {
|
||||
order = append(order, 2)
|
||||
next.Serve(params)
|
||||
})
|
||||
}).GetParam("/users", func(w http.ResponseWriter, r *http.Request, params []string) {
|
||||
order = append(order, 3)
|
||||
})
|
||||
|
||||
h, params, found := r.Lookup("GET", "/api/users")
|
||||
assert.True(t, found)
|
||||
h.Serve(params)
|
||||
|
||||
// Check middleware execution order
|
||||
assert.Equal(t, len(order), 3)
|
||||
assert.Equal(t, order[0], 1) // Group middleware
|
||||
assert.Equal(t, order[1], 2) // Route-specific middleware
|
||||
assert.Equal(t, order[2], 3) // Handler
|
||||
})
|
||||
}
|
||||
|
||||
// Benchmarks
|
||||
func BenchmarkRouterLookup(b *testing.B) {
|
||||
r := New()
|
||||
handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
|
||||
h := func(params []string) {}
|
||||
|
||||
// Setup routes for benchmarking
|
||||
r.GetParam("/", handler)
|
||||
r.GetParam("/users/all", handler)
|
||||
r.GetParam("/users/[id]", handler)
|
||||
r.GetParam("/users/[id]/posts/[postId]", handler)
|
||||
r.Get("/", h)
|
||||
r.Get("/users/all", h)
|
||||
r.Get("/users/[id]", h)
|
||||
r.Get("/users/[id]/posts/[postId]", h)
|
||||
|
||||
b.Run("root", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
@ -433,11 +180,11 @@ func BenchmarkRouterLookup(b *testing.B) {
|
||||
|
||||
func BenchmarkParallelLookup(b *testing.B) {
|
||||
r := New()
|
||||
handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
|
||||
h := func(params []string) {}
|
||||
|
||||
r.GetParam("/users/[id]", handler)
|
||||
r.GetParam("/posts/[id]/comments", handler)
|
||||
r.GetParam("/products/[category]/[id]", handler)
|
||||
r.Get("/users/[id]", h)
|
||||
r.Get("/posts/[id]/comments", h)
|
||||
r.Get("/products/[category]/[id]", h)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
@ -457,11 +204,11 @@ func BenchmarkParallelLookup(b *testing.B) {
|
||||
|
||||
func BenchmarkWildcardLookup(b *testing.B) {
|
||||
r := New()
|
||||
handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
|
||||
h := func(params []string) {}
|
||||
|
||||
// Setup routes for benchmarking
|
||||
r.GetParam("/files/*path", handler)
|
||||
r.GetParam("/users/[id]/*action", handler)
|
||||
r.Get("/files/*path", h)
|
||||
r.Get("/users/[id]/*action", h)
|
||||
|
||||
b.Run("simple_wildcard", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
@ -476,104 +223,13 @@ func BenchmarkWildcardLookup(b *testing.B) {
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMiddleware(b *testing.B) {
|
||||
passthrough := func(next Handler) Handler {
|
||||
return newHandler(func(params []string) {
|
||||
next.Serve(params)
|
||||
})
|
||||
}
|
||||
|
||||
b.Run("no_middleware", func(b *testing.B) {
|
||||
r := New()
|
||||
r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {})
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
h, params, _ := r.Lookup("GET", "/test")
|
||||
h.Serve(params)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("one_middleware", func(b *testing.B) {
|
||||
r := New()
|
||||
r.Use(passthrough)
|
||||
r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {})
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
h, params, _ := r.Lookup("GET", "/test")
|
||||
h.Serve(params)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("five_middleware", func(b *testing.B) {
|
||||
r := New()
|
||||
for i := 0; i < 5; i++ {
|
||||
r.Use(passthrough)
|
||||
}
|
||||
r.GetParam("/test", func(w http.ResponseWriter, r *http.Request, params []string) {})
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
h, params, _ := r.Lookup("GET", "/test")
|
||||
h.Serve(params)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkGroups(b *testing.B) {
|
||||
handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
|
||||
|
||||
b.Run("flat_route", func(b *testing.B) {
|
||||
r := New()
|
||||
r.GetParam("/api/v1/users", handler)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Lookup("GET", "/api/v1/users")
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("grouped_route", func(b *testing.B) {
|
||||
r := New()
|
||||
api := r.Group("/api")
|
||||
v1 := api.Group("/v1")
|
||||
v1.GetParam("/users", handler)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Lookup("GET", "/api/v1/users")
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("grouped_route_with_middleware", func(b *testing.B) {
|
||||
r := New()
|
||||
api := r.Group("/api")
|
||||
api.Use(func(next Handler) Handler {
|
||||
return newHandler(func(params []string) {
|
||||
next.Serve(params)
|
||||
})
|
||||
})
|
||||
v1 := api.Group("/v1")
|
||||
v1.GetParam("/users", handler)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
h, params, _ := r.Lookup("GET", "/api/v1/users")
|
||||
h.Serve(params)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkComparison(b *testing.B) {
|
||||
// Custom router setup
|
||||
customRouter := New()
|
||||
handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
|
||||
|
||||
customRouter.GetParam("/", handler)
|
||||
customRouter.GetParam("/users/all", handler)
|
||||
customRouter.GetParam("/users/[id]", handler)
|
||||
customRouter.GetParam("/users/[id]/posts/[postId]", handler)
|
||||
customRouter.Get("/", func(params []string) {})
|
||||
customRouter.Get("/users/all", func(params []string) {})
|
||||
customRouter.Get("/users/[id]", func(params []string) {})
|
||||
customRouter.Get("/users/[id]/posts/[postId]", func(params []string) {})
|
||||
|
||||
// Standard mux setup
|
||||
mux := http.NewServeMux()
|
||||
|
Loading…
x
Reference in New Issue
Block a user