jsonrpsee_ws_client/lib.rs
1// Copyright 2019-2021 Parity Technologies (UK) Ltd.
2//
3// Permission is hereby granted, free of charge, to any
4// person obtaining a copy of this software and associated
5// documentation files (the "Software"), to deal in the
6// Software without restriction, including without
7// limitation the rights to use, copy, modify, merge,
8// publish, distribute, sublicense, and/or sell copies of
9// the Software, and to permit persons to whom the Software
10// is furnished to do so, subject to the following
11// conditions:
12//
13// The above copyright notice and this permission notice
14// shall be included in all copies or substantial portions
15// of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25// DEALINGS IN THE SOFTWARE.
26
27//! # jsonrpsee-ws-client
28//!
29//! `jsonrpsee-ws-client` is a [JSON RPC](https://www.jsonrpc.org/specification) WebSocket client library that's is built for `async/await`.
30//!
31//! ## Async runtime support
32//!
33//! This library uses `tokio` as the runtime and does not support other runtimes.
34
35#![warn(missing_docs, missing_debug_implementations, missing_copy_implementations, unreachable_pub)]
36#![cfg_attr(not(test), warn(unused_crate_dependencies))]
37#![cfg_attr(docsrs, feature(doc_cfg))]
38
39#[cfg(test)]
40mod tests;
41
42pub use http::{HeaderMap, HeaderValue};
43pub use jsonrpsee_core::client::async_client::PingConfig;
44pub use jsonrpsee_core::client::Client as WsClient;
45pub use jsonrpsee_types as types;
46
47use jsonrpsee_client_transport::ws::{AsyncRead, AsyncWrite, WsTransportClientBuilder};
48use jsonrpsee_core::client::{ClientBuilder, Error, IdKind, MaybeSend, TransportReceiverT, TransportSenderT};
49use jsonrpsee_core::TEN_MB_SIZE_BYTES;
50use std::time::Duration;
51use url::Url;
52
53#[cfg(feature = "tls")]
54pub use jsonrpsee_client_transport::ws::CustomCertStore;
55
56#[cfg(feature = "tls")]
57use jsonrpsee_client_transport::ws::CertificateStore;
58
59/// Builder for [`WsClient`].
60///
61/// # Examples
62///
63/// ```no_run
64///
65/// use jsonrpsee_ws_client::{WsClientBuilder, HeaderMap, HeaderValue};
66///
67/// #[tokio::main]
68/// async fn main() {
69/// // Build custom headers used during the handshake process.
70/// let mut headers = HeaderMap::new();
71/// headers.insert("Any-Header-You-Like", HeaderValue::from_static("42"));
72///
73/// // Build client
74/// let client = WsClientBuilder::default()
75/// .set_headers(headers)
76/// .build("wss://localhost:443")
77/// .await
78/// .unwrap();
79///
80/// // use client....
81/// }
82///
83/// ```
84#[derive(Clone, Debug)]
85pub struct WsClientBuilder {
86 #[cfg(feature = "tls")]
87 certificate_store: CertificateStore,
88 max_request_size: u32,
89 max_response_size: u32,
90 request_timeout: Duration,
91 connection_timeout: Duration,
92 ping_config: Option<PingConfig>,
93 headers: http::HeaderMap,
94 max_concurrent_requests: usize,
95 max_buffer_capacity_per_subscription: usize,
96 max_redirections: usize,
97 id_kind: IdKind,
98 max_log_length: u32,
99 tcp_no_delay: bool,
100}
101
102impl Default for WsClientBuilder {
103 fn default() -> Self {
104 Self {
105 #[cfg(feature = "tls")]
106 certificate_store: CertificateStore::Native,
107 max_request_size: TEN_MB_SIZE_BYTES,
108 max_response_size: TEN_MB_SIZE_BYTES,
109 request_timeout: Duration::from_secs(60),
110 connection_timeout: Duration::from_secs(10),
111 ping_config: None,
112 headers: HeaderMap::new(),
113 max_concurrent_requests: 256,
114 max_buffer_capacity_per_subscription: 1024,
115 max_redirections: 5,
116 id_kind: IdKind::Number,
117 max_log_length: 4096,
118 tcp_no_delay: true,
119 }
120 }
121}
122
123impl WsClientBuilder {
124 /// Create a new WebSocket client builder.
125 pub fn new() -> WsClientBuilder {
126 WsClientBuilder::default()
127 }
128
129 /// Force to use a custom certificate store.
130 ///
131 /// # Optional
132 ///
133 /// This requires the optional `tls` feature.
134 ///
135 /// # Example
136 ///
137 /// ```no_run
138 /// use jsonrpsee_ws_client::{WsClientBuilder, CustomCertStore};
139 /// use rustls::{
140 /// client::danger::{self, HandshakeSignatureValid, ServerCertVerified},
141 /// pki_types::{CertificateDer, ServerName, UnixTime},
142 /// Error,
143 /// };
144 ///
145 /// #[derive(Debug)]
146 /// struct NoCertificateVerification;
147 ///
148 /// impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
149 /// fn verify_server_cert(
150 /// &self,
151 /// _: &CertificateDer<'_>,
152 /// _: &[CertificateDer<'_>],
153 /// _: &ServerName<'_>,
154 /// _: &[u8],
155 /// _: UnixTime,
156 /// ) -> Result<ServerCertVerified, Error> {
157 /// Ok(ServerCertVerified::assertion())
158 /// }
159 ///
160 /// fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
161 /// vec![rustls::SignatureScheme::ECDSA_NISTP256_SHA256]
162 /// }
163 ///
164 /// fn verify_tls12_signature(
165 /// &self,
166 /// _: &[u8],
167 /// _: &CertificateDer<'_>,
168 /// _: &rustls::DigitallySignedStruct,
169 /// ) -> Result<rustls::client::danger::HandshakeSignatureValid, Error> {
170 /// Ok(HandshakeSignatureValid::assertion())
171 /// }
172 ///
173 /// fn verify_tls13_signature(
174 /// &self,
175 /// _: &[u8],
176 /// _: &CertificateDer<'_>,
177 /// _: &rustls::DigitallySignedStruct,
178 /// ) -> Result<HandshakeSignatureValid, Error> {
179 /// Ok(HandshakeSignatureValid::assertion())
180 /// }
181 /// }
182 ///
183 /// let tls_cfg = CustomCertStore::builder()
184 /// .dangerous()
185 /// .with_custom_certificate_verifier(std::sync::Arc::new(NoCertificateVerification))
186 /// .with_no_client_auth();
187 ///
188 /// // client builder with disabled certificate verification.
189 /// let client_builder = WsClientBuilder::new().with_custom_cert_store(tls_cfg);
190 /// ```
191 #[cfg(feature = "tls")]
192 pub fn with_custom_cert_store(mut self, cfg: CustomCertStore) -> Self {
193 self.certificate_store = CertificateStore::Custom(cfg);
194 self
195 }
196
197 /// See documentation [`WsTransportClientBuilder::max_request_size`] (default is 10 MB).
198 pub fn max_request_size(mut self, size: u32) -> Self {
199 self.max_request_size = size;
200 self
201 }
202
203 /// See documentation [`WsTransportClientBuilder::max_response_size`] (default is 10 MB).
204 pub fn max_response_size(mut self, size: u32) -> Self {
205 self.max_response_size = size;
206 self
207 }
208
209 /// See documentation [`ClientBuilder::request_timeout`] (default is 60 seconds).
210 pub fn request_timeout(mut self, timeout: Duration) -> Self {
211 self.request_timeout = timeout;
212 self
213 }
214
215 /// See documentation [`WsTransportClientBuilder::connection_timeout`] (default is 10 seconds).
216 pub fn connection_timeout(mut self, timeout: Duration) -> Self {
217 self.connection_timeout = timeout;
218 self
219 }
220
221 /// See documentation [`ClientBuilder::enable_ws_ping`] (disabled by default).
222 pub fn enable_ws_ping(mut self, cfg: PingConfig) -> Self {
223 self.ping_config = Some(cfg);
224 self
225 }
226
227 /// See documentation [`ClientBuilder::disable_ws_ping`]
228 pub fn disable_ws_ping(mut self) -> Self {
229 self.ping_config = None;
230 self
231 }
232
233 /// See documentation [`WsTransportClientBuilder::set_headers`] (default is none).
234 pub fn set_headers(mut self, headers: http::HeaderMap) -> Self {
235 self.headers = headers;
236 self
237 }
238
239 /// See documentation [`ClientBuilder::max_concurrent_requests`] (default is 256).
240 pub fn max_concurrent_requests(mut self, max: usize) -> Self {
241 self.max_concurrent_requests = max;
242 self
243 }
244
245 /// See documentation [`ClientBuilder::max_buffer_capacity_per_subscription`] (default is 1024).
246 pub fn max_buffer_capacity_per_subscription(mut self, max: usize) -> Self {
247 self.max_buffer_capacity_per_subscription = max;
248 self
249 }
250
251 /// See documentation [`WsTransportClientBuilder::max_redirections`] (default is 5).
252 pub fn max_redirections(mut self, redirect: usize) -> Self {
253 self.max_redirections = redirect;
254 self
255 }
256
257 /// See documentation for [`ClientBuilder::id_format`] (default is Number).
258 pub fn id_format(mut self, kind: IdKind) -> Self {
259 self.id_kind = kind;
260 self
261 }
262
263 /// Set maximum length for logging calls and responses.
264 ///
265 /// Logs bigger than this limit will be truncated.
266 pub fn set_max_logging_length(mut self, max: u32) -> Self {
267 self.max_log_length = max;
268 self
269 }
270
271 /// See documentation [`ClientBuilder::set_tcp_no_delay`] (default is true).
272 pub fn set_tcp_no_delay(mut self, no_delay: bool) -> Self {
273 self.tcp_no_delay = no_delay;
274 self
275 }
276
277 /// Build the [`WsClient`] with specified [`TransportSenderT`] [`TransportReceiverT`] parameters
278 ///
279 /// ## Panics
280 ///
281 /// Panics if being called outside of `tokio` runtime context.
282 pub fn build_with_transport<S, R>(self, sender: S, receiver: R) -> WsClient
283 where
284 S: TransportSenderT + Send,
285 R: TransportReceiverT + Send,
286 {
287 let Self {
288 max_concurrent_requests,
289 request_timeout,
290 ping_config,
291 max_buffer_capacity_per_subscription,
292 id_kind,
293 max_log_length,
294 tcp_no_delay,
295 ..
296 } = self;
297
298 let mut client = ClientBuilder::default()
299 .max_buffer_capacity_per_subscription(max_buffer_capacity_per_subscription)
300 .request_timeout(request_timeout)
301 .max_concurrent_requests(max_concurrent_requests)
302 .id_format(id_kind)
303 .set_max_logging_length(max_log_length)
304 .set_tcp_no_delay(tcp_no_delay);
305
306 if let Some(cfg) = ping_config {
307 client = client.enable_ws_ping(cfg);
308 }
309
310 client.build_with_tokio(sender, receiver)
311 }
312
313 /// Build the [`WsClient`] with specified data stream, using [`WsTransportClientBuilder::build_with_stream`].
314 ///
315 /// ## Panics
316 ///
317 /// Panics if being called outside of `tokio` runtime context.
318 pub async fn build_with_stream<T>(self, url: impl AsRef<str>, data_stream: T) -> Result<WsClient, Error>
319 where
320 T: AsyncRead + AsyncWrite + Unpin + MaybeSend + 'static,
321 {
322 let transport_builder = WsTransportClientBuilder {
323 #[cfg(feature = "tls")]
324 certificate_store: self.certificate_store.clone(),
325 connection_timeout: self.connection_timeout,
326 headers: self.headers.clone(),
327 max_request_size: self.max_request_size,
328 max_response_size: self.max_response_size,
329 max_redirections: self.max_redirections,
330 tcp_no_delay: self.tcp_no_delay,
331 };
332
333 let uri = Url::parse(url.as_ref()).map_err(|e| Error::Transport(e.into()))?;
334 let (sender, receiver) =
335 transport_builder.build_with_stream(uri, data_stream).await.map_err(|e| Error::Transport(e.into()))?;
336
337 let ws_client = self.build_with_transport(sender, receiver);
338 Ok(ws_client)
339 }
340
341 /// Build the [`WsClient`] with specified URL to connect to, using the default
342 /// [`WsTransportClientBuilder::build_with_stream`], therefore with the default TCP as transport layer.
343 ///
344 /// ## Panics
345 ///
346 /// Panics if being called outside of `tokio` runtime context.
347 pub async fn build(self, url: impl AsRef<str>) -> Result<WsClient, Error> {
348 let transport_builder = WsTransportClientBuilder {
349 #[cfg(feature = "tls")]
350 certificate_store: self.certificate_store.clone(),
351 connection_timeout: self.connection_timeout,
352 headers: self.headers.clone(),
353 max_request_size: self.max_request_size,
354 max_response_size: self.max_response_size,
355 max_redirections: self.max_redirections,
356 tcp_no_delay: self.tcp_no_delay,
357 };
358
359 let uri = Url::parse(url.as_ref()).map_err(|e| Error::Transport(e.into()))?;
360 let (sender, receiver) = transport_builder.build(uri).await.map_err(|e| Error::Transport(e.into()))?;
361
362 let ws_client = self.build_with_transport(sender, receiver);
363 Ok(ws_client)
364 }
365}