GizStack
View .mdOpen in ChatGPTOpen in Claude

gizlang:gateway

the front door of a host. One program takes the HTTP and HTTPS ports (HTTP/1.1, HTTP/2 and HTTP/3), picks a route for each request by its host and path, and relays the request to the route's upstreams (Http.Balancer: turns, failover, streamed bodies, WebSocket and other upgrades). A JSON file describes the routes and the certificates; the program reads it again when the file changes, and reloads a certificate when its file changes, with no restart and no dropped connection. It counts every request in gizlang:metrics (GET /metrics on its own port) and writes one JSON line for each to an access log.

{ "http": 80, "https": 443, "host": "", "metrics": "127.0.0.1:18082", "accessLog": "/var/log/gateway/access.log", "challenges": "/var/lib/gateway/challenges", "headers": { "strict-transport-security": "max-age=31536000; includeSubDomains" }, "certs": [ { "names": ["example.com", ".example.com"], "chain": "/etc/gateway/a.crt", "key": "/etc/gateway/a.key" }, { "names": ["api.example.com"], "acme": { "folder": "/var/lib/gateway/api", "contact": "mailto:ops@example.com" } } ], "routes": [ { "name": "site", "hosts": ["example.com", ".example.com"], "prefix": "/", "stripPrefix": false, "upstreams": ["http://10.0.0.2:8800"], "standby": ["http://10.0.0.3:8800"], "holdMs": 2000, "keepHost": true, "headers": {}, "upstreamHeaders": {}, "priority": 0, "responseMs": 120000, "pathRegex": "^/v[0-9]+/", "hostRegex": "^app[0-9]+\.example\.com$", "basicAuth": { "realm": "private", "users": ["alice:$apr1$salt$hash", "bob:{SHA}base64sha1"] }, "retry": { "attempts": 2, "statuses": [502] }, "errors": { "statuses": [502, 503, 504], "upstream": "http://127.0.0.1:18089" } }, { "name": "old", "hosts": ["old.example.com"], "redirect": { "to": "https://example.com/", "status": 301 } } ] }

A host is exact or *.suffix; no hosts means any. A prefix matches a whole path segment ("/api" matches "/api" and "/api/x"). Of the routes that match, an exact host beats a wildcard, a wildcard beats none, and then the longer prefix wins. The first certificate answers a client that names no host or one no certificate has; the others answer for their names (the server name of the handshake). pathRegex and hostRegex must also match the request path (without its query) and the host (lower case, without a port). basicAuth answers 401 unless the request has a listed user and password; a user line is an htpasswd line with an $apr1$, $1$, $2y$ (bcrypt) or {SHA} hash (a bcrypt hash $2y$ is checked once per password and remembered). A higher priority beats every other rule. standby upstreams get a request only when no upstream can take it (a refused connection passes on any request, since nothing was sent). responseMs is the longest wait for an upstream's response head. upstreamHeaders are set on each request the route sends on, in place of any copy the client sent (a key the upstream checks). retry sends an idempotent request again, up to attempts more times, when an upstream answers with one of the statuses. errors answers with the page that upstream serves at "/", under the upstream's own status, when that status is listed or no upstream can be reached (502). A route with redirect has no upstreams: it answers with the status (302 unless given) and the Location to. A top-level limit { "requests": 600, "windowMs": 60000, "proxies": ["10.0.0.1"] } answers 429 with Retry-After to a client over it, before any route. A request from one of the proxies keeps the scheme of its X-Forwarded-Proto (a TLS proxy in front of a plain port).

gizlang
import * as Gateway from "gizlang:gateway";

Types

RouteConfig

gizlang
type RouteConfig = { name: string; hosts: Vec<string>; prefix: string; strip: bool; upstreams: Vec<Http.Origin>; standby: Vec<Http.Origin>; holdMs: i64; keepHost: bool; headers: Vec<Http.Header>; upstreamHeaders: Vec<Http.Header>; priority: i64; policy: Http.Policy; redirect: string; redirectStatus: i64; pathRegex: Option<Regex>; hostRegex: Option<Regex>; realm: string; users: Vec<Login>; }

One route as the file describes it.

  • name: string
  • hosts: Vec<string>
  • prefix: string
  • strip: bool
  • upstreams: Vec<gizlang_http.Origin>
  • standby: Vec<gizlang_http.Origin>
  • holdMs: i64
  • keepHost: bool
  • headers: Vec<gizlang_http.Header>
  • upstreamHeaders: Vec<gizlang_http.Header>
  • priority: i64
  • policy: gizlang_http.Policy
  • redirect: string
  • redirectStatus: i64
  • pathRegex: _gizlang_sdk_core.Option<gizlang_regex.Regex>
  • hostRegex: _gizlang_sdk_core.Option<gizlang_regex.Regex>
  • realm: string
  • users: Vec<gizlang_gateway.Login>

Login

gizlang
type Login = { name: string; hash: string; }

A user of a route and the htpasswd hash of the password.

  • name: string
  • hash: string

CertConfig

gizlang
type CertConfig = { names: Vec<string>; chain: string; key: string; folder: string; directory: string; contact: string; }

One certificate: the names it answers for and the PEM files of its chain and key. With folder it is issued and renewed by ACME (HTTP-01 on the plain port) from directory, contact being "mailto:..." or "", and the files are chain.pem and domain.key in the folder.

  • names: Vec<string>
  • chain: string
  • key: string
  • folder: string
  • directory: string
  • contact: string

Limit

