Skip to main content

http-patterns

HTTP handler patterns for Go 1.22+. Covers ServeMux method routing, path parameters, middleware, ResponseWriter wrapping, request context, graceful shutdown, error responses, handler naming conventions. Use when this capability is needed.

跳到安装

来源信息

仓库
tomevault-io/skills-registry
最近来源活动
2026年4月28日 22:53
检测到的 SKILL.md 语言
英语
星标
0
分支
0

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
2 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
http-patterns
description
HTTP handler patterns for Go 1.22+. Covers ServeMux method routing, path parameters, middleware, ResponseWriter wrapping, request context, graceful shutdown, error responses, handler naming conventions. Use when this capability is needed.
metadata
{"author":"air-gapped"}
# HTTP Patterns ## Go 1.22: ServeMux Method Routing No more manual method checks. Register with HTTP verb prefix: ```go mux := http.NewServeMux() mux.HandleFunc("GET /healthz", healthHandler) mux.HandleFunc("GET /_cooked/{path...}", assetHandler) mux.HandleFunc("GET /{upstream...}", renderHandler) ``` ### Path Parameters ```go func renderHandler(w http.ResponseWriter, r *http.Request) { upstream := r.PathValue("upstream") // NEW in Go 1.22 // ... } ``` ### Special Patterns | Pattern | Matches | |---------|---------| | `/posts/{id}` | `/posts/123` (single segment) | | `/files/{path...}` | `/files/a/b/c` (remainder of path) | | `/posts/{$}` | `/posts/` only (not `/posts` or `/posts/x`) | ### Precedence More specific wins: - `/healthz` beats `/{upstream...}` - `GET /posts/{id}` beats `/posts/{id}` **Conflicting patterns panic at registration:** ```go mux.HandleFunc("GET /posts/{id}", h1) mux.HandleFunc("GET /{resource}/latest", h2) // PANIC - both match /posts/latest ``` ### Automatic 405 Unmatched methods return `405 Method Not Allowed` with `Allow` header. ### go.mod Requirement Go 1.22+ patterns require `go 1.22` or later in `go.mod`. Without it, patterns are treated literally (braces aren't wildcards). Sources: [Go Blog: Routing Enhancements](https://go.dev/blog/routing-enhancements), [Eli Bendersky](https://eli.thegreenplace.net/2023/better-http-server-routing-in-go-122/) --- ## Middleware Pattern This project uses the standard wrapper pattern: ```go func RequestLogger(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() wrapped := &byteCountingWriter{ResponseWriter: w} next.ServeHTTP(wrapped, r) slog.Info("request", "method", r.Method, "path", r.URL.Path, "status", wrapped.statusCode, "bytes", wrapped.bytes, "total_ms", time.Since(start).Milliseconds(), ) }) } // Composable var handler http.Handler = mux handler = RequestLogger(handler) handler = RecoveryMiddleware(handler) srv := &http.Server{Handler: handler} ``` --- ## Request Context Store request-scoped values using typed keys: ```go // Define typed key (prevents collisions) type contextKey string const loggerKey contextKey = "logger" // Set in middleware ctx := context.WithValue(r.Context(), loggerKey, logger) next.ServeHTTP(w, r.WithContext(ctx)) // Retrieve in handlers func Logger(ctx context.Context) *slog.Logger { if l, ok := ctx.Value(loggerKey).(*slog.Logger); ok { return l } return slog.Default() } ``` --- ## ResponseWriter Wrapping Wrap to capture bytes and status: ```go type byteCountingWriter struct { http.ResponseWriter bytes int64 statusCode int } func (w *byteCountingWriter) WriteHeader(code int) { w.statusCode = code w.ResponseWriter.WriteHeader(code) } func (w *byteCountingWriter) Write(b []byte) (int, error) { if w.statusCode == 0 { w.statusCode = 200 // Default if WriteHeader not called } n, err := w.ResponseWriter.Write(b) w.bytes += int64(n) return n, err } ``` --- ## Graceful Shutdown ### Standard Pattern ```go srv := &http.Server{ Addr: ":8080", Handler: mux, } // Start server go func() { if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { log.Fatal(err) } }() // Wait for signal <-ctx.Done() // Graceful shutdown with timeout shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil { log.Printf("shutdown error: %v", err) } ``` ### What Shutdown() Does 1. Stops accepting new connections immediately 2. Closes idle connections 3. Waits for active requests to complete 4. Returns when all handlers finish OR context expires ### Kubernetes Considerations Add a health endpoint that fails during shutdown: ```go healthService.MarkShuttingDown() // Called before Shutdown() // Readiness probe now returns 503 ``` Sources: [VictoriaMetrics](https://victoriametrics.com/blog/go-graceful-shutdown/), [DEV Community](https://dev.to/mokiat/proper-http-shutdown-in-go-3fji) --- ## Error Responses cooked returns HTML error pages, not JSON: ```go func renderError(w http.ResponseWriter, r *http.Request, status int, errType, message string) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(status) // Render error template with same theming as content pages } ``` Error pages use the same HTML template with: - Proper theming (respects user's theme choice) - `#cooked-error` element with `data-error-type` and `data-status-code` - Direct link to the upstream URL --- ## Handler Naming | Pattern | Returns | Example | |---------|---------|---------| | `FeatureHandler(deps)` | `http.HandlerFunc` | `RenderHandler(deps)` | | `FeatureMiddleware(next)` | `http.Handler` | `RequestLogger(next)` | --- > Converted and distributed by [TomeVault](https://tomevault.io/claim/air-gapped) — claim your Tome and manage your conversions. <!-- tomevault:4.0:skill_md:2026-04-13 -->
在 GitHub 查看