feat: improve resource routing API and add comprehensive quality standards
BREAKING CHANGES:
- Renamed HandleGET -> MemberGET for member-level routes
- Renamed HandlePOST -> MemberPOST for member-level routes
- Renamed HandlePUT -> MemberPUT for member-level routes
- Renamed HandlePATCH -> MemberPATCH for member-level routes
- Renamed HandleDELETE -> MemberDELETE for member-level routes
New Features:
- Added collection-level route methods: GET, POST, PUT, PATCH, DELETE
- Clear distinction between collection (/pattern/action) and member (/pattern/{id}/action) routes
- Comprehensive documentation (README, CONTRIBUTING, QUICKSTART, DOCS)
- Development tooling (Makefile, check.sh script)
- AI coding assistant guidelines (.cursorrules)
- GitHub Actions CI/CD pipeline
- golangci-lint configuration
Code Quality:
- Optimized struct field alignment for better memory usage
- All code passes go vet, staticcheck, and fieldalignment
- All tests pass with race detector
- Go 1.25+ requirement enforced
Documentation:
- Complete README rewrite with examples
- CONTRIBUTING.md with development guidelines
- QUICKSTART.md for new users
- CHANGELOG.md with version history
- SUMMARY.md documenting all changes
- DOCS.md as documentation index
This commit is contained in:
572
README.md
572
README.md
@@ -1,16 +1,26 @@
|
||||
# Mux - A Lightweight HTTP Router for Go
|
||||
|
||||
Mux is a simple, lightweight HTTP router for Go that wraps around the standard `http.ServeMux` to provide additional functionality and a more ergonomic API.
|
||||
[](https://go.dev/)
|
||||
[](LICENSE)
|
||||
|
||||
Mux is a simple, lightweight HTTP router for Go that wraps around the standard `http.ServeMux` to provide additional functionality and a more ergonomic API for building web applications and APIs.
|
||||
|
||||
## Features
|
||||
|
||||
- HTTP method-specific routing (GET, POST, PUT, DELETE, etc.)
|
||||
- Middleware support with flexible stacking
|
||||
- Route grouping for organization and shared middleware
|
||||
- RESTful resource routing
|
||||
- URL parameter extraction
|
||||
- Graceful shutdown support
|
||||
- Minimal dependencies (only uses Go standard library)
|
||||
- 🚀 Built on top of Go's standard `http.ServeMux` (Go 1.22+ routing enhancements)
|
||||
- 🎯 HTTP method-specific routing (GET, POST, PUT, DELETE, PATCH, etc.)
|
||||
- 🔌 Flexible middleware support with stackable composition
|
||||
- 📦 Route grouping for organization and shared middleware
|
||||
- 🎨 RESTful resource routing with collection and member routes
|
||||
- 🔗 URL parameter extraction using Go's standard path values
|
||||
- 🛡️ Graceful shutdown support with signal handling
|
||||
- 📋 Route listing and debugging
|
||||
- ⚡ Zero external dependencies (only Go standard library)
|
||||
- 🪶 Minimal overhead and excellent performance
|
||||
|
||||
## Requirements
|
||||
|
||||
- Go 1.25 or higher
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -18,136 +28,554 @@ Mux is a simple, lightweight HTTP router for Go that wraps around the standard `
|
||||
go get code.patial.tech/go/mux
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.patial.tech/go/mux"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"code.patial.tech/go/mux"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a new router
|
||||
router := mux.NewRouter()
|
||||
// Create a new router
|
||||
m := mux.New()
|
||||
|
||||
// Define a simple route
|
||||
router.GET("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "Hello, World!")
|
||||
})
|
||||
// Define routes
|
||||
m.GET("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "Hello, World!")
|
||||
})
|
||||
|
||||
// Start the server
|
||||
http.ListenAndServe(":8080", router)
|
||||
m.GET("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
fmt.Fprintf(w, "User ID: %s", id)
|
||||
})
|
||||
|
||||
// Start server with graceful shutdown
|
||||
m.Serve(func(srv *http.Server) error {
|
||||
srv.Addr = ":8080"
|
||||
return srv.ListenAndServe()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Routing
|
||||
## Table of Contents
|
||||
|
||||
Mux supports all HTTP methods defined in the Go standard library:
|
||||
- [Basic Routing](#basic-routing)
|
||||
- [URL Parameters](#url-parameters)
|
||||
- [Middleware](#middleware)
|
||||
- [Route Groups](#route-groups)
|
||||
- [RESTful Resources](#restful-resources)
|
||||
- [Inline Middleware](#inline-middleware)
|
||||
- [Graceful Shutdown](#graceful-shutdown)
|
||||
- [Route Debugging](#route-debugging)
|
||||
- [Complete Example](#complete-example)
|
||||
|
||||
## Basic Routing
|
||||
|
||||
Mux supports all standard HTTP methods:
|
||||
|
||||
```go
|
||||
router.GET("/users", listUsers)
|
||||
router.POST("/users", createUser)
|
||||
router.PUT("/users/{id}", updateUser)
|
||||
router.DELETE("/users/{id}", deleteUser)
|
||||
router.PATCH("/users/{id}", partialUpdateUser)
|
||||
router.HEAD("/users", headUsers)
|
||||
router.OPTIONS("/users", optionsUsers)
|
||||
router.TRACE("/users", traceUsers)
|
||||
router.CONNECT("/users", connectUsers)
|
||||
m := mux.New()
|
||||
|
||||
// HTTP method routes
|
||||
m.GET("/users", listUsers)
|
||||
m.POST("/users", createUser)
|
||||
m.PUT("/users/{id}", updateUser)
|
||||
m.PATCH("/users/{id}", partialUpdateUser)
|
||||
m.DELETE("/users/{id}", deleteUser)
|
||||
m.HEAD("/users", headUsers)
|
||||
m.OPTIONS("/users", optionsUsers)
|
||||
m.CONNECT("/proxy", connectProxy)
|
||||
m.TRACE("/debug", traceDebug)
|
||||
```
|
||||
|
||||
## URL Parameters
|
||||
|
||||
Mux supports URL parameters using curly braces:
|
||||
Extract URL parameters using Go's standard `r.PathValue()`:
|
||||
|
||||
```go
|
||||
router.GET("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
fmt.Fprintf(w, "User ID: %s", id)
|
||||
m.GET("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.PathValue("id")
|
||||
fmt.Fprintf(w, "Fetching user: %s", userID)
|
||||
})
|
||||
|
||||
m.GET("/posts/{year}/{month}/{slug}", func(w http.ResponseWriter, r *http.Request) {
|
||||
year := r.PathValue("year")
|
||||
month := r.PathValue("month")
|
||||
slug := r.PathValue("slug")
|
||||
// ... handle request
|
||||
})
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
Middleware functions take an `http.Handler` and return an `http.Handler`. You can add global middleware to all routes:
|
||||
Middleware functions follow the standard `func(http.Handler) http.Handler` signature, making them compatible with most Go middleware libraries.
|
||||
|
||||
### Global Middleware
|
||||
|
||||
Apply middleware to all routes:
|
||||
|
||||
```go
|
||||
// Logging middleware
|
||||
func loggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Printf("[%s] %s\n", r.Method, r.URL.Path)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
func logger(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
slog.Info("request", "method", r.Method, "path", r.URL.Path)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// Add middleware to all routes
|
||||
router.Use(loggingMiddleware)
|
||||
// Authentication middleware
|
||||
func auth(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get("Authorization")
|
||||
if token == "" {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
m := mux.New()
|
||||
m.Use(logger)
|
||||
m.Use(auth)
|
||||
|
||||
// All routes will use logger and auth middleware
|
||||
m.GET("/protected", protectedHandler)
|
||||
```
|
||||
|
||||
### Compatible with Popular Middleware
|
||||
|
||||
Works with any middleware following the standard signature:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/gorilla/handlers"
|
||||
chimiddleware "github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
|
||||
m.Use(handlers.CompressHandler)
|
||||
m.Use(chimiddleware.RealIP)
|
||||
m.Use(chimiddleware.Recoverer)
|
||||
```
|
||||
|
||||
## Route Groups
|
||||
|
||||
Group related routes and apply middleware to specific groups:
|
||||
Organize routes and apply middleware to specific groups:
|
||||
|
||||
```go
|
||||
// API routes group
|
||||
router.Group(func(api *mux.Router) {
|
||||
// Middleware only for API routes
|
||||
api.Use(authMiddleware)
|
||||
m := mux.New()
|
||||
|
||||
// API routes
|
||||
api.GET("/api/users", listUsers)
|
||||
api.POST("/api/users", createUser)
|
||||
// Public routes
|
||||
m.GET("/", homeHandler)
|
||||
m.GET("/about", aboutHandler)
|
||||
|
||||
// API routes with shared middleware
|
||||
m.Group(func(api *mux.Mux) {
|
||||
api.Use(jsonMiddleware)
|
||||
api.Use(authMiddleware)
|
||||
|
||||
api.GET("/api/users", listUsersAPI)
|
||||
api.POST("/api/users", createUserAPI)
|
||||
api.DELETE("/api/users/{id}", deleteUserAPI)
|
||||
})
|
||||
|
||||
// Admin routes with different middleware
|
||||
m.Group(func(admin *mux.Mux) {
|
||||
admin.Use(adminAuthMiddleware)
|
||||
admin.Use(auditLogMiddleware)
|
||||
|
||||
admin.GET("/admin/dashboard", dashboardHandler)
|
||||
admin.GET("/admin/users", adminUsersHandler)
|
||||
})
|
||||
```
|
||||
|
||||
## RESTful Resources
|
||||
|
||||
Easily define RESTful resources:
|
||||
Define RESTful resources with conventional routing:
|
||||
|
||||
```go
|
||||
router.Resource("/posts", func(r *mux.Resource) {
|
||||
r.Index(listPosts) // GET /posts
|
||||
r.Show(showPost) // GET /posts/{id}
|
||||
r.Create(createPost) // POST /posts
|
||||
r.Update(updatePost) // PUT /posts/{id}
|
||||
r.Destroy(deletePost) // DELETE /posts/{id}
|
||||
r.New(newPostForm) // GET /posts/new
|
||||
m.Resource("/posts", func(res *mux.Resource) {
|
||||
// Standard RESTful routes
|
||||
res.Index(listPosts) // GET /posts
|
||||
res.CreateView(newPostForm) // GET /posts/create
|
||||
res.Create(createPost) // POST /posts
|
||||
res.View(showPost) // GET /posts/{id}
|
||||
res.Update(updatePost) // PUT /posts/{id}
|
||||
res.UpdatePartial(patchPost) // PATCH /posts/{id}
|
||||
res.Delete(deletePost) // DELETE /posts/{id}
|
||||
})
|
||||
```
|
||||
|
||||
### Custom Resource Routes
|
||||
|
||||
Add custom routes at collection or member level:
|
||||
|
||||
```go
|
||||
m.Resource("/posts", func(res *mux.Resource) {
|
||||
// Standard routes
|
||||
res.Index(listPosts)
|
||||
res.View(showPost)
|
||||
|
||||
// Collection-level custom routes (on /posts/...)
|
||||
res.POST("/search", searchPosts) // POST /posts/search
|
||||
res.GET("/archived", archivedPosts) // GET /posts/archived
|
||||
res.GET("/trending", trendingPosts) // GET /posts/trending
|
||||
|
||||
// Member-level custom routes (on /posts/{id}/...)
|
||||
res.MemberPOST("/publish", publishPost) // POST /posts/{id}/publish
|
||||
res.MemberPOST("/archive", archivePost) // POST /posts/{id}/archive
|
||||
res.MemberGET("/comments", getComments) // GET /posts/{id}/comments
|
||||
res.MemberDELETE("/cache", clearCache) // DELETE /posts/{id}/cache
|
||||
})
|
||||
```
|
||||
|
||||
#### Collection vs Member Routes
|
||||
|
||||
- **Collection routes** (`POST`, `GET`, `PUT`, `PATCH`, `DELETE`): Operate on `/pattern/action`
|
||||
- Example: `res.POST("/search", handler)` → `POST /posts/search`
|
||||
|
||||
- **Member routes** (`MemberPOST`, `MemberGET`, `MemberPUT`, `MemberPATCH`, `MemberDELETE`): Operate on `/pattern/{id}/action`
|
||||
- Example: `res.MemberPOST("/publish", handler)` → `POST /posts/{id}/publish`
|
||||
|
||||
### Resource Middleware
|
||||
|
||||
Apply middleware to all resource routes:
|
||||
|
||||
```go
|
||||
m.Resource("/posts", func(res *mux.Resource) {
|
||||
// Middleware for all routes in this resource
|
||||
res.Use(postAuthMiddleware)
|
||||
res.Use(postValidationMiddleware)
|
||||
|
||||
res.Index(listPosts)
|
||||
res.Create(createPost)
|
||||
// ... other routes
|
||||
}, resourceSpecificMiddleware) // Can also pass middleware as arguments
|
||||
```
|
||||
|
||||
## Inline Middleware
|
||||
|
||||
Apply middleware to specific routes without affecting others:
|
||||
|
||||
```go
|
||||
m := mux.New()
|
||||
|
||||
// Route without middleware
|
||||
m.GET("/public", publicHandler)
|
||||
|
||||
// Route with inline middleware
|
||||
m.With(authMiddleware, rateLimitMiddleware).
|
||||
GET("/protected", protectedHandler)
|
||||
|
||||
// Another route with different middleware
|
||||
m.With(adminMiddleware).
|
||||
POST("/admin/action", adminActionHandler)
|
||||
```
|
||||
|
||||
## Graceful Shutdown
|
||||
|
||||
Use the built-in graceful shutdown functionality:
|
||||
Built-in graceful shutdown with signal handling:
|
||||
|
||||
```go
|
||||
router.Serve(func(srv *http.Server) error {
|
||||
srv.Addr = ":8080"
|
||||
return srv.ListenAndServe()
|
||||
m := mux.New()
|
||||
|
||||
// Define routes...
|
||||
m.GET("/", homeHandler)
|
||||
|
||||
// Serve with graceful shutdown
|
||||
m.Serve(func(srv *http.Server) error {
|
||||
srv.Addr = ":8080"
|
||||
srv.ReadTimeout = 10 * time.Second
|
||||
srv.WriteTimeout = 10 * time.Second
|
||||
|
||||
slog.Info("Server starting", "addr", srv.Addr)
|
||||
return srv.ListenAndServe()
|
||||
})
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Listens for `SIGINT` and `SIGTERM` signals
|
||||
- Drains existing connections (10 second grace period)
|
||||
- Propagates shutdown signal to handlers via `m.IsShuttingDown`
|
||||
- Automatic OPTIONS handler for all routes
|
||||
- Hard shutdown after grace period to prevent hanging
|
||||
|
||||
### Checking Shutdown Status
|
||||
|
||||
```go
|
||||
m.GET("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
if m.IsShuttingDown.Load() {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
w.Write([]byte("Server is shutting down"))
|
||||
return
|
||||
}
|
||||
w.Write([]byte("OK"))
|
||||
})
|
||||
```
|
||||
|
||||
## Route Debugging
|
||||
|
||||
List all registered routes:
|
||||
|
||||
```go
|
||||
// Print routes to stdout
|
||||
m.PrintRoutes(os.Stdout)
|
||||
|
||||
// Get routes as slice
|
||||
routes := m.RouteList()
|
||||
for _, route := range routes {
|
||||
fmt.Println(route)
|
||||
}
|
||||
|
||||
// Expose routes via HTTP endpoint
|
||||
m.GET("/debug/routes", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
m.PrintRoutes(w)
|
||||
})
|
||||
```
|
||||
|
||||
Output example:
|
||||
```
|
||||
GET /
|
||||
GET /users
|
||||
POST /users
|
||||
GET /users/{id}
|
||||
PUT /users/{id}
|
||||
DELETE /users/{id}
|
||||
GET /posts
|
||||
POST /posts/search
|
||||
GET /posts/{id}
|
||||
POST /posts/{id}/publish
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"code.patial.tech/go/mux"
|
||||
)
|
||||
|
||||
func main() {
|
||||
m := mux.New()
|
||||
|
||||
// Global middleware
|
||||
m.Use(loggingMiddleware)
|
||||
m.Use(recoveryMiddleware)
|
||||
|
||||
// Public routes
|
||||
m.GET("/", homeHandler)
|
||||
m.GET("/about", aboutHandler)
|
||||
|
||||
// API group
|
||||
m.Group(func(api *mux.Mux) {
|
||||
api.Use(jsonMiddleware)
|
||||
|
||||
// Users resource
|
||||
api.Resource("/users", func(res *mux.Resource) {
|
||||
res.Index(listUsers)
|
||||
res.Create(createUser)
|
||||
res.View(showUser)
|
||||
res.Update(updateUser)
|
||||
res.Delete(deleteUser)
|
||||
|
||||
// Custom user actions
|
||||
res.POST("/search", searchUsers)
|
||||
res.MemberPOST("/activate", activateUser)
|
||||
res.MemberGET("/posts", getUserPosts)
|
||||
})
|
||||
|
||||
// Posts resource
|
||||
api.Resource("/posts", func(res *mux.Resource) {
|
||||
res.Index(listPosts)
|
||||
res.Create(createPost)
|
||||
res.View(showPost)
|
||||
|
||||
res.MemberPOST("/publish", publishPost)
|
||||
res.MemberGET("/comments", getPostComments)
|
||||
})
|
||||
})
|
||||
|
||||
// Debug route
|
||||
m.GET("/debug/routes", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
m.PrintRoutes(w)
|
||||
})
|
||||
|
||||
// Start server
|
||||
m.Serve(func(srv *http.Server) error {
|
||||
srv.Addr = ":8080"
|
||||
srv.ReadTimeout = 30 * time.Second
|
||||
srv.WriteTimeout = 30 * time.Second
|
||||
|
||||
slog.Info("Server listening", "addr", srv.Addr)
|
||||
return srv.ListenAndServe()
|
||||
})
|
||||
}
|
||||
|
||||
func loggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
next.ServeHTTP(w, r)
|
||||
slog.Info("request",
|
||||
"method", r.Method,
|
||||
"path", r.URL.Path,
|
||||
"duration", time.Since(start))
|
||||
})
|
||||
}
|
||||
|
||||
func recoveryMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
slog.Error("panic recovered", "error", err)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func jsonMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// Handler implementations
|
||||
func homeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "Welcome Home!")
|
||||
}
|
||||
|
||||
func aboutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "About Us")
|
||||
}
|
||||
|
||||
func listUsers(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, `{"users": []}`)
|
||||
}
|
||||
|
||||
func createUser(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, `{"message": "User created"}`)
|
||||
}
|
||||
|
||||
func showUser(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
fmt.Fprintf(w, `{"id": "%s"}`, id)
|
||||
}
|
||||
|
||||
func updateUser(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
fmt.Fprintf(w, `{"message": "User %s updated"}`, id)
|
||||
}
|
||||
|
||||
func deleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
fmt.Fprintf(w, `{"message": "User %s deleted"}`, id)
|
||||
}
|
||||
|
||||
func searchUsers(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, `{"results": []}`)
|
||||
}
|
||||
|
||||
func activateUser(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
fmt.Fprintf(w, `{"message": "User %s activated"}`, id)
|
||||
}
|
||||
|
||||
func getUserPosts(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
fmt.Fprintf(w, `{"user_id": "%s", "posts": []}`, id)
|
||||
}
|
||||
|
||||
func listPosts(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, `{"posts": []}`)
|
||||
}
|
||||
|
||||
func createPost(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, `{"message": "Post created"}`)
|
||||
}
|
||||
|
||||
func showPost(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
fmt.Fprintf(w, `{"id": "%s"}`, id)
|
||||
}
|
||||
|
||||
func publishPost(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
fmt.Fprintf(w, `{"message": "Post %s published"}`, id)
|
||||
}
|
||||
|
||||
func getPostComments(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
fmt.Fprintf(w, `{"post_id": "%s", "comments": []}`, id)
|
||||
}
|
||||
```
|
||||
|
||||
## Custom 404 Handler
|
||||
|
||||
can be tried like this
|
||||
Handle 404 errors with a catch-all route:
|
||||
|
||||
```go
|
||||
router.GET("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.URL.Path != "/" {
|
||||
writer.WriteHeader(404)
|
||||
writer.Write([]byte(`not found, da xiong dei !!!`))
|
||||
m.GET("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.Error(w, "404 - Page Not Found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
// Handle root path
|
||||
fmt.Fprint(w, "Home Page")
|
||||
})
|
||||
```
|
||||
|
||||
## Full Example
|
||||
## Testing
|
||||
|
||||
See the [examples directory](./example) for complete working examples.
|
||||
Testing routes is straightforward using Go's `httptest` package:
|
||||
|
||||
```go
|
||||
func TestHomeHandler(t *testing.T) {
|
||||
m := mux.New()
|
||||
m.GET("/", homeHandler)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
m.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
Mux has minimal overhead since it wraps Go's standard `http.ServeMux`:
|
||||
|
||||
- Zero heap allocations for simple routes
|
||||
- Efficient middleware chaining using composition
|
||||
- Fast pattern matching powered by Go's stdlib
|
||||
- No reflection or runtime code generation
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit issues, fork the repository, and create pull requests.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Credits
|
||||
|
||||
Built with ❤️ using Go's excellent standard library.
|
||||
|
||||
Reference in New Issue
Block a user