gizlang
type Limit = { requests: i64; windowMs: i64; proxies: Vec<string>; }

At most requests requests from one client (Http.clientOf: the peer, or the X-Forwarded-For hop that one of proxies appended) in windowMs; 0 requests: no limit.

  • requests: i64
  • windowMs: i64
  • proxies: Vec<string>

Config

gizlang
type Config = { http: i64; https: i64; host: string; metrics: string; accessLog: string; challenges: string; headers: Vec<Http.Header>; certs: Vec<CertConfig>; routes: Vec<RouteConfig>; threads: i64; limit: Limit; }

What the file describes. http and https are ports (0: not served), metrics an address "host:port" ("": none).

  • http: i64
  • https: i64
  • host: string
  • metrics: string
  • accessLog: string
  • challenges: string
  • headers: Vec<gizlang_http.Header>
  • certs: Vec<gizlang_gateway.CertConfig>
  • routes: Vec<gizlang_gateway.RouteConfig>
  • threads: i64
  • limit: gizlang_gateway.Limit

Route

gizlang
type Route = { name: string; hosts: Vec<string>; prefix: string; strip: bool; balancer: Http.Balancer; extra: Vec<Http.Header>; upstreamHeaders: Vec<Http.Header>; priority: i64; redirect: string; redirectStatus: i64; pathRegex: Option<Regex>; hostRegex: Option<Regex>; realm: string; users: Vec<Login>; verified: Ref<Vec<string>>; classes: Vec<Metrics.Series>; latency: Metrics.Series; }

A route in use: its rule, its balancer and the series that count its requests.

  • name: string
  • hosts: Vec<string>
  • prefix: string
  • strip: bool
  • balancer: gizlang_http.Balancer
  • extra: Vec<gizlang_http.Header>
  • upstreamHeaders: Vec<gizlang_http.Header>
  • priority: i64
  • redirect: string
  • redirectStatus: i64
  • pathRegex: _gizlang_sdk_core.Option<gizlang_regex.Regex>
  • hostRegex: _gizlang_sdk_core.Option<gizlang_regex.Regex>
  • realm: string
  • users: Vec<gizlang_gateway.Login>
  • verified: Ref<Vec<string>>
  • classes: Vec<gizlang_metrics.Series>
  • latency: gizlang_metrics.Series

Table

gizlang
type Table = { routes: Vec<Route>; unrouted: Metrics.Series; limited: Metrics.Series; limit: Limit; metrics: Vec<Metrics.Metric>; }

The routes of one configuration and the metrics that count them.

  • routes: Vec<gizlang_gateway.Route>
  • unrouted: gizlang_metrics.Series
  • limited: gizlang_metrics.Series
  • limit: gizlang_gateway.Limit
  • metrics: Vec<gizlang_metrics.Metric>

Gateway

gizlang
type Gateway

The running gateway: its table (replaced on a reload), its access log and its client limiter, which a reload keeps while the limit stays the same. Each serving thread has its own (a client that spreads its connections over N threads can get up to N times the limit).

Opaque: create and use it through this module's functions.

Functions

parse

gizlang
function parse(source: string): Config throws string

The configuration in TEXT, or why it cannot be used.

Effects: fail.

table

gizlang
function table(pool: Http.Pool, config: Config): Table throws string

The table of CONFIG; its upstreams share POOL. Series are global by name, so the counts go on across a reload.

Effects: fail, write, io.

gateway

gizlang
function gateway(sys: System, table: Table): Gateway

A gateway that routes by TABLE, with no access log.

Effects: fail, write.

run

gizlang
function run(sys: System, path: string): i64

Run the gateway of the file PATH until the process ends: the ports of the file, its certificates, its routes, a reload when it changes. The status for the process.

Effects: fail, read, write, console, io.

Methods

Table.pick

gizlang
function Table.pick(self: Table, host: string, path: string): Option<Route>

The route for a request to HOST (lower case, without a port) and PATH.

Effects: fail.

Gateway.use

gizlang
function Gateway.use(self: Gateway, table: Table): void

The routes of a new configuration take over; requests in flight finish on the old ones.

Effects: fail, read, write.

Gateway.metrics

gizlang
function Gateway.metrics(self: Gateway): Vec<Metrics.Metric>

The metrics of the gateway's current table.

Effects: fail, read.

Gateway.logTo

gizlang
function Gateway.logTo(self: Gateway, dir: Dir, path: string): bool throws IoError

Write the access log to the file PATH, appending ("" closes it).

Effects: fail, read, write, io.

Gateway.answer

gizlang
function Gateway.answer(self: Gateway, scheme: string): (request: Http.Request) => Http.ReplyPlan

The reply for requests that arrive by SCHEME ("http" or "https"): each is routed, relayed, counted and logged.

Gateway.uploads

gizlang
function Gateway.uploads(self: Gateway, scheme: string): Http.RequestHead

Chooses the requests whose body streams to the upstream: a chunked body or one of STREAM_FROM bytes or more, on a route that proxies (an upgrade, a redirect, a refusal or no route is answered as before, and the unread body ends the connection).

Constants

WATCH_MS

gizlang
const WATCH_MS = 2000

How often the program looks at its files, in milliseconds.

LETS_ENCRYPT

gizlang
const LETS_ENCRYPT = "https://acme-v02.api.letsencrypt.org/directory"

The ACME directory of a certificate that names none.

REOPEN_MS

gizlang
const REOPEN_MS = 30000

How often the access log file is opened again (so that a rotation takes effect), in milliseconds.