Documentation
¶
Overview ¶
Package relay serves third-party services (analytics vendors, chat widgets, error trackers) first-party: a declarative, hardened same-origin reverse proxy mounted under one path on the host app.
The point is the Content-Security-Policy. A host that links https://vendor.example/collect.js must punch script-src/connect-src holes into its strict default CSP and hand every visitor's browser a direct channel to the vendor's origin. A host that relays the vendor through /__gofastr/t/... keeps `default-src 'self'` untouched: the browser only ever talks to the app's own origin.
This is a proxy, not a tunnel. Every route names ONE fixed upstream at construction; request data (path tail, query, headers, body) never selects scheme, host, or port. See the hardening contract in framework/docs/content/relay.md.
Index ¶
Constants ¶
const DefaultPath = "/__gofastr/t"
DefaultPath is the mount prefix used when Config.Path is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// Path is the local mount prefix. Default "/__gofastr/t".
//
// Validated at New: absolute, no trailing slash, no traversal, no
// percent-encoding, and no collision with a reserved /__gofastr
// route. Any other absolute path works ("/firstparty", "/fp", …).
Path string
// Routes declare what gets proxied. Required, non-empty.
Routes []Route
// ClientIP resolves the client IP written into X-Forwarded-For.
// Default: the host part of r.RemoteAddr.
//
// Inbound X-Forwarded-For is NEVER trusted, on any setting: the
// relay sits on the public edge where that header is one
// `curl -H` away from arbitrary values. Override this only with a
// resolver backed by a trusted proxy layer (e.g. a PROXY-protocol
// or unix-socket peer), never with a header-reading function.
ClientIP func(*http.Request) string
}
Config constructs a Relay. The zero value is invalid: routes are the whole point and must be declared explicitly (empty Methods is a configuration error, not "allow everything").
type Relay ¶
type Relay struct {
// contains filtered or unexported fields
}
Relay is the first-party relay plugin. Construct with New, register with App.RegisterPlugin. Implements framework.Plugin.
func New ¶
New validates cfg and constructs the Relay. It panics on invalid configuration with a message prefixed "relay:": a bad relay Config is a construction-time programmer error (same posture as framework.NewApp's registration panics), not a runtime condition the process should limp along with.
func (*Relay) Base ¶
Base returns the mount path (Config.Path, or the default). Use it to point server-side SDKs and page templates at the relay without hard-coding the prefix:
plausibleBase := relay.Base() + "/e"
type Route ¶
type Route struct {
// Prefix is the local path segment under Path, e.g. "assets/" or
// "e".
//
// A trailing slash declares a subtree: Path + "/" + Prefix +
// "{rest...}" maps the sanitized remainder of the request path
// onto the same remainder of Upstream. Without a trailing slash
// the route matches exactly Path + "/" + Prefix and nothing
// deeper.
Prefix string
// Upstream is the absolute https:// URL (scheme, host, optional
// base path) the route proxies to. The subtree rest (sanitized) is
// appended to the base path; the query string passes through.
//
// http:// is accepted ONLY for loopback hosts so tests and local
// development work; any other http:// upstream panics at New.
// Private/internal non-loopback hosts (RFC1918, link-local
// including cloud metadata, CGNAT, IPv6 unique-local,
// *.internal, metadata.google.internal) are refused at New
// regardless of scheme: the relay has no per-customer SSRF story
// yet, so it refuses to point at anything that smells like
// internal infrastructure. localhost and *.localhost count as
// loopback, so they are ACCEPTED (that's how tests and local dev
// point at an httptest upstream).
Upstream string
// Methods is the allow-list of HTTP methods the route answers.
// Empty is invalid — be explicit. Anything else gets 405 with an
// Allow header.
Methods []string
// MaxBodyBytes caps the request body per route. Default 8 MiB
// when zero; both declared Content-Length and chunked bodies are
// enforced, with 413 on overflow.
MaxBodyBytes int64
// CacheOK selects the response caching posture.
//
// false (default): responses carry Cache-Control: no-store. The
// relay is for beacons and dynamic endpoints; caching vendor
// responses under the app's own origin is rarely what anyone
// wants.
//
// true: upstream cache headers pass through unchanged, for
// immutable versioned assets (/t/assets/sdk@1.2.3.js).
CacheOK bool
}
Route is one fixed upstream mounted under Config.Path.