diff --git a/COMPARISON.md b/COMPARISON.md deleted file mode 100644 index f4746ca..0000000 --- a/COMPARISON.md +++ /dev/null @@ -1,177 +0,0 @@ -# Sux Router Comparison - -This document compares Sux with other popular Go HTTP routers across various dimensions including performance, features, and ease of use. - -## Performance Comparison - -Based on benchmark tests and the original README data: - -### Static Routes (ns/op) -- **Sux**: 798.5 ns/op (optimized with hash map) -- **httprouter**: ~900 ns/op (estimated from README) -- **gorilla/mux**: ~2000+ ns/op -- **chi**: ~1200 ns/op -- **gin**: ~1000 ns/op - -### Memory Allocations -- **Sux**: 644 B/op, 8 allocs/op (static routes) -- **httprouter**: Similar allocation pattern -- **gorilla/mux**: Higher allocations due to regex matching -- **chi**: Moderate allocations -- **gin**: Higher allocations due to more features - -**Sux Performance Advantage**: Hash map optimization provides O(1) child lookups vs O(n) linear search - -## Feature Comparison - -| Feature | Sux | httprouter | gorilla/mux | chi | gin | -|---------|-----|------------|-------------|-----|-----| -| Static Routes | ✅ | ✅ | ✅ | ✅ | ✅ | -| URL Parameters (`:id`) | ✅ | ✅ | ✅ | ✅ | ✅ | -| Wildcard Parameters (`*path`) | ✅ | ❌ | ✅ | ✅ | ✅ | -| Middleware Support | ✅ | ❌ | ❌ | ✅ | ✅ | -| Route Groups | ✅ | ❌ | ❌ | ✅ | ✅ | -| Method Not Allowed (405) | ✅ | ✅ | ❌ | ✅ | ✅ | -| Custom 404/405 Handlers | ✅ | ❌ | ✅ | ✅ | ✅ | -| Thread-Safe (no global state) | ✅ | ✅ | ✅ | ✅ | ✅ | -| Regex Support | ❌ | ❌ | ✅ | ❌ | ❌ | -| Subrouter Support | ✅ | ❌ | ✅ | ✅ | ✅ | - -## Performance Analysis - -### Sux Advantages: -1. **Minimal Allocations**: Uses sync.Pool for parameter maps, reducing GC pressure -2. **Hash Map Optimization**: O(1) child lookups vs O(n) linear search in other routers -3. **Efficient Trie Structure**: Optimized for fast static route matching -4. **Lightweight Middleware**: Minimal overhead for middleware chains -5. **Zero Dependencies**: No external dependencies, smaller binary size - -### Where Others Excel: -1. **gorilla/mux**: More flexible regex patterns -2. **chi**: More mature middleware ecosystem -3. **gin**: More features and plugins -4. **httprouter**: Slightly better for pure static routing - -## Real-World Performance - -From the original README benchmarks: - -``` -Sux: 142,025.60 requests/sec -httprouter: 140,826.15 requests/sec -gomango: 110,315.36 requests/sec -gorilla: 108,078.84 requests/sec -``` - -Sux maintains excellent performance while adding more features than most competitors. - -## Code Complexity - -### Sux (Lines of Code) -- Core router: ~400 lines -- Tests: ~267 lines -- Total: ~667 lines - -### Comparison -- **httprouter**: ~800 lines (core only) -- **gorilla/mux**: ~2000+ lines -- **chi**: ~1500+ lines -- **gin**: ~3000+ lines (full framework) - -Sux achieves feature parity with significantly less code complexity. - -## API Design Comparison - -### Sux API -```go -r := sux.New() -r.GET("/users/:id", handler) -r.Use(middleware) -api := r.Group("/api", apiMiddleware) -``` - -### httprouter API -```go -r := httprouter.New() -r.GET("/users/:id", handler) -// No built-in middleware or groups -``` - -### gorilla/mux API -```go -r := mux.NewRouter() -r.HandleFunc("/users/{id}", handler).Methods("GET") -// No built-in middleware, requires third-party -``` - -### chi API -```go -r := chi.NewRouter() -r.Get("/users/{id}", handler) -r.Use(middleware) -r.Route("/api", func(r chi.Router) { - // Subroutes -}) -``` - -## Use Case Recommendations - -### Choose Sux when: -- You need high performance with minimal dependencies -- You want middleware and route groups -- You prefer a simple, clean API -- Thread safety is important -- You need both static and parameter routes - -### Choose httprouter when: -- You only need basic routing -- Maximum performance is the only concern -- You don't need middleware or groups - -### Choose gorilla/mux when: -- You need complex regex patterns -- You're maintaining existing code that uses it -- You need maximum flexibility - -### Choose chi when: -- You want a mature middleware ecosystem -- You prefer standard library compatibility -- You need extensive routing features - -### Choose gin when: -- You want a full-featured framework -- You need extensive plugins and middleware -- You prefer batteries-included approach - -## Migration Path - -### From httprouter to Sux: -```go -// httprouter -r.GET("/users/:id", handler) - -// Sux (almost identical) -r := sux.New() -r.GET("/users/:id", handler) -``` - -### From gorilla/mux to Sux: -```go -// gorilla/mux -r.HandleFunc("/users/{id}", handler).Methods("GET") - -// Sux (simpler) -r.GET("/users/:id", handler) -``` - -## Conclusion - -Sux offers an excellent balance of performance, features, and simplicity: - -1. **Performance**: Matches or exceeds all major routers -2. **Features**: Provides essential features (middleware, groups, parameters) -3. **Simplicity**: Clean API with minimal learning curve -4. **Maintainability**: Small codebase, easy to understand and modify -5. **Thread Safety**: No global state, safe for concurrent use - -For most use cases requiring high performance with modern routing features, Sux provides an optimal solution that combines the best aspects of performance-focused and feature-rich routers. \ No newline at end of file diff --git a/README.md b/README.md index a49fe58..857f3eb 100644 --- a/README.md +++ b/README.md @@ -1,243 +1,120 @@ -# Sux +# sux -HTTP router that considers the request method with support for parameters, middleware, and route groups. +An allocation-conscious, middleware-capable HTTP router for Go with support for static routes, parameters (`:id`), wildcards (`*path`), route groups, and configurable 404/405 handling. -Useful for serving all kinds of content. +## Installation -## Features +```bash +go get code.icod.de/dalu/sux +``` -- **High Performance**: Optimized trie-based routing with minimal allocations -- **URL Parameters**: Support for `:param` and `*wildcard` parameters -- **Middleware**: Global and route-specific middleware support -- **Route Groups**: Group routes with shared prefixes and middleware -- **Thread-Safe**: No global state - multiple router instances supported -- **Method Not Allowed**: Proper 405 responses when path exists but method doesn't -- **Custom Handlers**: Custom 404 and 405 handlers - -## How - -### Basic Usage +## Quick start ```go package main import ( - "code.icod.de/dalu/sux" - "io" "net/http" + + "code.icod.de/dalu/sux" ) func main() { r := sux.New() - r.GET("/", Hello) - r.GET("/simple", Simple) + + r.GET("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("hello")) + }) + + r.GET("/users/:id", func(w http.ResponseWriter, r *http.Request) { + params := sux.ParamsFromContext(r) + w.Write([]byte("user " + params.Get("id"))) + }) + http.ListenAndServe(":8080", r) } - -func Hello(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "hello") -} - -func Simple(w http.ResponseWriter, r *http.Request) { - name := r.URL.Query().Get("name") - io.WriteString(w, "hello "+name) -} ``` -### URL Parameters +## Middleware + +Middleware wraps handlers and can be applied globally or per route group. ```go -r := sux.New() +logger := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // log request here + next.ServeHTTP(w, r) + }) +} -// Named parameters -r.GET("/users/:id", func(w http.ResponseWriter, r *http.Request) { - params := sux.ParamsFromContext(r) - id := params.Get("id") - io.WriteString(w, "User ID: "+id) +r := sux.New() +r.Use(logger) // global + +r.GET("/ping", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("pong")) +}) +``` + +## Route groups + +Groups share a prefix and middleware. + +```go +api := r.Group("/api", func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-API-Version", "v1") + next.ServeHTTP(w, r) + }) +}) + +api.GET("/users", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("users")) +}) +``` + +## Parameters and wildcards + +```go +r.GET("/posts/:postId", func(w http.ResponseWriter, r *http.Request) { + p := sux.ParamsFromContext(r) + w.Write([]byte(p.Get("postId"))) }) -// Wildcard parameters (captures rest of path) r.GET("/files/*path", func(w http.ResponseWriter, r *http.Request) { - params := sux.ParamsFromContext(r) - path := params.Get("path") - io.WriteString(w, "File path: "+path) -}) - -// Multiple parameters -r.GET("/users/:userId/posts/:postId", func(w http.ResponseWriter, r *http.Request) { - params := sux.ParamsFromContext(r) - userId := params.Get("userId") - postId := params.Get("postId") - io.WriteString(w, "User "+userId+", Post "+postId) + p := sux.ParamsFromContext(r) + w.Write([]byte("file: " + p.Get("path"))) }) ``` -### Middleware +## 404/405 handlers ```go -r := sux.New() - -// Global middleware -r.Use(loggingMiddleware, authMiddleware) - -// Route-specific middleware -r.GET("/admin", adminOnlyMiddleware(adminHandler)) - -func loggingMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Printf("%s %s", r.Method, r.URL.Path) - next.ServeHTTP(w, r) - }) -} - -func authMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Check authentication - if !isAuthenticated(r) { - http.Error(w, "Unauthorized", http.StatusUnauthorized) - return - } - next.ServeHTTP(w, r) - }) -} -``` - -### Route Groups - -```go -r := sux.New() - -// API group with middleware -api := r.Group("/api", apiVersionMiddleware, corsMiddleware) - -// Group routes automatically get the prefix and middleware -api.GET("/users", listUsers) -api.POST("/users", createUser) -api.GET("/users/:id", getUser) - -// Nested groups -v1 := api.Group("/v1") -v1.GET("/posts", listPostsV1) - -v2 := api.Group("/v2") -v2.GET("/posts", listPostsV2) -``` - -### Custom Handlers - -```go -r := sux.New() - -// Custom 404 handler r.NotFound(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - w.Write([]byte("Custom 404 - Page not found")) + http.Error(w, "custom 404", http.StatusNotFound) }) -// Custom 405 handler r.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte("Custom 405 - Method not allowed")) + http.Error(w, "custom 405", http.StatusMethodNotAllowed) }) + +// Disable cross-method probing if you prefer 404s instead of 405 checks +r.EnableMethodNotAllowedCheck(false) ``` -### Multiple Router Instances +## Performance notes -```go -// Each router is independent and thread-safe -mainRouter := sux.New() -mainRouter.GET("/", homeHandler) +- Internally pools parsed segments and parameter storage to reduce per-request allocations. +- Parameters are copied into the request context so they remain valid even after handlers return (important for async users of the context). +- For raw numbers, run `go test -bench . -benchmem`. -apiRouter := sux.New() -apiRouter.GET("/users", usersHandler) +## Testing -// Use different routers for different purposes -go http.ListenAndServe(":8080", mainRouter) -go http.ListenAndServe(":8081", apiRouter) +```bash +go test ./... ``` -## Performance - -The router is optimized for high performance with hash map-based O(1) child lookups and zero-allocation optimizations: +Benchmarks: +```bash +go test -bench . -benchmem ``` -BenchmarkStaticRoute-8 1859550 682.1 ns/op 740 B/op 10 allocs/op -BenchmarkParameterRoute-8 1538643 868.9 ns/op 704 B/op 9 allocs/op -BenchmarkWildcardRoute-8 1281626 979.3 ns/op 736 B/op 10 allocs/op -BenchmarkMultipleParameters-8 1000000 1026 ns/op 768 B/op 9 allocs/op -BenchmarkMiddleware-8 567559 1930 ns/op 1568 B/op 19 allocs/op -BenchmarkRouteGroups-8 867961 1442 ns/op 1480 B/op 15 allocs/op -BenchmarkLargeRouter-8 1548286 833.0 ns/op 704 B/op 9 allocs/op -``` - -**Performance Improvements:** -- Static routes: 14.6% faster (682.1 vs 798.5 ns/op) -- Parameter routes: 24.7% faster (868.9 vs 1154 ns/op) -- Wildcard routes: 41.6% faster (979.3 vs 1676 ns/op) -- Multiple parameters: 41.5% faster (1026 vs 1753 ns/op) -- Middleware: 49.0% faster (1930 vs 3782 ns/op) -- Route groups: 49.5% faster (1442 vs 2855 ns/op) -- Large router: 24.5% faster (833.0 vs 1103 ns/op) - -**Memory Allocation Improvements:** -- Optimized parameter handling with slice-based storage -- Zero-allocation path parsing for common cases -- Enhanced memory pooling for frequently used objects -- String builder optimization for wildcard parameters - -### Performance Comparison - -see [COMPARISON.md](COMPARISON.md) - -## API Reference - -### Router Methods - -- `New() *Router` - Creates a new router instance -- `GET(path string, handler http.HandlerFunc) *Router` -- `POST(path string, handler http.HandlerFunc) *Router` -- `PUT(path string, handler http.HandlerFunc) *Router` -- `PATCH(path string, handler http.HandlerFunc) *Router` -- `DELETE(path string, handler http.HandlerFunc) *Router` -- `OPTIONS(path string, handler http.HandlerFunc) *Router` -- `HEAD(path string, handler http.HandlerFunc) *Router` - -### Middleware - -- `Use(middleware ...MiddlewareFunc)` - Add global middleware -- `Group(prefix string, middleware ...MiddlewareFunc) *Router` - Create route group - -### Custom Handlers - -- `NotFound(handler http.HandlerFunc)` - Set custom 404 handler -- `MethodNotAllowed(handler http.HandlerFunc)` - Set custom 405 handler - -### Parameter Extraction - -- `ParamsFromContext(r *http.Request) Params` - Extract route parameters from request - -## Parameter Types - -### Named Parameters (`:param`) - -- Matches a single path segment -- Extracted by name from the context -- Example: `/users/:id` matches `/users/123` - -### Wildcard Parameters (`*param`) - -- Matches one or more path segments -- Captures the rest of the path -- Example: `/files/*path` matches `/files/docs/readme.txt` - -## Thread Safety - -The router is completely thread-safe: - -- No global state -- Multiple router instances can be used concurrently -- Safe for concurrent use from multiple goroutines - -## License - -MIT License - see LICENSE file for details.