Skip to main content

workflow_rpc/server/
mod.rs

1//!
2//! RPC server module (native only). This module encapsulates
3//! server-side types used to create an RPC server: [`RpcServer`],
4//! [`RpcHandler`], [`Messenger`], [`Interface`] and the
5//! protocol handlers: [`BorshProtocol`] and [`JsonProtocol`].
6//!
7
8pub mod error;
9mod interface;
10pub mod prelude;
11pub mod protocol;
12pub mod result;
13
14pub use super::error::*;
15pub use crate::encoding::Encoding;
16use crate::imports::*;
17pub use interface::{Interface, Method, Notification};
18pub use protocol::{BorshProtocol, JsonProtocol, ProtocolHandler};
19pub use std::net::SocketAddr;
20pub use tokio::sync::mpsc::UnboundedSender as TokioUnboundedSender;
21pub use workflow_core::task::spawn;
22pub use workflow_websocket::server::{
23    Error as WebSocketError, Message, Result as WebSocketResult, TcpListener, WebSocketConfig,
24    WebSocketCounters, WebSocketHandler, WebSocketReceiver, WebSocketSender, WebSocketServer,
25    WebSocketServerTrait, WebSocketSink,
26};
27pub mod handshake {
28    //! WebSocket handshake helpers
29    pub use workflow_websocket::server::handshake::*;
30}
31use crate::server::result::Result;
32
33///
34/// method!() macro for declaration of RPC method handlers
35///
36/// This macro simplifies creation of async method handler
37/// closures supplied to the RPC dispatch interface. An
38/// async method closure requires to be *Box*ed
39/// and its result must be *Pin*ned, resulting in the following
40/// syntax:
41///
42/// ```ignore
43///
44/// interface.method(Box::new(MyOps::Method, Method::new(|req: MyReq|
45///     Box::pin(
46///         async move {
47///             // ...
48///             Ok(MyResp { })
49///         }
50///     )
51/// )))
52///
53/// ```
54///
55/// The method macro adds the required Box and Pin syntax,
56/// simplifying the declaration as follows:
57///
58/// ```ignore
59/// interface.method(MyOps::Method, method!(
60///   | connection_ctx: ConnectionCtx,
61///     server_ctx: ServerContext,
62///     req: MyReq |
63/// async move {
64///     // ...
65///     Ok(MyResp { })
66/// }))
67/// ```
68///
69pub use workflow_rpc_macros::server_method as method;
70
71///
72/// notification!() macro for declaration of RPC notification handlers
73///
74/// This macro simplifies creation of async notification handler
75/// closures supplied to the RPC notification interface. An
76/// async notification closure requires to be *Box*ed
77/// and its result must be *Pin*ned, resulting in the following
78/// syntax:
79///
80/// ```ignore
81///
82/// interface.notification(MyOps::Notify,Box::new(Notification::new(|msg: MyMsg|
83///     Box::pin(
84///         async move {
85///             // ...
86///             Ok(())
87///         }
88///     )
89/// )))
90///
91/// ```
92///
93/// The notification macro adds the required Box and Pin syntax,
94/// simplifying the declaration as follows:
95///
96/// ```ignore
97/// interface.notification(MyOps::Notify, notification!(|msg: MyMsg| async move {
98///     // ...
99///     Ok(())
100/// }))
101/// ```
102///
103pub use workflow_rpc_macros::server_notification as notification;
104
105/// A basic example RpcContext, can be used to keep track of
106/// connected peers.
107#[derive(Debug, Clone)]
108pub struct RpcContext {
109    /// Network address of the connected peer.
110    pub peer: SocketAddr,
111}
112
113/// [`RpcHandler`] - a server-side event handler for RPC connections.
114#[async_trait]
115pub trait RpcHandler: Send + Sync + 'static {
116    /// Per-connection context produced during the handshake and supplied to
117    /// all subsequent RPC calls from that connection.
118    type Context: Send + Sync;
119
120    /// Called to determine if the connection should be accepted.
121    fn accept(&self, _peer: &SocketAddr) -> bool {
122        true
123    }
124
125    /// Connection notification - issued when the server has opened a WebSocket
126    /// connection, before any other interactions occur.  The supplied argument
127    /// is the [`SocketAddr`] of the incoming connection. This function should
128    /// return [`WebSocketResult::Ok`] if the server accepts connection or
129    /// [`WebSocketError`] if the connection is rejected. This function can
130    /// be used to reject connections based on a ban list.
131    async fn connect(self: Arc<Self>, _peer: &SocketAddr) -> WebSocketResult<()> {
132        Ok(())
133    }
134
135    /// [`RpcHandler::handshake()`] is called right after [`RpcHandler::connect()`]
136    /// and is provided with a [`WebSocketSender`] and [`WebSocketReceiver`] channels
137    /// which can be used to communicate with the underlying WebSocket connection
138    /// to negotiate a connection. The function also receives the `&peer` ([`SocketAddr`])
139    /// of the connection and a [`Messenger`] struct.  The [`Messenger`] struct can
140    /// be used to post notifications to the given connection as well as to close it.
141    /// If negotiation is successful, this function should return a `ConnectionContext`
142    /// defined as [`Self::Context`]. This context will be supplied to all subsequent
143    /// RPC calls received from this connection. The [`Messenger`] struct can be
144    /// cloned and captured within the `ConnectionContext`. This allows an RPC
145    /// method handler to later capture and post notifications to the connection
146    /// asynchronously.
147    async fn handshake(
148        self: Arc<Self>,
149        peer: &SocketAddr,
150        sender: &mut WebSocketSender,
151        receiver: &mut WebSocketReceiver,
152        messenger: Arc<Messenger>,
153    ) -> WebSocketResult<Self::Context>;
154
155    /// Disconnect notification, receives the context and the result containing
156    /// the disconnection reason (can be success if the connection is closed gracefully)
157    async fn disconnect(self: Arc<Self>, _ctx: Self::Context, _result: WebSocketResult<()>) {}
158}
159
160///
161/// The [`Messenger`] struct is supplied to the [`RpcHandler::handshake()`] call at
162/// the connection negotiation time. This structure comes in as [`Arc<Messenger>`]
163/// and can be retained for later processing. It provides two methods: [`Messenger::notify`]
164/// that can be used asynchronously to dispatch RPC notifications to the client
165/// and [`Messenger::close`] that can be used to terminate the RPC connection with
166/// the client.
167///
168#[derive(Debug)]
169pub struct Messenger {
170    encoding: Encoding,
171    sink: WebSocketSink,
172}
173
174impl Messenger {
175    /// Create a new messenger for a connection, bound to the given encoding
176    /// and WebSocket sink used to dispatch outgoing messages.
177    pub fn new(encoding: Encoding, sink: &WebSocketSink) -> Self {
178        Self {
179            encoding,
180            sink: sink.clone(),
181        }
182    }
183
184    /// Close the WebSocket connection. The server checks for the connection channel
185    /// for the dispatch of this message and relays it to the client as well as
186    /// proactively terminates the connection.
187    pub fn close(&self) -> Result<()> {
188        self.sink.send(Message::Close(None))?;
189        Ok(())
190    }
191
192    /// Post notification message to the WebSocket connection
193    pub async fn notify<Ops, Msg>(&self, op: Ops, msg: Msg) -> Result<()>
194    where
195        Ops: OpsT,
196        Msg: BorshSerialize + BorshDeserialize + Serialize + Send + Sync + 'static,
197    {
198        match self.encoding {
199            Encoding::Borsh => {
200                self.sink
201                    .send(protocol::borsh::create_serialized_notification_message(
202                        op, msg,
203                    )?)?;
204            }
205            Encoding::SerdeJson => {
206                self.sink
207                    .send(protocol::serde_json::create_serialized_notification_message(op, msg)?)?;
208            }
209        }
210
211        Ok(())
212    }
213
214    /// Serialize message into a [`tungstenite::Message`] for direct websocket delivery.
215    /// Once serialized it can be relayed using [`Messenger::send_raw_message()`].
216    pub fn serialize_notification_message<Ops, Msg>(
217        &self,
218        op: Ops,
219        msg: Msg,
220    ) -> Result<tungstenite::Message>
221    where
222        Ops: OpsT,
223        Msg: MsgT,
224    {
225        match self.encoding {
226            Encoding::Borsh => Ok(protocol::borsh::create_serialized_notification_message(
227                op, msg,
228            )?),
229            Encoding::SerdeJson => {
230                Ok(protocol::serde_json::create_serialized_notification_message(op, msg)?)
231            }
232        }
233    }
234
235    /// Send a raw [`tungstenite::Message`] via the websocket tokio channel.
236    pub fn send_raw_message(&self, msg: tungstenite::Message) -> Result<()> {
237        self.sink.send(msg)?;
238        Ok(())
239    }
240
241    /// Provides direct access to the underlying tokio channel.
242    pub fn sink(&self) -> &WebSocketSink {
243        &self.sink
244    }
245
246    /// Get encoding of the current messenger.
247    pub fn encoding(&self) -> Encoding {
248        self.encoding
249    }
250}
251
252/// WebSocket processor in charge of managing
253/// WRPC Request/Response interactions.
254#[derive(Clone)]
255struct RpcWebSocketHandler<ServerContext, ConnectionContext, Protocol, Ops>
256where
257    Ops: OpsT,
258    ServerContext: Clone + Send + Sync + 'static,
259    ConnectionContext: Clone + Send + Sync + 'static,
260    Protocol: ProtocolHandler<ServerContext, ConnectionContext, Ops> + Send + Sync + 'static,
261{
262    rpc_handler: Arc<dyn RpcHandler<Context = ConnectionContext>>,
263    protocol: Arc<Protocol>,
264    enable_async_handling: bool,
265    _server_ctx: PhantomData<ServerContext>,
266    _ops: PhantomData<Ops>,
267}
268
269impl<ServerContext, ConnectionContext, Protocol, Ops>
270    RpcWebSocketHandler<ServerContext, ConnectionContext, Protocol, Ops>
271where
272    Ops: OpsT,
273    ServerContext: Clone + Send + Sync + 'static,
274    ConnectionContext: Clone + Send + Sync + 'static,
275    Protocol: ProtocolHandler<ServerContext, ConnectionContext, Ops> + Send + Sync + 'static,
276{
277    pub fn new(
278        rpc_handler: Arc<dyn RpcHandler<Context = ConnectionContext>>,
279        interface: Arc<Interface<ServerContext, ConnectionContext, Ops>>,
280        enable_async_handling: bool,
281    ) -> Self {
282        let protocol = Arc::new(Protocol::new(interface));
283        Self {
284            rpc_handler,
285            protocol,
286            enable_async_handling,
287            _server_ctx: PhantomData,
288            _ops: PhantomData,
289        }
290    }
291}
292
293#[async_trait]
294impl<ServerContext, ConnectionContext, Protocol, Ops> WebSocketHandler
295    for RpcWebSocketHandler<ServerContext, ConnectionContext, Protocol, Ops>
296where
297    Ops: OpsT,
298    ServerContext: Clone + Send + Sync + 'static,
299    ConnectionContext: Clone + Send + Sync + 'static,
300    Protocol: ProtocolHandler<ServerContext, ConnectionContext, Ops> + Send + Sync + 'static,
301{
302    type Context = ConnectionContext;
303
304    fn accept(&self, peer: &SocketAddr) -> bool {
305        self.rpc_handler.accept(peer)
306    }
307
308    async fn connect(self: &Arc<Self>, peer: &SocketAddr) -> WebSocketResult<()> {
309        self.rpc_handler.clone().connect(peer).await
310    }
311
312    async fn disconnect(self: &Arc<Self>, ctx: Self::Context, result: WebSocketResult<()>) {
313        self.rpc_handler.clone().disconnect(ctx, result).await
314    }
315
316    async fn handshake(
317        self: &Arc<Self>,
318        peer: &SocketAddr,
319        sender: &mut WebSocketSender,
320        receiver: &mut WebSocketReceiver,
321        sink: &WebSocketSink,
322    ) -> WebSocketResult<Self::Context> {
323        let messenger = Arc::new(Messenger::new(self.protocol.encoding(), sink));
324
325        self.rpc_handler
326            .clone()
327            .handshake(peer, sender, receiver, messenger)
328            .await
329    }
330
331    async fn message(
332        self: &Arc<Self>,
333        connection_ctx: &Self::Context,
334        msg: Message,
335        sink: &WebSocketSink,
336    ) -> WebSocketResult<()> {
337        let connection_ctx = (*connection_ctx).clone();
338        if self.enable_async_handling {
339            let sink = sink.clone();
340            let this = self.clone();
341            spawn(async move {
342                this.protocol
343                    .handle_message(connection_ctx, msg, &sink)
344                    .await
345            });
346            Ok(())
347        } else {
348            self.protocol
349                .handle_message(connection_ctx, msg, sink)
350                .await
351        }
352    }
353}
354
355/// [`RpcServer`] - a server-side object that listens
356/// for incoming websocket connections and delegates interaction
357/// with them to the supplied interfaces: [`RpcHandler`] (for RPC server
358/// management) and [`Interface`] (for method and notification dispatch).
359#[derive(Clone)]
360pub struct RpcServer {
361    ws_server: Arc<dyn WebSocketServerTrait>,
362}
363
364impl RpcServer {
365    /// Create a new [`RpcServer`] supplying an [`Arc`] of the previously-created
366    /// [`RpcHandler`] trait and the [`Interface`] struct.
367    /// This method takes 4 generics:
368    /// - `ConnectionContext`: a struct used as [`RpcHandler::Context`] to
369    ///   represent the connection. This struct is passed to each RPC method
370    ///   and notification call.
371    /// - `ServerContext`: a struct supplied to the [`Interface`] at the
372    ///   Interface creation time. This struct is passed to each RPC method
373    ///   and notification call.
374    /// - `Protocol`: A protocol type used for the RPC message serialization
375    ///   and deserialization (this can be omitted by using [`RpcServer::new_with_encoding`])
376    /// - `Ops`: A data type (index or an `enum`) representing the RPC method
377    ///   or notification.
378    pub fn new<ServerContext, ConnectionContext, Protocol, Ops>(
379        rpc_handler: Arc<dyn RpcHandler<Context = ConnectionContext>>,
380        interface: Arc<Interface<ServerContext, ConnectionContext, Ops>>,
381        counters: Option<Arc<WebSocketCounters>>,
382        enable_async_handling: bool,
383    ) -> RpcServer
384    where
385        ServerContext: Clone + Send + Sync + 'static,
386        ConnectionContext: Clone + Send + Sync + 'static,
387        Protocol: ProtocolHandler<ServerContext, ConnectionContext, Ops> + Send + Sync + 'static,
388        Ops: OpsT,
389    {
390        let ws_handler = Arc::new(RpcWebSocketHandler::<
391            ServerContext,
392            ConnectionContext,
393            Protocol,
394            Ops,
395        >::new(rpc_handler, interface, enable_async_handling));
396
397        let ws_server = WebSocketServer::new(ws_handler, counters);
398        RpcServer { ws_server }
399    }
400    /// Create a new [`RpcServer`] supplying an [`Arc`] of the previously-created
401    /// [`RpcHandler`] trait and the [`Interface`] struct.
402    /// This method takes 4 generics:
403    /// - `ConnectionContext`: a struct used as [`RpcHandler::Context`] to
404    ///   represent the connection. This struct is passed to each RPC method
405    ///   and notification call.
406    /// - `ServerContext`: a struct supplied to the [`Interface`] at the
407    ///   Interface creation time. This struct is passed to each RPC method
408    ///   and notification call.
409    /// - `Ops`: A data type (index or an `enum`) representing the RPC method
410    ///   or notification.
411    /// - `Id`: A data type representing a message `Id` - this type must implement
412    ///   the [`id::Generator`](crate::id::Generator) trait. Implementation for default
413    ///   Ids such as [`Id32`] and [`Id64`] can be found in the [`id`](crate::id) module.
414    ///
415    /// This function call receives an `encoding`: [`Encoding`] argument containing
416    /// [`Encoding::Borsh`] or [`Encoding::SerdeJson`], based on which it will
417    /// instantiate the corresponding protocol handler ([`BorshProtocol`] or
418    /// [`JsonProtocol`] respectively).
419    ///
420    /// `enable_async_handling` is a boolean flag that determines if the server
421    /// should spawn a new async task for each incoming message. If set to `false`,
422    /// the server will handle message intake synchronously where each message
423    /// is posted to the underlying handler one-at-a-time. (i.e. RPC awaits for the
424    /// message intake processing to be complete before the next message arrives).
425    /// If `true`, each message is dispatched via a new async task.
426    ///
427    pub fn new_with_encoding<ServerContext, ConnectionContext, Ops, Id>(
428        encoding: Encoding,
429        rpc_handler: Arc<dyn RpcHandler<Context = ConnectionContext>>,
430        interface: Arc<Interface<ServerContext, ConnectionContext, Ops>>,
431        counters: Option<Arc<WebSocketCounters>>,
432        enable_async_handling: bool,
433    ) -> RpcServer
434    where
435        ServerContext: Clone + Send + Sync + 'static,
436        ConnectionContext: Clone + Send + Sync + 'static,
437        Ops: OpsT,
438        Id: IdT,
439    {
440        match encoding {
441            Encoding::Borsh => {
442                RpcServer::new::<
443                    ServerContext,
444                    ConnectionContext,
445                    BorshProtocol<ServerContext, ConnectionContext, Ops, Id>,
446                    Ops,
447                >(rpc_handler, interface, counters, enable_async_handling)
448            }
449            Encoding::SerdeJson => {
450                RpcServer::new::<
451                    ServerContext,
452                    ConnectionContext,
453                    JsonProtocol<ServerContext, ConnectionContext, Ops, Id>,
454                    Ops,
455                >(rpc_handler, interface, counters, enable_async_handling)
456            }
457        }
458    }
459
460    /// Bind network interface address to the `TcpListener`
461    pub async fn bind(&self, addr: &str) -> WebSocketResult<TcpListener> {
462        let addr = addr.replace("wrpc://", "");
463        self.ws_server.clone().bind(&addr).await
464    }
465
466    /// Start listening for incoming RPC connections on the supplied `TcpListener`
467    pub async fn listen(
468        &self,
469        listener: TcpListener,
470        config: Option<WebSocketConfig>,
471    ) -> WebSocketResult<()> {
472        self.ws_server.clone().listen(listener, config).await
473    }
474
475    /// Signal the listening task to stop
476    pub fn stop(&self) -> WebSocketResult<()> {
477        self.ws_server.stop()
478    }
479
480    /// Blocks until the listening task has stopped
481    pub async fn join(&self) -> WebSocketResult<()> {
482        self.ws_server.join().await
483    }
484
485    /// Signal the listening task to stop and block
486    /// until it has stopped
487    pub async fn stop_and_join(&self) -> WebSocketResult<()> {
488        self.ws_server.stop_and_join().await
489    }
490}