alkhttp
HTTP interface for the alk stack: serves HTTP/1.1 + HTTP/2 on standard ALPNs (with WebSocket upgrade carrying the channels protocol for browser bidirectional access) and hosts the HTTP-backed call-protocol adapters.
alkhttp wraps alkcall — the pure
call + channels protocol crate — and turns its operation registry into
an HTTP surface. It is the HTTP server host (a ProtocolHandler for the
IANA h2/http/1.1 ALPNs) and the HTTP client host (the import
adapters that call out over reqwest) in one crate.
What's inside
Server side (server feature) — the producer-facing HTTP surface:
HttpAdapter— an axumRouterdriven by hyper's HTTP/1.1 and HTTP/2 connection handling over aBiStream, served on standard ALPNs so any HTTP client connects without knowing about the alk stack.- Six fixed gateway endpoints — the sole HTTP invoke path (no
per-operation REST tree):
GET /search,GET /schema?name=…,POST /call,POST /batch,POST /subscribe(SSE),POST /publish(NDJSON). /healthz,/openapi.json(ato_openapiprojection of the local registry), and/mcp(the MCP tool gateway, featuremcp).- WebSocket upgrade on
/alk/channelscarrying the channels protocol: 8-byte chunk multiplexing with channel 0 pre-negotiated asalk/call, so a browser runs the native bidirectional call-protocol session. - Bearer auth, a stealth decoy surface for unknown paths, custom-route mounting, and assembly hooks for WS session caps and timeouts.
Client side (client feature) — the consumer-facing import
adapters, each producing ordinary HandlerRegistration bundles for an
alkcall registry:
from_openapi— import an external HTTP API described by an OpenAPI document (JSON or YAML), forwarding calls over the shared reqwest client.from_jsonschema— import one non-OpenAPI HTTP endpoint behind a caller-builtOperationSpec.from_mcp— import a remote MCP server's tools as operations over streamable HTTP (featuremcp).from_wss— import a remote alk node's operations over a WSS channels connection (featurewss).SharedHttpClient— the hot-reloadable reqwest stack allfrom_*forwarding rides: same-host-only redirects, idempotent-only retries with a wall-clock budget,Retry-Aftersupport, TLS config, and streaming byte caps.
Both sides (openapi feature, implied) — the shared OpenAPI
document model plus the reverse projections to_openapi (generate the
gateway's OpenAPI document from the registry) and to_mcp (expose local
operations as MCP tools).
Quick start
Serve the gateway over HTTP
HttpAdapter implements alkcall's ProtocolHandler; the endpoint /
accept loop is the consumer's concern (dial and TLS live downstream).
Wire it for the ALPN you serve and drive it with your connection source:
use Arc;
use IdentityProvider;
use Connection;
use OperationRegistry;
use ;
let registry = new;
let provider: = /* your identity provider */;
let adapter = h2
.with_decoy;
// adapter.alpn() == b"h2" — register it in your handler registry for
// that ALPN; each accepted Connection is handled by
// ProtocolHandler::handle(&adapter, connection, &auth).
Once wired, HTTP callers get the fixed gateway surface. Discovery is
per-caller, filtered by AccessControl:
GET /search → { "request_id": …, "result": "ok",
"output": { "operations": [ … ] } }
GET /schema?name=fs/readFile → the operation's input/output schema
POST /call ← { "operation": "fs/readFile",
"input": { … } }
POST /batch ← [ { "operation": …, "input": … }, … ]
POST /subscribe → SSE stream of call-protocol envelopes
POST /publish ← NDJSON body of published chunks
/openapi.json serves the same surface as an OpenAPI document, so
standard tooling can drive the gateway without knowing the alk stack.
Import an OpenAPI-described API
use HashMap;
use Arc;
use ;
use ;
let doc = r#"{"openapi":"3.0.0","info":{"title":"Widgets","version":"1"},
"paths":{"/widgets":{"get":{"operationId":"listWidgets",
"responses":{"200":{"content":{"application/json":{"schema":{}}}}}}}}}"#;
let spec = from_json?;
let config = HttpServiceConfig ;
let http_client = new;
let adapter = new;
let registrations = import.await?;
let mut registry = new;
for reg in registrations
// "widgets/listWidgets" is now callable — and composable — through the
// call protocol and the gateway.
Imported operations register Visibility::Internal (they are
composition material); an External facade op composed over them via
ctx.env.invoke is what the wire sees.
WebSocket for browsers
A browser upgrades GET /alk/channels (Bearer token in the
Authorization header) to a WebSocket and speaks the channels protocol
in binary messages: an 8-byte chunk header multiplexes N channels;
channel 0 is pre-negotiated as alk/call and runs the native
call-protocol session. Both sides can initiate calls; the browser can
also register operations and open channels — it is a full consumer and
producer on its own connection-local overlay.
Feature flags
The crate is feature-sided — build one side, or both:
| Feature | Default | Gates |
|---|---|---|
server |
yes | the axum host, gateway routes, WS upgrade, /healthz, /openapi.json, to_openapi |
client |
yes | the shared outbound client host, from_openapi, from_jsonschema |
openapi |
(implied) | the shared OpenAPI document model (implied by both sides) |
mcp |
no | from_mcp (needs client) and to_mcp (needs server) |
wss |
no | the from_wss consumer adapter |
h2, http1 |
yes | the server's HTTP protocol features (imply server) |
A lean single-side build takes default-features = false plus the side
it needs:
= { = "0.4", = false, = ["server"] }
Security posture
- No secret material on the wire. Request/response payloads and
headers carry no keys or tokens beyond the caller's own Bearer
credential. Outbound credentials flow vault → assembly layer →
Capabilities→ handler; thefrom_*adapters are the injection point, and no handler readsstd::env::var. - Stealth mode. The HTTP surface serves on standard ALPNs, and unregistered paths answer with a configurable decoy (fake nginx 404, static site, or redirect) instead of advertising the gateway.
- Tight forwarding defaults. Same-host-only redirects,
idempotent-only retries with a wall-clock budget and
Retry-Afterceiling, 30 s request / 10 s connect / 30 s read timeouts, a 2 MiB gateway body cap (8 MiB on/mcp), and a 1 GiB streamed-bytes cap per subscription. - Internal-by-default imports. Adapter-registered operations are
invisible to direct wire calls (
NOT_FOUND) until composed behind an External facade.
Documentation
- Architecture docs — the authoritative spec: ADRs 001–071, component documents, and open questions.
- API docs — full crate documentation on docs.rs.
- Changelog
License
MIT OR Apache-2.0 — see LICENSE-MIT and LICENSE-APACHE.