relay

package
v0.72.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: MIT Imports: 16 Imported by: 0

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

View Source
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

func New(cfg Config) *Relay

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

func (r *Relay) Base() string

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"

func (*Relay) Init

func (r *Relay) Init(app *framework.App) error

Init registers the routes on the App's router and wires transport cleanup into shutdown. One ServeMux pattern per (route, method): exact routes register Path + "/" + Prefix, subtree routes register Path + "/" + Prefix + "{rest...}".

func (*Relay) Name

func (r *Relay) Name() string

Name implements framework.Plugin.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL