Documentation
¶
Overview ¶
Package httpx provides the HTTP primitives shared by FlyingWhale servers: a JSON error envelope with a shared code vocabulary, request id and structured logging middlewares, a panic-recovery middleware, a rate limiter, a router with prefixed and middleware-chained route groups, Accept-Language negotiation, and a slog handler that correlates log lines with a request. A request-scoped status recorder couples the envelope and the middlewares so a recorded error code reaches the access log line that matches what the client actually received. Policy defaults (rate limit capacity, supported languages, a fallback locale) come from the caller. The package keeps one safety cap of its own, the request body limit, which DecodeJSONLimit overrides.
Index ¶
- func DecodeJSON(w http.ResponseWriter, r *http.Request, dst any) error
- func DecodeJSONLimit(w http.ResponseWriter, r *http.Request, dst any, maxBytes int64) error
- func NegotiateLanguage(header string, supported []string, fallback string) string
- func NewRequestIDLogHandler(inner slog.Handler) slog.Handler
- func RequestID(next http.Handler) http.Handler
- func RequestIDFrom(ctx context.Context) string
- func WriteError(w http.ResponseWriter, status int, code Code, message string)
- func WriteJSON(w http.ResponseWriter, status int, payload any)
- type Code
- type Middleware
- type RouteGroup
- type Router
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DecodeJSON ¶
DecodeJSON decodes the request body of r as JSON into dst, capping the body at 8KB.
Unknown fields are ignored on purpose: a shipped client binary may keep sending a field this server has already stopped reading.
func DecodeJSONLimit ¶
DecodeJSONLimit is DecodeJSON with a caller-supplied body cap, for the rare consumer whose legitimate payloads run past the 8KB default.
func NegotiateLanguage ¶
NegotiateLanguage picks the best of the supported languages for an Accept-Language header, falling back to fallback when the header is absent, unparseable, or names nothing in supported.
A bare "*" is treated as a match for fallback rather than for some arbitrary supported language: "*" means the client has no real preference, and fallback is the caller's answer to "no preference", so this keeps both cases consistent instead of picking one supported language over the others for no principled reason.
func NewRequestIDLogHandler ¶
NewRequestIDLogHandler wraps inner so every log record whose context carries a request id (set by RequestID) gains a request_id attribute, correlating any *Context log call anywhere in the call graph with the access log line for the same request, without repeating the attribute at each call site.
func RequestID ¶
RequestID assigns a random id to the request, sets it on the response's X-Request-Id header, and stores it in the request context for RequestIDFrom.
func RequestIDFrom ¶
RequestIDFrom returns the request id RequestID stored on ctx, or an empty string if RequestID never ran on this request.
func WriteError ¶
func WriteError(w http.ResponseWriter, status int, code Code, message string)
WriteError records the code on the writer when it is the package's own recorder. The assertion works only because WriteError and statusRecorder live in the same package, so no exported type has to carry the code across a boundary that does not exist.
func WriteJSON ¶
func WriteJSON(w http.ResponseWriter, status int, payload any)
WriteJSON writes payload to w as a JSON body with the given status code.
Marshal happens before the header is written so an encoding failure (for example a NaN float) can still fall back to a 500 instead of leaving a 200 header already committed with a truncated body. The header is not written yet at that point, so the substituted code is still eligible to be recorded under the same first-write-wins rule as any other code.
Types ¶
type Code ¶
type Code string
Code identifies a machine-readable error category carried in an error envelope's body.
CodeInternal and CodeTooManyRequests are append-only: a client maps these wire strings against known cases, so an existing value never changes and an existing constant is never renamed once released. Any other code is defined by the application that owns it.
type Middleware ¶
Middleware wraps an http.Handler with additional behavior.
Compose Logging outside Recover (Logging(logger)(Recover(logger)(next))) so a panic recovered downstream is written through Logging's own status recorder and the request line reports the resulting status.
func Logging ¶
func Logging(logger *slog.Logger) Middleware
Logging returns a Middleware that logs one line per request through logger, including the response status and any error code WriteError recorded on it.
func RateLimit ¶
func RateLimit(requestsPerMinute int, now func() time.Time) Middleware
RateLimit rate-limits by client IP using a token bucket refilled at requestsPerMinute tokens per minute. Capacity is required and has no library-owned default: RateLimit panics if requestsPerMinute is not positive, so a missing or misconfigured limit fails at wiring time rather than silently falling back to a value the caller never chose. It keys on the peer address, trusting X-Forwarded-For only from a loopback peer and only its rightmost entry, which suits a single trusted reverse proxy on loopback.
func Recover ¶
func Recover(logger *slog.Logger) Middleware
Recover returns a Middleware that recovers a panic from the wrapped handler, logs it through logger, and responds with a 500 envelope if the response has not already committed a header.
type RouteGroup ¶
type RouteGroup struct {
// contains filtered or unexported fields
}
RouteGroup registers handlers that share a path prefix and a middleware chain, and reports each registered pattern back to its Router.
func (RouteGroup) Handle ¶
func (group RouteGroup) Handle(pattern string, handler http.Handler)
Handle registers handler for pattern under the group's prefix, wrapped in the group's middleware chain, and records the resulting pattern on the group's Router.
func (RouteGroup) HandleFunc ¶
func (group RouteGroup) HandleFunc(pattern string, handler http.HandlerFunc)
HandleFunc is Handle for a plain http.HandlerFunc.
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
Router wraps an http.ServeMux and records every pattern registered through it, so a caller can enumerate the routes it exposes after setup. Values come from NewRouter; the zero value is not usable.
func NewRouter ¶
func NewRouter() *Router
NewRouter returns an empty Router ready to register route groups on.
func (*Router) Group ¶
func (rt *Router) Group(prefix string, chain ...Middleware) *RouteGroup
Group returns a RouteGroup that prefixes every pattern it registers with prefix and wraps every handler in chain, outermost middleware first.