Documentation
¶
Overview ¶
Package httputil provides HTTP helpers for JSON APIs and chi-based servers.
Error handling and responses ¶
HandleError writes a JSON error response; if err implements HTTPStatus and GetCode (e.g. httperr.HTTPError), that status and code are used, otherwise 500 INTERNAL_ERROR. ErrorHandler wraps logging (Info for 4xx, Error for 5xx) and HandleError. ErrorResponse and ValidationErrorResponse are the JSON shapes. ValidationHTTPError combines *HTTPError with a slice of field errors. RenderJSON, RenderOK, RenderCreated, RenderAccepted, RenderNoContent, RenderError, RenderErrorWithCode, RenderInvalidID, and RenderText send typed responses. RenderText writes the body as-is with no escaping; use Content-Type text/plain and do not pass user-controlled HTML to avoid XSS. For 5xx, RenderError and RenderErrorWithCode replace the message with "Internal server error".
Request decoding and validation ¶
DecodeAndValidate reads JSON from the body (limit MaxRequestBodySize), disallows unknown fields and trailing data, then validates with Validator; on failure it writes the response and returns false. DecodeAndValidateE returns an error instead of writing. DecodeJSON decodes without validation. ParseMultipartFormLimit parses multipart form with body and memory limits.
Pagination ¶
ClampPage, ClampPerPage, ClampLimit, ParseIntQuery, TotalPages, NewPaginationMeta, and PaginationMeta support page/per-page handling. Paginated[T] holds a page of items and metadata; NewPaginated builds it; FetchPage runs fetch and count in parallel and returns Paginated. MaxPage is the maximum allowed page number.
Query parsing ¶
ParseBoolQuery, ParseEnumQuery, ParseSortQuery, and ParseTimeQuery parse and validate query parameters.
Client IP ¶
ParseTrustedProxyCIDRs parses CIDR strings for trusted proxies. GetClientIPWithNets returns the client IP using X-Real-IP and X-Forwarded-For when the connection is from a trusted net. Restrict trustedNets to your actual proxy/load-balancer CIDRs; overly broad ranges (e.g. 0.0.0.0/0) allow clients to spoof X-Forwarded-For. GetClientIPE parses CIDRs from strings and returns an error on invalid input. GetClientIP is deprecated; use GetClientIPE.
Path and context ¶
ChiPathFromRequest returns the chi route pattern. UserIDKey is the context key for the authenticated user ID. GetUserID, ParseUUID, ParseUUIDField, and ParseAuthUserID read or parse IDs and optionally write error responses.
Search and download ¶
ValidateSearchQ checks length and control characters. EscapeILIKE and SanitizeSearchQ escape strings for PostgreSQL ILIKE. RenderJSONAttachment, RenderStream, RenderStreamLimited, and RenderBytes send file downloads with sanitized Content-Disposition filenames. RenderStreamLimited returns ErrStreamTruncated when maxBytes > 0 and the source exceeds the limit (response is already committed). RenderStream and RenderStreamLimited return ErrInvalidContentType for disallowed content types.
SSE and health ¶
NewSSEWriter and NewSSEWriterWithLimit return an SSEWriter for Server-Sent Events; Send and SendJSON are concurrent-safe. Close marks the writer done. NewSSEWriterWithLimit accepts MaxEventBytes(n) to cap event payload size (default 64KB); larger payloads return ErrSSEPayloadTooLarge. HealthHandler runs Checker implementations in parallel with a timeout and returns JSON status and checks. Use HealthOnEncodeError to log or handle encode failures; HealthHideDetails omits check details from the response.
Index ¶
- Constants
- Variables
- func ChiPathFromRequest(r *http.Request) string
- func ClampLimit(p *int, defaultVal, maxVal int) int
- func ClampPage(p *int) int
- func ClampPerPage(p *int, defaultVal, maxVal int) int
- func DecodeAndValidate[T any](w http.ResponseWriter, r *http.Request, v Validator, opts ...DecodeOption) (T, bool)
- func DecodeAndValidateE[T any](r *http.Request, v Validator, opts ...DecodeOption) (T, error)
- func DecodeJSON[T any](r *http.Request, v *T, opts ...DecodeOption) error
- func EscapeILIKE(s string, maxLen int) string
- func GetClientIP(r *http.Request, trustedProxyCIDRs []string) stringdeprecated
- func GetClientIPE(r *http.Request, trustedProxyCIDRs []string) (string, error)
- func GetClientIPWithNets(r *http.Request, trustedNets []*net.IPNet) string
- func GetUserID(ctx context.Context) string
- func HandleError(w http.ResponseWriter, r *http.Request, err error)
- func HealthHandler(checkers map[string]Checker, opts ...HealthHandlerOption) http.HandlerFunc
- func ParseAuthUserID(w http.ResponseWriter, r *http.Request) (uuid.UUID, bool)
- func ParseBoolQuery(r *http.Request, key string) (bool, bool)
- func ParseEnumQuery[T ~string](r *http.Request, key string, allowed []T) (T, bool)
- func ParseIntQuery(r *http.Request, key string) *int
- func ParseMultipartFormLimit(w http.ResponseWriter, r *http.Request, maxBodySize, maxMemory int64) bool
- func ParseSortQuery(r *http.Request, allowedFields []string) (field, dir string, ok bool)
- func ParseTimeQuery(r *http.Request, key, layout string) (time.Time, bool)
- func ParseTrustedProxyCIDRs(cidrs []string) ([]*net.IPNet, error)
- func ParseUUID(w http.ResponseWriter, r *http.Request, id string) (uuid.UUID, bool)
- func ParseUUIDField(w http.ResponseWriter, r *http.Request, value, field string) (uuid.UUID, bool)
- func Ptr[T any](v T) *T
- func RenderAccepted[T any](w http.ResponseWriter, r *http.Request, data T)
- func RenderBytes(w http.ResponseWriter, contentType, filename string, data []byte) error
- func RenderCreated[T any](w http.ResponseWriter, r *http.Request, data T)
- func RenderError(w http.ResponseWriter, r *http.Request, status int, message string)
- func RenderErrorWithCode(w http.ResponseWriter, r *http.Request, status int, message, code string)
- func RenderInvalidID(w http.ResponseWriter, r *http.Request)
- func RenderJSON[T any](w http.ResponseWriter, r *http.Request, status int, data T)
- func RenderJSONAttachment[T any](w http.ResponseWriter, data T, filename string) error
- func RenderNoContent(w http.ResponseWriter, _ *http.Request)
- func RenderOK[T any](w http.ResponseWriter, r *http.Request, data T)
- func RenderStream(w http.ResponseWriter, contentType, filename string, rc io.Reader) error
- func RenderStreamLimited(w http.ResponseWriter, contentType, filename string, rc io.Reader, ...) error
- func RenderText(w http.ResponseWriter, _ *http.Request, status int, contentType, body string)
- func SanitizeSearchQ(q string, maxLen int) string
- func TotalPages(total int64, perPage int) int
- func ValidateSearchQ(q string) bool
- type Checker
- type DecodeOption
- type ErrorHandler
- type ErrorResponse
- type HealthHandlerOption
- type Paginated
- type PaginationMeta
- type SSEOption
- type SSEWriter
- type ValidationErrorItem
- type ValidationErrorResponse
- type ValidationHTTPError
- type Validator
Constants ¶
const DefaultSearchMaxLen = 100
DefaultSearchMaxLen is the default maximum rune length for search input in ValidateSearchQ and EscapeILIKE.
const MaxPage = 10000
MaxPage is the maximum allowed page number for ClampPage.
const MaxRequestBodySize = 1 << 20
MaxRequestBodySize is the default body size limit (1 MiB) for DecodeAndValidate, DecodeAndValidateE, and DecodeJSON.
const UserIDKey contextKey = "user_id"
UserIDKey is the context key for the authenticated user ID (string, e.g. UUID). Set by auth middleware; read with GetUserID.
Variables ¶
var ErrInvalidContentType = errors.New("content-type contains invalid characters")
ErrInvalidContentType is returned when contentType contains CR/LF or disallowed characters (header injection).
var ErrRequestBodyTooLarge = errors.New("request body too large")
ErrRequestBodyTooLarge is returned by DecodeJSON when the request body exceeds the configured limit.
var ErrSSEClosed = errors.New("SSE writer closed")
ErrSSEClosed is returned by Send and SendJSON after the writer has been closed.
var ErrSSEPayloadTooLarge = errors.New("SSE event payload exceeds size limit")
ErrSSEPayloadTooLarge is returned by Send and SendJSON when the event payload would exceed MaxEventBytes.
var ErrStreamTruncated = errors.New("response stream truncated: source exceeded max bytes")
ErrStreamTruncated is returned by RenderStreamLimited when the source exceeds maxBytes and the response was truncated.
Functions ¶
func ChiPathFromRequest ¶ added in v0.1.1
ChiPathFromRequest returns the chi route pattern (e.g. "/users/{id}") from the request context, or "" if not set.
func ClampLimit ¶
ClampLimit returns limit clamped to [defaultVal, maxVal], or defaultVal if nil/<=0.
func ClampPage ¶
ClampPage returns the page number clamped to [1, MaxPage]; uses 1 if p is nil or *p < 1.
func ClampPerPage ¶
ClampPerPage returns perPage clamped to [defaultVal, maxVal], or defaultVal if nil/<=0. If defaultVal > maxVal, the effective default is maxVal so the result is never above maxVal.
func DecodeAndValidate ¶
func DecodeAndValidate[T any](w http.ResponseWriter, r *http.Request, v Validator, opts ...DecodeOption) (T, bool)
DecodeAndValidate reads JSON from the request body (limit from WithMaxBodySize or MaxRequestBodySize, no unknown fields, no trailing data), then validates with v. On error it writes the appropriate JSON response and returns (zero, false).
func DecodeAndValidateE ¶
DecodeAndValidateE reads and validates JSON from the request body and returns an error without writing a response. Returns *httperr.HTTPError for invalid JSON, trailing data, body too large, or validation failure.
func DecodeJSON ¶
func DecodeJSON[T any](r *http.Request, v *T, opts ...DecodeOption) error
DecodeJSON decodes JSON from the request body (limit from WithMaxBodySize or MaxRequestBodySize, no unknown fields, no trailing data) into v.
func EscapeILIKE ¶
EscapeILIKE escapes %, _, and \ for safe use in PostgreSQL ILIKE. Output is truncated to maxLen runes (or DefaultSearchMaxLen if maxLen <= 0).
func GetClientIP
deprecated
GetClientIP returns the client IP. When RemoteAddr is in a trusted proxy CIDR, X-Real-IP and X-Forwarded-For are used. Parse errors from trustedProxyCIDRs are ignored; if all are invalid, only RemoteAddr is used.
Deprecated: Use GetClientIPE so invalid CIDR configuration is not silently ignored.
func GetClientIPE ¶ added in v0.1.4
GetClientIPE returns the client IP like GetClientIP but returns an error when ParseTrustedProxyCIDRs fails (e.g. all CIDRs invalid). When only some CIDRs are invalid, returns the IP using valid nets and the parse error so the caller can log it.
func GetClientIPWithNets ¶ added in v0.1.4
GetClientIPWithNets returns the client IP, using X-Real-IP and X-Forwarded-For when RemoteAddr is in trustedNets.
func HandleError ¶
func HandleError(w http.ResponseWriter, r *http.Request, err error)
HandleError writes a JSON error response. If err implements HTTPStatus and GetCode (e.g. *httperr.HTTPError), that status and code are used; otherwise 500 and INTERNAL_ERROR. For 5xx the message is replaced with "Internal server error". ValidationHTTPError is rendered as ValidationErrorResponse with per-field errors.
func HealthHandler ¶ added in v0.1.3
func HealthHandler(checkers map[string]Checker, opts ...HealthHandlerOption) http.HandlerFunc
HealthHandler returns an HTTP handler that runs all checkers in parallel with a timeout and returns JSON with status and optional check results.
func ParseAuthUserID ¶
ParseAuthUserID returns the authenticated user's UUID from context. Writes 401 and returns (uuid.Nil, false) if not authenticated or invalid.
func ParseBoolQuery ¶ added in v0.1.3
ParseBoolQuery parses the query parameter key as a boolean. Accepts "1", "true", "yes" for true and "0", "false", "no" for false. Returns (value, true) when valid, (false, false) when missing or invalid.
func ParseEnumQuery ¶ added in v0.1.3
ParseEnumQuery parses the query parameter key and returns it as T if it is in allowed; otherwise returns ("", false).
func ParseIntQuery ¶
ParseIntQuery parses the first query parameter key as positive int; returns nil if missing or invalid.
func ParseMultipartFormLimit ¶
func ParseMultipartFormLimit(w http.ResponseWriter, r *http.Request, maxBodySize, maxMemory int64) bool
ParseMultipartFormLimit parses the multipart form with the given body size and memory limits. On error writes an error response and returns false.
func ParseSortQuery ¶ added in v0.1.3
ParseSortQuery parses the "sort" query parameter. Supports "field", "-field", or "field:asc"/"field:desc". Returns (field, dir, true) when field is in allowedFields and dir is "asc" or "desc"; otherwise ("", "", false).
func ParseTimeQuery ¶ added in v0.1.3
ParseTimeQuery parses the query parameter key with the given time layout (e.g. time.RFC3339). Returns (zero time.Time, false) on missing or invalid value.
func ParseTrustedProxyCIDRs ¶ added in v0.1.4
ParseTrustedProxyCIDRs parses CIDR strings into networks. Invalid or empty entries are skipped. Returns (nil, error) if all entries are invalid. If only some are invalid, returns (valid nets, error) so the caller can log the invalid list.
func ParseUUID ¶
ParseUUID parses id as a UUID. On failure writes an error response and returns (uuid.Nil, false).
func ParseUUIDField ¶
ParseUUIDField parses value as a UUID. On failure writes a validation error for the given field name and returns (uuid.Nil, false). field is used in the error message sent to the client; it must be a constant or sanitized (e.g. alphanumeric, underscore). Do not pass user-controlled input as field.
func RenderAccepted ¶ added in v0.1.1
func RenderAccepted[T any](w http.ResponseWriter, r *http.Request, data T)
RenderAccepted sends 202 Accepted with data as JSON body.
func RenderBytes ¶
func RenderBytes(w http.ResponseWriter, contentType, filename string, data []byte) error
RenderBytes writes raw bytes with Content-Type and Content-Disposition attachment (filename sanitized).
func RenderCreated ¶
func RenderCreated[T any](w http.ResponseWriter, r *http.Request, data T)
RenderCreated sends 201 Created with data as JSON body.
func RenderError ¶
RenderError sends JSON error with status and message; code is derived from status. For 5xx status, message is replaced with "Internal server error" to avoid leaking details.
func RenderErrorWithCode ¶
RenderErrorWithCode sends JSON error with explicit code. For 5xx status, message is replaced with "Internal server error" to avoid leaking details.
func RenderInvalidID ¶
func RenderInvalidID(w http.ResponseWriter, r *http.Request)
RenderInvalidID sends 400 Bad Request with code INVALID_ID.
func RenderJSON ¶
RenderJSON writes data as JSON with the given HTTP status.
func RenderJSONAttachment ¶
func RenderJSONAttachment[T any](w http.ResponseWriter, data T, filename string) error
RenderJSONAttachment encodes data as JSON and writes it with Content-Disposition attachment and sanitized filename.
func RenderNoContent ¶
func RenderNoContent(w http.ResponseWriter, _ *http.Request)
RenderNoContent sends 204 No Content with no body.
func RenderOK ¶
func RenderOK[T any](w http.ResponseWriter, r *http.Request, data T)
RenderOK sends 200 OK with data as JSON body.
func RenderStream ¶
RenderStream streams the response with Content-Disposition attachment. Caller is responsible for closing rc after the function returns (e.g. if rc implements io.Closer, call rc.Close() in defer). For untrusted or unbounded sources use RenderStreamLimited to cap the response size. contentType is sent as-is in the Content-Type header; it MUST NOT contain user-controlled input—validate or use a fixed allowlist to avoid header injection (e.g. XSS via attachment).
func RenderStreamLimited ¶ added in v0.1.4
func RenderStreamLimited(w http.ResponseWriter, contentType, filename string, rc io.Reader, maxBytes int64) error
RenderStreamLimited is like RenderStream but limits the number of bytes copied from rc to maxBytes. If maxBytes <= 0, no limit is applied. When maxBytes > 0 and the source exceeds the limit, the response is already committed (headers and up to maxBytes sent) and the function returns ErrStreamTruncated so the caller can log or handle the truncation. Use a pre-buffered reader or Content-Length if you need to reject before sending.
func RenderText ¶
RenderText writes a plain text response with the given status, Content-Type, and body. Only text/plain, application/json, and application/octet-stream are allowed; any other Content-Type is forced to "text/plain; charset=utf-8" to avoid reflected XSS when body contains user input. Do not pass user-controlled content that could be interpreted as HTML; ensure clients do not render the response as HTML.
func SanitizeSearchQ ¶
SanitizeSearchQ is EscapeILIKE with default max length (DefaultSearchMaxLen when maxLen <= 0).
func TotalPages ¶ added in v0.1.2
TotalPages calculates the total number of pages for a given total and perPage.
func ValidateSearchQ ¶
ValidateSearchQ returns true if q is valid for search (within DefaultSearchMaxLen runes and no control characters).
Types ¶
type Checker ¶ added in v0.1.3
Checker performs a single health check. Implementations should respect ctx cancellation.
type DecodeOption ¶ added in v0.2.0
type DecodeOption func(*decodeConfig)
DecodeOption configures decode behaviour (e.g. body size limit).
func WithMaxBodySize ¶ added in v0.2.0
func WithMaxBodySize(n int64) DecodeOption
WithMaxBodySize sets the request body size limit for decode. Values <= 0 use MaxRequestBodySize.
type ErrorHandler ¶ added in v0.1.2
type ErrorHandler struct {
// Logger is optional; when set, Handle logs 4xx at Info and 5xx at Error.
Logger logger.Logger
}
ErrorHandler handles errors by optionally logging and writing a JSON error response via HandleError.
func (*ErrorHandler) Handle ¶ added in v0.1.2
func (h *ErrorHandler) Handle(w http.ResponseWriter, r *http.Request, err error, msg string) bool
Handle logs err (if Logger is set) and writes a JSON error response. Returns true if err was non-nil and handled. 4xx errors are logged at Info level, everything else at Error level.
type ErrorResponse ¶
ErrorResponse is the JSON body for a single error (code and message).
type HealthHandlerOption ¶ added in v0.1.4
type HealthHandlerOption func(*healthOpts)
HealthHandlerOption configures HealthHandler behaviour.
func HealthHideDetails ¶ added in v0.1.4
func HealthHideDetails() HealthHandlerOption
HealthHideDetails omits per-check results from the JSON response; only status (ok/degraded) is returned.
func HealthOnEncodeError ¶ added in v0.1.4
func HealthOnEncodeError(f func(error)) HealthHandlerOption
HealthOnEncodeError sets a callback invoked when JSON encoding of the health response fails (e.g. for logging).
type Paginated ¶ added in v0.1.4
type Paginated[T any] struct { Data []T `json:"data"` Total int64 `json:"total"` Page int `json:"page"` PerPage int `json:"per_page"` TotalPages int `json:"total_pages"` }
Paginated holds a page of items and pagination metadata for JSON responses.
func FetchPage ¶ added in v0.1.4
func FetchPage[T any]( ctx context.Context, page, perPage int, fetchFn func(ctx context.Context, limit, offset int) ([]T, error), countFn func(ctx context.Context) (int64, error), ) (*Paginated[T], error)
FetchPage runs fetchFn and countFn in parallel and returns a Paginated result. Page and perPage are clamped to at least 1.
type PaginationMeta ¶ added in v0.1.2
type PaginationMeta struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
Total int `json:"total"`
TotalPages int `json:"total_pages"`
}
PaginationMeta holds pagination metadata for a response.
func NewPaginationMeta ¶ added in v0.1.2
func NewPaginationMeta(page, perPage int, total int64) PaginationMeta
NewPaginationMeta builds PaginationMeta with Total and TotalPages derived from total and perPage.
type SSEOption ¶ added in v0.1.4
type SSEOption func(*SSEWriter)
SSEOption configures NewSSEWriterWithLimit.
func MaxEventBytes ¶ added in v0.1.4
MaxEventBytes limits the size of a single event (event + data lines); 0 means use default (64KB).
type SSEWriter ¶ added in v0.1.3
type SSEWriter struct {
// contains filtered or unexported fields
}
SSEWriter sends Server-Sent Events. Create with NewSSEWriter or NewSSEWriterWithLimit. Send and SendJSON are safe for concurrent use. Call Close when done. The 200 response is committed on the first successful Send or SendJSON, not in NewSSEWriter.
func NewSSEWriter ¶ added in v0.1.3
func NewSSEWriter(w http.ResponseWriter) (*SSEWriter, bool)
NewSSEWriter sets SSE headers on w and returns an SSEWriter. The 200 response is sent on the first Send or SendJSON. The second return value is false if w does not implement http.Flusher.
func NewSSEWriterWithLimit ¶ added in v0.1.4
func NewSSEWriterWithLimit(w http.ResponseWriter, opts ...SSEOption) (*SSEWriter, bool)
NewSSEWriterWithLimit is like NewSSEWriter but accepts options (e.g. MaxEventBytes). Does not write 200 until the first Send or SendJSON. If MaxEventBytes is not set, a default of 64KB is used to limit memory for untrusted event sizes.
func (*SSEWriter) Close ¶ added in v0.1.3
func (s *SSEWriter) Close()
Close marks the writer as done; subsequent Send or SendJSON calls return ErrSSEClosed.
type ValidationErrorItem ¶
ValidationErrorItem is one field-level validation error.
type ValidationErrorResponse ¶
type ValidationErrorResponse struct {
Code string `json:"code"`
Message string `json:"message"`
Errors []ValidationErrorItem `json:"errors,omitempty"`
}
ValidationErrorResponse is the JSON body for validation errors (code, message, and optional per-field errors).
type ValidationHTTPError ¶ added in v0.1.4
type ValidationHTTPError struct {
*httperr.HTTPError
Errors []ValidationErrorItem
}
ValidationHTTPError is an HTTPError that carries per-field validation errors for JSON responses.
func (*ValidationHTTPError) Error ¶ added in v0.1.4
func (e *ValidationHTTPError) Error() string
func (*ValidationHTTPError) GetCode ¶ added in v0.1.4
func (e *ValidationHTTPError) GetCode() string
func (*ValidationHTTPError) HTTPStatus ¶ added in v0.1.4
func (e *ValidationHTTPError) HTTPStatus() int
func (*ValidationHTTPError) Unwrap ¶ added in v0.1.4
func (e *ValidationHTTPError) Unwrap() error
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package middleware provides HTTP middleware for use with go-httpkit and chi.
|
Package middleware provides HTTP middleware for use with go-httpkit and chi. |