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}