Transports
One router, any wire: vox in process, over WebSocket and over iroh; the HTTP face with query arguments, ranged bytes and SSE.
What it is and when to use it#
A backend becomes one LayerRouter, and the transport is picked last. vox
runs it in process (LocalServer, for desktop apps, CLIs and tests), over a
WebSocket (/vox on a server, architect::connect on the client) or over
iroh QUIC, dialled by endpoint id. The same trait also has an HTTP face: JSON
by default, arguments in the query string on a get, byte methods with
Range and 206, and each #[subscribe] method as Server-Sent Events.
Pick vox for an app’s own clients (typed, streaming, one connection) and HTTP for browsers without a vox client, curl and other tools. Design pages: The in-process transport, The HTTP face and Peer to peer for iroh.
A minimal example#
Cargo features on architect: local (native and wasm), connect (the
WebSocket client and iroh’s client side), server-axum, iroh (plus
iroh-lan for LAN lookup), http, http-client.
// In process
let scope = Scope::new();
let local = LocalServer::serve(Memory::default().into_router(), scope.clone());
let client: GreeterClient = local.establish().await?;
// WebSocket: EngineHost serves /vox; the client shares one connection per endpoint
let endpoint = architect::connect::Endpoint::url(format!("ws://{addr}/vox")).with_bearer(token);
let client: GreeterClient = architect::connect::pool().client(&endpoint).await?;
// HTTP: the same trait
let http = GreeterHttpClient::at(base);
let mut greeted = http.greeted().await?; // an SSE EventStream
On the HTTP face, a get reads its arguments from the query, and a method
returning ByteBody answers ranged requests (from
crates/architect/tests/http_routes.rs and crates/architect/tests/http_bytes.rs):
#[http(get, path = "search")]
async fn search(&self, slug: String, q: String, limit: u32, after: Option<u32>)
-> Result<Vec<u32>, NoteError>;
#[http(get, path = "blobs/{hash}")]
async fn blob(&self, slug: String, hash: String) -> Result<ByteBody, MediaError>;
// ByteBody::new(total, mime, |offset, len| async move { .. }), ByteBody::from_bytes, ByteBody::file
Code tour#
crates/architect/src/local.rsLocalServer,establish: an in-process link plus an acceptor task registered on theScope.crates/architect/src/memory_link.rsmemory_link_pair: the link architect uses on wasm.crates/architect/src/axum_ws.rsserve_router,AxumWsLink: the server half of vox over a WebSocket.crates/architect/src/connect/mod.rsPool,Endpoint,pool: one connection per URL and bearer, dialled once for concurrent callers and checked for liveness.crates/architect/src/connect/ws.rsdial: the bearer is anAuthorizationheader natively and avox.bearer.<token>subprotocol in a browser.crates/architect/src/iroh_link.rsbind_endpoint_with,serve_router,connect: vox over iroh on ALPNarchitect/vox/1, a bounded dial with one retry.crates/architect/src/http.rsQueryString,query_value,sse,HttpClient: query arguments, SSE and the typed client.crates/architect/src/http/body.rsByteBody,BodyStream,RequestHeadandcrates/architect/src/http/ranged.rsserve_body,serve_file,parse_range: byte methods.
A vox call goes out over a link (memory, WebSocket or iroh stream). The far
side hands each connection to LayerRouter::acceptor, which dispatches by
method id: the three transports run the same router. On HTTP, a get handler
parses path parameters (400 invalid_path) and query arguments
(400 invalid_query; an absent Option is None), calls the method, and a
ByteBody result goes through serve_body as 200, 206 or 416.
Tests to read#
apps/auth-server/tests/surfaces.rs: one scenario over HTTP,LocalServer, pooled WebSocket and iroh.examples/greeter/tests/round_trip.rs: in process, HTTP and SSE.crates/architect/tests/http_routes.rs: every face runs one scenario; agetreads its arguments from the query.crates/architect/tests/http_bytes.rs: an upload past its cap, a ranged download, query arguments on both.crates/architect/tests/http_ranged.rs:serve_fileandserve_bodyanswering 206 and 416.crates/architect/tests/http_face.rs: subscriptions as SSE; the typed client over a real socket.crates/architect/tests/iroh_link.rs: vox over iroh, redial after a restart, an unreachable peer timing out.crates/architect/tests/iroh_lan.rs: dial by bare id on a LAN (needs multicast).crates/architect/tests/reconnect_supervisor.rs: re-establishing after the server dies.
Status#
Done in architect; query arguments are newer than the latest tag. Not yet
(from the HTTP design page): an HTTP face for context = T traits; query
arguments on JSON-body routes; #[subscribe] under a service path with
parameters; more than one range per request or If-Range; typed errors on
SSE; byte methods over vox.
- Task uses
LocalServer, mountsaxum_ws::serve_routeron its own routes (task:apps/server/src/lib.rs), usesiroh_link(task:apps/server/src/iroh_host.rs) and the ranged helpers on hand-written routes (task:apps/server/src/attachments/routes.rs). To adopt: the generated HTTP face and clients,ByteBodymethods,connect::pool. - Studio uses
LocalServerandiroh_link::serve_router(studio:apps/session/desktop/src/engine_server.rs); no HTTP face yet.