~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs

0
0
main
156 lines · 5.7 KB Raw History
# 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

```bash run
atlibs serve examples/rust/compose --port 8791 &
```

```bash run
curl -s -X POST localhost:8791/run/lookup -H 'content-type: application/json' -d '{"n":5}'; echo
```

```output
		"doubled": 10
```

Sign-in, origins and reverse proxies: [Server sessions](sessions.md).

## Add the crate

`rust/cli/Cargo.toml`

```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](../transports.md)). Outside this repo, take both by git:

```toml norun
[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/`:

```bash run=gate
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`

```rust
/// 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`

```rust
    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`

```rust
                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`

```rust
/// 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`

```rust
        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`

```rust
        // 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:

```bash run=gate
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](node.md) | Next: [Server sessions](sessions.md)