docs(http): add ADR-048 and websocket.md — WS carries native session, not gateway
Promote the WebSocket browser path from a section in http-server.md to a first-class spec (websocket.md) and commit the contract-pattern decision (ADR-048): a WS connection carries the native EventEnvelope call-protocol session, not the HTTP gateway shape. The gateway endpoints are HTTP-only; discovery on WS is via services/list/services/schema as ordinary call-protocol ops; subscriptions project as native call.responded events (no SSE). ADR-044 already decided WS as the v1 browser bidirectional path; ADR-048 clarifies the shape of what ADR-044 committed (§1 implies native session; the ADR makes it an explicit implementer-visible rule). The from_wss adapter (importing a remote node's ops over WS) is recorded as out-of-scope with a concrete reversal trigger so it is not re-derived later. Spec cleanup: http-server.md WS section collapsed to a stub pointer; websocket.md Why section references ADRs rather than re-arguing them; length-prefix decision made canonical (no prefix on WS — message boundary is the delimiter); default upgrade path pinned (/alknet/call) with HTTP/2 extended CONNECT noted; indexes (README, http/README, overview) updated.
This commit is contained in:
@@ -6,12 +6,13 @@ last_updated: 2026-06-30
|
||||
# HTTP Server
|
||||
|
||||
The `HttpAdapter` — the `ProtocolHandler` for `h2` and `http/1.1` (and
|
||||
WebSocket upgrade — see §"WebSocket browser path"). The `h3`/WebTransport
|
||||
WebSocket upgrade — see [websocket.md](websocket.md)). The `h3`/WebTransport
|
||||
path is deferred per [ADR-044](../../decisions/044-defer-webtransport-browsers-use-websocket.md);
|
||||
the deferred spec is at [webtransport.md](webtransport.md). This document
|
||||
covers how axum is run over a QUIC bidirectional stream, Bearer auth
|
||||
resolution, the HTTP-to-call dispatch, the `/healthz` raw route, stealth
|
||||
decoy, and the WebSocket browser path.
|
||||
decoy, and the WebSocket upgrade route (which hands off to the native
|
||||
call-protocol session specified in [websocket.md](websocket.md)).
|
||||
|
||||
## What
|
||||
|
||||
@@ -66,10 +67,10 @@ The endpoint's `HandlerRegistry` maps each ALPN byte string to the same
|
||||
adapter instance; `handle()` branches on `connection.remote_alpn()` to
|
||||
pick the HTTP framing. For `http/1.1` and `h2`, the framing is hyper's
|
||||
HTTP/1.1 or HTTP/2 over a QUIC bidirectional stream. WebSocket upgrade
|
||||
(§"WebSocket browser path") layers on top of the same hyper connection
|
||||
driver — a WS upgrade is an HTTP/1.1 or HTTP/2 request that switches
|
||||
protocols. The `h3` ALPN is deferred (ADR-044); the deferred handler
|
||||
design is at [webtransport.md](webtransport.md).
|
||||
(see [websocket.md](websocket.md)) layers on top of the same hyper
|
||||
connection driver — a WS upgrade is an HTTP/1.1 or HTTP/2 request that
|
||||
switches protocols. The `h3` ALPN is deferred (ADR-044); the deferred
|
||||
handler design is at [webtransport.md](webtransport.md).
|
||||
|
||||
## Why
|
||||
|
||||
@@ -198,7 +199,7 @@ The axum route handler:
|
||||
to descendants per ADR-016.
|
||||
|
||||
This is the HTTP/1.1 + HTTP/2 streaming projection. Over WebSocket
|
||||
(§"WebSocket browser path" below), the subscription projects directly
|
||||
([websocket.md](websocket.md)), the subscription projects directly
|
||||
onto the WS connection — `call.responded` events as binary WS messages,
|
||||
no SSE framing. WebTransport (`h3`, deferred per ADR-044) would project
|
||||
onto WebTransport bidirectional streams; see
|
||||
@@ -222,7 +223,7 @@ SSE response — but even there, the *call* is client-initiated; only the
|
||||
|
||||
This is a structural property of HTTP, not a design choice in this
|
||||
crate. **WebSocket restores the bidirectional call model for browsers**
|
||||
(see §"WebSocket browser path" below): a WS connection is a long-lived
|
||||
(see [websocket.md](websocket.md)): a WS connection is a long-lived
|
||||
full-duplex channel over which either side can send `call.requested`
|
||||
frames in either direction — the call protocol's native bidirectionality
|
||||
applies unchanged (ADR-012 — stream-agnostic correlation; a WS message
|
||||
@@ -233,67 +234,27 @@ The HTTP/1.1 + HTTP/2 surface is the projection for clients that only
|
||||
speak HTTP; WebSocket is the surface for browser clients that speak the
|
||||
call protocol in both directions.
|
||||
|
||||
### WebSocket browser path (ADR-044)
|
||||
### WebSocket browser path (ADR-044, ADR-048)
|
||||
|
||||
A browser connecting to a hub upgrades an HTTP/1.1 or HTTP/2 request to
|
||||
WebSocket (RFC 6455). The resulting full-duplex WS connection carries
|
||||
call-protocol `EventEnvelope` frames as binary WebSocket messages — one
|
||||
envelope per message. The browser authenticates by bearer token on the
|
||||
upgrade request (the HTTP `Authorization` header), resolved by the hub's
|
||||
`IdentityProvider::resolve_from_token`, same as any HTTP request. The WS
|
||||
connection is then a **bidirectional call-protocol session**:
|
||||
A browser (or any WS client) upgrades an HTTP/1.1 or HTTP/2 request to
|
||||
WebSocket (RFC 6455); the resulting full-duplex WS connection carries the
|
||||
call protocol's native `EventEnvelope` session over binary messages, and
|
||||
is the surface that **restores the call protocol's native bidirectionality
|
||||
for browsers** (unlike the one-directional HTTP projection above). The WS
|
||||
path carries the **native session, not the HTTP gateway shape** (ADR-048):
|
||||
the gateway endpoints are HTTP-only, discovery is via `services/list`/
|
||||
`services/schema` as call-protocol ops, and subscriptions project as
|
||||
native `call.responded` events (no SSE).
|
||||
|
||||
- The browser opens the WS connection to `/alknet/call` (or `/`).
|
||||
- The handler hands the WS message stream to the call protocol's
|
||||
`Dispatcher` — the same dispatch loop the `CallAdapter` uses for
|
||||
`alknet/call` QUIC connections (ADR-012, stream-agnostic correlation).
|
||||
- The browser writes `EventEnvelope` frames as binary WS messages; the
|
||||
handler reads them and dispatches via `OperationRegistry::invoke()`.
|
||||
- Responses (`call.responded`, `call.error`, `call.completed`,
|
||||
`call.aborted`) are written back as binary WS messages.
|
||||
|
||||
**Bidirectionality:** the WS call-protocol session inherits the call
|
||||
protocol's native bidirectionality — both sides can initiate calls
|
||||
(ADR-043 §2, transferred to WebSocket per ADR-044 §3). The browser calls
|
||||
operations on the hub; the hub can call operations registered on the
|
||||
browser's side, over the same session, using the same `PendingRequestMap`
|
||||
and `EventEnvelope` framing as `alknet/call`. The browser case where the
|
||||
client registers no operations of its own is the common case — the
|
||||
server→client call direction is unused because the browser has nothing
|
||||
to call. That is a use-case scoping, not an architectural limitation.
|
||||
|
||||
**No SSE translation.** A `Subscription` operation served over WebSocket
|
||||
projects its `call.responded` stream directly as binary WS messages — no
|
||||
SSE `data:` framing. `call.completed` closes the stream; `call.aborted`
|
||||
closes it with an error frame. This is the native streaming projection
|
||||
for the WS path; SSE (ADR-036) is the projection for `h2`/`http/1.1`
|
||||
clients that don't upgrade to WebSocket.
|
||||
|
||||
**Browsers are not alknet peers.** A browser over WebSocket authenticates
|
||||
by bearer token, gets no `PeerId`, does not enter `PeerCompositeEnv`, and
|
||||
its registered ops (if any) land in a connection-local Layer 2 overlay —
|
||||
the inbound mirror of ADR-034 §2. The rationale (addressability vs.
|
||||
bidirectionality) is stated in ADR-044 §5 and amends ADR-034 §4 by
|
||||
reference. In short: "peer" means an addressable node in the
|
||||
call-protocol peer graph (stable `PeerId`, `PeerRef::Specific`-reachable,
|
||||
identity stable across reconnects), not "any endpoint that exchanges
|
||||
calls during a live session." A browser is the second thing but not the
|
||||
first — it has no stable cryptographic identity of its own (it presents
|
||||
a bearer token the hub issued; nothing to pin), it is ephemeral (close
|
||||
the tab → connection dies → the connection-local overlay dies with it),
|
||||
and it is not addressable from other nodes (another alknet node has no
|
||||
way to reach "the browser currently connected to hub-A"; the hub holds
|
||||
it as a live `CallConnection` handle, not a peer-graph entry). The
|
||||
connection-local overlay is what gives the browser bidirectional-call
|
||||
capability *without* peer-graph membership.
|
||||
|
||||
**What WebSocket does not provide (deferred to WebTransport, ADR-044):**
|
||||
the ALPN-stream-proxy (ADR-040) — a browser running a WASM parser for
|
||||
SSH/SFTP/git to reach a non-call ALPN — requires WebTransport's
|
||||
multi-stream model and is the speculative use case whose deferral is
|
||||
ADR-044's reversal trigger. WebSocket carries the call protocol from a
|
||||
browser; it does not carry the non-call-ALPN substrate. A browser cannot
|
||||
reach SSH/SFTP/git ALPNs in the v1 release. See ADR-044.
|
||||
The full WS handler specification — the upgrade route, framing, dispatch
|
||||
handoff to the shared `Dispatcher`, bidirectionality, the connection-local
|
||||
Layer 2 overlay, the "browsers are not alknet peers" rationale
|
||||
(ADR-034 §4, amended by ADR-044 §5), the streaming projection, and the
|
||||
deferred `from_wss` adapter — is at [websocket.md](websocket.md).
|
||||
`h3`/WebTransport is deferred per ADR-044; the deferred handler design is
|
||||
at [webtransport.md](webtransport.md). When WebTransport revives, the two
|
||||
coexist: WS stays as the simpler call-protocol path; WebTransport adds the
|
||||
ALPN-stream-proxy path (ADR-040). Neither replaces the other.
|
||||
|
||||
### Auth
|
||||
|
||||
@@ -444,11 +405,14 @@ two-way door (add/remove freely). See
|
||||
Capabilities are used for outbound calls (`from_openapi`), never
|
||||
serialized into HTTP response bodies.
|
||||
- **`/healthz` is raw.** No auth, no call protocol. The one raw route.
|
||||
- **WebSocket is the browser bidirectional path (ADR-044).** A browser
|
||||
- **WebSocket is the browser bidirectional path (ADR-044, ADR-048).** A browser
|
||||
upgrades an HTTP request to WS and speaks the call protocol over binary
|
||||
messages. `h3`/WebTransport is deferred (ADR-044); the ALPN-stream-proxy
|
||||
(ADR-040) is not available in v1. The `h3` ALPN and its feature gate are
|
||||
not implemented in the initial release.
|
||||
messages — the **native `EventEnvelope` session, not the gateway shape**
|
||||
(the gateway endpoints are HTTP-only; discovery via `services/list`/
|
||||
`services/schema` as call-protocol ops). `h3`/WebTransport is deferred
|
||||
(ADR-044); the ALPN-stream-proxy (ADR-040) is not available in v1. The
|
||||
`h3` ALPN and its feature gate are not implemented in the initial release.
|
||||
Full WS handler spec: [websocket.md](websocket.md).
|
||||
- **Custom routes are raw HTTP, not call-protocol operations
|
||||
(ADR-046).** The assembly layer injects an `axum::Router` of extra
|
||||
routes at `HttpAdapter` construction. They are not in the
|
||||
@@ -470,8 +434,8 @@ two-way door (add/remove freely). See
|
||||
| `/healthz` is a raw route | [ADR-036](../../decisions/036-http-to-call-operation-mapping.md) | No auth, no call protocol |
|
||||
| Stealth decoy | [ADR-010](../../decisions/010-alpn-router-and-endpoint.md) | HTTP handler on standard ALPNs serves decoy for non-gateway, non-custom, non-`/healthz` paths |
|
||||
| Bearer auth via `resolve_from_token` | [ADR-004](../../decisions/004-auth-as-shared-core.md) | HTTP handler credential source (settled) |
|
||||
| WebSocket is the browser bidirectional path | [ADR-044](../../decisions/044-defer-webtransport-browsers-use-websocket.md) | Browsers upgrade to WS; `EventEnvelope` over binary messages; `h3`/WebTransport deferred |
|
||||
| Browsers are not alknet peers | [ADR-034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md) §4 (amended by ADR-044 §5) | Bearer token, no `PeerId`, connection-local overlay (addressability vs. bidirectionality) |
|
||||
| WebSocket is the browser bidirectional path | [ADR-044](../../decisions/044-defer-webtransport-browsers-use-websocket.md), [ADR-048](../../decisions/048-websocket-native-session-not-gateway.md) | Browsers upgrade to WS; `EventEnvelope` over binary messages; `h3`/WebTransport deferred. WS carries the native call-protocol session, not the gateway shape (gateway endpoints are HTTP-only). Full spec: [websocket.md](websocket.md) |
|
||||
| Browsers are not alknet peers | [ADR-034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md) §4 (amended by ADR-044 §5) | Bearer token, no `PeerId`, connection-local overlay (addressability vs. bidirectionality) — full rationale in [websocket.md](websocket.md) |
|
||||
| Error mapping (call codes → HTTP status) | [ADR-023](../../decisions/023-operation-error-schemas.md) | Protocol/operation codes distinct; `HTTP_<status>` prefix for imported |
|
||||
| Custom HTTP routes from the assembly layer | [ADR-046](../../decisions/046-assembly-layer-custom-http-routes.md) | `extra_routes: Option<Router>` at construction; raw HTTP, not operations; default surface takes precedence on collision |
|
||||
|
||||
@@ -499,6 +463,12 @@ See [open-questions.md](../../open-questions.md) for full details.
|
||||
References the `@alkdev/pubsub` WebSocket prior art (the
|
||||
`EventEnvelope { type, id, payload }` client/server the call
|
||||
protocol's envelope was derived from).
|
||||
- [ADR-048](../../decisions/048-websocket-native-session-not-gateway.md)
|
||||
— WS carries the native `EventEnvelope` call-protocol session, not the
|
||||
HTTP gateway shape; the gateway endpoints are HTTP-only.
|
||||
- [websocket.md](websocket.md) — the full WS browser path spec (framing,
|
||||
dispatch, bidirectionality, connection-local overlay, streaming
|
||||
projection, the deferred `from_wss` adapter).
|
||||
- [overview.md](overview.md) — crate overview, adapter location map
|
||||
- [webtransport.md](webtransport.md) — the deferred `h3` ALPN handler
|
||||
(kept intact for revival)
|
||||
|
||||
Reference in New Issue
Block a user