Status: implemented by kabel.remote on the JVM and in ClojureScript. This
document is the language-neutral description of the frames, so that a client
in another language can invoke functions on a Kabel peer, or serve them, without
reading the Clojure implementation.
A peer serves named functions. A connected peer invokes one by name with an argument map and receives one result, or one error, on the connection the request travelled on. The protocol is request and response over an ordered, bidirectional Kabel connection. It carries no streaming and no cancellation; both are layered above it by the application when needed.
Every frame is a map with a :type key. Frames of other types are not part of
this protocol and pass through untouched, so the middleware composes with
pub/sub, authentication, and application traffic on one connection.
| Frame | Direction | Fields |
|---|---|---|
:kabel.remote/register | both, once, at connection start | :scope the sender's peer id |
:kabel.remote/invoke | requester to server | :scope the target peer id, :request-scope the requester's peer id, :fn-name, :arg-map, :request-id |
:kabel.remote/result | server to requester | :scope the requester's peer id, :request-id, and exactly one of :result or :error |
Field types:
:scope, :request-scope: a peer id. Kabel peer ids are UUIDs.:fn-name: a namespaced symbol or a string. A symbol travels as a CBOR
symbol on the CBOR wire; a client in a language without symbols SHOULD send
the string form "namespace/name" and a server MUST accept both spellings as
the same name.:arg-map: a map with keyword keys. Values are anything the wire codec
encodes.:request-id: a value unique per requester and connection, correlated
verbatim in the result. The reference implementation sends a UUID.:result: any encodable value, including nil.:error: a map {:message string, :type keyword?, :fn-name, :data string?}.
:data is the printed representation of the server's exception data, for
diagnostics; it is never interpreted.:kabel.remote/register with its own peer
id. A side that has not received the other's registration MUST NOT send an
invoke on that connection; a requester waits for it.:kabel.remote/invoke. :scope names the peer that
should run the function. A peer that receives an invoke whose :scope is
not its own id MUST answer with an error; it MUST NOT forward it.:kabel.remote/result for every invoke
it received, on the connection the invoke arrived on, whether or not the
function ran.Invocations are concurrent: a server MAY run several at once and MAY answer
them out of order. The requester correlates by :request-id. A served
function must not block the thread it is invoked on: in the reference
implementation it runs inside a core.async go block, and blocking work is
offloaded to a thread whose channel the function returns.
A result carries :error when the function did not produce a value. The
:type values the reference implementation uses:
:type | Meaning |
|---|---|
:kabel.remote/unknown-function | no function is registered under :fn-name |
:kabel.remote/authentication-required | the authorization gate denied the call and the connection has no principal |
:kabel.remote/not-authorized | the gate denied the call for the connection's principal |
:kabel.remote/not-serving | the peer runs the middleware but is not serving functions |
:kabel.remote/wrong-peer | the invoke's :scope is not the receiving peer's id |
| an application keyword | the function threw an exception whose data carried this :type |
| absent | the function threw an exception without a typed data map |
The requester side reports two more without any frame:
:kabel.remote/disconnected when the connection closed before the result
arrived, and :kabel.remote/timeout when the caller's own deadline passed. A
disconnected call may or may not have run; a caller that retries a
non-idempotent function needs its own idempotency key in the argument map.
The protocol carries no credentials. When the connection is authenticated
(kabel.auth.websocket), the server's auth middleware stamps the principal on
every inbound frame, and the serving side consults its authorization gate with
the principal, the function name and the argument map before the function
runs. The function then receives the principal under :kabel/principal in its
argument map. A frame's own :kabel/principal key, if a requester sends one,
is overwritten by the server's auth middleware and never trusted.
Before this document the same frames travelled under the types
:is.simm.distributed-scope/register-scope, :is.simm.distributed-scope/invoke
and :is.simm.distributed-scope/invoke-result, with the same fields, and
:error as the printed exception string. A peer that receives a registration
in that dialect answers, and registers itself, in that dialect on that
connection. New implementations MUST send the :kabel.remote/* types and
SHOULD accept the old ones.
The frames are ordinary Kabel messages and travel in whatever codec the connection negotiated. For a client outside Clojure the CBOR wire (serializer id 14) is the intended binding; symbols and keywords are CBOR tagged values as documented for that wire. Frame size is bounded by Kabel's message limit.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |