Compare commits

...

2 Commits

Author SHA1 Message Date
de416933e8 rewrite3 2025-04-26 11:37:30 -05:00
ba32300bd9 rewrite 2 2025-04-26 11:12:58 -05:00
4 changed files with 983 additions and 106 deletions

18
LICENSE
View File

@ -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
View File

@ -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](LICENSE); do what you like!

503
router.go
View File

@ -2,10 +2,16 @@ package router
import (
"fmt"
"net/http"
)
// Handler processes HTTP requests with optional path parameters.
type Handler func(params []string)
// 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
// node represents a segment in the URL path and its handling logic.
type node struct {
@ -18,52 +24,488 @@ 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
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.
func New() *Router {
return &Router{
get: &node{},
post: &node{},
put: &node{},
patch: &node{},
delete: &node{},
get: &node{},
post: &node{},
put: &node{},
patch: &node{},
delete: &node{},
middleware: []Middleware{},
}
}
// HTTP method registration functions
// Routes can contain static segments (/users), parameters ([id]), and wildcards (*filepath)
// 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
}
// 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)
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)
}
// 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)
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)
}
// 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)
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 Handler) error {
return r.addRoute(r.patch, path, handler)
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 Handler) error {
return r.addRoute(r.delete, path, handler)
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)
}
// readSegment extracts the next path segment starting at the given position.
@ -89,11 +531,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 +592,7 @@ func (r *Router) addRoute(root *node, path string, handler Handler) error {
pos = newPos
}
current.handler = handler
current.handler = wrappedHandler
return nil
}

View File

@ -7,54 +7,78 @@ import (
assert "git.sharkk.net/Go/Assert"
)
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)
}
}
// 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) {
r := New()
r.Get("/", handler(t, nil))
r.GetParam("/", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for testing
})
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.GetParam("/users/all", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for testing
})
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"}))
called := false
r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
called = true
assert.Equal(t, params[0], "123")
})
h, params, found := r.Lookup("GET", "/users/123")
assert.True(t, found)
h(params)
h.Serve(params)
assert.True(t, called)
}
func TestMultipleParameters(t *testing.T) {
r := New()
r.Get("/users/[id]/posts/[postId]", handler(t, []string{"123", "456"}))
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")
})
h, params, found := r.Lookup("GET", "/users/123/posts/456")
assert.True(t, found)
h(params)
h.Serve(params)
assert.True(t, called)
}
func TestNonExistentPath(t *testing.T) {
r := New()
r.Get("/users/[id]", handler(t, nil))
r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for testing
})
_, _, found := r.Lookup("GET", "/posts/123")
assert.False(t, found)
@ -62,7 +86,9 @@ func TestNonExistentPath(t *testing.T) {
func TestWrongMethod(t *testing.T) {
r := New()
r.Get("/users/[id]", handler(t, nil))
r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
// No-op for testing
})
_, _, found := r.Lookup("POST", "/users/123")
assert.False(t, found)
@ -70,21 +96,29 @@ func TestWrongMethod(t *testing.T) {
func TestTrailingSlash(t *testing.T) {
r := New()
r.Get("/users/[id]", handler(t, []string{"123"}))
called := false
r.GetParam("/users/[id]", func(w http.ResponseWriter, r *http.Request, params []string) {
called = true
assert.Equal(t, params[0], "123")
})
h, params, found := r.Lookup("GET", "/users/123/")
assert.True(t, found)
h(params)
h.Serve(params)
assert.True(t, called)
}
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))
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)
methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"}
for _, method := range methods {
@ -99,53 +133,272 @@ 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"}))
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")
})
assert.Nil(t, err)
h, params, found := r.Lookup("GET", "/files/docs/report.pdf")
assert.True(t, found)
h(params)
h.Serve(params)
assert.True(t, called)
})
t.Run("wildcard with empty path", func(t *testing.T) {
err := r.Get("/download/*filepath", handler(t, []string{""}))
called := false
err := r.GetParam("/download/*filepath", func(w http.ResponseWriter, r *http.Request, params []string) {
called = true
assert.Equal(t, params[0], "")
})
assert.Nil(t, err)
h, params, found := r.Lookup("GET", "/download/")
assert.True(t, found)
h(params)
h.Serve(params)
assert.True(t, called)
})
t.Run("wildcard with parameter", func(t *testing.T) {
err := r.Get("/users/[id]/*action", handler(t, []string{"123", "settings/profile/avatar"}))
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")
})
assert.Nil(t, err)
h, params, found := r.Lookup("GET", "/users/123/settings/profile/avatar")
assert.True(t, found)
h(params)
h.Serve(params)
assert.True(t, called)
})
t.Run("multiple wildcards not allowed", func(t *testing.T) {
err := r.Get("/api/*version/*path", handler(t, nil))
err := r.GetParam("/api/*version/*path", func(w http.ResponseWriter, r *http.Request, params []string) {})
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.GetParam("/api/*version/users", func(w http.ResponseWriter, r *http.Request, params []string) {})
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()
h := func(params []string) {}
handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
// Setup routes for benchmarking
r.Get("/", h)
r.Get("/users/all", h)
r.Get("/users/[id]", h)
r.Get("/users/[id]/posts/[postId]", h)
r.GetParam("/", handler)
r.GetParam("/users/all", handler)
r.GetParam("/users/[id]", handler)
r.GetParam("/users/[id]/posts/[postId]", handler)
b.Run("root", func(b *testing.B) {
for i := 0; i < b.N; i++ {
@ -180,11 +433,11 @@ func BenchmarkRouterLookup(b *testing.B) {
func BenchmarkParallelLookup(b *testing.B) {
r := New()
h := func(params []string) {}
handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
r.Get("/users/[id]", h)
r.Get("/posts/[id]/comments", h)
r.Get("/products/[category]/[id]", h)
r.GetParam("/users/[id]", handler)
r.GetParam("/posts/[id]/comments", handler)
r.GetParam("/products/[category]/[id]", handler)
b.RunParallel(func(pb *testing.PB) {
i := 0
@ -204,11 +457,11 @@ func BenchmarkParallelLookup(b *testing.B) {
func BenchmarkWildcardLookup(b *testing.B) {
r := New()
h := func(params []string) {}
handler := func(w http.ResponseWriter, r *http.Request, params []string) {}
// Setup routes for benchmarking
r.Get("/files/*path", h)
r.Get("/users/[id]/*action", h)
r.GetParam("/files/*path", handler)
r.GetParam("/users/[id]/*action", handler)
b.Run("simple_wildcard", func(b *testing.B) {
for i := 0; i < b.N; i++ {
@ -223,13 +476,104 @@ 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()
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) {})
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)
// Standard mux setup
mux := http.NewServeMux()