httpx

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 15 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecodeJSON

func DecodeJSON(w http.ResponseWriter, r *http.Request, dst any) error

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

func DecodeJSONLimit(w http.ResponseWriter, r *http.Request, dst any, maxBytes int64) error

DecodeJSONLimit is DecodeJSON with a caller-supplied body cap, for the rare consumer whose legitimate payloads run past the 8KB default.

func NegotiateLanguage

func NegotiateLanguage(header string, supported []string, fallback string) string

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

func NewRequestIDLogHandler(inner slog.Handler) slog.Handler

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

func RequestID(next http.Handler) http.Handler

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

func RequestIDFrom(ctx context.Context) string

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.

const (
	CodeInternal        Code = "internal"
	CodeTooManyRequests Code = "too_many_requests"
)

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

type Middleware func(http.Handler) http.Handler

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.

func (*Router) Mux

func (rt *Router) Mux() *http.ServeMux

Mux returns the underlying http.ServeMux, for serving the router or for wrapping it in server-level middleware.

func (*Router) Patterns

func (rt *Router) Patterns() []string

Patterns returns every pattern registered through the router's route groups, in registration order.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL