259 lines
6.7 KiB
Markdown
259 lines
6.7 KiB
Markdown
# Sux
|
|
|
|
Static route http router that considers the request method with support for parameters, middleware, and route groups.
|
|
|
|
Useful for serving server-side rendered content.
|
|
|
|
## Features
|
|
|
|
- **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
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"code.icod.de/dalu/sux"
|
|
"io"
|
|
"net/http"
|
|
)
|
|
|
|
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")
|
|
}
|
|
|
|
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["id"]
|
|
io.WriteString(w, "User ID: "+id)
|
|
})
|
|
|
|
// Wildcard parameters (captures rest of path)
|
|
r.GET("/files/*path", func(w http.ResponseWriter, r *http.Request) {
|
|
params := sux.ParamsFromContext(r)
|
|
path := params["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["userId"]
|
|
postId := params["postId"]
|
|
io.WriteString(w, "User "+userId+", Post "+postId)
|
|
})
|
|
```
|
|
|
|
### Middleware
|
|
|
|
```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"))
|
|
})
|
|
|
|
// Custom 405 handler
|
|
r.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
w.Write([]byte("Custom 405 - Method not allowed"))
|
|
})
|
|
```
|
|
|
|
### Multiple Router Instances
|
|
|
|
```go
|
|
// Each router is independent and thread-safe
|
|
mainRouter := sux.New()
|
|
mainRouter.GET("/", homeHandler)
|
|
|
|
apiRouter := sux.New()
|
|
apiRouter.GET("/users", usersHandler)
|
|
|
|
// Use different routers for different purposes
|
|
go http.ListenAndServe(":8080", mainRouter)
|
|
go http.ListenAndServe(":8081", apiRouter)
|
|
```
|
|
|
|
## Performance
|
|
|
|
The router is optimized for high performance with hash map-based O(1) child lookups:
|
|
|
|
```
|
|
BenchmarkStaticRoute-8 1821735 798.5 ns/op 644 B/op 8 allocs/op
|
|
BenchmarkParameterRoute-8 1000000 1154 ns/op 576 B/op 6 allocs/op
|
|
BenchmarkWildcardRoute-8 757272 1676 ns/op 656 B/op 8 allocs/op
|
|
BenchmarkMultipleParameters-8 682251 1753 ns/op 768 B/op 8 allocs/op
|
|
BenchmarkMiddleware-8 753614 3782 ns/op 1472 B/op 17 allocs/op
|
|
BenchmarkRouteGroups-8 694045 2855 ns/op 1352 B/op 12 allocs/op
|
|
BenchmarkLargeRouter-8 1000000 1103 ns/op 576 B/op 6 allocs/op
|
|
```
|
|
|
|
**Performance Improvements:**
|
|
- Static routes: 9.3% faster with hash map optimization
|
|
- Multiple parameters: 12.2% faster
|
|
- Large router scenarios: 60.7% faster
|
|
|
|
### Performance Comparison
|
|
|
|
Compared to other popular Go routers:
|
|
|
|
```
|
|
darko@arch ~ $ wrk -c1000 -t8 -d30s http://inuc:8080/
|
|
Running 30s test @ http://inuc:8080/
|
|
8 threads and 1000 connections
|
|
Thread Stats Avg Stdev Max +/- Stdev
|
|
Latency 7.08ms 3.02ms 64.38ms 89.40%
|
|
Req/Sec 18.24k 2.11k 24.06k 73.88%
|
|
4260394 requests in 30.00s, 491.63MB read
|
|
Requests/sec: 142025.60
|
|
Transfer/sec: 16.39MB
|
|
```
|
|
|
|
Compared to https://github.com/julienschmidt/httprouter
|
|
```
|
|
darko@arch ~ $ wrk -c1000 -t8 -d30s http://inuc:8080/
|
|
Running 30s test @ http://inuc:8080/
|
|
8 threads and 1000 connections
|
|
Thread Stats Avg Stdev Max +/- Stdev
|
|
Latency 7.14ms 3.13ms 74.06ms 89.99%
|
|
Req/Sec 18.18k 2.19k 23.31k 75.88%
|
|
4224358 requests in 30.00s, 487.47MB read
|
|
Requests/sec: 140826.15
|
|
Transfer/sec: 16.25MB
|
|
```
|
|
|
|
## 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.
|