Ports & routing
Loopback curl, the port preview, and the default app preview all go
through the supervisor’s port registry. The registry is a map from port
to owning pid and the facet stub that serves it; a facet’s
listen(port) is an RPC that writes this map, and ports are torn down
with their pid. Loopback exists only inside the session: the DNS shim
resolves localhost and nothing else, and a connection to
127.0.0.1:<port> is a lookup in this registry, not a network hop.
No registry entry returns 502: no process is listening. A registered port
whose stub isn’t attached returns 501. That state exists during the boot
window between registration and a serving isolate. A dead port and a booting
port return different codes, and neither is masked by a retry.
A routed request crosses the isolate boundary as a native Request over Workers RPC — not a structured-clone copy — so binary bodies pass byte-for-byte in both directions, and the request body itself streams into the facet.
Responses stream end to end. The relay pipes the facet’s body through an
identity stream and keeps the routing call open until the body finishes;
closing it early would kill the stream, and materializing it first —
what an earlier version did — turned every SSE feed into one
batch at stream-close. Live probes check each SSE tick in the
session shell and through the preview path at ~300 ms intervals. In-session
curl writes each chunk as it arrives, and Ctrl+C
cancels the read. Abandoned readers also cancel their relay pipes.
Node’s HTTP bridge and the Python and Ruby virtual-socket shims are separate
adapters today, but both end at this registry. The target is one shared
virtual socket layer for Node, Python, Ruby, and WASI. sock_accept is the one
unimplemented preview1 function, so WASM servers can’t take /port traffic
yet. That work is on the research page.