diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-20 03:53:17 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-20 03:53:17 -0300 |
| commit | f1b53c1533539aecbf16ad19fd9156deae091f92 (patch) | |
| tree | 38ff00810ef5e0ad271c26e218cf6af4578c71ce /docs/design.md | |
| parent | ba7ec40782ba7020d82a56896a5eb1b52578d6aa (diff) | |
| download | cloud9-f1b53c1533539aecbf16ad19fd9156deae091f92.tar.gz cloud9-f1b53c1533539aecbf16ad19fd9156deae091f92.zip | |
9web: multiplexer, HTTP view of the tree, live streams, richer page
One upstream 9P connection now serves any number of browser WebSocket
sessions and, with --serve, plain 9P clients over TCP or Unix (a 9pserve-
style frame remux: tags and fids remapped, Tflush forwarded, reconnect on
upstream loss). /fs/<path> maps HTTP onto the tree: GET file or directory
(JSON or HTML), Range, HEAD with 9P headers, PUT (create, truncate, append,
trailing slash makes a directory), DELETE, and ?follow=1 or text/event-stream
turning a blocking read into server-sent events with Tflush on disconnect.
The page gains a lazy tree, stat panel, create, rename, delete, upload and
follow mode. --probe embeds 9proc for self-introspection. New mux-test and
http-fs-test steps; e2e still passes.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to 'docs/design.md')
| -rw-r--r-- | docs/design.md | 47 |
1 files changed, 47 insertions, 0 deletions
diff --git a/docs/design.md b/docs/design.md index 3ad9338..f34a3ea 100644 --- a/docs/design.md +++ b/docs/design.md @@ -173,6 +173,53 @@ that waits on another thread must stop waiting once `stop()` has begun. Unix socket paths are the application's: the runner neither unlinks, chmods nor removes them. +# Multiplexer (9web) + +`9web` (`web/`) is a gateway and a 9P multiplexer. It keeps **one** upstream +connection — a single `cloud9.Client`, all sixteen tags — and fans it out to +any number of downstream sessions: the browser WASM client over a WebSocket at +`/_cloud9/9p`, plain 9P clients over an optional `--serve` TCP/Unix listener, +and an HTTP view of the tree under `/fs/`. `web/mux.zig` owns the shared +upstream; `web/httpfs.zig` is the HTTP view; `web/probe.zig` embeds 9proc under +`--probe`. + +The mux is a **frame-level remux**, not an `fs.Server`/`serve.Runner` backend. +Each downstream request is forwarded upstream on a remapped tag with its fids +remapped into the shared upstream fid space, and each upstream reply is routed +back by tag. `Tversion` is answered locally (the upstream is negotiated once at +connect); `Tattach` is forwarded, so each downstream gets its own upstream tree +root; `Tflush` is forwarded as `Tflush`. Downstream fid spaces are isolated by +construction — two downstreams never share an upstream fid. The choice is +deliberate: the file-server engine re-decomposes each request into filesystem +operations and re-encodes directories with synthetic stats and an entry-index +cursor, which would lose the upstream's real directory stats, iounit and qids +and turn the readdir byte offset into an index mapping. Forwarding frames +unchanged is exactly the "forward each request upstream" contract, the shape +plan9port's 9pserve has, and it keeps the upstream's bytes intact. `serve.Runner` +remains the right tool for a *server* whose backend is a real filesystem; a +transparent *proxy* is not that. + +The shared upstream runs one reader task and any number of downstream forwarder +tasks under one mutex; the tag, fid and pending tables are comptime-sized and +nothing allocates per request. Sockets are only written under the mutex with the +client's own output buffer (disjoint from the stream writer's), and replies are +encoded into per-tag buffers and sent to downstreams outside the lock so a slow +downstream cannot stall the client. The WebSocket and `--serve` downstreams +drive the mux the same way — each reads whole frames (WebSocket binary messages +or `transport.readFrame`) and calls `forward`; the runner's socket-only listener +is not used for them because WebSocket framing and the HTTP `/fs` view do not fit +it. When all sixteen upstream tags are in flight, a further downstream request +waits for one to free (Tflush keeps its reserved seventeenth tag). On an upstream +I/O failure the reader fails every outstanding request, reconnects, bumps a +generation, and answers any downstream request that still names a pre-reconnect +fid with EIO until it re-attaches. + +The HTTP `/fs` view uses the same shared upstream at the fid level through a +blocking `rpc`: it allocates upstream fids from the mux pool and walks, stats, +reads and writes directly, so `GET`/`PUT`/`DELETE`, directory JSON, `Range` and +`?follow=1` server-sent events all ride the one upstream connection. A parked +`follow` read is cancelled with an upstream `Tflush` when its client goes away. + # Related programs Programs built on the library ship from this repository as `cloud9/<name>/`, |
