httpx
Package httpx provides the HTTP foundation of an apistock app: a server that runs under app.Run with safe timeouts, security middleware, and the RFC 9457 problem+json error contract with an application-owned error mapping (ADR-0018).
A typical middleware chain, outermost first:
handler := httpx.Chain(mux,
httpx.Recover(logger),
httpx.RequestID(),
httpx.AccessLog(logger),
httpx.SecureHeaders(httpx.SecureHeadersOptions{}),
cors,
crossOrigin,
httpx.BodyLimit(1<<20),
)
Stability: pre-1.0 (ADR-0015).
Constants#
const DefaultReadHeaderTimeout, …#
const (
DefaultReadHeaderTimeout = 5 * time.Second
DefaultReadTimeout = 30 * time.Second
DefaultWriteTimeout = 60 * time.Second
DefaultIdleTimeout = 120 * time.Second
DefaultShutdownTimeout = 20 * time.Second
DefaultMaxHeaderBytes = 1 << 20
)Server timeouts used by NewServer unless overridden.
const ProblemContentType#
const ProblemContentType = "application/problem+json"ProblemContentType is the media type of problem responses.
Functions#
func Chain#
func Chain(h http.Handler, middlewares ...Middleware) http.HandlerChain wraps h with middlewares. The first middleware is the outermost: it sees the request first and the response last.
func DefaultCode#
func DefaultCode(status int) stringDefaultCode returns the generic code for a status without a mapping, such as "not_found" for 404.
func WriteProblem#
func WriteProblem(w http.ResponseWriter, r *http.Request, p *Problem)WriteProblem writes p as application/problem+json, filling the request ID from the request context when empty.
Types#
type CORSOptions#
type CORSOptions struct {
// AllowedOrigins are exact origins such as "https://app.example.com".
// Empty disables CORS.
AllowedOrigins []string
AllowedMethods []string // default: GET, POST, PUT, PATCH, DELETE
AllowedHeaders []string // default: Authorization, Content-Type, X-Request-ID
ExposedHeaders []string // default: X-Request-ID, Retry-After
// AllowCredentials allows cookies. It can't be combined with a wildcard origin.
AllowCredentials bool
MaxAge time.Duration // preflight cache; default 10 minutes
}CORSOptions configure CORS.
type FieldError#
type FieldError struct {
Location string `json:"location,omitempty" doc:"Where the error occurred" example:"body.name"`
Message string `json:"message" doc:"What is wrong" example:"expected length >= 1"`
}FieldError describes one invalid input field. It never echoes the submitted value, which may be a password or personal data.
type Mapper#
type Mapper struct {
// contains filtered or unexported fields
}Mapper turns errors into problems using application-owned mappings. Errors without a mapping become a generic 500 and are logged once with the request ID. It is safe for concurrent use.
func NewMapper#
func NewMapper(logger *slog.Logger, mappings ...Mapping) (*Mapper, error)NewMapper returns a mapper with the given mappings.
func (*Mapper) Add#
func (m *Mapper) Add(mappings ...Mapping) errorAdd registers mappings. Each needs a non-nil error, a 4xx or 5xx status and a snake_case code. Each error is mapped once; several errors may share a code only with the same status, such as "unauthenticated" returned by different modules.
func (*Mapper) Match#
func (m *Mapper) Match(err error) (*Problem, bool)Match returns the problem for err if err is (or wraps) a *Problem or a mapped error. It never logs.
func (*Mapper) Problem#
func (m *Mapper) Problem(ctx context.Context, err error) *ProblemProblem returns the problem for err. Unmapped errors become 500 "internal_error" with a generic detail and are logged.
func (*Mapper) Write#
func (m *Mapper) Write(w http.ResponseWriter, r *http.Request, err error)Write maps err and writes the problem response.
type Mapping#
type Mapping struct {
Err error
Status int
Code string
Detail string
}A Mapping maps a sentinel error (matched with errors.Is) to an HTTP status and stable code. Detail defaults to the error's message.
type Middleware#
type Middleware func(http.Handler) http.HandlerMiddleware wraps an http.Handler.
func AccessLog#
func AccessLog(logger *slog.Logger) MiddlewareAccessLog logs one line per request: method, route pattern, status, duration, response size and request ID. Query strings and bodies are never logged.
func BodyLimit#
func BodyLimit(n int64) MiddlewareBodyLimit rejects request bodies larger than n bytes with a 413 problem. Bodies without a declared length are cut off at n bytes while reading.
func CORS#
func CORS(opts CORSOptions) (Middleware, error)CORS returns middleware allowing cross-origin requests from an explicit allowlist. A "*" origin is rejected when credentials are allowed.
func CrossOrigin#
func CrossOrigin(trustedOrigins ...string) (Middleware, error)CrossOrigin protects against cross-site request forgery using the browser's Sec-Fetch-Site and Origin headers (http.CrossOriginProtection). Non-browser clients, which send neither header, are allowed. Denied requests receive a 403 problem with code "cross_origin_request_denied".
func Recover#
func Recover(logger *slog.Logger) MiddlewareRecover turns a panic into a logged error and a 500 problem response. http.ErrAbortHandler panics are re-raised, as net/http expects.
func RequestID#
func RequestID() MiddlewareRequestID accepts a valid incoming X-Request-ID or generates one, stores it in the request context and echoes it in the response header.
func SecureHeaders#
func SecureHeaders(opts SecureHeadersOptions) MiddlewareSecureHeaders sets security headers suitable for a JSON API. HTML routes such as /docs set their own Content-Security-Policy.
type Problem#
type Problem struct {
Type string `json:"type,omitempty" doc:"URI identifying the problem type"`
Title string `json:"title" doc:"Short summary of the problem type" example:"Conflict"`
Status int `json:"status" doc:"HTTP status code" example:"409"`
Code string `json:"code" doc:"Stable machine-readable error code" example:"project_name_taken"`
Detail string `json:"detail,omitempty" doc:"Human-readable explanation" example:"project name is already taken"`
RequestID string `json:"request_id,omitempty" doc:"Correlates with server logs and traces" example:"req_9f86d081884c7d65"`
Errors []FieldError `json:"errors,omitempty" doc:"Field-level validation errors"`
}Problem is an RFC 9457 problem details response extended with a stable machine-readable code and the request ID. Codes are public API; titles and details are not (ADR-0015).
func NewProblem#
func NewProblem(status int, code, detail string) *ProblemNewProblem returns a problem with the standard title for status.
func (*Problem) ContentType#
func (p *Problem) ContentType(ct string) stringContentType returns application/problem+json for JSON responses.
func (*Problem) Error#
func (p *Problem) Error() stringError returns the detail, or the title when detail is empty.
func (*Problem) GetStatus#
func (p *Problem) GetStatus() intGetStatus returns the HTTP status. It lets frameworks such as Huma use a Problem as a status error.
type SecureHeadersOptions#
type SecureHeadersOptions struct {
// HSTSMaxAge enables Strict-Transport-Security when positive. Enable it
// only when the app is served exclusively over HTTPS.
HSTSMaxAge time.Duration
}SecureHeadersOptions configure SecureHeaders.
type Server#
type Server struct {
// contains filtered or unexported fields
}Server is an HTTP server that implements app.Runner.
func NewServer#
func NewServer(addr string, handler http.Handler, opts ...ServerOption) *ServerNewServer returns a server for handler listening on addr.
func (*Server) Addr#
func (s *Server) Addr(ctx context.Context) (string, error)Addr returns the listening address once Server.Run has started listening, blocking until then or until ctx is done. It is useful with port 0 in tests.
func (*Server) Run#
func (s *Server) Run(ctx context.Context) errorRun listens and serves until ctx is done, then shuts down gracefully: it stops accepting connections and waits for in-flight requests up to the shutdown timeout. Run returns nil after a graceful shutdown.
type ServerOption#
type ServerOption interface {
// contains filtered or unexported methods
}A ServerOption configures a Server.
func WithErrorLogger#
func WithErrorLogger(logger *slog.Logger) ServerOptionWithErrorLogger routes the server's internal errors (for example TLS handshake failures) to logger.
func WithShutdownTimeout#
func WithShutdownTimeout(d time.Duration) ServerOptionWithShutdownTimeout sets how long graceful shutdown may take. Default: DefaultShutdownTimeout.
func WithTimeouts#
func WithTimeouts(readHeader, read, write, idle time.Duration) ServerOptionWithTimeouts overrides read-header, read, write and idle timeouts. Zero values keep the defaults.