@rip-lang/server 1.3.126 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{docs/READ_VALIDATORS.md → API.md} +41 -119
- package/CONFIG.md +408 -0
- package/README.md +246 -1109
- package/acme/crypto.rip +0 -2
- package/browse.rip +62 -0
- package/control/cli.rip +89 -34
- package/control/lifecycle.rip +67 -1
- package/control/manager.rip +250 -0
- package/control/mdns.rip +3 -0
- package/middleware.rip +1 -1
- package/package.json +14 -11
- package/server.rip +189 -673
- package/serving/config.rip +766 -0
- package/{edge → serving}/forwarding.rip +2 -2
- package/serving/logging.rip +101 -0
- package/{edge → serving}/metrics.rip +29 -1
- package/serving/proxy.rip +99 -0
- package/{edge → serving}/queue.rip +1 -1
- package/{edge → serving}/ratelimit.rip +1 -1
- package/{edge → serving}/realtime.rip +71 -2
- package/{edge → serving}/registry.rip +1 -1
- package/{edge → serving}/router.rip +3 -3
- package/{edge → serving}/runtime.rip +18 -16
- package/{edge → serving}/security.rip +1 -1
- package/serving/static.rip +393 -0
- package/{edge → serving}/tls.rip +3 -7
- package/{edge → serving}/upstream.rip +4 -4
- package/{edge → serving}/verify.rip +16 -16
- package/streams/{tls_clienthello.rip → clienthello.rip} +1 -1
- package/streams/config.rip +8 -8
- package/streams/index.rip +5 -5
- package/streams/router.rip +2 -2
- package/tests/acme.rip +1 -1
- package/tests/config.rip +215 -0
- package/tests/control.rip +1 -1
- package/tests/{runtime_entrypoints.rip → entrypoints.rip} +11 -7
- package/tests/extracted.rip +118 -0
- package/tests/helpers.rip +4 -4
- package/tests/metrics.rip +3 -3
- package/tests/proxy.rip +9 -8
- package/tests/read.rip +1 -1
- package/tests/realtime.rip +3 -3
- package/tests/registry.rip +4 -4
- package/tests/router.rip +27 -27
- package/tests/runner.rip +70 -0
- package/tests/security.rip +4 -4
- package/tests/servers.rip +102 -136
- package/tests/static.rip +2 -2
- package/tests/streams_clienthello.rip +2 -2
- package/tests/streams_index.rip +4 -4
- package/tests/streams_pipe.rip +1 -1
- package/tests/streams_router.rip +10 -10
- package/tests/streams_runtime.rip +4 -4
- package/tests/streams_upstream.rip +1 -1
- package/tests/upstream.rip +2 -2
- package/tests/verify.rip +18 -18
- package/tests/watchers.rip +4 -4
- package/default.rip +0 -435
- package/docs/edge/CONFIG_LIFECYCLE.md +0 -111
- package/docs/edge/CONTRACTS.md +0 -137
- package/docs/edge/EDGEFILE_CONTRACT.md +0 -282
- package/docs/edge/M0B_REVIEW_NOTES.md +0 -102
- package/docs/edge/SCHEDULER.md +0 -46
- package/docs/logo.png +0 -0
- package/docs/logo.svg +0 -13
- package/docs/social.png +0 -0
- package/edge/config.rip +0 -607
- package/edge/static.rip +0 -69
- package/tests/edgefile.rip +0 -165
package/README.md
CHANGED
|
@@ -2,147 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
# Rip Server - @rip-lang/server
|
|
4
4
|
|
|
5
|
-
>
|
|
5
|
+
> Rip Server serves content: static sites, Rip apps, proxied HTTP services, and TCP/TLS services -- all from one Bun-native runtime.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
with
|
|
7
|
+
Here, `content` means anything you want to make reachable over the network. That
|
|
8
|
+
may be a static site, a small Rip app, an HTTP service behind a proxy, a raw
|
|
9
|
+
TCP/TLS service, or a containerized tool you want to publish.
|
|
10
|
+
|
|
11
|
+
Rip Server replaces the usual app framework + process manager + reverse proxy
|
|
12
|
+
stack with one runtime. Start with `rip server` for a single app. Add
|
|
13
|
+
`serve.rip` when you want multi-host routing, shared apps, named proxy
|
|
14
|
+
backends, reusable certs, or TCP/TLS passthrough. The job stays the same:
|
|
15
|
+
take something you have and make it reachable safely, cleanly, and coherently.
|
|
13
16
|
|
|
14
17
|
Written entirely in Rip. Runs on Bun.
|
|
15
18
|
|
|
16
|
-
##
|
|
17
|
-
|
|
18
|
-
### App Framework
|
|
19
|
-
- **Sinatra-style routing** — `get`, `post`, `put`, `del` with path parameters and wildcards
|
|
20
|
-
- **Request validation** — 40+ built-in validators via [`read()`](docs/READ_VALIDATORS.md) eliminate API boilerplate
|
|
21
|
-
- **Middleware** — cors, sessions, compression, security headers, rate limiting, body limits
|
|
22
|
-
- **File serving** — Auto-detected MIME types via `@send`, SPA fallback support
|
|
23
|
-
- **Realtime WebSocket** — Pub/sub hub where your backend stays HTTP-only
|
|
24
|
-
|
|
25
|
-
### Server Runtime
|
|
26
|
-
- **Multi-worker architecture** — Automatic worker spawning based on CPU cores
|
|
27
|
-
- **Hot module reloading** — Watches `*.rip` files by default, rolling restarts on change
|
|
28
|
-
- **Rolling restarts** — Zero-downtime deployments with per-app worker pools
|
|
29
|
-
- **Automatic HTTPS** — Shipped `*.ripdev.io` wildcard cert, or auto-TLS via Let's Encrypt ACME
|
|
30
|
-
- **Observability** — `/diagnostics` with request rates, latency percentiles, queue pressure
|
|
31
|
-
- **mDNS discovery** — `.local` hostname advertisement
|
|
32
|
-
|
|
33
|
-
### Edge Proxy
|
|
34
|
-
- **HTTP/WS reverse proxy** — Route to external upstreams with health checks, circuit breakers, and retry
|
|
35
|
-
- **Layer 4 TLS passthrough** — SNI-based raw TCP routing for services that keep their own TLS
|
|
36
|
-
- **Unified port multiplexer** — Stream passthrough and normal HTTPS on the same `:443`
|
|
37
|
-
- **Atomic config reload** — Staged activation with post-verify and automatic rollback
|
|
38
|
-
- **Declarative routing** — Host/path/method matching with wildcard hosts and per-site overrides
|
|
39
|
-
|
|
40
|
-
## What Can You Build?
|
|
41
|
-
|
|
42
|
-
- **A single API or web app** — `rip server` with zero config, one command
|
|
43
|
-
- **A multi-app platform** — Per-app worker pools, host routing, and rolling restarts via `config.rip`
|
|
44
|
-
- **A reverse proxy** — Route traffic to external HTTP/WS upstreams with health checks
|
|
45
|
-
- **A TLS passthrough edge** — Expose services like the Incus Web UI without terminating their TLS
|
|
46
|
-
- **A full edge runtime** — Replace Nginx, Caddy, or Traefik for single-host deployments via `Edgefile.rip`
|
|
47
|
-
|
|
48
|
-
| Directory | Role |
|
|
49
|
-
|-----------|------|
|
|
50
|
-
| `api.rip` | Core framework: routing, validation, `read()`, `session`, `@send` |
|
|
51
|
-
| `middleware.rip` | Built-in middleware: cors, logger, sessions, compression, security, serve |
|
|
52
|
-
| `server.rip` | Edge orchestrator: CLI, workers, load balancing, TLS, mDNS |
|
|
53
|
-
| `edge/` | Request path and edge runtime: config, forwarding, metrics, registry, router, runtime, TLS, upstreams, verification |
|
|
54
|
-
| `control/` | Management: CLI, lifecycle, workers, watchers, mDNS, events |
|
|
55
|
-
| `streams/` | Layer 4 stream routing: ClientHello parsing, SNI routing, stream runtimes, raw TCP upstreams |
|
|
56
|
-
| `acme/` | Auto-TLS: ACME client, crypto, cert store, challenge handler |
|
|
57
|
-
|
|
58
|
-
> **See Also**: For the DuckDB server, see [@rip-lang/db](../db/README.md).
|
|
59
|
-
|
|
60
|
-
## Runtime Tiers
|
|
61
|
-
|
|
62
|
-
`rip server` is a graduated runtime. Start simple and add capabilities as you need them:
|
|
63
|
-
|
|
64
|
-
1. **Single-app mode** — one app, zero config, one command
|
|
65
|
-
2. **Managed multi-app mode** — many Rip apps with per-app worker pools via `config.rip`
|
|
66
|
-
3. **Edge mode** — upstream proxying, host/path routing, TLS passthrough, staged reload, verification, and rollback via `Edgefile.rip`
|
|
67
|
-
|
|
68
|
-
```mermaid
|
|
69
|
-
flowchart TD
|
|
70
|
-
SingleApp["rip server"] --> ManagedApps["config.rip"]
|
|
71
|
-
ManagedApps --> EdgeMode["Edgefile.rip"]
|
|
72
|
-
EdgeMode --> Upstreams["HTTP and WS upstream routes"]
|
|
73
|
-
EdgeMode --> Streams["Layer 4 TLS passthrough"]
|
|
74
|
-
EdgeMode --> ManagedRoutes["Managed app routes"]
|
|
75
|
-
```
|
|
19
|
+
## Serving Modes
|
|
76
20
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Client[Client] --> Port[":443"]
|
|
82
|
-
Port -->|"HTTP/WS"| TLS["TLS termination"]
|
|
83
|
-
TLS --> Router["Route matching"]
|
|
84
|
-
Router -->|upstream| Proxy["Reverse proxy"]
|
|
85
|
-
Router -->|app| Workers["Worker pool"]
|
|
86
|
-
Port -->|"TLS passthrough"| SNI["SNI routing"]
|
|
87
|
-
SNI --> Upstream["Raw TCP upstream"]
|
|
88
|
-
```
|
|
21
|
+
- Static file and website serving
|
|
22
|
+
- Small app serving with Sinatra-style routing and `read()` validators
|
|
23
|
+
- HTTP / WebSocket reverse proxy
|
|
24
|
+
- Layer 4 TCP / TLS passthrough
|
|
89
25
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
| HTTP reverse proxy | Yes | Yes | Yes | Yes |
|
|
100
|
-
| WebSocket proxy | Yes | Yes | Yes | Yes |
|
|
101
|
-
| Auto-TLS (ACME) | Yes | Yes | Plugin | Yes |
|
|
102
|
-
| Layer 4 TLS passthrough | Yes | Yes | Stream module | Yes |
|
|
103
|
-
| Managed app workers | Yes | No | No | No |
|
|
104
|
-
| Built-in app framework | Yes | No | No | No |
|
|
105
|
-
| Hot reload | Yes | Yes | Reload | Yes |
|
|
106
|
-
| Atomic rollback | Yes | No | No | No |
|
|
107
|
-
| Per-SNI multi-cert TLS | Yes | Yes | Yes | Yes |
|
|
108
|
-
| Config validation with hints | Yes | Partial | No | Partial |
|
|
109
|
-
| Zero dependencies | Yes | Go binary | C binary | Go binary |
|
|
110
|
-
|
|
111
|
-
Use Rip Server when you want one Bun-native runtime for both the app and the
|
|
112
|
-
edge. Use Caddy, Nginx, or Traefik when you specifically need mature HTTP
|
|
113
|
-
caching, multi-node service discovery, or their broader ecosystem integrations.
|
|
26
|
+
## Serving Guarantees
|
|
27
|
+
|
|
28
|
+
- Managed worker pools with rolling restarts
|
|
29
|
+
- HTTPS, ACME, certificate reuse, and SNI routing
|
|
30
|
+
- Proxy health checks, retry behavior, and upstream timeouts
|
|
31
|
+
- Shared-port HTTPS multiplexer
|
|
32
|
+
- Atomic config reload with verification and rollback
|
|
33
|
+
- Drain semantics, diagnostics, and control APIs
|
|
34
|
+
- Composable `serve.rip` config with reusable groups and rules
|
|
114
35
|
|
|
115
36
|
## Quick Start
|
|
116
37
|
|
|
117
|
-
###
|
|
38
|
+
### Install
|
|
118
39
|
|
|
119
40
|
```bash
|
|
120
|
-
# Local (per-project)
|
|
121
41
|
bun add @rip-lang/server
|
|
122
|
-
|
|
123
|
-
# Global
|
|
124
|
-
bun add -g rip-lang @rip-lang/server
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Running Your App
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
# From your app directory (uses ./index.rip, watches *.rip)
|
|
131
|
-
rip server
|
|
132
|
-
|
|
133
|
-
# Name your app (for mDNS: myapp.local)
|
|
134
|
-
rip server myapp
|
|
135
|
-
|
|
136
|
-
# Explicit entry file
|
|
137
|
-
rip server ./app.rip
|
|
138
|
-
|
|
139
|
-
# HTTP only mode
|
|
140
|
-
rip server http
|
|
141
42
|
```
|
|
142
43
|
|
|
143
|
-
###
|
|
144
|
-
|
|
145
|
-
Create `index.rip`:
|
|
44
|
+
### Single app
|
|
146
45
|
|
|
147
46
|
```coffee
|
|
148
47
|
import { get, read, start } from '@rip-lang/server'
|
|
@@ -150,9 +49,6 @@ import { get, read, start } from '@rip-lang/server'
|
|
|
150
49
|
get '/' ->
|
|
151
50
|
'Hello from Rip Server!'
|
|
152
51
|
|
|
153
|
-
get '/json' ->
|
|
154
|
-
{ message: 'It works!', timestamp: Date.now() }
|
|
155
|
-
|
|
156
52
|
get '/users/:id' ->
|
|
157
53
|
id = read 'id', 'id!'
|
|
158
54
|
{ user: { id, name: "User #{id}" } }
|
|
@@ -166,1097 +62,338 @@ Run it:
|
|
|
166
62
|
rip server
|
|
167
63
|
```
|
|
168
64
|
|
|
169
|
-
|
|
65
|
+
### Add `serve.rip`
|
|
170
66
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
# Hello from Rip Server!
|
|
67
|
+
Add `serve.rip` next to your entry file when you want host routing, shared
|
|
68
|
+
apps, proxy backends, reusable TLS, or TCP passthrough.
|
|
174
69
|
|
|
175
|
-
|
|
176
|
-
# {"message":"It works!","timestamp":1234567890}
|
|
70
|
+
## Canonical `serve.rip`
|
|
177
71
|
|
|
178
|
-
|
|
179
|
-
# {"user":{"id":42,"name":"User 42"}}
|
|
72
|
+
`serve.rip` is the one config file. There are no alternate config formats.
|
|
180
73
|
|
|
181
|
-
|
|
182
|
-
# {"status":"healthy","app":"myapp","workers":5,"ports":{"https":443}}
|
|
183
|
-
```
|
|
74
|
+
Canonical top-level keys:
|
|
184
75
|
|
|
185
|
-
|
|
76
|
+
- `version`
|
|
77
|
+
- `server` (`edge` is a deprecated alias)
|
|
78
|
+
- `certs`
|
|
79
|
+
- `proxies`
|
|
80
|
+
- `apps`
|
|
81
|
+
- `rules`
|
|
82
|
+
- `groups`
|
|
83
|
+
- `hosts`
|
|
84
|
+
- `streams`
|
|
186
85
|
|
|
187
|
-
`
|
|
188
|
-
It gives you:
|
|
86
|
+
`hosts` is the canonical authoring surface. Reuse happens through:
|
|
189
87
|
|
|
190
|
-
-
|
|
191
|
-
-
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
- **Atomic reload** — staged activation with post-verify and automatic rollback via `SIGHUP` or control API
|
|
195
|
-
- **Strict validation** — field-path errors and remediation hints, with `--check-config` for dry-run validation
|
|
196
|
-
- **Diagnostics** — config metadata, counts, reload history, and route descriptions in `/diagnostics`
|
|
88
|
+
- `certs` for reusable TLS identities
|
|
89
|
+
- `proxies` for named HTTP or TCP backends
|
|
90
|
+
- `rules` for reusable HTTP route bundles
|
|
91
|
+
- `groups` for reusable hostname lists
|
|
197
92
|
|
|
198
|
-
|
|
93
|
+
Everything normalizes into concrete hosts, resolved TLS pairs, concrete route
|
|
94
|
+
lists, and concrete stream routes.
|
|
95
|
+
|
|
96
|
+
### Minimal shape
|
|
199
97
|
|
|
200
98
|
```coffee
|
|
201
99
|
export default
|
|
202
100
|
version: 1
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
connectMs: 2000
|
|
207
|
-
readMs: 30000
|
|
208
|
-
upstreams: {}
|
|
209
|
-
streamUpstreams: {}
|
|
101
|
+
server: {}
|
|
102
|
+
certs: {}
|
|
103
|
+
proxies: {}
|
|
210
104
|
apps: {}
|
|
211
|
-
|
|
105
|
+
rules: {}
|
|
106
|
+
groups: {}
|
|
107
|
+
hosts: {}
|
|
212
108
|
streams: []
|
|
213
|
-
sites: {}
|
|
214
109
|
```
|
|
215
110
|
|
|
216
|
-
|
|
111
|
+
## Config Reference
|
|
217
112
|
|
|
218
|
-
|
|
219
|
-
|------|---------|
|
|
220
|
-
| `version` | Schema version (optional, defaults to `1`). |
|
|
221
|
-
| `edge` | Global edge settings: TLS, trusted proxies, timeouts, verification policy. |
|
|
222
|
-
| `upstreams` | Named external HTTP services and their targets. |
|
|
223
|
-
| `streamUpstreams` | Named raw TCP upstreams using `host:port` targets and optional `connectTimeoutMs`. |
|
|
224
|
-
| `apps` | Managed Rip apps with entry paths, hosts, worker counts, and env. |
|
|
225
|
-
| `routes` | Declarative route objects that choose exactly one action. |
|
|
226
|
-
| `streams` | Layer 4 stream routes that match by listen port and SNI. |
|
|
227
|
-
| `sites` | Per-host route groups and policy overrides. |
|
|
113
|
+
### `certs`
|
|
228
114
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
Common route fields:
|
|
232
|
-
|
|
233
|
-
- `host?: string` — exact host, wildcard host like `*.example.com`, or `*`
|
|
234
|
-
- `path: string` — must start with `/`
|
|
235
|
-
- `methods?: string[] | "*"`
|
|
236
|
-
- `priority?: number`
|
|
237
|
-
- `timeouts?: { connectMs, readMs }`
|
|
238
|
-
|
|
239
|
-
Each route must define exactly one action:
|
|
240
|
-
|
|
241
|
-
- `upstream: 'name'`
|
|
242
|
-
- `app: 'name'`
|
|
243
|
-
- `static: '/dir'`
|
|
244
|
-
- `redirect: { to: '...', status: 301 }`
|
|
245
|
-
- `headers: { set, remove }`
|
|
246
|
-
|
|
247
|
-
WebSocket proxy routes use:
|
|
248
|
-
|
|
249
|
-
- `websocket: true`
|
|
250
|
-
- `upstream: 'name'`
|
|
251
|
-
|
|
252
|
-
### Stream Shape
|
|
253
|
-
|
|
254
|
-
Common stream fields:
|
|
255
|
-
|
|
256
|
-
- `listen: number` — TCP port to bind
|
|
257
|
-
- `sni: string[]` — exact or wildcard SNI patterns
|
|
258
|
-
- `upstream: 'name'`
|
|
259
|
-
- `timeouts?: { handshakeMs, idleMs, connectMs }`
|
|
260
|
-
|
|
261
|
-
When `listen` matches the active HTTPS port, Rip uses a shared-port
|
|
262
|
-
multiplexer. Matching SNI traffic is passed through to the stream upstream, and
|
|
263
|
-
everything else falls through to Rip's internal HTTPS server.
|
|
264
|
-
|
|
265
|
-
### Example: Pure Proxy Shape
|
|
115
|
+
Preferred shorthand:
|
|
266
116
|
|
|
267
117
|
```coffee
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
edge:
|
|
271
|
-
hsts: true
|
|
272
|
-
trustedProxies: ['10.0.0.0/8']
|
|
273
|
-
verify:
|
|
274
|
-
requireHealthyUpstreams: true
|
|
275
|
-
requireReadyApps: true
|
|
276
|
-
includeUnroutedManagedApps: true
|
|
277
|
-
minHealthyTargetsPerUpstream: 1
|
|
278
|
-
upstreams:
|
|
279
|
-
app:
|
|
280
|
-
targets: ['http://app.incusbr0:3000']
|
|
281
|
-
healthCheck:
|
|
282
|
-
path: '/health'
|
|
283
|
-
routes: [
|
|
284
|
-
{ path: '/ws', websocket: true, upstream: 'app' }
|
|
285
|
-
{ path: '/*', upstream: 'app' }
|
|
286
|
-
]
|
|
118
|
+
certs:
|
|
119
|
+
trusthealth: '/ssl/trusthealth.com'
|
|
287
120
|
```
|
|
288
121
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
```coffee
|
|
292
|
-
export default
|
|
293
|
-
version: 1
|
|
122
|
+
This expands to:
|
|
294
123
|
|
|
295
|
-
|
|
124
|
+
- `cert: '/ssl/trusthealth.com.crt'`
|
|
125
|
+
- `key: '/ssl/trusthealth.com.key'`
|
|
296
126
|
|
|
297
|
-
|
|
298
|
-
incus:
|
|
299
|
-
targets: ['127.0.0.1:8443']
|
|
127
|
+
Explicit object form is also allowed.
|
|
300
128
|
|
|
301
|
-
|
|
302
|
-
{ listen: 8443, sni: ['incus.example.com'], upstream: 'incus' }
|
|
303
|
-
]
|
|
304
|
-
```
|
|
129
|
+
### `proxies`
|
|
305
130
|
|
|
306
|
-
|
|
131
|
+
Named backend proxy targets. Transport is inferred from URL scheme:
|
|
307
132
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
133
|
+
- `http://...` => HTTP proxy
|
|
134
|
+
- `https://...` => HTTPS proxy
|
|
135
|
+
- `tcp://...` => raw TCP proxy
|
|
311
136
|
|
|
312
137
|
```coffee
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
This means:
|
|
327
|
-
|
|
328
|
-
- `https://incus.example.com` keeps Incus's own TLS and client-certificate flow
|
|
329
|
-
- every other HTTPS host on `:443` still terminates TLS in Rip and uses the
|
|
330
|
-
normal HTTP/WebSocket edge runtime
|
|
331
|
-
- if no stream route shares the HTTPS port, Rip stays on the normal direct
|
|
332
|
-
`Bun.serve()` path with no extra hop
|
|
333
|
-
|
|
334
|
-
### Example: Mixed Apps + Upstreams
|
|
138
|
+
proxies:
|
|
139
|
+
api:
|
|
140
|
+
hosts: ['http://127.0.0.1:4000']
|
|
141
|
+
check:
|
|
142
|
+
path: '/health'
|
|
143
|
+
intervalMs: 5000
|
|
144
|
+
timeoutMs: 2000
|
|
145
|
+
retry:
|
|
146
|
+
attempts: 2
|
|
147
|
+
retryOn: [502, 503, 504]
|
|
148
|
+
timeouts:
|
|
149
|
+
connectMs: 2000
|
|
150
|
+
readMs: 30000
|
|
335
151
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
edge: {}
|
|
340
|
-
upstreams:
|
|
341
|
-
api:
|
|
342
|
-
targets: ['http://api.incusbr0:4000']
|
|
343
|
-
apps:
|
|
344
|
-
admin:
|
|
345
|
-
entry: './admin/index.rip'
|
|
346
|
-
hosts: ['admin.example.com']
|
|
347
|
-
routes: [
|
|
348
|
-
{ path: '/api/*', upstream: 'api' }
|
|
349
|
-
{ path: '/admin/*', app: 'admin' }
|
|
350
|
-
]
|
|
351
|
-
sites:
|
|
352
|
-
'admin.example.com':
|
|
353
|
-
routes: [
|
|
354
|
-
{ path: '/*', app: 'admin' }
|
|
355
|
-
]
|
|
152
|
+
incus:
|
|
153
|
+
hosts: ['tcp://127.0.0.1:8443']
|
|
154
|
+
connectTimeoutMs: 5000
|
|
356
155
|
```
|
|
357
156
|
|
|
358
|
-
|
|
157
|
+
Mixed scheme families in one proxy are invalid.
|
|
359
158
|
|
|
360
|
-
|
|
361
|
-
`*.domain` certificates.
|
|
159
|
+
### `apps`
|
|
362
160
|
|
|
363
|
-
|
|
364
|
-
export default
|
|
365
|
-
version: 1
|
|
366
|
-
edge:
|
|
367
|
-
cert: './certs/wildcard.example.com.crt'
|
|
368
|
-
key: './certs/wildcard.example.com.key'
|
|
369
|
-
hsts: true
|
|
370
|
-
upstreams:
|
|
371
|
-
web:
|
|
372
|
-
targets: ['http://web.incusbr0:3000']
|
|
373
|
-
routes: [
|
|
374
|
-
{ path: '/*', upstream: 'web', host: '*.example.com' }
|
|
375
|
-
]
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
## Host Blocks
|
|
161
|
+
Named managed Rip apps with worker and queue settings.
|
|
379
162
|
|
|
380
|
-
|
|
381
|
-
under each hostname. This is the cleanest way to configure multi-domain hosting.
|
|
163
|
+
### `rules`
|
|
382
164
|
|
|
383
|
-
|
|
384
|
-
they default to `1` and `{}` respectively.
|
|
385
|
-
|
|
386
|
-
### Host Blocks Shape
|
|
165
|
+
Reusable HTTP rule bundles:
|
|
387
166
|
|
|
388
167
|
```coffee
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
'
|
|
392
|
-
|
|
393
|
-
key: '/ssl/trusthealth.com.key'
|
|
394
|
-
root: '/mnt/trusthealth/website'
|
|
395
|
-
routes: [
|
|
396
|
-
{ path: '/*', static: '.', spa: true }
|
|
397
|
-
]
|
|
398
|
-
|
|
399
|
-
'*.zionlabshare.com':
|
|
400
|
-
cert: '/ssl/zionlabshare.com.crt'
|
|
401
|
-
key: '/ssl/zionlabshare.com.key'
|
|
402
|
-
routes: [
|
|
403
|
-
{ path: '/api/*', upstream: 'api' }
|
|
404
|
-
{ path: '/*', static: '/mnt/zion/dist', spa: true }
|
|
405
|
-
]
|
|
406
|
-
|
|
407
|
-
upstreams:
|
|
408
|
-
api: { targets: ['http://127.0.0.1:3807'] }
|
|
409
|
-
|
|
410
|
-
streamUpstreams:
|
|
411
|
-
incus: { targets: ['127.0.0.1:8443'] }
|
|
412
|
-
|
|
413
|
-
streams: [
|
|
414
|
-
{ listen: 443, sni: ['incus.trusthealth.com'], upstream: 'incus' }
|
|
168
|
+
rules:
|
|
169
|
+
web: [
|
|
170
|
+
{ path: '/api/*', proxy: 'api' }
|
|
171
|
+
{ path: '/*', app: 'web' }
|
|
415
172
|
]
|
|
416
173
|
```
|
|
417
174
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
- Each server block is keyed by hostname (exact or wildcard)
|
|
421
|
-
- Per-server `cert`/`key` enable SNI-based certificate selection (multiple domains on one port)
|
|
422
|
-
- `edge.cert` and `edge.key` are the optional fallback TLS identity
|
|
423
|
-
- A server with just `root` and no `routes` serves static files automatically
|
|
424
|
-
- A server with `passthrough` routes raw TLS to another address (no termination)
|
|
425
|
-
- `streams`, `streamUpstreams`, `upstreams`, and `apps` stay top-level and are shared
|
|
426
|
-
- Routes inside a server block inherit the server hostname automatically
|
|
427
|
-
- Hosts not matching any server block or stream route fall through to the default app
|
|
428
|
-
|
|
429
|
-
### Host Block Fields
|
|
175
|
+
You do not have to use `rules`. Hosts may also define inline rules.
|
|
430
176
|
|
|
431
|
-
|
|
432
|
-
|-------|---------|
|
|
433
|
-
| `passthrough` | Raw TLS passthrough to `host:port` (no cert, no routes needed) |
|
|
434
|
-
| `cert` | TLS certificate path for this hostname (must pair with `key`) |
|
|
435
|
-
| `key` | TLS private key path for this hostname (must pair with `cert`) |
|
|
436
|
-
| `root` | Default filesystem base; if present with no `routes`, serves static files |
|
|
437
|
-
| `spa` | Server-level SPA fallback (boolean, used with `root`-only blocks) |
|
|
438
|
-
| `routes` | Array of route objects (optional if `root` or `passthrough` is set) |
|
|
439
|
-
| `timeouts` | Per-server timeout defaults |
|
|
177
|
+
### `groups`
|
|
440
178
|
|
|
441
|
-
|
|
179
|
+
Reusable hostname lists.
|
|
442
180
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
```coffee
|
|
446
|
-
{ path: '/*', static: '.', spa: true }
|
|
447
|
-
{ path: '/assets/*', static: '/mnt/assets' }
|
|
448
|
-
```
|
|
181
|
+
### `hosts`
|
|
449
182
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
- `spa: true` enables SPA fallback: when a file is not found and the request
|
|
453
|
-
accepts `text/html`, serves `index.html` instead
|
|
454
|
-
- Directory requests serve `index.html` if present
|
|
455
|
-
- Path traversal is rejected
|
|
183
|
+
The canonical config surface. Each binding resolves to one or more concrete
|
|
184
|
+
hosts and owns the HTTP or passthrough behavior for those hosts.
|
|
456
185
|
|
|
457
|
-
|
|
186
|
+
Examples:
|
|
458
187
|
|
|
459
188
|
```coffee
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
### Reload Config Safely
|
|
479
|
-
|
|
480
|
-
Send `SIGHUP` to the long-lived server process:
|
|
481
|
-
|
|
482
|
-
```bash
|
|
483
|
-
kill -HUP "$(cat /tmp/rip_myapp.pid)"
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
The default PID file is `/tmp/rip_<app-name>.pid`. If you use a custom socket
|
|
487
|
-
prefix, the PID file follows `/tmp/<socket-prefix>.pid`.
|
|
488
|
-
Reload success or rejection is printed to stderr and reflected in `/diagnostics`.
|
|
489
|
-
|
|
490
|
-
You can also trigger reload through the control socket:
|
|
491
|
-
|
|
492
|
-
```bash
|
|
493
|
-
curl --unix-socket /tmp/rip_myapp.ctl.sock -X POST http://localhost/reload
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
When file watching is enabled, application code changes use the same staged
|
|
497
|
-
reload path automatically. You can always reload Edgefile changes explicitly via
|
|
498
|
-
`SIGHUP` or the control socket API.
|
|
499
|
-
|
|
500
|
-
### Inspect Active Config And Diagnostics
|
|
501
|
-
|
|
502
|
-
```bash
|
|
503
|
-
curl http://localhost/diagnostics
|
|
189
|
+
hosts:
|
|
190
|
+
'example.com':
|
|
191
|
+
cert: 'main'
|
|
192
|
+
rules: [
|
|
193
|
+
{ path: '/api/*', proxy: 'api' }
|
|
194
|
+
{ path: '/*', app: 'web' }
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
publicWeb:
|
|
198
|
+
hosts: 'publicWeb'
|
|
199
|
+
cert: 'main'
|
|
200
|
+
rules: 'web'
|
|
201
|
+
|
|
202
|
+
hosts: *{
|
|
203
|
+
['example.com', 'foo.bar.com']:
|
|
204
|
+
cert: 'main'
|
|
205
|
+
rules: 'web'
|
|
206
|
+
}
|
|
504
207
|
```
|
|
505
208
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
- config kind (`edge`, `legacy`, or `none`)
|
|
509
|
-
- active path
|
|
510
|
-
- schema version
|
|
511
|
-
- counts for apps, upstreams, routes, and sites
|
|
512
|
-
- active compiled route descriptions
|
|
513
|
-
- last result (`loaded`, `rejected`, etc.)
|
|
514
|
-
- reload timestamp
|
|
515
|
-
- last error, if any
|
|
516
|
-
- active edge runtime inflight/WS counts
|
|
517
|
-
- retired edge runtimes still draining after a config swap
|
|
518
|
-
- last reload attempt record
|
|
519
|
-
- bounded reload history with source, versions, result, and reason
|
|
520
|
-
- structured rollback/rejection code and details for failed reloads
|
|
521
|
-
|
|
522
|
-
The top-level diagnostics payload also includes:
|
|
523
|
-
|
|
524
|
-
- `upstreams`: per-upstream target counts and healthy/unhealthy target totals
|
|
209
|
+
Host block fields:
|
|
525
210
|
|
|
526
|
-
|
|
211
|
+
- `hosts`
|
|
212
|
+
- `cert`
|
|
213
|
+
- `key`
|
|
214
|
+
- `rules`
|
|
215
|
+
- `proxy`
|
|
216
|
+
- `app`
|
|
217
|
+
- `root`
|
|
218
|
+
- `spa`
|
|
219
|
+
- `browse`
|
|
220
|
+
- `timeouts`
|
|
527
221
|
|
|
528
|
-
|
|
222
|
+
Rules:
|
|
529
223
|
|
|
530
|
-
-
|
|
531
|
-
-
|
|
532
|
-
-
|
|
533
|
-
- `
|
|
224
|
+
- host rules use `proxy`, not `upstream`
|
|
225
|
+
- rule arrays can be inline, reusable, or mixed
|
|
226
|
+
- rules inside a host block must not specify `host`
|
|
227
|
+
- `proxy` shorthand can target HTTP or TCP proxies
|
|
228
|
+
- if host-level `proxy` points to a TCP proxy, Rip creates the default `:443` SNI stream route for that host
|
|
534
229
|
|
|
535
|
-
|
|
230
|
+
### `streams`
|
|
536
231
|
|
|
537
|
-
|
|
538
|
-
90% of API boilerplate. It supports 40+ built-in validators including `email`,
|
|
539
|
-
`phone`, `money`, `uuid`, `json`, `regex`, and composable object schemas.
|
|
232
|
+
Explicit Layer 4 routes:
|
|
540
233
|
|
|
541
234
|
```coffee
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
age = read 'age', 'int', [18, 120] # range-checked
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
See the full [Validation Reference](docs/READ_VALIDATORS.md) for all 37+
|
|
549
|
-
validators, patterns, custom validators, and real-world examples.
|
|
550
|
-
|
|
551
|
-
## App Path & Naming
|
|
552
|
-
|
|
553
|
-
### Entry File Resolution
|
|
554
|
-
|
|
555
|
-
When you run `rip server`, it looks for your app's entry file:
|
|
556
|
-
|
|
557
|
-
```bash
|
|
558
|
-
# No arguments: looks for index.rip (or index.ts) in current directory
|
|
559
|
-
rip server
|
|
560
|
-
|
|
561
|
-
# Directory path: looks for index.rip (or index.ts) in that directory
|
|
562
|
-
rip server ./myapp/
|
|
563
|
-
|
|
564
|
-
# Explicit file: uses that file directly
|
|
565
|
-
rip server ./app.rip
|
|
566
|
-
rip server ./src/server.ts
|
|
235
|
+
streams: [
|
|
236
|
+
{ listen: 443, sni: ['db.example.com'], proxy: 'db' }
|
|
237
|
+
]
|
|
567
238
|
```
|
|
568
239
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
```bash
|
|
574
|
-
# Default: current directory name becomes app name
|
|
575
|
-
~/projects/api$ rip server # app name = "api"
|
|
576
|
-
|
|
577
|
-
# Explicit name: pass a name that's not a file path
|
|
578
|
-
rip server myapp # app name = "myapp"
|
|
579
|
-
|
|
580
|
-
# With aliases: name@alias1,alias2
|
|
581
|
-
rip server myapp@api,backend # accessible at myapp.local, api.local, backend.local
|
|
582
|
-
|
|
583
|
-
# Path with alias
|
|
584
|
-
rip server ./app.rip@myapp # explicit file + custom app name
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
**Examples:**
|
|
588
|
-
|
|
589
|
-
```bash
|
|
590
|
-
# In ~/projects/api/ with index.rip
|
|
591
|
-
rip server # app = "api", entry = ./index.rip
|
|
592
|
-
rip server myapp # app = "myapp", entry = ./index.rip
|
|
593
|
-
rip server ./server.rip # app = "api", entry = ./server.rip
|
|
594
|
-
rip server ./server.rip@myapp # app = "myapp", entry = ./server.rip
|
|
595
|
-
```
|
|
596
|
-
|
|
597
|
-
## File Watching
|
|
598
|
-
|
|
599
|
-
Directory watching is **on by default** — any `.rip` file change in your app directory triggers an automatic rolling restart. Use `--watch=<glob>` to customize the pattern, or `--static` to disable watching entirely.
|
|
600
|
-
|
|
601
|
-
```bash
|
|
602
|
-
rip server # Watches *.rip (default)
|
|
603
|
-
rip server --watch=*.ts # Watch TypeScript files instead
|
|
604
|
-
rip server --static # No watching, no hot reload (production)
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
**How it works:**
|
|
608
|
-
|
|
609
|
-
1. Uses OS-native file watching (FSEvents on macOS, inotify on Linux)
|
|
610
|
-
2. Watches the entire app directory recursively
|
|
611
|
-
3. When a matching file changes, touches the entry file
|
|
612
|
-
4. The hot-reload mechanism detects the mtime change and does a rolling restart
|
|
613
|
-
|
|
614
|
-
This is a single kernel-level file descriptor in the main process — no polling, zero overhead when files aren't changing.
|
|
615
|
-
|
|
616
|
-
## CLI Reference
|
|
617
|
-
|
|
618
|
-
### Basic Syntax
|
|
619
|
-
|
|
620
|
-
```bash
|
|
621
|
-
rip server [flags] [app-path] [app-name]
|
|
622
|
-
rip server [flags] [app-path]@<alias1>,<alias2>,...
|
|
623
|
-
```
|
|
624
|
-
|
|
625
|
-
### Flags
|
|
626
|
-
|
|
627
|
-
| Flag | Description | Default |
|
|
628
|
-
|------|-------------|---------|
|
|
629
|
-
| `-h`, `--help` | Show help and exit | — |
|
|
630
|
-
| `-v`, `--version` | Show version and exit | — |
|
|
631
|
-
| `--edgefile=<path>` | Load `Edgefile.rip` from an explicit path | Auto-discover or none |
|
|
632
|
-
| `--check-config` | Validate `Edgefile.rip` or `config.rip` and exit | Disabled |
|
|
633
|
-
| `--watch=<glob>` | Watch glob pattern | `*.rip` |
|
|
634
|
-
| `--static` | Disable hot reload and file watching | — |
|
|
635
|
-
| `--env=<mode>` | Environment mode (`dev`, `prod`) | `development` |
|
|
636
|
-
| `--debug` | Enable debug logging | Disabled |
|
|
637
|
-
| `--quiet` | Suppress startup URL output | Disabled |
|
|
638
|
-
| `http` | HTTP-only mode (no HTTPS) | HTTPS enabled |
|
|
639
|
-
| `https` | HTTPS mode (explicit) | Auto |
|
|
640
|
-
| `http:<port>` | Set HTTP port | 80, fallback 3000 |
|
|
641
|
-
| `https:<port>` | Set HTTPS port | 443, fallback 3443 |
|
|
642
|
-
| `--socket-prefix=<name>` | Override Unix socket / PID file prefix | `rip_<app-name>` |
|
|
643
|
-
| `w:<n>` | Worker count (`auto`, `half`, `2x`, `3x`, or number) | `half` of cores |
|
|
644
|
-
| `r:<reqs>,<secs>s` | Restart policy: requests, seconds (e.g., `5000,3600s`) | `10000,3600s` |
|
|
645
|
-
| `--cert=<path>` | TLS certificate path | Shipped `*.ripdev.io` cert |
|
|
646
|
-
| `--key=<path>` | TLS private key path | Shipped `*.ripdev.io` key |
|
|
647
|
-
| `--hsts` | Enable HSTS headers | Disabled |
|
|
648
|
-
| `--no-redirect-http` | Don't redirect HTTP to HTTPS | Redirects enabled |
|
|
649
|
-
| `--json-logging` | Output JSON access logs | Human-readable |
|
|
650
|
-
| `--no-access-log` | Disable access logging | Enabled |
|
|
651
|
-
| `--acme` | Enable auto-TLS via Let's Encrypt | Disabled |
|
|
652
|
-
| `--acme-staging` | Use Let's Encrypt staging CA | Disabled |
|
|
653
|
-
| `--acme-domain=<d>` | Domain for ACME certificate | — |
|
|
654
|
-
| `--realtime-path=<p>` | WebSocket endpoint path | `/realtime` |
|
|
655
|
-
| `--rate-limit=<n>` | Max requests per IP per window | Disabled (0) |
|
|
656
|
-
| `--rate-limit-window=<ms>` | Rate limit window in ms | `60000` (1 min) |
|
|
657
|
-
| `--publish-secret=<s>` | Bearer token for `/publish` endpoint | None (open) |
|
|
658
|
-
|
|
659
|
-
### Subcommands
|
|
660
|
-
|
|
661
|
-
```bash
|
|
662
|
-
rip server stop # Stop running server
|
|
663
|
-
rip server list # List registered hosts
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
### Examples
|
|
667
|
-
|
|
668
|
-
```bash
|
|
669
|
-
# Development (default: watches *.rip, HTTPS, hot reload)
|
|
670
|
-
rip server
|
|
671
|
-
|
|
672
|
-
# HTTP only
|
|
673
|
-
rip server http
|
|
674
|
-
|
|
675
|
-
# Production: 8 workers, no hot reload
|
|
676
|
-
rip server --static w:8
|
|
677
|
-
|
|
678
|
-
# Custom port
|
|
679
|
-
rip server http:3000
|
|
680
|
-
|
|
681
|
-
# With mDNS aliases (accessible as myapp.local and api.local)
|
|
682
|
-
rip server myapp@api
|
|
683
|
-
|
|
684
|
-
# Watch TypeScript files instead of Rip
|
|
685
|
-
rip server --watch=*.ts
|
|
686
|
-
|
|
687
|
-
# Debug mode
|
|
688
|
-
rip server --debug
|
|
689
|
-
|
|
690
|
-
# Restart workers after 5000 requests or 1 hour
|
|
691
|
-
rip server r:5000,3600s
|
|
692
|
-
```
|
|
693
|
-
|
|
694
|
-
## Internals
|
|
695
|
-
|
|
696
|
-
### Self-Spawning Design
|
|
697
|
-
|
|
698
|
-
The server uses a single-file, self-spawning architecture:
|
|
699
|
-
|
|
700
|
-
```
|
|
701
|
-
┌─────────────────────────────────────────────────────────┐
|
|
702
|
-
│ Main Process │
|
|
703
|
-
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
|
|
704
|
-
│ │ Server │ │ Manager │ │ Control Socket │ │
|
|
705
|
-
│ │ (HTTP/HTTPS)│ │ (Workers) │ │ (Commands) │ │
|
|
706
|
-
│ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │
|
|
707
|
-
└─────────┼────────────────┼──────────────────┼───────────┘
|
|
708
|
-
│ │ │
|
|
709
|
-
▼ ▼ │
|
|
710
|
-
┌──────────┐ ┌──────────────┐ │
|
|
711
|
-
│ Requests │ │ Spawn/Monitor│ │
|
|
712
|
-
└────┬─────┘ └──────┬───────┘ │
|
|
713
|
-
│ │ │
|
|
714
|
-
▼ ▼ │
|
|
715
|
-
┌─────────────────────────────────────────────│───┐
|
|
716
|
-
│ Worker Processes │ │
|
|
717
|
-
│ ┌────────┐ ┌────────┐ ┌────────┐ │ │
|
|
718
|
-
│ │Worker 0│ │Worker 1│ │Worker N│ ◄────────┘ │
|
|
719
|
-
│ │(Unix) │ │(Unix) │ │(Unix) │ │
|
|
720
|
-
│ └────────┘ └────────┘ └────────┘ │
|
|
721
|
-
└─────────────────────────────────────────────────┘
|
|
722
|
-
```
|
|
723
|
-
|
|
724
|
-
When `RIP_SETUP_MODE=1` is set, the same file runs the one-time setup phase. When `RIP_WORKER_MODE=1` is set, it runs as a worker.
|
|
725
|
-
|
|
726
|
-
### Startup Lifecycle
|
|
727
|
-
|
|
728
|
-
1. **Setup** — If `setup.rip` exists next to the entry file, it runs once in a temporary process before any workers spawn. Use this for database migrations, table creation, and seeding.
|
|
729
|
-
2. **Workers** — N worker processes are spawned, each loading the entry file and serving requests.
|
|
240
|
+
If a stream route shares the HTTPS port, Rip switches that port into shared
|
|
241
|
+
multiplexer mode: matching SNI is passed through at Layer 4, and everything
|
|
242
|
+
else falls through to Rip's internal HTTPS server.
|
|
730
243
|
|
|
731
|
-
|
|
244
|
+
## Clean Example
|
|
732
245
|
|
|
733
|
-
```
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
validate --> rateLimit[RateLimit]
|
|
737
|
-
rateLimit --> edgeRoute{Edge Route Match}
|
|
738
|
-
edgeRoute -->|upstream| upstream[Proxy To Upstream]
|
|
739
|
-
edgeRoute -->|app| worker[Forward To Managed Worker]
|
|
740
|
-
edgeRoute -->|no edge match| registry[Host Registry]
|
|
741
|
-
registry --> worker
|
|
742
|
-
worker --> response[Response]
|
|
743
|
-
upstream --> response
|
|
744
|
-
```
|
|
745
|
-
|
|
746
|
-
At runtime:
|
|
246
|
+
```coffee
|
|
247
|
+
export default
|
|
248
|
+
version: 1
|
|
747
249
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
250
|
+
server:
|
|
251
|
+
hsts: true
|
|
252
|
+
trustedProxies: ['10.0.0.0/8', '127.0.0.1']
|
|
253
|
+
timeouts:
|
|
254
|
+
connectMs: 2000
|
|
255
|
+
readMs: 30000
|
|
256
|
+
verify:
|
|
257
|
+
requireHealthyProxies: true
|
|
258
|
+
requireReadyApps: true
|
|
259
|
+
includeUnroutedManagedApps: false
|
|
260
|
+
minHealthyTargetsPerProxy: 1
|
|
753
261
|
|
|
754
|
-
|
|
262
|
+
certs:
|
|
263
|
+
trusthealth: '/ssl/trusthealth.com'
|
|
264
|
+
zion: '/ssl/zionlabshare.com'
|
|
755
265
|
|
|
756
|
-
|
|
266
|
+
proxies:
|
|
267
|
+
api:
|
|
268
|
+
hosts: ['http://127.0.0.1:7201']
|
|
269
|
+
check:
|
|
270
|
+
path: '/health'
|
|
271
|
+
intervalMs: 5000
|
|
272
|
+
timeoutMs: 2000
|
|
757
273
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
274
|
+
redmine:
|
|
275
|
+
hosts: ['http://127.0.0.1:7101']
|
|
276
|
+
check:
|
|
277
|
+
path: '/'
|
|
278
|
+
intervalMs: 5000
|
|
279
|
+
timeoutMs: 2000
|
|
761
280
|
|
|
762
|
-
|
|
281
|
+
incus:
|
|
282
|
+
hosts: ['tcp://127.0.0.1:8443']
|
|
763
283
|
|
|
764
|
-
|
|
284
|
+
apps:
|
|
285
|
+
web:
|
|
286
|
+
entry: './apps/web/index.rip'
|
|
287
|
+
workers: 4
|
|
765
288
|
|
|
766
|
-
|
|
289
|
+
admin:
|
|
290
|
+
entry: './apps/admin/index.rip'
|
|
291
|
+
workers: 2
|
|
767
292
|
|
|
768
|
-
|
|
293
|
+
rules:
|
|
294
|
+
web: [
|
|
295
|
+
{ path: '/api/*', proxy: 'api' }
|
|
296
|
+
{ path: '/*', app: 'web' }
|
|
297
|
+
]
|
|
769
298
|
|
|
770
|
-
|
|
771
|
-
|
|
299
|
+
admin: [
|
|
300
|
+
{ path: '/api/*', proxy: 'api' }
|
|
301
|
+
{ path: '/*', app: 'admin' }
|
|
302
|
+
]
|
|
772
303
|
|
|
773
|
-
|
|
304
|
+
redmine: [
|
|
305
|
+
{ path: '/*', proxy: 'redmine' }
|
|
306
|
+
]
|
|
774
307
|
|
|
775
|
-
|
|
308
|
+
groups:
|
|
309
|
+
trustSites: ['trusthealth.com', 'www.trusthealth.com']
|
|
310
|
+
zionSites: ['zionlabshare.com', 'www.zionlabshare.com']
|
|
776
311
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
| `/publish` | External WebSocket broadcast (when `--realtime` enabled) |
|
|
783
|
-
| `/realtime` | WebSocket upgrade endpoint (when `--realtime` enabled) |
|
|
312
|
+
hosts:
|
|
313
|
+
trustSites:
|
|
314
|
+
hosts: 'trustSites'
|
|
315
|
+
cert: 'trusthealth'
|
|
316
|
+
rules: 'web'
|
|
784
317
|
|
|
785
|
-
|
|
318
|
+
zionSites:
|
|
319
|
+
hosts: 'zionSites'
|
|
320
|
+
cert: 'zion'
|
|
321
|
+
rules: 'web'
|
|
786
322
|
|
|
787
|
-
|
|
323
|
+
'admin.trusthealth.com':
|
|
324
|
+
cert: 'trusthealth'
|
|
325
|
+
rules: 'admin'
|
|
788
326
|
|
|
789
|
-
|
|
327
|
+
'projects.trusthealth.com':
|
|
328
|
+
cert: 'trusthealth'
|
|
329
|
+
rules: 'redmine'
|
|
790
330
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
rip server analytics # → https://analytics.ripdev.io (green lock)
|
|
794
|
-
rip server myapp # → https://myapp.ripdev.io (green lock)
|
|
331
|
+
'incus.trusthealth.com':
|
|
332
|
+
proxy: 'incus'
|
|
795
333
|
```
|
|
796
334
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
### Custom Certificates
|
|
335
|
+
## Operator Runbook
|
|
800
336
|
|
|
801
|
-
|
|
337
|
+
### Start with an explicit config file
|
|
802
338
|
|
|
803
339
|
```bash
|
|
804
|
-
rip server --
|
|
340
|
+
rip server --file=./serve.rip
|
|
805
341
|
```
|
|
806
342
|
|
|
807
|
-
###
|
|
808
|
-
|
|
809
|
-
Automatic TLS certificate management via Let's Encrypt HTTP-01 challenges.
|
|
810
|
-
Zero dependencies — uses `node:crypto` for all cryptographic operations.
|
|
343
|
+
### Validate config without serving
|
|
811
344
|
|
|
812
345
|
```bash
|
|
813
|
-
|
|
814
|
-
rip server --
|
|
815
|
-
|
|
816
|
-
# Staging CA (for testing, no rate limits)
|
|
817
|
-
rip server --acme-staging --acme-domain=test.example.com
|
|
818
|
-
```
|
|
819
|
-
|
|
820
|
-
How it works:
|
|
821
|
-
|
|
822
|
-
1. Edge server generates an EC P-256 account key and registers with Let's Encrypt
|
|
823
|
-
2. Creates a certificate order for your domain
|
|
824
|
-
3. Serves HTTP-01 challenge tokens on port 80 at `/.well-known/acme-challenge/`
|
|
825
|
-
4. Finalizes the order with a CSR and downloads the certificate chain
|
|
826
|
-
5. Stores cert and key at `~/.rip/certs/{domain}/`
|
|
827
|
-
6. Starts a renewal loop (checks every 12 hours, renews 30 days before expiry)
|
|
828
|
-
|
|
829
|
-
The ACME crypto stack has been validated against the real Let's Encrypt staging
|
|
830
|
-
server — account creation, JWS signing, and nonce management all confirmed working.
|
|
831
|
-
|
|
832
|
-
## Realtime WebSocket (Bam-style)
|
|
833
|
-
|
|
834
|
-
Built-in WebSocket pub/sub where **your backend stays HTTP-only**. The edge
|
|
835
|
-
server manages all WebSocket connections, group membership, and message routing.
|
|
836
|
-
Your app just responds to HTTP POSTs with JSON instructions.
|
|
837
|
-
|
|
838
|
-
Realtime is always on — no flags needed. WebSocket connections are accepted
|
|
839
|
-
at `/realtime` by default. Customize the path with `--realtime-path=/ws`.
|
|
840
|
-
|
|
841
|
-
### How it works
|
|
842
|
-
|
|
843
|
-
1. Client connects via WebSocket to `/realtime` (configurable)
|
|
844
|
-
2. Edge forwards the event to a worker as a POST to `/v1/realtime` with `Sec-WebSocket-Frame: open` — using the same worker pool and scheduler as regular HTTP requests
|
|
845
|
-
3. Your handler responds with JSON: `{ "+": ["room1"], "@": ["user1"], "welcome": "hello" }`
|
|
846
|
-
4. Edge updates group membership and delivers messages to targets
|
|
847
|
-
|
|
848
|
-
### Protocol
|
|
849
|
-
|
|
850
|
-
Backend JSON response keys:
|
|
851
|
-
|
|
852
|
-
| Key | Meaning |
|
|
853
|
-
|-----|---------|
|
|
854
|
-
| `@` | Target groups — who receives the message |
|
|
855
|
-
| `+` | Subscribe — add client to these groups |
|
|
856
|
-
| `-` | Unsubscribe — remove client from these groups |
|
|
857
|
-
| `>` | Senders — exclude these clients from delivery |
|
|
858
|
-
| Any other | Event payload — delivered to all targets |
|
|
859
|
-
|
|
860
|
-
Groups starting with `/` are channel groups (members receive messages).
|
|
861
|
-
Other groups are direct client targets.
|
|
862
|
-
|
|
863
|
-
### Example backend handler
|
|
864
|
-
|
|
865
|
-
```coffee
|
|
866
|
-
post '/v1/realtime' ->
|
|
867
|
-
frame = @req.header 'Sec-WebSocket-Frame'
|
|
868
|
-
|
|
869
|
-
if frame is 'open'
|
|
870
|
-
user = authenticate!(@req)
|
|
871
|
-
return { '+': ["/lobby", "/user-#{user.id}"], 'connected': { userId: user.id } }
|
|
872
|
-
|
|
873
|
-
if frame is 'text'
|
|
874
|
-
data = @req.json!
|
|
875
|
-
return { '@': ["/lobby"], 'chat': { from: data.from, text: data.text } }
|
|
876
|
-
|
|
877
|
-
{ ok: true }
|
|
878
|
-
```
|
|
879
|
-
|
|
880
|
-
### External publish
|
|
881
|
-
|
|
882
|
-
Server-side code can broadcast messages without a WebSocket connection:
|
|
883
|
-
|
|
884
|
-
```bash
|
|
885
|
-
curl -X POST http://localhost/publish \
|
|
886
|
-
-d '{"@": ["/lobby"], "announcement": "Server restarting in 5 minutes"}'
|
|
346
|
+
rip server --check-config
|
|
347
|
+
rip server --check-config --file=./serve.rip
|
|
887
348
|
```
|
|
888
349
|
|
|
889
|
-
|
|
350
|
+
### Reload config safely
|
|
890
351
|
|
|
891
352
|
```bash
|
|
892
|
-
|
|
893
|
-
rip server --publish-secret=my-secret-token
|
|
894
|
-
|
|
895
|
-
# Via environment variable
|
|
896
|
-
RIP_PUBLISH_SECRET=my-secret-token rip server
|
|
353
|
+
kill -HUP "$(cat /tmp/rip_myapp.pid)"
|
|
897
354
|
```
|
|
898
355
|
|
|
899
|
-
|
|
900
|
-
secret, `/publish` is open to any client that can reach a valid host — fine for
|
|
901
|
-
development, but always set a secret in production.
|
|
902
|
-
|
|
903
|
-
## Diagnostics & Observability
|
|
904
|
-
|
|
905
|
-
### `/status` — Health check
|
|
356
|
+
or:
|
|
906
357
|
|
|
907
358
|
```bash
|
|
908
|
-
curl http://localhost/
|
|
909
|
-
# {"status":"healthy","app":"myapp","workers":4,"uptime":86400,"hosts":["localhost"]}
|
|
359
|
+
curl --unix-socket /tmp/rip_myapp.ctl.sock -X POST http://localhost/reload
|
|
910
360
|
```
|
|
911
361
|
|
|
912
|
-
###
|
|
362
|
+
### Diagnostics
|
|
913
363
|
|
|
914
364
|
```bash
|
|
915
365
|
curl http://localhost/diagnostics
|
|
916
366
|
```
|
|
917
367
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
```json
|
|
921
|
-
{
|
|
922
|
-
"status": "healthy",
|
|
923
|
-
"version": { "server": "1.0.0", "rip": "3.13.108" },
|
|
924
|
-
"uptime": 86400,
|
|
925
|
-
"apps": [{ "id": "myapp", "workers": 4, "inflight": 2, "queueDepth": 0 }],
|
|
926
|
-
"upstreams": [
|
|
927
|
-
{ "id": "app", "targets": 2, "healthyTargets": 2, "unhealthyTargets": 0 }
|
|
928
|
-
],
|
|
929
|
-
"metrics": {
|
|
930
|
-
"requests": 150000,
|
|
931
|
-
"responses": { "2xx": 148000, "4xx": 1500, "5xx": 500 },
|
|
932
|
-
"latency": { "p50": 0.012, "p95": 0.085, "p99": 0.210 },
|
|
933
|
-
"queue": { "queued": 3200, "timeouts": 5, "shed": 12 },
|
|
934
|
-
"workers": { "restarts": 2 },
|
|
935
|
-
"acme": { "renewals": 1, "failures": 0 },
|
|
936
|
-
"websocket": { "connections": 45, "messages": 12000, "deliveries": 89000 }
|
|
937
|
-
},
|
|
938
|
-
"gauges": {
|
|
939
|
-
"workersActive": 4,
|
|
940
|
-
"inflight": 2,
|
|
941
|
-
"queueDepth": 0,
|
|
942
|
-
"upstreamTargetsHealthy": 2,
|
|
943
|
-
"upstreamTargetsUnhealthy": 0
|
|
944
|
-
},
|
|
945
|
-
"realtime": { "clients": 23, "groups": 8, "deliveries": 89000, "messages": 12000 },
|
|
946
|
-
"hosts": ["localhost", "myapp.ripdev.io"],
|
|
947
|
-
"config": {
|
|
948
|
-
"kind": "edge",
|
|
949
|
-
"path": "/srv/Edgefile.rip",
|
|
950
|
-
"version": 1,
|
|
951
|
-
"counts": { "apps": 2, "upstreams": 1, "routes": 4, "sites": 1 },
|
|
952
|
-
"lastResult": "applied",
|
|
953
|
-
"lastError": null,
|
|
954
|
-
"lastErrorCode": null,
|
|
955
|
-
"lastErrorDetails": null,
|
|
956
|
-
"activeRouteDescriptions": [
|
|
957
|
-
"* /api/* proxy:api",
|
|
958
|
-
"admin.example.com /* app:admin"
|
|
959
|
-
],
|
|
960
|
-
"activeRuntime": { "id": "edge-123", "inflight": 1, "wsConnections": 2 },
|
|
961
|
-
"retiredRuntimes": [],
|
|
962
|
-
"lastReload": {
|
|
963
|
-
"id": "reload-4",
|
|
964
|
-
"source": "control_api",
|
|
965
|
-
"oldVersion": 1,
|
|
966
|
-
"newVersion": 1,
|
|
967
|
-
"result": "applied",
|
|
968
|
-
"reason": null,
|
|
969
|
-
"code": null
|
|
970
|
-
},
|
|
971
|
-
"reloadHistory": []
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
```
|
|
975
|
-
|
|
976
|
-
### Structured event logging
|
|
977
|
-
|
|
978
|
-
With `--json-logging`, lifecycle events are emitted as structured JSON:
|
|
979
|
-
|
|
980
|
-
```json
|
|
981
|
-
{"t":"2026-03-14T12:00:00.000Z","event":"server_start","app":"myapp","workers":4}
|
|
982
|
-
{"t":"2026-03-14T12:05:00.000Z","event":"worker_restart","workerId":3,"attempt":1}
|
|
983
|
-
{"t":"2026-03-14T12:10:00.000Z","event":"ws_open","clientId":"a1b2c3d4"}
|
|
984
|
-
```
|
|
985
|
-
|
|
986
|
-
Events: `server_start`, `server_stop`, `worker_restart`, `worker_abandon`, `ws_open`, `ws_close`
|
|
987
|
-
|
|
988
|
-
## mDNS Service Discovery
|
|
989
|
-
|
|
990
|
-
The server automatically advertises itself via mDNS (Bonjour/Zeroconf):
|
|
991
|
-
|
|
992
|
-
```bash
|
|
993
|
-
# App accessible at myapp.local
|
|
994
|
-
rip server myapp
|
|
995
|
-
|
|
996
|
-
# Multiple aliases
|
|
997
|
-
rip server myapp@api,backend
|
|
998
|
-
```
|
|
999
|
-
|
|
1000
|
-
Requires `dns-sd` (available on macOS by default).
|
|
1001
|
-
|
|
1002
|
-
## App Requirements
|
|
1003
|
-
|
|
1004
|
-
Your app must provide a fetch handler. Three patterns are supported:
|
|
1005
|
-
|
|
1006
|
-
### Pattern 1: Use `@rip-lang/server` with `start()` (Recommended)
|
|
1007
|
-
|
|
1008
|
-
```coffee
|
|
1009
|
-
import { get, start } from '@rip-lang/server'
|
|
1010
|
-
|
|
1011
|
-
get '/' -> 'Hello!'
|
|
1012
|
-
|
|
1013
|
-
start()
|
|
1014
|
-
```
|
|
1015
|
-
|
|
1016
|
-
The `start()` function automatically detects when running under `rip server` and registers the handler.
|
|
1017
|
-
|
|
1018
|
-
### Pattern 2: Export fetch function directly
|
|
1019
|
-
|
|
1020
|
-
```coffee
|
|
1021
|
-
export default (req) ->
|
|
1022
|
-
new Response('Hello!')
|
|
1023
|
-
```
|
|
1024
|
-
|
|
1025
|
-
### Pattern 3: Export object with fetch method
|
|
1026
|
-
|
|
1027
|
-
```coffee
|
|
1028
|
-
export default
|
|
1029
|
-
fetch: (req) -> new Response('Hello!')
|
|
1030
|
-
```
|
|
1031
|
-
|
|
1032
|
-
## One-Time Setup
|
|
1033
|
-
|
|
1034
|
-
If a `setup.rip` file exists next to your entry file, `rip server` runs it
|
|
1035
|
-
automatically **once** before spawning any workers. This is ideal for database
|
|
1036
|
-
migrations, table creation, and seeding.
|
|
1037
|
-
|
|
1038
|
-
```coffee
|
|
1039
|
-
# setup.rip — runs once before workers start
|
|
1040
|
-
export setup = ->
|
|
1041
|
-
await createTables()
|
|
1042
|
-
await seedData()
|
|
1043
|
-
p 'Database ready'
|
|
1044
|
-
```
|
|
1045
|
-
|
|
1046
|
-
The setup function can export as `setup` or `default`. If the file doesn't
|
|
1047
|
-
exist, the setup phase is skipped entirely (no overhead). If setup fails,
|
|
1048
|
-
the server exits immediately.
|
|
1049
|
-
|
|
1050
|
-
When `setup.rip` is present, the `rip-server: https://...` URL lines are
|
|
1051
|
-
**suppressed at the top** of the output — they will appear later via `[setup]`
|
|
1052
|
-
instead, after migrations complete. The actual server URLs are passed to the
|
|
1053
|
-
setup process as `process.env.RIP_URLS` (comma-separated), so you can display
|
|
1054
|
-
them exactly as rip-server computed them:
|
|
1055
|
-
|
|
1056
|
-
```coffee
|
|
1057
|
-
# setup.rip — print URLs at the bottom after setup completes
|
|
1058
|
-
export setup = ->
|
|
1059
|
-
await runMigrations()
|
|
1060
|
-
urls = process.env.RIP_URLS?.split(',') or []
|
|
1061
|
-
p "[setup] #{urls.join(' | ')}"
|
|
1062
|
-
```
|
|
1063
|
-
|
|
1064
|
-
## Environment Variables
|
|
1065
|
-
|
|
1066
|
-
Most settings are configured via CLI flags, but environment variables provide an alternative for containers, CI/CD, or system-wide defaults.
|
|
1067
|
-
|
|
1068
|
-
**Essential:**
|
|
1069
|
-
|
|
1070
|
-
| Variable | CLI Equivalent | Default | Description |
|
|
1071
|
-
|----------|----------------|---------|-------------|
|
|
1072
|
-
| `NODE_ENV` | `--env=` | `development` | Environment mode (`development` or `production`) |
|
|
1073
|
-
| `RIP_DEBUG` | `--debug` | — | Enable debug logging |
|
|
1074
|
-
| `RIP_STATIC` | `--static` | `0` | Set to `1` to disable hot reload |
|
|
1075
|
-
|
|
1076
|
-
**Advanced (rarely needed):**
|
|
1077
|
-
|
|
1078
|
-
| Variable | CLI Equivalent | Default | Description |
|
|
1079
|
-
|----------|----------------|---------|-------------|
|
|
1080
|
-
| `RIP_MAX_REQUESTS` | `r:N,...` | `10000` | Max requests before worker recycle |
|
|
1081
|
-
| `RIP_MAX_SECONDS` | `r:...,Ns` | `3600` | Max seconds before worker recycle |
|
|
1082
|
-
| `RIP_MAX_QUEUE` | `--max-queue=` | `512` | Request queue limit |
|
|
1083
|
-
| `RIP_QUEUE_TIMEOUT_MS` | `--queue-timeout-ms=` | `30000` | Queue wait timeout (ms) |
|
|
1084
|
-
| `RIP_CONNECT_TIMEOUT_MS` | `--connect-timeout-ms=` | `2000` | Reserved for future use |
|
|
1085
|
-
| `RIP_READ_TIMEOUT_MS` | `--read-timeout-ms=` | `30000` | Worker read timeout (ms) |
|
|
1086
|
-
|
|
1087
|
-
## Dashboard
|
|
1088
|
-
|
|
1089
|
-
The server includes a built-in dashboard accessible at `http://rip.local/` (when mDNS is active). This is a **meta-UI for the server itself**, not your application.
|
|
1090
|
-
|
|
1091
|
-
**Dashboard Features:**
|
|
1092
|
-
|
|
1093
|
-
- **Server Status** — Health status and uptime
|
|
1094
|
-
- **Worker Overview** — Active worker count
|
|
1095
|
-
- **Registered Hosts** — All mDNS aliases being advertised
|
|
1096
|
-
- **Server Ports** — HTTP/HTTPS port configuration
|
|
1097
|
-
|
|
1098
|
-
The dashboard uses the same mDNS infrastructure as your app, so it's always available at `rip.local` when any `rip server` instance is running.
|
|
368
|
+
The `config` section reports:
|
|
1099
369
|
|
|
1100
|
-
|
|
370
|
+
- active `serve.rip` path
|
|
371
|
+
- version
|
|
372
|
+
- counts for apps, proxies, hosts, routes, and streams
|
|
373
|
+
- active route descriptions
|
|
374
|
+
- reload history and rollback details
|
|
1101
375
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
**mDNS not working**: Ensure `dns-sd` is available (built into macOS). On Linux, install Avahi.
|
|
1105
|
-
|
|
1106
|
-
**Workers keep restarting**: Use `--debug` (or `RIP_DEBUG=1`) to see import errors in your app.
|
|
1107
|
-
|
|
1108
|
-
**Changes not triggering reload**: Ensure you're not using `--static`. Check that the file matches the watch pattern (default: `*.rip`).
|
|
1109
|
-
|
|
1110
|
-
## Serving Rip UI Apps
|
|
1111
|
-
|
|
1112
|
-
Rip Server works seamlessly with the `serve` middleware for serving
|
|
1113
|
-
reactive web applications with hot reload. The `serve` middleware handles
|
|
1114
|
-
framework files, page manifests, and SSE hot-reload — `rip server` adds HTTPS,
|
|
1115
|
-
mDNS, multi-worker load balancing, and rolling restarts on top.
|
|
1116
|
-
|
|
1117
|
-
### Example: Rip UI App
|
|
1118
|
-
|
|
1119
|
-
Create `index.rip`:
|
|
1120
|
-
|
|
1121
|
-
```coffee
|
|
1122
|
-
import { get, use, start, notFound } from '@rip-lang/server'
|
|
1123
|
-
import { serve } from '@rip-lang/server/middleware'
|
|
1124
|
-
|
|
1125
|
-
dir = import.meta.dir
|
|
1126
|
-
|
|
1127
|
-
use serve dir: dir, title: 'My App', watch: true
|
|
1128
|
-
|
|
1129
|
-
get '/css/*' -> @send "#{dir}/css/#{@req.path.slice(5)}"
|
|
1130
|
-
|
|
1131
|
-
notFound -> @send "#{dir}/index.html", 'text/html; charset=UTF-8'
|
|
1132
|
-
|
|
1133
|
-
start()
|
|
1134
|
-
```
|
|
1135
|
-
|
|
1136
|
-
Run it:
|
|
1137
|
-
|
|
1138
|
-
```bash
|
|
1139
|
-
rip server
|
|
1140
|
-
```
|
|
376
|
+
## Realtime
|
|
1141
377
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
- **Framework bundle** served at `/rip/rip.min.js`
|
|
1145
|
-
- **App bundle** auto-generated at `/{app}/bundle`
|
|
1146
|
-
- **Hot reload** via SSE at `/{app}/watch` — save a `.rip` file and the browser
|
|
1147
|
-
updates instantly
|
|
1148
|
-
- **HTTPS + mDNS** — access at `https://myapp.local`
|
|
1149
|
-
- **Multi-worker** — load balanced across CPU cores
|
|
1150
|
-
- **Rolling restarts** — zero-downtime file-watch reloading
|
|
1151
|
-
|
|
1152
|
-
See [Hot Reloading](#hot-reloading) for details on how the two layers (API + UI) work together.
|
|
1153
|
-
|
|
1154
|
-
## Multi-App Configuration (`config.rip`)
|
|
1155
|
-
|
|
1156
|
-
If a `config.rip` file exists next to your entry file, the server loads it
|
|
1157
|
-
and registers additional apps with their own hosts and worker pools.
|
|
1158
|
-
|
|
1159
|
-
```coffee
|
|
1160
|
-
# config.rip
|
|
1161
|
-
export default
|
|
1162
|
-
apps:
|
|
1163
|
-
main:
|
|
1164
|
-
entry: './index.rip'
|
|
1165
|
-
hosts: ['example.com', 'www.example.com']
|
|
1166
|
-
workers: 4
|
|
1167
|
-
api:
|
|
1168
|
-
entry: './api/index.rip'
|
|
1169
|
-
hosts: ['api.example.com']
|
|
1170
|
-
workers: 2
|
|
1171
|
-
maxQueue: 1024
|
|
1172
|
-
admin:
|
|
1173
|
-
entry: './admin/index.rip'
|
|
1174
|
-
hosts: ['admin.example.com']
|
|
1175
|
-
workers: 1
|
|
1176
|
-
```
|
|
1177
|
-
|
|
1178
|
-
Each app gets its own worker pool, queue, and host routing. The edge server
|
|
1179
|
-
routes requests to the correct app based on the `Host` header.
|
|
1180
|
-
|
|
1181
|
-
If no `config.rip` exists, the server runs in single-app mode as usual.
|
|
1182
|
-
|
|
1183
|
-
## Reverse Proxy
|
|
1184
|
-
|
|
1185
|
-
Forward requests to external HTTP upstreams with proper header handling:
|
|
1186
|
-
|
|
1187
|
-
```coffee
|
|
1188
|
-
import { get } from '@rip-lang/server'
|
|
1189
|
-
import { proxyToUpstream } from '@rip-lang/server/edge/forwarding.rip'
|
|
1190
|
-
|
|
1191
|
-
get '/api/*' -> proxyToUpstream!(@req.raw, 'http://backend:8080', { clientIp: @req.header('x-real-ip') or '127.0.0.1' })
|
|
1192
|
-
```
|
|
1193
|
-
|
|
1194
|
-
Features:
|
|
1195
|
-
- Strips hop-by-hop headers (Connection, Transfer-Encoding, etc.)
|
|
1196
|
-
- Sets `X-Forwarded-For` to actual client IP (overwrites, never appends — prevents spoofing)
|
|
1197
|
-
- Adds `X-Forwarded-Proto` and `X-Forwarded-Host`
|
|
1198
|
-
- Streaming response passthrough
|
|
1199
|
-
- Timeout with 504, connect failure with 502
|
|
1200
|
-
- Manual redirect handling (no auto-follow)
|
|
1201
|
-
|
|
1202
|
-
Pass `clientIp` in options so upstream services see the real client address.
|
|
1203
|
-
Pass `timeoutMs` to override the default 30-second upstream timeout.
|
|
1204
|
-
|
|
1205
|
-
## Request ID Tracing
|
|
1206
|
-
|
|
1207
|
-
Every request gets a unique `X-Request-Id` header for end-to-end correlation.
|
|
1208
|
-
If the client sends an `X-Request-Id`, it's preserved; otherwise one is generated.
|
|
1209
|
-
|
|
1210
|
-
```bash
|
|
1211
|
-
curl -v http://localhost/users/42
|
|
1212
|
-
# < X-Request-Id: req-a8f3b2c1d4e5
|
|
1213
|
-
```
|
|
1214
|
-
|
|
1215
|
-
## Rate Limiting
|
|
1216
|
-
|
|
1217
|
-
Per-IP sliding window rate limiting, disabled by default:
|
|
1218
|
-
|
|
1219
|
-
```bash
|
|
1220
|
-
rip server --rate-limit=100 # 100 requests per minute per IP
|
|
1221
|
-
rip server --rate-limit=1000 --rate-limit-window=3600000 # 1000 per hour
|
|
1222
|
-
```
|
|
1223
|
-
|
|
1224
|
-
Returns `429 Too Many Requests` with `Retry-After` header when exceeded.
|
|
378
|
+
Rip Server includes built-in WebSocket pub/sub while your backend stays
|
|
379
|
+
HTTP-oriented.
|
|
1225
380
|
|
|
1226
381
|
## Security
|
|
1227
382
|
|
|
1228
|
-
Built-in
|
|
1229
|
-
|
|
1230
|
-
- Rejects conflicting `Content-Length` + `Transfer-Encoding` headers
|
|
1231
|
-
- Rejects multiple `Host` headers
|
|
1232
|
-
- Rejects null bytes in URLs
|
|
1233
|
-
- Rejects oversized URLs (>8KB)
|
|
1234
|
-
- Path traversal normalized by URL parser
|
|
1235
|
-
|
|
1236
|
-
## WebSocket Passthrough
|
|
1237
|
-
|
|
1238
|
-
For reverse proxying WebSocket connections to external upstreams:
|
|
1239
|
-
|
|
1240
|
-
```coffee
|
|
1241
|
-
import { createWsPassthrough } from '@rip-lang/server/edge/forwarding.rip'
|
|
1242
|
-
|
|
1243
|
-
# In a WebSocket handler, tunnel to an external upstream
|
|
1244
|
-
upstream = createWsPassthrough(clientWs, 'ws://backend:8080/ws')
|
|
1245
|
-
```
|
|
383
|
+
Built-in protections include:
|
|
1246
384
|
|
|
1247
|
-
|
|
385
|
+
- conflicting `Content-Length` + `Transfer-Encoding` rejection
|
|
386
|
+
- multiple `Host` header rejection
|
|
387
|
+
- oversized URL rejection
|
|
388
|
+
- null-byte URL rejection
|
|
389
|
+
- path traversal protection for static serving
|
|
1248
390
|
|
|
1249
391
|
## Roadmap
|
|
1250
392
|
|
|
1251
|
-
-
|
|
1252
|
-
-
|
|
1253
|
-
-
|
|
393
|
+
- Prometheus / OpenTelemetry metrics export
|
|
394
|
+
- Inline edge handlers
|
|
395
|
+
- HTTP response caching at the edge
|
|
1254
396
|
|
|
1255
397
|
## License
|
|
1256
398
|
|
|
1257
399
|
MIT
|
|
1258
|
-
|
|
1259
|
-
## Links
|
|
1260
|
-
|
|
1261
|
-
- [Rip Language](https://github.com/shreeve/rip-lang) — Compiler + reactive UI framework
|
|
1262
|
-
- [Report Issues](https://github.com/shreeve/rip-lang/issues)
|