Rust servers
rust/host (crate atlibs-host) runs the same modules, step protocol, compositions and state dir as
the JS hosts, with wasmtime. The atlibs CLI (rust/cli) is built on it, and atlibs serve is a
ready-made server; this page shows the pieces to embed in your own.
Serve modules without writing a server
atlibs serve examples/rust/compose --port 8791 &
curl -s -X POST localhost:8791/run/lookup -H 'content-type: application/json' -d '{"n":5}'; echo
"doubled": 10
Sign-in, origins and reverse proxies: Server sessions.
Add the crate
rust/cli/Cargo.toml
[dependencies]
atlibs-host = { path = "../host" }
atlibs-iroh = { git = "https://knotless.dev/johnandersen777.bsky.social/atlibs-iroh", optional = true }
atlibs-host has no transport in it; atlibs-iroh is one, from its own repo behind a cargo feature
(Transports). Outside this repo, take both by git:
[dependencies]
atlibs-host = { git = "https://knotless.dev/johnandersen777.bsky.social/atlibs" }
atlibs-iroh = { git = "https://knotless.dev/johnandersen777.bsky.social/atlibs-iroh" }
Build and test everything in rust/:
cargo host # the CLI, into rust/host/target
cargo host-test # atlibs-host's and the CLI's tests
Choose who runs act as
SessionConfig is explicit: an app password or a saved OAuth session. Nothing is read from the
environment, and no config means signed out.
rust/host/src/host.rs
/// Who a session signs in as: an explicit choice, never read from the environment.
/// No config, no session.
#[derive(Clone, Debug)]
pub enum SessionConfig {
/// The simple / testing case: an app password at `service` (an entryway or PDS). The password may
/// be None when the state dir holds this identifier's saved session.
AppPassword { identifier: String, password: Option<String>, service: String },
/// A user's OAuth session, saved in the state dir by `atlibs login` or a server's /oauth/callback.
OAuth { did: String },
}
Run a module
A module's call is HostCall (XRPC with the session) wrapped in ScopeCall when signed in, so the
run makes only the calls its own session record's scope allows.
rust/host/src/registry.rs
let mut call: Arc<dyn Call> = Arc::new(HostCall { fetch: fetch.clone(), session: session.clone(), attest });
if session.is_some() {
let key = module.key.clone().unwrap_or_else(|| name.to_string());
call = Arc::new(ScopeCall { module: key, session: module.scope.clone(), inner: call });
}
Ctx holds the wasm runner and the records ledger; run_module drives the step loop and returns the
same { entry, result, error?, events, calls } JSON as the JS host.
rust/cli/src/run.rs
let call = self.call_for(module, module.id.as_deref().unwrap_or_default()).await?;
let ctx = Ctx { runner: Arc::new(Bound { wasm: self.wasm.clone(), bytes: module.bytes.clone(), sha256: module.sha256.clone() }), ledger, now: None };
let options = Options { load: Some(self.hook_loader(module, pinned.clone())), ..options };
Ok(run_module(&ctx, module, entry, &with_did, call, options).await)
Compositions go through compose::run_compose the same way, with a call_for per instance;
compose::check_specs runs the load checks from module.json fields alone (atlibs compose check).
Serve client metadata
ClientMetadataHandler::from_stage(&stage) takes an atlibs stage --target server stage.json and
answers GET /oauth/<session>/client-metadata.json with any http::Request; anything else is None.
rust/host/src/oauth.rs
/// Serves `<path><session>/client-metadata.json` for each record, the client_id being that URL on
/// `origin` (on a loopback Host, the request's origin when `origin` is unset). With `name`,
/// `<path><name>/client-metadata.json` is the composite of all of them.
#[derive(Clone, Debug, Default)]
pub struct ClientMetadataHandler {
rust/cli/src/serve.rs
if let Some(res) = self.oauth.handle(&req) {
One session per signed-in user
Each request carries its user's session handle; the server opens that user's session and runs with it alone.
rust/cli/src/serve.rs
// who this run acts as: the request's signed-in user (their session alone), else the server's own (--as / --app-password)
let host = match handle_of(&req) {
None => self.host.clone(),
Some(handle) => {
let did = self.cli.store().and_then(|store| oauth_client::did_of_handle(store, handle));
let session = match did {
Ok(Some(did)) => self.session(&did).await,
Ok(None) => Err("unknown or expired session: sign in again (GET /oauth/login?handle=...)".into()),
Err(e) => Err(e),
};
match session {
Ok(session) => Host { session: Some(session), ..self.host.clone() },
Err(e) => return json_reply(401, &json!({ "error": "AuthRequired", "message": e })),
}
}
};
Same results as the JS host
A conformance suite runs the same modules, inputs and call stubs through both hosts and compares results, call logs and ledgers. It runs JS tests against the Rust host too:
node --test rust/host/conformance.test.mjs
node --import ./rust/host/swap.mjs --test javascript/src/run-module/*.test.mjs examples/rust/*/test.mjs javascript/src/compose/compose.test.mjs javascript/src/server/server.test.mjs
Previous: Node servers | Next: Server sessions