From 366517de18f929a1283cfeb49716aaacdaff0395 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Wed, 7 May 2025 12:46:40 -0500 Subject: [PATCH] Go/Router parity and documentation --- DOCS.md | 169 ++++++++++++++++++++ EXAMPLES.md | 304 +++++++++++++++++++++++++++++++++++ router.go | 426 +++++++++++++++++++++---------------------------- router_test.go | 11 +- 4 files changed, 660 insertions(+), 250 deletions(-) create mode 100644 DOCS.md create mode 100644 EXAMPLES.md diff --git a/DOCS.md b/DOCS.md new file mode 100644 index 0000000..14b306d --- /dev/null +++ b/DOCS.md @@ -0,0 +1,169 @@ +# Documentation + +## Core Types + +### Router + +Main router that implements fasthttp handler. + +```go +router := router.New() +``` + +### Handler + +Request handler function type. + +```go +type Handler func(ctx Ctx, params []string) +``` + +### Middleware + +Function type for middleware. + +```go +type Middleware func(Handler) Handler +``` + +### Group + +Route group with a prefix. + +```go +group := router.Group("/api") +``` + +## Router Methods + +### New() + +Creates a new router. + +```go +router := router.New() +``` + +### ServeHTTP(ctx) + +Handler for fasthttp requests. + +### Handler() + +Returns a fasthttp.RequestHandler for use with fasthttp.ListenAndServe. + +```go +fasthttp.ListenAndServe(":8080", router.Handler()) +``` + +### Use(mw ...Middleware) + +Adds global middleware. + +```go +router.Use(loggingMiddleware, authMiddleware) +``` + +### Handle(method, path, handler) + +Registers a handler for the given method and path. + +```go +router.Handle("GET", "/users", listUsersHandler) +``` + +### HTTP Method Shortcuts + +```go +router.Get("/users", listUsersHandler) +router.Post("/users", createUserHandler) +router.Put("/users/[id]", updateUserHandler) +router.Patch("/users/[id]", patchUserHandler) +router.Delete("/users/[id]", deleteUserHandler) +``` + +### Group(prefix) + +Creates a route group with prefix. + +```go +api := router.Group("/api") +``` + +### WithMiddleware(mw ...Middleware) + +Applies middleware to the next route registration. + +```go +router.WithMiddleware(authMiddleware).Get("/admin", adminHandler) +``` + +### StandardHandler(handler) + +Adapts a standard fasthttp.RequestHandler to the router's Handler type. + +```go +router.Get("/legacy", router.StandardHandler(legacyHandler)) +``` + +## Group Methods + +### Use(mw ...Middleware) + +Adds middleware to the group. + +```go +api.Use(apiKeyMiddleware) +``` + +### Group(prefix) + +Creates a nested group. + +```go +v1 := api.Group("/v1") +``` + +### HTTP Method Shortcuts + +```go +api.Get("/users", listUsersHandler) +api.Post("/users", createUserHandler) +api.Put("/users/[id]", updateUserHandler) +api.Patch("/users/[id]", patchUserHandler) +api.Delete("/users/[id]", deleteUserHandler) +``` + +### WithMiddleware(mw ...Middleware) + +Applies middleware to the next route registration in this group. + +```go +api.WithMiddleware(authMiddleware).Get("/admin", adminHandler) +``` + +## Path Parameters + +Dynamic segments in paths are defined using square brackets. + +```go +router.Get("/users/[id]", func(ctx router.Ctx, params []string) { + id := params[0] + // ... +}) +``` + +## Wildcards + +Wildcard segments capture all remaining path segments. + +```go +router.Get("/files/*path", func(ctx router.Ctx, params []string) { + path := params[0] + // ... +}) +``` + +Notes: +- Wildcards must be the last segment in a path +- Only one wildcard is allowed per path diff --git a/EXAMPLES.md b/EXAMPLES.md new file mode 100644 index 0000000..8ac5ec5 --- /dev/null +++ b/EXAMPLES.md @@ -0,0 +1,304 @@ +# Examples + +## Basic Usage + +```go +package main + +import ( + "fmt" + "github.com/valyala/fasthttp" + "git.sharkk.net/Go/FastRouter" +) + +func main() { + r := router.New() + + r.Get("/", func(ctx router.Ctx, _ []string) { + fmt.Fprintf(ctx, "Hello World!") + }) + + r.Get("/about", func(ctx router.Ctx, _ []string) { + fmt.Fprintf(ctx, "About page") + }) + + fasthttp.ListenAndServe(":8080", r.Handler()) +} +``` + +## Path Parameters + +```go +r := router.New() + +// Single parameter +r.Get("/users/[id]", func(ctx router.Ctx, params []string) { + id := params[0] + fmt.Fprintf(ctx, "User ID: %s", id) +}) + +// Multiple parameters +r.Get("/posts/[category]/[id]", func(ctx router.Ctx, params []string) { + category := params[0] + id := params[1] + fmt.Fprintf(ctx, "Category: %s, Post ID: %s", category, id) +}) + +// Wildcard +r.Get("/files/*path", func(ctx router.Ctx, params []string) { + path := params[0] + fmt.Fprintf(ctx, "File path: %s", path) +}) +``` + +## Middleware + +```go +// Logging middleware +func LoggingMiddleware(next router.Handler) router.Handler { + return func(ctx router.Ctx, params []string) { + fmt.Printf("[%s] %s\n", string(ctx.Method()), string(ctx.Path())) + next(ctx, params) + } +} + +// Auth middleware +func AuthMiddleware(next router.Handler) router.Handler { + return func(ctx router.Ctx, params []string) { + token := string(ctx.Request.Header.Peek("Authorization")) + if token == "" { + ctx.Error("Unauthorized", fasthttp.StatusUnauthorized) + return + } + next(ctx, params) + } +} + +// Global middleware +r := router.New() +r.Use(LoggingMiddleware) + +// Route-specific middleware +r.WithMiddleware(AuthMiddleware).Get("/admin", adminHandler) +``` + +## Route Groups + +```go +r := router.New() + +// API group +api := r.Group("/api") +api.Get("/status", statusHandler) + +// Versioned API +v1 := api.Group("/v1") +v1.Get("/users", listUsersHandler) +v1.Post("/users", createUserHandler) + +v2 := api.Group("/v2") +v2.Get("/users", listUsersV2Handler) +``` + +## Using Standard fasthttp Handlers + +```go +r := router.New() + +// Using existing fasthttp handlers +r.Get("/legacy", router.StandardHandler(func(ctx *fasthttp.RequestCtx) { + fmt.Fprintf(ctx, "Legacy fasthttp handler") +})) + +// Mixing router handlers with standard handlers +r.Get("/users/[id]", func(ctx router.Ctx, params []string) { + // Get ID from params + id := params[0] + + // Pass to standard handler + standardUserHandler := router.StandardHandler(func(ctx *fasthttp.RequestCtx) { + fmt.Fprintf(ctx, "Processing user: %s", id) + }) + + standardUserHandler(ctx, params) +}) +``` + +## Combining Features + +```go +r := router.New() + +// Global middleware +r.Use(LoggingMiddleware) + +// API group with middleware +api := r.Group("/api") +api.Use(ApiKeyMiddleware) + +// Admin group with auth middleware +admin := r.Group("/admin") +admin.Use(AuthMiddleware) + +// Users endpoints with versioning +users := api.Group("/v1/users") +users.Get("/", listUsersHandler) +users.Post("/", createUserHandler) +users.Get("/[id]", getUserHandler) +users.Put("/[id]", updateUserHandler) +users.Delete("/[id]", deleteUserHandler) + +// Special case with route-specific middleware +api.WithMiddleware(CacheMiddleware).Get("/cached-resource", cachedResourceHandler) +``` + +## Error Handling + +```go +r := router.New() + +err := r.Get("/users/[id]", getUserHandler) +if err != nil { + // Handle error +} + +// Custom NotFound handler +r.ServeHTTP = func(ctx *fasthttp.RequestCtx) { + path := string(ctx.Path()) + method := string(ctx.Method()) + + h, params, found := r.Lookup(method, path) + if !found { + // Custom 404 handler + ctx.SetStatusCode(fasthttp.StatusNotFound) + fmt.Fprintf(ctx, "Custom 404: %s not found", path) + return + } + h(ctx, params) +} +``` + +## Complete Application Example + +```go +package main + +import ( + "fmt" + "log" + + "github.com/valyala/fasthttp" + "github.com/yourusername/fastrouter" +) + +func main() { + r := router.New() + + // Global middleware + r.Use(LoggingMiddleware) + + // Basic routes + r.Get("/", homeHandler) + r.Get("/about", aboutHandler) + + // API routes + api := r.Group("/api") + api.Use(ApiKeyMiddleware) + + // Users API + users := api.Group("/users") + users.Get("/", listUsersHandler) + users.Post("/", createUserHandler) + users.Get("/[id]", getUserHandler) + users.Put("/[id]", updateUserHandler) + users.Delete("/[id]", deleteUserHandler) + + // Admin routes with auth + admin := r.Group("/admin") + admin.Use(AuthMiddleware) + admin.Get("/", adminDashboardHandler) + admin.Get("/users", adminUsersHandler) + + // Start server + log.Println("Server starting on :8080") + log.Fatal(fasthttp.ListenAndServe(":8080", r.Handler())) +} + +// Handlers +func homeHandler(ctx router.Ctx, _ []string) { + fmt.Fprintf(ctx, "Welcome to the home page") +} + +func aboutHandler(ctx router.Ctx, _ []string) { + fmt.Fprintf(ctx, "About us") +} + +func listUsersHandler(ctx router.Ctx, _ []string) { + fmt.Fprintf(ctx, "List of users") +} + +func getUserHandler(ctx router.Ctx, params []string) { + id := params[0] + fmt.Fprintf(ctx, "User details for ID: %s", id) +} + +func createUserHandler(ctx router.Ctx, _ []string) { + // Parse form data or JSON body + // You can use ctx.PostBody() to get the request body + fmt.Fprintf(ctx, "User created") +} + +func updateUserHandler(ctx router.Ctx, params []string) { + id := params[0] + fmt.Fprintf(ctx, "User updated: %s", id) +} + +func deleteUserHandler(ctx router.Ctx, params []string) { + id := params[0] + fmt.Fprintf(ctx, "User deleted: %s", id) +} + +func adminDashboardHandler(ctx router.Ctx, _ []string) { + fmt.Fprintf(ctx, "Admin Dashboard") +} + +func adminUsersHandler(ctx router.Ctx, _ []string) { + fmt.Fprintf(ctx, "Admin Users Management") +} + +// Middleware +func LoggingMiddleware(next router.Handler) router.Handler { + return func(ctx router.Ctx, params []string) { + log.Printf("[%s] %s", string(ctx.Method()), string(ctx.Path())) + next(ctx, params) + } +} + +func ApiKeyMiddleware(next router.Handler) router.Handler { + return func(ctx router.Ctx, params []string) { + apiKey := string(ctx.Request.Header.Peek("X-API-Key")) + if apiKey == "" { + ctx.Error("API key required", fasthttp.StatusUnauthorized) + return + } + next(ctx, params) + } +} + +func AuthMiddleware(next router.Handler) router.Handler { + return func(ctx router.Ctx, params []string) { + // Check session or JWT + authorized := checkUserAuth(ctx) + if !authorized { + ctx.Redirect("/login", fasthttp.StatusSeeOther) + return + } + next(ctx, params) + } +} + +func checkUserAuth(ctx router.Ctx) bool { + // Implementation of auth check + return len(ctx.Request.Header.Peek("Authorization")) > 0 +} +``` diff --git a/router.go b/router.go index 07a6bad..57a742d 100644 --- a/router.go +++ b/router.go @@ -6,39 +6,36 @@ import ( "github.com/valyala/fasthttp" ) -// Ctx is an alias for fasthttp.RequestCtx for shorter, cleaner code type Ctx = *fasthttp.RequestCtx -// Handler is an interface for handling HTTP requests with path parameters. -type Handler interface { - Serve(ctx Ctx, params []string) +// Handler is a request handler with parameters. +type Handler func(ctx Ctx, params []string) + +func (h Handler) Serve(ctx Ctx, params []string) { + h(ctx, params) } -// 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 { - segment string // the path segment this node matches - handler Handler // handler for this path, if it's an endpoint - children []*node // child nodes for subsequent path segments - isDynamic bool // true for param segments like [id] - isWildcard bool // true for catch-all segments like *filepath - maxParams uint8 // maximum number of parameters in paths under this node + segment string + handler Handler + children []*node + isDynamic bool + isWildcard bool + maxParams uint8 } -// Router routes HTTP requests by method and path. -// 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 + get *node + post *node + put *node + patch *node + delete *node + middleware []Middleware + paramsBuffer []string // Pre-allocated buffer for parameters } -// Group represents a route group with a path prefix and shared middleware. type Group struct { router *Router prefix string @@ -48,12 +45,13 @@ type Group struct { // New creates a new Router instance. func New() *Router { return &Router{ - get: &node{}, - post: &node{}, - put: &node{}, - patch: &node{}, - delete: &node{}, - middleware: []Middleware{}, + get: &node{}, + post: &node{}, + put: &node{}, + patch: &node{}, + delete: &node{}, + middleware: []Middleware{}, + paramsBuffer: make([]string, 64), } } @@ -62,13 +60,13 @@ func (r *Router) ServeHTTP(ctx *fasthttp.RequestCtx) { path := string(ctx.Path()) method := string(ctx.Method()) - handler, params, found := r.Lookup(method, path) + h, params, found := r.Lookup(method, path) if !found { ctx.SetStatusCode(fasthttp.StatusNotFound) return } - handler.Serve(ctx, params) + h(ctx, params) } // Handler returns a fasthttp request handler @@ -76,38 +74,24 @@ func (r *Router) Handler() fasthttp.RequestHandler { return r.ServeHTTP } -// simpleHandler implements the Handler interface -type simpleHandler struct { - fn func(ctx Ctx, params []string) -} - -// Serve executes the handler function with params -func (h *simpleHandler) Serve(ctx Ctx, params []string) { - h.fn(ctx, params) -} - -// Use adds middleware to the router's global middleware stack. -func (r *Router) Use(middleware ...Middleware) *Router { - r.middleware = append(r.middleware, middleware...) +// Use adds middleware to the router. +func (r *Router) Use(mw ...Middleware) *Router { + r.middleware = append(r.middleware, mw...) return r } -// Group creates a new route group with the given path prefix. +// Group creates a new route group. func (r *Router) Group(prefix string) *Group { - return &Group{ - router: r, - prefix: prefix, - middleware: []Middleware{}, - } + return &Group{router: r, prefix: prefix, middleware: []Middleware{}} } -// Use adds middleware to the group's middleware stack. -func (g *Group) Use(middleware ...Middleware) *Group { - g.middleware = append(g.middleware, middleware...) +// Use adds middleware to the group. +func (g *Group) Use(mw ...Middleware) *Group { + g.middleware = append(g.middleware, mw...) return g } -// Group creates a nested group with an additional prefix. +// Group creates a nested group. func (g *Group) Group(prefix string) *Group { return &Group{ router: g.router, @@ -116,28 +100,23 @@ func (g *Group) Group(prefix string) *Group { } } -// 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) +// applyMiddleware applies middleware in reverse order. +func applyMiddleware(h Handler, mw []Middleware) Handler { + for i := len(mw) - 1; i >= 0; i-- { + h = mw[i](h) } return h } -// HandlerFunc is a function that handles HTTP requests with parameters. -type HandlerFunc func(ctx Ctx, params []string) - // Handle registers a handler for the given method and path. -func (r *Router) Handle(method, path string, handler HandlerFunc) error { +func (r *Router) Handle(method, path string, h Handler) error { root := r.methodNode(method) if root == nil { return fmt.Errorf("unsupported method: %s", method) } - return r.addRoute(root, path, &simpleHandler{fn: handler}, r.middleware) + return r.addRoute(root, path, h, r.middleware) } -// methodNode returns the root node for the given HTTP method. func (r *Router) methodNode(method string) *node { switch method { case "GET": @@ -155,335 +134,302 @@ func (r *Router) methodNode(method string) *node { } } -// Get registers a handler for GET requests at the given path. -func (r *Router) Get(path string, handler HandlerFunc) error { - return r.Handle("GET", path, handler) +// Get registers a GET handler. +func (r *Router) Get(path string, h Handler) error { + return r.Handle("GET", path, h) } -// Post registers a handler for POST requests at the given path. -func (r *Router) Post(path string, handler HandlerFunc) error { - return r.Handle("POST", path, handler) +// Post registers a POST handler. +func (r *Router) Post(path string, h Handler) error { + return r.Handle("POST", path, h) } -// Put registers a handler for PUT requests at the given path. -func (r *Router) Put(path string, handler HandlerFunc) error { - return r.Handle("PUT", path, handler) +// Put registers a PUT handler. +func (r *Router) Put(path string, h Handler) error { + return r.Handle("PUT", path, h) } -// Patch registers a handler for PATCH requests at the given path. -func (r *Router) Patch(path string, handler HandlerFunc) error { - return r.Handle("PATCH", path, handler) +// Patch registers a PATCH handler. +func (r *Router) Patch(path string, h Handler) error { + return r.Handle("PATCH", path, h) } -// Delete registers a handler for DELETE requests at the given path. -func (r *Router) Delete(path string, handler HandlerFunc) error { - return r.Handle("DELETE", path, handler) +// Delete registers a DELETE handler. +func (r *Router) Delete(path string, h Handler) error { + return r.Handle("DELETE", path, h) } -// buildGroupMiddleware returns combined middleware for the group func (g *Group) buildGroupMiddleware() []Middleware { - middleware := append([]Middleware{}, g.router.middleware...) - return append(middleware, g.middleware...) + mw := append([]Middleware{}, g.router.middleware...) + return append(mw, g.middleware...) } -// Handle registers a handler for the given method and path. -func (g *Group) Handle(method, path string, handler HandlerFunc) error { +// Handle registers a handler in the group. +func (g *Group) Handle(method, path string, h Handler) error { root := g.router.methodNode(method) if root == nil { return fmt.Errorf("unsupported method: %s", method) } - - fullPath := g.prefix + path - return g.router.addRoute(root, fullPath, &simpleHandler{fn: handler}, g.buildGroupMiddleware()) + return g.router.addRoute(root, g.prefix+path, h, g.buildGroupMiddleware()) } -// Get registers a handler for GET requests at the given path. -func (g *Group) Get(path string, handler HandlerFunc) error { - return g.Handle("GET", path, handler) +// Get registers a GET handler in the group. +func (g *Group) Get(path string, h Handler) error { + return g.Handle("GET", path, h) } -// Post registers a handler for POST requests at the given path. -func (g *Group) Post(path string, handler HandlerFunc) error { - return g.Handle("POST", path, handler) +// Post registers a POST handler in the group. +func (g *Group) Post(path string, h Handler) error { + return g.Handle("POST", path, h) } -// Put registers a handler for PUT requests at the given path. -func (g *Group) Put(path string, handler HandlerFunc) error { - return g.Handle("PUT", path, handler) +// Put registers a PUT handler in the group. +func (g *Group) Put(path string, h Handler) error { + return g.Handle("PUT", path, h) } -// Patch registers a handler for PATCH requests at the given path. -func (g *Group) Patch(path string, handler HandlerFunc) error { - return g.Handle("PATCH", path, handler) +// Patch registers a PATCH handler in the group. +func (g *Group) Patch(path string, h Handler) error { + return g.Handle("PATCH", path, h) } -// Delete registers a handler for DELETE requests at the given path. -func (g *Group) Delete(path string, handler HandlerFunc) error { - return g.Handle("DELETE", path, handler) +// Delete registers a DELETE handler in the group. +func (g *Group) Delete(path string, h Handler) error { + return g.Handle("DELETE", path, h) } -// WithMiddleware applies specific middleware to the next route registration. -func (r *Router) WithMiddleware(middleware ...Middleware) *MiddlewareRouter { - return &MiddlewareRouter{ - router: r, - middleware: middleware, - } +// WithMiddleware applies specific middleware for next registration. +func (r *Router) WithMiddleware(mw ...Middleware) *MiddlewareRouter { + return &MiddlewareRouter{router: r, middleware: mw} } -// WithMiddleware applies specific middleware to the next route registration. -func (g *Group) WithMiddleware(middleware ...Middleware) *MiddlewareGroup { - return &MiddlewareGroup{ - group: g, - middleware: middleware, - } +// WithMiddleware applies specific middleware for next group route. +func (g *Group) WithMiddleware(mw ...Middleware) *MiddlewareGroup { + return &MiddlewareGroup{group: g, middleware: mw} } -// MiddlewareRouter handles route registration with specific middleware. type MiddlewareRouter struct { router *Router middleware []Middleware } -// MiddlewareGroup handles group route registration with specific middleware. type MiddlewareGroup struct { group *Group middleware []Middleware } -// buildMiddleware returns combined middleware for the middleware router func (mr *MiddlewareRouter) buildMiddleware() []Middleware { - middleware := append([]Middleware{}, mr.router.middleware...) - return append(middleware, mr.middleware...) + mw := append([]Middleware{}, mr.router.middleware...) + return append(mw, mr.middleware...) } -// Handle registers a handler for the given method and path. -func (mr *MiddlewareRouter) Handle(method, path string, handler HandlerFunc) error { +// Handle registers a handler with middleware router. +func (mr *MiddlewareRouter) Handle(method, path string, h Handler) error { root := mr.router.methodNode(method) if root == nil { return fmt.Errorf("unsupported method: %s", method) } - - return mr.router.addRoute(root, path, &simpleHandler{fn: handler}, mr.buildMiddleware()) + return mr.router.addRoute(root, path, h, mr.buildMiddleware()) } -// Get registers a handler for GET requests with specific middleware. -func (mr *MiddlewareRouter) Get(path string, handler HandlerFunc) error { - return mr.Handle("GET", path, handler) +// Get registers a GET handler with middleware router. +func (mr *MiddlewareRouter) Get(path string, h Handler) error { + return mr.Handle("GET", path, h) } -// Post registers a handler for POST requests with specific middleware. -func (mr *MiddlewareRouter) Post(path string, handler HandlerFunc) error { - return mr.Handle("POST", path, handler) +// Post registers a POST handler with middleware router. +func (mr *MiddlewareRouter) Post(path string, h Handler) error { + return mr.Handle("POST", path, h) } -// Put registers a handler for PUT requests with specific middleware. -func (mr *MiddlewareRouter) Put(path string, handler HandlerFunc) error { - return mr.Handle("PUT", path, handler) +// Put registers a PUT handler with middleware router. +func (mr *MiddlewareRouter) Put(path string, h Handler) error { + return mr.Handle("PUT", path, h) } -// Patch registers a handler for PATCH requests with specific middleware. -func (mr *MiddlewareRouter) Patch(path string, handler HandlerFunc) error { - return mr.Handle("PATCH", path, handler) +// Patch registers a PATCH handler with middleware router. +func (mr *MiddlewareRouter) Patch(path string, h Handler) error { + return mr.Handle("PATCH", path, h) } -// Delete registers a handler for DELETE requests with specific middleware. -func (mr *MiddlewareRouter) Delete(path string, handler HandlerFunc) error { - return mr.Handle("DELETE", path, handler) +// Delete registers a DELETE handler with middleware router. +func (mr *MiddlewareRouter) Delete(path string, h Handler) error { + return mr.Handle("DELETE", path, h) } -// buildMiddleware returns combined middleware for the middleware group func (mg *MiddlewareGroup) buildMiddleware() []Middleware { - middleware := append([]Middleware{}, mg.group.router.middleware...) - middleware = append(middleware, mg.group.middleware...) - return append(middleware, mg.middleware...) + mw := append([]Middleware{}, mg.group.router.middleware...) + mw = append(mw, mg.group.middleware...) + return append(mw, mg.middleware...) } -// Handle registers a handler for the given method and path. -func (mg *MiddlewareGroup) Handle(method, path string, handler HandlerFunc) error { +// Handle registers a handler with middleware group. +func (mg *MiddlewareGroup) Handle(method, path string, h Handler) error { root := mg.group.router.methodNode(method) if root == nil { return fmt.Errorf("unsupported method: %s", method) } - - fullPath := mg.group.prefix + path - return mg.group.router.addRoute(root, fullPath, &simpleHandler{fn: handler}, mg.buildMiddleware()) + return mg.group.router.addRoute(root, mg.group.prefix+path, h, mg.buildMiddleware()) } -// Get registers a handler for GET requests with specific middleware. -func (mg *MiddlewareGroup) Get(path string, handler HandlerFunc) error { - return mg.Handle("GET", path, handler) +// Get registers a GET handler with middleware group. +func (mg *MiddlewareGroup) Get(path string, h Handler) error { + return mg.Handle("GET", path, h) } -// Post registers a handler for POST requests with specific middleware. -func (mg *MiddlewareGroup) Post(path string, handler HandlerFunc) error { - return mg.Handle("POST", path, handler) +// Post registers a POST handler with middleware group. +func (mg *MiddlewareGroup) Post(path string, h Handler) error { + return mg.Handle("POST", path, h) } -// Put registers a handler for PUT requests with specific middleware. -func (mg *MiddlewareGroup) Put(path string, handler HandlerFunc) error { - return mg.Handle("PUT", path, handler) +// Put registers a PUT handler with middleware group. +func (mg *MiddlewareGroup) Put(path string, h Handler) error { + return mg.Handle("PUT", path, h) } -// Patch registers a handler for PATCH requests with specific middleware. -func (mg *MiddlewareGroup) Patch(path string, handler HandlerFunc) error { - return mg.Handle("PATCH", path, handler) +// Patch registers a PATCH handler with middleware group. +func (mg *MiddlewareGroup) Patch(path string, h Handler) error { + return mg.Handle("PATCH", path, h) } -// Delete registers a handler for DELETE requests with specific middleware. -func (mg *MiddlewareGroup) Delete(path string, handler HandlerFunc) error { - return mg.Handle("DELETE", path, handler) +// Delete registers a DELETE handler with middleware group. +func (mg *MiddlewareGroup) Delete(path string, h Handler) error { + return mg.Handle("DELETE", path, h) } -// StandardHandler adapts a standard fasthttp.RequestHandler to the router's HandlerFunc -func StandardHandler(handler fasthttp.RequestHandler) HandlerFunc { +// StandardHandler adapts a standard fasthttp.RequestHandler to the router's Handler +func StandardHandler(handler fasthttp.RequestHandler) Handler { return func(ctx Ctx, _ []string) { handler(ctx) } } -// readSegment extracts the next path segment starting at the given position. -// Returns the segment, the position after it, and whether there are more segments. +// readSegment extracts the next path segment. func readSegment(path string, start int) (segment string, end int, hasMore bool) { if start >= len(path) { return "", start, false } - if path[start] == '/' { start++ } - if start >= len(path) { return "", start, false } - end = start for end < len(path) && path[end] != '/' { end++ } - 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 trie. +func (r *Router) addRoute(root *node, path string, h Handler, mw []Middleware) error { + h = applyMiddleware(h, mw) if path == "/" { - root.handler = wrappedHandler + root.handler = h return nil } - current := root pos := 0 - var lastWildcard bool - paramsCount := uint8(0) - + lastWC := false + count := uint8(0) for { - segment, newPos, hasMore := readSegment(path, pos) - if segment == "" { + seg, newPos, more := readSegment(path, pos) + if seg == "" { break } - - isDynamic := len(segment) > 2 && segment[0] == '[' && segment[len(segment)-1] == ']' - isWildcard := len(segment) > 0 && segment[0] == '*' - - if isWildcard { - if lastWildcard { + isDyn := len(seg) > 2 && seg[0] == '[' && seg[len(seg)-1] == ']' + isWC := len(seg) > 0 && seg[0] == '*' + if isWC { + if lastWC || more { return fmt.Errorf("wildcard must be the last segment in the path") } - if hasMore { - return fmt.Errorf("wildcard must be the last segment in the path") - } - lastWildcard = true + lastWC = true } - - if isDynamic || isWildcard { - paramsCount++ + if isDyn || isWC { + count++ } - var child *node - for _, n := range current.children { - if n.segment == segment { - child = n + for _, c := range current.children { + if c.segment == seg { + child = c break } } - if child == nil { - child = &node{ - segment: segment, - isDynamic: isDynamic, - isWildcard: isWildcard, - } + child = &node{segment: seg, isDynamic: isDyn, isWildcard: isWC} current.children = append(current.children, child) } - - if child.maxParams < paramsCount { - child.maxParams = paramsCount + if child.maxParams < count { + child.maxParams = count } current = child pos = newPos } - - current.handler = wrappedHandler + current.handler = h return nil } -// Lookup finds a handler matching the given method and path. -// Returns the handler, any captured parameters, and whether a match was found. +// Lookup finds a handler matching method and path. func (r *Router) Lookup(method, path string) (Handler, []string, bool) { root := r.methodNode(method) if root == nil { return nil, nil, false } - if path == "/" { - return root.handler, []string{}, root.handler != nil + return root.handler, nil, root.handler != nil } - params := make([]string, 0, root.maxParams) - h, found := match(root, path, 0, ¶ms) + buffer := r.paramsBuffer + if cap(buffer) < int(root.maxParams) { + buffer = make([]string, root.maxParams) + r.paramsBuffer = buffer + } + buffer = buffer[:0] + + h, paramCount, found := match(root, path, 0, &buffer) if !found { return nil, nil, false } - return h, params, true + + return h, buffer[:paramCount], true } -// match recursively traverses the prefix tree to find a matching handler. -// It populates params with any captured path parameters or wildcard matches. -func match(current *node, path string, start int, params *[]string) (Handler, bool) { - // Check for wildcard children first - for _, child := range current.children { - if child.isWildcard { - remaining := path[start:] - if len(remaining) > 0 && remaining[0] == '/' { - remaining = remaining[1:] +// match traverses the trie to find a handler. +func match(current *node, path string, start int, params *[]string) (Handler, int, bool) { + paramCount := 0 + + for _, c := range current.children { + if c.isWildcard { + rem := path[start:] + if len(rem) > 0 && rem[0] == '/' { + rem = rem[1:] } - *params = append(*params, remaining) - return child.handler, child.handler != nil + *params = append(*params, rem) + return c.handler, 1, c.handler != nil } } - // Read current segment - segment, pos, hasMore := readSegment(path, start) - if segment == "" { - return current.handler, current.handler != nil + seg, pos, more := readSegment(path, start) + if seg == "" { + return current.handler, 0, current.handler != nil } - // Try to match children - for _, child := range current.children { - if child.segment == segment || child.isDynamic { - if child.isDynamic { - *params = append(*params, segment) + for _, c := range current.children { + if c.segment == seg || c.isDynamic { + if c.isDynamic { + *params = append(*params, seg) + paramCount++ } - if !hasMore { - return child.handler, child.handler != nil + if !more { + return c.handler, paramCount, c.handler != nil } - if h, found := match(child, path, pos, params); found { - return h, true + h, nestedCount, ok := match(c, path, pos, params) + if ok { + return h, paramCount + nestedCount, true } } } - return nil, false + return nil, 0, false } diff --git a/router_test.go b/router_test.go index 271460a..95c8229 100644 --- a/router_test.go +++ b/router_test.go @@ -9,18 +9,9 @@ import ( "github.com/valyala/fasthttp/fasthttputil" ) -// simpleHandler implements the Handler interface -type testHandler struct { - fn func(ctx Ctx, params []string) -} - -func (h *testHandler) Serve(ctx Ctx, params []string) { - h.fn(ctx, params) -} - // newHandler creates a simple Handler from a function func newHandler(fn func(ctx Ctx, params []string)) Handler { - return &testHandler{fn: fn} + return fn } // performRequest is a helper function to test the router