docs: a Forwarding section in the README
commit db16d53845b564e6f09415db2a49f5aa5d35e8ec · parent 06f4ceb908
· browse this tree
Ximianth, 2026-08-14 16:40:33 UTC
Also softens two now-stale absolutes nearby: the intro line said "no dynamic anything", and config errors said "zero exhibits" where a forwards-only config is now legitimate.
diff --git a/README.md b/README.md index 1bbf98c..4303631 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Point it at some directories via a small config file, and it serves them: markdown renders to HTML, directories become listings, and `?fmt=raw` on any URL serves the file bytes directly. No javascript required, no uploads, no auth, no dynamic anything beyond rendering. +(URL prefixes can also forward to other local services; see Forwarding below.) Config ------ @@ -38,7 +39,7 @@ exhibits: Trailing slashes on names and paths are accepted and normalized away. - `expose-git` (optional, default false): see below. -A bad config (parse error, unknown key, missing path, or zero exhibits) +A bad config (parse error, unknown key, missing path, or nothing configured at all) is a startup error: vitrine refuses to run rather than serve the wrong thing. An exhibit whose directory is absent is not a bad config, though: @@ -50,6 +51,48 @@ Config changes apply live: vitrine watches the config file so a change lands within ~5s worst case, usually instantly). Edit freely; a typo mid-edit logs a rejection and leaves the running server untouched. +Forwarding +---------- + +Besides serving file trees, vitrine can reverse-proxy URL prefixes to other services, +so one publicly routable port can carry both the docs and, say, a game server: + +```yaml +exhibits: + some-repo: + path: "%" +forwards: + /: + to: http://localhost:8866 + tools/dashboard: + to: http://localhost:9090 +``` + +- `forwards` maps URL prefixes to backend base URLs, sibling to `exhibits`. + Names share the URL namespace with exhibits (a collision is a config error) + and appear in listings like any other mount. +- The name `/` is the root forward: it catches every request + that no exhibit, forward, or listing claims. + Exhibits keep winning at their own prefixes -- dispatch is longest-prefix-match -- + so a backend at the root simply never sees URLs that collide + with an exhibit prefix, its ancestors, or the reserved `/-/` prefix. +- `to` must be an absolute `http://` or `https://` URL; the scheme is mandatory. + Websocket backends are also `http://`: a websocket connection begins + as an ordinary http request with an `Upgrade` header, which the proxy passes through, + so websockets and SSE streams work with no extra configuration. +- A path on the `to` URL becomes a prefix on every proxied request. +- The mount prefix is stripped before proxying: + a backend behind `tools/dashboard` sees `/x`, not `/tools/dashboard/x`. + It should therefore use relative URLs in its pages + (or be told its public prefix by its own means; vitrine does not rewrite response bodies). +- An unreachable backend answers 502 per-request. + Backend liveness is deliberately not part of config validation: + backends come and go while vitrine runs. + +Forwarding is one of two deliberate exceptions to "read-only, no dynamic anything" +(the other is `expose-git`); vitrine adds no auth, no TLS, and no rewriting -- +the forward is a pane of glass to another local port. + Theming ------- diff --git a/main.go b/main.go index 8cda9ea..dd0f22e 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,8 @@ // // Exhibits come from a YAML config file given via the required -config flag; // see config.go for the schema and README.md for examples. +// The config can also declare forwards: URL prefixes reverse-proxied +// to other http services (websockets and SSE pass through); see serve.go. package main import (