README
This commit is contained in:
177
COMPARISON.md
177
COMPARISON.md
@@ -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.
|
||||
283
README.md
283
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)
|
||||
http.ListenAndServe(":8080", r)
|
||||
}
|
||||
|
||||
func Hello(w http.ResponseWriter, r *http.Request) {
|
||||
io.WriteString(w, "hello")
|
||||
}
|
||||
r.GET("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("hello"))
|
||||
})
|
||||
|
||||
func Simple(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.URL.Query().Get("name")
|
||||
io.WriteString(w, "hello "+name)
|
||||
}
|
||||
```
|
||||
|
||||
### URL Parameters
|
||||
|
||||
```go
|
||||
r := sux.New()
|
||||
|
||||
// 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)
|
||||
w.Write([]byte("user " + params.Get("id")))
|
||||
})
|
||||
|
||||
http.ListenAndServe(":8080", r)
|
||||
}
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
Middleware wraps handlers and can be applied globally or per route group.
|
||||
|
||||
```go
|
||||
logger := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// log request here
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user