# 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.

