rewrite 2
This commit is contained in:
parent
5791042454
commit
ba32300bd9
18
LICENSE
18
LICENSE
@ -1,9 +1,17 @@
|
||||
MIT License
|
||||
# Sharkk Minimal License
|
||||
|
||||
Copyright (c) 2024 Sharkk
|
||||
Copyright © 2025 Sharkk, sharkk.net
|
||||
|
||||
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:
|
||||
You can freely use and modify this software if you follow these simple rules:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. **Share-Alike**: When sharing modified versions, use this same license.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
128
README.md
128
README.md
@ -1,11 +1,14 @@
|
||||
# Router
|
||||
|
||||
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🔥.
|
||||
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🔥.
|
||||
|
||||
## 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
|
||||
|
||||
@ -17,41 +20,120 @@ go get git.sharkk.net/Go/Router
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
type Handler func(params []string)
|
||||
### Basic Routing
|
||||
|
||||
router := router.New()
|
||||
```go
|
||||
// Create a new router
|
||||
r := router.New()
|
||||
|
||||
// Static routes
|
||||
router.Get("/", func(params []string) {
|
||||
r.Get("/", router.FuncHandler(func(params []string) {
|
||||
fmt.Println("Root handler")
|
||||
})
|
||||
router.Get("/users/all", func(params []string) {
|
||||
fmt.Println("All users")
|
||||
})
|
||||
}))
|
||||
|
||||
// Parameter routes
|
||||
router.Get("/users/[id]", func(params []string) {
|
||||
r.Get("/users/[id]", router.FuncHandler(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
|
||||
router.Get("/files/*path", func(params []string) {
|
||||
r.Get("/files/*path", router.FuncHandler(func(params []string) {
|
||||
filePath := params[0]
|
||||
fmt.Printf("File path: %s\n", filePath)
|
||||
})
|
||||
}))
|
||||
|
||||
// Lookup routes
|
||||
if handler, params, ok := router.Lookup("GET", "/users/123"); ok {
|
||||
handler(params)
|
||||
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)
|
||||
}
|
||||
```
|
||||
|
||||
@ -87,4 +169,4 @@ Key Performance Points:
|
||||
|
||||
## License
|
||||
|
||||
Licensed under MIT. [Take a look!](LICENSE)
|
||||
Sharkk Minimal License; do what you like! [Take a look!](LICENSE)
|
||||
|
22
fasthttpHandler.go
Normal file
22
fasthttpHandler.go
Normal file
@ -0,0 +1,22 @@
|
||||
//go:build fasthttp
|
||||
// +build fasthttp
|
||||
|
||||
package router
|
||||
|
||||
import "github.com/valyala/fasthttp"
|
||||
|
||||
// FastHTTPHandler adapts fasthttp handlers to the router.
|
||||
type FastHTTPHandler struct {
|
||||
ctx *fasthttp.RequestCtx
|
||||
h func(ctx *fasthttp.RequestCtx, params []string)
|
||||
}
|
||||
|
||||
// Serve executes the fasthttp handler with parameters.
|
||||
func (f *FastHTTPHandler) Serve(params []string) {
|
||||
f.h(f.ctx, params)
|
||||
}
|
||||
|
||||
// NewFastHTTP creates a handler from a fasthttp handler function.
|
||||
func NewFastHTTP(ctx *fasthttp.RequestCtx, h func(ctx *fasthttp.RequestCtx, params []string)) Handler {
|
||||
return &FastHTTPHandler{ctx: ctx, h: h}
|
||||
}
|
17
handler.go
Normal file
17
handler.go
Normal file
@ -0,0 +1,17 @@
|
||||
package router
|
||||
|
||||
// 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
|
||||
|
||||
// FuncHandler is a function that implements the Handler interface.
|
||||
type FuncHandler func(params []string)
|
||||
|
||||
// Serve calls the handler function.
|
||||
func (f FuncHandler) Serve(params []string) {
|
||||
f(params)
|
||||
}
|
20
httpHandler.go
Normal file
20
httpHandler.go
Normal file
@ -0,0 +1,20 @@
|
||||
package router
|
||||
|
||||
import "net/http"
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// NewHTTP creates a handler from a net/http handler function.
|
||||
func NewHTTP(w http.ResponseWriter, r *http.Request, h func(w http.ResponseWriter, r *http.Request, params []string)) Handler {
|
||||
return &HTTPHandler{w: w, r: r, h: h}
|
||||
}
|
221
router.go
221
router.go
@ -4,9 +4,6 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
segment string // the path segment this node matches
|
||||
@ -18,13 +15,21 @@ type node struct {
|
||||
}
|
||||
|
||||
// Router routes HTTP requests by method and path.
|
||||
// It supports static paths, path parameters, and wildcards.
|
||||
// It supports static paths, path parameters, wildcards, and middleware.
|
||||
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.
|
||||
@ -35,35 +40,218 @@ func New() *Router {
|
||||
put: &node{},
|
||||
patch: &node{},
|
||||
delete: &node{},
|
||||
middleware: []Middleware{},
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP method registration functions
|
||||
// Routes can contain static segments (/users), parameters ([id]), and wildcards (*filepath)
|
||||
// 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 for Router
|
||||
|
||||
// Get registers a handler for GET requests at the given path.
|
||||
func (r *Router) Get(path string, handler Handler) error {
|
||||
return r.addRoute(r.get, path, handler)
|
||||
return r.addRoute(r.get, path, handler, r.middleware)
|
||||
}
|
||||
|
||||
// Post registers a handler for POST requests at the given path.
|
||||
func (r *Router) Post(path string, handler Handler) error {
|
||||
return r.addRoute(r.post, path, handler)
|
||||
return r.addRoute(r.post, path, handler, r.middleware)
|
||||
}
|
||||
|
||||
// Put registers a handler for PUT requests at the given path.
|
||||
func (r *Router) Put(path string, handler Handler) error {
|
||||
return r.addRoute(r.put, path, handler)
|
||||
return r.addRoute(r.put, path, handler, r.middleware)
|
||||
}
|
||||
|
||||
// Patch registers a handler for PATCH requests at the given path.
|
||||
func (r *Router) Patch(path string, handler Handler) error {
|
||||
return r.addRoute(r.patch, path, handler)
|
||||
return r.addRoute(r.patch, path, handler, r.middleware)
|
||||
}
|
||||
|
||||
// Delete registers a handler for DELETE requests at the given path.
|
||||
func (r *Router) Delete(path string, handler Handler) error {
|
||||
return r.addRoute(r.delete, path, handler)
|
||||
return r.addRoute(r.delete, path, handler, r.middleware)
|
||||
}
|
||||
|
||||
// HTTP method registration functions for Group
|
||||
|
||||
// Get registers a handler for GET requests at the given path.
|
||||
func (g *Group) Get(path string, handler Handler) error {
|
||||
middleware := append([]Middleware{}, g.router.middleware...)
|
||||
middleware = append(middleware, g.middleware...)
|
||||
return g.router.addRoute(g.router.get, g.prefix+path, handler, middleware)
|
||||
}
|
||||
|
||||
// Post registers a handler for POST requests at the given path.
|
||||
func (g *Group) Post(path string, handler Handler) error {
|
||||
middleware := append([]Middleware{}, g.router.middleware...)
|
||||
middleware = append(middleware, g.middleware...)
|
||||
return g.router.addRoute(g.router.post, g.prefix+path, handler, middleware)
|
||||
}
|
||||
|
||||
// Put registers a handler for PUT requests at the given path.
|
||||
func (g *Group) Put(path string, handler Handler) error {
|
||||
middleware := append([]Middleware{}, g.router.middleware...)
|
||||
middleware = append(middleware, g.middleware...)
|
||||
return g.router.addRoute(g.router.put, g.prefix+path, handler, middleware)
|
||||
}
|
||||
|
||||
// Patch registers a handler for PATCH requests at the given path.
|
||||
func (g *Group) Patch(path string, handler Handler) error {
|
||||
middleware := append([]Middleware{}, g.router.middleware...)
|
||||
middleware = append(middleware, g.middleware...)
|
||||
return g.router.addRoute(g.router.patch, g.prefix+path, handler, middleware)
|
||||
}
|
||||
|
||||
// Delete registers a handler for DELETE requests at the given path.
|
||||
func (g *Group) Delete(path string, handler Handler) error {
|
||||
middleware := append([]Middleware{}, g.router.middleware...)
|
||||
middleware = append(middleware, g.middleware...)
|
||||
return g.router.addRoute(g.router.delete, g.prefix+path, 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 Handler) error {
|
||||
middleware := append([]Middleware{}, mr.router.middleware...)
|
||||
middleware = append(middleware, mr.middleware...)
|
||||
return mr.router.addRoute(mr.router.get, path, handler, middleware)
|
||||
}
|
||||
|
||||
// Post registers a handler for POST requests with specific middleware.
|
||||
func (mr *MiddlewareRouter) Post(path string, handler Handler) error {
|
||||
middleware := append([]Middleware{}, mr.router.middleware...)
|
||||
middleware = append(middleware, mr.middleware...)
|
||||
return mr.router.addRoute(mr.router.post, path, handler, middleware)
|
||||
}
|
||||
|
||||
// Put registers a handler for PUT requests with specific middleware.
|
||||
func (mr *MiddlewareRouter) Put(path string, handler Handler) error {
|
||||
middleware := append([]Middleware{}, mr.router.middleware...)
|
||||
middleware = append(middleware, mr.middleware...)
|
||||
return mr.router.addRoute(mr.router.put, path, handler, middleware)
|
||||
}
|
||||
|
||||
// Patch registers a handler for PATCH requests with specific middleware.
|
||||
func (mr *MiddlewareRouter) Patch(path string, handler Handler) error {
|
||||
middleware := append([]Middleware{}, mr.router.middleware...)
|
||||
middleware = append(middleware, mr.middleware...)
|
||||
return mr.router.addRoute(mr.router.patch, path, handler, middleware)
|
||||
}
|
||||
|
||||
// Delete registers a handler for DELETE requests with specific middleware.
|
||||
func (mr *MiddlewareRouter) Delete(path string, handler Handler) error {
|
||||
middleware := append([]Middleware{}, mr.router.middleware...)
|
||||
middleware = append(middleware, mr.middleware...)
|
||||
return mr.router.addRoute(mr.router.delete, path, 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 Handler) 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, handler, middleware)
|
||||
}
|
||||
|
||||
// Post registers a handler for POST requests with specific middleware.
|
||||
func (mg *MiddlewareGroup) Post(path string, handler Handler) 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, handler, middleware)
|
||||
}
|
||||
|
||||
// Put registers a handler for PUT requests with specific middleware.
|
||||
func (mg *MiddlewareGroup) Put(path string, handler Handler) 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, handler, middleware)
|
||||
}
|
||||
|
||||
// Patch registers a handler for PATCH requests with specific middleware.
|
||||
func (mg *MiddlewareGroup) Patch(path string, handler Handler) 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, handler, middleware)
|
||||
}
|
||||
|
||||
// Delete registers a handler for DELETE requests with specific middleware.
|
||||
func (mg *MiddlewareGroup) Delete(path string, handler Handler) 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, handler, middleware)
|
||||
}
|
||||
|
||||
// readSegment extracts the next path segment starting at the given position.
|
||||
@ -89,11 +277,12 @@ 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.
|
||||
// It validates wildcard positions and tracks parameter counts.
|
||||
func (r *Router) addRoute(root *node, path string, handler Handler) error {
|
||||
// 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)
|
||||
|
||||
if path == "/" {
|
||||
root.handler = handler
|
||||
root.handler = wrappedHandler
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -149,7 +338,7 @@ func (r *Router) addRoute(root *node, path string, handler Handler) error {
|
||||
pos = newPos
|
||||
}
|
||||
|
||||
current.handler = handler
|
||||
current.handler = wrappedHandler
|
||||
return nil
|
||||
}
|
||||
|
||||
|
365
router_test.go
365
router_test.go
@ -7,54 +7,55 @@ import (
|
||||
assert "git.sharkk.net/Go/Assert"
|
||||
)
|
||||
|
||||
func handler(t *testing.T, expectedParams []string) Handler {
|
||||
return func(params []string) {
|
||||
// Create a test handler that implements the Handler interface
|
||||
func testHandler(t *testing.T, expectedParams []string) Handler {
|
||||
return FuncHandler(func(params []string) {
|
||||
assert.Equal(t, len(params), len(expectedParams))
|
||||
for i, expected := range expectedParams {
|
||||
assert.Equal(t, params[i], expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRootPath(t *testing.T) {
|
||||
r := New()
|
||||
r.Get("/", handler(t, nil))
|
||||
r.Get("/", testHandler(t, nil))
|
||||
|
||||
h, params, found := r.Lookup("GET", "/")
|
||||
assert.True(t, found)
|
||||
h(params)
|
||||
h.Serve(params)
|
||||
}
|
||||
|
||||
func TestStaticPath(t *testing.T) {
|
||||
r := New()
|
||||
r.Get("/users/all", handler(t, nil))
|
||||
r.Get("/users/all", testHandler(t, nil))
|
||||
|
||||
h, params, found := r.Lookup("GET", "/users/all")
|
||||
assert.True(t, found)
|
||||
h(params)
|
||||
h.Serve(params)
|
||||
}
|
||||
|
||||
func TestSingleParameter(t *testing.T) {
|
||||
r := New()
|
||||
r.Get("/users/[id]", handler(t, []string{"123"}))
|
||||
r.Get("/users/[id]", testHandler(t, []string{"123"}))
|
||||
|
||||
h, params, found := r.Lookup("GET", "/users/123")
|
||||
assert.True(t, found)
|
||||
h(params)
|
||||
h.Serve(params)
|
||||
}
|
||||
|
||||
func TestMultipleParameters(t *testing.T) {
|
||||
r := New()
|
||||
r.Get("/users/[id]/posts/[postId]", handler(t, []string{"123", "456"}))
|
||||
r.Get("/users/[id]/posts/[postId]", testHandler(t, []string{"123", "456"}))
|
||||
|
||||
h, params, found := r.Lookup("GET", "/users/123/posts/456")
|
||||
assert.True(t, found)
|
||||
h(params)
|
||||
h.Serve(params)
|
||||
}
|
||||
|
||||
func TestNonExistentPath(t *testing.T) {
|
||||
r := New()
|
||||
r.Get("/users/[id]", handler(t, nil))
|
||||
r.Get("/users/[id]", testHandler(t, nil))
|
||||
|
||||
_, _, found := r.Lookup("GET", "/posts/123")
|
||||
assert.False(t, found)
|
||||
@ -62,7 +63,7 @@ func TestNonExistentPath(t *testing.T) {
|
||||
|
||||
func TestWrongMethod(t *testing.T) {
|
||||
r := New()
|
||||
r.Get("/users/[id]", handler(t, nil))
|
||||
r.Get("/users/[id]", testHandler(t, nil))
|
||||
|
||||
_, _, found := r.Lookup("POST", "/users/123")
|
||||
assert.False(t, found)
|
||||
@ -70,21 +71,21 @@ func TestWrongMethod(t *testing.T) {
|
||||
|
||||
func TestTrailingSlash(t *testing.T) {
|
||||
r := New()
|
||||
r.Get("/users/[id]", handler(t, []string{"123"}))
|
||||
r.Get("/users/[id]", testHandler(t, []string{"123"}))
|
||||
|
||||
h, params, found := r.Lookup("GET", "/users/123/")
|
||||
assert.True(t, found)
|
||||
h(params)
|
||||
h.Serve(params)
|
||||
}
|
||||
|
||||
func TestDifferentMethods(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
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))
|
||||
r.Get("/test", testHandler(t, nil))
|
||||
r.Post("/test", testHandler(t, nil))
|
||||
r.Put("/test", testHandler(t, nil))
|
||||
r.Patch("/test", testHandler(t, nil))
|
||||
r.Delete("/test", testHandler(t, nil))
|
||||
|
||||
methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"}
|
||||
for _, method := range methods {
|
||||
@ -99,47 +100,250 @@ func TestWildcardPath(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
t.Run("simple wildcard", func(t *testing.T) {
|
||||
err := r.Get("/files/*path", handler(t, []string{"docs/report.pdf"}))
|
||||
err := r.Get("/files/*path", testHandler(t, []string{"docs/report.pdf"}))
|
||||
assert.Nil(t, err)
|
||||
|
||||
h, params, found := r.Lookup("GET", "/files/docs/report.pdf")
|
||||
assert.True(t, found)
|
||||
h(params)
|
||||
h.Serve(params)
|
||||
})
|
||||
|
||||
t.Run("wildcard with empty path", func(t *testing.T) {
|
||||
err := r.Get("/download/*filepath", handler(t, []string{""}))
|
||||
err := r.Get("/download/*filepath", testHandler(t, []string{""}))
|
||||
assert.Nil(t, err)
|
||||
|
||||
h, params, found := r.Lookup("GET", "/download/")
|
||||
assert.True(t, found)
|
||||
h(params)
|
||||
h.Serve(params)
|
||||
})
|
||||
|
||||
t.Run("wildcard with parameter", func(t *testing.T) {
|
||||
err := r.Get("/users/[id]/*action", handler(t, []string{"123", "settings/profile/avatar"}))
|
||||
err := r.Get("/users/[id]/*action", testHandler(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(params)
|
||||
h.Serve(params)
|
||||
})
|
||||
|
||||
t.Run("multiple wildcards not allowed", func(t *testing.T) {
|
||||
err := r.Get("/api/*version/*path", handler(t, nil))
|
||||
err := r.Get("/api/*version/*path", testHandler(t, nil))
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
|
||||
t.Run("non-last wildcard not allowed", func(t *testing.T) {
|
||||
err := r.Get("/api/*version/users", handler(t, nil))
|
||||
err := r.Get("/api/*version/users", testHandler(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 FuncHandler(func(params []string) {
|
||||
executed = true
|
||||
next.Serve(params)
|
||||
})
|
||||
})
|
||||
|
||||
r.Get("/test", testHandler(t, nil))
|
||||
|
||||
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 FuncHandler(func(params []string) {
|
||||
order = append(order, 1)
|
||||
next.Serve(params)
|
||||
order = append(order, 4)
|
||||
})
|
||||
})
|
||||
|
||||
r.Use(func(next Handler) Handler {
|
||||
return FuncHandler(func(params []string) {
|
||||
order = append(order, 2)
|
||||
next.Serve(params)
|
||||
order = append(order, 3)
|
||||
})
|
||||
})
|
||||
|
||||
r.Get("/test", FuncHandler(func(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 FuncHandler(func(params []string) {
|
||||
executed = true
|
||||
next.Serve(params)
|
||||
})
|
||||
}
|
||||
|
||||
r.WithMiddleware(middleware).Get("/test", testHandler(t, nil))
|
||||
|
||||
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.Get("/users", testHandler(t, nil))
|
||||
|
||||
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.Get("/users", testHandler(t, nil))
|
||||
|
||||
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 FuncHandler(func(params []string) {
|
||||
executed = true
|
||||
next.Serve(params)
|
||||
})
|
||||
})
|
||||
|
||||
api.Get("/users", testHandler(t, nil))
|
||||
|
||||
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 FuncHandler(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 FuncHandler(func(params []string) {
|
||||
order = append(order, 2)
|
||||
next.Serve(params)
|
||||
})
|
||||
})
|
||||
|
||||
v1.Get("/users", FuncHandler(func(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 FuncHandler(func(params []string) {
|
||||
order = append(order, 1)
|
||||
next.Serve(params)
|
||||
})
|
||||
})
|
||||
|
||||
// Add route with specific middleware
|
||||
api.WithMiddleware(func(next Handler) Handler {
|
||||
return FuncHandler(func(params []string) {
|
||||
order = append(order, 2)
|
||||
next.Serve(params)
|
||||
})
|
||||
}).Get("/users", FuncHandler(func(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()
|
||||
h := func(params []string) {}
|
||||
h := FuncHandler(func(params []string) {})
|
||||
|
||||
// Setup routes for benchmarking
|
||||
r.Get("/", h)
|
||||
@ -180,7 +384,7 @@ func BenchmarkRouterLookup(b *testing.B) {
|
||||
|
||||
func BenchmarkParallelLookup(b *testing.B) {
|
||||
r := New()
|
||||
h := func(params []string) {}
|
||||
h := FuncHandler(func(params []string) {})
|
||||
|
||||
r.Get("/users/[id]", h)
|
||||
r.Get("/posts/[id]/comments", h)
|
||||
@ -204,7 +408,7 @@ func BenchmarkParallelLookup(b *testing.B) {
|
||||
|
||||
func BenchmarkWildcardLookup(b *testing.B) {
|
||||
r := New()
|
||||
h := func(params []string) {}
|
||||
h := FuncHandler(func(params []string) {})
|
||||
|
||||
// Setup routes for benchmarking
|
||||
r.Get("/files/*path", h)
|
||||
@ -223,13 +427,104 @@ func BenchmarkWildcardLookup(b *testing.B) {
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMiddleware(b *testing.B) {
|
||||
passthrough := func(next Handler) Handler {
|
||||
return FuncHandler(func(params []string) {
|
||||
next.Serve(params)
|
||||
})
|
||||
}
|
||||
|
||||
simpleHandler := FuncHandler(func(params []string) {})
|
||||
|
||||
b.Run("no_middleware", func(b *testing.B) {
|
||||
r := New()
|
||||
r.Get("/test", simpleHandler)
|
||||
|
||||
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.Get("/test", simpleHandler)
|
||||
|
||||
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.Get("/test", simpleHandler)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
h, params, _ := r.Lookup("GET", "/test")
|
||||
h.Serve(params)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkGroups(b *testing.B) {
|
||||
simpleHandler := FuncHandler(func(params []string) {})
|
||||
|
||||
b.Run("flat_route", func(b *testing.B) {
|
||||
r := New()
|
||||
r.Get("/api/v1/users", simpleHandler)
|
||||
|
||||
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.Get("/users", simpleHandler)
|
||||
|
||||
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 FuncHandler(func(params []string) {
|
||||
next.Serve(params)
|
||||
})
|
||||
})
|
||||
v1 := api.Group("/v1")
|
||||
v1.Get("/users", simpleHandler)
|
||||
|
||||
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()
|
||||
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) {})
|
||||
customRouter.Get("/", FuncHandler(func(params []string) {}))
|
||||
customRouter.Get("/users/all", FuncHandler(func(params []string) {}))
|
||||
customRouter.Get("/users/[id]", FuncHandler(func(params []string) {}))
|
||||
customRouter.Get("/users/[id]/posts/[postId]", FuncHandler(func(params []string) {}))
|
||||
|
||||
// Standard mux setup
|
||||
mux := http.NewServeMux()
|
||||
|
Loading…
x
Reference in New Issue
Block a user