Problem
gin-gonic/gin#4219
Maybe solution
SDK ServerMux
package main
import (
"fmt"
"net/http"
"time"
"github.com/alexedwards/scs/v2" // Standard session manager for net/http
)
var sessionManager *scs.SessionManager
func main() {
// 1. Initialize Session Manager
sessionManager = scs.New()
sessionManager.Lifetime = 24 * time.Hour
// 2. Create the Go 1.22+ ServeMux
mux := http.NewServeMux()
// Route: GET /search/category/{cat}
// Matches: /search/category/electronics, /search/category/books
mux.HandleFunc("GET /search/category/{cat}", searchHandler)
// 3. Chain Middleware (SCS requires LoadAndSave to work)
// This makes session data accessible in the request context
handler := sessionManager.LoadAndSave(mux)
fmt.Println("Server starting on :8080")
http.ListenAndServe(":8080", handler)
}
func searchHandler(w http.ResponseWriter, r *http.Request) {
// A. Parse Path Parameter (New in Go 1.22)
category := r.PathValue("cat") //
// B. Parse Query Parameters (Standard SDK)
query := r.URL.Query().Get("q") // e.g., ?q=laptop
page := r.URL.Query().Get("page")
// C. Use Sessions (SCS Library)
// We'll track how many times this specific user has searched
count := sessionManager.GetInt(r.Context(), "search_count")
count++
sessionManager.Put(r.Context(), "search_count", count)
// Response
fmt.Fprintf(w, "Category: %s\nSearch Term: %s\nPage: %s\nTotal Searches: %d",
category, query, page, count)
}
Alice
package main
import (
"fmt"
"log"
"net/http"
"time"
"://github.com"
"://github.com" // Essential for clean middleware chaining
)
var sessionManager *scs.SessionManager
func main() {
sessionManager = scs.New()
sessionManager.Lifetime = 24 * time.Hour
mux := http.NewServeMux()
// --- Middleware Definitions ---
// Logger: Logs every request
logger := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
})
}
// Auth Guard: Checks for a session variable
authGuard := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !sessionManager.Exists(r.Context(), "user_id") {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// --- Chaining with Alice ---
// Standard chain for ALL routes (Logging + Sessions)
publicChain := alice.New(logger, sessionManager.LoadAndSave)
// Restricted chain for ADMIN routes (Public + AuthGuard)
protectedChain := publicChain.Append(authGuard)
// --- Route Mapping ---
// Public Route
mux.Handle("GET /", publicChain.ThenFunc(homeHandler))
// Protected Route (using the 1.22 wildcard)
mux.Handle("GET /settings/{id}", protectedChain.ThenFunc(settingsHandler))
log.Fatal(http.ListenAndServe(":8080", mux))
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Welcome to the Home Page!")
}
func settingsHandler(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "Viewing settings for user: %s", id)
}
Home-grown request validation and JSON-decoding helpers
With structs like
type CreateUserRequest struct {
Username string `json:"username" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"gte=18"`
}
, you can have
var validate = validator.New()
func decodeAndValidate[T any](r *http.Request, v *T) error {
// 1. Decode JSON from the request body stream
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
return fmt.Errorf("decode: %w", err)
}
// 2. Validate using struct tags
if err := validate.Struct(v); err != nil {
return err // This will be of type validator.ValidationErrors
}
return nil
}
func createUserHandler(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := decodeAndValidate(r, &req); err != nil {
// Handle validation or syntax errors
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Process valid data...
w.WriteHeader(http.StatusCreated)
}
func writeJSON(w http.ResponseWriter, status int, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(data); err != nil {
// If encoding fails, we've already sent the header,
// so we log it or handle it as a server error.
log.Printf("json encode error: %v", err)
}
}
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{"error": message})
}
Home-grown recovery middle-ware
func recoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
// 1. Set the Connection: close header
// This triggers the server to close the connection after the response
w.Header().Set("Connection", "close")
// 2. Log the error (and optionally the stack trace)
log.Printf("PANIC RECOVERED: %v\n", err)
// debug.Stack() from "runtime/debug" can be added here for full traces
// 3. Send a clean 500 error to the client
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
Home-grown response-wrapper
type statusRecorder struct {
http.ResponseWriter
status int
}
// WriteHeader intercepts the status code before sending it to the original writer
func (r *statusRecorder) WriteHeader(code int) {
r.status = code
r.ResponseWriter.WriteHeader(code)
}
// Unwrap allows other tools (like http.ResponseController) to access the underlying writer
func (r *statusRecorder) Unwrap() http.ResponseWriter {
return r.ResponseWriter
}
Problem
gin-gonic/gin#4219
Maybe solution
SDK ServerMux
Alice
Home-grown request validation and JSON-decoding helpers
With structs like
, you can have
Home-grown recovery middle-ware
Home-grown response-wrapper