Skip to main content

karyon_jsonrpc/client/
multiplexed.rs

1//! Multiplexed client mode (TCP/TLS/Unix and WS/WSS).
2//! A single message connection is shared across all calls and
3//! subscriptions. A background task splits reader from writer.
4
5use std::{
6    sync::{atomic::Ordering, Arc},
7    time::Duration,
8};
9
10use async_channel::Receiver;
11use log::{debug, error, info};
12use serde::{de::DeserializeOwned, Deserialize, Serialize};
13use serde_json::json;
14
15use karyon_core::{
16    async_util::{select, timeout, Either, TaskResult},
17    util::random_32,
18};
19
20use karyon_net::{framed, Endpoint, FramedConn, MessageRx, MessageTx};
21
22#[cfg(feature = "ws")]
23use karyon_net::{layers::ws::WsConn, ClientLayer};
24
25use crate::{
26    client::{
27        message_dispatcher::MessageDispatcher,
28        subscriptions::{NotificationDecoder, Subscriptions},
29        Client, ClientBackend, ClientConfig, RequestID, WsCodec,
30    },
31    codec::JsonRpcCodec,
32    error::{Error, Result},
33    message,
34};
35
36#[cfg(feature = "ws")]
37use crate::codec::JsonRpcWsCodec;
38
39/// Capacity of the outbound queue feeding the writer task. Small
40/// because each in-flight call already has its own response channel,
41/// so this only buffers bursts between caller and writer.
42const OUTBOUND_BUFFER_SIZE: usize = 10;
43
44#[derive(Serialize, Deserialize)]
45#[serde(untagged)]
46enum NewMsg {
47    Notification(message::Notification),
48    Response(message::Response),
49}
50
51/// Build the multiplexed backend by connecting over a byte stream
52/// (TCP, TLS, or Unix) and framing it.
53pub(super) async fn build_byte_backend<B>(
54    config: &ClientConfig,
55    codec: B,
56) -> Result<(ClientBackend, FramedConn<B>)>
57where
58    B: JsonRpcCodec,
59{
60    let conn = connect_byte(config, codec).await?;
61    let peer = conn.peer_endpoint();
62    info!("Successfully connected to the RPC server: {peer:?}");
63
64    let backend = build_backend_state(config);
65    Ok((backend, conn))
66}
67
68/// Build the multiplexed backend by connecting over a WebSocket.
69#[cfg(feature = "ws")]
70pub(super) async fn build_ws_backend<W>(
71    config: &ClientConfig,
72    codec: W,
73) -> Result<(ClientBackend, WsConn<W>)>
74where
75    W: JsonRpcWsCodec,
76{
77    let conn = connect_ws(config, codec).await?;
78    let peer = conn.peer_endpoint();
79    info!("Successfully connected to the RPC server: {peer:?}");
80
81    let backend = build_backend_state(config);
82    Ok((backend, conn))
83}
84
85fn build_backend_state(config: &ClientConfig) -> ClientBackend {
86    ClientBackend::Multiplexed {
87        message_dispatcher: MessageDispatcher::new(),
88        subscriptions: Subscriptions::new(config.subscription_buffer_size),
89        send_chan: async_channel::bounded(OUTBOUND_BUFFER_SIZE),
90    }
91}
92
93/// Spawn the reader/writer task that drives the multiplexed wire.
94pub(super) fn start_io_loop<B, W, R, Wr>(client: &Arc<Client<B, W>>, reader: R, writer: Wr)
95where
96    B: JsonRpcCodec,
97    W: WsCodec,
98    R: MessageRx<Message = serde_json::Value> + Send + 'static,
99    Wr: MessageTx<Message = serde_json::Value> + Send + 'static,
100{
101    let this = client.clone();
102    client.task_group.spawn_then(
103        background_loop(client.clone(), reader, writer),
104        move |result: TaskResult<Result<()>>| async move {
105            if let TaskResult::Completed(Err(err)) = result {
106                error!("Client stopped: {err}");
107            }
108            this.disconnect.store(true, Ordering::Relaxed);
109            if let ClientBackend::Multiplexed {
110                subscriptions,
111                message_dispatcher,
112                ..
113            } = &this.backend
114            {
115                subscriptions.clear().await;
116                message_dispatcher.clear().await;
117            }
118        },
119    );
120}
121
122async fn background_loop<B, W, R, Wr>(
123    client: Arc<Client<B, W>>,
124    reader: R,
125    writer: Wr,
126) -> Result<()>
127where
128    B: JsonRpcCodec,
129    W: WsCodec,
130    R: MessageRx<Message = serde_json::Value> + Send,
131    Wr: MessageTx<Message = serde_json::Value> + Send,
132{
133    let ClientBackend::Multiplexed {
134        message_dispatcher,
135        subscriptions,
136        send_chan,
137    } = &client.backend
138    else {
139        return Err(Error::InvalidState("not in multiplexed mode".into()));
140    };
141
142    run_io_loop(
143        reader,
144        writer,
145        send_chan.1.clone(),
146        message_dispatcher,
147        subscriptions,
148        client.config.notification_decoder,
149    )
150    .await
151}
152
153async fn run_io_loop<R, Wr>(
154    mut reader: R,
155    mut writer: Wr,
156    outbound: Receiver<serde_json::Value>,
157    message_dispatcher: &MessageDispatcher,
158    subscriptions: &Subscriptions,
159    decoder: NotificationDecoder,
160) -> Result<()>
161where
162    R: MessageRx<Message = serde_json::Value> + Send,
163    Wr: MessageTx<Message = serde_json::Value> + Send,
164{
165    loop {
166        match select(outbound.recv(), reader.recv_msg()).await {
167            Either::Left(req) => {
168                writer.send_msg(req?).await?;
169            }
170            Either::Right(msg) => {
171                match handle_mux_msg(message_dispatcher, subscriptions, decoder, msg?).await {
172                    Err(Error::SubscriptionBufferFull) => {
173                        return Err(Error::SubscriptionBufferFull);
174                    }
175                    Err(err) => {
176                        let ep = reader.peer_endpoint();
177                        error!("Handle msg from {ep:?}: {err}");
178                    }
179                    Ok(_) => {}
180                }
181            }
182        }
183    }
184}
185
186async fn handle_mux_msg(
187    message_dispatcher: &MessageDispatcher,
188    subscriptions: &Subscriptions,
189    decoder: NotificationDecoder,
190    msg: serde_json::Value,
191) -> Result<()> {
192    match serde_json::from_value::<NewMsg>(msg.clone()) {
193        Ok(NewMsg::Response(res)) => {
194            debug!("<-- {res}");
195            message_dispatcher.dispatch(res).await
196        }
197        Ok(NewMsg::Notification(nt)) => {
198            debug!("<-- {nt}");
199            subscriptions.notify(decoder(nt)?).await
200        }
201        Err(err) => {
202            error!("Receive unexpected msg {msg}: {err}");
203            Err(Error::InvalidMsg("Unexpected msg".to_string()))
204        }
205    }
206}
207
208async fn connect_byte<B>(config: &ClientConfig, codec: B) -> Result<FramedConn<B>>
209where
210    B: JsonRpcCodec,
211{
212    let endpoint = config.endpoint.clone();
213
214    match &endpoint {
215        #[cfg(feature = "tcp")]
216        Endpoint::Tcp(..) => {
217            let stream = karyon_net::tcp::connect(&endpoint, config.tcp_config.clone()).await?;
218            Ok(framed(stream, codec))
219        }
220        #[cfg(feature = "tls")]
221        Endpoint::Tls(..) => {
222            let stream = karyon_net::tcp::connect(&endpoint, config.tcp_config.clone()).await?;
223            let tls_config = config.tls_config.as_ref().ok_or(Error::TLSConfigRequired)?;
224            let tls_layer = karyon_net::tls::TlsLayer::client(tls_config.clone());
225            let tls_stream = karyon_net::ClientLayer::handshake(&tls_layer, stream).await?;
226            Ok(framed(tls_stream, codec))
227        }
228        #[cfg(all(feature = "unix", target_family = "unix"))]
229        Endpoint::Unix(..) => {
230            let stream = karyon_net::unix::connect(&endpoint).await?;
231            Ok(framed(stream, codec))
232        }
233        _ => Err(Error::UnsupportedProtocol(endpoint.to_string())),
234    }
235}
236
237#[cfg(feature = "ws")]
238async fn connect_ws<W>(config: &ClientConfig, codec: W) -> Result<WsConn<W>>
239where
240    W: JsonRpcWsCodec,
241{
242    let endpoint = config.endpoint.clone();
243    let url = endpoint.to_string();
244
245    match &endpoint {
246        Endpoint::Ws(..) => {
247            let stream = karyon_net::tcp::connect(&endpoint, config.tcp_config.clone()).await?;
248            let layer = karyon_net::layers::ws::WsLayer::client(&url, codec);
249            Ok(ClientLayer::handshake(&layer, stream).await?)
250        }
251        #[cfg(feature = "tls")]
252        Endpoint::Wss(..) => {
253            let stream = karyon_net::tcp::connect(&endpoint, config.tcp_config.clone()).await?;
254            let tls_config = config.tls_config.as_ref().ok_or(Error::TLSConfigRequired)?;
255            let tls_layer = karyon_net::tls::TlsLayer::client(tls_config.clone());
256            let tls_stream = karyon_net::ClientLayer::handshake(&tls_layer, stream).await?;
257            let layer = karyon_net::layers::ws::WsLayer::client(&url, codec);
258            Ok(ClientLayer::handshake(&layer, tls_stream).await?)
259        }
260        _ => Err(Error::UnsupportedProtocol(endpoint.to_string())),
261    }
262}
263
264pub(super) async fn send_request<B, W, T>(
265    client: &Client<B, W>,
266    method: &str,
267    params: T,
268) -> Result<message::Response>
269where
270    B: JsonRpcCodec,
271    W: WsCodec,
272    T: Serialize + DeserializeOwned,
273{
274    let ClientBackend::Multiplexed {
275        message_dispatcher,
276        send_chan,
277        ..
278    } = &client.backend
279    else {
280        return Err(Error::InvalidState("not in multiplexed mode".into()));
281    };
282
283    let id: RequestID = random_32();
284    let request = message::Request {
285        jsonrpc: message::JSONRPC_VERSION.to_string(),
286        id: json!(id),
287        method: method.to_string(),
288        params: Some(json!(params)),
289    };
290
291    if client.disconnect.load(Ordering::Relaxed) {
292        return Err(Error::ClientDisconnected);
293    }
294    let req = serde_json::to_value(request)?;
295
296    let rx = message_dispatcher.register(id).await;
297    if let Err(err) = send_chan.0.send(req).await {
298        message_dispatcher.unregister(&id).await;
299        return Err(err.into());
300    }
301
302    let result = match client.config.timeout {
303        Some(t) => match timeout(Duration::from_millis(t), rx.recv()).await {
304            Ok(res) => res.map_err(Error::from),
305            Err(err) => Err(Error::from(err)),
306        },
307        None => rx.recv().await.map_err(Error::from),
308    };
309
310    // Unregister on every error, timeout included, so the entry does not
311    // stay in the dispatcher.
312    let response = match result {
313        Ok(r) => r,
314        Err(err) => {
315            message_dispatcher.unregister(&id).await;
316            return Err(err);
317        }
318    };
319
320    // Error responses go back to the caller, which maps them to
321    // `CallError` or `SubscribeError`.
322
323    let resp_id = response
324        .id
325        .as_ref()
326        .ok_or_else(|| Error::InvalidMsg("Missing response id".to_string()))?;
327    if *resp_id != id {
328        return Err(Error::InvalidMsg("Invalid response id".to_string()));
329    }
330
331    Ok(response)
332}