Documentation
¶
Overview ¶
Package httpx holds shared HTTP plumbing: middleware, the API response envelope, form binding/validation, the SSRF-safe client and the admin template renderer.
Index ¶
- Constants
- Variables
- func AssetCacheControl(r *http.Request, name string) string
- func Bind(r *http.Request, dst any) error
- func ClientIP(r *http.Request) string
- func ClientIPSource(ctx context.Context) string
- func Compress(next http.Handler) http.Handler
- func Error(w http.ResponseWriter, status int, code, msg string, fields any)
- func JSON(w http.ResponseWriter, status int, data, meta any)
- func Logger(log *slog.Logger) func(http.Handler) http.Handler
- func NewSafeClient(allowPrivate bool) *http.Client
- func NewSafeClientOpts(o SafeClientOptions) *http.Client
- func Nonce(ctx context.Context) string
- func RealIP(next http.Handler) http.Handler
- func RealIPWithTrustedProxies(prefixes []netip.Prefix) func(http.Handler) http.Handler
- func Recover(log *slog.Logger) func(http.Handler) http.Handler
- func RedactPath(p string) string
- func RequestID(next http.Handler) http.Handler
- func RequestIDFromContext(ctx context.Context) string
- func RequestIsHTTPS(r *http.Request) bool
- func SecureCookie(r *http.Request, configuredBaseURL string) bool
- func SecurityHeaders(publicScripts ...string) func(http.Handler) http.Handler
- func SecurityHeadersWith(o SecurityOptions) func(http.Handler) http.Handler
- func SetFlash(w http.ResponseWriter, kind, message string)
- func TrustedProxyAddress(r *http.Request, ip netip.Addr) bool
- func WarnUntrustedForwarding(log *slog.Logger) func(http.Handler) http.Handler
- func WithTrustedProxies(ctx context.Context, prefixes []netip.Prefix) context.Context
- type FieldErrors
- type Flash
- type Funcs
- type Meta
- type NavGroup
- type NavItem
- type NavigationSection
- type NavigationTabGroup
- type Pagination
- type QuickAccessItem
- type SafeClientOptions
- type SecurityOptions
- type SidebarBrand
- type Templates
- func (t *Templates) RegisterExtension(fsys fs.FS) error
- func (t *Templates) Render(w http.ResponseWriter, r *http.Request, page string, data any)
- func (t *Templates) RenderError(w http.ResponseWriter, r *http.Request, status int)
- func (t *Templates) RenderPartial(w http.ResponseWriter, r *http.Request, name string, data any)
- func (t *Templates) RenderWith(w http.ResponseWriter, r *http.Request, status int, layout, page string, ...)
- func (t *Templates) SetSidebarBrandResolver(resolve func(context.Context) SidebarBrand)
Constants ¶
const ( CacheImmutable = "public, max-age=31536000, immutable" CacheShort = "public, max-age=3600" )
Cache-Control values for static assets.
const ( ScalarScript = "https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.25.0" ScalarIntegrity = "sha384-VPR3QD7bdyIR9EHf6dRrFXz7NO0yJV7/UCVTLQ6e7a0wgzot5WaxvciVrrYOKEpU" )
ScalarScript is the pinned Scalar API reference bundle served on /api/docs, and ScalarIntegrity its SRI hash. It is the only third-party script the built-in pages load, so the CSP allows exactly this URL on exactly that page.
const ( // StaticPrefix is where admin/static is served. StaticPrefix = "/static/admin/" // LaunchCSSPath is the public URL for the embedded launch overlay stylesheet. // Downstream /start pages and prior Traefik mounts use this exact path. LaunchCSSPath = "/static/launch.css" // LaunchPolishPath is the public URL for the launch title/validation script. LaunchPolishPath = "/static/launch-polish.js" )
Template layout (inside the FS given to NewTemplates):
admin/templates/base.html shared defines ("head", "scripts")
admin/templates/layouts/<n>.html full documents; call {{template "content" .}}
admin/templates/partials/**.html {{define "partials/<name>"}} blocks
admin/templates/pages/**.html {{define "title"}} + {{define "content"}}
admin/static/* served at /static/admin/ (see asset func)
Every file is parsed under its path relative to admin/templates. A page is executed through layouts/<layout>.html, or only its "content" template for HTMX requests (HX-Request: true).
Variables ¶
var ErrRedirectRefused = errors.New("httpx: redirect refused")
ErrRedirectRefused is returned by a client built with NoRedirects when the server answers with a redirect.
Functions ¶
func AssetCacheControl ¶
AssetCacheControl returns CacheImmutable for asset URLs whose content cannot change under the same URL: a ?v= cache-busting query, or a file name carrying its release version. Everything else gets CacheShort.
func Bind ¶
Bind fills the struct pointed to by dst from the request. JSON bodies (Content-Type application/json, max 1 MiB) use encoding/json; otherwise form values (URL-encoded or multipart) are matched by the `form` tag (default: lower-cased field name; "-" skips). Supported field kinds: string, bool ("on", "true", "1", "yes"), ints, uints, floats, []string and *string (nil when absent).
func ClientIP ¶
ClientIP walks the forwarded chain only through explicitly trusted hops. A malformed chain is ignored as a whole; provider-specific headers are never trusted.
func ClientIPSource ¶
func Compress ¶
Compress gzips text responses (see compressibleTypes) for clients that accept gzip. It leaves alone: responses that already have a Content-Encoding, range requests, bodiless statuses, and admin pages (/admin and /admin/...), which embed the session's CSRF token next to user-controlled text and so stay uncompressed to rule out BREACH-style length oracles. Admin static assets live under /static/admin/ and are compressed. It always adds Vary: Accept-Encoding to compressible paths.
func Error ¶
func Error(w http.ResponseWriter, status int, code, msg string, fields any)
Error writes {"ok":false,"error":{"code","message","fields"}}; fields is omitted when nil.
func JSON ¶
func JSON(w http.ResponseWriter, status int, data, meta any)
JSON writes {"ok":true,"data":data,"meta":meta}; meta is omitted when nil.
func NewSafeClient ¶
NewSafeClient returns an HTTP client for fetching user-supplied URLs. Unless allowPrivate, its dialer refuses blocked addresses after DNS resolution (so DNS rebinding cannot bypass it). Redirects may only stay on the original host. Environment proxies are ignored.
func NewSafeClientOpts ¶
func NewSafeClientOpts(o SafeClientOptions) *http.Client
NewSafeClientOpts is NewSafeClient with options (see SafeClientOptions).
func RealIP ¶
RealIP stores ClientIP in the context (audit.WithIP) for audit, logging and login rate limiting.
func RedactPath ¶
RedactPath replaces everything after a secret prefix with "…".
func RequestID ¶
RequestID reuses a well-formed incoming X-Request-ID or generates a ULID, stores it in the context and echoes it in the response header.
func RequestIDFromContext ¶
RequestIDFromContext returns the request id set by RequestID.
func RequestIsHTTPS ¶
RequestIsHTTPS reports whether r arrived over https: TLS on this connection, or X-Forwarded-Proto "https" (first value) set by a trusted proxy peer (the same trust rule as ClientIP).
func SecureCookie ¶
SecureCookie is the Secure flag for a cookie set while handling r: the request is https (RequestIsHTTPS), else the operator-configured base URL (GORUN_BASE_URL, never the site.base_url setting) is https.
func SecurityHeaders ¶
SecurityHeaders sets CSP (with a per-request nonce, see Nonce), X-Frame-Options (SAMEORIGIN for public pages, DENY for /admin and /api), nosniff, Referrer-Policy, and HSTS when the request itself is https (RequestIsHTTPS: TLS, or X-Forwarded-Proto from a trusted proxy). It never follows a database setting, so a wrong site.base_url cannot pin browsers to https on a plain-http deployment.
Scripts come from the site itself or carry the nonce. The admin adds 'unsafe-eval' because its Alpine directives are inline expressions that only the standard (eval-based) Alpine build evaluates. Public pages get no eval and no CDN: the bundled themes self-host everything (the default theme's Tailwind runtime included, which does not eval). publicScripts are the operator's extra public script sources (config.PublicScriptSources) for custom themes that still load a CDN or self-hosted Alpine. Inline styles serve Tailwind's runtime and HTMX indicators; blob: images serve upload previews.
func SecurityHeadersWith ¶
func SecurityHeadersWith(o SecurityOptions) func(http.Handler) http.Handler
SecurityHeadersWith is SecurityHeaders with options.
func SetFlash ¶
func SetFlash(w http.ResponseWriter, kind, message string)
SetFlash stores a one-shot message (kind: success, error, info; message: an i18n key) shown by the next rendered page.
func WarnUntrustedForwarding ¶
WarnUntrustedForwarding returns middleware that logs (at most once per hour, first occurrence immediately) when forwarding headers arrive from a peer outside the trusted proxies. It must run after RealIPWithTrustedProxies, which puts the trusted networks in the request context. It never changes the request.
Types ¶
type FieldErrors ¶
FieldErrors maps a field's form name to a translated message.
func Validate ¶
Validate checks `validate` struct tags and returns messages translated for i18n.Locale(ctx), keyed like Bind's form names. Rules (comma-separated, first failure wins): required, email, min=N / max=N (string length in characters, or numeric value), oneof=a b c. Optional empty fields skip the other rules.
type Flash ¶
type Flash struct{ Kind, Message string }
Flash is a one-shot message set by SetFlash; Message is an i18n key.
type Funcs ¶
type Funcs struct {
User func(ctx context.Context) any
CSRF func(ctx context.Context) string
Setting func(ctx context.Context, key string) string
}
Funcs supplies request-scoped data to the template funcs. Any may be nil.
type NavGroup ¶
type NavGroup struct {
NavGroup is one sidebar section returned by the nav template func. Label is LabelKey translated for the request locale; for a group without a catalog entry it is the key's last segment title-cased ("field_kit" → "Field Kit"), never the raw key.
type NavItem ¶
type NavItem struct {
NavItem mirrors NavItem field for field, so callers convert with httpx.NavItem(k). httpx cannot import kernel: kernel installs httpx's middleware on the root mux.
type NavigationSection ¶ added in v0.6.0
type NavigationSection struct {
NavigationSection presents the authorized, personalized registry without changing IDs, routes, permissions or stored preferences.
type NavigationTabGroup ¶ added in v0.6.0
type NavigationTabGroup struct {
type Pagination ¶
Pagination is returned by the pagination template func.
func NewPagination ¶
func NewPagination(page, perPage, total int) Pagination
NewPagination computes page links; page and perPage are clamped to ≥ 1.
type QuickAccessItem ¶ added in v0.9.0
QuickAccessItem contains only a currently authorized registry item. Categories share the main navigation's semantic colors; no saved label/URL is trusted.
type SafeClientOptions ¶
type SafeClientOptions struct {
// AllowPrivate disables the private-network dial check (tests, or an
// explicitly trusted internal endpoint).
AllowPrivate bool
// FollowCrossHost lets redirects leave the original host. Every hop still
// dials through the private-network check.
FollowCrossHost bool
// Timeout bounds the whole request including redirects (default 30s).
Timeout time.Duration
// MaxRedirects caps the redirect chain (default 5).
MaxRedirects int
// NoRedirects refuses every redirect with ErrRedirectRefused (signed
// webhook deliveries, which must reach exactly the configured URL).
NoRedirects bool
}
SafeClientOptions configures NewSafeClientOpts. Zero values mean the defaults: private addresses blocked, same-host redirects only, 30s timeout, at most 5 redirects. An https request is never redirected to http (that would resend the body and headers in cleartext).
type SecurityOptions ¶
type SecurityOptions struct {
// PublicScripts are extra public-page script sources (see SecurityHeaders).
PublicScripts []string
// HSTSIncludeSubdomains and HSTSPreload extend the HSTS header
// (config.HSTS*). Both are off by default: includeSubDomains would force
// https on every subdomain of the site's host, and preload is a
// browser-wide commitment that is slow to undo.
HSTSIncludeSubdomains, HSTSPreload bool
}
SecurityOptions configure SecurityHeadersWith.
func (SecurityOptions) HSTSValue ¶
func (o SecurityOptions) HSTSValue() string
HSTSValue is the Strict-Transport-Security value for o: one year, plus includeSubDomains and preload when enabled.
type SidebarBrand ¶
type Templates ¶
type Templates struct {
// contains filtered or unexported fields
}
Templates renders admin pages. In dev every request re-parses the FS when a source can change on disk; in prod, or when every source is embedded, everything is parsed once by NewTemplates.
func NewTemplates ¶
func NewTemplates(fsys fs.FS, dev bool, b *i18n.Bundle, f Funcs, log *slog.Logger) (*Templates, error)
NewTemplates parses fsys (see the layout above) and fails on any template error, so prod boot catches broken templates.
func (*Templates) RegisterExtension ¶
RegisterExtension attaches module-owned admin pages and asset hashes during module registration, before serving requests. Extensions cannot override core templates or other extensions. The owning module serves its static assets.
func (*Templates) RenderError ¶
RenderError renders pages/errors/<status>.html (403, 404, else 500) in the auth layout, falling back to plain text.
func (*Templates) RenderPartial ¶
RenderPartial executes a single named template (e.g. "partials/flash") without any layout.
func (*Templates) RenderWith ¶
func (t *Templates) RenderWith(w http.ResponseWriter, r *http.Request, status int, layout, page string, data any)
RenderWith renders page with the given status inside layouts/<layout>.html, or only its "content" template when layout is "" or for HTMX requests. Errors are logged and answered with a plain 500.
func (*Templates) SetSidebarBrandResolver ¶
func (t *Templates) SetSidebarBrandResolver(resolve func(context.Context) SidebarBrand)
SetSidebarBrandResolver connects the site's identity service during module registration.