Dispatchers (middleware)
The dispatcher is the provider side of a protocol: given a call and its
encoded params, it runs your handler and records the outcome. The generated
<Proto>Dispatcher implements the runtime's Dispatch trait, and the server
is generic over it, so you can wrap it. A wrapper that delegates to the
generated one can log, authorise, meter or reject a call before or after it.
It changes nothing on the wire, so it needs no name, profile or declaration. There is no framework for it: a wrapper is a type that implements the same trait.
Implementing
In Rust
use comline_runtime::contract::{Dispatch, Kind, Peer, Reply, RuntimeError, WireFormat};
/// Logs each call, then hands it on.
pub struct Logged<D>(pub D);
impl<D: Dispatch> Dispatch for Logged<D> {
fn calls(&self) -> &'static [&'static str] {
self.0.calls()
}
fn dispatch<W: WireFormat>(
&self,
call: Kind,
params: &[u8],
format: &W,
reply: &mut Reply,
) -> Result<(), RuntimeError> {
eprintln!("call {call:?}");
let result = self.0.dispatch(call, params, format, reply);
eprintln!("-> {:?}", reply.outcome());
result
}
fn dispatch_with<P: Peer>(
&self,
call: Kind,
params: &[u8],
reply: &mut Reply,
peer: &mut P,
) -> Result<(), RuntimeError> {
eprintln!("call {call:?}");
let result = self.0.dispatch_with(call, params, reply, peer);
eprintln!("-> {:?}", reply.outcome());
result
}
}
callsis the protocol's function names in declaration order. Delegate it.dispatchserves a call on a client-and-server pair.paramsis still encoded;replyis where the result is recorded.dispatch_withserves a call on aConnection, with a handle to the peer that made it, so a handler can call back. Forward both. The defaultdispatch_withcallsdispatch, and a generated dispatcher for a protocol on a connection answersdispatchwith a fault and does its work indispatch_with. A wrapper that forwards onlydispatchwould fault every call.reply.outcome()tells you what was recorded:Outcome::Ok,Outcome::Err(ordinal),Outcome::Fault, orOutcome::Nonefor a one-way call.- To reject a call, don't delegate. Call
reply.fault()and returnOk(()); the caller sees a provider fault and no detail crosses the wire. To refuse with a schema error, you would have to encode the error body yourself, so most wrappers fault.
Wire it where you hand the dispatcher over:
let dispatcher = Logged(FooDispatcher(my_impl));
// A client-and-server pair:
Server::new(dispatcher, format).serve(&mut transport, IR_HASH)?;
// A connection:
conn.provide(FooDispatcher::<MyImpl>::PROTOCOL, dispatcher);
The generated serve and provide_on helpers sit on the generated dispatcher
and build the server themselves, so a wrapped dispatcher goes through
Server::new (or Server::with_framing) and Connection::provide as above.
If the protocol declares a handshake other than comline@1, finish with
serve_with and that handshake instead of serve
(Handshakes).
Async
For an AsyncConnection, wrap AsyncDispatch. The trait is
Send + Sync + 'static, and its dispatch returns a future:
fn dispatch<'a, P: AsyncPeer>(
&'a self,
call: Kind,
params: &'a [u8],
peer: &'a P,
) -> impl Future<Output = Result<Answer, RuntimeError>> + Send + 'a;
An async wrapper delegates the same way, awaits the inner call, and inspects
or replaces the Answer. Register it with AsyncConnection::provide.
In TypeScript
Dispatch is an interface:
interface Dispatch {
calls(): readonly string[];
dispatch(call: Kind, params: Uint8Array, codec: Codec, reply: Reply): Promise<void>;
dispatchWith?(call: Kind, params: Uint8Array, reply: Reply, peer: Peer): Promise<void>;
}
A wrapper holds the generated dispatcher and forwards to it. dispatchWith is
optional: without it, a connection calls dispatch with the peer's codec. If
the inner dispatcher has one, forward it too.
Give the wrapper to Server where you would give the generated dispatcher, or
to Connection.provide(protocol, dispatcher).
What a wrapper can't do
- It sees
paramsas bytes. To look inside, decode them with the format. - It runs only for calls this side serves. Calls you make as a client go through the client, not the dispatcher.
- A helper for composing wrappers is a possible runtime addition; none exists today.
Becoming official
No instructions yet.