Documentation
¶
Overview ¶
Package edge holds the HTTP edge code shared by the frontend and the backend process: working out who the visitor is behind a reverse proxy, the PROXY protocol, and request limiting. It must not import the store, so the frontend can use it without database access.
Index ¶
- Constants
- func ClientIP(r *http.Request) string
- func Compress(next http.Handler) http.Handler
- func ExternalURL(r *http.Request, path string) string
- func IPKey(r *http.Request) string
- func IsSecure(r *http.Request) bool
- func LastForwarded(r *http.Request, name string) string
- func LimitBody(next http.Handler) http.Handler
- func LogRequests(next http.Handler) http.Handler
- func NoStore(h http.Header)
- func ParseBaseURL(raw string) (*url.URL, error)
- func ParseTrustedProxies(items []string) ([]netip.Prefix, error)
- func ProxyListener(ln net.Listener, policy func() (enabled bool, trusted []netip.Prefix)) net.Listener
- func SecureHeaders(next http.Handler) http.Handler
- func Trusted(trusted []netip.Prefix, a netip.Addr) bool
- func ValidHost(h string) bool
- func WithBaseURL(ctx context.Context, base *url.URL) context.Context
- func WithSecure(ctx context.Context, secure bool) context.Context
- type Limiter
- type Proxy
- type Settings
Constants ¶
const ContentSecurityPolicy = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; object-src 'none'"
contentSecurityPolicy only allows our own scripts. Styles may be inline because a few templates size inputs with a style attribute and htmx injects its indicator rules.
Variables ¶
This section is empty.
Functions ¶
func ClientIP ¶
ClientIP is the address of the peer, after proxy headers were settled by Proxy.Headers.
func ExternalURL ¶
ExternalURL builds an absolute URL for this server as seen by the browser. The configured base URL wins; without one the request's own host is used, which a proxy must pass on unchanged.
func IPKey ¶
IPKey identifies the client for rate limiting. An IPv6 client is counted by its /64, because a single subscriber usually controls the whole prefix and could otherwise rotate addresses freely.
func IsSecure ¶
IsSecure reports whether the visitor reached the app over HTTPS: directly, through a trusted proxy that said so, or because the configured base URL is https.
func LastForwarded ¶
LastForwarded returns the last value of a forwarding header: the one the nearest proxy added.
func LimitBody ¶
LimitBody rejects oversized form posts before any handler parses them, which would otherwise buffer up to 32 MiB of multipart data in memory and temp files for anonymous clients.
func LogRequests ¶
LogRequests logs every request at debug level.
func NoStore ¶
NoStore tells every cache that the response is for one visitor only. Cache-Control is what browsers and well-behaved proxies read. CDN-Cache-Control (RFC 9213, read by Cloudflare, Fastly and Akamai) and Surrogate-Control speak to CDNs directly, and a CDN rule such as "cache everything" may override Cache-Control but not these.
func ParseBaseURL ¶
ParseBaseURL checks the public address of the app, such as https://dexa.example.com. It must be an origin: the app serves from the root of its host and cannot live under a sub-path.
func ParseTrustedProxies ¶
ParseTrustedProxies turns addresses, CIDR ranges and the keywords "loopback" and "private" into prefixes. "private" covers loopback, the RFC 1918 ranges and IPv6 unique local addresses.
func ProxyListener ¶
func ProxyListener(ln net.Listener, policy func() (enabled bool, trusted []netip.Prefix)) net.Listener
ProxyListener applies the PROXY protocol policy to a listener. policy is asked for every new connection and reports whether the protocol is enabled and which peers may send the header. Wrap a listener once at start; it costs nothing while the setting is off. The HTTPS listener uses it too: the header comes before the TLS handshake.
func SecureHeaders ¶
SecureHeaders adds the browser hardening headers to every response and keeps pages that can hold personal data (or a freshly minted token) out of shared and back/forward caches.
func WithBaseURL ¶
WithBaseURL records the configured public address, which wins over the request's own host.
Types ¶
type Limiter ¶
type Limiter struct {
// MaxInFlight is the number of requests served at once. 0 means no cap.
MaxInFlight int
// MaxQueue is how many more requests may wait for a slot. Beyond that they are refused at once.
MaxQueue int
// MaxWait bounds how long a queued request waits for a slot.
MaxWait time.Duration
// Rate and Burst configure the per-key token bucket (requests per second, bucket size).
// Rate 0 turns it off.
Rate float64
Burst int
// Key names the bucket a request belongs to. nil means IPKey.
Key func(*http.Request) string
// Skip exempts a request (cheap static files, health checks).
Skip func(*http.Request) bool
// contains filtered or unexported fields
}
Limiter sheds load before it reaches the handlers behind it. It combines
- a cap on requests being served at once, with a bounded queue and a bounded wait, so a flood is answered with a cheap 503 instead of piling up work; and
- an optional token bucket per key (normally the client address), so one client cannot take the whole budget.
The zero values of the optional fields turn the matching part off.
func (*Limiter) Acquire ¶
Acquire reserves a slot. It reports false when the request must be refused: the queue is full or the wait ran out. The returned release must be called when the request is done.