# 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.