From 33ad7783c5179f1642b4d2ae9f1b3e9ad15acb07 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 15 Jan 2025 09:48:05 +0100 Subject: [PATCH 01/52] feat: client rpc middleware --- client/http-client/Cargo.toml | 1 + client/http-client/src/client.rs | 118 +++++++++++++------------- client/http-client/src/lib.rs | 4 + client/http-client/src/rpc_service.rs | 59 +++++++++++++ client/http-client/src/transport.rs | 20 ++++- core/Cargo.toml | 5 +- core/src/lib.rs | 4 + core/src/middleware.rs | 78 +++++++++++++++++ core/src/traits.rs | 2 + examples/examples/http.rs | 22 ++++- server/src/middleware/rpc/mod.rs | 79 +---------------- 11 files changed, 252 insertions(+), 140 deletions(-) create mode 100644 client/http-client/src/rpc_service.rs create mode 100644 core/src/middleware.rs diff --git a/client/http-client/Cargo.toml b/client/http-client/Cargo.toml index 3f668d6d5e..17bf55d8d9 100644 --- a/client/http-client/Cargo.toml +++ b/client/http-client/Cargo.toml @@ -16,6 +16,7 @@ publish = true [dependencies] async-trait = { workspace = true } base64 = { workspace = true } +futures-util = { workspace = true } hyper = { workspace = true, features = ["client", "http1", "http2"] } hyper-rustls = { workspace = true, features = ["http1", "http2", "tls12", "logging", "ring"], optional = true } hyper-util = { workspace = true, features = ["client", "client-legacy", "tokio", "http1", "http2"] } diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 52a9a8ca21..3c13569408 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -24,24 +24,25 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::borrow::Cow as StdCow; use std::fmt; use std::sync::Arc; use std::time::Duration; -use crate::transport::{self, Error as TransportError, HttpBackend, HttpTransportClient, HttpTransportClientBuilder}; -use crate::types::{NotificationSer, RequestSer, Response}; -use crate::{HttpRequest, HttpResponse}; +use crate::rpc_service::RpcService; +use crate::transport::{self, Error as TransportError, HttpBackend, HttpTransportClientBuilder}; +use crate::types::Response; +use crate::{HttpRequest, HttpResponse, IsNotification}; use async_trait::async_trait; use hyper::body::Bytes; use hyper::http::HeaderMap; use jsonrpsee_core::client::{ - generate_batch_id_range, BatchResponse, ClientT, Error, IdKind, RequestIdManager, Subscription, SubscriptionClientT, + BatchResponse, ClientT, Error, IdKind, RequestIdManager, Subscription, SubscriptionClientT, }; +use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::params::BatchRequestBuilder; use jsonrpsee_core::traits::ToRpcParams; use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; -use jsonrpsee_types::{ErrorObject, InvalidRequestId, ResponseSuccess, TwoPointZero}; +use jsonrpsee_types::{Id, InvalidRequestId, Request, ResponseSuccess}; use serde::de::DeserializeOwned; use tokio::sync::Semaphore; use tower::layer::util::Identity; @@ -75,7 +76,7 @@ use crate::{CertificateStore, CustomCertStore}; /// } /// ``` #[derive(Clone, Debug)] -pub struct HttpClientBuilder { +pub struct HttpClientBuilder { max_request_size: u32, max_response_size: u32, request_timeout: Duration, @@ -84,12 +85,13 @@ pub struct HttpClientBuilder { id_kind: IdKind, max_log_length: u32, headers: HeaderMap, - service_builder: tower::ServiceBuilder, + service_builder: tower::ServiceBuilder, + rpc_middleware: RpcServiceBuilder, tcp_no_delay: bool, max_concurrent_requests: Option, } -impl HttpClientBuilder { +impl HttpClientBuilder { /// Set the maximum size of a request body in bytes. Default is 10 MiB. pub fn max_request_size(mut self, size: u32) -> Self { self.max_request_size = size; @@ -215,8 +217,29 @@ impl HttpClientBuilder { self } + /// Set the RPC middleware. + pub fn set_rpc_middleware(self, rpc_builder: RpcServiceBuilder) -> HttpClientBuilder { + HttpClientBuilder { + #[cfg(feature = "tls")] + certificate_store: self.certificate_store, + id_kind: self.id_kind, + headers: self.headers, + max_log_length: self.max_log_length, + max_request_size: self.max_request_size, + max_response_size: self.max_response_size, + service_builder: self.service_builder, + rpc_middleware: rpc_builder, + request_timeout: self.request_timeout, + tcp_no_delay: self.tcp_no_delay, + max_concurrent_requests: self.max_concurrent_requests, + } + } + /// Set custom tower middleware. - pub fn set_http_middleware(self, service_builder: tower::ServiceBuilder) -> HttpClientBuilder { + pub fn set_http_middleware( + self, + service_builder: tower::ServiceBuilder, + ) -> HttpClientBuilder { HttpClientBuilder { #[cfg(feature = "tls")] certificate_store: self.certificate_store, @@ -226,6 +249,7 @@ impl HttpClientBuilder { max_request_size: self.max_request_size, max_response_size: self.max_response_size, service_builder, + rpc_middleware: self.rpc_middleware, request_timeout: self.request_timeout, tcp_no_delay: self.tcp_no_delay, max_concurrent_requests: self.max_concurrent_requests, @@ -233,16 +257,18 @@ impl HttpClientBuilder { } } -impl HttpClientBuilder +impl HttpClientBuilder where - L: Layer, + RpcMiddleware: Layer, Service = S2>, + for<'a> >>::Service: RpcServiceT<'a>, + HttpMiddleware: Layer, S: Service, Error = TransportError> + Clone, B: http_body::Body + Send + Unpin + 'static, B::Data: Send, B::Error: Into, { /// Build the HTTP client with target to connect to. - pub fn build(self, target: impl AsRef) -> Result, Error> { + pub fn build(self, target: impl AsRef) -> Result, Error> { let Self { max_request_size, max_response_size, @@ -254,10 +280,11 @@ where max_log_length, service_builder, tcp_no_delay, + rpc_middleware, .. } = self; - let transport = HttpTransportClientBuilder { + let http = HttpTransportClientBuilder { max_request_size, max_response_size, headers, @@ -266,6 +293,7 @@ where service_builder, #[cfg(feature = "tls")] certificate_store, + request_timeout, } .build(target) .map_err(|e| Error::Transport(e.into()))?; @@ -275,9 +303,8 @@ where .map(|max_concurrent_requests| Arc::new(Semaphore::new(max_concurrent_requests))); Ok(HttpClient { - transport, + transport: rpc_middleware.service(RpcService::new(http, max_response_size)), id_manager: Arc::new(RequestIdManager::new(id_kind)), - request_timeout, request_guard, }) } @@ -295,6 +322,7 @@ impl Default for HttpClientBuilder { max_log_length: 4096, headers: HeaderMap::new(), service_builder: tower::ServiceBuilder::new(), + rpc_middleware: RpcServiceBuilder::default(), tcp_no_delay: true, max_concurrent_requests: None, } @@ -310,11 +338,9 @@ impl HttpClientBuilder { /// JSON-RPC HTTP Client that provides functionality to perform method calls and notifications. #[derive(Debug, Clone)] -pub struct HttpClient { +pub struct HttpClient { /// HTTP transport client. - transport: HttpTransportClient, - /// Request timeout. Defaults to 60sec. - request_timeout: Duration, + transport: S, /// Request ID manager. id_manager: Arc, /// Concurrent requests limit guard. @@ -329,13 +355,9 @@ impl HttpClient { } #[async_trait] -impl ClientT for HttpClient +impl ClientT for HttpClient where - S: Service, Error = TransportError> + Send + Sync + Clone, - >::Future: Send, - B: http_body::Body + Send + Unpin + 'static, - B::Error: Into, - B::Data: Send, + for<'a> S: RpcServiceT<'a> + Send + Sync, { #[instrument(name = "notification", skip(self, params), level = "trace")] async fn notification(&self, method: &str, params: Params) -> Result<(), Error> @@ -347,16 +369,11 @@ where None => None, }; let params = params.to_rpc_params()?; - let notif = - serde_json::to_string(&NotificationSer::borrowed(&method, params.as_deref())).map_err(Error::ParseError)?; - - let fut = self.transport.send(notif); + let mut request = Request::new(method.into(), params.as_deref(), Id::Null); + request.extensions_mut().insert(IsNotification); - match tokio::time::timeout(self.request_timeout, fut).await { - Ok(Ok(ok)) => Ok(ok), - Err(_) => Err(Error::RequestTimeout), - Ok(Err(e)) => Err(Error::Transport(e.into())), - } + self.transport.call(request).await; + Ok(()) } #[instrument(name = "method_call", skip(self, params), level = "trace")] @@ -372,23 +389,12 @@ where let id = self.id_manager.next_request_id(); let params = params.to_rpc_params()?; - let request = RequestSer::borrowed(&id, &method, params.as_deref()); - let raw = serde_json::to_string(&request).map_err(Error::ParseError)?; - - let fut = self.transport.send_and_read_body(raw); - let body = match tokio::time::timeout(self.request_timeout, fut).await { - Ok(Ok(body)) => body, - Err(_e) => { - return Err(Error::RequestTimeout); - } - Ok(Err(e)) => { - return Err(Error::Transport(e.into())); - } - }; + let request = Request::new(method.into(), params.as_deref(), id.clone()); + let rp = self.transport.call(request).await; // NOTE: it's decoded first to `JsonRawValue` and then to `R` below to get // a better error message if `R` couldn't be decoded. - let response = ResponseSuccess::try_from(serde_json::from_slice::>(&body)?)?; + let response = ResponseSuccess::try_from(serde_json::from_str::>(&rp.as_result())?)?; let result = serde_json::from_str(response.result.get()).map_err(Error::ParseError)?; @@ -404,7 +410,9 @@ where where R: DeserializeOwned + fmt::Debug + 'a, { - let _permit = match self.request_guard.as_ref() { + todo!(); + + /*let _permit = match self.request_guard.as_ref() { Some(permit) => permit.acquire().await.ok(), None => None, }; @@ -468,18 +476,14 @@ where } } - Ok(BatchResponse::new(successful_calls, responses, failed_calls)) + Ok(BatchResponse::new(successful_calls, responses, failed_calls))*/ } } #[async_trait] -impl SubscriptionClientT for HttpClient +impl SubscriptionClientT for HttpClient where - S: Service, Error = TransportError> + Send + Sync + Clone, - >::Future: Send, - B: http_body::Body + Send + Unpin + 'static, - B::Data: Send, - B::Error: Into, + for<'a> S: RpcServiceT<'a> + Send + Sync, { /// Send a subscription request to the server. Not implemented for HTTP; will always return /// [`Error::HttpNotImplemented`]. diff --git a/client/http-client/src/lib.rs b/client/http-client/src/lib.rs index 27138106fb..5e4e91468b 100644 --- a/client/http-client/src/lib.rs +++ b/client/http-client/src/lib.rs @@ -37,6 +37,7 @@ #![cfg_attr(docsrs, feature(doc_cfg))] mod client; +mod rpc_service; /// HTTP transport. pub mod transport; @@ -67,3 +68,6 @@ pub(crate) enum CertificateStore { Native, Custom(CustomCertStore), } + +#[derive(Copy, Clone, Debug)] +pub(crate) struct IsNotification; diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs new file mode 100644 index 0000000000..f1fa5bb59c --- /dev/null +++ b/client/http-client/src/rpc_service.rs @@ -0,0 +1,59 @@ +use std::sync::Arc; + +use futures_util::{future::BoxFuture, FutureExt}; +use hyper::body::Bytes; +use jsonrpsee_core::{ + middleware::RpcServiceT, + server::{MethodResponse, ResponsePayload}, + BoxError, JsonRawValue, +}; +use jsonrpsee_types::{Id, Response}; +use tower::Service; + +use crate::{ + transport::{Error, HttpTransportClient}, + HttpRequest, HttpResponse, IsNotification, +}; + +#[derive(Clone, Debug)] +pub struct RpcService { + service: Arc>, + max_response_size: u32, +} + +impl RpcService { + pub fn new(service: HttpTransportClient, max_response_size: u32) -> Self { + Self { service: Arc::new(service), max_response_size } + } +} + +impl<'a, B, HttpMiddleware> RpcServiceT<'a> for RpcService +where + HttpMiddleware: Service, Error = Error> + Clone + Send + Sync + 'static, + HttpMiddleware::Future: Send, + B: http_body::Body + Send + 'static, + B::Data: Send, + B::Error: Into, +{ + type Future = BoxFuture<'a, MethodResponse>; + + fn call(&self, request: jsonrpsee_types::Request<'a>) -> Self::Future { + let raw = serde_json::to_string(&request).unwrap(); + let service = self.service.clone(); + let max_response_size = self.max_response_size; + + let is_notification = request.extensions().get::().is_some(); + + async move { + if is_notification { + service.send(raw).await.map_err(BoxError::from).unwrap(); + MethodResponse::response(Id::Null, ResponsePayload::success(""), max_response_size as usize) + } else { + let bytes = service.send_and_read_body(raw).await.map_err(BoxError::from).unwrap(); + let rp: Response> = serde_json::from_slice(&bytes).unwrap(); + MethodResponse::response(rp.id, rp.payload.into(), max_response_size as usize) + } + } + .boxed() + } +} diff --git a/client/http-client/src/transport.rs b/client/http-client/src/transport.rs index 56e2426bac..7660716f07 100644 --- a/client/http-client/src/transport.rs +++ b/client/http-client/src/transport.rs @@ -21,6 +21,7 @@ use jsonrpsee_core::{ use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; +use std::time::Duration; use thiserror::Error; use tower::layer::util::Identity; use tower::{Layer, Service, ServiceExt}; @@ -103,6 +104,8 @@ pub struct HttpTransportClientBuilder { pub(crate) service_builder: tower::ServiceBuilder, /// TCP_NODELAY pub(crate) tcp_no_delay: bool, + /// Request timeout + pub(crate) request_timeout: Duration, } impl Default for HttpTransportClientBuilder { @@ -123,6 +126,7 @@ impl HttpTransportClientBuilder { headers: HeaderMap::new(), service_builder: tower::ServiceBuilder::new(), tcp_no_delay: true, + request_timeout: Duration::from_secs(60), } } } @@ -182,6 +186,7 @@ impl HttpTransportClientBuilder { max_response_size: self.max_response_size, service_builder: service, tcp_no_delay: self.tcp_no_delay, + request_timeout: self.request_timeout, } } @@ -203,6 +208,7 @@ impl HttpTransportClientBuilder { headers, service_builder, tcp_no_delay, + request_timeout, } = self; let mut url = Url::parse(target.as_ref()).map_err(|e| Error::Url(format!("Invalid URL: {e}")))?; @@ -288,6 +294,7 @@ impl HttpTransportClientBuilder { max_response_size, max_log_length, headers: cached_headers, + request_timeout, }) } } @@ -309,6 +316,8 @@ pub struct HttpTransportClient { max_log_length: u32, /// Custom headers to pass with every request. headers: HeaderMap, + /// Request timeout + request_timeout: Duration, } impl HttpTransportClient @@ -342,7 +351,9 @@ where pub(crate) async fn send_and_read_body(&self, body: String) -> Result, Error> { tx_log_from_str(&body, self.max_log_length); - let response = self.inner_send(body).await?; + let response = + tokio::time::timeout(self.request_timeout, self.inner_send(body)).await.map_err(|_| Error::Timeout)??; + let (parts, body) = response.into_parts(); let (body, _is_single) = http_helpers::read_body(&parts.headers, body, self.max_response_size).await?; @@ -354,7 +365,8 @@ where /// Send serialized message without reading the HTTP message body. pub(crate) async fn send(&self, body: String) -> Result<(), Error> { - let _ = self.inner_send(body).await?; + let _ = + tokio::time::timeout(self.request_timeout, self.inner_send(body)).await.map_err(|_| Error::Timeout)??; Ok(()) } @@ -385,6 +397,10 @@ pub enum Error { /// Invalid certificate store. #[error("Invalid certificate store")] InvalidCertficateStore, + + /// Timeout. + #[error("Request timed out")] + Timeout, } #[cfg(test)] diff --git a/core/Cargo.toml b/core/Cargo.toml index 1e20c7c481..82d4804dc9 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -31,6 +31,7 @@ rustc-hash = { workspace = true, optional = true } rand = { workspace = true, optional = true } parking_lot = { workspace = true, optional = true } tokio = { workspace = true, optional = true } +tower = { workspace = true, optional = true } futures-timer = { workspace = true, optional = true } tokio-stream = { workspace = true, optional = true } pin-project = { workspace = true, optional = true } @@ -41,8 +42,8 @@ wasm-bindgen-futures = { workspace = true, optional = true } [features] default = [] http-helpers = ["bytes", "futures-util", "http-body", "http-body-util", "http"] -server = ["futures-util/alloc", "rustc-hash/std", "parking_lot", "rand", "tokio/rt", "tokio/sync", "tokio/macros", "tokio/time", "http"] -client = ["futures-util/sink", "tokio/sync"] +server = ["futures-util/alloc", "rustc-hash/std", "parking_lot", "rand", "tokio/rt", "tokio/sync", "tokio/macros", "tokio/time", "tower", "http"] +client = ["futures-util/sink", "tokio/sync", "tower"] async-client = [ "client", "futures-util/alloc", diff --git a/core/src/lib.rs b/core/src/lib.rs index b203f807f6..750cf10a69 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -57,6 +57,10 @@ cfg_client! { pub use client::Error as ClientError; } +cfg_client_or_server! { + pub mod middleware; +} + /// Shared tracing helpers to trace RPC calls. pub mod tracing; pub use async_trait::async_trait; diff --git a/core/src/middleware.rs b/core/src/middleware.rs new file mode 100644 index 0000000000..709d742ecc --- /dev/null +++ b/core/src/middleware.rs @@ -0,0 +1,78 @@ +//! Middleware for the RPC service. + +use futures_util::future::{Either, Future}; +use jsonrpsee_types::Request; +use tower::layer::util::{Identity, Stack}; +use tower::layer::LayerFn; + +use crate::server::MethodResponse; + +/// Similar to the [`tower::Service`] but specific for jsonrpsee and +/// doesn't requires `&mut self` for performance reasons. +pub trait RpcServiceT<'a> { + /// The future response value. + type Future: Future + Send; + + /// Process a single JSON-RPC call it may be a subscription or regular call. + /// In this interface they are treated in the same way but it's possible to + /// distinguish those based on the `MethodResponse`. + fn call(&self, request: Request<'a>) -> Self::Future; +} + +/// Similar to [`tower::ServiceBuilder`] but doesn't +/// support any tower middleware implementations. +#[derive(Debug, Clone)] +pub struct RpcServiceBuilder(tower::ServiceBuilder); + +impl Default for RpcServiceBuilder { + fn default() -> Self { + RpcServiceBuilder(tower::ServiceBuilder::new()) + } +} + +impl RpcServiceBuilder { + /// Create a new [`RpcServiceBuilder`]. + pub fn new() -> Self { + Self(tower::ServiceBuilder::new()) + } +} + +impl RpcServiceBuilder { + /// Optionally add a new layer `T` to the [`RpcServiceBuilder`]. + /// + /// See the documentation for [`tower::ServiceBuilder::option_layer`] for more details. + pub fn option_layer(self, layer: Option) -> RpcServiceBuilder, L>> { + let layer = if let Some(layer) = layer { Either::Left(layer) } else { Either::Right(Identity::new()) }; + self.layer(layer) + } + + /// Add a new layer `T` to the [`RpcServiceBuilder`]. + /// + /// See the documentation for [`tower::ServiceBuilder::layer`] for more details. + pub fn layer(self, layer: T) -> RpcServiceBuilder> { + RpcServiceBuilder(self.0.layer(layer)) + } + + /// Add a [`tower::Layer`] built from a function that accepts a service and returns another service. + /// + /// See the documentation for [`tower::ServiceBuilder::layer_fn`] for more details. + pub fn layer_fn(self, f: F) -> RpcServiceBuilder, L>> { + RpcServiceBuilder(self.0.layer_fn(f)) + } + + /// Add a logging layer to [`RpcServiceBuilder`] + /// + /// This logs each request and response for every call. + /// + /*pub fn rpc_logger(self, max_log_len: u32) -> RpcServiceBuilder> { + RpcServiceBuilder(self.0.layer(RpcLoggerLayer::new(max_log_len))) + }*/ + + /// Wrap the service `S` with the middleware. + pub fn service(&self, service: S) -> L::Service + where + L: tower::Layer, + { + self.0.service(service) + } +} diff --git a/core/src/traits.rs b/core/src/traits.rs index 64e158c913..9d6afc5325 100644 --- a/core/src/traits.rs +++ b/core/src/traits.rs @@ -150,3 +150,5 @@ impl IdProvider for Box { (**self).next_id() } } + + diff --git a/examples/examples/http.rs b/examples/examples/http.rs index c65886ed21..9e7525db9c 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -29,13 +29,29 @@ use std::time::Duration; use hyper::body::Bytes; use jsonrpsee::core::client::ClientT; +use jsonrpsee::core::middleware::RpcServiceT; use jsonrpsee::http_client::HttpClient; use jsonrpsee::rpc_params; -use jsonrpsee::server::{RpcModule, Server}; +use jsonrpsee::server::{RpcModule, RpcServiceBuilder, Server}; +use jsonrpsee::types::Request; use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}; use tower_http::LatencyUnit; use tracing_subscriber::util::SubscriberInitExt; +struct Logger(S); + +impl<'a, S> RpcServiceT<'a> for Logger +where + S: RpcServiceT<'a>, +{ + type Future = S::Future; + + fn call(&self, req: Request<'a>) -> Self::Future { + println!("logger layer : {:?}", req); + self.0.call(req) + } +} + #[tokio::main] async fn main() -> anyhow::Result<()> { let filter = tracing_subscriber::EnvFilter::try_from_default_env()? @@ -58,7 +74,9 @@ async fn main() -> anyhow::Result<()> { .on_response(DefaultOnResponse::new().include_headers(true).latency_unit(LatencyUnit::Micros)), ); - let client = HttpClient::builder().set_http_middleware(middleware).build(url)?; + let rpc = RpcServiceBuilder::new().layer_fn(|service| Logger(service)); + + let client = HttpClient::builder().set_http_middleware(middleware).set_rpc_middleware(rpc).build(url)?; let params = rpc_params![1_u64, 2, 3]; let response: Result = client.request("say_hello", params).await; tracing::info!("r: {:?}", response); diff --git a/server/src/middleware/rpc/mod.rs b/server/src/middleware/rpc/mod.rs index abe25ba0ed..244e926f46 100644 --- a/server/src/middleware/rpc/mod.rs +++ b/server/src/middleware/rpc/mod.rs @@ -29,80 +29,5 @@ pub mod layer; pub use layer::*; -use futures_util::Future; -use jsonrpsee_core::server::MethodResponse; -use jsonrpsee_types::Request; -use layer::either::Either; - -use tower::layer::util::{Identity, Stack}; -use tower::layer::LayerFn; - -/// Similar to the [`tower::Service`] but specific for jsonrpsee and -/// doesn't requires `&mut self` for performance reasons. -pub trait RpcServiceT<'a> { - /// The future response value. - type Future: Future + Send; - - /// Process a single JSON-RPC call it may be a subscription or regular call. - /// In this interface they are treated in the same way but it's possible to - /// distinguish those based on the `MethodResponse`. - fn call(&self, request: Request<'a>) -> Self::Future; -} - -/// Similar to [`tower::ServiceBuilder`] but doesn't -/// support any tower middleware implementations. -#[derive(Debug, Clone)] -pub struct RpcServiceBuilder(tower::ServiceBuilder); - -impl Default for RpcServiceBuilder { - fn default() -> Self { - RpcServiceBuilder(tower::ServiceBuilder::new()) - } -} - -impl RpcServiceBuilder { - /// Create a new [`RpcServiceBuilder`]. - pub fn new() -> Self { - Self(tower::ServiceBuilder::new()) - } -} - -impl RpcServiceBuilder { - /// Optionally add a new layer `T` to the [`RpcServiceBuilder`]. - /// - /// See the documentation for [`tower::ServiceBuilder::option_layer`] for more details. - pub fn option_layer(self, layer: Option) -> RpcServiceBuilder, L>> { - let layer = if let Some(layer) = layer { Either::Left(layer) } else { Either::Right(Identity::new()) }; - self.layer(layer) - } - - /// Add a new layer `T` to the [`RpcServiceBuilder`]. - /// - /// See the documentation for [`tower::ServiceBuilder::layer`] for more details. - pub fn layer(self, layer: T) -> RpcServiceBuilder> { - RpcServiceBuilder(self.0.layer(layer)) - } - - /// Add a [`tower::Layer`] built from a function that accepts a service and returns another service. - /// - /// See the documentation for [`tower::ServiceBuilder::layer_fn`] for more details. - pub fn layer_fn(self, f: F) -> RpcServiceBuilder, L>> { - RpcServiceBuilder(self.0.layer_fn(f)) - } - - /// Add a logging layer to [`RpcServiceBuilder`] - /// - /// This logs each request and response for every call. - /// - pub fn rpc_logger(self, max_log_len: u32) -> RpcServiceBuilder> { - RpcServiceBuilder(self.0.layer(RpcLoggerLayer::new(max_log_len))) - } - - /// Wrap the service `S` with the middleware. - pub(crate) fn service(&self, service: S) -> L::Service - where - L: tower::Layer, - { - self.0.service(service) - } -} +pub use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; +pub use jsonrpsee_core::server::MethodResponse; From 401dfc1fecbb5ef0295ecb2b5390d6147328caf3 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 29 Jan 2025 14:50:08 +0100 Subject: [PATCH 02/52] PoC works --- client/http-client/src/client.rs | 72 ++++++++--------------- client/http-client/src/lib.rs | 3 - client/http-client/src/rpc_service.rs | 55 +++++++++++++---- core/src/middleware.rs | 19 +++++- core/src/server/method_response.rs | 12 ++++ examples/examples/jsonrpsee_as_service.rs | 9 ++- examples/examples/ws.rs | 2 +- 7 files changed, 103 insertions(+), 69 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 3c13569408..2b24af8b64 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -24,6 +24,7 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::borrow::Cow as StdCow; use std::fmt; use std::sync::Arc; use std::time::Duration; @@ -31,18 +32,18 @@ use std::time::Duration; use crate::rpc_service::RpcService; use crate::transport::{self, Error as TransportError, HttpBackend, HttpTransportClientBuilder}; use crate::types::Response; -use crate::{HttpRequest, HttpResponse, IsNotification}; +use crate::{HttpRequest, HttpResponse}; use async_trait::async_trait; use hyper::body::Bytes; use hyper::http::HeaderMap; use jsonrpsee_core::client::{ - BatchResponse, ClientT, Error, IdKind, RequestIdManager, Subscription, SubscriptionClientT, + generate_batch_id_range, BatchResponse, ClientT, Error, IdKind, RequestIdManager, Subscription, SubscriptionClientT, }; use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::params::BatchRequestBuilder; use jsonrpsee_core::traits::ToRpcParams; use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; -use jsonrpsee_types::{Id, InvalidRequestId, Request, ResponseSuccess}; +use jsonrpsee_types::{InvalidRequestId, Notification, Request, ResponseSuccess, TwoPointZero}; use serde::de::DeserializeOwned; use tokio::sync::Semaphore; use tower::layer::util::Identity; @@ -369,10 +370,8 @@ where None => None, }; let params = params.to_rpc_params()?; - let mut request = Request::new(method.into(), params.as_deref(), Id::Null); - request.extensions_mut().insert(IsNotification); - - self.transport.call(request).await; + let n = Notification { jsonrpc: TwoPointZero, method: method.into(), params }; + self.transport.notification(n).await; Ok(()) } @@ -410,9 +409,7 @@ where where R: DeserializeOwned + fmt::Debug + 'a, { - todo!(); - - /*let _permit = match self.request_guard.as_ref() { + let _permit = match self.request_guard.as_ref() { Some(permit) => permit.acquire().await.ok(), None => None, }; @@ -423,60 +420,37 @@ where let mut batch_request = Vec::with_capacity(batch.len()); for ((method, params), id) in batch.into_iter().zip(id_range.clone()) { let id = self.id_manager.as_id_kind().into_id(id); - batch_request.push(RequestSer { + batch_request.push(Request { jsonrpc: TwoPointZero, - id, method: method.into(), params: params.map(StdCow::Owned), + id, + extensions: Default::default(), }); } - let fut = self.transport.send_and_read_body(serde_json::to_string(&batch_request).map_err(Error::ParseError)?); - - let body = match tokio::time::timeout(self.request_timeout, fut).await { - Ok(Ok(body)) => body, - Err(_e) => return Err(Error::RequestTimeout), - Ok(Err(e)) => return Err(Error::Transport(e.into())), - }; + let batch = self.transport.batch(batch_request).await; + let responses: Vec> = serde_json::from_str(&batch.as_result()).unwrap(); - let json_rps: Vec> = serde_json::from_slice(&body).map_err(Error::ParseError)?; + let mut x = Vec::new(); + let mut success = 0; + let mut failed = 0; - let mut responses = Vec::with_capacity(json_rps.len()); - let mut successful_calls = 0; - let mut failed_calls = 0; - - for _ in 0..json_rps.len() { - responses.push(Err(ErrorObject::borrowed(0, "", None))); - } - - for rp in json_rps { - let id = rp.id.try_parse_inner_as_number()?; - - let res = match ResponseSuccess::try_from(rp) { + for rp in responses.into_iter() { + match ResponseSuccess::try_from(rp) { Ok(r) => { - let result = serde_json::from_str(r.result.get())?; - successful_calls += 1; - Ok(result) + let v = serde_json::from_str(r.result.get()).map_err(Error::ParseError)?; + x.push(Ok(v)); + success += 1; } Err(err) => { - failed_calls += 1; - Err(err) + x.push(Err(err)); + failed += 1; } }; - - let maybe_elem = id - .checked_sub(id_range.start) - .and_then(|p| p.try_into().ok()) - .and_then(|p: usize| responses.get_mut(p)); - - if let Some(elem) = maybe_elem { - *elem = res; - } else { - return Err(InvalidRequestId::NotPendingRequest(id.to_string()).into()); - } } - Ok(BatchResponse::new(successful_calls, responses, failed_calls))*/ + Ok(BatchResponse::new(success, x, failed)) } } diff --git a/client/http-client/src/lib.rs b/client/http-client/src/lib.rs index 5e4e91468b..ade090a32c 100644 --- a/client/http-client/src/lib.rs +++ b/client/http-client/src/lib.rs @@ -68,6 +68,3 @@ pub(crate) enum CertificateStore { Native, Custom(CustomCertStore), } - -#[derive(Copy, Clone, Debug)] -pub(crate) struct IsNotification; diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index f1fa5bb59c..084c5672d6 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -4,15 +4,15 @@ use futures_util::{future::BoxFuture, FutureExt}; use hyper::body::Bytes; use jsonrpsee_core::{ middleware::RpcServiceT, - server::{MethodResponse, ResponsePayload}, + server::{BatchResponseBuilder, MethodResponse, ResponsePayload}, BoxError, JsonRawValue, }; -use jsonrpsee_types::{Id, Response}; +use jsonrpsee_types::{Id, Response, ResponseSuccess}; use tower::Service; use crate::{ transport::{Error, HttpTransportClient}, - HttpRequest, HttpResponse, IsNotification, + HttpRequest, HttpResponse, }; #[derive(Clone, Debug)] @@ -42,17 +42,50 @@ where let service = self.service.clone(); let max_response_size = self.max_response_size; - let is_notification = request.extensions().get::().is_some(); + async move { + let bytes = service.send_and_read_body(raw).await.map_err(BoxError::from).unwrap(); + let rp: Response> = serde_json::from_slice(&bytes).unwrap(); + MethodResponse::response(rp.id, rp.payload.into(), max_response_size as usize) + } + .boxed() + } + + fn batch(&self, requests: Vec>) -> Self::Future { + let raw = serde_json::to_string(&requests).unwrap(); + let service = self.service.clone(); + let max_response_size = self.max_response_size; async move { - if is_notification { - service.send(raw).await.map_err(BoxError::from).unwrap(); - MethodResponse::response(Id::Null, ResponsePayload::success(""), max_response_size as usize) - } else { - let bytes = service.send_and_read_body(raw).await.map_err(BoxError::from).unwrap(); - let rp: Response> = serde_json::from_slice(&bytes).unwrap(); - MethodResponse::response(rp.id, rp.payload.into(), max_response_size as usize) + let bytes = service.send_and_read_body(raw).await.map_err(BoxError::from).unwrap(); + let json_rps: Vec> = serde_json::from_slice(&bytes).unwrap(); + let mut batch = BatchResponseBuilder::new_with_limit(max_response_size as usize); + + for rp in json_rps { + let id = rp.id.try_parse_inner_as_number().unwrap(); + + let response = match ResponseSuccess::try_from(rp) { + Ok(r) => { + let payload = ResponsePayload::success(r.result); + MethodResponse::response(r.id, payload, max_response_size as usize) + } + Err(err) => MethodResponse::error(Id::Number(id), err), + }; + + batch.append(&response).unwrap(); } + + MethodResponse::from_batch(batch.finish()) + } + .boxed() + } + + fn notification(&self, notif: jsonrpsee_types::Notification<'a, Option>>) -> Self::Future { + let raw = serde_json::to_string(¬if).unwrap(); + let service = self.service.clone(); + + async move { + service.send(raw).await.map_err(BoxError::from).unwrap(); + MethodResponse::notification() } .boxed() } diff --git a/core/src/middleware.rs b/core/src/middleware.rs index 709d742ecc..1d9b5e9599 100644 --- a/core/src/middleware.rs +++ b/core/src/middleware.rs @@ -1,7 +1,8 @@ //! Middleware for the RPC service. use futures_util::future::{Either, Future}; -use jsonrpsee_types::Request; +use jsonrpsee_types::{Notification, Request}; +use serde_json::value::RawValue; use tower::layer::util::{Identity, Stack}; use tower::layer::LayerFn; @@ -14,9 +15,23 @@ pub trait RpcServiceT<'a> { type Future: Future + Send; /// Process a single JSON-RPC call it may be a subscription or regular call. - /// In this interface they are treated in the same way but it's possible to + /// + /// In this interface both are treated in the same way but it's possible to /// distinguish those based on the `MethodResponse`. fn call(&self, request: Request<'a>) -> Self::Future; + + /// Similar to `RpcServiceT::call` but process multiple JSON-RPC calls at once. + /// + /// This method is optional because it's generally not by the server however + /// it may be useful for batch processing on the client side. + fn batch(&self, _requests: Vec>) -> Self::Future { + todo!(); + } + + /// Similar to `RpcServiceT::call` but process a JSON-RPC notification. + fn notification(&self, _request: Notification<'a, Option>>) -> Self::Future { + todo!(); + } } /// Similar to [`tower::ServiceBuilder`] but doesn't diff --git a/core/src/server/method_response.rs b/core/src/server/method_response.rs index 30b3e84ea8..c84526a389 100644 --- a/core/src/server/method_response.rs +++ b/core/src/server/method_response.rs @@ -41,6 +41,7 @@ enum ResponseKind { MethodCall, Subscription, Batch, + Notification, } /// Represents a response to a method call. @@ -226,6 +227,17 @@ impl MethodResponse { } } + /// Create notification response which is a response that doesn't expect a reply. + pub fn notification() -> Self { + Self { + result: String::new(), + success_or_error: MethodResponseResult::Success, + kind: ResponseKind::Notification, + on_close: None, + extensions: Extensions::new(), + } + } + /// Returns a reference to the associated extensions. pub fn extensions(&self) -> &Extensions { &self.extensions diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index cb8c303144..a2f9641292 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -209,9 +209,12 @@ async fn run_server(metrics: Metrics) -> anyhow::Result { // NOTE, the rpc middleware must be initialized here to be able to created once per connection // with data from the connection such as the headers in this example let headers = req.headers().clone(); - let rpc_middleware = RpcServiceBuilder::new().rpc_logger(1024).layer_fn(move |service| { - AuthorizationMiddleware { inner: service, headers: headers.clone(), transport_label } - }); + let rpc_middleware = RpcServiceBuilder::new() /* .rpc_logger(1024)*/ + .layer_fn(move |service| AuthorizationMiddleware { + inner: service, + headers: headers.clone(), + transport_label, + }); let mut svc = svc_builder .set_http_middleware(http_middleware) diff --git a/examples/examples/ws.rs b/examples/examples/ws.rs index 6587585cc3..c84762d3b2 100644 --- a/examples/examples/ws.rs +++ b/examples/examples/ws.rs @@ -50,7 +50,7 @@ async fn main() -> anyhow::Result<()> { } async fn run_server() -> anyhow::Result { - let rpc_middleware = RpcServiceBuilder::new().rpc_logger(1024); + let rpc_middleware = RpcServiceBuilder::new()/* .rpc_logger(1024)*/; let server = Server::builder().set_rpc_middleware(rpc_middleware).build("127.0.0.1:0").await?; let mut module = RpcModule::new(()); module.register_method("say_hello", |_, _, _| "lo")?; From 11d7b4dda66d469563d30458da56fefafef88550 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 29 Jan 2025 15:11:30 +0100 Subject: [PATCH 03/52] cargo fmt --- core/src/traits.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/traits.rs b/core/src/traits.rs index 9d6afc5325..64e158c913 100644 --- a/core/src/traits.rs +++ b/core/src/traits.rs @@ -150,5 +150,3 @@ impl IdProvider for Box { (**self).next_id() } } - - From 622bffd974446dd5afe58c6c49511817b148ad54 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 25 Feb 2025 19:06:14 +0100 Subject: [PATCH 04/52] more refactoring --- .../src/middleware}/layer/either.rs | 19 +++++- .../src/middleware}/layer/logger.rs | 23 +++++-- .../rpc => core/src/middleware/layer}/mod.rs | 9 ++- core/src/{middleware.rs => middleware/mod.rs} | 50 +++++++++++--- core/src/server/method_response.rs | 9 ++- examples/examples/http.rs | 12 +++- examples/examples/jsonrpsee_as_service.rs | 10 ++- .../jsonrpsee_server_low_level_api.rs | 10 ++- examples/examples/rpc_middleware.rs | 26 +++++++- .../examples/rpc_middleware_modify_request.rs | 10 ++- .../examples/rpc_middleware_rate_limiting.rs | 10 ++- .../server_with_connection_details.rs | 12 +++- server/src/lib.rs | 2 +- server/src/middleware/mod.rs | 1 - .../{rpc/layer/rpc_service.rs => rpc.rs} | 27 ++++++-- server/src/middleware/rpc/layer/mod.rs | 66 ------------------- server/src/server.rs | 55 ++++++---------- server/src/transport/http.rs | 13 ++-- server/src/transport/ws.rs | 43 +++++------- tests/tests/helpers.rs | 11 +++- tests/tests/integration_tests.rs | 6 +- tests/tests/metrics.rs | 10 ++- 22 files changed, 258 insertions(+), 176 deletions(-) rename {server/src/middleware/rpc => core/src/middleware}/layer/either.rs (79%) rename {server/src/middleware/rpc => core/src/middleware}/layer/logger.rs (81%) rename {server/src/middleware/rpc => core/src/middleware/layer}/mod.rs (83%) rename core/src/{middleware.rs => middleware/mod.rs} (68%) rename server/src/middleware/{rpc/layer/rpc_service.rs => rpc.rs} (86%) delete mode 100644 server/src/middleware/rpc/layer/mod.rs diff --git a/server/src/middleware/rpc/layer/either.rs b/core/src/middleware/layer/either.rs similarity index 79% rename from server/src/middleware/rpc/layer/either.rs rename to core/src/middleware/layer/either.rs index 01209323c3..b64464d974 100644 --- a/server/src/middleware/rpc/layer/either.rs +++ b/core/src/middleware/layer/either.rs @@ -31,7 +31,7 @@ //! work to implement tower::Layer for //! external types such as future::Either. -use crate::middleware::rpc::RpcServiceT; +use crate::middleware::RpcServiceT; use jsonrpsee_types::Request; /// [`tower::util::Either`] but @@ -72,4 +72,21 @@ where Either::Right(service) => futures_util::future::Either::Right(service.call(request)), } } + + fn batch(&self, requests: Vec>) -> Self::Future { + match self { + Either::Left(service) => futures_util::future::Either::Left(service.batch(requests)), + Either::Right(service) => futures_util::future::Either::Right(service.batch(requests)), + } + } + + fn notification( + &self, + request: jsonrpsee_types::Notification<'a, Option>>, + ) -> Self::Future { + match self { + Either::Left(service) => futures_util::future::Either::Left(service.notification(request)), + Either::Right(service) => futures_util::future::Either::Right(service.notification(request)), + } + } } diff --git a/server/src/middleware/rpc/layer/logger.rs b/core/src/middleware/layer/logger.rs similarity index 81% rename from server/src/middleware/rpc/layer/logger.rs rename to core/src/middleware/layer/logger.rs index 9849bce057..366ff622ce 100644 --- a/server/src/middleware/rpc/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -31,17 +31,16 @@ use std::{ task::{Context, Poll}, }; -use futures_util::Future; -use jsonrpsee_core::{ - server::MethodResponse, +use crate::{ + middleware::{MethodResponse, Notification, RpcServiceT}, tracing::server::{rx_log_from_json, tx_log_from_str}, }; + +use futures_util::Future; use jsonrpsee_types::Request; use pin_project::pin_project; use tracing::{instrument::Instrumented, Instrument}; -use crate::middleware::rpc::RpcServiceT; - /// RPC logger layer. #[derive(Copy, Clone, Debug)] pub struct RpcLoggerLayer(u32); @@ -80,6 +79,20 @@ where ResponseFuture { fut: self.service.call(request), max: self.max }.in_current_span() } + + #[tracing::instrument(name = "batch", skip_all, fields(method = "batch"), level = "trace")] + fn batch(&self, requests: Vec>) -> Self::Future { + rx_log_from_json(&requests, self.max); + + ResponseFuture { fut: self.service.batch(requests), max: self.max }.in_current_span() + } + + #[tracing::instrument(name = "notification", skip_all, fields(method = &*n.method), level = "trace")] + fn notification(&self, n: Notification<'a>) -> Self::Future { + rx_log_from_json(&n, self.max); + + ResponseFuture { fut: self.service.notification(n), max: self.max }.in_current_span() + } } /// Response future to log the response for a method call. diff --git a/server/src/middleware/rpc/mod.rs b/core/src/middleware/layer/mod.rs similarity index 83% rename from server/src/middleware/rpc/mod.rs rename to core/src/middleware/layer/mod.rs index 244e926f46..e31c352ec9 100644 --- a/server/src/middleware/rpc/mod.rs +++ b/core/src/middleware/layer/mod.rs @@ -24,10 +24,9 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! Various middleware implementations for JSON-RPC specific purposes. +//! Specific middleware layer implementation provided by jsonrpsee. -pub mod layer; -pub use layer::*; +pub mod either; +pub mod logger; -pub use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; -pub use jsonrpsee_core::server::MethodResponse; +pub use logger::*; diff --git a/core/src/middleware.rs b/core/src/middleware/mod.rs similarity index 68% rename from core/src/middleware.rs rename to core/src/middleware/mod.rs index 1d9b5e9599..58cffed70e 100644 --- a/core/src/middleware.rs +++ b/core/src/middleware/mod.rs @@ -1,13 +1,22 @@ //! Middleware for the RPC service. +pub mod layer; + use futures_util::future::{Either, Future}; -use jsonrpsee_types::{Notification, Request}; +use jsonrpsee_types::Request; +use pin_project::pin_project; use serde_json::value::RawValue; use tower::layer::util::{Identity, Stack}; use tower::layer::LayerFn; +use std::pin::Pin; +use std::task::{Context, Poll}; + use crate::server::MethodResponse; +/// JSON-RPC notification. +pub type Notification<'a> = jsonrpsee_types::Notification<'a, Option>>; + /// Similar to the [`tower::Service`] but specific for jsonrpsee and /// doesn't requires `&mut self` for performance reasons. pub trait RpcServiceT<'a> { @@ -24,14 +33,10 @@ pub trait RpcServiceT<'a> { /// /// This method is optional because it's generally not by the server however /// it may be useful for batch processing on the client side. - fn batch(&self, _requests: Vec>) -> Self::Future { - todo!(); - } + fn batch(&self, requests: Vec>) -> Self::Future; /// Similar to `RpcServiceT::call` but process a JSON-RPC notification. - fn notification(&self, _request: Notification<'a, Option>>) -> Self::Future { - todo!(); - } + fn notification(&self, n: Notification<'a>) -> Self::Future; } /// Similar to [`tower::ServiceBuilder`] but doesn't @@ -79,9 +84,9 @@ impl RpcServiceBuilder { /// /// This logs each request and response for every call. /// - /*pub fn rpc_logger(self, max_log_len: u32) -> RpcServiceBuilder> { - RpcServiceBuilder(self.0.layer(RpcLoggerLayer::new(max_log_len))) - }*/ + pub fn rpc_logger(self, max_log_len: u32) -> RpcServiceBuilder> { + RpcServiceBuilder(self.0.layer(layer::RpcLoggerLayer::new(max_log_len))) + } /// Wrap the service `S` with the middleware. pub fn service(&self, service: S) -> L::Service @@ -91,3 +96,28 @@ impl RpcServiceBuilder { self.0.service(service) } } + +/// Response which may be ready or a future. +#[derive(Debug)] +#[pin_project] +pub struct ResponseFuture(#[pin] futures_util::future::Either>); + +impl ResponseFuture { + /// Returns a future that resolves to a response. + pub fn future(f: F) -> ResponseFuture { + ResponseFuture(Either::Left(f)) + } + + /// Return a response which is already computed. + pub fn ready(response: MethodResponse) -> ResponseFuture { + ResponseFuture(Either::Right(std::future::ready(response))) + } +} + +impl> Future for ResponseFuture { + type Output = MethodResponse; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.project().0.poll(cx) + } +} diff --git a/core/src/server/method_response.rs b/core/src/server/method_response.rs index c84526a389..f505c17272 100644 --- a/core/src/server/method_response.rs +++ b/core/src/server/method_response.rs @@ -86,6 +86,11 @@ impl MethodResponse { matches!(self.kind, ResponseKind::MethodCall) } + /// Returns whether the response is a notification response. + pub fn is_notification(&self) -> bool { + matches!(self.kind, ResponseKind::Notification) + } + /// Returns whether the response is a batch response. pub fn is_batch(&self) -> bool { matches!(self.kind, ResponseKind::Batch) @@ -443,11 +448,11 @@ pub struct MethodResponseFuture(tokio::sync::oneshot::Receiver); /// was succesful or not. #[derive(Debug, Copy, Clone)] pub enum NotifyMsg { - /// The response was succesfully processed. + /// The response was successfully processed. Ok, /// The response was the wrong kind /// such an error response when - /// one expected a succesful response. + /// one expected a successful response. Err, } diff --git a/examples/examples/http.rs b/examples/examples/http.rs index 9e7525db9c..af17815128 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -29,7 +29,7 @@ use std::time::Duration; use hyper::body::Bytes; use jsonrpsee::core::client::ClientT; -use jsonrpsee::core::middleware::RpcServiceT; +use jsonrpsee::core::middleware::{Notification, RpcServiceT}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, RpcServiceBuilder, Server}; @@ -50,6 +50,16 @@ where println!("logger layer : {:?}", req); self.0.call(req) } + + fn batch(&self, reqs: Vec>) -> Self::Future { + println!("logger layer : {:?}", reqs); + self.0.batch(reqs) + } + + fn notification(&self, n: Notification<'a>) -> Self::Future { + println!("logger layer : {:?}", n); + self.0.notification(n) + } } #[tokio::main] diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index a2f9641292..3daa87fb87 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -39,9 +39,9 @@ use futures::FutureExt; use hyper::header::AUTHORIZATION; use hyper::HeaderMap; use jsonrpsee::core::async_trait; +use jsonrpsee::core::middleware::{Notification, ResponseFuture, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::middleware::rpc::{ResponseFuture, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::server::{ serve_with_graceful_shutdown, stop_channel, ServerConfig, ServerHandle, StopHandle, TowerServiceBuilder, }; @@ -91,6 +91,14 @@ where ResponseFuture::future(self.inner.call(req)) } } + + fn batch(&self, reqs: Vec>) -> Self::Future { + ResponseFuture::future(self.inner.batch(reqs)) + } + + fn notification(&self, n: Notification<'a>) -> Self::Future { + ResponseFuture::future(self.inner.notification(n)) + } } #[rpc(server, client)] diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index e7a458d58b..fc0f88d25c 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -47,9 +47,9 @@ use std::sync::{Arc, Mutex}; use futures::future::BoxFuture; use futures::FutureExt; use jsonrpsee::core::async_trait; +use jsonrpsee::core::middleware::{Notification, RpcServiceT}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::middleware::rpc::RpcServiceT; use jsonrpsee::server::{ http, serve_with_graceful_shutdown, stop_channel, ws, ConnectionGuard, ConnectionState, RpcServiceBuilder, ServerConfig, ServerHandle, StopHandle, @@ -98,6 +98,14 @@ where } .boxed() } + + fn batch(&self, reqs: Vec>) -> Self::Future { + Box::pin(self.service.batch(reqs)) + } + + fn notification(&self, n: Notification<'a>) -> Self::Future { + Box::pin(self.service.notification(n)) + } } #[rpc(server, client)] diff --git a/examples/examples/rpc_middleware.rs b/examples/examples/rpc_middleware.rs index 4783257014..f4bc071534 100644 --- a/examples/examples/rpc_middleware.rs +++ b/examples/examples/rpc_middleware.rs @@ -44,8 +44,8 @@ use std::sync::Arc; use futures::future::BoxFuture; use futures::FutureExt; use jsonrpsee::core::client::ClientT; +use jsonrpsee::core::middleware::{Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::rpc_params; -use jsonrpsee::server::middleware::rpc::{RpcServiceBuilder, RpcServiceT}; use jsonrpsee::server::{MethodResponse, RpcModule, Server}; use jsonrpsee::types::Request; use jsonrpsee::ws_client::WsClientBuilder; @@ -77,6 +77,14 @@ where } .boxed() } + + fn batch(&self, reqs: Vec>) -> Self::Future { + Box::pin(self.service.batch(reqs)) + } + + fn notification(&self, n: Notification<'a>) -> Self::Future { + Box::pin(self.service.notification(n)) + } } #[derive(Clone)] @@ -104,6 +112,14 @@ where } .boxed() } + + fn batch(&self, reqs: Vec>) -> Self::Future { + Box::pin(self.service.batch(reqs)) + } + + fn notification(&self, n: Notification<'a>) -> Self::Future { + Box::pin(self.service.notification(n)) + } } #[derive(Clone)] @@ -119,6 +135,14 @@ where println!("logger middleware: method `{}`", req.method); self.0.call(req) } + + fn batch(&self, reqs: Vec>) -> Self::Future { + self.0.batch(reqs) + } + + fn notification(&self, n: Notification<'a>) -> Self::Future { + self.0.notification(n) + } } #[tokio::main] diff --git a/examples/examples/rpc_middleware_modify_request.rs b/examples/examples/rpc_middleware_modify_request.rs index f0f75e3584..548375c09f 100644 --- a/examples/examples/rpc_middleware_modify_request.rs +++ b/examples/examples/rpc_middleware_modify_request.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use jsonrpsee::core::client::ClientT; -use jsonrpsee::server::middleware::rpc::{RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::server::Server; use jsonrpsee::types::Request; use jsonrpsee::ws_client::WsClientBuilder; @@ -57,6 +57,14 @@ where self.0.call(req) } + + fn batch(&self, _reqs: Vec>) -> Self::Future { + todo!(); + } + + fn notification(&self, _n: Notification<'a>) -> Self::Future { + todo!(); + } } #[tokio::main] diff --git a/examples/examples/rpc_middleware_rate_limiting.rs b/examples/examples/rpc_middleware_rate_limiting.rs index cf058a4a66..ee08a0a019 100644 --- a/examples/examples/rpc_middleware_rate_limiting.rs +++ b/examples/examples/rpc_middleware_rate_limiting.rs @@ -32,7 +32,7 @@ //! such as `Arc` use jsonrpsee::core::client::ClientT; -use jsonrpsee::server::middleware::rpc::{ResponseFuture, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Notification, ResponseFuture, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::server::Server; use jsonrpsee::types::{ErrorObject, Request}; use jsonrpsee::ws_client::WsClientBuilder; @@ -126,6 +126,14 @@ where ResponseFuture::future(self.service.call(req)) } } + + fn batch(&self, requests: Vec>) -> Self::Future { + ResponseFuture::future(self.service.batch(requests)) + } + + fn notification(&self, n: Notification<'a>) -> Self::Future { + ResponseFuture::future(self.service.notification(n)) + } } #[tokio::main] diff --git a/examples/examples/server_with_connection_details.rs b/examples/examples/server_with_connection_details.rs index 3e339a5b4c..bde27faf2d 100644 --- a/examples/examples/server_with_connection_details.rs +++ b/examples/examples/server_with_connection_details.rs @@ -27,9 +27,9 @@ use std::net::SocketAddr; use jsonrpsee::core::async_trait; +use jsonrpsee::core::middleware::{Notification, RpcServiceT}; use jsonrpsee::core::SubscriptionResult; use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::middleware::rpc::RpcServiceT; use jsonrpsee::server::{PendingSubscriptionSink, SubscriptionMessage}; use jsonrpsee::types::{ErrorObject, ErrorObjectOwned}; use jsonrpsee::ws_client::WsClientBuilder; @@ -60,6 +60,14 @@ impl<'a, S: RpcServiceT<'a>> RpcServiceT<'a> for LoggingMiddleware { self.0.call(request) } + + fn batch(&self, _requests: Vec>) -> Self::Future { + todo!(); + } + + fn notification(&self, _n: Notification<'a>) -> Self::Future { + todo!(); + } } pub struct RpcServerImpl; @@ -127,7 +135,7 @@ async fn main() -> anyhow::Result<()> { } async fn run_server() -> anyhow::Result { - let rpc_middleware = jsonrpsee::server::middleware::rpc::RpcServiceBuilder::new().layer_fn(LoggingMiddleware); + let rpc_middleware = jsonrpsee::server::RpcServiceBuilder::new().layer_fn(LoggingMiddleware); let server = jsonrpsee::server::Server::builder().set_rpc_middleware(rpc_middleware).build("127.0.0.1:0").await?; let addr = server.local_addr()?; diff --git a/server/src/lib.rs b/server/src/lib.rs index d949628b40..3dee440e63 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -44,10 +44,10 @@ mod tests; pub use future::{stop_channel, AlreadyStoppedError, ConnectionGuard, ConnectionPermit, ServerHandle, StopHandle}; pub use jsonrpsee_core::error::RegisterMethodError; +pub use jsonrpsee_core::middleware::RpcServiceBuilder; pub use jsonrpsee_core::server::*; pub use jsonrpsee_core::{id_providers::*, traits::IdProvider}; pub use jsonrpsee_types as types; -pub use middleware::rpc::RpcServiceBuilder; pub use server::{ BatchRequestConfig, Builder as ServerBuilder, ConnectionState, PingConfig, Server, ServerConfig, ServerConfigBuilder, TowerService, TowerServiceBuilder, diff --git a/server/src/middleware/mod.rs b/server/src/middleware/mod.rs index dff6948aa3..93b66a7b29 100644 --- a/server/src/middleware/mod.rs +++ b/server/src/middleware/mod.rs @@ -28,5 +28,4 @@ /// HTTP related middleware. pub mod http; -/// JSON-RPC specific middleware. pub mod rpc; diff --git a/server/src/middleware/rpc/layer/rpc_service.rs b/server/src/middleware/rpc.rs similarity index 86% rename from server/src/middleware/rpc/layer/rpc_service.rs rename to server/src/middleware/rpc.rs index 24d049477c..4a65e454b9 100644 --- a/server/src/middleware/rpc/layer/rpc_service.rs +++ b/server/src/middleware/rpc.rs @@ -26,14 +26,13 @@ //! JSON-RPC service middleware. -use super::ResponseFuture; use std::sync::Arc; -use crate::middleware::rpc::RpcServiceT; use crate::ConnectionId; -use futures_util::future::BoxFuture; +use futures_util::future::{BoxFuture, FutureExt}; +use jsonrpsee_core::middleware::{Notification, ResponseFuture, RpcServiceT}; use jsonrpsee_core::server::{ - BoundedSubscriptions, MethodCallback, MethodResponse, MethodSink, Methods, SubscriptionState, + BatchResponseBuilder, BoundedSubscriptions, MethodCallback, MethodResponse, MethodSink, Methods, SubscriptionState, }; use jsonrpsee_core::traits::IdProvider; use jsonrpsee_types::error::{reject_too_many_subscriptions, ErrorCode}; @@ -147,4 +146,24 @@ impl<'a> RpcServiceT<'a> for RpcService { }, } } + + fn batch(&self, reqs: Vec>) -> Self::Future { + let mut batch = BatchResponseBuilder::new_with_limit(self.max_response_body_size); + let service = self.clone(); + let fut = async move { + for req in reqs { + let rp = service.call(req).await; + if let Err(err) = batch.append(&rp) { + return err; + } + } + MethodResponse::from_batch(batch.finish()) + } + .boxed(); + ResponseFuture::future(fut) + } + + fn notification(&self, _: Notification<'a>) -> Self::Future { + ResponseFuture::ready(MethodResponse::notification()) + } } diff --git a/server/src/middleware/rpc/layer/mod.rs b/server/src/middleware/rpc/layer/mod.rs deleted file mode 100644 index 16a37c8852..0000000000 --- a/server/src/middleware/rpc/layer/mod.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any -// person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the -// Software without restriction, including without -// limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software -// is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice -// shall be included in all copies or substantial portions -// of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Specific middleware layer implementation provided by jsonrpsee. - -pub mod either; -pub mod logger; -pub mod rpc_service; - -pub use logger::*; -pub use rpc_service::*; - -use std::pin::Pin; -use std::task::{Context, Poll}; - -use futures_util::future::{Either, Future}; -use jsonrpsee_core::server::MethodResponse; -use pin_project::pin_project; - -/// Response which may be ready or a future. -#[derive(Debug)] -#[pin_project] -pub struct ResponseFuture(#[pin] futures_util::future::Either>); - -impl ResponseFuture { - /// Returns a future that resolves to a response. - pub fn future(f: F) -> ResponseFuture { - ResponseFuture(Either::Left(f)) - } - - /// Return a response which is already computed. - pub fn ready(response: MethodResponse) -> ResponseFuture { - ResponseFuture(Either::Right(std::future::ready(response))) - } -} - -impl> Future for ResponseFuture { - type Output = MethodResponse; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.project().0.poll(cx) - } -} diff --git a/server/src/server.rs b/server/src/server.rs index e06e837fd3..db1095a376 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -34,7 +34,7 @@ use std::task::Poll; use std::time::Duration; use crate::future::{session_close, ConnectionGuard, ServerHandle, SessionClose, SessionClosedFuture, StopHandle}; -use crate::middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}; +use crate::middleware::rpc::{RpcService, RpcServiceCfg}; use crate::transport::ws::BackgroundTaskParams; use crate::transport::{http, ws}; use crate::utils::deserialize; @@ -46,10 +46,9 @@ use futures_util::io::{BufReader, BufWriter}; use hyper::body::Bytes; use hyper_util::rt::{TokioExecutor, TokioIo}; use jsonrpsee_core::id_providers::RandomIntegerIdProvider; +use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::server::helpers::prepare_error; -use jsonrpsee_core::server::{ - BatchResponseBuilder, BoundedSubscriptions, ConnectionId, MethodResponse, MethodSink, Methods, -}; +use jsonrpsee_core::server::{BoundedSubscriptions, ConnectionId, MethodResponse, MethodSink, Methods}; use jsonrpsee_core::traits::IdProvider; use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; @@ -65,7 +64,7 @@ use tower::layer::util::Identity; use tower::{Layer, Service}; use tracing::{instrument, Instrument}; -type Notif<'a> = Notification<'a, Option<&'a JsonRawValue>>; +type Notif<'a> = Notification<'a, Option>>; /// Default maximum connections allowed. const MAX_CONNECTIONS: u32 = 100; @@ -1102,9 +1101,7 @@ where )); Box::pin(async move { - let rp = - http::call_with_service(request, batch_config, max_request_size, rpc_service, max_response_size) - .await; + let rp = http::call_with_service(request, batch_config, max_request_size, rpc_service).await; // NOTE: The `conn guard` must be held until the response is processed // to respect the `max_connections` limit. drop(conn); @@ -1230,22 +1227,21 @@ pub(crate) async fn handle_rpc_call( body: &[u8], is_single: bool, batch_config: BatchRequestConfig, - max_response_size: u32, rpc_service: &S, extensions: Extensions, -) -> Option +) -> MethodResponse where for<'a> S: RpcServiceT<'a> + Send, { // Single request or notification if is_single { if let Ok(req) = deserialize::from_slice_with_extensions(body, extensions) { - Some(rpc_service.call(req).await) - } else if let Ok(_notif) = serde_json::from_slice::(body) { - None + rpc_service.call(req).await + } else if let Ok(notif) = serde_json::from_slice::(body) { + rpc_service.notification(notif).await } else { let (id, code) = prepare_error(body); - Some(MethodResponse::error(id, ErrorObject::from(code))) + MethodResponse::error(id, ErrorObject::from(code)) } } // Batch of requests. @@ -1256,7 +1252,7 @@ where Id::Null, ErrorObject::borrowed(BATCHES_NOT_SUPPORTED_CODE, BATCHES_NOT_SUPPORTED_MSG, None), ); - return Some(rp); + return rp; } BatchRequestConfig::Limit(limit) => limit as usize, BatchRequestConfig::Unlimited => usize::MAX, @@ -1264,45 +1260,36 @@ where if let Ok(batch) = serde_json::from_slice::>(body) { if batch.len() > max_len { - return Some(MethodResponse::error(Id::Null, reject_too_big_batch_request(max_len))); + return MethodResponse::error(Id::Null, reject_too_big_batch_request(max_len)); } + let mut b = Vec::with_capacity(batch.len()); let mut got_notif = false; - let mut batch_response = BatchResponseBuilder::new_with_limit(max_response_size as usize); for call in batch { if let Ok(req) = deserialize::from_str_with_extensions(call.get(), extensions.clone()) { - let rp = rpc_service.call(req).await; - - if let Err(too_large) = batch_response.append(&rp) { - return Some(too_large); - } + b.push(req); } else if let Ok(_notif) = serde_json::from_str::(call.get()) { - // notifications should not be answered. got_notif = true; } else { - // valid JSON but could be not parsable as `InvalidRequest` let id = match serde_json::from_str::(call.get()) { Ok(err) => err.id, Err(_) => Id::Null, }; - if let Err(too_large) = - batch_response.append(&MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidRequest))) - { - return Some(too_large); - } + return MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidRequest)); } } - if got_notif && batch_response.is_empty() { - None + let batch_response = rpc_service.batch(b).await; + + if got_notif && batch_response.as_result().len() == 0 { + MethodResponse::notification() } else { - let batch_rp = batch_response.finish(); - Some(MethodResponse::from_batch(batch_rp)) + batch_response } } else { - Some(MethodResponse::error(Id::Null, ErrorObject::from(ErrorCode::ParseError))) + MethodResponse::error(Id::Null, ErrorObject::from(ErrorCode::ParseError)) } } } diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index 1d0b7d5cd7..f45f4bc30e 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -1,5 +1,5 @@ use crate::{ - middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}, + middleware::rpc::{RpcService, RpcServiceCfg}, server::{handle_rpc_call, ServerConfig}, BatchRequestConfig, ConnectionState, HttpRequest, HttpResponse, LOG_TARGET, }; @@ -7,6 +7,7 @@ use http::Method; use hyper::body::{Body, Bytes}; use jsonrpsee_core::{ http_helpers::{read_body, HttpError}, + middleware::{RpcServiceBuilder, RpcServiceT}, server::Methods, BoxError, }; @@ -55,9 +56,7 @@ where RpcServiceCfg::OnlyCalls, )); - let rp = - call_with_service(request, batch_requests_config, max_request_body_size, rpc_service, max_response_body_size) - .await; + let rp = call_with_service(request, batch_requests_config, max_request_body_size, rpc_service).await; drop(conn); @@ -72,7 +71,6 @@ pub async fn call_with_service( batch_config: BatchRequestConfig, max_request_size: u32, rpc_service: S, - max_response_size: u32, ) -> HttpResponse where B: http_body::Body + Send + 'static, @@ -95,12 +93,11 @@ where } }; - let rp = handle_rpc_call(&body, is_single, batch_config, max_response_size, &rpc_service, parts.extensions) - .await; + let rp = handle_rpc_call(&body, is_single, batch_config, &rpc_service, parts.extensions).await; // If the response is empty it means that it was a notification or empty batch. // For HTTP these are just ACK:ed with a empty body. - response::ok_response(rp.map_or(String::new(), |r| r.into_result())) + response::ok_response(rp.into_result()) } // Error scenarios: Method::POST => response::unsupported_content_type(), diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 8bccb14e25..2c3bf1abad 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use std::time::Instant; use crate::future::{IntervalStream, SessionClose}; -use crate::middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}; +use crate::middleware::rpc::{RpcService, RpcServiceCfg}; use crate::server::{handle_rpc_call, ConnectionState, ServerConfig}; use crate::{HttpBody, HttpRequest, HttpResponse, PingConfig, LOG_TARGET}; @@ -11,6 +11,7 @@ use futures_util::io::{BufReader, BufWriter}; use futures_util::{Future, StreamExt, TryStreamExt}; use hyper::upgrade::Upgraded; use hyper_util::rt::TokioIo; +use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::server::{BoundedSubscriptions, MethodSink, Methods}; use jsonrpsee_types::error::{reject_too_big_request, ErrorCode}; use jsonrpsee_types::Id; @@ -76,8 +77,7 @@ where mut on_session_close, extensions, } = params; - let ServerConfig { ping_config, batch_requests_config, max_request_body_size, max_response_body_size, .. } = - server_cfg; + let ServerConfig { ping_config, batch_requests_config, max_request_body_size, .. } = server_cfg; let (conn_tx, conn_rx) = oneshot::channel(); @@ -157,30 +157,21 @@ where } }; - if let Some(rp) = handle_rpc_call( - &data[idx..], - is_single, - batch_requests_config, - max_response_body_size, - &*rpc_service, - extensions, - ) - .await - { - if !rp.is_subscription() { - let is_success = rp.is_success(); - let (serialized_rp, mut on_close) = rp.into_parts(); - - // The connection is closed, just quit. - if sink.send(serialized_rp).await.is_err() { - return; - } + let rp = handle_rpc_call(&data[idx..], is_single, batch_requests_config, &*rpc_service, extensions).await; - // Notify that the message has been sent out to the internal - // WebSocket buffer. - if let Some(n) = on_close.take() { - n.notify(is_success); - } + if !rp.is_subscription() || !rp.is_notification() { + let is_success = rp.is_success(); + let (serialized_rp, mut on_close) = rp.into_parts(); + + // The connection is closed, just quit. + if sink.send(serialized_rp).await.is_err() { + return; + } + + // Notify that the message has been sent out to the internal + // WebSocket buffer. + if let Some(n) = on_close.take() { + n.notify(is_success); } } }); diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index 86481a7ab7..845634e08b 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -35,9 +35,10 @@ use std::time::Duration; use fast_socks5::client::Socks5Stream; use fast_socks5::server; use futures::{SinkExt, Stream, StreamExt}; +use jsonrpsee::core::middleware::Notification; use jsonrpsee::server::middleware::http::ProxyGetRequestLayer; -use jsonrpsee::server::middleware::rpc::RpcServiceT; +use jsonrpsee::core::middleware::RpcServiceT; use jsonrpsee::server::{ serve_with_graceful_shutdown, stop_channel, ConnectionGuard, PendingSubscriptionSink, RpcModule, RpcServiceBuilder, Server, ServerBuilder, ServerHandle, SubscriptionMessage, TrySendError, @@ -160,6 +161,14 @@ pub async fn server() -> SocketAddr { request.extensions_mut().insert(self.connection_id); self.inner.call(request) } + + fn batch(&self, requests: Vec>) -> Self::Future { + self.inner.batch(requests) + } + + fn notification(&self, n: Notification<'a>) -> Self::Future { + self.inner.notification(n) + } } let mut module = RpcModule::new(()); diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 81140a1d25..b0a4e98a45 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -1466,10 +1466,10 @@ async fn server_ws_low_api_works() { async fn run_server() -> anyhow::Result { use futures_util::future::FutureExt; - use jsonrpsee::core::BoxError; + use jsonrpsee::core::{middleware::RpcServiceBuilder, BoxError}; use jsonrpsee::server::{ - http, middleware::rpc::RpcServiceBuilder, serve_with_graceful_shutdown, stop_channel, ws, ConnectionGuard, - ConnectionState, Methods, ServerConfig, StopHandle, + http, serve_with_graceful_shutdown, stop_channel, ws, ConnectionGuard, ConnectionState, Methods, + ServerConfig, StopHandle, }; let listener = tokio::net::TcpListener::bind(std::net::SocketAddr::from(([127, 0, 0, 1], 0))).await?; diff --git a/tests/tests/metrics.rs b/tests/tests/metrics.rs index c8cef4d626..85e41e88e0 100644 --- a/tests/tests/metrics.rs +++ b/tests/tests/metrics.rs @@ -37,10 +37,10 @@ use std::time::Duration; use futures::future::BoxFuture; use futures::FutureExt; use helpers::init_logger; +use jsonrpsee::core::middleware::{RpcServiceBuilder, RpcServiceT}; use jsonrpsee::core::{client::ClientT, ClientError}; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::middleware::rpc::{RpcServiceBuilder, RpcServiceT}; use jsonrpsee::server::{Server, ServerHandle}; use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, Id, Request}; use jsonrpsee::ws_client::WsClientBuilder; @@ -97,6 +97,14 @@ where } .boxed() } + + fn batch(&self, _requests: Vec>) -> Self::Future { + todo!(); + } + + fn notification(&self, _n: jsonrpsee::core::middleware::Notification<'a>) -> Self::Future { + todo!(); + } } fn test_module() -> RpcModule<()> { From 93c9b6f395bc47c3dccb3ea0827a704399a77f8f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 26 Feb 2025 10:28:50 +0100 Subject: [PATCH 05/52] use Cow in Notification to avoid alloc --- client/http-client/src/client.rs | 2 +- client/http-client/src/rpc_service.rs | 4 ++-- core/src/middleware/layer/either.rs | 11 ++++------- core/src/middleware/mod.rs | 3 ++- server/src/server.rs | 10 ++++------ 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 2b24af8b64..bffd76437d 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -369,7 +369,7 @@ where Some(permit) => permit.acquire().await.ok(), None => None, }; - let params = params.to_rpc_params()?; + let params = params.to_rpc_params()?.map(StdCow::Owned); let n = Notification { jsonrpc: TwoPointZero, method: method.into(), params }; self.transport.notification(n).await; Ok(()) diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index 963dcc0b5a..02536c7a52 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use futures_util::{future::BoxFuture, FutureExt}; use hyper::body::Bytes; use jsonrpsee_core::{ - middleware::RpcServiceT, + middleware::{Notification, RpcServiceT}, server::{BatchResponseBuilder, MethodResponse, ResponsePayload}, BoxError, JsonRawValue, }; @@ -81,7 +81,7 @@ where .boxed() } - fn notification(&self, notif: jsonrpsee_types::Notification<'a, Option>>) -> Self::Future { + fn notification(&self, notif: Notification<'a>) -> Self::Future { let raw = serde_json::to_string(¬if).unwrap(); let service = self.service.clone(); diff --git a/core/src/middleware/layer/either.rs b/core/src/middleware/layer/either.rs index b64464d974..b7d3766fbe 100644 --- a/core/src/middleware/layer/either.rs +++ b/core/src/middleware/layer/either.rs @@ -31,7 +31,7 @@ //! work to implement tower::Layer for //! external types such as future::Either. -use crate::middleware::RpcServiceT; +use crate::middleware::{Notification, RpcServiceT}; use jsonrpsee_types::Request; /// [`tower::util::Either`] but @@ -80,13 +80,10 @@ where } } - fn notification( - &self, - request: jsonrpsee_types::Notification<'a, Option>>, - ) -> Self::Future { + fn notification(&self, n: Notification<'a>) -> Self::Future { match self { - Either::Left(service) => futures_util::future::Either::Left(service.notification(request)), - Either::Right(service) => futures_util::future::Either::Right(service.notification(request)), + Either::Left(service) => futures_util::future::Either::Left(service.notification(n)), + Either::Right(service) => futures_util::future::Either::Right(service.notification(n)), } } } diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 58cffed70e..f8d241f91d 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -9,13 +9,14 @@ use serde_json::value::RawValue; use tower::layer::util::{Identity, Stack}; use tower::layer::LayerFn; +use std::borrow::Cow; use std::pin::Pin; use std::task::{Context, Poll}; use crate::server::MethodResponse; /// JSON-RPC notification. -pub type Notification<'a> = jsonrpsee_types::Notification<'a, Option>>; +pub type Notification<'a> = jsonrpsee_types::Notification<'a, Option>>; /// Similar to the [`tower::Service`] but specific for jsonrpsee and /// doesn't requires `&mut self` for performance reasons. diff --git a/server/src/server.rs b/server/src/server.rs index 656b4d7d54..a4b9b3c440 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -46,7 +46,7 @@ use futures_util::io::{BufReader, BufWriter}; use hyper::body::Bytes; use hyper_util::rt::{TokioExecutor, TokioIo}; use jsonrpsee_core::id_providers::RandomIntegerIdProvider; -use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; +use jsonrpsee_core::middleware::{Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::server::helpers::prepare_error; use jsonrpsee_core::server::{BoundedSubscriptions, ConnectionId, MethodResponse, MethodSink, Methods}; use jsonrpsee_core::traits::IdProvider; @@ -55,7 +55,7 @@ use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; use jsonrpsee_types::error::{ reject_too_big_batch_request, ErrorCode, BATCHES_NOT_SUPPORTED_CODE, BATCHES_NOT_SUPPORTED_MSG, }; -use jsonrpsee_types::{ErrorObject, Id, InvalidRequest, Notification}; +use jsonrpsee_types::{ErrorObject, Id, InvalidRequest}; use soketto::handshake::http::is_upgrade_request; use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; use tokio::sync::{mpsc, watch, OwnedSemaphorePermit}; @@ -64,8 +64,6 @@ use tower::layer::util::Identity; use tower::{Layer, Service}; use tracing::{instrument, Instrument}; -type Notif<'a> = Notification<'a, Option>>; - /// Default maximum connections allowed. const MAX_CONNECTIONS: u32 = 100; @@ -1238,7 +1236,7 @@ where if is_single { if let Ok(req) = deserialize::from_slice_with_extensions(body, extensions) { rpc_service.call(req).await - } else if let Ok(notif) = serde_json::from_slice::(body) { + } else if let Ok(notif) = serde_json::from_slice::(body) { rpc_service.notification(notif).await } else { let (id, code) = prepare_error(body); @@ -1270,7 +1268,7 @@ where for call in unchecked_batch { if let Ok(req) = deserialize::from_str_with_extensions(call.get(), extensions.clone()) { batch.push(req); - } else if let Ok(_notif) = serde_json::from_str::(call.get()) { + } else if let Ok(_notif) = serde_json::from_str::(call.get()) { got_notif = true; } else { let id = match serde_json::from_str::(call.get()) { From 5929436015cdab81b636762059060d697f5d8f72 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 26 Feb 2025 10:55:08 +0100 Subject: [PATCH 06/52] cleanup some todos --- core/src/middleware/mod.rs | 5 +++-- examples/examples/jsonrpsee_as_service.rs | 9 +++------ examples/examples/rpc_middleware_modify_request.rs | 8 ++++---- examples/examples/server_with_connection_details.rs | 12 ++++++------ examples/examples/ws.rs | 2 +- tests/tests/metrics.rs | 12 ++++++------ 6 files changed, 23 insertions(+), 25 deletions(-) diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index f8d241f91d..d137f7cd95 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -3,7 +3,6 @@ pub mod layer; use futures_util::future::{Either, Future}; -use jsonrpsee_types::Request; use pin_project::pin_project; use serde_json::value::RawValue; use tower::layer::util::{Identity, Stack}; @@ -15,8 +14,10 @@ use std::task::{Context, Poll}; use crate::server::MethodResponse; -/// JSON-RPC notification. +/// Re-export types from `jsonrpsee_types` crate for convenience pub type Notification<'a> = jsonrpsee_types::Notification<'a, Option>>; +/// Re-export types from `jsonrpsee_types` crate for convenience +pub use jsonrpsee_types::Request; /// Similar to the [`tower::Service`] but specific for jsonrpsee and /// doesn't requires `&mut self` for performance reasons. diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index 3daa87fb87..decaa8b8d6 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -217,12 +217,9 @@ async fn run_server(metrics: Metrics) -> anyhow::Result { // NOTE, the rpc middleware must be initialized here to be able to created once per connection // with data from the connection such as the headers in this example let headers = req.headers().clone(); - let rpc_middleware = RpcServiceBuilder::new() /* .rpc_logger(1024)*/ - .layer_fn(move |service| AuthorizationMiddleware { - inner: service, - headers: headers.clone(), - transport_label, - }); + let rpc_middleware = RpcServiceBuilder::new().rpc_logger(1024).layer_fn(move |service| { + AuthorizationMiddleware { inner: service, headers: headers.clone(), transport_label } + }); let mut svc = svc_builder .set_http_middleware(http_middleware) diff --git a/examples/examples/rpc_middleware_modify_request.rs b/examples/examples/rpc_middleware_modify_request.rs index 548375c09f..09c62821e6 100644 --- a/examples/examples/rpc_middleware_modify_request.rs +++ b/examples/examples/rpc_middleware_modify_request.rs @@ -58,12 +58,12 @@ where self.0.call(req) } - fn batch(&self, _reqs: Vec>) -> Self::Future { - todo!(); + fn batch(&self, requests: Vec>) -> Self::Future { + self.0.batch(requests) } - fn notification(&self, _n: Notification<'a>) -> Self::Future { - todo!(); + fn notification(&self, n: Notification<'a>) -> Self::Future { + self.0.notification(n) } } diff --git a/examples/examples/server_with_connection_details.rs b/examples/examples/server_with_connection_details.rs index bde27faf2d..cd9c203698 100644 --- a/examples/examples/server_with_connection_details.rs +++ b/examples/examples/server_with_connection_details.rs @@ -27,7 +27,7 @@ use std::net::SocketAddr; use jsonrpsee::core::async_trait; -use jsonrpsee::core::middleware::{Notification, RpcServiceT}; +use jsonrpsee::core::middleware::{Notification, Request, RpcServiceT}; use jsonrpsee::core::SubscriptionResult; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{PendingSubscriptionSink, SubscriptionMessage}; @@ -54,19 +54,19 @@ struct LoggingMiddleware(S); impl<'a, S: RpcServiceT<'a>> RpcServiceT<'a> for LoggingMiddleware { type Future = S::Future; - fn call(&self, request: jsonrpsee::types::Request<'a>) -> Self::Future { + fn call(&self, request: Request<'a>) -> Self::Future { tracing::info!("Received request: {:?}", request); assert!(request.extensions().get::().is_some()); self.0.call(request) } - fn batch(&self, _requests: Vec>) -> Self::Future { - todo!(); + fn batch(&self, requests: Vec>) -> Self::Future { + self.0.batch(requests) } - fn notification(&self, _n: Notification<'a>) -> Self::Future { - todo!(); + fn notification(&self, n: Notification<'a>) -> Self::Future { + self.0.notification(n) } } diff --git a/examples/examples/ws.rs b/examples/examples/ws.rs index c84762d3b2..6587585cc3 100644 --- a/examples/examples/ws.rs +++ b/examples/examples/ws.rs @@ -50,7 +50,7 @@ async fn main() -> anyhow::Result<()> { } async fn run_server() -> anyhow::Result { - let rpc_middleware = RpcServiceBuilder::new()/* .rpc_logger(1024)*/; + let rpc_middleware = RpcServiceBuilder::new().rpc_logger(1024); let server = Server::builder().set_rpc_middleware(rpc_middleware).build("127.0.0.1:0").await?; let mut module = RpcModule::new(()); module.register_method("say_hello", |_, _, _| "lo")?; diff --git a/tests/tests/metrics.rs b/tests/tests/metrics.rs index 85e41e88e0..45d5a8c1d0 100644 --- a/tests/tests/metrics.rs +++ b/tests/tests/metrics.rs @@ -37,12 +37,12 @@ use std::time::Duration; use futures::future::BoxFuture; use futures::FutureExt; use helpers::init_logger; -use jsonrpsee::core::middleware::{RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Notification, Request, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::core::{client::ClientT, ClientError}; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{Server, ServerHandle}; -use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, Id, Request}; +use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, Id}; use jsonrpsee::ws_client::WsClientBuilder; use jsonrpsee::RpcModule; use jsonrpsee::{rpc_params, MethodResponse}; @@ -98,12 +98,12 @@ where .boxed() } - fn batch(&self, _requests: Vec>) -> Self::Future { - todo!(); + fn batch(&self, requests: Vec>) -> Self::Future { + self.service.batch(requests).boxed() } - fn notification(&self, _n: jsonrpsee::core::middleware::Notification<'a>) -> Self::Future { - todo!(); + fn notification(&self, n: Notification<'a>) -> Self::Future { + self.service.notification(n).boxed() } } From ff64b91966a782e124d5dde334ab7fcbc6fc90f9 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 11 Mar 2025 18:19:09 +0100 Subject: [PATCH 07/52] rpc trait: return Result --- client/http-client/src/client.rs | 20 ++++----- client/http-client/src/rpc_service.rs | 32 +++++++------- core/src/middleware/layer/either.rs | 3 +- core/src/middleware/layer/logger.rs | 17 +++++--- core/src/middleware/mod.rs | 32 +++++++++----- examples/examples/http.rs | 1 + examples/examples/jsonrpsee_as_service.rs | 6 ++- .../jsonrpsee_server_low_level_api.rs | 8 ++-- examples/examples/rpc_middleware.rs | 9 ++-- .../examples/rpc_middleware_modify_request.rs | 1 + .../examples/rpc_middleware_rate_limiting.rs | 3 +- .../server_with_connection_details.rs | 1 + server/src/middleware/rpc.rs | 42 ++++++++++--------- server/src/server.rs | 20 ++++++--- server/src/tests/http.rs | 11 ++--- server/src/transport/http.rs | 6 ++- server/src/transport/ws.rs | 5 ++- tests/tests/helpers.rs | 1 + tests/tests/metrics.rs | 5 ++- 19 files changed, 137 insertions(+), 86 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index a92f69a907..3237f09ad4 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -363,7 +363,7 @@ impl HttpClient { #[async_trait] impl ClientT for HttpClient where - for<'a> S: RpcServiceT<'a> + Send + Sync, + for<'a> S: RpcServiceT<'a, Error = Error> + Send + Sync, { #[instrument(name = "notification", skip(self, params), level = "trace")] async fn notification(&self, method: &str, params: Params) -> Result<(), Error> @@ -376,7 +376,7 @@ where }; let params = params.to_rpc_params()?.map(StdCow::Owned); let n = Notification { jsonrpc: TwoPointZero, method: method.into(), params }; - self.transport.notification(n).await; + self.transport.notification(n).await?; Ok(()) } @@ -394,7 +394,7 @@ where let params = params.to_rpc_params()?; let request = Request::new(method.into(), params.as_deref(), id.clone()); - let rp = self.transport.call(request).await; + let rp = self.transport.call(request).await?; // NOTE: it's decoded first to `JsonRawValue` and then to `R` below to get // a better error message if `R` couldn't be decoded. @@ -434,10 +434,10 @@ where }); } - let batch = self.transport.batch(batch_request).await; - let responses: Vec> = serde_json::from_str(&batch.as_result()).unwrap(); + let batch = self.transport.batch(batch_request).await?; + let responses: Vec> = serde_json::from_str(&batch.as_result())?; - let mut x = Vec::new(); + let mut res = Vec::new(); let mut success = 0; let mut failed = 0; @@ -445,24 +445,24 @@ where match ResponseSuccess::try_from(rp) { Ok(r) => { let v = serde_json::from_str(r.result.get()).map_err(Error::ParseError)?; - x.push(Ok(v)); + res.push(Ok(v)); success += 1; } Err(err) => { - x.push(Err(err)); + res.push(Err(err)); failed += 1; } }; } - Ok(BatchResponse::new(success, x, failed)) + Ok(BatchResponse::new(success, res, failed)) } } #[async_trait] impl SubscriptionClientT for HttpClient where - for<'a> S: RpcServiceT<'a> + Send + Sync, + for<'a> S: RpcServiceT<'a, Error = Error> + Send + Sync, { /// Send a subscription request to the server. Not implemented for HTTP; will always return /// [`Error::HttpNotImplemented`]. diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index 9a2a5f7782..7fec0bdd03 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -4,6 +4,7 @@ use futures_util::{FutureExt, future::BoxFuture}; use hyper::body::Bytes; use jsonrpsee_core::{ BoxError, JsonRawValue, + client::Error, middleware::{Notification, RpcServiceT}, server::{BatchResponseBuilder, MethodResponse, ResponsePayload}, }; @@ -12,7 +13,7 @@ use tower::Service; use crate::{ HttpRequest, HttpResponse, - transport::{Error, HttpTransportClient}, + transport::{Error as TransportError, HttpTransportClient}, }; #[derive(Clone, Debug)] @@ -29,13 +30,15 @@ impl RpcService { impl<'a, B, HttpMiddleware> RpcServiceT<'a> for RpcService where - HttpMiddleware: Service, Error = Error> + Clone + Send + Sync + 'static, + HttpMiddleware: + Service, Error = TransportError> + Clone + Send + Sync + 'static, HttpMiddleware::Future: Send, B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into, { - type Future = BoxFuture<'a, MethodResponse>; + type Future = BoxFuture<'a, Result>; + type Error = Error; fn call(&self, request: jsonrpsee_types::Request<'a>) -> Self::Future { let raw = serde_json::to_string(&request).unwrap(); @@ -43,9 +46,9 @@ where let max_response_size = self.max_response_size; async move { - let bytes = service.send_and_read_body(raw).await.map_err(BoxError::from).unwrap(); - let rp: Response> = serde_json::from_slice(&bytes).unwrap(); - MethodResponse::response(rp.id, rp.payload.into(), max_response_size as usize) + let bytes = service.send_and_read_body(raw).await.map_err(|e| Error::Transport(e.into()))?; + let rp: Response> = serde_json::from_slice(&bytes)?; + Ok(MethodResponse::response(rp.id, rp.payload.into(), max_response_size as usize)) } .boxed() } @@ -56,12 +59,12 @@ where let max_response_size = self.max_response_size; async move { - let bytes = service.send_and_read_body(raw).await.map_err(BoxError::from).unwrap(); - let json_rps: Vec> = serde_json::from_slice(&bytes).unwrap(); + let bytes = service.send_and_read_body(raw).await.map_err(|e| Error::Transport(e.into()))?; + let json_rps: Vec> = serde_json::from_slice(&bytes)?; let mut batch = BatchResponseBuilder::new_with_limit(max_response_size as usize); for rp in json_rps { - let id = rp.id.try_parse_inner_as_number().unwrap(); + let id = rp.id.try_parse_inner_as_number()?; let response = match ResponseSuccess::try_from(rp) { Ok(r) => { @@ -72,22 +75,23 @@ where }; if let Err(rp) = batch.append(response) { - return rp; + return Ok(rp); } } - MethodResponse::from_batch(batch.finish()) + Ok(MethodResponse::from_batch(batch.finish())) } .boxed() } fn notification(&self, notif: Notification<'a>) -> Self::Future { - let raw = serde_json::to_string(¬if).unwrap(); + let notif = serde_json::to_string(¬if); let service = self.service.clone(); async move { - service.send(raw).await.map_err(BoxError::from).unwrap(); - MethodResponse::notification() + let notif = notif?; + service.send(notif).await.map_err(|e| Error::Transport(e.into()))?; + Ok(MethodResponse::notification()) } .boxed() } diff --git a/core/src/middleware/layer/either.rs b/core/src/middleware/layer/either.rs index b7d3766fbe..400fa3d161 100644 --- a/core/src/middleware/layer/either.rs +++ b/core/src/middleware/layer/either.rs @@ -62,9 +62,10 @@ where impl<'a, A, B> RpcServiceT<'a> for Either where A: RpcServiceT<'a> + Send + 'a, - B: RpcServiceT<'a> + Send + 'a, + B: RpcServiceT<'a, Error = A::Error> + Send + 'a, { type Future = futures_util::future::Either; + type Error = A::Error; fn call(&self, request: Request<'a>) -> Self::Future { match self { diff --git a/core/src/middleware/layer/logger.rs b/core/src/middleware/layer/logger.rs index 865aab7d3e..8520b07f2d 100644 --- a/core/src/middleware/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -27,6 +27,7 @@ //! RPC Logger layer. use std::{ + convert::Infallible, pin::Pin, task::{Context, Poll}, }; @@ -69,9 +70,10 @@ pub struct RpcLogger { impl<'a, S> RpcServiceT<'a> for RpcLogger where - S: RpcServiceT<'a>, + S: RpcServiceT<'a, Error = Infallible>, { type Future = Instrumented>; + type Error = Infallible; #[tracing::instrument(name = "method_call", skip_all, fields(method = request.method_name()), level = "trace")] fn call(&self, request: Request<'a>) -> Self::Future { @@ -109,17 +111,20 @@ impl std::fmt::Debug for ResponseFuture { } } -impl> Future for ResponseFuture { +impl>> Future for ResponseFuture { type Output = F::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let max = self.max; let fut = self.project().fut; - let res = fut.poll(cx); - if let Poll::Ready(rp) = &res { - tx_log_from_str(rp.as_result(), max); + match fut.poll(cx) { + Poll::Ready(Ok(rp)) => { + tx_log_from_str(rp.as_result(), max); + Poll::Ready(Ok(rp)) + } + Poll::Ready(Err(e)) => match e {}, + Poll::Pending => Poll::Pending, } - res } } diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 2e3adfb9cd..4e73d4faeb 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -2,7 +2,7 @@ pub mod layer; -use futures_util::future::{Either, Future}; +use futures_util::future::{BoxFuture, Either, Future}; use pin_project::pin_project; use serde_json::value::RawValue; use tower::layer::LayerFn; @@ -19,11 +19,17 @@ pub type Notification<'a> = jsonrpsee_types::Notification<'a, Option = BoxFuture<'a, Result>; + /// Similar to the [`tower::Service`] but specific for jsonrpsee and /// doesn't requires `&mut self` for performance reasons. pub trait RpcServiceT<'a> { /// The future response value. - type Future: Future + Send; + type Future: Future> + Send; + + /// The error type. + type Error: std::error::Error + Send + Sync + 'static; /// Process a single JSON-RPC call it may be a subscription or regular call. /// @@ -102,24 +108,30 @@ impl RpcServiceBuilder { /// Response which may be ready or a future. #[derive(Debug)] #[pin_project] -pub struct ResponseFuture(#[pin] futures_util::future::Either>); +pub struct ResponseFuture(#[pin] futures_util::future::Either>>); -impl ResponseFuture { +impl ResponseFuture { /// Returns a future that resolves to a response. - pub fn future(f: F) -> ResponseFuture { + pub fn future(f: F) -> ResponseFuture { ResponseFuture(Either::Left(f)) } /// Return a response which is already computed. - pub fn ready(response: MethodResponse) -> ResponseFuture { - ResponseFuture(Either::Right(std::future::ready(response))) + pub fn ready(response: MethodResponse) -> ResponseFuture { + ResponseFuture(Either::Right(std::future::ready(Ok(response)))) } } -impl> Future for ResponseFuture { - type Output = MethodResponse; +impl Future for ResponseFuture +where + F: Future>, +{ + type Output = F::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.project().0.poll(cx) + match self.project().0.poll(cx) { + Poll::Ready(rp) => Poll::Ready(rp), + Poll::Pending => Poll::Pending, + } } } diff --git a/examples/examples/http.rs b/examples/examples/http.rs index ed381021d6..c7d7e6b8f6 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -45,6 +45,7 @@ where S: RpcServiceT<'a>, { type Future = S::Future; + type Error = S::Error; fn call(&self, req: Request<'a>) -> Self::Future { println!("logger layer : {:?}", req); diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index ebb8eebfab..a9c80f5404 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -38,8 +38,8 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use futures::FutureExt; use hyper::HeaderMap; use hyper::header::AUTHORIZATION; -use jsonrpsee::core::async_trait; use jsonrpsee::core::middleware::{Notification, ResponseFuture, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::{BoxError, async_trait}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{ @@ -73,8 +73,10 @@ struct AuthorizationMiddleware { impl<'a, S> RpcServiceT<'a> for AuthorizationMiddleware where S: Send + Clone + Sync + RpcServiceT<'a>, + S::Error: Into, { - type Future = ResponseFuture; + type Future = ResponseFuture; + type Error = S::Error; fn call(&self, req: Request<'a>) -> Self::Future { if req.method_name() == "trusted_call" { diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index 26607f35f1..e073ee55cf 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -46,8 +46,8 @@ use std::sync::{Arc, Mutex}; use futures::FutureExt; use futures::future::BoxFuture; -use jsonrpsee::core::async_trait; use jsonrpsee::core::middleware::{Notification, RpcServiceT}; +use jsonrpsee::core::{BoxError, async_trait}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{ @@ -76,8 +76,10 @@ struct CallLimit { impl<'a, S> RpcServiceT<'a> for CallLimit where S: Send + Sync + RpcServiceT<'a> + Clone + 'static, + S::Error: Into, { - type Future = BoxFuture<'a, MethodResponse>; + type Future = BoxFuture<'a, Result>; + type Error = S::Error; fn call(&self, req: Request<'a>) -> Self::Future { let count = self.count.clone(); @@ -89,7 +91,7 @@ where if *lock >= 10 { let _ = state.try_send(()); - MethodResponse::error(req.id, ErrorObject::borrowed(-32000, "RPC rate limit", None)) + Ok(MethodResponse::error(req.id, ErrorObject::borrowed(-32000, "RPC rate limit", None))) } else { let rp = service.call(req).await; *lock += 1; diff --git a/examples/examples/rpc_middleware.rs b/examples/examples/rpc_middleware.rs index c6525ca1c2..7881b6d14f 100644 --- a/examples/examples/rpc_middleware.rs +++ b/examples/examples/rpc_middleware.rs @@ -44,7 +44,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use futures::FutureExt; use futures::future::BoxFuture; use jsonrpsee::core::client::ClientT; -use jsonrpsee::core::middleware::{Notification, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{MethodResponseBoxFuture, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::rpc_params; use jsonrpsee::server::{MethodResponse, RpcModule, Server}; use jsonrpsee::types::Request; @@ -62,7 +62,8 @@ impl<'a, S> RpcServiceT<'a> for CallsPerConn where S: RpcServiceT<'a> + Send + Sync + Clone + 'static, { - type Future = BoxFuture<'a, MethodResponse>; + type Future = BoxFuture<'a, Result>; + type Error = S::Error; fn call(&self, req: Request<'a>) -> Self::Future { let count = self.count.clone(); @@ -97,7 +98,8 @@ impl<'a, S> RpcServiceT<'a> for GlobalCalls where S: RpcServiceT<'a> + Send + Sync + Clone + 'static, { - type Future = BoxFuture<'a, MethodResponse>; + type Future = MethodResponseBoxFuture<'a, Self::Error>; + type Error = S::Error; fn call(&self, req: Request<'a>) -> Self::Future { let count = self.count.clone(); @@ -130,6 +132,7 @@ where S: RpcServiceT<'a> + Send + Sync, { type Future = S::Future; + type Error = S::Error; fn call(&self, req: Request<'a>) -> Self::Future { println!("logger middleware: method `{}`", req.method); diff --git a/examples/examples/rpc_middleware_modify_request.rs b/examples/examples/rpc_middleware_modify_request.rs index 415bebe043..601713ffaa 100644 --- a/examples/examples/rpc_middleware_modify_request.rs +++ b/examples/examples/rpc_middleware_modify_request.rs @@ -41,6 +41,7 @@ where S: Send + Sync + RpcServiceT<'a>, { type Future = S::Future; + type Error = S::Error; fn call(&self, mut req: Request<'a>) -> Self::Future { // Example how to modify the params in the call. diff --git a/examples/examples/rpc_middleware_rate_limiting.rs b/examples/examples/rpc_middleware_rate_limiting.rs index d9405b7f14..c67bbba4ca 100644 --- a/examples/examples/rpc_middleware_rate_limiting.rs +++ b/examples/examples/rpc_middleware_rate_limiting.rs @@ -87,7 +87,8 @@ where // Instead of `Boxing` the future in this example // we are using a jsonrpsee's ResponseFuture future // type to avoid those extra allocations. - type Future = ResponseFuture; + type Future = ResponseFuture; + type Error = S::Error; fn call(&self, req: Request<'a>) -> Self::Future { let now = Instant::now(); diff --git a/examples/examples/server_with_connection_details.rs b/examples/examples/server_with_connection_details.rs index c910cb45d1..539322d112 100644 --- a/examples/examples/server_with_connection_details.rs +++ b/examples/examples/server_with_connection_details.rs @@ -51,6 +51,7 @@ struct LoggingMiddleware(S); impl<'a, S: RpcServiceT<'a>> RpcServiceT<'a> for LoggingMiddleware { type Future = S::Future; + type Error = S::Error; fn call(&self, request: Request<'a>) -> Self::Future { tracing::info!("Received request: {:?}", request); diff --git a/server/src/middleware/rpc.rs b/server/src/middleware/rpc.rs index 6470cddeac..3c59df0644 100644 --- a/server/src/middleware/rpc.rs +++ b/server/src/middleware/rpc.rs @@ -26,11 +26,12 @@ //! JSON-RPC service middleware. +use std::convert::Infallible; use std::sync::Arc; use crate::ConnectionId; -use futures_util::future::{BoxFuture, FutureExt}; -use jsonrpsee_core::middleware::{Notification, ResponseFuture, RpcServiceT}; +use futures_util::future::FutureExt; +use jsonrpsee_core::middleware::{MethodResponseBoxFuture, Notification, RpcServiceT}; use jsonrpsee_core::server::{ BatchResponseBuilder, BoundedSubscriptions, MethodCallback, MethodResponse, MethodSink, Methods, SubscriptionState, }; @@ -76,7 +77,8 @@ impl RpcService { impl<'a> RpcServiceT<'a> for RpcService { // The rpc module is already boxing the futures and // it's used to under the hood by the RpcService. - type Future = ResponseFuture>; + type Future = MethodResponseBoxFuture<'a, Self::Error>; + type Error = Infallible; fn call(&self, req: Request<'a>) -> Self::Future { let conn_id = self.conn_id; @@ -89,19 +91,18 @@ impl<'a> RpcServiceT<'a> for RpcService { None => { let rp = MethodResponse::error(id, ErrorObject::from(ErrorCode::MethodNotFound)).with_extensions(extensions); - ResponseFuture::ready(rp) + async move { Ok(rp) }.boxed() } Some((_name, method)) => match method { MethodCallback::Async(callback) => { let params = params.into_owned(); let id = id.into_owned(); - let fut = (callback)(id, params, conn_id, max_response_body_size, extensions); - ResponseFuture::future(fut) + (callback)(id, params, conn_id, max_response_body_size, extensions).map(Ok).boxed() } MethodCallback::Sync(callback) => { let rp = (callback)(id, params, max_response_body_size, extensions); - ResponseFuture::ready(rp) + async move { Ok(rp) }.boxed() } MethodCallback::Subscription(callback) => { let RpcServiceCfg::CallsAndSubscriptions { @@ -114,20 +115,19 @@ impl<'a> RpcServiceT<'a> for RpcService { tracing::warn!("Subscriptions not supported"); let rp = MethodResponse::error(id, ErrorObject::from(ErrorCode::InternalError)) .with_extensions(extensions); - return ResponseFuture::ready(rp); + return async move { Ok(rp) }.boxed(); }; if let Some(p) = bounded_subscriptions.acquire() { let conn_state = SubscriptionState { conn_id, id_provider: &*id_provider.clone(), subscription_permit: p }; - let fut = callback(id.clone(), params, sink, conn_state, extensions); - ResponseFuture::future(fut) + callback(id.clone(), params, sink, conn_state, extensions).map(Ok).boxed() } else { let max = bounded_subscriptions.max(); let rp = MethodResponse::error(id, reject_too_many_subscriptions(max)).with_extensions(extensions); - ResponseFuture::ready(rp) + async move { Ok(rp) }.boxed() } } MethodCallback::Unsubscription(callback) => { @@ -137,11 +137,11 @@ impl<'a> RpcServiceT<'a> for RpcService { tracing::warn!("Subscriptions not supported"); let rp = MethodResponse::error(id, ErrorObject::from(ErrorCode::InternalError)) .with_extensions(extensions); - return ResponseFuture::ready(rp); + return async move { Ok(rp) }.boxed(); }; let rp = callback(id, params, conn_id, max_response_body_size, extensions); - ResponseFuture::ready(rp) + async move { Ok(rp) }.boxed() } }, } @@ -150,20 +150,22 @@ impl<'a> RpcServiceT<'a> for RpcService { fn batch(&self, reqs: Vec>) -> Self::Future { let mut batch = BatchResponseBuilder::new_with_limit(self.max_response_body_size); let service = self.clone(); - let fut = async move { + async move { for req in reqs { - let rp = service.call(req).await; + let rp = match service.call(req).await { + Ok(rp) => rp, + Err(e) => match e {}, + }; if let Err(err) = batch.append(rp) { - return err; + return Ok(err); } } - MethodResponse::from_batch(batch.finish()) + Ok(MethodResponse::from_batch(batch.finish())) } - .boxed(); - ResponseFuture::future(fut) + .boxed() } fn notification(&self, _: Notification<'a>) -> Self::Future { - ResponseFuture::ready(MethodResponse::notification()) + async move { Ok(MethodResponse::notification()) }.boxed() } } diff --git a/server/src/server.rs b/server/src/server.rs index 6ddaee01c0..41461a090a 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -24,6 +24,7 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::convert::Infallible; use std::error::Error as StdError; use std::future::Future; use std::net::{SocketAddr, TcpListener as StdTcpListener}; @@ -964,7 +965,7 @@ impl Service> for TowerServiceNoHttp tower::Layer, >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT<'a>, + for<'a> >::Service: RpcServiceT<'a, Error = Infallible>, Body: http_body::Body + Send + 'static, Body::Error: Into, { @@ -1230,14 +1231,20 @@ pub(crate) async fn handle_rpc_call( extensions: Extensions, ) -> MethodResponse where - for<'a> S: RpcServiceT<'a> + Send, + for<'a> S: RpcServiceT<'a, Error = Infallible> + Send, { // Single request or notification if is_single { if let Ok(req) = deserialize::from_slice_with_extensions(body, extensions) { - rpc_service.call(req).await + match rpc_service.call(req).await { + Ok(rp) => rp, + Err(e) => match e {}, + } } else if let Ok(notif) = serde_json::from_slice::(body) { - rpc_service.notification(notif).await + match rpc_service.notification(notif).await { + Ok(rp) => rp, + Err(e) => match e {}, + } } else { let (id, code) = prepare_error(body); MethodResponse::error(id, ErrorObject::from(code)) @@ -1280,7 +1287,10 @@ where } } - let batch_response = rpc_service.batch(batch).await; + let batch_response = match rpc_service.batch(batch).await { + Ok(rp) => rp, + Err(e) => match e {}, + }; if got_notif && batch_response.as_result().len() == 0 { MethodResponse::notification() diff --git a/server/src/tests/http.rs b/server/src/tests/http.rs index 0f99f2a38b..a8461e5c56 100644 --- a/server/src/tests/http.rs +++ b/server/src/tests/http.rs @@ -28,12 +28,12 @@ use std::net::SocketAddr; use crate::types::Request; use crate::{ - BatchRequestConfig, HttpBody, HttpRequest, HttpResponse, MethodResponse, RegisterMethodError, RpcModule, - ServerBuilder, ServerConfig, ServerHandle, + BatchRequestConfig, HttpBody, HttpRequest, HttpResponse, RegisterMethodError, RpcModule, ServerBuilder, + ServerConfig, ServerHandle, }; -use futures_util::future::{BoxFuture, Future, FutureExt}; +use futures_util::future::{Future, FutureExt}; use hyper::body::Bytes; -use jsonrpsee_core::middleware::{Notification, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee_core::middleware::{MethodResponseBoxFuture, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::{BoxError, RpcResult}; use jsonrpsee_test_utils::TimeoutFutureExt; use jsonrpsee_test_utils::helpers::*; @@ -61,7 +61,8 @@ impl<'a, S> RpcServiceT<'a> for InjectExt where S: Send + Sync + RpcServiceT<'a> + Clone + 'static, { - type Future = BoxFuture<'a, MethodResponse>; + type Future = MethodResponseBoxFuture<'a, Self::Error>; + type Error = S::Error; fn call(&self, mut req: Request<'a>) -> Self::Future { if req.method_name().contains("err") { diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index f3f35dfef3..7255e49be6 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -1,3 +1,5 @@ +use std::convert::Infallible; + use crate::{ BatchRequestConfig, ConnectionState, HttpRequest, HttpResponse, LOG_TARGET, middleware::rpc::{RpcService, RpcServiceCfg}, @@ -45,7 +47,7 @@ where B::Error: Into, L: for<'a> tower::Layer, >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT<'a>, + for<'a> >::Service: RpcServiceT<'a, Error = Infallible> + Send, { let ServerConfig { max_response_body_size, batch_requests_config, max_request_body_size, .. } = server_cfg; @@ -76,7 +78,7 @@ where B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into, - for<'a> S: RpcServiceT<'a> + Send, + for<'a> S: RpcServiceT<'a, Error = Infallible> + Send, { // Only the `POST` method is allowed. match *request.method() { diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 684b917bdc..04314c9316 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -1,3 +1,4 @@ +use std::convert::Infallible; use std::sync::Arc; use std::time::Instant; @@ -63,7 +64,7 @@ pub(crate) struct BackgroundTaskParams { pub(crate) async fn background_task(params: BackgroundTaskParams) where - for<'a> S: RpcServiceT<'a> + Send + Sync + 'static, + for<'a> S: RpcServiceT<'a, Error = Infallible> + Send + Sync + 'static, { let BackgroundTaskParams { server_cfg, @@ -420,7 +421,7 @@ pub async fn connect( where L: for<'a> tower::Layer, >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT<'a>, + for<'a> >::Service: RpcServiceT<'a, Error = Infallible>, { let mut server = soketto::handshake::http::Server::new(); diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index fb278d6fa4..97a1f21ee5 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -156,6 +156,7 @@ pub async fn server() -> SocketAddr { S: RpcServiceT<'a>, { type Future = S::Future; + type Error = S::Error; fn call(&self, mut request: jsonrpsee::types::Request<'a>) -> Self::Future { request.extensions_mut().insert(self.connection_id); diff --git a/tests/tests/metrics.rs b/tests/tests/metrics.rs index b6a31bae9a..10bd72b73a 100644 --- a/tests/tests/metrics.rs +++ b/tests/tests/metrics.rs @@ -65,7 +65,8 @@ impl<'a, S> RpcServiceT<'a> for CounterMiddleware where S: RpcServiceT<'a> + Send + Sync + Clone + 'static, { - type Future = BoxFuture<'a, MethodResponse>; + type Future = BoxFuture<'a, Result>; + type Error = S::Error; fn call(&self, request: Request<'a>) -> Self::Future { let counter = self.counter.clone(); @@ -87,7 +88,7 @@ where { let mut n = counter.lock().unwrap(); n.requests.1 += 1; - if rp.is_success() { + if rp.as_ref().map_or(false, |r| r.is_success()) { n.calls.get_mut(&name).unwrap().1.push(id.into_owned()); } } From 9515e40a325aa5954f03f31d6c93ef8dd851b66b Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 12 Mar 2025 12:03:59 +0100 Subject: [PATCH 08/52] remove infallible err --- core/src/middleware/layer/logger.rs | 34 +++++++++++++------ core/src/middleware/mod.rs | 2 +- examples/examples/jsonrpsee_as_service.rs | 2 +- .../examples/rpc_middleware_rate_limiting.rs | 1 + server/src/server.rs | 22 ++++++++---- server/src/transport/http.rs | 6 ++-- server/src/transport/ws.rs | 5 ++- types/src/error.rs | 9 +++++ 8 files changed, 55 insertions(+), 26 deletions(-) diff --git a/core/src/middleware/layer/logger.rs b/core/src/middleware/layer/logger.rs index 8520b07f2d..e4b7e32564 100644 --- a/core/src/middleware/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -27,7 +27,7 @@ //! RPC Logger layer. use std::{ - convert::Infallible, + marker::PhantomData, pin::Pin, task::{Context, Poll}, }; @@ -70,48 +70,60 @@ pub struct RpcLogger { impl<'a, S> RpcServiceT<'a> for RpcLogger where - S: RpcServiceT<'a, Error = Infallible>, + S: RpcServiceT<'a>, + S::Error: Send, { - type Future = Instrumented>; - type Error = Infallible; + type Future = Instrumented>; + type Error = S::Error; #[tracing::instrument(name = "method_call", skip_all, fields(method = request.method_name()), level = "trace")] fn call(&self, request: Request<'a>) -> Self::Future { rx_log_from_json(&request, self.max); - ResponseFuture { fut: self.service.call(request), max: self.max }.in_current_span() + ResponseFuture::<_, Self::Error>::new(self.service.call(request), self.max).in_current_span() } #[tracing::instrument(name = "batch", skip_all, fields(method = "batch"), level = "trace")] fn batch(&self, requests: Vec>) -> Self::Future { rx_log_from_json(&requests, self.max); - ResponseFuture { fut: self.service.batch(requests), max: self.max }.in_current_span() + ResponseFuture::<_, Self::Error>::new(self.service.batch(requests), self.max).in_current_span() } #[tracing::instrument(name = "notification", skip_all, fields(method = &*n.method), level = "trace")] fn notification(&self, n: Notification<'a>) -> Self::Future { rx_log_from_json(&n, self.max); - ResponseFuture { fut: self.service.notification(n), max: self.max }.in_current_span() + ResponseFuture::<_, Self::Error>::new(self.service.notification(n), self.max).in_current_span() } } /// Response future to log the response for a method call. #[pin_project] -pub struct ResponseFuture { +pub struct ResponseFuture { #[pin] fut: F, max: u32, + _marker: std::marker::PhantomData, } -impl std::fmt::Debug for ResponseFuture { +impl ResponseFuture { + /// Create a new response future. + fn new(fut: F, max: u32) -> Self { + Self { fut, max, _marker: PhantomData } + } +} + +impl std::fmt::Debug for ResponseFuture { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("ResponseFuture") } } -impl>> Future for ResponseFuture { +impl Future for ResponseFuture +where + F: Future>, +{ type Output = F::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -123,7 +135,7 @@ impl>> Future for Response tx_log_from_str(rp.as_result(), max); Poll::Ready(Ok(rp)) } - Poll::Ready(Err(e)) => match e {}, + Poll::Ready(Err(e)) => Poll::Ready(Err(e)), Poll::Pending => Poll::Pending, } } diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 4e73d4faeb..5577dec049 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -29,7 +29,7 @@ pub trait RpcServiceT<'a> { type Future: Future> + Send; /// The error type. - type Error: std::error::Error + Send + Sync + 'static; + type Error: std::fmt::Debug; /// Process a single JSON-RPC call it may be a subscription or regular call. /// diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index a9c80f5404..66411af298 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -73,7 +73,7 @@ struct AuthorizationMiddleware { impl<'a, S> RpcServiceT<'a> for AuthorizationMiddleware where S: Send + Clone + Sync + RpcServiceT<'a>, - S::Error: Into, + S::Error: Into + Send, { type Future = ResponseFuture; type Error = S::Error; diff --git a/examples/examples/rpc_middleware_rate_limiting.rs b/examples/examples/rpc_middleware_rate_limiting.rs index c67bbba4ca..f1ba26514b 100644 --- a/examples/examples/rpc_middleware_rate_limiting.rs +++ b/examples/examples/rpc_middleware_rate_limiting.rs @@ -83,6 +83,7 @@ impl RateLimit { impl<'a, S> RpcServiceT<'a> for RateLimit where S: Send + RpcServiceT<'a>, + S::Error: Send, { // Instead of `Boxing` the future in this example // we are using a jsonrpsee's ResponseFuture future diff --git a/server/src/server.rs b/server/src/server.rs index 41461a090a..e361d39464 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -24,7 +24,6 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::convert::Infallible; use std::error::Error as StdError; use std::future::Future; use std::net::{SocketAddr, TcpListener as StdTcpListener}; @@ -55,6 +54,7 @@ use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; use jsonrpsee_types::error::{ BATCHES_NOT_SUPPORTED_CODE, BATCHES_NOT_SUPPORTED_MSG, ErrorCode, reject_too_big_batch_request, + rpc_middleware_error, }; use jsonrpsee_types::{ErrorObject, Id, InvalidRequest}; use soketto::handshake::http::is_upgrade_request; @@ -965,7 +965,7 @@ impl Service> for TowerServiceNoHttp tower::Layer, >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT<'a, Error = Infallible>, + for<'a> >::Service: RpcServiceT<'a>, Body: http_body::Body + Send + 'static, Body::Error: Into, { @@ -1231,19 +1231,27 @@ pub(crate) async fn handle_rpc_call( extensions: Extensions, ) -> MethodResponse where - for<'a> S: RpcServiceT<'a, Error = Infallible> + Send, + for<'a> S: RpcServiceT<'a> + Send, { // Single request or notification if is_single { if let Ok(req) = deserialize::from_slice_with_extensions(body, extensions) { + let id = req.id(); + match rpc_service.call(req).await { Ok(rp) => rp, - Err(e) => match e {}, + Err(err) => { + return MethodResponse::error(id, rpc_middleware_error(err)); + } } } else if let Ok(notif) = serde_json::from_slice::(body) { match rpc_service.notification(notif).await { Ok(rp) => rp, - Err(e) => match e {}, + Err(e) => { + // We don't care about the error if it's a notification. + tracing::debug!(target: LOG_TARGET, "Notification error: {:?}", e); + return MethodResponse::notification(); + } } } else { let (id, code) = prepare_error(body); @@ -1289,7 +1297,9 @@ where let batch_response = match rpc_service.batch(batch).await { Ok(rp) => rp, - Err(e) => match e {}, + Err(e) => { + return MethodResponse::error(Id::Null, rpc_middleware_error(e)); + } }; if got_notif && batch_response.as_result().len() == 0 { diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index 7255e49be6..cc65362e57 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -1,5 +1,3 @@ -use std::convert::Infallible; - use crate::{ BatchRequestConfig, ConnectionState, HttpRequest, HttpResponse, LOG_TARGET, middleware::rpc::{RpcService, RpcServiceCfg}, @@ -47,7 +45,7 @@ where B::Error: Into, L: for<'a> tower::Layer, >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT<'a, Error = Infallible> + Send, + for<'a> >::Service: RpcServiceT<'a> + Send, { let ServerConfig { max_response_body_size, batch_requests_config, max_request_body_size, .. } = server_cfg; @@ -78,7 +76,7 @@ where B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into, - for<'a> S: RpcServiceT<'a, Error = Infallible> + Send, + for<'a> S: RpcServiceT<'a> + Send, { // Only the `POST` method is allowed. match *request.method() { diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 04314c9316..684b917bdc 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -1,4 +1,3 @@ -use std::convert::Infallible; use std::sync::Arc; use std::time::Instant; @@ -64,7 +63,7 @@ pub(crate) struct BackgroundTaskParams { pub(crate) async fn background_task(params: BackgroundTaskParams) where - for<'a> S: RpcServiceT<'a, Error = Infallible> + Send + Sync + 'static, + for<'a> S: RpcServiceT<'a> + Send + Sync + 'static, { let BackgroundTaskParams { server_cfg, @@ -421,7 +420,7 @@ pub async fn connect( where L: for<'a> tower::Layer, >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT<'a, Error = Infallible>, + for<'a> >::Service: RpcServiceT<'a>, { let mut server = soketto::handshake::http::Server::new(); diff --git a/types/src/error.rs b/types/src/error.rs index 32f6a13c8c..0155083033 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -138,6 +138,8 @@ pub const SERVER_IS_BUSY_CODE: i32 = -32009; pub const TOO_BIG_BATCH_REQUEST_CODE: i32 = -32010; /// Batch response limit was exceed. pub const TOO_BIG_BATCH_RESPONSE_CODE: i32 = -32011; +/// Middleware error code. +pub const MIDDLEWARE_ERROR_CODE: i32 = -32012; /// Parse error message pub const PARSE_ERROR_MSG: &str = "Parse error"; @@ -165,6 +167,8 @@ pub const TOO_MANY_SUBSCRIPTIONS_MSG: &str = "Too many subscriptions on the conn pub const TOO_BIG_BATCH_REQUEST_MSG: &str = "The batch request was too large"; /// Batch request response limit was exceed. pub const TOO_BIG_BATCH_RESPONSE_MSG: &str = "The batch response was too large"; +/// Middleware error message. +pub const MIDDLEWARE_ERROR_MSG: &str = "Middleware error"; /// JSONRPC error code #[derive(Error, Debug, PartialEq, Eq, Copy, Clone)] @@ -296,6 +300,11 @@ pub fn reject_too_big_batch_response(limit: usize) -> ErrorObjectOwned { ) } +/// Helper to return a `JSON-RPC` error object when middleware encounters an internal error. +pub fn rpc_middleware_error(err: impl std::fmt::Debug) -> ErrorObjectOwned { + ErrorObjectOwned::owned(MIDDLEWARE_ERROR_CODE, MIDDLEWARE_ERROR_MSG, Some(format!("{:?}", err))) +} + #[cfg(test)] mod tests { use super::{ErrorCode, ErrorObject}; From 062ead323df253c0790eedca31d97cc831d0995f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 12 Mar 2025 15:30:03 +0100 Subject: [PATCH 09/52] make it compile --- client/http-client/src/rpc_service.rs | 2 +- client/http-client/src/tests.rs | 2 - core/Cargo.toml | 10 ++-- core/src/client/async_client/mod.rs | 8 +-- core/src/lib.rs | 1 + core/src/{server => }/method_response.rs | 75 ++++++++++++++++++++++-- core/src/middleware/mod.rs | 4 +- core/src/server/helpers.rs | 69 ---------------------- core/src/server/mod.rs | 4 +- core/src/server/rpc_module.rs | 12 ++-- server/src/server.rs | 4 +- server/src/transport/ws.rs | 4 +- 12 files changed, 95 insertions(+), 100 deletions(-) rename core/src/{server => }/method_response.rs (89%) diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index 7fec0bdd03..68f7c3d877 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -5,8 +5,8 @@ use hyper::body::Bytes; use jsonrpsee_core::{ BoxError, JsonRawValue, client::Error, + method_response::{BatchResponseBuilder, MethodResponse, ResponsePayload}, middleware::{Notification, RpcServiceT}, - server::{BatchResponseBuilder, MethodResponse, ResponsePayload}, }; use jsonrpsee_types::{Id, Response, ResponseSuccess}; use tower::Service; diff --git a/client/http-client/src/tests.rs b/client/http-client/src/tests.rs index 9a49f1cbde..5b620cf1b4 100644 --- a/client/http-client/src/tests.rs +++ b/client/http-client/src/tests.rs @@ -133,7 +133,6 @@ async fn internal_error_works() { } #[tokio::test] -#[ignore] async fn subscription_response_to_request() { let req = r#"{"jsonrpc":"2.0","method":"subscribe_hello","params":{"subscription":"3px4FrtxSYQ1zBKW154NoVnrDhrq764yQNCXEgZyM6Mu","result":"hello my friend"}}"#.to_string(); let err = run_request_with_response(req).with_default_timeout().await.unwrap().unwrap_err(); @@ -239,7 +238,6 @@ async fn batch_request_with_untagged_enum_works() { } #[tokio::test] -#[ignore] async fn batch_request_out_of_order_response() { let mut batch_request = BatchRequestBuilder::new(); batch_request.insert("say_hello", rpc_params![]).unwrap(); diff --git a/core/Cargo.toml b/core/Cargo.toml index 292a5fcfd6..c5d323ac0b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -25,7 +25,7 @@ serde_json = { workspace = true, features = ["std"] } tracing = { workspace = true } # optional deps -futures-util = { workspace = true, optional = true } +futures-util = { workspace = true, optional = true, features = ["alloc"] } http = { workspace = true, optional = true } bytes = { workspace = true, optional = true } http-body = { workspace = true, optional = true } @@ -45,11 +45,11 @@ wasm-bindgen-futures = { workspace = true, optional = true } [features] default = [] http-helpers = ["bytes", "futures-util", "http-body", "http-body-util", "http"] -server = ["futures-util/alloc", "rustc-hash/std", "parking_lot", "rand", "tokio/rt", "tokio/sync", "tokio/macros", "tokio/time", "tower", "http"] -client = ["futures-util/sink", "tokio/sync", "tower"] +server = ["futures-util", "rustc-hash/std", "parking_lot", "rand", "tokio/rt", "tokio/sync", "tokio/macros", "tokio/time", "tower", "http", "pin-project"] +client = ["futures-util/sink", "tokio/sync", "tower", "pin-project", "http"] async-client = [ "client", - "futures-util/alloc", + "futures-util", "rustc-hash", "tokio/macros", "tokio/rt", @@ -60,7 +60,7 @@ async-client = [ ] async-wasm-client = [ "client", - "futures-util/alloc", + "futures-util", "wasm-bindgen-futures", "rustc-hash/std", "futures-timer/wasm-bindgen", diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index 3ed6353dea..96ae3c62ae 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -30,7 +30,8 @@ mod helpers; mod manager; mod utils; -use crate::client::async_client::helpers::{process_subscription_close_response, InnerBatchResponse}; +use crate::JsonRawValue; +use crate::client::async_client::helpers::{InnerBatchResponse, process_subscription_close_response}; use crate::client::async_client::utils::MaybePendingFutures; use crate::client::{ BatchMessage, BatchResponse, ClientT, Error, ReceivedMessage, RegisterNotificationMessage, RequestMessage, @@ -40,7 +41,6 @@ use crate::error::RegisterMethodError; use crate::params::{BatchRequestBuilder, EmptyBatchRequest}; use crate::tracing::client::{rx_log_from_json, tx_log_from_str}; use crate::traits::ToRpcParams; -use crate::JsonRawValue; use std::borrow::Cow as StdCow; use core::time::Duration; @@ -54,9 +54,9 @@ use std::sync::Arc; use async_trait::async_trait; use futures_timer::Delay; +use futures_util::Stream; use futures_util::future::{self, Either}; use futures_util::stream::StreamExt; -use futures_util::Stream; use jsonrpsee_types::response::{ResponsePayload, SubscriptionError}; use jsonrpsee_types::{NotificationSer, RequestSer, Response, SubscriptionResponse}; use serde::de::DeserializeOwned; @@ -64,7 +64,7 @@ use tokio::sync::{mpsc, oneshot}; use tracing::instrument; use self::utils::{InactivityCheck, IntervalStream}; -use super::{generate_batch_id_range, subscription_channel, FrontToBack, IdKind, RequestIdManager}; +use super::{FrontToBack, IdKind, RequestIdManager, generate_batch_id_range, subscription_channel}; pub(crate) type Notification<'a> = jsonrpsee_types::Notification<'a, Option>; diff --git a/core/src/lib.rs b/core/src/lib.rs index 1199417b57..061ffc50fc 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -58,6 +58,7 @@ cfg_client! { cfg_client_or_server! { pub mod middleware; + pub mod method_response; } /// Shared tracing helpers to trace RPC calls. diff --git a/core/src/server/method_response.rs b/core/src/method_response.rs similarity index 89% rename from core/src/server/method_response.rs rename to core/src/method_response.rs index ae81c17cc6..78e069a678 100644 --- a/core/src/server/method_response.rs +++ b/core/src/method_response.rs @@ -24,13 +24,17 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::server::{BoundedWriter, LOG_TARGET}; +//! Represent a method response. + +const LOG_TARGET: &str = "jsonrpsee-core"; + +use std::io; use std::task::Poll; use futures_util::{Future, FutureExt}; use http::Extensions; use jsonrpsee_types::error::{ - reject_too_big_batch_response, ErrorCode, ErrorObject, OVERSIZED_RESPONSE_CODE, OVERSIZED_RESPONSE_MSG, + ErrorCode, ErrorObject, OVERSIZED_RESPONSE_CODE, OVERSIZED_RESPONSE_MSG, reject_too_big_batch_response, }; use jsonrpsee_types::{ErrorObjectOwned, Id, Response, ResponsePayload as InnerResponsePayload}; use serde::Serialize; @@ -487,10 +491,54 @@ impl Future for MethodResponseFuture { } } +/// Bounded writer that allows writing at most `max_len` bytes. +/// +/// ``` +/// use std::io::Write; +/// +/// use jsonrpsee_core::server::helpers::BoundedWriter; +/// +/// let mut writer = BoundedWriter::new(10); +/// (&mut writer).write("hello".as_bytes()).unwrap(); +/// assert_eq!(std::str::from_utf8(&writer.into_bytes()).unwrap(), "hello"); +/// ``` +#[derive(Debug, Clone)] +struct BoundedWriter { + max_len: usize, + buf: Vec, +} + +impl BoundedWriter { + /// Create a new bounded writer. + pub fn new(max_len: usize) -> Self { + Self { max_len, buf: Vec::with_capacity(128) } + } + + /// Consume the writer and extract the written bytes. + pub fn into_bytes(self) -> Vec { + self.buf + } +} + +impl io::Write for &mut BoundedWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + let len = self.buf.len() + buf.len(); + if self.max_len >= len { + self.buf.extend_from_slice(buf); + Ok(buf.len()) + } else { + Err(io::Error::new(io::ErrorKind::OutOfMemory, "Memory capacity exceeded")) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + #[cfg(test)] mod tests { - use super::{BatchResponseBuilder, MethodResponse, ResponsePayload}; - use jsonrpsee_types::Id; + use super::{BatchResponseBuilder, BoundedWriter, Id, MethodResponse, ResponsePayload}; #[test] fn batch_with_single_works() { @@ -540,4 +588,23 @@ mod tests { let exp_err = r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32011,"message":"The batch response was too large","data":"Exceeded max limit of 63"}}"#; assert_eq!(batch.result, exp_err); } + + #[test] + fn bounded_serializer_work() { + use jsonrpsee_types::{Response, ResponsePayload}; + + let mut writer = BoundedWriter::new(100); + let result = ResponsePayload::success(&"success"); + let rp = &Response::new(result, Id::Number(1)); + + assert!(serde_json::to_writer(&mut writer, rp).is_ok()); + assert_eq!(String::from_utf8(writer.into_bytes()).unwrap(), r#"{"jsonrpc":"2.0","id":1,"result":"success"}"#); + } + + #[test] + fn bounded_serializer_cap_works() { + let mut writer = BoundedWriter::new(100); + // NOTE: `"` is part of the serialization so 101 characters. + assert!(serde_json::to_writer(&mut writer, &"x".repeat(99)).is_err()); + } } diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 5577dec049..a54497128f 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -12,8 +12,8 @@ use std::borrow::Cow; use std::pin::Pin; use std::task::{Context, Poll}; -use crate::server::MethodResponse; - +/// Re-export `MethodResponse` for convenience. +pub use crate::method_response::MethodResponse; /// Re-export types from `jsonrpsee_types` crate for convenience pub type Notification<'a> = jsonrpsee_types::Notification<'a, Option>>; /// Re-export types from `jsonrpsee_types` crate for convenience diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index 1135155d30..cf480312ea 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -24,7 +24,6 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::io; use std::time::Duration; use jsonrpsee_types::{ErrorCode, ErrorObject, Id, InvalidRequest, Response, ResponsePayload}; @@ -32,51 +31,6 @@ use tokio::sync::mpsc; use super::{DisconnectError, SendTimeoutError, SubscriptionMessage, TrySendError}; -/// Bounded writer that allows writing at most `max_len` bytes. -/// -/// ``` -/// use std::io::Write; -/// -/// use jsonrpsee_core::server::helpers::BoundedWriter; -/// -/// let mut writer = BoundedWriter::new(10); -/// (&mut writer).write("hello".as_bytes()).unwrap(); -/// assert_eq!(std::str::from_utf8(&writer.into_bytes()).unwrap(), "hello"); -/// ``` -#[derive(Debug, Clone)] -pub struct BoundedWriter { - max_len: usize, - buf: Vec, -} - -impl BoundedWriter { - /// Create a new bounded writer. - pub fn new(max_len: usize) -> Self { - Self { max_len, buf: Vec::with_capacity(128) } - } - - /// Consume the writer and extract the written bytes. - pub fn into_bytes(self) -> Vec { - self.buf - } -} - -impl io::Write for &mut BoundedWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - let len = self.buf.len() + buf.len(); - if self.max_len >= len { - self.buf.extend_from_slice(buf); - Ok(buf.len()) - } else { - Err(io::Error::new(io::ErrorKind::OutOfMemory, "Memory capacity exceeded")) - } - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - /// Sink that is used to send back the result to the server for a specific method. #[derive(Clone, Debug)] pub struct MethodSink { @@ -171,26 +125,3 @@ pub fn prepare_error(data: &[u8]) -> (Id<'_>, ErrorCode) { Err(_) => (Id::Null, ErrorCode::ParseError), } } - -#[cfg(test)] -mod tests { - use crate::server::BoundedWriter; - use jsonrpsee_types::{Id, Response, ResponsePayload}; - - #[test] - fn bounded_serializer_work() { - let mut writer = BoundedWriter::new(100); - let result = ResponsePayload::success(&"success"); - let rp = &Response::new(result, Id::Number(1)); - - assert!(serde_json::to_writer(&mut writer, rp).is_ok()); - assert_eq!(String::from_utf8(writer.into_bytes()).unwrap(), r#"{"jsonrpc":"2.0","id":1,"result":"success"}"#); - } - - #[test] - fn bounded_serializer_cap_works() { - let mut writer = BoundedWriter::new(100); - // NOTE: `"` is part of the serialization so 101 characters. - assert!(serde_json::to_writer(&mut writer, &"x".repeat(99)).is_err()); - } -} diff --git a/core/src/server/mod.rs b/core/src/server/mod.rs index 8a187fafd3..48617085b7 100644 --- a/core/src/server/mod.rs +++ b/core/src/server/mod.rs @@ -30,17 +30,15 @@ mod error; /// Helpers. pub mod helpers; -/// Method response related types. -mod method_response; /// JSON-RPC "modules" group sets of methods that belong together and handles method/subscription registration. mod rpc_module; /// Subscription related types. mod subscription; +pub use crate::method_response::*; pub use error::*; pub use helpers::*; pub use http::Extensions; -pub use method_response::*; pub use rpc_module::*; pub use subscription::*; diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 4fcd5aab32..2bd67809e1 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -32,16 +32,16 @@ use std::sync::Arc; use crate::error::RegisterMethodError; use crate::id_providers::RandomIntegerIdProvider; +use crate::method_response::MethodResponse; use crate::server::helpers::MethodSink; -use crate::server::method_response::MethodResponse; use crate::server::subscription::{ - sub_message_to_json, BoundedSubscriptions, IntoSubscriptionCloseResponse, PendingSubscriptionSink, - SubNotifResultOrError, Subscribers, Subscription, SubscriptionCloseResponse, SubscriptionKey, SubscriptionPermit, - SubscriptionState, + BoundedSubscriptions, IntoSubscriptionCloseResponse, PendingSubscriptionSink, SubNotifResultOrError, Subscribers, + Subscription, SubscriptionCloseResponse, SubscriptionKey, SubscriptionPermit, SubscriptionState, + sub_message_to_json, }; -use crate::server::{ResponsePayload, LOG_TARGET}; +use crate::server::{LOG_TARGET, ResponsePayload}; use crate::traits::ToRpcParams; -use futures_util::{future::BoxFuture, FutureExt}; +use futures_util::{FutureExt, future::BoxFuture}; use http::Extensions; use jsonrpsee_types::error::{ErrorCode, ErrorObject}; use jsonrpsee_types::{ diff --git a/server/src/server.rs b/server/src/server.rs index e361d39464..23ef6c146b 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -1240,9 +1240,7 @@ where match rpc_service.call(req).await { Ok(rp) => rp, - Err(err) => { - return MethodResponse::error(id, rpc_middleware_error(err)); - } + Err(err) => MethodResponse::error(id, rpc_middleware_error(err)), } } else if let Ok(notif) = serde_json::from_slice::(body) { match rpc_service.notification(notif).await { diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 684b917bdc..be700c9f9b 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -159,7 +159,9 @@ where let rp = handle_rpc_call(&data[idx..], is_single, batch_requests_config, &*rpc_service, extensions).await; - if !rp.is_subscription() || !rp.is_notification() { + // Subscriptions are handled by the subscription callback and + // "ordinary notifications" should not be sent back to the client. + if rp.is_method_call() || rp.is_batch() { let is_success = rp.is_success(); let (serialized_rp, mut on_close, _) = rp.into_parts(); From d52740b30cb6c5fc976504e727745f0035b1a31f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 12 Mar 2025 16:19:03 +0100 Subject: [PATCH 10/52] fix tests --- client/http-client/src/client.rs | 36 +++++++++++++++++++++------ proc-macros/tests/ui/correct/basic.rs | 19 ++++++++++---- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 3237f09ad4..cc84561f91 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -43,7 +43,7 @@ use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::params::BatchRequestBuilder; use jsonrpsee_core::traits::ToRpcParams; use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; -use jsonrpsee_types::{InvalidRequestId, Notification, Request, ResponseSuccess, TwoPointZero}; +use jsonrpsee_types::{ErrorObject, InvalidRequestId, Notification, Request, ResponseSuccess, TwoPointZero}; use serde::de::DeserializeOwned; use tokio::sync::Semaphore; use tower::layer::util::Identity; @@ -398,6 +398,7 @@ where // NOTE: it's decoded first to `JsonRawValue` and then to `R` below to get // a better error message if `R` couldn't be decoded. + // TODO: this is inefficient, we should avoid double parsing. let response = ResponseSuccess::try_from(serde_json::from_str::>(&rp.as_result())?)?; let result = serde_json::from_str(response.result.get()).map_err(Error::ParseError)?; @@ -435,27 +436,46 @@ where } let batch = self.transport.batch(batch_request).await?; - let responses: Vec> = serde_json::from_str(&batch.as_result())?; + // TODO: this is inefficient, we should avoid double parsing. + let json_responses: Vec> = serde_json::from_str(&batch.as_result())?; - let mut res = Vec::new(); + let mut batch_response = Vec::new(); let mut success = 0; let mut failed = 0; - for rp in responses.into_iter() { - match ResponseSuccess::try_from(rp) { + // Fill the batch response with placeholder values. + for _ in 0..json_responses.len() { + batch_response.push(Err(ErrorObject::borrowed(0, "", None))); + } + + for rp in json_responses.into_iter() { + let id = rp.id.try_parse_inner_as_number()?; + + let res = match ResponseSuccess::try_from(rp) { Ok(r) => { let v = serde_json::from_str(r.result.get()).map_err(Error::ParseError)?; - res.push(Ok(v)); success += 1; + Ok(v) } Err(err) => { - res.push(Err(err)); failed += 1; + Err(err) } }; + + let maybe_elem = id + .checked_sub(id_range.start) + .and_then(|p| p.try_into().ok()) + .and_then(|p: usize| batch_response.get_mut(p)); + + if let Some(elem) = maybe_elem { + *elem = res; + } else { + return Err(InvalidRequestId::NotPendingRequest(id.to_string()).into()); + } } - Ok(BatchResponse::new(success, res, failed)) + Ok(BatchResponse::new(success, batch_response, failed)) } } diff --git a/proc-macros/tests/ui/correct/basic.rs b/proc-macros/tests/ui/correct/basic.rs index 1144674c29..745c380a97 100644 --- a/proc-macros/tests/ui/correct/basic.rs +++ b/proc-macros/tests/ui/correct/basic.rs @@ -4,12 +4,12 @@ use std::net::SocketAddr; use jsonrpsee::core::client::ClientT; use jsonrpsee::core::params::ArrayParams; -use jsonrpsee::core::{async_trait, RpcResult, SubscriptionResult}; +use jsonrpsee::core::{RpcResult, SubscriptionResult, async_trait}; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::SubscriptionMessage; use jsonrpsee::types::ErrorObject; use jsonrpsee::ws_client::*; -use jsonrpsee::{rpc_params, Extensions, PendingSubscriptionSink}; +use jsonrpsee::{Extensions, PendingSubscriptionSink, rpc_params}; #[rpc(client, server, namespace = "foo")] pub trait Rpc { @@ -135,10 +135,10 @@ impl RpcServer for RpcServerImpl { pub async fn server() -> SocketAddr { use hyper_util::rt::{TokioExecutor, TokioIo}; - use jsonrpsee::server::middleware::rpc::RpcServiceT; - use jsonrpsee::server::{stop_channel, RpcServiceBuilder}; + use jsonrpsee::core::middleware::{Notification, RpcServiceT}; + use jsonrpsee::server::{RpcServiceBuilder, stop_channel}; use std::convert::Infallible; - use std::sync::{atomic::AtomicU32, Arc}; + use std::sync::{Arc, atomic::AtomicU32}; use tower::Service; #[derive(Debug, Clone)] @@ -152,11 +152,20 @@ pub async fn server() -> SocketAddr { S: RpcServiceT<'a>, { type Future = S::Future; + type Error = S::Error; fn call(&self, mut request: jsonrpsee::types::Request<'a>) -> Self::Future { request.extensions_mut().insert(self.connection_id); self.inner.call(request) } + + fn batch(&self, requests: Vec>) -> Self::Future { + self.inner.batch(requests) + } + + fn notification(&self, notif: Notification<'a>) -> Self::Future { + self.inner.notification(notif) + } } let listener = tokio::net::TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))).await.unwrap(); From b5084703cbab0233e6d5ba69342c622d6b5eac91 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 19 Mar 2025 19:04:22 +0100 Subject: [PATCH 11/52] introduce client method response type --- client/http-client/src/client.rs | 51 ++++---- client/http-client/src/rpc_service.rs | 53 +++----- core/src/client/mod.rs | 116 +++++++++++++++++- core/src/method_response.rs | 8 +- core/src/middleware/layer/either.rs | 3 +- core/src/middleware/layer/logger.rs | 32 ++--- core/src/middleware/mod.rs | 19 +-- examples/examples/http.rs | 1 + examples/examples/jsonrpsee_as_service.rs | 6 +- .../jsonrpsee_server_low_level_api.rs | 5 +- examples/examples/rpc_middleware.rs | 9 +- .../examples/rpc_middleware_modify_request.rs | 1 + .../examples/rpc_middleware_rate_limiting.rs | 5 +- .../server_with_connection_details.rs | 1 + server/src/middleware/rpc.rs | 3 +- server/src/server.rs | 6 +- server/src/tests/http.rs | 3 +- server/src/transport/http.rs | 8 +- server/src/transport/ws.rs | 7 +- tests/tests/helpers.rs | 1 + tests/tests/metrics.rs | 5 +- types/src/lib.rs | 85 +++++++++++++ types/src/request.rs | 39 +++--- types/src/response.rs | 82 +++++++++++-- 24 files changed, 404 insertions(+), 145 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index cc84561f91..f57185c228 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -37,10 +37,12 @@ use async_trait::async_trait; use hyper::body::Bytes; use hyper::http::HeaderMap; use jsonrpsee_core::client::{ - BatchResponse, ClientT, Error, IdKind, RequestIdManager, Subscription, SubscriptionClientT, generate_batch_id_range, + BatchResponse, ClientT, Error, IdKind, MethodResponse, RequestIdManager, Subscription, SubscriptionClientT, + generate_batch_id_range, }; use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::params::BatchRequestBuilder; +use jsonrpsee_core::server::Extensions; use jsonrpsee_core::traits::ToRpcParams; use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; use jsonrpsee_types::{ErrorObject, InvalidRequestId, Notification, Request, ResponseSuccess, TwoPointZero}; @@ -304,7 +306,7 @@ where .map(|max_concurrent_requests| Arc::new(Semaphore::new(max_concurrent_requests))); Ok(HttpClient { - transport: rpc_middleware.service(RpcService::new(http, max_response_size)), + transport: rpc_middleware.service(RpcService::new(http)), id_manager: Arc::new(RequestIdManager::new(id_kind)), request_guard, }) @@ -363,7 +365,7 @@ impl HttpClient { #[async_trait] impl ClientT for HttpClient where - for<'a> S: RpcServiceT<'a, Error = Error> + Send + Sync, + for<'a> S: RpcServiceT<'a, Error = Error, Response = MethodResponse> + Send + Sync, { #[instrument(name = "notification", skip(self, params), level = "trace")] async fn notification(&self, method: &str, params: Params) -> Result<(), Error> @@ -375,8 +377,7 @@ where None => None, }; let params = params.to_rpc_params()?.map(StdCow::Owned); - let n = Notification { jsonrpc: TwoPointZero, method: method.into(), params }; - self.transport.notification(n).await?; + self.transport.notification(Notification::new(method.into(), params)).await?; Ok(()) } @@ -394,20 +395,11 @@ where let params = params.to_rpc_params()?; let request = Request::new(method.into(), params.as_deref(), id.clone()); - let rp = self.transport.call(request).await?; + let method_response = self.transport.call(request).await?; + let rp = method_response.as_method_call().expect("Transport::call must return a method call"); - // NOTE: it's decoded first to `JsonRawValue` and then to `R` below to get - // a better error message if `R` couldn't be decoded. - // TODO: this is inefficient, we should avoid double parsing. - let response = ResponseSuccess::try_from(serde_json::from_str::>(&rp.as_result())?)?; - - let result = serde_json::from_str(response.result.get()).map_err(Error::ParseError)?; - - if response.id == id { - Ok(result) - } else { - Err(InvalidRequestId::NotPendingRequest(response.id.to_string()).into()) - } + let result = rp.decode()?; + if rp.id() == &id { Ok(result) } else { Err(InvalidRequestId::NotPendingRequest(rp.id().to_string()).into()) } } #[instrument(name = "batch", skip(self, batch), level = "trace")] @@ -426,29 +418,30 @@ where let mut batch_request = Vec::with_capacity(batch.len()); for ((method, params), id) in batch.into_iter().zip(id_range.clone()) { let id = self.id_manager.as_id_kind().into_id(id); - batch_request.push(Request { - jsonrpc: TwoPointZero, + let req = Request { + jsonrpc: TwoPointZero::default(), method: method.into(), params: params.map(StdCow::Owned), - id, - extensions: Default::default(), - }); + id: id.into(), + extensions: Extensions::new(), + }; + batch_request.push(req); } - let batch = self.transport.batch(batch_request).await?; - // TODO: this is inefficient, we should avoid double parsing. - let json_responses: Vec> = serde_json::from_str(&batch.as_result())?; + let method_response = self.transport.batch(batch_request).await?; + let json_rps = method_response.as_batch().expect("Transport::batch must return a batch"); let mut batch_response = Vec::new(); let mut success = 0; let mut failed = 0; // Fill the batch response with placeholder values. - for _ in 0..json_responses.len() { + for _ in 0..json_rps.len() { batch_response.push(Err(ErrorObject::borrowed(0, "", None))); } - for rp in json_responses.into_iter() { + for json_rp in json_rps.into_iter() { + let rp: Response<&JsonRawValue> = serde_json::from_str(json_rp.get()).map_err(Error::ParseError)?; let id = rp.id.try_parse_inner_as_number()?; let res = match ResponseSuccess::try_from(rp) { @@ -482,7 +475,7 @@ where #[async_trait] impl SubscriptionClientT for HttpClient where - for<'a> S: RpcServiceT<'a, Error = Error> + Send + Sync, + for<'a> S: RpcServiceT<'a, Error = Error, Response = MethodResponse> + Send + Sync, { /// Send a subscription request to the server. Not implemented for HTTP; will always return /// [`Error::HttpNotImplemented`]. diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index 68f7c3d877..1d8d13e7cc 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -4,11 +4,11 @@ use futures_util::{FutureExt, future::BoxFuture}; use hyper::body::Bytes; use jsonrpsee_core::{ BoxError, JsonRawValue, - client::Error, - method_response::{BatchResponseBuilder, MethodResponse, ResponsePayload}, + client::{Error, MethodResponse}, middleware::{Notification, RpcServiceT}, + server::Extensions, }; -use jsonrpsee_types::{Id, Response, ResponseSuccess}; +use jsonrpsee_types::{Response, ResponseSuccess}; use tower::Service; use crate::{ @@ -19,12 +19,11 @@ use crate::{ #[derive(Clone, Debug)] pub struct RpcService { service: Arc>, - max_response_size: u32, } impl RpcService { - pub fn new(service: HttpTransportClient, max_response_size: u32) -> Self { - Self { service: Arc::new(service), max_response_size } + pub fn new(service: HttpTransportClient) -> Self { + Self { service: Arc::new(service) } } } @@ -37,61 +36,49 @@ where B::Data: Send, B::Error: Into, { - type Future = BoxFuture<'a, Result>; + type Future = BoxFuture<'a, Result>; type Error = Error; + type Response = MethodResponse; fn call(&self, request: jsonrpsee_types::Request<'a>) -> Self::Future { - let raw = serde_json::to_string(&request).unwrap(); let service = self.service.clone(); - let max_response_size = self.max_response_size; async move { + let raw = serde_json::to_string(&request)?; let bytes = service.send_and_read_body(raw).await.map_err(|e| Error::Transport(e.into()))?; - let rp: Response> = serde_json::from_slice(&bytes)?; - Ok(MethodResponse::response(rp.id, rp.payload.into(), max_response_size as usize)) + let json_rp: Response> = serde_json::from_slice(&bytes)?; + let success = ResponseSuccess::try_from(json_rp)?; + Ok(MethodResponse::method_call(success.result, request.extensions, success.id.into_owned())) } .boxed() } fn batch(&self, requests: Vec>) -> Self::Future { - let raw = serde_json::to_string(&requests).unwrap(); let service = self.service.clone(); - let max_response_size = self.max_response_size; async move { + let raw = serde_json::to_string(&requests)?; let bytes = service.send_and_read_body(raw).await.map_err(|e| Error::Transport(e.into()))?; - let json_rps: Vec> = serde_json::from_slice(&bytes)?; - let mut batch = BatchResponseBuilder::new_with_limit(max_response_size as usize); + let json: Vec> = serde_json::from_slice(&bytes)?; - for rp in json_rps { - let id = rp.id.try_parse_inner_as_number()?; + let mut extensions = Extensions::new(); - let response = match ResponseSuccess::try_from(rp) { - Ok(r) => { - let payload = ResponsePayload::success(r.result); - MethodResponse::response(r.id, payload, max_response_size as usize) - } - Err(err) => MethodResponse::error(Id::Number(id), err), - }; - - if let Err(rp) = batch.append(response) { - return Ok(rp); - } + for req in requests { + extensions.extend(req.extensions); } - Ok(MethodResponse::from_batch(batch.finish())) + Ok(MethodResponse::batch(json, extensions)) } .boxed() } fn notification(&self, notif: Notification<'a>) -> Self::Future { - let notif = serde_json::to_string(¬if); let service = self.service.clone(); async move { - let notif = notif?; - service.send(notif).await.map_err(|e| Error::Transport(e.into()))?; - Ok(MethodResponse::notification()) + let raw = serde_json::to_string(¬if)?; + service.send(raw).await.map_err(|e| Error::Transport(e.into()))?; + Ok(MethodResponse::notification(notif.extensions)) } .boxed() } diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index cea2442eaa..e22a104860 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -33,6 +33,9 @@ cfg_async_client! { pub mod error; pub use error::Error; +use http::Extensions; +use serde::Deserialize; +use serde_json::value::RawValue; use std::fmt; use std::ops::Range; @@ -320,11 +323,7 @@ impl Subscription { return None; } - if lagged { - Some(SubscriptionCloseReason::Lagged) - } else { - Some(SubscriptionCloseReason::ConnectionClosed) - } + if lagged { Some(SubscriptionCloseReason::Lagged) } else { Some(SubscriptionCloseReason::ConnectionClosed) } } } @@ -650,3 +649,110 @@ fn subscription_channel(max_buf_size: usize) -> (SubscriptionSender, Subscriptio (SubscriptionSender { inner: tx, lagged: lagged_tx }, SubscriptionReceiver { inner: rx, lagged: lagged_rx }) } + +#[derive(Debug, Clone)] +enum MethodResponseKind { + MethodCall(MethodCall), + Notification, + Batch(Vec>), +} + +/// Represents a method call from the server. +#[derive(Debug, Clone)] +pub struct MethodCall { + json: Box, + id: Id<'static>, +} + +impl MethodCall { + /// Consume the method call and return the raw JSON value. + pub fn into_json(self) -> Box { + self.json + } + + /// Get the ID of the method call. + pub fn id(&self) -> &Id<'static> { + &self.id + } + + /// Decode the JSON value into the desired type. + pub fn decode<'a, T: Deserialize<'a>>(&'a self) -> Result { + serde_json::from_str(self.json.get()) + } +} + +/// Represents a response from the server which can be a method call, notification or batch. +#[derive(Debug, Clone)] +pub struct MethodResponse { + extensions: Extensions, + inner: MethodResponseKind, +} + +impl MethodResponse { + /// Create a new method response. + pub fn method_call(json: Box, extensions: Extensions, id: Id<'static>) -> Self { + Self { inner: MethodResponseKind::MethodCall(MethodCall { json, id }), extensions } + } + + /// Create a new notification response. + pub fn notification(extensions: Extensions) -> Self { + Self { inner: MethodResponseKind::Notification, extensions } + } + + /// Create a new batch response. + pub fn batch(json: Vec>, extensions: Extensions) -> Self { + Self { inner: MethodResponseKind::Batch(json), extensions } + } + + /// Consume the response and return the raw JSON value. + pub fn into_json(self) -> Box { + match self.inner { + MethodResponseKind::MethodCall(call) => call.json, + MethodResponseKind::Notification => panic!("MethodResponse::into_json called on a notification"), + MethodResponseKind::Batch(json) => { + serde_json::value::to_raw_value(&json).expect("Batch serialization failed") + } + } + } + + /// Get the method call if this response is a method call. + pub fn as_method_call(&self) -> Option<&MethodCall> { + match &self.inner { + MethodResponseKind::MethodCall(call) => Some(call), + _ => None, + } + } + + /// Get the batch if this response is a batch. + pub fn as_batch(&self) -> Option<&[Box]> { + match &self.inner { + MethodResponseKind::Batch(batch) => Some(batch), + _ => None, + } + } + + /// Returns whether this response is a method call. + pub fn is_method_call(&self) -> bool { + matches!(self.inner, MethodResponseKind::MethodCall(_)) + } + + /// Returns whether this response is a notification. + pub fn is_notification(&self) -> bool { + matches!(self.inner, MethodResponseKind::Notification) + } + + /// Returns whether this response is a batch. + pub fn is_batch(&self) -> bool { + matches!(self.inner, MethodResponseKind::Batch(_)) + } + + /// Returns a reference to the associated extensions. + pub fn extensions(&self) -> &Extensions { + &self.extensions + } + + /// Returns a mutable reference to the associated extensions. + pub fn extensions_mut(&mut self) -> &mut Extensions { + &mut self.extensions + } +} diff --git a/core/src/method_response.rs b/core/src/method_response.rs index 78e069a678..06d0441ee1 100644 --- a/core/src/method_response.rs +++ b/core/src/method_response.rs @@ -69,6 +69,12 @@ pub struct MethodResponse { extensions: Extensions, } +impl AsRef for MethodResponse { + fn as_ref(&self) -> &str { + self.as_result() + } +} + impl MethodResponse { /// Returns whether the call was successful. pub fn is_success(&self) -> bool { @@ -252,7 +258,7 @@ impl MethodResponse { &self.extensions } - /// Returns a reference to the associated extensions. + /// Returns a mut reference to the associated extensions. pub fn extensions_mut(&mut self) -> &mut Extensions { &mut self.extensions } diff --git a/core/src/middleware/layer/either.rs b/core/src/middleware/layer/either.rs index 400fa3d161..ab846373bf 100644 --- a/core/src/middleware/layer/either.rs +++ b/core/src/middleware/layer/either.rs @@ -62,10 +62,11 @@ where impl<'a, A, B> RpcServiceT<'a> for Either where A: RpcServiceT<'a> + Send + 'a, - B: RpcServiceT<'a, Error = A::Error> + Send + 'a, + B: RpcServiceT<'a, Error = A::Error, Response = A::Response> + Send + 'a, { type Future = futures_util::future::Either; type Error = A::Error; + type Response = A::Response; fn call(&self, request: Request<'a>) -> Self::Future { match self { diff --git a/core/src/middleware/layer/logger.rs b/core/src/middleware/layer/logger.rs index e4b7e32564..5604c6f9ae 100644 --- a/core/src/middleware/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -27,13 +27,12 @@ //! RPC Logger layer. use std::{ - marker::PhantomData, pin::Pin, task::{Context, Poll}, }; use crate::{ - middleware::{MethodResponse, Notification, RpcServiceT}, + middleware::{Notification, RpcServiceT}, tracing::server::{rx_log_from_json, tx_log_from_str}, }; @@ -71,58 +70,61 @@ pub struct RpcLogger { impl<'a, S> RpcServiceT<'a> for RpcLogger where S: RpcServiceT<'a>, - S::Error: Send, + S::Error: std::fmt::Debug + Send, + S::Response: AsRef, { - type Future = Instrumented>; + type Future = Instrumented>; type Error = S::Error; + type Response = S::Response; #[tracing::instrument(name = "method_call", skip_all, fields(method = request.method_name()), level = "trace")] fn call(&self, request: Request<'a>) -> Self::Future { rx_log_from_json(&request, self.max); - ResponseFuture::<_, Self::Error>::new(self.service.call(request), self.max).in_current_span() + ResponseFuture::new(self.service.call(request), self.max).in_current_span() } #[tracing::instrument(name = "batch", skip_all, fields(method = "batch"), level = "trace")] fn batch(&self, requests: Vec>) -> Self::Future { rx_log_from_json(&requests, self.max); - ResponseFuture::<_, Self::Error>::new(self.service.batch(requests), self.max).in_current_span() + ResponseFuture::new(self.service.batch(requests), self.max).in_current_span() } #[tracing::instrument(name = "notification", skip_all, fields(method = &*n.method), level = "trace")] fn notification(&self, n: Notification<'a>) -> Self::Future { rx_log_from_json(&n, self.max); - ResponseFuture::<_, Self::Error>::new(self.service.notification(n), self.max).in_current_span() + ResponseFuture::new(self.service.notification(n), self.max).in_current_span() } } /// Response future to log the response for a method call. #[pin_project] -pub struct ResponseFuture { +pub struct ResponseFuture { #[pin] fut: F, max: u32, - _marker: std::marker::PhantomData, } -impl ResponseFuture { +impl ResponseFuture { /// Create a new response future. fn new(fut: F, max: u32) -> Self { - Self { fut, max, _marker: PhantomData } + Self { fut, max } } } -impl std::fmt::Debug for ResponseFuture { +impl std::fmt::Debug for ResponseFuture { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("ResponseFuture") } } -impl Future for ResponseFuture +impl Future for ResponseFuture where - F: Future>, + F: Future>, + R: AsRef, + E: std::fmt::Debug, { type Output = F::Output; @@ -132,7 +134,7 @@ where match fut.poll(cx) { Poll::Ready(Ok(rp)) => { - tx_log_from_str(rp.as_result(), max); + tx_log_from_str(&rp, max); Poll::Ready(Ok(rp)) } Poll::Ready(Err(e)) => Poll::Ready(Err(e)), diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index a54497128f..b7e28b46bb 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -20,17 +20,20 @@ pub type Notification<'a> = jsonrpsee_types::Notification<'a, Option = BoxFuture<'a, Result>; +pub type MethodResponseBoxFuture<'a, R, E> = BoxFuture<'a, Result>; /// Similar to the [`tower::Service`] but specific for jsonrpsee and /// doesn't requires `&mut self` for performance reasons. pub trait RpcServiceT<'a> { /// The future response value. - type Future: Future> + Send; + type Future: Future> + Send; /// The error type. type Error: std::fmt::Debug; + /// The response type + type Response; + /// Process a single JSON-RPC call it may be a subscription or regular call. /// /// In this interface both are treated in the same way but it's possible to @@ -108,23 +111,23 @@ impl RpcServiceBuilder { /// Response which may be ready or a future. #[derive(Debug)] #[pin_project] -pub struct ResponseFuture(#[pin] futures_util::future::Either>>); +pub struct ResponseFuture(#[pin] futures_util::future::Either>>); -impl ResponseFuture { +impl ResponseFuture { /// Returns a future that resolves to a response. - pub fn future(f: F) -> ResponseFuture { + pub fn future(f: F) -> ResponseFuture { ResponseFuture(Either::Left(f)) } /// Return a response which is already computed. - pub fn ready(response: MethodResponse) -> ResponseFuture { + pub fn ready(response: R) -> ResponseFuture { ResponseFuture(Either::Right(std::future::ready(Ok(response)))) } } -impl Future for ResponseFuture +impl Future for ResponseFuture where - F: Future>, + F: Future>, { type Output = F::Output; diff --git a/examples/examples/http.rs b/examples/examples/http.rs index c7d7e6b8f6..4e72d2927c 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -46,6 +46,7 @@ where { type Future = S::Future; type Error = S::Error; + type Response = S::Response; fn call(&self, req: Request<'a>) -> Self::Future { println!("logger layer : {:?}", req); diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index 66411af298..4d814b2d6d 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -72,11 +72,13 @@ struct AuthorizationMiddleware { impl<'a, S> RpcServiceT<'a> for AuthorizationMiddleware where - S: Send + Clone + Sync + RpcServiceT<'a>, + S: Send + Clone + Sync + RpcServiceT<'a, Response = MethodResponse>, S::Error: Into + Send, + S::Response: Send, { - type Future = ResponseFuture; + type Future = ResponseFuture; type Error = S::Error; + type Response = S::Response; fn call(&self, req: Request<'a>) -> Self::Future { if req.method_name() == "trusted_call" { diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index e073ee55cf..9d9c9371ef 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -75,11 +75,12 @@ struct CallLimit { impl<'a, S> RpcServiceT<'a> for CallLimit where - S: Send + Sync + RpcServiceT<'a> + Clone + 'static, + S: Send + Sync + RpcServiceT<'a, Response = MethodResponse> + Clone + 'static, S::Error: Into, { - type Future = BoxFuture<'a, Result>; + type Future = BoxFuture<'a, Result>; type Error = S::Error; + type Response = S::Response; fn call(&self, req: Request<'a>) -> Self::Future { let count = self.count.clone(); diff --git a/examples/examples/rpc_middleware.rs b/examples/examples/rpc_middleware.rs index 7881b6d14f..d2d317d418 100644 --- a/examples/examples/rpc_middleware.rs +++ b/examples/examples/rpc_middleware.rs @@ -46,7 +46,7 @@ use futures::future::BoxFuture; use jsonrpsee::core::client::ClientT; use jsonrpsee::core::middleware::{MethodResponseBoxFuture, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::rpc_params; -use jsonrpsee::server::{MethodResponse, RpcModule, Server}; +use jsonrpsee::server::{RpcModule, Server}; use jsonrpsee::types::Request; use jsonrpsee::ws_client::WsClientBuilder; @@ -62,8 +62,9 @@ impl<'a, S> RpcServiceT<'a> for CallsPerConn where S: RpcServiceT<'a> + Send + Sync + Clone + 'static, { - type Future = BoxFuture<'a, Result>; + type Future = BoxFuture<'a, Result>; type Error = S::Error; + type Response = S::Response; fn call(&self, req: Request<'a>) -> Self::Future { let count = self.count.clone(); @@ -98,8 +99,9 @@ impl<'a, S> RpcServiceT<'a> for GlobalCalls where S: RpcServiceT<'a> + Send + Sync + Clone + 'static, { - type Future = MethodResponseBoxFuture<'a, Self::Error>; + type Future = MethodResponseBoxFuture<'a, Self::Response, Self::Error>; type Error = S::Error; + type Response = S::Response; fn call(&self, req: Request<'a>) -> Self::Future { let count = self.count.clone(); @@ -133,6 +135,7 @@ where { type Future = S::Future; type Error = S::Error; + type Response = S::Response; fn call(&self, req: Request<'a>) -> Self::Future { println!("logger middleware: method `{}`", req.method); diff --git a/examples/examples/rpc_middleware_modify_request.rs b/examples/examples/rpc_middleware_modify_request.rs index 601713ffaa..22d4650f0a 100644 --- a/examples/examples/rpc_middleware_modify_request.rs +++ b/examples/examples/rpc_middleware_modify_request.rs @@ -42,6 +42,7 @@ where { type Future = S::Future; type Error = S::Error; + type Response = S::Response; fn call(&self, mut req: Request<'a>) -> Self::Future { // Example how to modify the params in the call. diff --git a/examples/examples/rpc_middleware_rate_limiting.rs b/examples/examples/rpc_middleware_rate_limiting.rs index f1ba26514b..1fc73047f6 100644 --- a/examples/examples/rpc_middleware_rate_limiting.rs +++ b/examples/examples/rpc_middleware_rate_limiting.rs @@ -82,14 +82,15 @@ impl RateLimit { impl<'a, S> RpcServiceT<'a> for RateLimit where - S: Send + RpcServiceT<'a>, + S: Send + RpcServiceT<'a, Response = MethodResponse> + 'static, S::Error: Send, { // Instead of `Boxing` the future in this example // we are using a jsonrpsee's ResponseFuture future // type to avoid those extra allocations. - type Future = ResponseFuture; + type Future = ResponseFuture; type Error = S::Error; + type Response = S::Response; fn call(&self, req: Request<'a>) -> Self::Future { let now = Instant::now(); diff --git a/examples/examples/server_with_connection_details.rs b/examples/examples/server_with_connection_details.rs index 539322d112..0004fd5632 100644 --- a/examples/examples/server_with_connection_details.rs +++ b/examples/examples/server_with_connection_details.rs @@ -52,6 +52,7 @@ struct LoggingMiddleware(S); impl<'a, S: RpcServiceT<'a>> RpcServiceT<'a> for LoggingMiddleware { type Future = S::Future; type Error = S::Error; + type Response = S::Response; fn call(&self, request: Request<'a>) -> Self::Future { tracing::info!("Received request: {:?}", request); diff --git a/server/src/middleware/rpc.rs b/server/src/middleware/rpc.rs index 3c59df0644..5cf1742aad 100644 --- a/server/src/middleware/rpc.rs +++ b/server/src/middleware/rpc.rs @@ -77,8 +77,9 @@ impl RpcService { impl<'a> RpcServiceT<'a> for RpcService { // The rpc module is already boxing the futures and // it's used to under the hood by the RpcService. - type Future = MethodResponseBoxFuture<'a, Self::Error>; + type Future = MethodResponseBoxFuture<'a, Self::Response, Self::Error>; type Error = Infallible; + type Response = MethodResponse; fn call(&self, req: Request<'a>) -> Self::Future { let conn_id = self.conn_id; diff --git a/server/src/server.rs b/server/src/server.rs index 23ef6c146b..4d6620edd9 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -965,7 +965,8 @@ impl Service> for TowerServiceNoHttp tower::Layer, >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT<'a>, + for<'a> >::Service: RpcServiceT<'a, Response = MethodResponse>, + for<'a> <>::Service as RpcServiceT<'a>>::Error: std::fmt::Debug, Body: http_body::Body + Send + 'static, Body::Error: Into, { @@ -1231,7 +1232,8 @@ pub(crate) async fn handle_rpc_call( extensions: Extensions, ) -> MethodResponse where - for<'a> S: RpcServiceT<'a> + Send, + for<'a> S: RpcServiceT<'a, Response = MethodResponse> + Send, + for<'a> >::Error: std::fmt::Debug, { // Single request or notification if is_single { diff --git a/server/src/tests/http.rs b/server/src/tests/http.rs index a8461e5c56..62e03a9741 100644 --- a/server/src/tests/http.rs +++ b/server/src/tests/http.rs @@ -61,8 +61,9 @@ impl<'a, S> RpcServiceT<'a> for InjectExt where S: Send + Sync + RpcServiceT<'a> + Clone + 'static, { - type Future = MethodResponseBoxFuture<'a, Self::Error>; + type Future = MethodResponseBoxFuture<'a, Self::Response, Self::Error>; type Error = S::Error; + type Response = S::Response; fn call(&self, mut req: Request<'a>) -> Self::Future { if req.method_name().contains("err") { diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index cc65362e57..9b3ad49497 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -9,7 +9,7 @@ use jsonrpsee_core::{ BoxError, http_helpers::{HttpError, read_body}, middleware::{RpcServiceBuilder, RpcServiceT}, - server::Methods, + server::{MethodResponse, Methods}, }; /// Checks that content type of received request is valid for JSON-RPC. @@ -45,7 +45,8 @@ where B::Error: Into, L: for<'a> tower::Layer, >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT<'a> + Send, + for<'a> >::Service: RpcServiceT<'a, Response = MethodResponse> + Send, + for<'a> <>::Service as RpcServiceT<'a>>::Error: std::fmt::Debug, { let ServerConfig { max_response_body_size, batch_requests_config, max_request_body_size, .. } = server_cfg; @@ -76,7 +77,8 @@ where B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into, - for<'a> S: RpcServiceT<'a> + Send, + for<'a> S: RpcServiceT<'a, Response = MethodResponse> + Send, + for<'a> >::Error: std::fmt::Debug, { // Only the `POST` method is allowed. match *request.method() { diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index be700c9f9b..ad55f2dac3 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -12,7 +12,7 @@ use futures_util::{Future, StreamExt, TryStreamExt}; use hyper::upgrade::Upgraded; use hyper_util::rt::TokioIo; use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; -use jsonrpsee_core::server::{BoundedSubscriptions, MethodSink, Methods}; +use jsonrpsee_core::server::{BoundedSubscriptions, MethodResponse, MethodSink, Methods}; use jsonrpsee_types::Id; use jsonrpsee_types::error::{ErrorCode, reject_too_big_request}; use soketto::connection::Error as SokettoError; @@ -63,7 +63,8 @@ pub(crate) struct BackgroundTaskParams { pub(crate) async fn background_task(params: BackgroundTaskParams) where - for<'a> S: RpcServiceT<'a> + Send + Sync + 'static, + for<'a> S: RpcServiceT<'a, Response = MethodResponse> + Send + Sync + 'static, + for<'a> >::Error: std::fmt::Debug, { let BackgroundTaskParams { server_cfg, @@ -422,7 +423,7 @@ pub async fn connect( where L: for<'a> tower::Layer, >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT<'a>, + for<'a> >::Service: RpcServiceT<'a, Response = MethodResponse>, { let mut server = soketto::handshake::http::Server::new(); diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index 97a1f21ee5..5b43cc959b 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -157,6 +157,7 @@ pub async fn server() -> SocketAddr { { type Future = S::Future; type Error = S::Error; + type Response = S::Response; fn call(&self, mut request: jsonrpsee::types::Request<'a>) -> Self::Future { request.extensions_mut().insert(self.connection_id); diff --git a/tests/tests/metrics.rs b/tests/tests/metrics.rs index 10bd72b73a..108030d1b4 100644 --- a/tests/tests/metrics.rs +++ b/tests/tests/metrics.rs @@ -63,10 +63,11 @@ pub struct CounterMiddleware { impl<'a, S> RpcServiceT<'a> for CounterMiddleware where - S: RpcServiceT<'a> + Send + Sync + Clone + 'static, + S: RpcServiceT<'a, Response = MethodResponse> + Send + Sync + Clone + 'static, { - type Future = BoxFuture<'a, Result>; + type Future = BoxFuture<'a, Result>; type Error = S::Error; + type Response = S::Response; fn call(&self, request: Request<'a>) -> Self::Future { let counter = self.counter.clone(); diff --git a/types/src/lib.rs b/types/src/lib.rs index 48b6d87692..4c54d5ceaa 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -47,3 +47,88 @@ pub use error::{ErrorCode, ErrorObject, ErrorObjectOwned}; pub use params::{Id, InvalidRequestId, Params, ParamsSequence, SubscriptionId, TwoPointZero}; pub use request::{InvalidRequest, Notification, NotificationSer, Request, RequestSer}; pub use response::{Response, ResponsePayload, SubscriptionPayload, SubscriptionResponse, Success as ResponseSuccess}; + +/// Helper to deserialize calls with extensions. +pub mod deserialize_with_ext { + + /// Request. + pub mod request { + use crate::Request; + + /// Helper to deserialize a request with extensions. + pub fn from_slice(data: &[u8], extensions: http::Extensions) -> Result { + let mut req: Request = serde_json::from_slice(data)?; + *req.extensions_mut() = extensions; + Ok(req) + } + + /// Helper to deserialize a request with extensions. + pub fn from_str(data: &str, extensions: http::Extensions) -> Result { + let mut req: Request = serde_json::from_str(data)?; + *req.extensions_mut() = extensions; + Ok(req) + } + } + + /// Notification. + pub mod notification { + use crate::Notification; + + /// Helper to deserialize a request with extensions. + pub fn from_slice<'a, T>( + data: &'a [u8], + extensions: http::Extensions, + ) -> Result, serde_json::Error> + where + T: serde::Deserialize<'a>, + { + let mut notif: Notification = serde_json::from_slice(data)?; + *notif.extensions_mut() = extensions; + Ok(notif) + } + + /// Helper to deserialize a request with extensions. + pub fn from_str<'a, T>( + data: &'a str, + extensions: http::Extensions, + ) -> Result, serde_json::Error> + where + T: serde::Deserialize<'a>, + { + let mut notif: Notification = serde_json::from_str(data)?; + *notif.extensions_mut() = extensions; + Ok(notif) + } + } + + /// Response + pub mod response { + use crate::Response; + + /// Helper to deserialize a response with extensions. + pub fn from_slice<'a, T>( + data: &'a [u8], + extensions: http::Extensions, + ) -> Result, serde_json::Error> + where + T: serde::Deserialize<'a> + Clone, + { + let mut res: Response = serde_json::from_slice(data)?; + *res.extensions_mut() = extensions; + Ok(res) + } + + /// Helper to deserialize a response with extensions. + pub fn from_str<'a, T>( + data: &'a str, + extensions: http::Extensions, + ) -> Result, serde_json::Error> + where + T: serde::Deserialize<'a> + Clone, + { + let mut res: Response = serde_json::from_str(data)?; + *res.extensions_mut() = extensions; + Ok(res) + } + } +} diff --git a/types/src/request.rs b/types/src/request.rs index b954586c26..034297627d 100644 --- a/types/src/request.rs +++ b/types/src/request.rs @@ -107,12 +107,30 @@ pub struct Notification<'a, T> { pub method: Cow<'a, str>, /// Parameter values of the request. pub params: T, + /// Extensions of the notification. + #[serde(skip)] + pub extensions: Extensions, } impl<'a, T> Notification<'a, T> { /// Create a new [`Notification`]. pub fn new(method: Cow<'a, str>, params: T) -> Self { - Self { jsonrpc: TwoPointZero, method, params } + Self { jsonrpc: TwoPointZero, method, params, extensions: Extensions::new() } + } + + /// Get the method name of the request. + pub fn method_name(&self) -> &str { + &self.method + } + + /// Returns a reference to the associated extensions. + pub fn extensions(&self) -> &Extensions { + &self.extensions + } + + /// Returns a reference to the associated extensions. + pub fn extensions_mut(&mut self) -> &mut Extensions { + &mut self.extensions } } @@ -175,7 +193,7 @@ impl<'a> NotificationSer<'a> { #[cfg(test)] mod test { - use super::{Cow, Id, InvalidRequest, Notification, NotificationSer, Request, RequestSer, TwoPointZero}; + use super::{Cow, Id, InvalidRequest, Notification, Request, RequestSer, TwoPointZero}; use serde_json::value::RawValue; fn assert_request<'a>(request: Request<'a>, id: Id<'a>, method: &str, params: Option<&str>) { @@ -284,21 +302,4 @@ mod test { assert_eq!(&request, ser); } } - - #[test] - fn serialize_notif() { - let exp = r#"{"jsonrpc":"2.0","method":"say_hello","params":["hello"]}"#; - let params = Some(RawValue::from_string(r#"["hello"]"#.into()).unwrap()); - let req = NotificationSer::owned("say_hello", params); - let ser = serde_json::to_string(&req).unwrap(); - assert_eq!(exp, ser); - } - - #[test] - fn serialize_notif_escaped_method_name() { - let exp = r#"{"jsonrpc":"2.0","method":"\"method\""}"#; - let req = NotificationSer::owned("\"method\"", None); - let ser = serde_json::to_string(&req).unwrap(); - assert_eq!(exp, ser); - } } diff --git a/types/src/response.rs b/types/src/response.rs index 3ebb3df640..1a6f143149 100644 --- a/types/src/response.rs +++ b/types/src/response.rs @@ -34,6 +34,7 @@ use crate::error::ErrorCode; use crate::params::{Id, SubscriptionId, TwoPointZero}; use crate::request::Notification; use crate::{ErrorObject, ErrorObjectOwned}; +use http::Extensions; use serde::ser::SerializeStruct; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -45,17 +46,39 @@ pub struct Response<'a, T: Clone> { pub payload: ResponsePayload<'a, T>, /// Request ID pub id: Id<'a>, + /// Extensions + pub extensions: Extensions, } impl<'a, T: Clone> Response<'a, T> { /// Create a new [`Response`]. pub fn new(payload: ResponsePayload<'a, T>, id: Id<'a>) -> Response<'a, T> { - Response { jsonrpc: Some(TwoPointZero), payload, id } + Response { jsonrpc: Some(TwoPointZero), payload, id, extensions: Extensions::new() } + } + + /// Create a new [`Response`] with extensions + pub fn new_with_extensions(payload: ResponsePayload<'a, T>, id: Id<'a>, ext: Extensions) -> Response<'a, T> { + Response { jsonrpc: Some(TwoPointZero), payload, id, extensions: ext } } /// Create an owned [`Response`]. pub fn into_owned(self) -> Response<'static, T> { - Response { jsonrpc: self.jsonrpc, payload: self.payload.into_owned(), id: self.id.into_owned() } + Response { + jsonrpc: self.jsonrpc, + payload: self.payload.into_owned(), + id: self.id.into_owned(), + extensions: self.extensions, + } + } + + /// Get the extensions of the response. + pub fn extensions(&self) -> &Extensions { + &self.extensions + } + + /// Get the mutable ref to the extensions of the response. + pub fn extensions_mut(&mut self) -> &mut Extensions { + &mut self.extensions } } @@ -293,14 +316,27 @@ where (_, Some(_), Some(_)) => { return Err(serde::de::Error::duplicate_field("result and error are mutually exclusive")); } - (Some(jsonrpc), Some(result), None) => { - Response { jsonrpc, payload: ResponsePayload::Success(result), id } - } - (Some(jsonrpc), None, Some(err)) => Response { jsonrpc, payload: ResponsePayload::Error(err), id }, - (None, Some(result), _) => { - Response { jsonrpc: None, payload: ResponsePayload::Success(result), id } + (Some(jsonrpc), Some(result), None) => Response { + jsonrpc, + payload: ResponsePayload::Success(result), + id, + extensions: Extensions::new(), + }, + (Some(jsonrpc), None, Some(err)) => { + Response { jsonrpc, payload: ResponsePayload::Error(err), id, extensions: Extensions::new() } } - (None, _, Some(err)) => Response { jsonrpc: None, payload: ResponsePayload::Error(err), id }, + (None, Some(result), _) => Response { + jsonrpc: None, + payload: ResponsePayload::Success(result), + id, + extensions: Extensions::new(), + }, + (None, _, Some(err)) => Response { + jsonrpc: None, + payload: ResponsePayload::Error(err), + id, + extensions: Extensions::new(), + }, (_, None, None) => return Err(serde::de::Error::missing_field("result/error")), }; @@ -340,6 +376,8 @@ where #[cfg(test)] mod tests { + use http::Extensions; + use super::{Id, Response, TwoPointZero}; use crate::{ErrorObjectOwned, response::ResponsePayload}; @@ -349,6 +387,7 @@ mod tests { jsonrpc: Some(TwoPointZero), payload: ResponsePayload::success("ok"), id: Id::Number(1), + extensions: Extensions::new(), }) .unwrap(); let exp = r#"{"jsonrpc":"2.0","id":1,"result":"ok"}"#; @@ -361,6 +400,7 @@ mod tests { jsonrpc: Some(TwoPointZero), payload: ResponsePayload::<()>::error(ErrorObjectOwned::owned(1, "lo", None::<()>)), id: Id::Number(1), + extensions: Extensions::new(), }) .unwrap(); let exp = r#"{"jsonrpc":"2.0","id":1,"error":{"code":1,"message":"lo"}}"#; @@ -373,6 +413,7 @@ mod tests { jsonrpc: None, payload: ResponsePayload::success("ok"), id: Id::Number(1), + extensions: Extensions::new(), }) .unwrap(); let exp = r#"{"id":1,"result":"ok"}"#; @@ -381,8 +422,12 @@ mod tests { #[test] fn deserialize_success_call() { - let exp = - Response { jsonrpc: Some(TwoPointZero), payload: ResponsePayload::success(99_u64), id: Id::Number(11) }; + let exp = Response { + jsonrpc: Some(TwoPointZero), + payload: ResponsePayload::success(99_u64), + id: Id::Number(11), + extensions: Extensions::new(), + }; let dsr: Response = serde_json::from_str(r#"{"jsonrpc":"2.0", "result":99, "id":11}"#).unwrap(); assert_eq!(dsr.jsonrpc, exp.jsonrpc); assert_eq!(dsr.payload, exp.payload); @@ -395,6 +440,7 @@ mod tests { jsonrpc: Some(TwoPointZero), payload: ResponsePayload::error(ErrorObjectOwned::owned(1, "lo", None::<()>)), id: Id::Number(11), + extensions: Extensions::new(), }; let dsr: Response<()> = serde_json::from_str(r#"{"jsonrpc":"2.0","error":{"code":1,"message":"lo"},"id":11}"#).unwrap(); @@ -405,7 +451,12 @@ mod tests { #[test] fn deserialize_call_missing_version_field() { - let exp = Response { jsonrpc: None, payload: ResponsePayload::success(99_u64), id: Id::Number(11) }; + let exp = Response { + jsonrpc: None, + payload: ResponsePayload::success(99_u64), + id: Id::Number(11), + extensions: Extensions::new(), + }; let dsr: Response = serde_json::from_str(r#"{"jsonrpc":null, "result":99, "id":11}"#).unwrap(); assert_eq!(dsr.jsonrpc, exp.jsonrpc); assert_eq!(dsr.payload, exp.payload); @@ -414,7 +465,12 @@ mod tests { #[test] fn deserialize_with_unknown_field() { - let exp = Response { jsonrpc: None, payload: ResponsePayload::success(99_u64), id: Id::Number(11) }; + let exp = Response { + jsonrpc: None, + payload: ResponsePayload::success(99_u64), + id: Id::Number(11), + extensions: Extensions::new(), + }; let dsr: Response = serde_json::from_str(r#"{"jsonrpc":null, "result":99, "id":11, "unknown":11}"#).unwrap(); assert_eq!(dsr.jsonrpc, exp.jsonrpc); From dec873f0dd9ac5c830fb28f8ac3f20e067000b9e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 20 Mar 2025 09:57:19 +0100 Subject: [PATCH 12/52] fix faulty imports --- client/http-client/src/client.rs | 3 +-- client/http-client/src/rpc_service.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index f57185c228..cec7782eb8 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -35,14 +35,13 @@ use crate::types::Response; use crate::{HttpRequest, HttpResponse}; use async_trait::async_trait; use hyper::body::Bytes; -use hyper::http::HeaderMap; +use hyper::http::{Extensions, HeaderMap}; use jsonrpsee_core::client::{ BatchResponse, ClientT, Error, IdKind, MethodResponse, RequestIdManager, Subscription, SubscriptionClientT, generate_batch_id_range, }; use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::params::BatchRequestBuilder; -use jsonrpsee_core::server::Extensions; use jsonrpsee_core::traits::ToRpcParams; use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; use jsonrpsee_types::{ErrorObject, InvalidRequestId, Notification, Request, ResponseSuccess, TwoPointZero}; diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index 1d8d13e7cc..6aa8fa2ff8 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -1,12 +1,11 @@ use std::sync::Arc; use futures_util::{FutureExt, future::BoxFuture}; -use hyper::body::Bytes; +use hyper::{body::Bytes, http::Extensions}; use jsonrpsee_core::{ BoxError, JsonRawValue, client::{Error, MethodResponse}, middleware::{Notification, RpcServiceT}, - server::Extensions, }; use jsonrpsee_types::{Response, ResponseSuccess}; use tower::Service; From 3aa828d0673b35169aa440aae54b28c9aeed0a89 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 20 Mar 2025 10:38:45 +0100 Subject: [PATCH 13/52] minor cleanup --- client/http-client/src/client.rs | 6 +-- core/src/client/mod.rs | 10 ++--- core/src/lib.rs | 1 - core/src/middleware/mod.rs | 6 +-- core/src/{ => server}/method_response.rs | 0 core/src/server/mod.rs | 4 +- core/src/server/rpc_module.rs | 3 +- core/src/server/subscription.rs | 18 +++++---- examples/examples/rpc_middleware.rs | 4 +- proc-macros/tests/ui/correct/basic.rs | 1 + server/src/middleware/rpc.rs | 4 +- server/src/server.rs | 21 +++++----- server/src/tests/http.rs | 4 +- server/src/utils.rs | 23 ----------- types/src/lib.rs | 50 +++++++++++++----------- 15 files changed, 69 insertions(+), 86 deletions(-) rename core/src/{ => server}/method_response.rs (100%) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index cec7782eb8..559ba7473d 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -418,10 +418,10 @@ where for ((method, params), id) in batch.into_iter().zip(id_range.clone()) { let id = self.id_manager.as_id_kind().into_id(id); let req = Request { - jsonrpc: TwoPointZero::default(), + jsonrpc: TwoPointZero, method: method.into(), params: params.map(StdCow::Owned), - id: id.into(), + id, extensions: Extensions::new(), }; batch_request.push(req); @@ -439,7 +439,7 @@ where batch_response.push(Err(ErrorObject::borrowed(0, "", None))); } - for json_rp in json_rps.into_iter() { + for json_rp in json_rps.iter() { let rp: Response<&JsonRawValue> = serde_json::from_str(json_rp.get()).map_err(Error::ParseError)?; let id = rp.id.try_parse_inner_as_number()?; diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index e22a104860..73275e5075 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -705,13 +705,11 @@ impl MethodResponse { } /// Consume the response and return the raw JSON value. - pub fn into_json(self) -> Box { + pub fn into_json(self) -> Result, serde_json::Error> { match self.inner { - MethodResponseKind::MethodCall(call) => call.json, - MethodResponseKind::Notification => panic!("MethodResponse::into_json called on a notification"), - MethodResponseKind::Batch(json) => { - serde_json::value::to_raw_value(&json).expect("Batch serialization failed") - } + MethodResponseKind::MethodCall(call) => Ok(call.json), + MethodResponseKind::Notification => Ok(RawValue::NULL.to_owned()), + MethodResponseKind::Batch(json) => serde_json::value::to_raw_value(&json), } } diff --git a/core/src/lib.rs b/core/src/lib.rs index 061ffc50fc..1199417b57 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -58,7 +58,6 @@ cfg_client! { cfg_client_or_server! { pub mod middleware; - pub mod method_response; } /// Shared tracing helpers to trace RPC calls. diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index b7e28b46bb..87bdc60e00 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -12,15 +12,13 @@ use std::borrow::Cow; use std::pin::Pin; use std::task::{Context, Poll}; -/// Re-export `MethodResponse` for convenience. -pub use crate::method_response::MethodResponse; /// Re-export types from `jsonrpsee_types` crate for convenience pub type Notification<'a> = jsonrpsee_types::Notification<'a, Option>>; /// Re-export types from `jsonrpsee_types` crate for convenience pub use jsonrpsee_types::Request; -/// Type alias for a future that resolves to a [`MethodResponse`]. -pub type MethodResponseBoxFuture<'a, R, E> = BoxFuture<'a, Result>; +/// Type alias for a boxed future that resolves to Result. +pub type ResponseBoxFuture<'a, R, E> = BoxFuture<'a, Result>; /// Similar to the [`tower::Service`] but specific for jsonrpsee and /// doesn't requires `&mut self` for performance reasons. diff --git a/core/src/method_response.rs b/core/src/server/method_response.rs similarity index 100% rename from core/src/method_response.rs rename to core/src/server/method_response.rs diff --git a/core/src/server/mod.rs b/core/src/server/mod.rs index 48617085b7..12e1fbe544 100644 --- a/core/src/server/mod.rs +++ b/core/src/server/mod.rs @@ -30,15 +30,17 @@ mod error; /// Helpers. pub mod helpers; +/// Method response. +mod method_response; /// JSON-RPC "modules" group sets of methods that belong together and handles method/subscription registration. mod rpc_module; /// Subscription related types. mod subscription; -pub use crate::method_response::*; pub use error::*; pub use helpers::*; pub use http::Extensions; +pub use method_response::*; pub use rpc_module::*; pub use subscription::*; diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 2bd67809e1..39564a354c 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -32,14 +32,13 @@ use std::sync::Arc; use crate::error::RegisterMethodError; use crate::id_providers::RandomIntegerIdProvider; -use crate::method_response::MethodResponse; use crate::server::helpers::MethodSink; use crate::server::subscription::{ BoundedSubscriptions, IntoSubscriptionCloseResponse, PendingSubscriptionSink, SubNotifResultOrError, Subscribers, Subscription, SubscriptionCloseResponse, SubscriptionKey, SubscriptionPermit, SubscriptionState, sub_message_to_json, }; -use crate::server::{LOG_TARGET, ResponsePayload}; +use crate::server::{LOG_TARGET, MethodResponse, ResponsePayload}; use crate::traits::ToRpcParams; use futures_util::{FutureExt, future::BoxFuture}; use http::Extensions; diff --git a/core/src/server/subscription.rs b/core/src/server/subscription.rs index 6ed504640d..fed1874cb3 100644 --- a/core/src/server/subscription.rs +++ b/core/src/server/subscription.rs @@ -28,17 +28,17 @@ use super::helpers::MethodSink; use super::{MethodResponse, MethodsError, ResponsePayload}; +use crate::server::LOG_TARGET; use crate::server::error::{DisconnectError, PendingSubscriptionAcceptError, SendTimeoutError, TrySendError}; use crate::server::rpc_module::ConnectionId; -use crate::server::LOG_TARGET; use crate::{error::StringError, traits::IdProvider}; use jsonrpsee_types::SubscriptionPayload; -use jsonrpsee_types::{response::SubscriptionError, ErrorObjectOwned, Id, SubscriptionId, SubscriptionResponse}; +use jsonrpsee_types::{ErrorObjectOwned, Id, SubscriptionId, SubscriptionResponse, response::SubscriptionError}; use parking_lot::Mutex; use rustc_hash::FxHashMap; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{Serialize, de::DeserializeOwned}; use std::{sync::Arc, time::Duration}; -use tokio::sync::{mpsc, oneshot, OwnedSemaphorePermit, Semaphore}; +use tokio::sync::{OwnedSemaphorePermit, Semaphore, mpsc, oneshot}; /// Type-alias for subscribers. pub type Subscribers = Arc)>>>; @@ -297,7 +297,9 @@ impl PendingSubscriptionSink { _permit: Arc::new(self.permit), }) } else { - panic!("The subscription response was too big; adjust the `max_response_size` or change Subscription ID generation"); + panic!( + "The subscription response was too big; adjust the `max_response_size` or change Subscription ID generation" + ); } } @@ -372,7 +374,7 @@ impl SubscriptionSink { } let json = sub_message_to_json(msg, SubNotifResultOrError::Result, &self.uniq_sub.sub_id, self.method); - self.inner.send(json).await.map_err(Into::into) + self.inner.send(json).await } /// Similar to `SubscriptionSink::send` but only waits for a limited time. @@ -383,7 +385,7 @@ impl SubscriptionSink { } let json = sub_message_to_json(msg, SubNotifResultOrError::Result, &self.uniq_sub.sub_id, self.method); - self.inner.send_timeout(json, timeout).await.map_err(Into::into) + self.inner.send_timeout(json, timeout).await } /// Attempts to immediately send out the message as JSON string to the subscribers but fails if the @@ -399,7 +401,7 @@ impl SubscriptionSink { } let json = sub_message_to_json(msg, SubNotifResultOrError::Result, &self.uniq_sub.sub_id, self.method); - self.inner.try_send(json).map_err(Into::into) + self.inner.try_send(json) } /// Returns whether the subscription is closed. diff --git a/examples/examples/rpc_middleware.rs b/examples/examples/rpc_middleware.rs index d2d317d418..b3ad9ad8ca 100644 --- a/examples/examples/rpc_middleware.rs +++ b/examples/examples/rpc_middleware.rs @@ -44,7 +44,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use futures::FutureExt; use futures::future::BoxFuture; use jsonrpsee::core::client::ClientT; -use jsonrpsee::core::middleware::{MethodResponseBoxFuture, Notification, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Notification, ResponseBoxFuture, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, Server}; use jsonrpsee::types::Request; @@ -99,7 +99,7 @@ impl<'a, S> RpcServiceT<'a> for GlobalCalls where S: RpcServiceT<'a> + Send + Sync + Clone + 'static, { - type Future = MethodResponseBoxFuture<'a, Self::Response, Self::Error>; + type Future = ResponseBoxFuture<'a, Self::Response, Self::Error>; type Error = S::Error; type Response = S::Response; diff --git a/proc-macros/tests/ui/correct/basic.rs b/proc-macros/tests/ui/correct/basic.rs index 745c380a97..01c21c16a3 100644 --- a/proc-macros/tests/ui/correct/basic.rs +++ b/proc-macros/tests/ui/correct/basic.rs @@ -153,6 +153,7 @@ pub async fn server() -> SocketAddr { { type Future = S::Future; type Error = S::Error; + type Response = S::Response; fn call(&self, mut request: jsonrpsee::types::Request<'a>) -> Self::Future { request.extensions_mut().insert(self.connection_id); diff --git a/server/src/middleware/rpc.rs b/server/src/middleware/rpc.rs index 5cf1742aad..1277fc469c 100644 --- a/server/src/middleware/rpc.rs +++ b/server/src/middleware/rpc.rs @@ -31,7 +31,7 @@ use std::sync::Arc; use crate::ConnectionId; use futures_util::future::FutureExt; -use jsonrpsee_core::middleware::{MethodResponseBoxFuture, Notification, RpcServiceT}; +use jsonrpsee_core::middleware::{Notification, ResponseBoxFuture, RpcServiceT}; use jsonrpsee_core::server::{ BatchResponseBuilder, BoundedSubscriptions, MethodCallback, MethodResponse, MethodSink, Methods, SubscriptionState, }; @@ -77,7 +77,7 @@ impl RpcService { impl<'a> RpcServiceT<'a> for RpcService { // The rpc module is already boxing the futures and // it's used to under the hood by the RpcService. - type Future = MethodResponseBoxFuture<'a, Self::Response, Self::Error>; + type Future = ResponseBoxFuture<'a, Self::Response, Self::Error>; type Error = Infallible; type Response = MethodResponse; diff --git a/server/src/server.rs b/server/src/server.rs index 4d6620edd9..58c28bc2f6 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -37,7 +37,6 @@ use crate::future::{ConnectionGuard, ServerHandle, SessionClose, SessionClosedFu use crate::middleware::rpc::{RpcService, RpcServiceCfg}; use crate::transport::ws::BackgroundTaskParams; use crate::transport::{http, ws}; -use crate::utils::deserialize; use crate::{Extensions, HttpBody, HttpRequest, HttpResponse, LOG_TARGET}; use futures_util::future::{self, Either, FutureExt}; @@ -46,7 +45,7 @@ use futures_util::io::{BufReader, BufWriter}; use hyper::body::Bytes; use hyper_util::rt::{TokioExecutor, TokioIo}; use jsonrpsee_core::id_providers::RandomIntegerIdProvider; -use jsonrpsee_core::middleware::{Notification, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::server::helpers::prepare_error; use jsonrpsee_core::server::{BoundedSubscriptions, ConnectionId, MethodResponse, MethodSink, Methods}; use jsonrpsee_core::traits::IdProvider; @@ -56,7 +55,7 @@ use jsonrpsee_types::error::{ BATCHES_NOT_SUPPORTED_CODE, BATCHES_NOT_SUPPORTED_MSG, ErrorCode, reject_too_big_batch_request, rpc_middleware_error, }; -use jsonrpsee_types::{ErrorObject, Id, InvalidRequest}; +use jsonrpsee_types::{ErrorObject, Id, InvalidRequest, deserialize_with_ext}; use soketto::handshake::http::is_upgrade_request; use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; use tokio::sync::{OwnedSemaphorePermit, mpsc, watch}; @@ -68,6 +67,8 @@ use tracing::{Instrument, instrument}; /// Default maximum connections allowed. const MAX_CONNECTIONS: u32 = 100; +type Notif<'a> = Option>; + /// JSON RPC server. pub struct Server { listener: TcpListener, @@ -653,7 +654,7 @@ impl Builder { /// The builder itself exposes a similar API as the [`tower::ServiceBuilder`] /// where it is possible to compose layers to the middleware. /// - /// To add a middleware [`crate::middleware::rpc::RpcServiceBuilder`] exposes a few different layer APIs that + /// To add a middleware [`crate::RpcServiceBuilder`] exposes a few different layer APIs that /// is wrapped on top of the [`tower::ServiceBuilder`]. /// /// When the server is started these layers are wrapped in the [`crate::middleware::rpc::RpcService`] and @@ -1237,14 +1238,14 @@ where { // Single request or notification if is_single { - if let Ok(req) = deserialize::from_slice_with_extensions(body, extensions) { + if let Ok(req) = deserialize_with_ext::call::from_slice(body, &extensions) { let id = req.id(); match rpc_service.call(req).await { Ok(rp) => rp, Err(err) => MethodResponse::error(id, rpc_middleware_error(err)), } - } else if let Ok(notif) = serde_json::from_slice::(body) { + } else if let Ok(notif) = deserialize_with_ext::notif::from_slice::(body, &extensions) { match rpc_service.notification(notif).await { Ok(rp) => rp, Err(e) => { @@ -1281,9 +1282,11 @@ where let mut got_notif = false; for call in unchecked_batch { - if let Ok(req) = deserialize::from_str_with_extensions(call.get(), extensions.clone()) { + if let Ok(req) = deserialize_with_ext::call::from_str(call.get(), &extensions) { batch.push(req); - } else if let Ok(_notif) = serde_json::from_str::(call.get()) { + } else if let Ok(notif) = deserialize_with_ext::notif::from_str::(call.get(), &extensions) { + // Notifications in a batch should not be answered but invoke middleware. + _ = rpc_service.notification(notif).await; got_notif = true; } else { let id = match serde_json::from_str::(call.get()) { @@ -1302,7 +1305,7 @@ where } }; - if got_notif && batch_response.as_result().len() == 0 { + if got_notif && batch_response.as_result().is_empty() { MethodResponse::notification() } else { batch_response diff --git a/server/src/tests/http.rs b/server/src/tests/http.rs index 62e03a9741..0751bd40de 100644 --- a/server/src/tests/http.rs +++ b/server/src/tests/http.rs @@ -33,7 +33,7 @@ use crate::{ }; use futures_util::future::{Future, FutureExt}; use hyper::body::Bytes; -use jsonrpsee_core::middleware::{MethodResponseBoxFuture, Notification, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee_core::middleware::{Notification, ResponseBoxFuture, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::{BoxError, RpcResult}; use jsonrpsee_test_utils::TimeoutFutureExt; use jsonrpsee_test_utils::helpers::*; @@ -61,7 +61,7 @@ impl<'a, S> RpcServiceT<'a> for InjectExt where S: Send + Sync + RpcServiceT<'a> + Clone + 'static, { - type Future = MethodResponseBoxFuture<'a, Self::Response, Self::Error>; + type Future = ResponseBoxFuture<'a, Self::Response, Self::Error>; type Error = S::Error; type Response = S::Response; diff --git a/server/src/utils.rs b/server/src/utils.rs index cf2dc6daaa..8461f6681f 100644 --- a/server/src/utils.rs +++ b/server/src/utils.rs @@ -140,26 +140,3 @@ where } } } - -/// Helpers to deserialize a request with extensions. -pub(crate) mod deserialize { - /// Helper to deserialize a request with extensions. - pub(crate) fn from_slice_with_extensions( - data: &[u8], - extensions: http::Extensions, - ) -> Result { - let mut req: jsonrpsee_types::Request = serde_json::from_slice(data)?; - *req.extensions_mut() = extensions; - Ok(req) - } - - /// Helper to deserialize a request with extensions. - pub(crate) fn from_str_with_extensions( - data: &str, - extensions: http::Extensions, - ) -> Result { - let mut req: jsonrpsee_types::Request = serde_json::from_str(data)?; - *req.extensions_mut() = extensions; - Ok(req) - } -} diff --git a/types/src/lib.rs b/types/src/lib.rs index 4c54d5ceaa..f99ea1096f 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -44,90 +44,94 @@ pub mod response; pub mod error; pub use error::{ErrorCode, ErrorObject, ErrorObjectOwned}; +pub use http::Extensions; pub use params::{Id, InvalidRequestId, Params, ParamsSequence, SubscriptionId, TwoPointZero}; pub use request::{InvalidRequest, Notification, NotificationSer, Request, RequestSer}; pub use response::{Response, ResponsePayload, SubscriptionPayload, SubscriptionResponse, Success as ResponseSuccess}; -/// Helper to deserialize calls with extensions. +/// Deserialize calls, notifications and responses with HTTP extensions. pub mod deserialize_with_ext { - /// Request. - pub mod request { + /// Method call. + pub mod call { use crate::Request; - /// Helper to deserialize a request with extensions. - pub fn from_slice(data: &[u8], extensions: http::Extensions) -> Result { + /// Wrapper over `serde_json::from_slice` that sets the extensions. + pub fn from_slice<'a>( + data: &'a [u8], + extensions: &'a http::Extensions, + ) -> Result, serde_json::Error> { let mut req: Request = serde_json::from_slice(data)?; - *req.extensions_mut() = extensions; + *req.extensions_mut() = extensions.clone(); Ok(req) } - /// Helper to deserialize a request with extensions. - pub fn from_str(data: &str, extensions: http::Extensions) -> Result { + /// Wrapper over `serde_json::from_str` that sets the extensions. + pub fn from_str<'a>(data: &'a str, extensions: &'a http::Extensions) -> Result, serde_json::Error> { let mut req: Request = serde_json::from_str(data)?; - *req.extensions_mut() = extensions; + *req.extensions_mut() = extensions.clone(); Ok(req) } } /// Notification. - pub mod notification { + pub mod notif { use crate::Notification; - /// Helper to deserialize a request with extensions. + /// Wrapper over `serde_json::from_slice` that sets the extensions. pub fn from_slice<'a, T>( data: &'a [u8], - extensions: http::Extensions, + extensions: &'a http::Extensions, ) -> Result, serde_json::Error> where T: serde::Deserialize<'a>, { let mut notif: Notification = serde_json::from_slice(data)?; - *notif.extensions_mut() = extensions; + *notif.extensions_mut() = extensions.clone(); Ok(notif) } - /// Helper to deserialize a request with extensions. + /// Wrapper over `serde_json::from_str` that sets the extensions. pub fn from_str<'a, T>( data: &'a str, - extensions: http::Extensions, + extensions: &http::Extensions, ) -> Result, serde_json::Error> where T: serde::Deserialize<'a>, { let mut notif: Notification = serde_json::from_str(data)?; - *notif.extensions_mut() = extensions; + *notif.extensions_mut() = extensions.clone(); Ok(notif) } } - /// Response + /// Response. pub mod response { use crate::Response; - /// Helper to deserialize a response with extensions. + /// Wrapper over `serde_json::from_slice` that sets the extensions. pub fn from_slice<'a, T>( data: &'a [u8], - extensions: http::Extensions, + extensions: &'a http::Extensions, ) -> Result, serde_json::Error> where T: serde::Deserialize<'a> + Clone, { let mut res: Response = serde_json::from_slice(data)?; - *res.extensions_mut() = extensions; + *res.extensions_mut() = extensions.clone(); Ok(res) } - /// Helper to deserialize a response with extensions. + /// Wrapper over `serde_json::from_str` that sets the extensions. pub fn from_str<'a, T>( data: &'a str, - extensions: http::Extensions, + extensions: &'a http::Extensions, ) -> Result, serde_json::Error> where T: serde::Deserialize<'a> + Clone, { let mut res: Response = serde_json::from_str(data)?; - *res.extensions_mut() = extensions; + *res.extensions_mut() = extensions.clone(); Ok(res) } } From b1e3b4103f3d96167c16f8afa4ac53b0257472c8 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 21 Mar 2025 16:29:36 +0100 Subject: [PATCH 14/52] introduce Batch/BatchEntry for middleware --- client/http-client/src/client.rs | 4 +- client/http-client/src/rpc_service.rs | 12 +- core/src/middleware/layer/either.rs | 4 +- core/src/middleware/layer/logger.rs | 6 +- core/src/middleware/mod.rs | 133 ++++++++++++++++-- core/src/server/method_response.rs | 10 -- examples/examples/http.rs | 6 +- examples/examples/jsonrpsee_as_service.rs | 6 +- ...erver_close_connection_from_rpc_handler.rs | 3 +- .../jsonrpsee_server_low_level_api.rs | 10 +- examples/examples/rpc_middleware.rs | 15 +- .../examples/rpc_middleware_modify_request.rs | 6 +- .../examples/rpc_middleware_rate_limiting.rs | 6 +- .../server_with_connection_details.rs | 8 +- examples/examples/ws.rs | 3 +- proc-macros/tests/ui/correct/basic.rs | 8 +- server/src/lib.rs | 1 - server/src/middleware/rpc.rs | 53 +++++-- server/src/server.rs | 45 +++--- server/src/tests/http.rs | 14 +- server/src/transport/http.rs | 2 +- server/src/transport/ws.rs | 4 +- tests/tests/helpers.rs | 12 +- tests/tests/metrics.rs | 6 +- types/src/request.rs | 3 +- 25 files changed, 253 insertions(+), 127 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 559ba7473d..57c14758bf 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -40,7 +40,7 @@ use jsonrpsee_core::client::{ BatchResponse, ClientT, Error, IdKind, MethodResponse, RequestIdManager, Subscription, SubscriptionClientT, generate_batch_id_range, }; -use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; +use jsonrpsee_core::middleware::{BatchEntry, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::params::BatchRequestBuilder; use jsonrpsee_core::traits::ToRpcParams; use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; @@ -424,7 +424,7 @@ where id, extensions: Extensions::new(), }; - batch_request.push(req); + batch_request.push(BatchEntry::Call(req)); } let method_response = self.transport.batch(batch_request).await?; diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index 6aa8fa2ff8..bbb4bb3636 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -5,7 +5,7 @@ use hyper::{body::Bytes, http::Extensions}; use jsonrpsee_core::{ BoxError, JsonRawValue, client::{Error, MethodResponse}, - middleware::{Notification, RpcServiceT}, + middleware::{Batch, Notification, Request, RpcServiceT}, }; use jsonrpsee_types::{Response, ResponseSuccess}; use tower::Service; @@ -39,7 +39,7 @@ where type Error = Error; type Response = MethodResponse; - fn call(&self, request: jsonrpsee_types::Request<'a>) -> Self::Future { + fn call(&self, request: Request<'a>) -> Self::Future { let service = self.service.clone(); async move { @@ -52,18 +52,18 @@ where .boxed() } - fn batch(&self, requests: Vec>) -> Self::Future { + fn batch(&self, batch: Batch<'a>) -> Self::Future { let service = self.service.clone(); async move { - let raw = serde_json::to_string(&requests)?; + let raw = serde_json::to_string(&batch)?; let bytes = service.send_and_read_body(raw).await.map_err(|e| Error::Transport(e.into()))?; let json: Vec> = serde_json::from_slice(&bytes)?; let mut extensions = Extensions::new(); - for req in requests { - extensions.extend(req.extensions); + for call in batch { + extensions.extend(call.into_extensions()); } Ok(MethodResponse::batch(json, extensions)) diff --git a/core/src/middleware/layer/either.rs b/core/src/middleware/layer/either.rs index ab846373bf..a9beb2a6db 100644 --- a/core/src/middleware/layer/either.rs +++ b/core/src/middleware/layer/either.rs @@ -31,7 +31,7 @@ //! work to implement tower::Layer for //! external types such as future::Either. -use crate::middleware::{Notification, RpcServiceT}; +use crate::middleware::{BatchEntry, Notification, RpcServiceT}; use jsonrpsee_types::Request; /// [`tower::util::Either`] but @@ -75,7 +75,7 @@ where } } - fn batch(&self, requests: Vec>) -> Self::Future { + fn batch(&self, requests: Vec>) -> Self::Future { match self { Either::Left(service) => futures_util::future::Either::Left(service.batch(requests)), Either::Right(service) => futures_util::future::Either::Right(service.batch(requests)), diff --git a/core/src/middleware/layer/logger.rs b/core/src/middleware/layer/logger.rs index 5604c6f9ae..cedab26bf3 100644 --- a/core/src/middleware/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -32,7 +32,7 @@ use std::{ }; use crate::{ - middleware::{Notification, RpcServiceT}, + middleware::{BatchEntry, Notification, RpcServiceT}, tracing::server::{rx_log_from_json, tx_log_from_str}, }; @@ -85,8 +85,8 @@ where } #[tracing::instrument(name = "batch", skip_all, fields(method = "batch"), level = "trace")] - fn batch(&self, requests: Vec>) -> Self::Future { - rx_log_from_json(&requests, self.max); + fn batch(&self, requests: Vec>) -> Self::Future { + //rx_log_from_json(&requests, self.max); ResponseFuture::new(self.service.batch(requests), self.max).in_current_span() } diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 87bdc60e00..7606feaa27 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -3,7 +3,9 @@ pub mod layer; use futures_util::future::{BoxFuture, Either, Future}; +use jsonrpsee_types::Id; use pin_project::pin_project; +use serde::Serialize; use serde_json::value::RawValue; use tower::layer::LayerFn; use tower::layer::util::{Identity, Stack}; @@ -12,16 +14,91 @@ use std::borrow::Cow; use std::pin::Pin; use std::task::{Context, Poll}; -/// Re-export types from `jsonrpsee_types` crate for convenience +/// Re-export types from `jsonrpsee_types` crate for convenience. pub type Notification<'a> = jsonrpsee_types::Notification<'a, Option>>; -/// Re-export types from `jsonrpsee_types` crate for convenience -pub use jsonrpsee_types::Request; - +/// Re-export types from `jsonrpsee_types` crate for convenience. +pub use jsonrpsee_types::{Extensions, Request}; /// Type alias for a boxed future that resolves to Result. pub type ResponseBoxFuture<'a, R, E> = BoxFuture<'a, Result>; +/// Type alias for a batch of JSON-RPC calls and notifications. +pub type Batch<'a> = Vec>; + +/// A batch entry specific for the [`RpcServiceT::batch`] method to support both +/// method calls and notifications. +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum BatchEntry<'a> { + /// A regular JSON-RPC call. + Call(Request<'a>), + /// A JSON-RPC notification. + Notification(Notification<'a>), + /// The representation of an invalid request and kept to maintain the batch order. + /// + /// WARNING: This is an internal state and should be NOT be used by external users + /// + #[serde(skip)] + #[doc(hidden)] + InvalidRequest(Id<'a>), +} + +/// Internal InvalidRequest that can't be instantiated by the user. +#[derive(Debug, Clone)] +pub struct InvalidRequest<'a>(Id<'a>); + +impl<'a> InvalidRequest<'a> { + /// Consume the invalid request and extract the id. + pub fn into_id(self) -> Id<'a> { + self.0 + } +} + +impl<'a> BatchEntry<'a> { + /// Get a reference to extensions of the batch entry. + pub fn extensions(&self) -> &Extensions { + match self { + BatchEntry::Call(req) => req.extensions(), + BatchEntry::Notification(n) => n.extensions(), + BatchEntry::InvalidRequest(_) => panic!("BatchEntry::InvalidRequest should not be used"), + } + } + + /// Get a mut reference to extensions of the batch entry. + pub fn extensions_mut(&mut self) -> &mut Extensions { + match self { + BatchEntry::Call(req) => req.extensions_mut(), + BatchEntry::Notification(n) => n.extensions_mut(), + BatchEntry::InvalidRequest(_) => panic!("BatchEntry::InvalidRequest should not be used"), + } + } + + /// Get the method name of the batch entry. + pub fn method_name(&self) -> &str { + match self { + BatchEntry::Call(req) => req.method_name(), + BatchEntry::Notification(n) => n.method_name(), + BatchEntry::InvalidRequest(_) => panic!("BatchEntry::InvalidRequest should not be used"), + } + } + + /// Consume the batch entry and extract the extensions. + pub fn into_extensions(self) -> Extensions { + match self { + BatchEntry::Call(req) => req.extensions, + BatchEntry::Notification(n) => n.extensions, + BatchEntry::InvalidRequest(_) => panic!("BatchEntry::InvalidRequest should not be used"), + } + } +} -/// Similar to the [`tower::Service`] but specific for jsonrpsee and -/// doesn't requires `&mut self` for performance reasons. +/// Present a JSON-RPC service that can process JSON-RPC calls, notifications, and batch requests. +/// +/// This trait is similar to [`tower::Service`] but it's specialized for JSON-RPC operations. +/// +/// The response type is a future that resolves to a `Result` mainly because this trait is +/// intended to by used by both client and server implementations. +/// +/// In the server implementation, the error is infallible but in the client implementation, the error +/// can occur due to I/O errors or JSON-RPC protocol errors. pub trait RpcServiceT<'a> { /// The future response value. type Future: Future> + Send; @@ -32,19 +109,22 @@ pub trait RpcServiceT<'a> { /// The response type type Response; - /// Process a single JSON-RPC call it may be a subscription or regular call. - /// - /// In this interface both are treated in the same way but it's possible to - /// distinguish those based on the `MethodResponse`. + /// Processes a single JSON-RPC call, which may be a subscription or regular call. fn call(&self, request: Request<'a>) -> Self::Future; - /// Similar to `RpcServiceT::call` but process multiple JSON-RPC calls at once. + /// Processes multiple JSON-RPC calls at once, similar to `RpcServiceT::call`. + /// + /// This method wraps `RpcServiceT::call` and `RpcServiceT::notification`, + /// but the root RPC service does not inherently recognize custom implementations + /// of these methods. + /// + /// As a result, if you have custom logic for individual calls or notifications, + /// you must duplicate that logic here. /// - /// This method is optional because it's generally not by the server however - /// it may be useful for batch processing on the client side. - fn batch(&self, requests: Vec>) -> Self::Future; + // TODO: Investigate if the complete service can be invoked inside `RpcService`. + fn batch(&self, requests: Batch<'a>) -> Self::Future; - /// Similar to `RpcServiceT::call` but process a JSON-RPC notification. + /// Similar to `RpcServiceT::call` but processes a JSON-RPC notification. fn notification(&self, n: Notification<'a>) -> Self::Future; } @@ -136,3 +216,26 @@ where } } } + +#[cfg(test)] +mod tests { + #[test] + fn serialize_batch_entry() { + use super::{BatchEntry, Notification, Request}; + use jsonrpsee_types::Id; + + let req = Request::new("say_hello".into(), None, Id::Number(1)); + let batch_entry = BatchEntry::Call(req.clone()); + assert_eq!( + serde_json::to_string(&batch_entry).unwrap(), + "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"say_hello\"}", + ); + + let notification = Notification::new("say_hello".into(), None); + let batch_entry = BatchEntry::Notification(notification.clone()); + assert_eq!( + serde_json::to_string(&batch_entry).unwrap(), + "{\"jsonrpc\":\"2.0\",\"method\":\"say_hello\",\"params\":null}", + ); + } +} diff --git a/core/src/server/method_response.rs b/core/src/server/method_response.rs index 06d0441ee1..2dcd49ba65 100644 --- a/core/src/server/method_response.rs +++ b/core/src/server/method_response.rs @@ -498,16 +498,6 @@ impl Future for MethodResponseFuture { } /// Bounded writer that allows writing at most `max_len` bytes. -/// -/// ``` -/// use std::io::Write; -/// -/// use jsonrpsee_core::server::helpers::BoundedWriter; -/// -/// let mut writer = BoundedWriter::new(10); -/// (&mut writer).write("hello".as_bytes()).unwrap(); -/// assert_eq!(std::str::from_utf8(&writer.into_bytes()).unwrap(), "hello"); -/// ``` #[derive(Debug, Clone)] struct BoundedWriter { max_len: usize, diff --git a/examples/examples/http.rs b/examples/examples/http.rs index 4e72d2927c..19d3b2cd4c 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -29,10 +29,10 @@ use std::time::Duration; use hyper::body::Bytes; use jsonrpsee::core::client::ClientT; -use jsonrpsee::core::middleware::{Notification, RpcServiceT}; +use jsonrpsee::core::middleware::{BatchEntry, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::rpc_params; -use jsonrpsee::server::{RpcModule, RpcServiceBuilder, Server}; +use jsonrpsee::server::{RpcModule, Server}; use jsonrpsee::types::Request; use tower_http::LatencyUnit; use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}; @@ -53,7 +53,7 @@ where self.0.call(req) } - fn batch(&self, reqs: Vec>) -> Self::Future { + fn batch(&self, reqs: Vec>) -> Self::Future { println!("logger layer : {:?}", reqs); self.0.batch(reqs) } diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index 4d814b2d6d..121ba9951b 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -38,7 +38,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use futures::FutureExt; use hyper::HeaderMap; use hyper::header::AUTHORIZATION; -use jsonrpsee::core::middleware::{Notification, ResponseFuture, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Batch, Notification, ResponseFuture, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::core::{BoxError, async_trait}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::proc_macros::rpc; @@ -96,8 +96,8 @@ where } } - fn batch(&self, reqs: Vec>) -> Self::Future { - ResponseFuture::future(self.inner.batch(reqs)) + fn batch(&self, batch: Batch<'a>) -> Self::Future { + ResponseFuture::future(self.inner.batch(batch)) } fn notification(&self, n: Notification<'a>) -> Self::Future { diff --git a/examples/examples/jsonrpsee_server_close_connection_from_rpc_handler.rs b/examples/examples/jsonrpsee_server_close_connection_from_rpc_handler.rs index e9c91a08b8..d834d35f81 100644 --- a/examples/examples/jsonrpsee_server_close_connection_from_rpc_handler.rs +++ b/examples/examples/jsonrpsee_server_close_connection_from_rpc_handler.rs @@ -35,10 +35,11 @@ use std::sync::Arc; use std::sync::atomic::{AtomicU32, Ordering}; use futures::FutureExt; +use jsonrpsee::core::middleware::RpcServiceBuilder; use jsonrpsee::core::{SubscriptionResult, async_trait}; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{ - ConnectionGuard, ConnectionState, HttpRequest, RpcServiceBuilder, ServerConfig, ServerHandle, StopHandle, http, + ConnectionGuard, ConnectionState, HttpRequest, ServerConfig, ServerHandle, StopHandle, http, serve_with_graceful_shutdown, stop_channel, ws, }; use jsonrpsee::types::ErrorObjectOwned; diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index 9d9c9371ef..9ef1f815e6 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -46,13 +46,13 @@ use std::sync::{Arc, Mutex}; use futures::FutureExt; use futures::future::BoxFuture; -use jsonrpsee::core::middleware::{Notification, RpcServiceT}; +use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::core::{BoxError, async_trait}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{ - ConnectionGuard, ConnectionState, RpcServiceBuilder, ServerConfig, ServerHandle, StopHandle, http, - serve_with_graceful_shutdown, stop_channel, ws, + ConnectionGuard, ConnectionState, ServerConfig, ServerHandle, StopHandle, http, serve_with_graceful_shutdown, + stop_channel, ws, }; use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, Request}; use jsonrpsee::ws_client::WsClientBuilder; @@ -102,8 +102,8 @@ where .boxed() } - fn batch(&self, reqs: Vec>) -> Self::Future { - Box::pin(self.service.batch(reqs)) + fn batch(&self, batch: Batch<'a>) -> Self::Future { + Box::pin(self.service.batch(batch)) } fn notification(&self, n: Notification<'a>) -> Self::Future { diff --git a/examples/examples/rpc_middleware.rs b/examples/examples/rpc_middleware.rs index b3ad9ad8ca..f9307e0f1d 100644 --- a/examples/examples/rpc_middleware.rs +++ b/examples/examples/rpc_middleware.rs @@ -44,7 +44,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use futures::FutureExt; use futures::future::BoxFuture; use jsonrpsee::core::client::ClientT; -use jsonrpsee::core::middleware::{Notification, ResponseBoxFuture, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Batch, Notification, ResponseBoxFuture, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, Server}; use jsonrpsee::types::Request; @@ -80,8 +80,8 @@ where .boxed() } - fn batch(&self, reqs: Vec>) -> Self::Future { - Box::pin(self.service.batch(reqs)) + fn batch(&self, batch: Batch<'a>) -> Self::Future { + Box::pin(self.service.batch(batch)) } fn notification(&self, n: Notification<'a>) -> Self::Future { @@ -117,8 +117,8 @@ where .boxed() } - fn batch(&self, reqs: Vec>) -> Self::Future { - Box::pin(self.service.batch(reqs)) + fn batch(&self, batch: Batch<'a>) -> Self::Future { + Box::pin(self.service.batch(batch)) } fn notification(&self, n: Notification<'a>) -> Self::Future { @@ -142,10 +142,9 @@ where self.0.call(req) } - fn batch(&self, reqs: Vec>) -> Self::Future { - self.0.batch(reqs) + fn batch(&self, batch: Batch<'a>) -> Self::Future { + self.0.batch(batch) } - fn notification(&self, n: Notification<'a>) -> Self::Future { self.0.notification(n) } diff --git a/examples/examples/rpc_middleware_modify_request.rs b/examples/examples/rpc_middleware_modify_request.rs index 22d4650f0a..c812f991a9 100644 --- a/examples/examples/rpc_middleware_modify_request.rs +++ b/examples/examples/rpc_middleware_modify_request.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use jsonrpsee::core::client::ClientT; -use jsonrpsee::core::middleware::{Notification, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::server::Server; use jsonrpsee::types::Request; use jsonrpsee::ws_client::WsClientBuilder; @@ -60,8 +60,8 @@ where self.0.call(req) } - fn batch(&self, requests: Vec>) -> Self::Future { - self.0.batch(requests) + fn batch(&self, batch: Batch<'a>) -> Self::Future { + self.0.batch(batch) } fn notification(&self, n: Notification<'a>) -> Self::Future { diff --git a/examples/examples/rpc_middleware_rate_limiting.rs b/examples/examples/rpc_middleware_rate_limiting.rs index 1fc73047f6..8414f06626 100644 --- a/examples/examples/rpc_middleware_rate_limiting.rs +++ b/examples/examples/rpc_middleware_rate_limiting.rs @@ -32,7 +32,7 @@ //! such as `Arc` use jsonrpsee::core::client::ClientT; -use jsonrpsee::core::middleware::{Notification, ResponseFuture, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Batch, Notification, ResponseFuture, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::server::Server; use jsonrpsee::types::{ErrorObject, Request}; use jsonrpsee::ws_client::WsClientBuilder; @@ -130,8 +130,8 @@ where } } - fn batch(&self, requests: Vec>) -> Self::Future { - ResponseFuture::future(self.service.batch(requests)) + fn batch(&self, batch: Batch<'a>) -> Self::Future { + ResponseFuture::future(self.service.batch(batch)) } fn notification(&self, n: Notification<'a>) -> Self::Future { diff --git a/examples/examples/server_with_connection_details.rs b/examples/examples/server_with_connection_details.rs index 0004fd5632..9f5ed37e3e 100644 --- a/examples/examples/server_with_connection_details.rs +++ b/examples/examples/server_with_connection_details.rs @@ -26,7 +26,7 @@ use std::net::SocketAddr; -use jsonrpsee::core::middleware::{Notification, Request, RpcServiceT}; +use jsonrpsee::core::middleware::{Batch, Notification, Request, RpcServiceT}; use jsonrpsee::core::{SubscriptionResult, async_trait}; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{PendingSubscriptionSink, SubscriptionMessage}; @@ -61,8 +61,8 @@ impl<'a, S: RpcServiceT<'a>> RpcServiceT<'a> for LoggingMiddleware { self.0.call(request) } - fn batch(&self, requests: Vec>) -> Self::Future { - self.0.batch(requests) + fn batch(&self, batch: Batch<'a>) -> Self::Future { + self.0.batch(batch) } fn notification(&self, n: Notification<'a>) -> Self::Future { @@ -135,7 +135,7 @@ async fn main() -> anyhow::Result<()> { } async fn run_server() -> anyhow::Result { - let rpc_middleware = jsonrpsee::server::RpcServiceBuilder::new().layer_fn(LoggingMiddleware); + let rpc_middleware = jsonrpsee::server::middleware::rpc::RpcServiceBuilder::new().layer_fn(LoggingMiddleware); let server = jsonrpsee::server::Server::builder().set_rpc_middleware(rpc_middleware).build("127.0.0.1:0").await?; let addr = server.local_addr()?; diff --git a/examples/examples/ws.rs b/examples/examples/ws.rs index 4309d1a90a..031df7d96b 100644 --- a/examples/examples/ws.rs +++ b/examples/examples/ws.rs @@ -27,7 +27,8 @@ use std::net::SocketAddr; use jsonrpsee::core::client::ClientT; -use jsonrpsee::server::{RpcServiceBuilder, Server}; +use jsonrpsee::core::middleware::RpcServiceBuilder; +use jsonrpsee::server::Server; use jsonrpsee::ws_client::WsClientBuilder; use jsonrpsee::{RpcModule, rpc_params}; use tracing_subscriber::util::SubscriberInitExt; diff --git a/proc-macros/tests/ui/correct/basic.rs b/proc-macros/tests/ui/correct/basic.rs index 01c21c16a3..e27363265a 100644 --- a/proc-macros/tests/ui/correct/basic.rs +++ b/proc-macros/tests/ui/correct/basic.rs @@ -135,8 +135,8 @@ impl RpcServer for RpcServerImpl { pub async fn server() -> SocketAddr { use hyper_util::rt::{TokioExecutor, TokioIo}; - use jsonrpsee::core::middleware::{Notification, RpcServiceT}; - use jsonrpsee::server::{RpcServiceBuilder, stop_channel}; + use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; + use jsonrpsee::server::stop_channel; use std::convert::Infallible; use std::sync::{Arc, atomic::AtomicU32}; use tower::Service; @@ -160,8 +160,8 @@ pub async fn server() -> SocketAddr { self.inner.call(request) } - fn batch(&self, requests: Vec>) -> Self::Future { - self.inner.batch(requests) + fn batch(&self, batch: Batch<'a>) -> Self::Future { + self.inner.batch(batch) } fn notification(&self, notif: Notification<'a>) -> Self::Future { diff --git a/server/src/lib.rs b/server/src/lib.rs index 43e6c7ee21..023d9a8046 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -43,7 +43,6 @@ mod tests; pub use future::{AlreadyStoppedError, ConnectionGuard, ConnectionPermit, ServerHandle, StopHandle, stop_channel}; pub use jsonrpsee_core::error::RegisterMethodError; -pub use jsonrpsee_core::middleware::RpcServiceBuilder; pub use jsonrpsee_core::server::*; pub use jsonrpsee_core::{id_providers::*, traits::IdProvider}; pub use jsonrpsee_types as types; diff --git a/server/src/middleware/rpc.rs b/server/src/middleware/rpc.rs index 1277fc469c..4d9970571b 100644 --- a/server/src/middleware/rpc.rs +++ b/server/src/middleware/rpc.rs @@ -26,18 +26,20 @@ //! JSON-RPC service middleware. +pub use jsonrpsee_core::middleware::*; +pub use jsonrpsee_core::server::MethodResponse; + use std::convert::Infallible; use std::sync::Arc; use crate::ConnectionId; use futures_util::future::FutureExt; -use jsonrpsee_core::middleware::{Notification, ResponseBoxFuture, RpcServiceT}; use jsonrpsee_core::server::{ - BatchResponseBuilder, BoundedSubscriptions, MethodCallback, MethodResponse, MethodSink, Methods, SubscriptionState, + BatchResponseBuilder, BoundedSubscriptions, MethodCallback, MethodSink, Methods, SubscriptionState, }; use jsonrpsee_core::traits::IdProvider; +use jsonrpsee_types::ErrorObject; use jsonrpsee_types::error::{ErrorCode, reject_too_many_subscriptions}; -use jsonrpsee_types::{ErrorObject, Request}; /// JSON-RPC service middleware. #[derive(Clone, Debug)] @@ -148,20 +150,47 @@ impl<'a> RpcServiceT<'a> for RpcService { } } - fn batch(&self, reqs: Vec>) -> Self::Future { + fn batch(&self, reqs: Vec>) -> Self::Future { let mut batch = BatchResponseBuilder::new_with_limit(self.max_response_body_size); let service = self.clone(); async move { - for req in reqs { - let rp = match service.call(req).await { - Ok(rp) => rp, - Err(e) => match e {}, - }; - if let Err(err) = batch.append(rp) { - return Ok(err); + let mut got_notification = false; + + for batch_entry in reqs { + match batch_entry { + BatchEntry::Call(req) => { + let rp = match service.call(req).await { + Ok(rp) => rp, + Err(e) => match e {}, + }; + if let Err(err) = batch.append(rp) { + return Ok(err); + } + } + BatchEntry::Notification(n) => { + got_notification = true; + match service.notification(n).await { + Ok(rp) => rp, + Err(e) => match e {}, + }; + } + BatchEntry::InvalidRequest(id) => { + let rp = MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidRequest)); + if let Err(err) = batch.append(rp) { + return Ok(err); + } + } } } - Ok(MethodResponse::from_batch(batch.finish())) + + // If the batch is empty and we got a notification, we return an empty response. + if batch.is_empty() && got_notification { + Ok(MethodResponse::notification()) + } + // An empty batch is regarded as an invalid request here. + else { + Ok(MethodResponse::from_batch(batch.finish())) + } } .boxed() } diff --git a/server/src/server.rs b/server/src/server.rs index 58c28bc2f6..7cb60ee8f6 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -45,7 +45,7 @@ use futures_util::io::{BufReader, BufWriter}; use hyper::body::Bytes; use hyper_util::rt::{TokioExecutor, TokioIo}; use jsonrpsee_core::id_providers::RandomIntegerIdProvider; -use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; +use jsonrpsee_core::middleware::{BatchEntry, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::server::helpers::prepare_error; use jsonrpsee_core::server::{BoundedSubscriptions, ConnectionId, MethodResponse, MethodSink, Methods}; use jsonrpsee_core::traits::IdProvider; @@ -664,11 +664,8 @@ impl Builder { /// use std::{time::Instant, net::SocketAddr, sync::Arc}; /// use std::sync::atomic::{Ordering, AtomicUsize}; /// - /// use jsonrpsee_server::middleware::rpc::{RpcServiceT, RpcService, RpcServiceBuilder}; - /// use jsonrpsee_server::{ServerBuilder, MethodResponse}; - /// use jsonrpsee_core::async_trait; - /// use jsonrpsee_types::Request; - /// use futures_util::future::BoxFuture; + /// use jsonrpsee_server::middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceT, MethodResponse, ResponseBoxFuture, Notification, Request, Batch}; + /// use jsonrpsee_server::ServerBuilder; /// /// #[derive(Clone)] /// struct MyMiddleware { @@ -679,7 +676,9 @@ impl Builder { /// impl<'a, S> RpcServiceT<'a> for MyMiddleware /// where S: RpcServiceT<'a> + Send + Sync + Clone + 'static, /// { - /// type Future = BoxFuture<'a, MethodResponse>; + /// type Future = ResponseBoxFuture<'a, Self::Response, Self::Error>; + /// type Error = S::Error; + /// type Response = S::Response; /// /// fn call(&self, req: Request<'a>) -> Self::Future { /// tracing::info!("MyMiddleware processed call {}", req.method); @@ -693,6 +692,15 @@ impl Builder { /// rp /// }) /// } + /// + /// fn batch(&self, batch: Batch<'a>) -> Self::Future { + /// Box::pin(self.service.batch(batch)) + /// } + /// + /// fn notification(&self, notif: Notification<'a>) -> Self::Future { + /// Box::pin(self.service.notification(notif)) + /// } + /// /// } /// /// // Create a state per connection @@ -1249,7 +1257,7 @@ where match rpc_service.notification(notif).await { Ok(rp) => rp, Err(e) => { - // We don't care about the error if it's a notification. + // We don't care about if middleware/service encountered if it's an notification. tracing::debug!(target: LOG_TARGET, "Notification error: {:?}", e); return MethodResponse::notification(); } @@ -1279,36 +1287,25 @@ where } let mut batch = Vec::with_capacity(unchecked_batch.len()); - let mut got_notif = false; for call in unchecked_batch { if let Ok(req) = deserialize_with_ext::call::from_str(call.get(), &extensions) { - batch.push(req); + batch.push(BatchEntry::Call(req)); } else if let Ok(notif) = deserialize_with_ext::notif::from_str::(call.get(), &extensions) { - // Notifications in a batch should not be answered but invoke middleware. - _ = rpc_service.notification(notif).await; - got_notif = true; + batch.push(BatchEntry::Notification(notif)); } else { let id = match serde_json::from_str::(call.get()) { Ok(err) => err.id, Err(_) => Id::Null, }; - return MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidRequest)); + batch.push(BatchEntry::InvalidRequest(id)); } } - let batch_response = match rpc_service.batch(batch).await { + match rpc_service.batch(batch).await { Ok(rp) => rp, - Err(e) => { - return MethodResponse::error(Id::Null, rpc_middleware_error(e)); - } - }; - - if got_notif && batch_response.as_result().is_empty() { - MethodResponse::notification() - } else { - batch_response + Err(e) => MethodResponse::error(Id::Null, rpc_middleware_error(e)), } } else { MethodResponse::error(Id::Null, ErrorObject::from(ErrorCode::ParseError)) diff --git a/server/src/tests/http.rs b/server/src/tests/http.rs index 0751bd40de..253161b285 100644 --- a/server/src/tests/http.rs +++ b/server/src/tests/http.rs @@ -33,7 +33,7 @@ use crate::{ }; use futures_util::future::{Future, FutureExt}; use hyper::body::Bytes; -use jsonrpsee_core::middleware::{Notification, ResponseBoxFuture, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee_core::middleware::{Batch, Notification, ResponseBoxFuture, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::{BoxError, RpcResult}; use jsonrpsee_test_utils::TimeoutFutureExt; use jsonrpsee_test_utils::helpers::*; @@ -75,8 +75,16 @@ where self.service.call(req).boxed() } - fn batch(&self, requests: Vec>) -> Self::Future { - self.service.batch(requests).boxed() + fn batch(&self, mut batch: Batch<'a>) -> Self::Future { + if let Some(last) = batch.iter_mut().last() { + if last.method_name().contains("err") { + last.extensions_mut().insert(StatusCode::IM_A_TEAPOT); + } else { + last.extensions_mut().insert(StatusCode::OK); + } + } + + self.service.batch(batch).boxed() } fn notification(&self, n: Notification<'a>) -> Self::Future { diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index 9b3ad49497..31268bc2d6 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -99,7 +99,7 @@ where // If the response is empty it means that it was a notification or empty batch. // For HTTP these are just ACK:ed with a empty body. - response::ok_response(rp.into_result()) + response::from_method_response(rp) } // Error scenarios: Method::POST => response::unsupported_content_type(), diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index ad55f2dac3..73c68f84b5 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -382,7 +382,7 @@ async fn graceful_shutdown( /// /// ```no_run /// use jsonrpsee_server::{ws, ServerConfig, Methods, ConnectionState, HttpRequest, HttpResponse}; -/// use jsonrpsee_server::middleware::rpc::{RpcServiceBuilder, RpcServiceT, RpcService}; +/// use jsonrpsee_server::middleware::rpc::{RpcServiceBuilder, RpcServiceT, RpcService, MethodResponse}; /// /// async fn handle_websocket_conn( /// req: HttpRequest, @@ -395,7 +395,7 @@ async fn graceful_shutdown( /// where /// L: for<'a> tower::Layer + 'static, /// >::Service: Send + Sync + 'static, -/// for<'a> >::Service: RpcServiceT<'a> + 'static, +/// for<'a> >::Service: RpcServiceT<'a, Response = MethodResponse> + 'static, /// { /// match ws::connect(req, server_cfg, methods, conn, rpc_middleware).await { /// Ok((rp, conn_fut)) => { diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index 5b43cc959b..5edb82ed6f 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -35,13 +35,11 @@ use std::time::Duration; use fast_socks5::client::Socks5Stream; use fast_socks5::server; use futures::{SinkExt, Stream, StreamExt}; -use jsonrpsee::core::middleware::Notification; +use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::server::middleware::http::ProxyGetRequestLayer; - -use jsonrpsee::core::middleware::RpcServiceT; use jsonrpsee::server::{ - ConnectionGuard, PendingSubscriptionSink, RpcModule, RpcServiceBuilder, Server, ServerBuilder, ServerHandle, - SubscriptionMessage, TrySendError, serve_with_graceful_shutdown, stop_channel, + ConnectionGuard, PendingSubscriptionSink, RpcModule, Server, ServerBuilder, ServerHandle, SubscriptionMessage, + TrySendError, serve_with_graceful_shutdown, stop_channel, }; use jsonrpsee::types::{ErrorObject, ErrorObjectOwned}; use jsonrpsee::{Methods, SubscriptionCloseResponse}; @@ -164,8 +162,8 @@ pub async fn server() -> SocketAddr { self.inner.call(request) } - fn batch(&self, requests: Vec>) -> Self::Future { - self.inner.batch(requests) + fn batch(&self, batch: Batch<'a>) -> Self::Future { + self.inner.batch(batch) } fn notification(&self, n: Notification<'a>) -> Self::Future { diff --git a/tests/tests/metrics.rs b/tests/tests/metrics.rs index 108030d1b4..6e936f5bad 100644 --- a/tests/tests/metrics.rs +++ b/tests/tests/metrics.rs @@ -37,7 +37,7 @@ use std::time::Duration; use futures::FutureExt; use futures::future::BoxFuture; use helpers::init_logger; -use jsonrpsee::core::middleware::{Notification, Request, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Batch, Notification, Request, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::core::{ClientError, client::ClientT}; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::proc_macros::rpc; @@ -99,8 +99,8 @@ where .boxed() } - fn batch(&self, requests: Vec>) -> Self::Future { - self.service.batch(requests).boxed() + fn batch(&self, batch: Batch<'a>) -> Self::Future { + self.service.batch(batch).boxed() } fn notification(&self, n: Notification<'a>) -> Self::Future { diff --git a/types/src/request.rs b/types/src/request.rs index 034297627d..c4e8d393e7 100644 --- a/types/src/request.rs +++ b/types/src/request.rs @@ -50,6 +50,7 @@ pub struct Request<'a> { pub method: Cow<'a, str>, /// Parameter values of the request. #[serde(borrow)] + #[serde(skip_serializing_if = "Option::is_none")] pub params: Option>, /// The request's extensions. #[serde(skip)] @@ -89,7 +90,7 @@ impl<'a> Request<'a> { } /// JSON-RPC Invalid request as defined in the [spec](https://www.jsonrpc.org/specification#request-object). -#[derive(Deserialize, Debug, PartialEq, Eq)] +#[derive(Deserialize, Debug, PartialEq, Eq, Clone)] pub struct InvalidRequest<'a> { /// Request ID #[serde(borrow)] From 46ef085a44aad728f25bac2d2a35eff2e1195a93 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 21 Mar 2025 16:33:52 +0100 Subject: [PATCH 15/52] remove ignore for batch test --- client/ws-client/src/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ws-client/src/tests.rs b/client/ws-client/src/tests.rs index 2ba6d5e9f0..51560ffd68 100644 --- a/client/ws-client/src/tests.rs +++ b/client/ws-client/src/tests.rs @@ -323,7 +323,6 @@ async fn batch_request_works() { } #[tokio::test] -#[ignore] async fn batch_request_out_of_order_response() { let mut batch_request = BatchRequestBuilder::new(); batch_request.insert("say_hello", rpc_params![]).unwrap(); From 575940707f8c914d607cee4e68a998d1ece611a1 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 22 Mar 2025 12:15:53 +0100 Subject: [PATCH 16/52] fix rustdocs --- server/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server.rs b/server/src/server.rs index 7cb60ee8f6..529e8dc7da 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -654,7 +654,7 @@ impl Builder { /// The builder itself exposes a similar API as the [`tower::ServiceBuilder`] /// where it is possible to compose layers to the middleware. /// - /// To add a middleware [`crate::RpcServiceBuilder`] exposes a few different layer APIs that + /// To add a middleware [`crate::middleware::rpc::RpcServiceBuilder`] exposes a few different layer APIs that /// is wrapped on top of the [`tower::ServiceBuilder`]. /// /// When the server is started these layers are wrapped in the [`crate::middleware::rpc::RpcService`] and From ee75d58078061295fb45089ad6aba5ae0561f554 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 25 Mar 2025 16:59:21 +0100 Subject: [PATCH 17/52] add rpc middleware for the async client --- client/http-client/src/client.rs | 19 +- client/http-client/src/rpc_service.rs | 2 + client/ws-client/Cargo.toml | 1 + client/ws-client/src/lib.rs | 48 ++++- core/src/client/async_client/helpers.rs | 25 +-- core/src/client/async_client/manager.rs | 170 ++++++++++------- core/src/client/async_client/mod.rs | 234 +++++++++++------------- core/src/client/mod.rs | 60 ++++-- core/src/middleware/mod.rs | 8 + examples/examples/core_client.rs | 2 +- 10 files changed, 333 insertions(+), 236 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 57c14758bf..a5baacb665 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -394,8 +394,8 @@ where let params = params.to_rpc_params()?; let request = Request::new(method.into(), params.as_deref(), id.clone()); - let method_response = self.transport.call(request).await?; - let rp = method_response.as_method_call().expect("Transport::call must return a method call"); + let rp = + self.transport.call(request).await?.into_method_call().expect("Transport::call must return a method call"); let result = rp.decode()?; if rp.id() == &id { Ok(result) } else { Err(InvalidRequestId::NotPendingRequest(rp.id().to_string()).into()) } @@ -427,8 +427,8 @@ where batch_request.push(BatchEntry::Call(req)); } - let method_response = self.transport.batch(batch_request).await?; - let json_rps = method_response.as_batch().expect("Transport::batch must return a batch"); + let json_rps = + self.transport.batch(batch_request).await?.into_batch().expect("Transport::batch must return a batch"); let mut batch_response = Vec::new(); let mut success = 0; @@ -440,7 +440,16 @@ where } for json_rp in json_rps.iter() { - let rp: Response<&JsonRawValue> = serde_json::from_str(json_rp.get()).map_err(Error::ParseError)?; + let json = match json_rp { + Ok(json) => json, + Err(e) => { + failed += 1; + batch_response.push(Err(e.clone())); + continue; + } + }; + + let rp: Response<&JsonRawValue> = serde_json::from_str(json.get()).map_err(Error::ParseError)?; let id = rp.id.try_parse_inner_as_number()?; let res = match ResponseSuccess::try_from(rp) { diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index bbb4bb3636..e6ed0b9134 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -66,6 +66,8 @@ where extensions.extend(call.into_extensions()); } + let json = json.into_iter().map(Ok).collect(); + Ok(MethodResponse::batch(json, extensions)) } .boxed() diff --git a/client/ws-client/Cargo.toml b/client/ws-client/Cargo.toml index a457359ac4..bfbb7a7cdf 100644 --- a/client/ws-client/Cargo.toml +++ b/client/ws-client/Cargo.toml @@ -22,6 +22,7 @@ jsonrpsee-types = { workspace = true } jsonrpsee-client-transport = { workspace = true, features = ["ws"] } jsonrpsee-core = { workspace = true, features = ["async-client"] } url = { workspace = true } +tower = { workspace = true } [dev-dependencies] tracing-subscriber = { workspace = true } diff --git a/client/ws-client/src/lib.rs b/client/ws-client/src/lib.rs index 255dfb1d3c..0ad4b3df6d 100644 --- a/client/ws-client/src/lib.rs +++ b/client/ws-client/src/lib.rs @@ -41,12 +41,15 @@ mod tests; pub use http::{HeaderMap, HeaderValue}; pub use jsonrpsee_core::client::Client as WsClient; pub use jsonrpsee_core::client::async_client::PingConfig; +pub use jsonrpsee_core::client::async_client::RpcService; +pub use jsonrpsee_core::middleware::RpcServiceBuilder; pub use jsonrpsee_types as types; use jsonrpsee_client_transport::ws::{AsyncRead, AsyncWrite, WsTransportClientBuilder}; use jsonrpsee_core::TEN_MB_SIZE_BYTES; use jsonrpsee_core::client::{ClientBuilder, Error, IdKind, MaybeSend, TransportReceiverT, TransportSenderT}; use std::time::Duration; +use tower::layer::util::Identity; use url::Url; #[cfg(feature = "tls")] @@ -81,7 +84,7 @@ use jsonrpsee_client_transport::ws::CertificateStore; /// /// ``` #[derive(Clone, Debug)] -pub struct WsClientBuilder { +pub struct WsClientBuilder { #[cfg(feature = "tls")] certificate_store: CertificateStore, max_request_size: u32, @@ -96,9 +99,10 @@ pub struct WsClientBuilder { id_kind: IdKind, max_log_length: u32, tcp_no_delay: bool, + service_builder: RpcServiceBuilder, } -impl Default for WsClientBuilder { +impl Default for WsClientBuilder { fn default() -> Self { Self { #[cfg(feature = "tls")] @@ -115,13 +119,14 @@ impl Default for WsClientBuilder { id_kind: IdKind::Number, max_log_length: 4096, tcp_no_delay: true, + service_builder: RpcServiceBuilder::default(), } } } -impl WsClientBuilder { +impl WsClientBuilder { /// Create a new WebSocket client builder. - pub fn new() -> WsClientBuilder { + pub fn new() -> WsClientBuilder { WsClientBuilder::default() } @@ -273,15 +278,36 @@ impl WsClientBuilder { self } + /// Set the RPC service builder. + pub fn set_rpc_middleware(self, service_builder: RpcServiceBuilder) -> WsClientBuilder { + WsClientBuilder { + certificate_store: self.certificate_store, + max_request_size: self.max_request_size, + max_response_size: self.max_response_size, + request_timeout: self.request_timeout, + connection_timeout: self.connection_timeout, + ping_config: self.ping_config, + headers: self.headers, + max_concurrent_requests: self.max_concurrent_requests, + max_buffer_capacity_per_subscription: self.max_buffer_capacity_per_subscription, + max_redirections: self.max_redirections, + id_kind: self.id_kind, + max_log_length: self.max_log_length, + tcp_no_delay: self.tcp_no_delay, + service_builder, + } + } + /// Build the [`WsClient`] with specified [`TransportSenderT`] [`TransportReceiverT`] parameters /// /// ## Panics /// /// Panics if being called outside of `tokio` runtime context. - pub fn build_with_transport(self, sender: S, receiver: R) -> WsClient + pub fn build_with_transport(self, sender: S, receiver: R) -> WsClient where S: TransportSenderT + Send, R: TransportReceiverT + Send, + RpcMiddleware: tower::Layer + Clone + Send + Sync + 'static, { let Self { max_concurrent_requests, @@ -291,6 +317,7 @@ impl WsClientBuilder { id_kind, max_log_length, tcp_no_delay, + service_builder, .. } = self; @@ -300,7 +327,8 @@ impl WsClientBuilder { .max_concurrent_requests(max_concurrent_requests) .id_format(id_kind) .set_max_logging_length(max_log_length) - .set_tcp_no_delay(tcp_no_delay); + .set_tcp_no_delay(tcp_no_delay) + .set_rpc_middleware(service_builder); if let Some(cfg) = ping_config { client = client.enable_ws_ping(cfg); @@ -314,9 +342,10 @@ impl WsClientBuilder { /// ## Panics /// /// Panics if being called outside of `tokio` runtime context. - pub async fn build_with_stream(self, url: impl AsRef, data_stream: T) -> Result + pub async fn build_with_stream(self, url: impl AsRef, data_stream: T) -> Result, Error> where T: AsyncRead + AsyncWrite + Unpin + MaybeSend + 'static, + RpcMiddleware: tower::Layer + Clone + Send + Sync + 'static, { let transport_builder = WsTransportClientBuilder { #[cfg(feature = "tls")] @@ -343,7 +372,10 @@ impl WsClientBuilder { /// ## Panics /// /// Panics if being called outside of `tokio` runtime context. - pub async fn build(self, url: impl AsRef) -> Result { + pub async fn build(self, url: impl AsRef) -> Result, Error> + where + RpcMiddleware: tower::Layer + Clone + Send + Sync + 'static, + { let transport_builder = WsTransportClientBuilder { #[cfg(feature = "tls")] certificate_store: self.certificate_store.clone(), diff --git a/core/src/client/async_client/helpers.rs b/core/src/client/async_client/helpers.rs index 21c54b85a9..907e187bb8 100644 --- a/core/src/client/async_client/helpers.rs +++ b/core/src/client/async_client/helpers.rs @@ -25,13 +25,14 @@ // DEALINGS IN THE SOFTWARE. use crate::client::async_client::manager::{RequestManager, RequestStatus}; -use crate::client::async_client::{Notification, LOG_TARGET}; -use crate::client::{subscription_channel, Error, RequestMessage, TransportSenderT, TrySubscriptionSendError}; +use crate::client::async_client::{LOG_TARGET, Notification}; +use crate::client::{Error, RequestMessage, TransportSenderT, TrySubscriptionSendError, subscription_channel}; use crate::params::ArrayParams; use crate::traits::ToRpcParams; use futures_timer::Delay; use futures_util::future::{self, Either}; +use serde_json::value::RawValue; use tokio::sync::oneshot; use jsonrpsee_types::response::SubscriptionError; @@ -44,7 +45,7 @@ use std::ops::Range; #[derive(Debug, Clone)] pub(crate) struct InnerBatchResponse { pub(crate) id: u64, - pub(crate) result: Result>, + pub(crate) result: Result, ErrorObject<'static>>, } /// Attempts to process a batch response. @@ -173,7 +174,7 @@ pub(crate) fn process_notification(manager: &mut RequestManager, notif: Notifica /// Returns `Err(_)` if the response couldn't be handled. pub(crate) fn process_single_response( manager: &mut RequestManager, - response: Response, + response: Response>, max_capacity_per_subscription: usize, ) -> Result, InvalidRequestId> { let response_id = response.id.clone().into_owned(); @@ -195,16 +196,18 @@ pub(crate) fn process_single_response( .complete_pending_subscription(response_id.clone()) .ok_or(InvalidRequestId::NotPendingRequest(response_id.to_string()))?; - let sub_id = result.map(|r| SubscriptionId::try_from(r).ok()); - - let sub_id = match sub_id { - Ok(Some(sub_id)) => sub_id, - Ok(None) => { - let _ = send_back_oneshot.send(Err(Error::InvalidSubscriptionId)); + let json = match result { + Ok(s) => s, + Err(e) => { + let _ = send_back_oneshot.send(Err(e)); return Ok(None); } + }; + + let sub_id = match serde_json::from_str::(json.get()) { + Ok(s) => s.into_owned(), Err(e) => { - let _ = send_back_oneshot.send(Err(e)); + let _ = send_back_oneshot.send(Err(e.into())); return Ok(None); } }; diff --git a/core/src/client/async_client/manager.rs b/core/src/client/async_client/manager.rs index f638b80164..67e8f44631 100644 --- a/core/src/client/async_client/manager.rs +++ b/core/src/client/async_client/manager.rs @@ -33,7 +33,7 @@ //! - SubscriptionId: unique ID generated by server use std::{ - collections::{hash_map::Entry, HashMap}, + collections::{HashMap, hash_map::Entry}, ops::Range, }; @@ -43,7 +43,7 @@ use crate::{ }; use jsonrpsee_types::{Id, SubscriptionId}; use rustc_hash::FxHashMap; -use serde_json::value::Value as JsonValue; +use serde_json::value::RawValue; use tokio::sync::oneshot; #[derive(Debug)] @@ -66,8 +66,8 @@ pub(crate) enum RequestStatus { Invalid, } -type PendingCallOneshot = Option>>; -type PendingBatchOneshot = oneshot::Sender>, Error>>; +type PendingCallOneshot = Option, Error>>>; +type PendingBatchOneshot = oneshot::Sender>>, Error>>; type PendingSubscriptionOneshot = oneshot::Sender), Error>>; type SubscriptionSink = SubscriptionSender; type UnsubscribeMethod = String; @@ -315,11 +315,7 @@ impl RequestManager { /// /// Returns `Some` if the `request_id` was registered as a subscription otherwise `None`. pub(crate) fn as_subscription_mut(&mut self, request_id: &RequestId) -> Option<&mut SubscriptionSink> { - if let Some(Kind::Subscription((_, sink, _))) = self.requests.get_mut(request_id) { - Some(sink) - } else { - None - } + if let Some(Kind::Subscription((_, sink, _))) = self.requests.get_mut(request_id) { Some(sink) } else { None } } /// Get a mutable reference to underlying `Sink` in order to send incoming notifications to the subscription. @@ -343,12 +339,12 @@ mod tests { use super::{Error, RequestManager}; use jsonrpsee_types::{Id, SubscriptionId}; - use serde_json::Value as JsonValue; + use serde_json::value::RawValue; use tokio::sync::oneshot; #[test] fn insert_remove_pending_request_works() { - let (request_tx, _) = oneshot::channel::>(); + let (request_tx, _) = oneshot::channel::, Error>>(); let mut manager = RequestManager::new(); assert!(manager.insert_pending_call(Id::Number(0), Some(request_tx)).is_ok()); @@ -360,26 +356,30 @@ mod tests { let (pending_sub_tx, _) = oneshot::channel(); let (sub_tx, _) = subscription_channel(1); let mut manager = RequestManager::new(); - assert!(manager - .insert_pending_subscription(Id::Number(1), Id::Number(2), pending_sub_tx, "unsubscribe_method".into()) - .is_ok()); + assert!( + manager + .insert_pending_subscription(Id::Number(1), Id::Number(2), pending_sub_tx, "unsubscribe_method".into()) + .is_ok() + ); let (unsub_req_id, _send_back_oneshot, unsubscribe_method) = manager.complete_pending_subscription(Id::Number(1)).unwrap(); assert_eq!(unsub_req_id, Id::Number(2)); - assert!(manager - .insert_subscription( - Id::Number(1), - Id::Number(2), - SubscriptionId::Str("uniq_id_from_server".into()), - sub_tx, - unsubscribe_method - ) - .is_ok()); + assert!( + manager + .insert_subscription( + Id::Number(1), + Id::Number(2), + SubscriptionId::Str("uniq_id_from_server".into()), + sub_tx, + unsubscribe_method + ) + .is_ok() + ); assert!(manager.as_subscription_mut(&Id::Number(1)).is_some()); - assert!(manager - .remove_subscription(Id::Number(1), SubscriptionId::Str("uniq_id_from_server".into())) - .is_some()); + assert!( + manager.remove_subscription(Id::Number(1), SubscriptionId::Str("uniq_id_from_server".into())).is_some() + ); } #[test] @@ -389,12 +389,16 @@ mod tests { let (tx3, _) = oneshot::channel(); let (tx4, _) = oneshot::channel(); let mut manager = RequestManager::new(); - assert!(manager - .insert_pending_subscription(Id::Str("1".into()), Id::Str("1".into()), tx1, "unsubscribe_method".into()) - .is_err()); - assert!(manager - .insert_pending_subscription(Id::Str("0".into()), Id::Str("1".into()), tx2, "unsubscribe_method".into()) - .is_ok()); + assert!( + manager + .insert_pending_subscription(Id::Str("1".into()), Id::Str("1".into()), tx1, "unsubscribe_method".into()) + .is_err() + ); + assert!( + manager + .insert_pending_subscription(Id::Str("0".into()), Id::Str("1".into()), tx2, "unsubscribe_method".into()) + .is_ok() + ); assert!( manager .insert_pending_subscription( @@ -429,18 +433,22 @@ mod tests { let mut manager = RequestManager::new(); assert!(manager.insert_pending_call(Id::Number(0), Some(request_tx1)).is_ok()); assert!(manager.insert_pending_call(Id::Number(0), Some(request_tx2)).is_err()); - assert!(manager - .insert_pending_subscription(Id::Number(0), Id::Number(1), pending_sub_tx, "beef".to_string()) - .is_err()); - assert!(manager - .insert_subscription( - Id::Number(0), - Id::Number(99), - SubscriptionId::Num(137), - sub_tx, - "bibimbap".to_string() - ) - .is_err()); + assert!( + manager + .insert_pending_subscription(Id::Number(0), Id::Number(1), pending_sub_tx, "beef".to_string()) + .is_err() + ); + assert!( + manager + .insert_subscription( + Id::Number(0), + Id::Number(99), + SubscriptionId::Num(137), + sub_tx, + "bibimbap".to_string() + ) + .is_err() + ); assert!(manager.remove_subscription(Id::Number(0), SubscriptionId::Num(137)).is_none()); assert!(manager.complete_pending_subscription(Id::Number(0)).is_none()); @@ -455,23 +463,29 @@ mod tests { let (sub_tx, _) = subscription_channel(1); let mut manager = RequestManager::new(); - assert!(manager - .insert_pending_subscription(Id::Number(99), Id::Number(100), pending_sub_tx1, "beef".to_string()) - .is_ok()); + assert!( + manager + .insert_pending_subscription(Id::Number(99), Id::Number(100), pending_sub_tx1, "beef".to_string()) + .is_ok() + ); assert!(manager.insert_pending_call(Id::Number(99), Some(request_tx)).is_err()); - assert!(manager - .insert_pending_subscription(Id::Number(99), Id::Number(1337), pending_sub_tx2, "vegan".to_string()) - .is_err()); - - assert!(manager - .insert_subscription( - Id::Number(99), - Id::Number(100), - SubscriptionId::Num(0), - sub_tx, - "bibimbap".to_string() - ) - .is_err()); + assert!( + manager + .insert_pending_subscription(Id::Number(99), Id::Number(1337), pending_sub_tx2, "vegan".to_string()) + .is_err() + ); + + assert!( + manager + .insert_subscription( + Id::Number(99), + Id::Number(100), + SubscriptionId::Num(0), + sub_tx, + "bibimbap".to_string() + ) + .is_err() + ); assert!(manager.remove_subscription(Id::Number(99), SubscriptionId::Num(0)).is_none()); assert!(manager.complete_pending_call(Id::Number(99)).is_none()); @@ -487,15 +501,33 @@ mod tests { let mut manager = RequestManager::new(); - assert!(manager - .insert_subscription(Id::Number(3), Id::Number(4), SubscriptionId::Num(0), sub_tx1, "bibimbap".to_string()) - .is_ok()); - assert!(manager - .insert_subscription(Id::Number(3), Id::Number(4), SubscriptionId::Num(1), sub_tx2, "bibimbap".to_string()) - .is_err()); - assert!(manager - .insert_pending_subscription(Id::Number(3), Id::Number(4), pending_sub_tx, "beef".to_string()) - .is_err()); + assert!( + manager + .insert_subscription( + Id::Number(3), + Id::Number(4), + SubscriptionId::Num(0), + sub_tx1, + "bibimbap".to_string() + ) + .is_ok() + ); + assert!( + manager + .insert_subscription( + Id::Number(3), + Id::Number(4), + SubscriptionId::Num(1), + sub_tx2, + "bibimbap".to_string() + ) + .is_err() + ); + assert!( + manager + .insert_pending_subscription(Id::Number(3), Id::Number(4), pending_sub_tx, "beef".to_string()) + .is_err() + ); assert!(manager.insert_pending_call(Id::Number(3), Some(request_tx)).is_err()); assert!(manager.remove_subscription(Id::Number(3), SubscriptionId::Num(7)).is_none()); diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index 96ae3c62ae..4f46831248 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -28,43 +28,46 @@ mod helpers; mod manager; +mod rpc_service; mod utils; +pub use rpc_service::{Error as RpcServiceError, RpcService}; + use crate::JsonRawValue; use crate::client::async_client::helpers::{InnerBatchResponse, process_subscription_close_response}; use crate::client::async_client::utils::MaybePendingFutures; use crate::client::{ - BatchMessage, BatchResponse, ClientT, Error, ReceivedMessage, RegisterNotificationMessage, RequestMessage, - Subscription, SubscriptionClientT, SubscriptionKind, SubscriptionMessage, TransportReceiverT, TransportSenderT, + BatchResponse, ClientT, Error, ReceivedMessage, RegisterNotificationMessage, Subscription, SubscriptionClientT, + SubscriptionKind, TransportReceiverT, TransportSenderT, }; use crate::error::RegisterMethodError; +use crate::middleware::{BatchEntry, IsSubscription, Request, RpcServiceBuilder, RpcServiceT}; use crate::params::{BatchRequestBuilder, EmptyBatchRequest}; -use crate::tracing::client::{rx_log_from_json, tx_log_from_str}; use crate::traits::ToRpcParams; use std::borrow::Cow as StdCow; +use async_trait::async_trait; use core::time::Duration; +use futures_util::Stream; +use futures_util::future::{self, Either}; +use futures_util::stream::StreamExt; use helpers::{ build_unsubscribe_message, call_with_timeout, process_batch_response, process_notification, process_single_response, process_subscription_response, stop_subscription, }; +use http::Extensions; +use jsonrpsee_types::response::SubscriptionError; use jsonrpsee_types::{InvalidRequestId, ResponseSuccess, TwoPointZero}; +use jsonrpsee_types::{Response, SubscriptionResponse}; use manager::RequestManager; -use std::sync::Arc; - -use async_trait::async_trait; -use futures_timer::Delay; -use futures_util::Stream; -use futures_util::future::{self, Either}; -use futures_util::stream::StreamExt; -use jsonrpsee_types::response::{ResponsePayload, SubscriptionError}; -use jsonrpsee_types::{NotificationSer, RequestSer, Response, SubscriptionResponse}; use serde::de::DeserializeOwned; +use std::sync::Arc; use tokio::sync::{mpsc, oneshot}; +use tower::layer::util::Identity; use tracing::instrument; use self::utils::{InactivityCheck, IntervalStream}; -use super::{FrontToBack, IdKind, RequestIdManager, generate_batch_id_range, subscription_channel}; +use super::{FrontToBack, IdKind, MethodResponse, RequestIdManager, generate_batch_id_range, subscription_channel}; pub(crate) type Notification<'a> = jsonrpsee_types::Notification<'a, Option>; @@ -177,8 +180,8 @@ impl ErrorFromBack { } /// Builder for [`Client`]. -#[derive(Debug, Copy, Clone)] -pub struct ClientBuilder { +#[derive(Debug, Clone)] +pub struct ClientBuilder { request_timeout: Duration, max_concurrent_requests: usize, max_buffer_capacity_per_subscription: usize, @@ -186,9 +189,10 @@ pub struct ClientBuilder { max_log_length: u32, ping_config: Option, tcp_no_delay: bool, + service_builder: RpcServiceBuilder, } -impl Default for ClientBuilder { +impl Default for ClientBuilder { fn default() -> Self { Self { request_timeout: Duration::from_secs(60), @@ -198,13 +202,14 @@ impl Default for ClientBuilder { max_log_length: 4096, ping_config: None, tcp_no_delay: true, + service_builder: RpcServiceBuilder::new(), } } } -impl ClientBuilder { +impl ClientBuilder { /// Create a builder for the client. - pub fn new() -> ClientBuilder { + pub fn new() -> ClientBuilder { ClientBuilder::default() } @@ -279,6 +284,20 @@ impl ClientBuilder { self } + /// Set the rpc middleware service. + pub fn set_rpc_middleware(self, service_builder: RpcServiceBuilder) -> ClientBuilder { + ClientBuilder { + request_timeout: self.request_timeout, + max_concurrent_requests: self.max_concurrent_requests, + max_buffer_capacity_per_subscription: self.max_buffer_capacity_per_subscription, + id_kind: self.id_kind, + max_log_length: self.max_log_length, + ping_config: self.ping_config, + tcp_no_delay: self.tcp_no_delay, + service_builder, + } + } + /// Build the client with given transport. /// /// ## Panics @@ -286,10 +305,11 @@ impl ClientBuilder { /// Panics if called outside of `tokio` runtime context. #[cfg(feature = "async-client")] #[cfg_attr(docsrs, doc(cfg(feature = "async-client")))] - pub fn build_with_tokio(self, sender: S, receiver: R) -> Client + pub fn build_with_tokio(self, sender: S, receiver: R) -> Client where S: TransportSenderT + Send, R: TransportReceiverT + Send, + L: tower::Layer + Clone + Send + Sync + 'static, { let (to_back, from_front) = mpsc::channel(self.max_concurrent_requests); let disconnect_reason = SharedDisconnectReason::default(); @@ -344,6 +364,7 @@ impl ClientBuilder { Client { to_back: to_back.clone(), + service: self.service_builder.service(RpcService::new(to_back.clone(), self.request_timeout)), request_timeout: self.request_timeout, error: ErrorFromBack::new(to_back, disconnect_reason), id_manager: RequestIdManager::new(self.id_kind), @@ -413,7 +434,7 @@ impl ClientBuilder { /// Generic asynchronous client. #[derive(Debug)] -pub struct Client { +pub struct Client { /// Channel to send requests to the background task. to_back: mpsc::Sender, error: ErrorFromBack, @@ -427,12 +448,13 @@ pub struct Client { max_log_length: u32, /// When the client is dropped a message is sent to the background thread. on_exit: Option>, + service: L, } -impl Client { +impl Client { /// Create a builder for the server. - pub fn builder() -> ClientBuilder { - ClientBuilder::new() + pub fn builder() -> ClientBuilder { + ClientBuilder::::new() } /// Checks if the client is connected to the target. @@ -440,6 +462,17 @@ impl Client { !self.to_back.is_closed() } + async fn map_rpc_service_err( + &self, + fut: impl Future>, + ) -> Result { + match fut.await { + Ok(r) => Ok(r), + Err(RpcServiceError::Client(e)) => Err(e), + Err(RpcServiceError::FetchFromBackend) => Err(self.on_disconnect().await), + } + } + /// Completes when the client is disconnected or the client's background task encountered an error. /// If the client is already disconnected, the future produced by this method will complete immediately. /// @@ -456,7 +489,7 @@ impl Client { } } -impl Drop for Client { +impl Drop for Client { fn drop(&mut self) { if let Some(e) = self.on_exit.take() { let _ = e.send(()); @@ -465,7 +498,10 @@ impl Drop for Client { } #[async_trait] -impl ClientT for Client { +impl ClientT for Client +where + for<'a> L: RpcServiceT<'a, Error = RpcServiceError, Response = MethodResponse> + Send + Sync, +{ #[instrument(name = "notification", skip(self, params), level = "trace")] async fn notification(&self, method: &str, params: Params) -> Result<(), Error> where @@ -473,22 +509,10 @@ impl ClientT for Client { { // NOTE: we use this to guard against max number of concurrent requests. let _req_id = self.id_manager.next_request_id(); - let params = params.to_rpc_params()?; - let notif = NotificationSer::borrowed(&method, params.as_deref()); - - let raw = serde_json::to_string(¬if).map_err(Error::ParseError)?; - tx_log_from_str(&raw, self.max_log_length); - - let sender = self.to_back.clone(); - let fut = sender.send(FrontToBack::Notification(raw)); - - tokio::pin!(fut); - - match future::select(fut, Delay::new(self.request_timeout)).await { - Either::Left((Ok(()), _)) => Ok(()), - Either::Left((Err(_), _)) => Err(self.on_disconnect().await), - Either::Right((_, _)) => Err(Error::RequestTimeout), - } + let params = params.to_rpc_params()?.map(StdCow::Owned); + let fut = self.service.notification(jsonrpsee_types::Notification::new(method.into(), params)); + self.map_rpc_service_err(fut).await?; + Ok(()) } #[instrument(name = "method_call", skip(self, params), level = "trace")] @@ -497,33 +521,14 @@ impl ClientT for Client { R: DeserializeOwned, Params: ToRpcParams + Send, { - let (send_back_tx, send_back_rx) = oneshot::channel(); let id = self.id_manager.next_request_id(); - let params = params.to_rpc_params()?; - let raw = - serde_json::to_string(&RequestSer::borrowed(&id, &method, params.as_deref())).map_err(Error::ParseError)?; - tx_log_from_str(&raw, self.max_log_length); - - if self - .to_back - .clone() - .send(FrontToBack::Request(RequestMessage { raw, id: id.clone(), send_back: Some(send_back_tx) })) - .await - .is_err() - { - return Err(self.on_disconnect().await); - } - let json_value = match call_with_timeout(self.request_timeout, send_back_rx).await { - Ok(Ok(v)) => v, - Ok(Err(err)) => return Err(err), - Err(_) => return Err(self.on_disconnect().await), - }; + let request = jsonrpsee_types::Request::new(method.into(), params.as_deref(), id.clone()); + let fut = self.service.call(request); + let rp = self.map_rpc_service_err(fut).await?.into_method_call().expect("Method call response"); - rx_log_from_json(&Response::new(ResponsePayload::success_borrowed(&json_value), id), self.max_log_length); - - serde_json::from_value(json_value).map_err(Error::ParseError) + serde_json::from_str(rp.json.get()).map_err(Error::ParseError) } #[instrument(name = "batch", skip(self, batch), level = "trace")] @@ -535,41 +540,22 @@ impl ClientT for Client { let id = self.id_manager.next_request_id(); let id_range = generate_batch_id_range(id, batch.len() as u64)?; - let mut batches = Vec::with_capacity(batch.len()); - for ((method, params), id) in batch.into_iter().zip(id_range.clone()) { - let id = self.id_manager.as_id_kind().into_id(id); - batches.push(RequestSer { - jsonrpc: TwoPointZero, - id, - method: method.into(), - params: params.map(StdCow::Owned), - }); - } - - let (send_back_tx, send_back_rx) = oneshot::channel(); - - let raw = serde_json::to_string(&batches).map_err(Error::ParseError)?; - - tx_log_from_str(&raw, self.max_log_length); - - if self - .to_back - .clone() - .send(FrontToBack::Batch(BatchMessage { raw, ids: id_range, send_back: send_back_tx })) - .await - .is_err() - { - return Err(self.on_disconnect().await); - } - - let res = call_with_timeout(self.request_timeout, send_back_rx).await; - let json_values = match res { - Ok(Ok(v)) => v, - Ok(Err(err)) => return Err(err), - Err(_) => return Err(self.on_disconnect().await), - }; - - rx_log_from_json(&json_values, self.max_log_length); + let batch: Vec<_> = batch + .into_iter() + .zip(id_range.clone()) + .map(|((method, params), id)| { + BatchEntry::Call(jsonrpsee_types::Request { + jsonrpc: TwoPointZero, + id: self.id_manager.as_id_kind().into_id(id), + method: method.into(), + params: params.map(StdCow::Owned), + extensions: Extensions::new(), + }) + }) + .collect(); + + let fut = self.service.batch(batch); + let json_values = self.map_rpc_service_err(fut).await?.into_batch().expect("Batch response"); let mut responses = Vec::with_capacity(json_values.len()); let mut successful_calls = 0; @@ -578,7 +564,7 @@ impl ClientT for Client { for json_val in json_values { match json_val { Ok(val) => { - let result: R = serde_json::from_value(val).map_err(Error::ParseError)?; + let result: R = serde_json::from_str(val.get()).map_err(Error::ParseError)?; responses.push(Ok(result)); successful_calls += 1; } @@ -593,7 +579,10 @@ impl ClientT for Client { } #[async_trait] -impl SubscriptionClientT for Client { +impl SubscriptionClientT for Client +where + for<'a> L: RpcServiceT<'a, Error = RpcServiceError, Response = MethodResponse> + Send + Sync, +{ /// Send a subscription request to the server. /// /// The `subscribe_method` and `params` are used to ask for the subscription towards the @@ -617,35 +606,24 @@ impl SubscriptionClientT for Client { let id_unsub = self.id_manager.next_request_id(); let params = params.to_rpc_params()?; - let raw = serde_json::to_string(&RequestSer::borrowed(&id_sub, &subscribe_method, params.as_deref())) - .map_err(Error::ParseError)?; - - tx_log_from_str(&raw, self.max_log_length); - - let (send_back_tx, send_back_rx) = tokio::sync::oneshot::channel(); - if self - .to_back - .clone() - .send(FrontToBack::Subscribe(SubscriptionMessage { - raw, - subscribe_id: id_sub, - unsubscribe_id: id_unsub.clone(), - unsubscribe_method: unsubscribe_method.to_owned(), - send_back: send_back_tx, - })) - .await - .is_err() - { - return Err(self.on_disconnect().await); - } - - let (notifs_rx, sub_id) = match call_with_timeout(self.request_timeout, send_back_rx).await { - Ok(Ok(val)) => val, - Ok(Err(err)) => return Err(err), - Err(_) => return Err(self.on_disconnect().await), + let mut ext = Extensions::new(); + ext.insert(IsSubscription { + sub_id: id_sub.clone(), + unsub_id: id_unsub.clone(), + unsub_method: unsubscribe_method.to_owned(), + }); + + let req = Request { + jsonrpc: TwoPointZero, + id: id_sub, + method: subscribe_method.into(), + params: params.map(StdCow::Owned), + extensions: ext, }; - rx_log_from_json(&Response::new(ResponsePayload::success_borrowed(&sub_id), id_unsub), self.max_log_length); + let fut = self.service.call(req); + let (sub_id, notifs_rx) = + self.map_rpc_service_err(fut).await?.into_subscription().expect("Subscription response"); Ok(Subscription::new(self.to_back.clone(), notifs_rx, SubscriptionKind::Subscription(sub_id))) } diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 73275e5075..7e86cc689b 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -33,9 +33,6 @@ cfg_async_client! { pub mod error; pub use error::Error; -use http::Extensions; -use serde::Deserialize; -use serde_json::value::RawValue; use std::fmt; use std::ops::Range; @@ -47,12 +44,16 @@ use tokio::sync::mpsc::error::TrySendError; use crate::params::BatchRequestBuilder; use crate::traits::ToRpcParams; + use async_trait::async_trait; use core::marker::PhantomData; use futures_util::stream::{Stream, StreamExt}; -use jsonrpsee_types::{ErrorObject, Id, SubscriptionId}; +use http::Extensions; +use jsonrpsee_types::{ErrorObject, ErrorObjectOwned, Id, SubscriptionId}; +use serde::Deserialize; use serde::de::DeserializeOwned; use serde_json::Value as JsonValue; +use serde_json::value::RawValue; use tokio::sync::{mpsc, oneshot}; /// Shared state whether a subscription has lagged or not. @@ -335,7 +336,7 @@ struct BatchMessage { /// Request IDs. ids: Range, /// One-shot channel over which we send back the result of this request. - send_back: oneshot::Sender>, Error>>, + send_back: oneshot::Sender>>, Error>>, } /// Request message. @@ -346,7 +347,7 @@ struct RequestMessage { /// Request ID. id: Id<'static>, /// One-shot channel over which we send back the result of this request. - send_back: Option>>, + send_back: Option, Error>>>, } /// Subscription message. @@ -650,11 +651,23 @@ fn subscription_channel(max_buf_size: usize) -> (SubscriptionSender, Subscriptio (SubscriptionSender { inner: tx, lagged: lagged_tx }, SubscriptionReceiver { inner: rx, lagged: lagged_rx }) } -#[derive(Debug, Clone)] +#[derive(Debug)] enum MethodResponseKind { MethodCall(MethodCall), + Subscription(SubscriptionResponse), Notification, - Batch(Vec>), + Batch(Vec, ErrorObjectOwned>>), +} + +#[derive(Debug)] +/// Represents an active subscription returned by the server. +pub struct SubscriptionResponse { + /// The ID of the subscription. + sub_id: SubscriptionId<'static>, + // The receiver is used to receive notifications from the server and shouldn't be exposed to the user + // from the middleware. + #[doc(hidden)] + stream: SubscriptionReceiver, } /// Represents a method call from the server. @@ -682,7 +695,7 @@ impl MethodCall { } /// Represents a response from the server which can be a method call, notification or batch. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct MethodResponse { extensions: Extensions, inner: MethodResponseKind, @@ -694,13 +707,18 @@ impl MethodResponse { Self { inner: MethodResponseKind::MethodCall(MethodCall { json, id }), extensions } } + /// Create a new subscription response. + pub fn subscription(sub_id: SubscriptionId<'static>, stream: SubscriptionReceiver, extensions: Extensions) -> Self { + Self { inner: MethodResponseKind::Subscription(SubscriptionResponse { sub_id, stream }), extensions } + } + /// Create a new notification response. pub fn notification(extensions: Extensions) -> Self { Self { inner: MethodResponseKind::Notification, extensions } } /// Create a new batch response. - pub fn batch(json: Vec>, extensions: Extensions) -> Self { + pub fn batch(json: Vec, ErrorObjectOwned>>, extensions: Extensions) -> Self { Self { inner: MethodResponseKind::Batch(json), extensions } } @@ -710,25 +728,34 @@ impl MethodResponse { MethodResponseKind::MethodCall(call) => Ok(call.json), MethodResponseKind::Notification => Ok(RawValue::NULL.to_owned()), MethodResponseKind::Batch(json) => serde_json::value::to_raw_value(&json), + MethodResponseKind::Subscription(s) => serde_json::value::to_raw_value(&s.sub_id), } } /// Get the method call if this response is a method call. - pub fn as_method_call(&self) -> Option<&MethodCall> { - match &self.inner { + pub fn into_method_call(self) -> Option { + match self.inner { MethodResponseKind::MethodCall(call) => Some(call), _ => None, } } /// Get the batch if this response is a batch. - pub fn as_batch(&self) -> Option<&[Box]> { - match &self.inner { + pub fn into_batch(self) -> Option, ErrorObjectOwned>>> { + match self.inner { MethodResponseKind::Batch(batch) => Some(batch), _ => None, } } + /// Get the subscription if this response is a subscription. + pub fn into_subscription(self) -> Option<(SubscriptionId<'static>, SubscriptionReceiver)> { + match self.inner { + MethodResponseKind::Subscription(s) => Some((s.sub_id, s.stream)), + _ => None, + } + } + /// Returns whether this response is a method call. pub fn is_method_call(&self) -> bool { matches!(self.inner, MethodResponseKind::MethodCall(_)) @@ -744,6 +771,11 @@ impl MethodResponse { matches!(self.inner, MethodResponseKind::Batch(_)) } + /// Returns whether this response is a subscription. + pub fn is_subscription(&self) -> bool { + matches!(self.inner, MethodResponseKind::Subscription { .. }) + } + /// Returns a reference to the associated extensions. pub fn extensions(&self) -> &Extensions { &self.extensions diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 7606feaa27..46ed96526a 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -23,6 +23,14 @@ pub type ResponseBoxFuture<'a, R, E> = BoxFuture<'a, Result>; /// Type alias for a batch of JSON-RPC calls and notifications. pub type Batch<'a> = Vec>; +#[derive(Debug, Clone)] +/// A marker type to indicate that the request is a subscription for the [`RpcServiceT::call`] method. +pub struct IsSubscription { + pub sub_id: Id<'static>, + pub unsub_id: Id<'static>, + pub unsub_method: String, +} + /// A batch entry specific for the [`RpcServiceT::batch`] method to support both /// method calls and notifications. #[derive(Debug, Clone, Serialize)] diff --git a/examples/examples/core_client.rs b/examples/examples/core_client.rs index c7483b46be..b12f5f4c60 100644 --- a/examples/examples/core_client.rs +++ b/examples/examples/core_client.rs @@ -42,7 +42,7 @@ async fn main() -> anyhow::Result<()> { let uri = Url::parse(&format!("ws://{}", addr))?; let (tx, rx) = WsTransportClientBuilder::default().build(uri).await?; - let client: Client = ClientBuilder::default().build_with_tokio(tx, rx); + let client: Client<_> = ClientBuilder::default().build_with_tokio(tx, rx); let response: String = client.request("say_hello", rpc_params![]).await?; tracing::info!("response: {:?}", response); From 79216b543e5c0cb503c389b65b6118750e521eb6 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 25 Mar 2025 17:33:02 +0100 Subject: [PATCH 18/52] remove serialize specific types --- benches/bench.rs | 12 +-- client/http-client/src/client.rs | 2 +- core/src/client/async_client/helpers.rs | 14 ++- core/src/client/async_client/mod.rs | 2 +- core/src/middleware/mod.rs | 2 +- core/src/server/rpc_module.rs | 4 +- .../src/middleware/http/proxy_get_request.rs | 6 +- types/src/lib.rs | 2 +- types/src/request.rs | 92 +++++-------------- 9 files changed, 51 insertions(+), 85 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index 06397cee5d..51c1d212a9 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -9,7 +9,7 @@ use helpers::fixed_client::{ ws_handshake, }; use helpers::{KIB, SUB_METHOD_NAME, UNSUB_METHOD_NAME}; -use jsonrpsee::types::{Id, RequestSer}; +use jsonrpsee::types::{Id, Request}; use pprof::criterion::{Output, PProfProfiler}; use tokio::runtime::Runtime as TokioRuntime; @@ -101,7 +101,7 @@ impl RequestType { } } -fn v2_serialize(req: RequestSer<'_>) -> String { +fn v2_serialize(req: Request<'_>) -> String { serde_json::to_string(&req).unwrap() } @@ -115,7 +115,7 @@ pub fn jsonrpsee_types_v2(crit: &mut Criterion) { b.iter(|| { let params = serde_json::value::RawValue::from_string("[1, 2]".to_string()).unwrap(); - let request = RequestSer::borrowed(&Id::Number(0), &"say_hello", Some(¶ms)); + let request = Request::borrowed("say_hello", Some(¶ms), Id::Number(0)); v2_serialize(request); }) }); @@ -128,7 +128,7 @@ pub fn jsonrpsee_types_v2(crit: &mut Criterion) { builder.insert(1u64).unwrap(); builder.insert(2u32).unwrap(); let params = builder.to_rpc_params().expect("Valid params"); - let request = RequestSer::borrowed(&Id::Number(0), &"say_hello", params.as_deref()); + let request = Request::borrowed(&"say_hello", params.as_deref(), Id::Number(0)); v2_serialize(request); }) }); @@ -138,7 +138,7 @@ pub fn jsonrpsee_types_v2(crit: &mut Criterion) { b.iter(|| { let params = serde_json::value::RawValue::from_string(r#"{"key": 1}"#.to_string()).unwrap(); - let request = RequestSer::borrowed(&Id::Number(0), &"say_hello", Some(¶ms)); + let request = Request::borrowed("say_hello", Some(¶ms), Id::Number(0)); v2_serialize(request); }) }); @@ -150,7 +150,7 @@ pub fn jsonrpsee_types_v2(crit: &mut Criterion) { let mut builder = ObjectParams::new(); builder.insert("key", 1u32).unwrap(); let params = builder.to_rpc_params().expect("Valid params"); - let request = RequestSer::borrowed(&Id::Number(0), &"say_hello", params.as_deref()); + let request = Request::borrowed(&"say_hello", params.as_deref(), Id::Number(0)); v2_serialize(request); }) }); diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index a5baacb665..3d0d39a4b2 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -393,7 +393,7 @@ where let id = self.id_manager.next_request_id(); let params = params.to_rpc_params()?; - let request = Request::new(method.into(), params.as_deref(), id.clone()); + let request = Request::borrowed(method, params.as_deref(), id.clone()); let rp = self.transport.call(request).await?.into_method_call().expect("Transport::call must return a method call"); diff --git a/core/src/client/async_client/helpers.rs b/core/src/client/async_client/helpers.rs index 907e187bb8..959c7a4109 100644 --- a/core/src/client/async_client/helpers.rs +++ b/core/src/client/async_client/helpers.rs @@ -32,14 +32,17 @@ use crate::traits::ToRpcParams; use futures_timer::Delay; use futures_util::future::{self, Either}; +use http::Extensions; use serde_json::value::RawValue; use tokio::sync::oneshot; use jsonrpsee_types::response::SubscriptionError; use jsonrpsee_types::{ - ErrorObject, Id, InvalidRequestId, RequestSer, Response, ResponseSuccess, SubscriptionId, SubscriptionResponse, + ErrorObject, Id, InvalidRequestId, Request, Response, ResponseSuccess, SubscriptionId, SubscriptionResponse, + TwoPointZero, }; use serde_json::Value as JsonValue; +use std::borrow::Cow; use std::ops::Range; #[derive(Debug, Clone)] @@ -257,7 +260,14 @@ pub(crate) fn build_unsubscribe_message( params.insert(sub_id).ok()?; let params = params.to_rpc_params().ok()?; - let raw = serde_json::to_string(&RequestSer::owned(unsub_req_id.clone(), unsub, params)).ok()?; + let raw = serde_json::to_string(&Request { + jsonrpc: TwoPointZero, + id: unsub_req_id.clone(), + method: unsub.into(), + params: params.map(Cow::Owned), + extensions: Extensions::new(), + }) + .ok()?; Some(RequestMessage { raw, id: unsub_req_id, send_back: None }) } diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index 4f46831248..1e868ca496 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -524,7 +524,7 @@ where let id = self.id_manager.next_request_id(); let params = params.to_rpc_params()?; - let request = jsonrpsee_types::Request::new(method.into(), params.as_deref(), id.clone()); + let request = jsonrpsee_types::Request::borrowed(method.into(), params.as_deref(), id.clone()); let fut = self.service.call(request); let rp = self.map_rpc_service_err(fut).await?.into_method_call().expect("Method call response"); diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 46ed96526a..c86ea39538 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -232,7 +232,7 @@ mod tests { use super::{BatchEntry, Notification, Request}; use jsonrpsee_types::Id; - let req = Request::new("say_hello".into(), None, Id::Number(1)); + let req = Request::borrowed("say_hello", None, Id::Number(1)); let batch_entry = BatchEntry::Call(req.clone()); assert_eq!( serde_json::to_string(&batch_entry).unwrap(), diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 39564a354c..097edbbbbc 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -306,7 +306,7 @@ impl Methods { params: Params, ) -> Result { let params = params.to_rpc_params()?; - let req = Request::new(method.into(), params.as_ref().map(|p| p.as_ref()), Id::Number(0)); + let req = Request::borrowed(method, params.as_ref().map(|p| p.as_ref()), Id::Number(0)); tracing::trace!(target: LOG_TARGET, "[Methods::call] Method: {:?}, params: {:?}", method, params); let (rp, _) = self.inner_call(req, 1, mock_subscription_permit()).await; @@ -454,7 +454,7 @@ impl Methods { buf_size: usize, ) -> Result { let params = params.to_rpc_params()?; - let req = Request::new(sub_method.into(), params.as_ref().map(|p| p.as_ref()), Id::Number(0)); + let req = Request::borrowed(sub_method, params.as_ref().map(|p| p.as_ref()), Id::Number(0)); tracing::trace!(target: LOG_TARGET, "[Methods::subscribe] Method: {}, params: {:?}", sub_method, params); diff --git a/server/src/middleware/http/proxy_get_request.rs b/server/src/middleware/http/proxy_get_request.rs index 0ae473ff0a..bd25090fea 100644 --- a/server/src/middleware/http/proxy_get_request.rs +++ b/server/src/middleware/http/proxy_get_request.rs @@ -35,7 +35,7 @@ use hyper::header::{ACCEPT, CONTENT_TYPE}; use hyper::http::HeaderValue; use hyper::{Method, StatusCode, Uri}; use jsonrpsee_core::BoxError; -use jsonrpsee_types::{ErrorCode, ErrorObject, Id, RequestSer}; +use jsonrpsee_types::{ErrorCode, ErrorObject, Id, Request}; use std::collections::HashMap; use std::future::Future; use std::pin::Pin; @@ -160,8 +160,8 @@ where req.headers_mut().insert(ACCEPT, HeaderValue::from_static("application/json")); // Adjust the body to reflect the method call. - let bytes = serde_json::to_vec(&RequestSer::borrowed(&Id::Number(0), method, None)) - .expect("Valid request; qed"); + let bytes = + serde_json::to_vec(&Request::borrowed(method, None, Id::Number(0))).expect("Valid request; qed"); let req = req.map(|_| HttpBody::from(bytes)); // Call the inner service and get a future that resolves to the response. diff --git a/types/src/lib.rs b/types/src/lib.rs index f99ea1096f..f1ee76d182 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -46,7 +46,7 @@ pub mod error; pub use error::{ErrorCode, ErrorObject, ErrorObjectOwned}; pub use http::Extensions; pub use params::{Id, InvalidRequestId, Params, ParamsSequence, SubscriptionId, TwoPointZero}; -pub use request::{InvalidRequest, Notification, NotificationSer, Request, RequestSer}; +pub use request::{InvalidRequest, Notification, Request}; pub use response::{Response, ResponsePayload, SubscriptionPayload, SubscriptionResponse, Success as ResponseSuccess}; /// Deserialize calls, notifications and responses with HTTP extensions. diff --git a/types/src/request.rs b/types/src/request.rs index c4e8d393e7..cd458eac42 100644 --- a/types/src/request.rs +++ b/types/src/request.rs @@ -58,9 +58,26 @@ pub struct Request<'a> { } impl<'a> Request<'a> { - /// Create a new [`Request`]. - pub fn new(method: Cow<'a, str>, params: Option<&'a RawValue>, id: Id<'a>) -> Self { - Self { jsonrpc: TwoPointZero, id, method, params: params.map(Cow::Borrowed), extensions: Extensions::new() } + /// Create new borrowed [`Request`]. + pub fn borrowed(method: &'a str, params: Option<&'a RawValue>, id: Id<'a>) -> Self { + Self { + jsonrpc: TwoPointZero, + id, + method: Cow::Borrowed(method), + params: params.map(Cow::Borrowed), + extensions: Extensions::new(), + } + } + + /// Create new owned [`Request`]. + pub fn owned(method: String, params: Option>, id: Id<'a>) -> Self { + Self { + jsonrpc: TwoPointZero, + id, + method: Cow::Owned(method), + params: params.map(Cow::Owned), + extensions: Extensions::new(), + } } /// Get the ID of the request. @@ -135,66 +152,9 @@ impl<'a, T> Notification<'a, T> { } } -/// Serializable [JSON-RPC object](https://www.jsonrpc.org/specification#request-object). -#[derive(Serialize, Debug, Clone)] -pub struct RequestSer<'a> { - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Request ID - pub id: Id<'a>, - /// Name of the method to be invoked. - // NOTE: as this type only implements serialize `#[serde(borrow)]` is not needed. - pub method: Cow<'a, str>, - /// Parameter values of the request. - #[serde(skip_serializing_if = "Option::is_none")] - pub params: Option>, -} - -impl<'a> RequestSer<'a> { - /// Create a borrowed serializable JSON-RPC method call. - pub fn borrowed(id: &'a Id<'a>, method: &'a impl AsRef, params: Option<&'a RawValue>) -> Self { - Self { - jsonrpc: TwoPointZero, - id: id.clone(), - method: method.as_ref().into(), - params: params.map(Cow::Borrowed), - } - } - - /// Create a owned serializable JSON-RPC method call. - pub fn owned(id: Id<'a>, method: impl Into, params: Option>) -> Self { - Self { jsonrpc: TwoPointZero, id, method: method.into().into(), params: params.map(Cow::Owned) } - } -} - -/// Serializable [JSON-RPC notification object](https://www.jsonrpc.org/specification#request-object). -#[derive(Serialize, Debug, Clone)] -pub struct NotificationSer<'a> { - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Name of the method to be invoked. - // NOTE: as this type only implements serialize `#[serde(borrow)]` is not needed. - pub method: Cow<'a, str>, - /// Parameter values of the request. - #[serde(skip_serializing_if = "Option::is_none")] - pub params: Option>, -} - -impl<'a> NotificationSer<'a> { - /// Create a borrowed serializable JSON-RPC notification. - pub fn borrowed(method: &'a impl AsRef, params: Option<&'a RawValue>) -> Self { - Self { jsonrpc: TwoPointZero, method: method.as_ref().into(), params: params.map(Cow::Borrowed) } - } - - /// Create an owned serializable JSON-RPC notification. - pub fn owned(method: impl Into, params: Option>) -> Self { - Self { jsonrpc: TwoPointZero, method: method.into().into(), params: params.map(Cow::Owned) } - } -} - #[cfg(test)] mod test { - use super::{Cow, Id, InvalidRequest, Notification, Request, RequestSer, TwoPointZero}; + use super::{Id, InvalidRequest, Notification, Request, TwoPointZero}; use serde_json::value::RawValue; fn assert_request<'a>(request: Request<'a>, id: Id<'a>, method: &str, params: Option<&str>) { @@ -292,13 +252,9 @@ mod test { ]; for (ser, id, params, method) in test_vector.iter().cloned() { - let request = serde_json::to_string(&RequestSer { - jsonrpc: TwoPointZero, - method: method.into(), - id: id.unwrap_or(Id::Null), - params: params.map(Cow::Owned), - }) - .unwrap(); + let request = + serde_json::to_string(&Request::borrowed(method.into(), params.as_deref(), id.unwrap_or(Id::Null))) + .unwrap(); assert_eq!(&request, ser); } From 8849af5b1b596f1ae4b2d85a0a573095e2b5f5ab Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 25 Mar 2025 17:33:23 +0100 Subject: [PATCH 19/52] commit missing file --- core/src/client/async_client/rpc_service.rs | 179 ++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 core/src/client/async_client/rpc_service.rs diff --git a/core/src/client/async_client/rpc_service.rs b/core/src/client/async_client/rpc_service.rs new file mode 100644 index 0000000000..bb2673b9ac --- /dev/null +++ b/core/src/client/async_client/rpc_service.rs @@ -0,0 +1,179 @@ +use std::time::Duration; + +use crate::{ + client::{ + BatchMessage, Error as ClientError, FrontToBack, MethodResponse, RequestMessage, SubscriptionMessage, + async_client::helpers::call_with_timeout, + }, + middleware::{Batch, BatchEntry, IsSubscription, Notification, Request, ResponseBoxFuture, RpcServiceT}, +}; + +use futures_timer::Delay; +use futures_util::{ + FutureExt, + future::{self, Either}, +}; +use http::Extensions; +use tokio::sync::{mpsc, oneshot}; + +/// RpcService error. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Client error. + #[error(transparent)] + Client(#[from] crate::client::Error), + #[error("Fetch from backend")] + /// Internal error state when the underlying channel is closed + /// and the error reason needs to be fetched from the backend. + FetchFromBackend, +} + +/// RpcService implementation for the async client. +#[derive(Debug, Clone)] +pub struct RpcService { + tx: mpsc::Sender, + request_timeout: Duration, +} + +impl RpcService { + /// Create a new RpcService instance. + pub fn new(tx: mpsc::Sender, request_timeout: Duration) -> Self { + Self { tx, request_timeout } + } +} + +impl<'a> RpcServiceT<'a> for RpcService { + type Future = ResponseBoxFuture<'a, Self::Response, Self::Error>; + type Response = MethodResponse; + type Error = Error; + + fn call(&self, request: Request<'a>) -> Self::Future { + let tx = self.tx.clone(); + let request_timeout = self.request_timeout; + + async move { + let raw = serde_json::to_string(&request).map_err(client_err)?; + + match request.extensions.get::() { + Some(sub) => { + let (send_back_tx, send_back_rx) = tokio::sync::oneshot::channel(); + + if tx + .clone() + .send(FrontToBack::Subscribe(SubscriptionMessage { + raw, + subscribe_id: sub.sub_id.clone(), + unsubscribe_id: sub.unsub_id.clone(), + unsubscribe_method: sub.unsub_method.clone(), + send_back: send_back_tx, + })) + .await + .is_err() + { + return Err(Error::FetchFromBackend); + } + + let (subscribe_rx, sub_id) = match call_with_timeout(request_timeout, send_back_rx).await { + Ok(Ok(v)) => v, + Ok(Err(err)) => return Err(client_err(err)), + Err(_) => return Err(Error::FetchFromBackend), + }; + + Ok(MethodResponse::subscription(sub_id, subscribe_rx, request.extensions)) + } + None => { + let (send_back_tx, send_back_rx) = oneshot::channel(); + + if tx + .send(FrontToBack::Request(RequestMessage { + raw, + send_back: Some(send_back_tx), + id: request.id.clone().into_owned(), + })) + .await + .is_err() + { + return Err(Error::FetchFromBackend); + } + let rp = match call_with_timeout(request_timeout, send_back_rx).await { + Ok(Ok(v)) => v, + Ok(Err(err)) => return Err(client_err(err)), + Err(_) => return Err(Error::FetchFromBackend), + }; + + Ok(MethodResponse::method_call(rp, request.extensions, request.id.clone().into_owned())) + } + } + } + .boxed() + } + + fn batch(&self, batch: Batch<'a>) -> Self::Future { + let tx = self.tx.clone(); + let request_timeout = self.request_timeout; + + async move { + let (send_back_tx, send_back_rx) = oneshot::channel(); + + let raw = serde_json::to_string(&batch).map_err(client_err)?; + let first = { + match batch.first() { + Some(&BatchEntry::Call(ref r)) => r.id.clone().into_owned().try_parse_inner_as_number().unwrap(), + _ => unreachable!("Only method calls are allowed in batch requests"), + } + }; + let last = { + match batch.last() { + Some(&BatchEntry::Call(ref r)) => r.id.clone().into_owned().try_parse_inner_as_number().unwrap(), + _ => unreachable!("Only method calls are allowed in batch requests"), + } + }; + + if tx + .send(FrontToBack::Batch(BatchMessage { raw, ids: first..last, send_back: send_back_tx })) + .await + .is_err() + { + return Err(Error::FetchFromBackend); + } + + let json = match call_with_timeout(request_timeout, send_back_rx).await { + Ok(Ok(v)) => v, + Ok(Err(err)) => return Err(client_err(err)), + Err(_) => return Err(Error::FetchFromBackend), + }; + + let mut extensions = Extensions::new(); + + for entry in batch.into_iter() { + extensions.extend(entry.into_extensions()); + } + + Ok(MethodResponse::batch(json, extensions)) + } + .boxed() + } + + fn notification(&self, n: Notification<'a>) -> Self::Future { + let tx = self.tx.clone(); + let request_timeout = self.request_timeout; + + async move { + let raw = serde_json::to_string(&n).map_err(client_err)?; + let fut = tx.send(FrontToBack::Notification(raw)); + + tokio::pin!(fut); + + match future::select(fut, Delay::new(request_timeout)).await { + Either::Left((Ok(()), _)) => Ok(MethodResponse::notification(n.extensions)), + Either::Left((Err(_), _)) => todo!(), + Either::Right((_, _)) => Err(client_err(ClientError::RequestTimeout)), + } + } + .boxed() + } +} + +fn client_err(err: impl Into) -> Error { + Error::Client(err.into()) +} From 6a6e3aaffa121d2d2cbb110c5075f914cff3786e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 26 Mar 2025 10:58:20 +0100 Subject: [PATCH 20/52] no serde_json::Value --- core/src/client/async_client/helpers.rs | 7 +- core/src/client/async_client/mod.rs | 8 ++- core/src/client/async_client/rpc_service.rs | 73 ++++++++------------- core/src/client/mod.rs | 16 +++-- 4 files changed, 47 insertions(+), 57 deletions(-) diff --git a/core/src/client/async_client/helpers.rs b/core/src/client/async_client/helpers.rs index 959c7a4109..c8dace5f50 100644 --- a/core/src/client/async_client/helpers.rs +++ b/core/src/client/async_client/helpers.rs @@ -26,7 +26,9 @@ use crate::client::async_client::manager::{RequestManager, RequestStatus}; use crate::client::async_client::{LOG_TARGET, Notification}; -use crate::client::{Error, RequestMessage, TransportSenderT, TrySubscriptionSendError, subscription_channel}; +use crate::client::{ + Error, JsonValue, RequestMessage, TransportSenderT, TrySubscriptionSendError, subscription_channel, +}; use crate::params::ArrayParams; use crate::traits::ToRpcParams; @@ -41,7 +43,6 @@ use jsonrpsee_types::{ ErrorObject, Id, InvalidRequestId, Request, Response, ResponseSuccess, SubscriptionId, SubscriptionResponse, TwoPointZero, }; -use serde_json::Value as JsonValue; use std::borrow::Cow; use std::ops::Range; @@ -97,7 +98,7 @@ pub(crate) fn process_batch_response( /// `None` is returned. pub(crate) fn process_subscription_response( manager: &mut RequestManager, - response: SubscriptionResponse, + response: SubscriptionResponse>, ) -> Option> { let sub_id = response.params.subscription.into_owned(); let request_id = match manager.get_request_id_by_subscription_id(&sub_id) { diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index 1e868ca496..d726350bbd 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -69,7 +69,7 @@ use tracing::instrument; use self::utils::{InactivityCheck, IntervalStream}; use super::{FrontToBack, IdKind, MethodResponse, RequestIdManager, generate_batch_id_range, subscription_channel}; -pub(crate) type Notification<'a> = jsonrpsee_types::Notification<'a, Option>; +pub(crate) type Notification<'a> = jsonrpsee_types::Notification<'a, Option>>; const LOG_TARGET: &str = "jsonrpsee-client"; const NOT_POISONED: &str = "Not poisoned; qed"; @@ -524,7 +524,7 @@ where let id = self.id_manager.next_request_id(); let params = params.to_rpc_params()?; - let request = jsonrpsee_types::Request::borrowed(method.into(), params.as_deref(), id.clone()); + let request = Request::borrowed(method.into(), params.as_deref(), id.clone()); let fut = self.service.call(request); let rp = self.map_rpc_service_err(fut).await?.into_method_call().expect("Method call response"); @@ -544,7 +544,7 @@ where .into_iter() .zip(id_range.clone()) .map(|((method, params), id)| { - BatchEntry::Call(jsonrpsee_types::Request { + BatchEntry::Call(Request { jsonrpc: TwoPointZero, id: self.id_manager.as_id_kind().into_id(id), method: method.into(), @@ -677,6 +677,8 @@ fn handle_backend_messages( let first_non_whitespace = raw.iter().find(|byte| !byte.is_ascii_whitespace()); let mut messages = Vec::new(); + tracing::trace!(target: LOG_TARGET, "Received JSON: {}", serde_json::from_slice::<&JsonRawValue>(&raw).map_or("", |v| v.get())); + match first_non_whitespace { Some(b'{') => { // Single response to a request. diff --git a/core/src/client/async_client/rpc_service.rs b/core/src/client/async_client/rpc_service.rs index bb2673b9ac..51aad3d8ea 100644 --- a/core/src/client/async_client/rpc_service.rs +++ b/core/src/client/async_client/rpc_service.rs @@ -21,13 +21,25 @@ use tokio::sync::{mpsc, oneshot}; pub enum Error { /// Client error. #[error(transparent)] - Client(#[from] crate::client::Error), + Client(#[from] ClientError), #[error("Fetch from backend")] /// Internal error state when the underlying channel is closed /// and the error reason needs to be fetched from the backend. FetchFromBackend, } +impl From> for Error { + fn from(_: mpsc::error::SendError) -> Self { + Error::FetchFromBackend + } +} + +impl From for Error { + fn from(_: oneshot::error::RecvError) -> Self { + Error::FetchFromBackend + } +} + /// RpcService implementation for the async client. #[derive(Debug, Clone)] pub struct RpcService { @@ -58,8 +70,7 @@ impl<'a> RpcServiceT<'a> for RpcService { Some(sub) => { let (send_back_tx, send_back_rx) = tokio::sync::oneshot::channel(); - if tx - .clone() + tx.clone() .send(FrontToBack::Subscribe(SubscriptionMessage { raw, subscribe_id: sub.sub_id.clone(), @@ -67,39 +78,22 @@ impl<'a> RpcServiceT<'a> for RpcService { unsubscribe_method: sub.unsub_method.clone(), send_back: send_back_tx, })) - .await - .is_err() - { - return Err(Error::FetchFromBackend); - } - - let (subscribe_rx, sub_id) = match call_with_timeout(request_timeout, send_back_rx).await { - Ok(Ok(v)) => v, - Ok(Err(err)) => return Err(client_err(err)), - Err(_) => return Err(Error::FetchFromBackend), - }; + .await?; + + let (subscribe_rx, sub_id) = call_with_timeout(request_timeout, send_back_rx).await??; Ok(MethodResponse::subscription(sub_id, subscribe_rx, request.extensions)) } None => { let (send_back_tx, send_back_rx) = oneshot::channel(); - if tx - .send(FrontToBack::Request(RequestMessage { - raw, - send_back: Some(send_back_tx), - id: request.id.clone().into_owned(), - })) - .await - .is_err() - { - return Err(Error::FetchFromBackend); - } - let rp = match call_with_timeout(request_timeout, send_back_rx).await { - Ok(Ok(v)) => v, - Ok(Err(err)) => return Err(client_err(err)), - Err(_) => return Err(Error::FetchFromBackend), - }; + tx.send(FrontToBack::Request(RequestMessage { + raw, + send_back: Some(send_back_tx), + id: request.id.clone().into_owned(), + })) + .await?; + let rp = call_with_timeout(request_timeout, send_back_rx).await??; Ok(MethodResponse::method_call(rp, request.extensions, request.id.clone().into_owned())) } @@ -129,19 +123,8 @@ impl<'a> RpcServiceT<'a> for RpcService { } }; - if tx - .send(FrontToBack::Batch(BatchMessage { raw, ids: first..last, send_back: send_back_tx })) - .await - .is_err() - { - return Err(Error::FetchFromBackend); - } - - let json = match call_with_timeout(request_timeout, send_back_rx).await { - Ok(Ok(v)) => v, - Ok(Err(err)) => return Err(client_err(err)), - Err(_) => return Err(Error::FetchFromBackend), - }; + tx.send(FrontToBack::Batch(BatchMessage { raw, ids: first..last + 1, send_back: send_back_tx })).await?; + let json = call_with_timeout(request_timeout, send_back_rx).await??; let mut extensions = Extensions::new(); @@ -166,8 +149,8 @@ impl<'a> RpcServiceT<'a> for RpcService { match future::select(fut, Delay::new(request_timeout)).await { Either::Left((Ok(()), _)) => Ok(MethodResponse::notification(n.extensions)), - Either::Left((Err(_), _)) => todo!(), - Either::Right((_, _)) => Err(client_err(ClientError::RequestTimeout)), + Either::Left((Err(e), _)) => Err(e.into()), + Either::Right((_, _)) => Err(ClientError::RequestTimeout.into()), } } .boxed() diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 7e86cc689b..5ed5f1c016 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -52,7 +52,6 @@ use http::Extensions; use jsonrpsee_types::{ErrorObject, ErrorObjectOwned, Id, SubscriptionId}; use serde::Deserialize; use serde::de::DeserializeOwned; -use serde_json::Value as JsonValue; use serde_json::value::RawValue; use tokio::sync::{mpsc, oneshot}; @@ -60,6 +59,11 @@ use tokio::sync::{mpsc, oneshot}; #[derive(Debug, Clone)] pub(crate) struct SubscriptionLagged(Arc>); +type JsonValue = Box; + +//pub(crate) type JsonValue<'a> = std::borrow::Cow<'a, RawValue>; +//pub(crate) type OwnedJsonValue = std::borrow::Cow<'static, RawValue>; + impl SubscriptionLagged { /// Create a new [`SubscriptionLagged`]. pub(crate) fn new() -> Self { @@ -425,7 +429,7 @@ where type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll> { let res = match futures_util::ready!(self.rx.poll_next_unpin(cx)) { - Some(v) => Some(serde_json::from_value::(v).map_err(Into::into)), + Some(v) => Some(serde_json::from_str::(v.get()).map_err(Into::into)), None => { self.is_closed = true; None @@ -607,17 +611,17 @@ enum TrySubscriptionSendError { #[error("The subscription is closed")] Closed, #[error("A subscription message was dropped")] - TooSlow(JsonValue), + TooSlow(Box), } #[derive(Debug)] pub(crate) struct SubscriptionSender { - inner: mpsc::Sender, + inner: mpsc::Sender>, lagged: SubscriptionLagged, } impl SubscriptionSender { - fn send(&self, msg: JsonValue) -> Result<(), TrySubscriptionSendError> { + fn send(&self, msg: Box) -> Result<(), TrySubscriptionSendError> { match self.inner.try_send(msg) { Ok(_) => Ok(()), Err(TrySendError::Closed(_)) => Err(TrySubscriptionSendError::Closed), @@ -631,7 +635,7 @@ impl SubscriptionSender { #[derive(Debug)] pub(crate) struct SubscriptionReceiver { - inner: mpsc::Receiver, + inner: mpsc::Receiver>, lagged: SubscriptionLagged, } From 02fb9175b34735d42bb4cc2aa3473566f5a17e00 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 27 Mar 2025 16:09:40 +0100 Subject: [PATCH 21/52] more nit fixing --- client/http-client/src/client.rs | 6 +- client/http-client/src/rpc_service.rs | 2 +- core/src/client/async_client/mod.rs | 45 ++++---- core/src/client/async_client/rpc_service.rs | 38 +++---- core/src/client/mod.rs | 7 +- core/src/middleware/layer/either.rs | 8 +- core/src/middleware/layer/logger.rs | 11 +- core/src/middleware/mod.rs | 111 ++++++++++++++++++-- examples/examples/http.rs | 8 +- server/src/middleware/rpc.rs | 14 +-- server/src/server.rs | 4 +- 11 files changed, 175 insertions(+), 79 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 3d0d39a4b2..0398672983 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -40,7 +40,7 @@ use jsonrpsee_core::client::{ BatchResponse, ClientT, Error, IdKind, MethodResponse, RequestIdManager, Subscription, SubscriptionClientT, generate_batch_id_range, }; -use jsonrpsee_core::middleware::{BatchEntry, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee_core::middleware::{Batch, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::params::BatchRequestBuilder; use jsonrpsee_core::traits::ToRpcParams; use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; @@ -414,7 +414,7 @@ where let id = self.id_manager.next_request_id(); let id_range = generate_batch_id_range(id, batch.len() as u64)?; - let mut batch_request = Vec::with_capacity(batch.len()); + let mut batch_request = Batch::new(); for ((method, params), id) in batch.into_iter().zip(id_range.clone()) { let id = self.id_manager.as_id_kind().into_id(id); let req = Request { @@ -424,7 +424,7 @@ where id, extensions: Extensions::new(), }; - batch_request.push(BatchEntry::Call(req)); + batch_request.push(req)?; } let json_rps = diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index e6ed0b9134..738bce08a9 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -62,7 +62,7 @@ where let mut extensions = Extensions::new(); - for call in batch { + for call in batch.into_iter() { extensions.extend(call.into_extensions()); } diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index d726350bbd..c186a37604 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -41,7 +41,7 @@ use crate::client::{ SubscriptionKind, TransportReceiverT, TransportSenderT, }; use crate::error::RegisterMethodError; -use crate::middleware::{BatchEntry, IsSubscription, Request, RpcServiceBuilder, RpcServiceT}; +use crate::middleware::{Batch, IsSubscription, Request, RpcServiceBuilder, RpcServiceT}; use crate::params::{BatchRequestBuilder, EmptyBatchRequest}; use crate::traits::ToRpcParams; use std::borrow::Cow as StdCow; @@ -470,6 +470,7 @@ impl Client { Ok(r) => Ok(r), Err(RpcServiceError::Client(e)) => Err(e), Err(RpcServiceError::FetchFromBackend) => Err(self.on_disconnect().await), + Err(RpcServiceError::Internal) => Err(Error::Custom("Internal error".to_string())), } } @@ -528,7 +529,7 @@ where let fut = self.service.call(request); let rp = self.map_rpc_service_err(fut).await?.into_method_call().expect("Method call response"); - serde_json::from_str(rp.json.get()).map_err(Error::ParseError) + rp.decode().map_err(Into::into) } #[instrument(name = "batch", skip(self, batch), level = "trace")] @@ -540,21 +541,19 @@ where let id = self.id_manager.next_request_id(); let id_range = generate_batch_id_range(id, batch.len() as u64)?; - let batch: Vec<_> = batch - .into_iter() - .zip(id_range.clone()) - .map(|((method, params), id)| { - BatchEntry::Call(Request { - jsonrpc: TwoPointZero, - id: self.id_manager.as_id_kind().into_id(id), - method: method.into(), - params: params.map(StdCow::Owned), - extensions: Extensions::new(), - }) - }) - .collect(); - - let fut = self.service.batch(batch); + let mut b = Batch::new(); + + for ((method, params), id) in batch.into_iter().zip(id_range.clone()) { + b.push(Request { + jsonrpc: TwoPointZero, + id: self.id_manager.as_id_kind().into_id(id), + method: method.into(), + params: params.map(StdCow::Owned), + extensions: Extensions::new(), + })?; + } + + let fut = self.service.batch(b); let json_values = self.map_rpc_service_err(fut).await?.into_batch().expect("Batch response"); let mut responses = Vec::with_capacity(json_values.len()); @@ -602,20 +601,16 @@ where return Err(RegisterMethodError::SubscriptionNameConflict(unsubscribe_method.to_owned()).into()); } - let id_sub = self.id_manager.next_request_id(); - let id_unsub = self.id_manager.next_request_id(); + let req_id_sub = self.id_manager.next_request_id(); + let req_id_unsub = self.id_manager.next_request_id(); let params = params.to_rpc_params()?; let mut ext = Extensions::new(); - ext.insert(IsSubscription { - sub_id: id_sub.clone(), - unsub_id: id_unsub.clone(), - unsub_method: unsubscribe_method.to_owned(), - }); + ext.insert(IsSubscription::new(req_id_sub.clone(), req_id_unsub, unsubscribe_method.to_owned())); let req = Request { jsonrpc: TwoPointZero, - id: id_sub, + id: req_id_sub, method: subscribe_method.into(), params: params.map(StdCow::Owned), extensions: ext, diff --git a/core/src/client/async_client/rpc_service.rs b/core/src/client/async_client/rpc_service.rs index 51aad3d8ea..364eaf6b8c 100644 --- a/core/src/client/async_client/rpc_service.rs +++ b/core/src/client/async_client/rpc_service.rs @@ -3,9 +3,9 @@ use std::time::Duration; use crate::{ client::{ BatchMessage, Error as ClientError, FrontToBack, MethodResponse, RequestMessage, SubscriptionMessage, - async_client::helpers::call_with_timeout, + SubscriptionResponse, async_client::helpers::call_with_timeout, }, - middleware::{Batch, BatchEntry, IsSubscription, Notification, Request, ResponseBoxFuture, RpcServiceT}, + middleware::{Batch, IsSubscription, Notification, Request, ResponseBoxFuture, RpcServiceT}, }; use futures_timer::Delay; @@ -14,6 +14,7 @@ use futures_util::{ future::{self, Either}, }; use http::Extensions; +use jsonrpsee_types::InvalidRequestId; use tokio::sync::{mpsc, oneshot}; /// RpcService error. @@ -26,6 +27,9 @@ pub enum Error { /// Internal error state when the underlying channel is closed /// and the error reason needs to be fetched from the backend. FetchFromBackend, + /// Internal error + #[error("Internal error")] + Internal, } impl From> for Error { @@ -49,6 +53,8 @@ pub struct RpcService { impl RpcService { /// Create a new RpcService instance. + #[doc(hidden)] + #[allow(private_interfaces)] pub fn new(tx: mpsc::Sender, request_timeout: Duration) -> Self { Self { tx, request_timeout } } @@ -73,16 +79,19 @@ impl<'a> RpcServiceT<'a> for RpcService { tx.clone() .send(FrontToBack::Subscribe(SubscriptionMessage { raw, - subscribe_id: sub.sub_id.clone(), - unsubscribe_id: sub.unsub_id.clone(), - unsubscribe_method: sub.unsub_method.clone(), + subscribe_id: sub.sub_req_id(), + unsubscribe_id: sub.unsub_req_id(), + unsubscribe_method: sub.unsubscribe_method().to_owned(), send_back: send_back_tx, })) .await?; let (subscribe_rx, sub_id) = call_with_timeout(request_timeout, send_back_rx).await??; - Ok(MethodResponse::subscription(sub_id, subscribe_rx, request.extensions)) + Ok(MethodResponse::subscription( + SubscriptionResponse { sub_id, stream: subscribe_rx }, + request.extensions, + )) } None => { let (send_back_tx, send_back_rx) = oneshot::channel(); @@ -110,20 +119,11 @@ impl<'a> RpcServiceT<'a> for RpcService { let (send_back_tx, send_back_rx) = oneshot::channel(); let raw = serde_json::to_string(&batch).map_err(client_err)?; - let first = { - match batch.first() { - Some(&BatchEntry::Call(ref r)) => r.id.clone().into_owned().try_parse_inner_as_number().unwrap(), - _ => unreachable!("Only method calls are allowed in batch requests"), - } - }; - let last = { - match batch.last() { - Some(&BatchEntry::Call(ref r)) => r.id.clone().into_owned().try_parse_inner_as_number().unwrap(), - _ => unreachable!("Only method calls are allowed in batch requests"), - } - }; + let id_range = batch.id_range().ok_or(ClientError::InvalidRequestId(InvalidRequestId::Invalid( + "Batch request id range missing".to_owned(), + )))?; - tx.send(FrontToBack::Batch(BatchMessage { raw, ids: first..last + 1, send_back: send_back_tx })).await?; + tx.send(FrontToBack::Batch(BatchMessage { raw, ids: id_range, send_back: send_back_tx })).await?; let json = call_with_timeout(request_timeout, send_back_rx).await??; let mut extensions = Extensions::new(); diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 5ed5f1c016..107e884ef3 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -663,14 +663,13 @@ enum MethodResponseKind { Batch(Vec, ErrorObjectOwned>>), } -#[derive(Debug)] /// Represents an active subscription returned by the server. +#[derive(Debug)] pub struct SubscriptionResponse { /// The ID of the subscription. sub_id: SubscriptionId<'static>, // The receiver is used to receive notifications from the server and shouldn't be exposed to the user // from the middleware. - #[doc(hidden)] stream: SubscriptionReceiver, } @@ -712,8 +711,8 @@ impl MethodResponse { } /// Create a new subscription response. - pub fn subscription(sub_id: SubscriptionId<'static>, stream: SubscriptionReceiver, extensions: Extensions) -> Self { - Self { inner: MethodResponseKind::Subscription(SubscriptionResponse { sub_id, stream }), extensions } + pub fn subscription(sub: SubscriptionResponse, extensions: Extensions) -> Self { + Self { inner: MethodResponseKind::Subscription(sub), extensions } } /// Create a new notification response. diff --git a/core/src/middleware/layer/either.rs b/core/src/middleware/layer/either.rs index a9beb2a6db..feb55565d7 100644 --- a/core/src/middleware/layer/either.rs +++ b/core/src/middleware/layer/either.rs @@ -31,7 +31,7 @@ //! work to implement tower::Layer for //! external types such as future::Either. -use crate::middleware::{BatchEntry, Notification, RpcServiceT}; +use crate::middleware::{Batch, Notification, RpcServiceT}; use jsonrpsee_types::Request; /// [`tower::util::Either`] but @@ -75,10 +75,10 @@ where } } - fn batch(&self, requests: Vec>) -> Self::Future { + fn batch(&self, batch: Batch<'a>) -> Self::Future { match self { - Either::Left(service) => futures_util::future::Either::Left(service.batch(requests)), - Either::Right(service) => futures_util::future::Either::Right(service.batch(requests)), + Either::Left(service) => futures_util::future::Either::Left(service.batch(batch)), + Either::Right(service) => futures_util::future::Either::Right(service.batch(batch)), } } diff --git a/core/src/middleware/layer/logger.rs b/core/src/middleware/layer/logger.rs index cedab26bf3..411f3d5980 100644 --- a/core/src/middleware/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -32,7 +32,7 @@ use std::{ }; use crate::{ - middleware::{BatchEntry, Notification, RpcServiceT}, + middleware::{Batch, BatchEntry, Notification, RpcServiceT}, tracing::server::{rx_log_from_json, tx_log_from_str}, }; @@ -60,6 +60,9 @@ impl tower::Layer for RpcLoggerLayer { } } + + + /// A middleware that logs each RPC call and response. #[derive(Debug)] pub struct RpcLogger { @@ -85,10 +88,10 @@ where } #[tracing::instrument(name = "batch", skip_all, fields(method = "batch"), level = "trace")] - fn batch(&self, requests: Vec>) -> Self::Future { - //rx_log_from_json(&requests, self.max); + fn batch(&self, batch: Batch<'a>) -> Self::Future { + rx_log_from_json(&batch, self.max); - ResponseFuture::new(self.service.batch(requests), self.max).in_current_span() + ResponseFuture::new(self.service.batch(batch), self.max).in_current_span() } #[tracing::instrument(name = "notification", skip_all, fields(method = &*n.method), level = "trace")] diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index c86ea39538..1e444c58b5 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -3,7 +3,7 @@ pub mod layer; use futures_util::future::{BoxFuture, Either, Future}; -use jsonrpsee_types::Id; +use jsonrpsee_types::{Id, InvalidRequestId}; use pin_project::pin_project; use serde::Serialize; use serde_json::value::RawValue; @@ -20,15 +20,114 @@ pub type Notification<'a> = jsonrpsee_types::Notification<'a, Option. pub type ResponseBoxFuture<'a, R, E> = BoxFuture<'a, Result>; -/// Type alias for a batch of JSON-RPC calls and notifications. -pub type Batch<'a> = Vec>; + +/// A batch of JSON-RPC calls and notifications. +#[derive(Clone, Debug)] +pub struct Batch<'a> { + inner: Vec>, + id_range: Option>, +} + +impl Serialize for Batch<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + // Serialize the batch entries directly without the Batch wrapper. + serde::Serialize::serialize(&self.inner, serializer) + } +} + +impl<'a> std::fmt::Display for Batch<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = serde_json::to_string(&self.inner).expect("Batch serialization failed"); + f.write_str(&s) + } +} + +impl<'a> Batch<'a> { + /// Create a new empty batch. + pub fn new() -> Self { + Self { inner: Vec::new(), id_range: None } + } + + /// Create a new batch from a list of batch entries without an id range. + pub fn from_batch_entries(inner: Vec>) -> Self { + Self { inner, id_range: None } + } + + /// Insert a new batch entry into the batch. + /// + /// Fails if the request id is not a number or the id range overflows. + pub fn push(&mut self, req: Request<'a>) -> Result<(), InvalidRequestId> { + let id = req.id().try_parse_inner_as_number()?; + + match self.id_range { + Some(ref mut range) => { + debug_assert!(id + 1 > range.end); + range.end = + id.checked_add(1).ok_or_else(|| InvalidRequestId::Invalid("Id range overflow".to_string()))?; + } + None => { + self.id_range = Some(id..id); + } + } + self.inner.push(BatchEntry::Call(req)); + + Ok(()) + } + + /// Mutable iterator over the batch entries. + pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, BatchEntry<'a>> { + self.inner.iter_mut() + } + + /// Immutable iterator over the batch entries. + pub fn iter(&self) -> std::slice::Iter<'_, BatchEntry<'a>> { + self.inner.iter() + } + + /// Consume the batch and return batch entries. + pub fn into_iter(self) -> std::vec::IntoIter> { + self.inner.into_iter() + } + + /// Get the id range of the batch. + /// + /// This is only available if the batch has been constructed using `Batch::push`. + pub fn id_range(&self) -> Option> { + self.id_range.clone() + } +} #[derive(Debug, Clone)] /// A marker type to indicate that the request is a subscription for the [`RpcServiceT::call`] method. pub struct IsSubscription { - pub sub_id: Id<'static>, - pub unsub_id: Id<'static>, - pub unsub_method: String, + sub_id: Id<'static>, + unsub_id: Id<'static>, + unsub_method: String, +} + +impl IsSubscription { + /// Create a new [`IsSubscription`] instance. + pub fn new(sub_id: Id<'static>, unsub_id: Id<'static>, unsub_method: String) -> Self { + Self { sub_id, unsub_id, unsub_method } + } + + /// Get the request id of the subscription calls. + pub fn sub_req_id(&self) -> Id<'static> { + self.sub_id.clone() + } + + /// Get the request id of the unsubscription call. + pub fn unsub_req_id(&self) -> Id<'static> { + self.unsub_id.clone() + } + + /// Get the unsubscription method name. + pub fn unsubscribe_method(&self) -> &str { + &self.unsub_method + } } /// A batch entry specific for the [`RpcServiceT::batch`] method to support both diff --git a/examples/examples/http.rs b/examples/examples/http.rs index 19d3b2cd4c..9c48dfb76e 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -29,7 +29,7 @@ use std::time::Duration; use hyper::body::Bytes; use jsonrpsee::core::client::ClientT; -use jsonrpsee::core::middleware::{BatchEntry, Notification, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, Server}; @@ -53,9 +53,9 @@ where self.0.call(req) } - fn batch(&self, reqs: Vec>) -> Self::Future { - println!("logger layer : {:?}", reqs); - self.0.batch(reqs) + fn batch(&self, batch: Batch<'a>) -> Self::Future { + println!("logger layer : {:?}", batch); + self.0.batch(batch) } fn notification(&self, n: Notification<'a>) -> Self::Future { diff --git a/server/src/middleware/rpc.rs b/server/src/middleware/rpc.rs index 4d9970571b..41ffd26b25 100644 --- a/server/src/middleware/rpc.rs +++ b/server/src/middleware/rpc.rs @@ -150,20 +150,20 @@ impl<'a> RpcServiceT<'a> for RpcService { } } - fn batch(&self, reqs: Vec>) -> Self::Future { - let mut batch = BatchResponseBuilder::new_with_limit(self.max_response_body_size); + fn batch(&self, batch: Batch<'a>) -> Self::Future { + let mut batch_rp = BatchResponseBuilder::new_with_limit(self.max_response_body_size); let service = self.clone(); async move { let mut got_notification = false; - for batch_entry in reqs { + for batch_entry in batch.into_iter() { match batch_entry { BatchEntry::Call(req) => { let rp = match service.call(req).await { Ok(rp) => rp, Err(e) => match e {}, }; - if let Err(err) = batch.append(rp) { + if let Err(err) = batch_rp.append(rp) { return Ok(err); } } @@ -176,7 +176,7 @@ impl<'a> RpcServiceT<'a> for RpcService { } BatchEntry::InvalidRequest(id) => { let rp = MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidRequest)); - if let Err(err) = batch.append(rp) { + if let Err(err) = batch_rp.append(rp) { return Ok(err); } } @@ -184,12 +184,12 @@ impl<'a> RpcServiceT<'a> for RpcService { } // If the batch is empty and we got a notification, we return an empty response. - if batch.is_empty() && got_notification { + if batch_rp.is_empty() && got_notification { Ok(MethodResponse::notification()) } // An empty batch is regarded as an invalid request here. else { - Ok(MethodResponse::from_batch(batch.finish())) + Ok(MethodResponse::from_batch(batch_rp.finish())) } } .boxed() diff --git a/server/src/server.rs b/server/src/server.rs index 529e8dc7da..069c6e2714 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -45,7 +45,7 @@ use futures_util::io::{BufReader, BufWriter}; use hyper::body::Bytes; use hyper_util::rt::{TokioExecutor, TokioIo}; use jsonrpsee_core::id_providers::RandomIntegerIdProvider; -use jsonrpsee_core::middleware::{BatchEntry, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee_core::middleware::{Batch, BatchEntry, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::server::helpers::prepare_error; use jsonrpsee_core::server::{BoundedSubscriptions, ConnectionId, MethodResponse, MethodSink, Methods}; use jsonrpsee_core::traits::IdProvider; @@ -1303,7 +1303,7 @@ where } } - match rpc_service.batch(batch).await { + match rpc_service.batch(Batch::from_batch_entries(batch)).await { Ok(rp) => rp, Err(e) => MethodResponse::error(Id::Null, rpc_middleware_error(e)), } From 09b4e0b3db16b4283b1bde74005a2fdbb6e8cf5b Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 27 Mar 2025 18:19:58 +0100 Subject: [PATCH 22/52] more cleanup --- client/http-client/src/client.rs | 19 ++++----------- client/http-client/src/transport.rs | 26 --------------------- client/ws-client/src/lib.rs | 15 +----------- core/src/client/async_client/mod.rs | 19 +-------------- core/src/client/async_client/rpc_service.rs | 9 +++---- core/src/client/mod.rs | 5 +++- core/src/middleware/layer/logger.rs | 5 +--- 7 files changed, 14 insertions(+), 84 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 0398672983..9166e3f41b 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -85,7 +85,6 @@ pub struct HttpClientBuilder, rpc_middleware: RpcServiceBuilder, @@ -195,14 +194,6 @@ impl HttpClientBuilder Self { - self.max_log_length = max; - self - } - /// Set a custom header passed to the server with every request (default is none). /// /// The caller is responsible for checking that the headers do not conflict or are duplicated. @@ -226,7 +217,6 @@ impl HttpClientBuilder HttpClientBuilder { #[cfg(feature = "tls")] certificate_store: CertificateStore::Native, id_kind: IdKind::Number, - max_log_length: 4096, headers: HeaderMap::new(), service_builder: tower::ServiceBuilder::new(), rpc_middleware: RpcServiceBuilder::default(), @@ -347,6 +334,8 @@ pub struct HttpClient { id_manager: Arc, /// Concurrent requests limit guard. request_guard: Option>, + /// Request timeout. + request_timeout: Duration, } impl HttpClient { @@ -357,7 +346,7 @@ impl HttpClient { /// Returns configured request timeout. pub fn request_timeout(&self) -> Duration { - todo!(); + self.request_timeout } } diff --git a/client/http-client/src/transport.rs b/client/http-client/src/transport.rs index f4bc9996ae..d364bc8022 100644 --- a/client/http-client/src/transport.rs +++ b/client/http-client/src/transport.rs @@ -13,7 +13,6 @@ use hyper_util::client::legacy::Client; use hyper_util::client::legacy::connect::HttpConnector; use hyper_util::rt::TokioExecutor; use jsonrpsee_core::BoxError; -use jsonrpsee_core::tracing::client::{rx_log_from_bytes, tx_log_from_str}; use jsonrpsee_core::{ TEN_MB_SIZE_BYTES, http_helpers::{self, HttpError}, @@ -94,10 +93,6 @@ pub struct HttpTransportClientBuilder { pub(crate) max_request_size: u32, /// Configurable max response body size pub(crate) max_response_size: u32, - /// Max length for logging for requests and responses - /// - /// Logs bigger than this limit will be truncated. - pub(crate) max_log_length: u32, /// Custom headers to pass with every request. pub(crate) headers: HeaderMap, /// Service builder @@ -122,7 +117,6 @@ impl HttpTransportClientBuilder { certificate_store: CertificateStore::Native, max_request_size: TEN_MB_SIZE_BYTES, max_response_size: TEN_MB_SIZE_BYTES, - max_log_length: 1024, headers: HeaderMap::new(), service_builder: tower::ServiceBuilder::new(), tcp_no_delay: true, @@ -167,21 +161,12 @@ impl HttpTransportClientBuilder { self } - /// Max length for logging for requests and responses in number characters. - /// - /// Logs bigger than this limit will be truncated. - pub fn set_max_logging_length(mut self, max: u32) -> Self { - self.max_log_length = max; - self - } - /// Configure a tower service. pub fn set_service(self, service: tower::ServiceBuilder) -> HttpTransportClientBuilder { HttpTransportClientBuilder { #[cfg(feature = "tls")] certificate_store: self.certificate_store, headers: self.headers, - max_log_length: self.max_log_length, max_request_size: self.max_request_size, max_response_size: self.max_response_size, service_builder: service, @@ -204,7 +189,6 @@ impl HttpTransportClientBuilder { certificate_store, max_request_size, max_response_size, - max_log_length, headers, service_builder, tcp_no_delay, @@ -292,7 +276,6 @@ impl HttpTransportClientBuilder { client: service_builder.service(client), max_request_size, max_response_size, - max_log_length, headers: cached_headers, request_timeout, }) @@ -310,10 +293,6 @@ pub struct HttpTransportClient { max_request_size: u32, /// Configurable max response body size max_response_size: u32, - /// Max length for logging for requests and responses - /// - /// Logs bigger than this limit will be truncated. - max_log_length: u32, /// Custom headers to pass with every request. headers: HeaderMap, /// Request timeout @@ -349,17 +328,12 @@ where /// Send serialized message and wait until all bytes from the HTTP message body have been read. pub(crate) async fn send_and_read_body(&self, body: String) -> Result, Error> { - tx_log_from_str(&body, self.max_log_length); - let response = tokio::time::timeout(self.request_timeout, self.inner_send(body)).await.map_err(|_| Error::Timeout)??; let (parts, body) = response.into_parts(); - let (body, _is_single) = http_helpers::read_body(&parts.headers, body, self.max_response_size).await?; - rx_log_from_bytes(&body, self.max_log_length); - Ok(body) } diff --git a/client/ws-client/src/lib.rs b/client/ws-client/src/lib.rs index 0ad4b3df6d..2dd9b3777d 100644 --- a/client/ws-client/src/lib.rs +++ b/client/ws-client/src/lib.rs @@ -84,7 +84,7 @@ use jsonrpsee_client_transport::ws::CertificateStore; /// /// ``` #[derive(Clone, Debug)] -pub struct WsClientBuilder { +pub struct WsClientBuilder { #[cfg(feature = "tls")] certificate_store: CertificateStore, max_request_size: u32, @@ -97,7 +97,6 @@ pub struct WsClientBuilder { max_buffer_capacity_per_subscription: usize, max_redirections: usize, id_kind: IdKind, - max_log_length: u32, tcp_no_delay: bool, service_builder: RpcServiceBuilder, } @@ -117,7 +116,6 @@ impl Default for WsClientBuilder { max_buffer_capacity_per_subscription: 1024, max_redirections: 5, id_kind: IdKind::Number, - max_log_length: 4096, tcp_no_delay: true, service_builder: RpcServiceBuilder::default(), } @@ -264,14 +262,6 @@ impl WsClientBuilder { self } - /// Set maximum length for logging calls and responses. - /// - /// Logs bigger than this limit will be truncated. - pub fn set_max_logging_length(mut self, max: u32) -> Self { - self.max_log_length = max; - self - } - /// See documentation [`ClientBuilder::set_tcp_no_delay`] (default is true). pub fn set_tcp_no_delay(mut self, no_delay: bool) -> Self { self.tcp_no_delay = no_delay; @@ -292,7 +282,6 @@ impl WsClientBuilder { max_buffer_capacity_per_subscription: self.max_buffer_capacity_per_subscription, max_redirections: self.max_redirections, id_kind: self.id_kind, - max_log_length: self.max_log_length, tcp_no_delay: self.tcp_no_delay, service_builder, } @@ -315,7 +304,6 @@ impl WsClientBuilder { ping_config, max_buffer_capacity_per_subscription, id_kind, - max_log_length, tcp_no_delay, service_builder, .. @@ -326,7 +314,6 @@ impl WsClientBuilder { .request_timeout(request_timeout) .max_concurrent_requests(max_concurrent_requests) .id_format(id_kind) - .set_max_logging_length(max_log_length) .set_tcp_no_delay(tcp_no_delay) .set_rpc_middleware(service_builder); diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index c186a37604..1bc8cff98d 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -186,7 +186,6 @@ pub struct ClientBuilder { max_concurrent_requests: usize, max_buffer_capacity_per_subscription: usize, id_kind: IdKind, - max_log_length: u32, ping_config: Option, tcp_no_delay: bool, service_builder: RpcServiceBuilder, @@ -199,10 +198,9 @@ impl Default for ClientBuilder { max_concurrent_requests: 256, max_buffer_capacity_per_subscription: 1024, id_kind: IdKind::Number, - max_log_length: 4096, ping_config: None, tcp_no_delay: true, - service_builder: RpcServiceBuilder::new(), + service_builder: RpcServiceBuilder::default(), } } } @@ -248,14 +246,6 @@ impl ClientBuilder { self } - /// Set maximum length for logging calls and responses. - /// - /// Logs bigger than this limit will be truncated. - pub fn set_max_logging_length(mut self, max: u32) -> Self { - self.max_log_length = max; - self - } - /// Enable WebSocket ping/pong on the client. /// /// This only works if the transport supports WebSocket pings. @@ -291,7 +281,6 @@ impl ClientBuilder { max_concurrent_requests: self.max_concurrent_requests, max_buffer_capacity_per_subscription: self.max_buffer_capacity_per_subscription, id_kind: self.id_kind, - max_log_length: self.max_log_length, ping_config: self.ping_config, tcp_no_delay: self.tcp_no_delay, service_builder, @@ -368,7 +357,6 @@ impl ClientBuilder { request_timeout: self.request_timeout, error: ErrorFromBack::new(to_back, disconnect_reason), id_manager: RequestIdManager::new(self.id_kind), - max_log_length: self.max_log_length, on_exit: Some(client_dropped_tx), } } @@ -442,10 +430,6 @@ pub struct Client { request_timeout: Duration, /// Request ID manager. id_manager: RequestIdManager, - /// Max length for logging for requests and responses. - /// - /// Entries bigger than this limit will be truncated. - max_log_length: u32, /// When the client is dropped a message is sent to the background thread. on_exit: Option>, service: L, @@ -470,7 +454,6 @@ impl Client { Ok(r) => Ok(r), Err(RpcServiceError::Client(e)) => Err(e), Err(RpcServiceError::FetchFromBackend) => Err(self.on_disconnect().await), - Err(RpcServiceError::Internal) => Err(Error::Custom("Internal error".to_string())), } } diff --git a/core/src/client/async_client/rpc_service.rs b/core/src/client/async_client/rpc_service.rs index 364eaf6b8c..b79da68e2b 100644 --- a/core/src/client/async_client/rpc_service.rs +++ b/core/src/client/async_client/rpc_service.rs @@ -27,9 +27,6 @@ pub enum Error { /// Internal error state when the underlying channel is closed /// and the error reason needs to be fetched from the backend. FetchFromBackend, - /// Internal error - #[error("Internal error")] - Internal, } impl From> for Error { @@ -52,10 +49,10 @@ pub struct RpcService { } impl RpcService { - /// Create a new RpcService instance. - #[doc(hidden)] + // This is a private interface but we need to expose it for the async client + // to be able to create the service. #[allow(private_interfaces)] - pub fn new(tx: mpsc::Sender, request_timeout: Duration) -> Self { + pub(crate) fn new(tx: mpsc::Sender, request_timeout: Duration) -> Self { Self { tx, request_timeout } } } diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 107e884ef3..7e890ffe4c 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -736,6 +736,7 @@ impl MethodResponse { } /// Get the method call if this response is a method call. + #[doc(hidden)] pub fn into_method_call(self) -> Option { match self.inner { MethodResponseKind::MethodCall(call) => Some(call), @@ -744,6 +745,7 @@ impl MethodResponse { } /// Get the batch if this response is a batch. + #[doc(hidden)] pub fn into_batch(self) -> Option, ErrorObjectOwned>>> { match self.inner { MethodResponseKind::Batch(batch) => Some(batch), @@ -752,7 +754,8 @@ impl MethodResponse { } /// Get the subscription if this response is a subscription. - pub fn into_subscription(self) -> Option<(SubscriptionId<'static>, SubscriptionReceiver)> { + #[doc(hidden)] + fn into_subscription(self) -> Option<(SubscriptionId<'static>, SubscriptionReceiver)> { match self.inner { MethodResponseKind::Subscription(s) => Some((s.sub_id, s.stream)), _ => None, diff --git a/core/src/middleware/layer/logger.rs b/core/src/middleware/layer/logger.rs index 411f3d5980..b4d637d3b0 100644 --- a/core/src/middleware/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -32,7 +32,7 @@ use std::{ }; use crate::{ - middleware::{Batch, BatchEntry, Notification, RpcServiceT}, + middleware::{Batch, Notification, RpcServiceT}, tracing::server::{rx_log_from_json, tx_log_from_str}, }; @@ -60,9 +60,6 @@ impl tower::Layer for RpcLoggerLayer { } } - - - /// A middleware that logs each RPC call and response. #[derive(Debug)] pub struct RpcLogger { From 763cad6a33d60256d8c0af484729ac13e7985a3c Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 28 Mar 2025 17:34:42 +0100 Subject: [PATCH 23/52] refactor method response client --- client/http-client/src/client.rs | 26 ++------ client/http-client/src/rpc_service.rs | 14 ++--- client/ws-client/src/lib.rs | 4 +- core/src/client/async_client/helpers.rs | 39 +++++++----- core/src/client/async_client/manager.rs | 14 ++--- core/src/client/async_client/mod.rs | 22 +++---- core/src/client/async_client/rpc_service.rs | 16 +++-- core/src/client/mod.rs | 67 +++++++++------------ core/src/middleware/layer/logger.rs | 18 +++--- core/src/middleware/mod.rs | 6 ++ core/src/server/method_response.rs | 6 ++ core/src/tracing.rs | 2 +- examples/examples/http.rs | 2 +- examples/examples/ws.rs | 3 +- 14 files changed, 119 insertions(+), 120 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 9166e3f41b..de78bd2265 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -31,7 +31,6 @@ use std::time::Duration; use crate::rpc_service::RpcService; use crate::transport::{self, Error as TransportError, HttpBackend, HttpTransportClientBuilder}; -use crate::types::Response; use crate::{HttpRequest, HttpResponse}; use async_trait::async_trait; use hyper::body::Bytes; @@ -43,13 +42,12 @@ use jsonrpsee_core::client::{ use jsonrpsee_core::middleware::{Batch, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::params::BatchRequestBuilder; use jsonrpsee_core::traits::ToRpcParams; -use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; +use jsonrpsee_core::{BoxError, TEN_MB_SIZE_BYTES}; use jsonrpsee_types::{ErrorObject, InvalidRequestId, Notification, Request, ResponseSuccess, TwoPointZero}; use serde::de::DeserializeOwned; use tokio::sync::Semaphore; use tower::layer::util::Identity; use tower::{Layer, Service}; -use tracing::instrument; #[cfg(feature = "tls")] use crate::{CertificateStore, CustomCertStore}; @@ -355,7 +353,6 @@ impl ClientT for HttpClient where for<'a> S: RpcServiceT<'a, Error = Error, Response = MethodResponse> + Send + Sync, { - #[instrument(name = "notification", skip(self, params), level = "trace")] async fn notification(&self, method: &str, params: Params) -> Result<(), Error> where Params: ToRpcParams + Send, @@ -369,7 +366,6 @@ where Ok(()) } - #[instrument(name = "method_call", skip(self, params), level = "trace")] async fn request(&self, method: &str, params: Params) -> Result where R: DeserializeOwned, @@ -385,12 +381,12 @@ where let request = Request::borrowed(method, params.as_deref(), id.clone()); let rp = self.transport.call(request).await?.into_method_call().expect("Transport::call must return a method call"); + let rp = ResponseSuccess::try_from(rp)?; - let result = rp.decode()?; - if rp.id() == &id { Ok(result) } else { Err(InvalidRequestId::NotPendingRequest(rp.id().to_string()).into()) } + let result = serde_json::from_str(rp.result.get()).map_err(Error::ParseError)?; + if rp.id == id { Ok(result) } else { Err(InvalidRequestId::NotPendingRequest(rp.id.to_string()).into()) } } - #[instrument(name = "batch", skip(self, batch), level = "trace")] async fn batch_request<'a, R>(&self, batch: BatchRequestBuilder<'a>) -> Result, Error> where R: DeserializeOwned + fmt::Debug + 'a, @@ -428,17 +424,7 @@ where batch_response.push(Err(ErrorObject::borrowed(0, "", None))); } - for json_rp in json_rps.iter() { - let json = match json_rp { - Ok(json) => json, - Err(e) => { - failed += 1; - batch_response.push(Err(e.clone())); - continue; - } - }; - - let rp: Response<&JsonRawValue> = serde_json::from_str(json.get()).map_err(Error::ParseError)?; + for rp in json_rps.into_iter() { let id = rp.id.try_parse_inner_as_number()?; let res = match ResponseSuccess::try_from(rp) { @@ -476,7 +462,6 @@ where { /// Send a subscription request to the server. Not implemented for HTTP; will always return /// [`Error::HttpNotImplemented`]. - #[instrument(name = "subscription", fields(method = _subscribe_method), skip(self, _params, _subscribe_method, _unsubscribe_method), level = "trace")] async fn subscribe<'a, N, Params>( &self, _subscribe_method: &'a str, @@ -491,7 +476,6 @@ where } /// Subscribe to a specific method. Not implemented for HTTP; will always return [`Error::HttpNotImplemented`]. - #[instrument(name = "subscribe_method", fields(method = _method), skip(self, _method), level = "trace")] async fn subscribe_to_method<'a, N>(&self, _method: &'a str) -> Result, Error> where N: DeserializeOwned, diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index 738bce08a9..29ba1d144c 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -7,7 +7,7 @@ use jsonrpsee_core::{ client::{Error, MethodResponse}, middleware::{Batch, Notification, Request, RpcServiceT}, }; -use jsonrpsee_types::{Response, ResponseSuccess}; +use jsonrpsee_types::Response; use tower::Service; use crate::{ @@ -46,8 +46,7 @@ where let raw = serde_json::to_string(&request)?; let bytes = service.send_and_read_body(raw).await.map_err(|e| Error::Transport(e.into()))?; let json_rp: Response> = serde_json::from_slice(&bytes)?; - let success = ResponseSuccess::try_from(json_rp)?; - Ok(MethodResponse::method_call(success.result, request.extensions, success.id.into_owned())) + Ok(MethodResponse::method_call(json_rp.into_owned(), request.extensions)) } .boxed() } @@ -58,7 +57,10 @@ where async move { let raw = serde_json::to_string(&batch)?; let bytes = service.send_and_read_body(raw).await.map_err(|e| Error::Transport(e.into()))?; - let json: Vec> = serde_json::from_slice(&bytes)?; + let rp: Vec<_> = serde_json::from_slice::>>>(&bytes)? + .into_iter() + .map(|r| r.into_owned()) + .collect(); let mut extensions = Extensions::new(); @@ -66,9 +68,7 @@ where extensions.extend(call.into_extensions()); } - let json = json.into_iter().map(Ok).collect(); - - Ok(MethodResponse::batch(json, extensions)) + Ok(MethodResponse::batch(rp, extensions)) } .boxed() } diff --git a/client/ws-client/src/lib.rs b/client/ws-client/src/lib.rs index 2dd9b3777d..5a935d30a7 100644 --- a/client/ws-client/src/lib.rs +++ b/client/ws-client/src/lib.rs @@ -122,12 +122,14 @@ impl Default for WsClientBuilder { } } -impl WsClientBuilder { +impl WsClientBuilder { /// Create a new WebSocket client builder. pub fn new() -> WsClientBuilder { WsClientBuilder::default() } +} +impl WsClientBuilder { /// Force to use a custom certificate store. /// /// # Optional diff --git a/core/src/client/async_client/helpers.rs b/core/src/client/async_client/helpers.rs index c8dace5f50..8fc9a1b0eb 100644 --- a/core/src/client/async_client/helpers.rs +++ b/core/src/client/async_client/helpers.rs @@ -46,18 +46,12 @@ use jsonrpsee_types::{ use std::borrow::Cow; use std::ops::Range; -#[derive(Debug, Clone)] -pub(crate) struct InnerBatchResponse { - pub(crate) id: u64, - pub(crate) result: Result, ErrorObject<'static>>, -} - /// Attempts to process a batch response. /// /// On success the result is sent to the frontend. pub(crate) fn process_batch_response( manager: &mut RequestManager, - rps: Vec, + rps: Vec>>, range: Range, ) -> Result<(), InvalidRequestId> { let mut responses = Vec::with_capacity(rps.len()); @@ -74,15 +68,16 @@ pub(crate) fn process_batch_response( for _ in range { let err_obj = ErrorObject::borrowed(0, "", None); - responses.push(Err(err_obj)); + responses.push(Response::new(jsonrpsee_types::ResponsePayload::error(err_obj), Id::Null)); } for rp in rps { + let id = rp.id.try_parse_inner_as_number()?; let maybe_elem = - rp.id.checked_sub(start_idx).and_then(|p| p.try_into().ok()).and_then(|p: usize| responses.get_mut(p)); + id.checked_sub(start_idx).and_then(|p| p.try_into().ok()).and_then(|p: usize| responses.get_mut(p)); if let Some(elem) = maybe_elem { - *elem = rp.result; + *elem = rp; } else { return Err(InvalidRequestId::NotPendingRequest(rp.id.to_string())); } @@ -182,7 +177,6 @@ pub(crate) fn process_single_response( max_capacity_per_subscription: usize, ) -> Result, InvalidRequestId> { let response_id = response.id.clone().into_owned(); - let result = ResponseSuccess::try_from(response).map(|s| s.result).map_err(Error::Call); match manager.request_status(&response_id) { RequestStatus::PendingMethodCall => { @@ -192,7 +186,7 @@ pub(crate) fn process_single_response( None => return Err(InvalidRequestId::NotPendingRequest(response_id.to_string())), }; - let _ = send_back_oneshot.send(result); + let _ = send_back_oneshot.send(Ok(response.into_owned())); Ok(None) } RequestStatus::PendingSubscription => { @@ -200,10 +194,12 @@ pub(crate) fn process_single_response( .complete_pending_subscription(response_id.clone()) .ok_or(InvalidRequestId::NotPendingRequest(response_id.to_string()))?; + let result = ResponseSuccess::try_from(response); + let json = match result { - Ok(s) => s, + Ok(s) => s.result, Err(e) => { - let _ = send_back_oneshot.send(Err(e)); + let _ = send_back_oneshot.send(Err(Error::Call(e))); return Ok(None); } }; @@ -274,11 +270,24 @@ pub(crate) fn build_unsubscribe_message( /// Wait for a stream to complete within the given timeout. pub(crate) async fn call_with_timeout( + timeout: std::time::Duration, + rx: oneshot::Receiver>, +) -> Result, oneshot::error::RecvError> { + match future::select(rx, Delay::new(timeout)).await { + Either::Left((Ok(Ok(r)), _)) => Ok(Ok(r)), + Either::Left((Ok(Err(e)), _)) => Ok(Err(Error::InvalidRequestId(e))), + Either::Left((Err(e), _)) => Err(e), + Either::Right((_, _)) => Ok(Err(Error::RequestTimeout)), + } +} + +/// Wait for a stream to complete within the given timeout. +pub(crate) async fn call_with_timeout_sub( timeout: std::time::Duration, rx: oneshot::Receiver>, ) -> Result, oneshot::error::RecvError> { match future::select(rx, Delay::new(timeout)).await { - Either::Left((res, _)) => res, + Either::Left((r, _)) => r, Either::Right((_, _)) => Ok(Err(Error::RequestTimeout)), } } diff --git a/core/src/client/async_client/manager.rs b/core/src/client/async_client/manager.rs index 67e8f44631..5c816cc904 100644 --- a/core/src/client/async_client/manager.rs +++ b/core/src/client/async_client/manager.rs @@ -38,12 +38,11 @@ use std::{ }; use crate::{ - client::{BatchEntry, Error, SubscriptionReceiver, SubscriptionSender}, + client::{Error, Response, ResponseResult, SubscriptionReceiver, SubscriptionSender}, error::RegisterMethodError, }; -use jsonrpsee_types::{Id, SubscriptionId}; +use jsonrpsee_types::{Id, InvalidRequestId, SubscriptionId}; use rustc_hash::FxHashMap; -use serde_json::value::RawValue; use tokio::sync::oneshot; #[derive(Debug)] @@ -66,8 +65,8 @@ pub(crate) enum RequestStatus { Invalid, } -type PendingCallOneshot = Option, Error>>>; -type PendingBatchOneshot = oneshot::Sender>>, Error>>; +type PendingCallOneshot = Option>; +type PendingBatchOneshot = oneshot::Sender, InvalidRequestId>>; type PendingSubscriptionOneshot = oneshot::Sender), Error>>; type SubscriptionSink = SubscriptionSender; type UnsubscribeMethod = String; @@ -337,14 +336,13 @@ impl RequestManager { mod tests { use crate::client::subscription_channel; - use super::{Error, RequestManager}; + use super::RequestManager; use jsonrpsee_types::{Id, SubscriptionId}; - use serde_json::value::RawValue; use tokio::sync::oneshot; #[test] fn insert_remove_pending_request_works() { - let (request_tx, _) = oneshot::channel::, Error>>(); + let (request_tx, _) = oneshot::channel(); let mut manager = RequestManager::new(); assert!(manager.insert_pending_call(Id::Number(0), Some(request_tx)).is_ok()); diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index 1bc8cff98d..591f966481 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -34,7 +34,7 @@ mod utils; pub use rpc_service::{Error as RpcServiceError, RpcService}; use crate::JsonRawValue; -use crate::client::async_client::helpers::{InnerBatchResponse, process_subscription_close_response}; +use crate::client::async_client::helpers::process_subscription_close_response; use crate::client::async_client::utils::MaybePendingFutures; use crate::client::{ BatchResponse, ClientT, Error, ReceivedMessage, RegisterNotificationMessage, Subscription, SubscriptionClientT, @@ -52,7 +52,7 @@ use futures_util::Stream; use futures_util::future::{self, Either}; use futures_util::stream::StreamExt; use helpers::{ - build_unsubscribe_message, call_with_timeout, process_batch_response, process_notification, + build_unsubscribe_message, call_with_timeout_sub, process_batch_response, process_notification, process_single_response, process_subscription_response, stop_subscription, }; use http::Extensions; @@ -64,7 +64,6 @@ use serde::de::DeserializeOwned; use std::sync::Arc; use tokio::sync::{mpsc, oneshot}; use tower::layer::util::Identity; -use tracing::instrument; use self::utils::{InactivityCheck, IntervalStream}; use super::{FrontToBack, IdKind, MethodResponse, RequestIdManager, generate_batch_id_range, subscription_channel}; @@ -486,7 +485,6 @@ impl ClientT for Client where for<'a> L: RpcServiceT<'a, Error = RpcServiceError, Response = MethodResponse> + Send + Sync, { - #[instrument(name = "notification", skip(self, params), level = "trace")] async fn notification(&self, method: &str, params: Params) -> Result<(), Error> where Params: ToRpcParams + Send, @@ -499,7 +497,6 @@ where Ok(()) } - #[instrument(name = "method_call", skip(self, params), level = "trace")] async fn request(&self, method: &str, params: Params) -> Result where R: DeserializeOwned, @@ -511,11 +508,11 @@ where let request = Request::borrowed(method.into(), params.as_deref(), id.clone()); let fut = self.service.call(request); let rp = self.map_rpc_service_err(fut).await?.into_method_call().expect("Method call response"); + let success = ResponseSuccess::try_from(rp)?; - rp.decode().map_err(Into::into) + serde_json::from_str(success.result.get()).map_err(Into::into) } - #[instrument(name = "batch", skip(self, batch), level = "trace")] async fn batch_request<'a, R>(&self, batch: BatchRequestBuilder<'a>) -> Result, Error> where R: DeserializeOwned, @@ -544,9 +541,9 @@ where let mut failed_calls = 0; for json_val in json_values { - match json_val { + match ResponseSuccess::try_from(json_val) { Ok(val) => { - let result: R = serde_json::from_str(val.get()).map_err(Error::ParseError)?; + let result: R = serde_json::from_str(val.result.get()).map_err(Error::ParseError)?; responses.push(Ok(result)); successful_calls += 1; } @@ -569,7 +566,6 @@ where /// /// The `subscribe_method` and `params` are used to ask for the subscription towards the /// server. The `unsubscribe_method` is used to close the subscription. - #[instrument(name = "subscription", fields(method = subscribe_method), skip(self, params, subscribe_method, unsubscribe_method), level = "trace")] async fn subscribe<'a, Notif, Params>( &self, subscribe_method: &'a str, @@ -607,7 +603,6 @@ where } /// Subscribe to a specific method. - #[instrument(name = "subscribe_method", skip(self), level = "trace")] async fn subscribe_to_method<'a, N>(&self, method: &'a str) -> Result, Error> where N: DeserializeOwned, @@ -626,7 +621,7 @@ where return Err(self.on_disconnect().await); } - let res = call_with_timeout(self.request_timeout, send_back_rx).await; + let res = call_with_timeout_sub(self.request_timeout, send_back_rx).await; let (rx, method) = match res { Ok(Ok(val)) => val, @@ -696,8 +691,7 @@ fn handle_backend_messages( for r in raw_responses { if let Ok(response) = serde_json::from_str::>(r.get()) { let id = response.id.try_parse_inner_as_number()?; - let result = ResponseSuccess::try_from(response).map(|s| s.result); - batch.push(InnerBatchResponse { id, result }); + batch.push(response.into_owned()); let r = range.get_or_insert(id..id); diff --git a/core/src/client/async_client/rpc_service.rs b/core/src/client/async_client/rpc_service.rs index b79da68e2b..efd99f5a63 100644 --- a/core/src/client/async_client/rpc_service.rs +++ b/core/src/client/async_client/rpc_service.rs @@ -14,9 +14,11 @@ use futures_util::{ future::{self, Either}, }; use http::Extensions; -use jsonrpsee_types::InvalidRequestId; +use jsonrpsee_types::{InvalidRequestId, Response, ResponsePayload}; use tokio::sync::{mpsc, oneshot}; +use super::helpers::call_with_timeout_sub; + /// RpcService error. #[derive(Debug, thiserror::Error)] pub enum Error { @@ -83,10 +85,16 @@ impl<'a> RpcServiceT<'a> for RpcService { })) .await?; - let (subscribe_rx, sub_id) = call_with_timeout(request_timeout, send_back_rx).await??; + let (subscribe_rx, sub_id) = call_with_timeout_sub(request_timeout, send_back_rx).await??; + + let s = serde_json::value::to_raw_value(&sub_id).map_err(client_err)?; Ok(MethodResponse::subscription( - SubscriptionResponse { sub_id, stream: subscribe_rx }, + SubscriptionResponse { + rp: Response::new(ResponsePayload::success(s), request.id.clone().into_owned()), + sub_id, + stream: subscribe_rx, + }, request.extensions, )) } @@ -101,7 +109,7 @@ impl<'a> RpcServiceT<'a> for RpcService { .await?; let rp = call_with_timeout(request_timeout, send_back_rx).await??; - Ok(MethodResponse::method_call(rp, request.extensions, request.id.clone().into_owned())) + Ok(MethodResponse::method_call(rp, request.extensions)) } } } diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 7e890ffe4c..0e11779b7c 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -49,8 +49,7 @@ use async_trait::async_trait; use core::marker::PhantomData; use futures_util::stream::{Stream, StreamExt}; use http::Extensions; -use jsonrpsee_types::{ErrorObject, ErrorObjectOwned, Id, SubscriptionId}; -use serde::Deserialize; +use jsonrpsee_types::{ErrorObject, Id, InvalidRequestId, SubscriptionId}; use serde::de::DeserializeOwned; use serde_json::value::RawValue; use tokio::sync::{mpsc, oneshot}; @@ -61,6 +60,9 @@ pub(crate) struct SubscriptionLagged(Arc>); type JsonValue = Box; +pub(crate) type Response = jsonrpsee_types::Response<'static, Box>; +pub(crate) type ResponseResult = Result; + //pub(crate) type JsonValue<'a> = std::borrow::Cow<'a, RawValue>; //pub(crate) type OwnedJsonValue = std::borrow::Cow<'static, RawValue>; @@ -340,7 +342,7 @@ struct BatchMessage { /// Request IDs. ids: Range, /// One-shot channel over which we send back the result of this request. - send_back: oneshot::Sender>>, Error>>, + send_back: oneshot::Sender, InvalidRequestId>>, } /// Request message. @@ -351,7 +353,7 @@ struct RequestMessage { /// Request ID. id: Id<'static>, /// One-shot channel over which we send back the result of this request. - send_back: Option, Error>>>, + send_back: Option>, } /// Subscription message. @@ -657,10 +659,10 @@ fn subscription_channel(max_buf_size: usize) -> (SubscriptionSender, Subscriptio #[derive(Debug)] enum MethodResponseKind { - MethodCall(MethodCall), + MethodCall(Response), Subscription(SubscriptionResponse), Notification, - Batch(Vec, ErrorObjectOwned>>), + Batch(Vec), } /// Represents an active subscription returned by the server. @@ -671,30 +673,8 @@ pub struct SubscriptionResponse { // The receiver is used to receive notifications from the server and shouldn't be exposed to the user // from the middleware. stream: SubscriptionReceiver, -} - -/// Represents a method call from the server. -#[derive(Debug, Clone)] -pub struct MethodCall { - json: Box, - id: Id<'static>, -} - -impl MethodCall { - /// Consume the method call and return the raw JSON value. - pub fn into_json(self) -> Box { - self.json - } - - /// Get the ID of the method call. - pub fn id(&self) -> &Id<'static> { - &self.id - } - - /// Decode the JSON value into the desired type. - pub fn decode<'a, T: Deserialize<'a>>(&'a self) -> Result { - serde_json::from_str(self.json.get()) - } + /// Response + rp: Response, } /// Represents a response from the server which can be a method call, notification or batch. @@ -706,8 +686,8 @@ pub struct MethodResponse { impl MethodResponse { /// Create a new method response. - pub fn method_call(json: Box, extensions: Extensions, id: Id<'static>) -> Self { - Self { inner: MethodResponseKind::MethodCall(MethodCall { json, id }), extensions } + pub fn method_call(rp: Response, extensions: Extensions) -> Self { + Self { inner: MethodResponseKind::MethodCall(rp), extensions } } /// Create a new subscription response. @@ -721,23 +701,23 @@ impl MethodResponse { } /// Create a new batch response. - pub fn batch(json: Vec, ErrorObjectOwned>>, extensions: Extensions) -> Self { + pub fn batch(json: Vec, extensions: Extensions) -> Self { Self { inner: MethodResponseKind::Batch(json), extensions } } - /// Consume the response and return the raw JSON value. - pub fn into_json(self) -> Result, serde_json::Error> { + /// Get the raw JSON value. + pub fn to_json(&self) -> Result, serde_json::Error> { match self.inner { - MethodResponseKind::MethodCall(call) => Ok(call.json), + MethodResponseKind::MethodCall(ref call) => serde_json::value::to_raw_value(&call), MethodResponseKind::Notification => Ok(RawValue::NULL.to_owned()), - MethodResponseKind::Batch(json) => serde_json::value::to_raw_value(&json), - MethodResponseKind::Subscription(s) => serde_json::value::to_raw_value(&s.sub_id), + MethodResponseKind::Batch(ref json) => serde_json::value::to_raw_value(&json), + MethodResponseKind::Subscription(ref s) => serde_json::value::to_raw_value(&s.rp), } } /// Get the method call if this response is a method call. #[doc(hidden)] - pub fn into_method_call(self) -> Option { + pub fn into_method_call(self) -> Option { match self.inner { MethodResponseKind::MethodCall(call) => Some(call), _ => None, @@ -746,7 +726,7 @@ impl MethodResponse { /// Get the batch if this response is a batch. #[doc(hidden)] - pub fn into_batch(self) -> Option, ErrorObjectOwned>>> { + pub fn into_batch(self) -> Option> { match self.inner { MethodResponseKind::Batch(batch) => Some(batch), _ => None, @@ -792,3 +772,10 @@ impl MethodResponse { &mut self.extensions } } + +impl std::fmt::Display for MethodResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let json = self.to_json().expect("JSON serialization failed"); + f.write_str(json.get()) + } +} diff --git a/core/src/middleware/layer/logger.rs b/core/src/middleware/layer/logger.rs index b4d637d3b0..d473001567 100644 --- a/core/src/middleware/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -33,7 +33,7 @@ use std::{ use crate::{ middleware::{Batch, Notification, RpcServiceT}, - tracing::server::{rx_log_from_json, tx_log_from_str}, + tracing::truncate_at_char_boundary, }; use futures_util::Future; @@ -71,7 +71,7 @@ impl<'a, S> RpcServiceT<'a> for RpcLogger where S: RpcServiceT<'a>, S::Error: std::fmt::Debug + Send, - S::Response: AsRef, + S::Response: std::fmt::Display, { type Future = Instrumented>; type Error = S::Error; @@ -79,21 +79,24 @@ where #[tracing::instrument(name = "method_call", skip_all, fields(method = request.method_name()), level = "trace")] fn call(&self, request: Request<'a>) -> Self::Future { - rx_log_from_json(&request, self.max); + let json = serde_json::to_string(&request).unwrap_or_default(); + tracing::trace!(target: "jsonrpsee", "request = {}", truncate_at_char_boundary(&json, self.max as usize)); ResponseFuture::new(self.service.call(request), self.max).in_current_span() } #[tracing::instrument(name = "batch", skip_all, fields(method = "batch"), level = "trace")] fn batch(&self, batch: Batch<'a>) -> Self::Future { - rx_log_from_json(&batch, self.max); + let json = serde_json::to_string(&batch).unwrap_or_default(); + tracing::trace!(target: "jsonrpsee", "batch request = {}", truncate_at_char_boundary(&json, self.max as usize)); ResponseFuture::new(self.service.batch(batch), self.max).in_current_span() } #[tracing::instrument(name = "notification", skip_all, fields(method = &*n.method), level = "trace")] fn notification(&self, n: Notification<'a>) -> Self::Future { - rx_log_from_json(&n, self.max); + let json = serde_json::to_string(&n).unwrap_or_default(); + tracing::trace!(target: "jsonrpsee", "notification = {}", truncate_at_char_boundary(&json, self.max as usize)); ResponseFuture::new(self.service.notification(n), self.max).in_current_span() } @@ -123,7 +126,7 @@ impl std::fmt::Debug for ResponseFuture { impl Future for ResponseFuture where F: Future>, - R: AsRef, + R: std::fmt::Display, E: std::fmt::Debug, { type Output = F::Output; @@ -134,7 +137,8 @@ where match fut.poll(cx) { Poll::Ready(Ok(rp)) => { - tx_log_from_str(&rp, max); + let json = rp.to_string(); + tracing::trace!(target: "jsonrpsee", "response = {}", truncate_at_char_boundary(&json, max as usize)); Poll::Ready(Ok(rp)) } Poll::Ready(Err(e)) => Poll::Ready(Err(e)), diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 1e444c58b5..ca6f534afd 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -235,6 +235,12 @@ pub trait RpcServiceT<'a> { fn notification(&self, n: Notification<'a>) -> Self::Future; } +/// I +pub trait IntoJson { + /// Convert the type into a JSON value. + fn into_json(self) -> Result, serde_json::Error>; +} + /// Similar to [`tower::ServiceBuilder`] but doesn't /// support any tower middleware implementations. #[derive(Debug, Clone)] diff --git a/core/src/server/method_response.rs b/core/src/server/method_response.rs index 2dcd49ba65..350d37c8c7 100644 --- a/core/src/server/method_response.rs +++ b/core/src/server/method_response.rs @@ -75,6 +75,12 @@ impl AsRef for MethodResponse { } } +impl std::fmt::Display for MethodResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.result) + } +} + impl MethodResponse { /// Returns whether the call was successful. pub fn is_success(&self) -> bool { diff --git a/core/src/tracing.rs b/core/src/tracing.rs index 4967f1a8e6..e9a775b800 100644 --- a/core/src/tracing.rs +++ b/core/src/tracing.rs @@ -99,7 +99,7 @@ pub mod server { } /// Find the next char boundary to truncate at. -fn truncate_at_char_boundary(s: &str, max: usize) -> &str { +pub fn truncate_at_char_boundary(s: &str, max: usize) -> &str { if s.len() < max { return s; } diff --git a/examples/examples/http.rs b/examples/examples/http.rs index 9c48dfb76e..87bb39e0bc 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -86,7 +86,7 @@ async fn main() -> anyhow::Result<()> { .on_response(DefaultOnResponse::new().include_headers(true).latency_unit(LatencyUnit::Micros)), ); - let rpc = RpcServiceBuilder::new().layer_fn(|service| Logger(service)); + let rpc = RpcServiceBuilder::new().layer_fn(|service| Logger(service)).rpc_logger(1024); let client = HttpClient::builder().set_http_middleware(middleware).set_rpc_middleware(rpc).build(url)?; let params = rpc_params![1_u64, 2, 3]; diff --git a/examples/examples/ws.rs b/examples/examples/ws.rs index 031df7d96b..61173162a4 100644 --- a/examples/examples/ws.rs +++ b/examples/examples/ws.rs @@ -43,7 +43,8 @@ async fn main() -> anyhow::Result<()> { let addr = run_server().await?; let url = format!("ws://{}", addr); - let client = WsClientBuilder::default().build(&url).await?; + let rpc_middleware = RpcServiceBuilder::new().rpc_logger(1024); + let client = WsClientBuilder::new().set_rpc_middleware(rpc_middleware).build(&url).await?; let response: String = client.request("say_hello", rpc_params![]).await?; tracing::info!("response: {:?}", response); From 0e2adb0fe031a17d931146dcf700157ae8a2b90d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 28 Mar 2025 20:46:20 +0100 Subject: [PATCH 24/52] fix some nits --- client/http-client/Cargo.toml | 1 - client/ws-client/src/lib.rs | 1 + core/src/client/async_client/mod.rs | 8 ++++---- core/src/client/mod.rs | 5 +---- core/src/middleware/mod.rs | 6 +++--- types/src/request.rs | 3 +-- 6 files changed, 10 insertions(+), 14 deletions(-) diff --git a/client/http-client/Cargo.toml b/client/http-client/Cargo.toml index 0b36c24d80..dbc2768e38 100644 --- a/client/http-client/Cargo.toml +++ b/client/http-client/Cargo.toml @@ -32,7 +32,6 @@ serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["time"] } -tracing = { workspace = true } tower = { workspace = true, features = ["util"] } url = { workspace = true } diff --git a/client/ws-client/src/lib.rs b/client/ws-client/src/lib.rs index 5a935d30a7..1935ffa23f 100644 --- a/client/ws-client/src/lib.rs +++ b/client/ws-client/src/lib.rs @@ -273,6 +273,7 @@ impl WsClientBuilder { /// Set the RPC service builder. pub fn set_rpc_middleware(self, service_builder: RpcServiceBuilder) -> WsClientBuilder { WsClientBuilder { + #[cfg(feature = "tls")] certificate_store: self.certificate_store, max_request_size: self.max_request_size, max_response_size: self.max_response_size, diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index 591f966481..1cb18f45d2 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -505,7 +505,7 @@ where let id = self.id_manager.next_request_id(); let params = params.to_rpc_params()?; - let request = Request::borrowed(method.into(), params.as_deref(), id.clone()); + let request = Request::borrowed(method, params.as_deref(), id.clone()); let fut = self.service.call(request); let rp = self.map_rpc_service_err(fut).await?.into_method_call().expect("Method call response"); let success = ResponseSuccess::try_from(rp)?; @@ -650,7 +650,7 @@ fn handle_backend_messages( let first_non_whitespace = raw.iter().find(|byte| !byte.is_ascii_whitespace()); let mut messages = Vec::new(); - tracing::trace!(target: LOG_TARGET, "Received JSON: {}", serde_json::from_slice::<&JsonRawValue>(&raw).map_or("", |v| v.get())); + tracing::trace!(target: LOG_TARGET, "rx: {}", serde_json::from_slice::<&JsonRawValue>(raw).map_or("", |v| v.get())); match first_non_whitespace { Some(b'{') => { @@ -764,7 +764,7 @@ async fn handle_frontend_messages( FrontToBack::Batch(batch) => { if let Err(send_back) = manager.lock().insert_pending_batch(batch.ids.clone(), batch.send_back) { tracing::debug!(target: LOG_TARGET, "Batch request already pending: {:?}", batch.ids); - let _ = send_back.send(Err(InvalidRequestId::Occupied(format!("{:?}", batch.ids)).into())); + let _ = send_back.send(Err(InvalidRequestId::Occupied(format!("{:?}", batch.ids)))); return Ok(()); } @@ -780,7 +780,7 @@ async fn handle_frontend_messages( tracing::debug!(target: LOG_TARGET, "Denied duplicate method call"); if let Some(s) = send_back { - let _ = s.send(Err(InvalidRequestId::Occupied(request.id.to_string()).into())); + let _ = s.send(Err(InvalidRequestId::Occupied(request.id.to_string()))); } return Ok(()); } diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 0e11779b7c..f39eab3e73 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -63,9 +63,6 @@ type JsonValue = Box; pub(crate) type Response = jsonrpsee_types::Response<'static, Box>; pub(crate) type ResponseResult = Result; -//pub(crate) type JsonValue<'a> = std::borrow::Cow<'a, RawValue>; -//pub(crate) type OwnedJsonValue = std::borrow::Cow<'static, RawValue>; - impl SubscriptionLagged { /// Create a new [`SubscriptionLagged`]. pub(crate) fn new() -> Self { @@ -431,7 +428,7 @@ where type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll> { let res = match futures_util::ready!(self.rx.poll_next_unpin(cx)) { - Some(v) => Some(serde_json::from_str::(v.get()).map_err(Into::into)), + Some(v) => Some(serde_json::from_str::(v.get())), None => { self.is_closed = true; None diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index ca6f534afd..c1aeeddd64 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -22,7 +22,7 @@ pub use jsonrpsee_types::{Extensions, Request}; pub type ResponseBoxFuture<'a, R, E> = BoxFuture<'a, Result>; /// A batch of JSON-RPC calls and notifications. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Batch<'a> { inner: Vec>, id_range: Option>, @@ -48,7 +48,7 @@ impl<'a> std::fmt::Display for Batch<'a> { impl<'a> Batch<'a> { /// Create a new empty batch. pub fn new() -> Self { - Self { inner: Vec::new(), id_range: None } + Self::default() } /// Create a new batch from a list of batch entries without an id range. @@ -235,7 +235,7 @@ pub trait RpcServiceT<'a> { fn notification(&self, n: Notification<'a>) -> Self::Future; } -/// I +/// Interface for types that can be converted into a JSON value. pub trait IntoJson { /// Convert the type into a JSON value. fn into_json(self) -> Result, serde_json::Error>; diff --git a/types/src/request.rs b/types/src/request.rs index cd458eac42..117d7ef91e 100644 --- a/types/src/request.rs +++ b/types/src/request.rs @@ -253,8 +253,7 @@ mod test { for (ser, id, params, method) in test_vector.iter().cloned() { let request = - serde_json::to_string(&Request::borrowed(method.into(), params.as_deref(), id.unwrap_or(Id::Null))) - .unwrap(); + serde_json::to_string(&Request::borrowed(method, params.as_deref(), id.unwrap_or(Id::Null))).unwrap(); assert_eq!(&request, ser); } From 6b80964d7719ad943e08beb631977065c5230395 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 31 Mar 2025 18:31:24 +0200 Subject: [PATCH 25/52] add client middleware rpc --- benches/bench.rs | 4 +- client/http-client/src/client.rs | 6 +- client/http-client/src/rpc_service.rs | 6 +- core/src/client/async_client/helpers.rs | 20 +-- core/src/client/async_client/manager.rs | 6 +- core/src/client/async_client/mod.rs | 13 +- core/src/client/async_client/rpc_service.rs | 4 +- core/src/client/mod.rs | 153 +++++++++++++++----- core/src/middleware/layer/logger.rs | 11 +- core/src/middleware/mod.rs | 35 +++-- core/src/server/method_response.rs | 8 + examples/examples/rpc_middleware.rs | 51 +++++-- server/src/middleware/rpc.rs | 2 +- server/src/tests/http.rs | 2 +- 14 files changed, 221 insertions(+), 100 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index 51c1d212a9..1f45168dcc 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -128,7 +128,7 @@ pub fn jsonrpsee_types_v2(crit: &mut Criterion) { builder.insert(1u64).unwrap(); builder.insert(2u32).unwrap(); let params = builder.to_rpc_params().expect("Valid params"); - let request = Request::borrowed(&"say_hello", params.as_deref(), Id::Number(0)); + let request = Request::borrowed("say_hello", params.as_deref(), Id::Number(0)); v2_serialize(request); }) }); @@ -150,7 +150,7 @@ pub fn jsonrpsee_types_v2(crit: &mut Criterion) { let mut builder = ObjectParams::new(); builder.insert("key", 1u32).unwrap(); let params = builder.to_rpc_params().expect("Valid params"); - let request = Request::borrowed(&"say_hello", params.as_deref(), Id::Number(0)); + let request = Request::borrowed("say_hello", params.as_deref(), Id::Number(0)); v2_serialize(request); }) }); diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index de78bd2265..9c59b76e10 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -381,7 +381,7 @@ where let request = Request::borrowed(method, params.as_deref(), id.clone()); let rp = self.transport.call(request).await?.into_method_call().expect("Transport::call must return a method call"); - let rp = ResponseSuccess::try_from(rp)?; + let rp = ResponseSuccess::try_from(rp.into_inner())?; let result = serde_json::from_str(rp.result.get()).map_err(Error::ParseError)?; if rp.id == id { Ok(result) } else { Err(InvalidRequestId::NotPendingRequest(rp.id.to_string()).into()) } @@ -425,9 +425,9 @@ where } for rp in json_rps.into_iter() { - let id = rp.id.try_parse_inner_as_number()?; + let id = rp.id().try_parse_inner_as_number()?; - let res = match ResponseSuccess::try_from(rp) { + let res = match ResponseSuccess::try_from(rp.into_inner()) { Ok(r) => { let v = serde_json::from_str(r.result.get()).map_err(Error::ParseError)?; success += 1; diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index 29ba1d144c..ac1723cb5b 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -46,7 +46,7 @@ where let raw = serde_json::to_string(&request)?; let bytes = service.send_and_read_body(raw).await.map_err(|e| Error::Transport(e.into()))?; let json_rp: Response> = serde_json::from_slice(&bytes)?; - Ok(MethodResponse::method_call(json_rp.into_owned(), request.extensions)) + Ok(MethodResponse::method_call(json_rp.into_owned().into(), request.extensions)) } .boxed() } @@ -59,12 +59,12 @@ where let bytes = service.send_and_read_body(raw).await.map_err(|e| Error::Transport(e.into()))?; let rp: Vec<_> = serde_json::from_slice::>>>(&bytes)? .into_iter() - .map(|r| r.into_owned()) + .map(|r| r.into_owned().into()) .collect(); let mut extensions = Extensions::new(); - for call in batch.into_iter() { + for call in batch.into_batch_entries() { extensions.extend(call.into_extensions()); } diff --git a/core/src/client/async_client/helpers.rs b/core/src/client/async_client/helpers.rs index 8fc9a1b0eb..a07adbc9cb 100644 --- a/core/src/client/async_client/helpers.rs +++ b/core/src/client/async_client/helpers.rs @@ -27,7 +27,7 @@ use crate::client::async_client::manager::{RequestManager, RequestStatus}; use crate::client::async_client::{LOG_TARGET, Notification}; use crate::client::{ - Error, JsonValue, RequestMessage, TransportSenderT, TrySubscriptionSendError, subscription_channel, + Error, RawResponseOwned, RequestMessage, TransportSenderT, TrySubscriptionSendError, subscription_channel, }; use crate::params::ArrayParams; use crate::traits::ToRpcParams; @@ -51,7 +51,7 @@ use std::ops::Range; /// On success the result is sent to the frontend. pub(crate) fn process_batch_response( manager: &mut RequestManager, - rps: Vec>>, + rps: Vec, range: Range, ) -> Result<(), InvalidRequestId> { let mut responses = Vec::with_capacity(rps.len()); @@ -68,18 +68,18 @@ pub(crate) fn process_batch_response( for _ in range { let err_obj = ErrorObject::borrowed(0, "", None); - responses.push(Response::new(jsonrpsee_types::ResponsePayload::error(err_obj), Id::Null)); + responses.push(Response::new(jsonrpsee_types::ResponsePayload::error(err_obj), Id::Null).into()); } for rp in rps { - let id = rp.id.try_parse_inner_as_number()?; + let id = rp.id().try_parse_inner_as_number()?; let maybe_elem = id.checked_sub(start_idx).and_then(|p| p.try_into().ok()).and_then(|p: usize| responses.get_mut(p)); if let Some(elem) = maybe_elem { *elem = rp; } else { - return Err(InvalidRequestId::NotPendingRequest(rp.id.to_string())); + return Err(InvalidRequestId::NotPendingRequest(rp.id().to_string())); } } @@ -128,7 +128,7 @@ pub(crate) fn process_subscription_response( /// It's possible that the user closed down the subscription before the actual close response is received pub(crate) fn process_subscription_close_response( manager: &mut RequestManager, - response: SubscriptionError, + response: SubscriptionError<&RawValue>, ) { let sub_id = response.params.subscription.into_owned(); match manager.get_request_id_by_subscription_id(&sub_id) { @@ -173,10 +173,10 @@ pub(crate) fn process_notification(manager: &mut RequestManager, notif: Notifica /// Returns `Err(_)` if the response couldn't be handled. pub(crate) fn process_single_response( manager: &mut RequestManager, - response: Response>, + response: RawResponseOwned, max_capacity_per_subscription: usize, ) -> Result, InvalidRequestId> { - let response_id = response.id.clone().into_owned(); + let response_id = response.id().clone().into_owned(); match manager.request_status(&response_id) { RequestStatus::PendingMethodCall => { @@ -186,7 +186,7 @@ pub(crate) fn process_single_response( None => return Err(InvalidRequestId::NotPendingRequest(response_id.to_string())), }; - let _ = send_back_oneshot.send(Ok(response.into_owned())); + let _ = send_back_oneshot.send(Ok(response)); Ok(None) } RequestStatus::PendingSubscription => { @@ -194,7 +194,7 @@ pub(crate) fn process_single_response( .complete_pending_subscription(response_id.clone()) .ok_or(InvalidRequestId::NotPendingRequest(response_id.to_string()))?; - let result = ResponseSuccess::try_from(response); + let result = ResponseSuccess::try_from(response.into_inner()); let json = match result { Ok(s) => s.result, diff --git a/core/src/client/async_client/manager.rs b/core/src/client/async_client/manager.rs index 5c816cc904..333d745804 100644 --- a/core/src/client/async_client/manager.rs +++ b/core/src/client/async_client/manager.rs @@ -38,7 +38,7 @@ use std::{ }; use crate::{ - client::{Error, Response, ResponseResult, SubscriptionReceiver, SubscriptionSender}, + client::{Error, RawResponseOwned, SubscriptionReceiver, SubscriptionSender}, error::RegisterMethodError, }; use jsonrpsee_types::{Id, InvalidRequestId, SubscriptionId}; @@ -65,8 +65,8 @@ pub(crate) enum RequestStatus { Invalid, } -type PendingCallOneshot = Option>; -type PendingBatchOneshot = oneshot::Sender, InvalidRequestId>>; +type PendingCallOneshot = Option>>; +type PendingBatchOneshot = oneshot::Sender, InvalidRequestId>>; type PendingSubscriptionOneshot = oneshot::Sender), Error>>; type SubscriptionSink = SubscriptionSender; type UnsubscribeMethod = String; diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index 1cb18f45d2..3c591c1764 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -508,7 +508,7 @@ where let request = Request::borrowed(method, params.as_deref(), id.clone()); let fut = self.service.call(request); let rp = self.map_rpc_service_err(fut).await?.into_method_call().expect("Method call response"); - let success = ResponseSuccess::try_from(rp)?; + let success = ResponseSuccess::try_from(rp.into_inner())?; serde_json::from_str(success.result.get()).map_err(Into::into) } @@ -541,7 +541,7 @@ where let mut failed_calls = 0; for json_val in json_values { - match ResponseSuccess::try_from(json_val) { + match ResponseSuccess::try_from(json_val.into_inner()) { Ok(val) => { let result: R = serde_json::from_str(val.result.get()).map_err(Error::ParseError)?; responses.push(Ok(result)); @@ -656,8 +656,11 @@ fn handle_backend_messages( Some(b'{') => { // Single response to a request. if let Ok(single) = serde_json::from_slice::>(raw) { - let maybe_unsub = - process_single_response(&mut manager.lock(), single, max_buffer_capacity_per_subscription)?; + let maybe_unsub = process_single_response( + &mut manager.lock(), + single.into_owned().into(), + max_buffer_capacity_per_subscription, + )?; if let Some(unsub) = maybe_unsub { return Ok(vec![FrontToBack::Request(unsub)]); @@ -691,7 +694,7 @@ fn handle_backend_messages( for r in raw_responses { if let Ok(response) = serde_json::from_str::>(r.get()) { let id = response.id.try_parse_inner_as_number()?; - batch.push(response.into_owned()); + batch.push(response.into_owned().into()); let r = range.get_or_insert(id..id); diff --git a/core/src/client/async_client/rpc_service.rs b/core/src/client/async_client/rpc_service.rs index efd99f5a63..2e17e24b60 100644 --- a/core/src/client/async_client/rpc_service.rs +++ b/core/src/client/async_client/rpc_service.rs @@ -91,7 +91,7 @@ impl<'a> RpcServiceT<'a> for RpcService { Ok(MethodResponse::subscription( SubscriptionResponse { - rp: Response::new(ResponsePayload::success(s), request.id.clone().into_owned()), + rp: Response::new(ResponsePayload::success(s), request.id.clone().into_owned()).into(), sub_id, stream: subscribe_rx, }, @@ -133,7 +133,7 @@ impl<'a> RpcServiceT<'a> for RpcService { let mut extensions = Extensions::new(); - for entry in batch.into_iter() { + for entry in batch.into_batch_entries() { extensions.extend(entry.into_extensions()); } diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index f39eab3e73..6c70d4fc9f 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -32,6 +32,7 @@ cfg_async_client! { } pub mod error; + pub use error::Error; use std::fmt; @@ -42,6 +43,7 @@ use std::sync::{Arc, RwLock}; use std::task::{self, Poll}; use tokio::sync::mpsc::error::TrySendError; +use crate::middleware::ToJson; use crate::params::BatchRequestBuilder; use crate::traits::ToRpcParams; @@ -50,6 +52,7 @@ use core::marker::PhantomData; use futures_util::stream::{Stream, StreamExt}; use http::Extensions; use jsonrpsee_types::{ErrorObject, Id, InvalidRequestId, SubscriptionId}; +use serde::Serialize; use serde::de::DeserializeOwned; use serde_json::value::RawValue; use tokio::sync::{mpsc, oneshot}; @@ -58,10 +61,8 @@ use tokio::sync::{mpsc, oneshot}; #[derive(Debug, Clone)] pub(crate) struct SubscriptionLagged(Arc>); -type JsonValue = Box; - -pub(crate) type Response = jsonrpsee_types::Response<'static, Box>; -pub(crate) type ResponseResult = Result; +/// Owned version of [`RawResponse`]. +pub type RawResponseOwned = RawResponse<'static>; impl SubscriptionLagged { /// Create a new [`SubscriptionLagged`]. @@ -276,7 +277,7 @@ pub struct Subscription { is_closed: bool, /// Channel to send requests to the background task. to_back: mpsc::Sender, - /// Channel from which we receive notifications from the server, as encoded `JsonValue`s. + /// Channel from which we receive notifications from the server, as encoded as JSON. rx: SubscriptionReceiver, /// Callback kind. kind: Option, @@ -339,7 +340,7 @@ struct BatchMessage { /// Request IDs. ids: Range, /// One-shot channel over which we send back the result of this request. - send_back: oneshot::Sender, InvalidRequestId>>, + send_back: oneshot::Sender, InvalidRequestId>>, } /// Request message. @@ -350,7 +351,7 @@ struct RequestMessage { /// Request ID. id: Id<'static>, /// One-shot channel over which we send back the result of this request. - send_back: Option>, + send_back: Option>>, } /// Subscription message. @@ -639,7 +640,7 @@ pub(crate) struct SubscriptionReceiver { } impl Stream for SubscriptionReceiver { - type Item = JsonValue; + type Item = Box; fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll> { self.inner.poll_recv(cx) @@ -654,12 +655,17 @@ fn subscription_channel(max_buf_size: usize) -> (SubscriptionSender, Subscriptio (SubscriptionSender { inner: tx, lagged: lagged_tx }, SubscriptionReceiver { inner: rx, lagged: lagged_rx }) } +/// Represents the kind of response that can be received from the server. #[derive(Debug)] -enum MethodResponseKind { - MethodCall(Response), +pub enum MethodResponseKind { + /// Method call response. + MethodCall(RawResponseOwned), + /// Subscription response. Subscription(SubscriptionResponse), + /// Notification response (no payload). Notification, - Batch(Vec), + /// Batch response. + Batch(Vec), } /// Represents an active subscription returned by the server. @@ -670,8 +676,20 @@ pub struct SubscriptionResponse { // The receiver is used to receive notifications from the server and shouldn't be exposed to the user // from the middleware. stream: SubscriptionReceiver, - /// Response - rp: Response, + /// The raw response from the server (mostly used for middleware). + rp: RawResponseOwned, +} + +impl SubscriptionResponse { + /// Get the subscription ID. + pub fn subscription_id(&self) -> &SubscriptionId<'static> { + &self.sub_id + } + + /// Get the raw response. + pub fn response(&self) -> &RawResponseOwned { + &self.rp + } } /// Represents a response from the server which can be a method call, notification or batch. @@ -683,7 +701,7 @@ pub struct MethodResponse { impl MethodResponse { /// Create a new method response. - pub fn method_call(rp: Response, extensions: Extensions) -> Self { + pub fn method_call(rp: RawResponseOwned, extensions: Extensions) -> Self { Self { inner: MethodResponseKind::MethodCall(rp), extensions } } @@ -698,23 +716,12 @@ impl MethodResponse { } /// Create a new batch response. - pub fn batch(json: Vec, extensions: Extensions) -> Self { + pub fn batch(json: Vec, extensions: Extensions) -> Self { Self { inner: MethodResponseKind::Batch(json), extensions } } - /// Get the raw JSON value. - pub fn to_json(&self) -> Result, serde_json::Error> { - match self.inner { - MethodResponseKind::MethodCall(ref call) => serde_json::value::to_raw_value(&call), - MethodResponseKind::Notification => Ok(RawValue::NULL.to_owned()), - MethodResponseKind::Batch(ref json) => serde_json::value::to_raw_value(&json), - MethodResponseKind::Subscription(ref s) => serde_json::value::to_raw_value(&s.rp), - } - } - /// Get the method call if this response is a method call. - #[doc(hidden)] - pub fn into_method_call(self) -> Option { + pub fn into_method_call(self) -> Option { match self.inner { MethodResponseKind::MethodCall(call) => Some(call), _ => None, @@ -722,16 +729,19 @@ impl MethodResponse { } /// Get the batch if this response is a batch. - #[doc(hidden)] - pub fn into_batch(self) -> Option> { + pub fn into_batch(self) -> Option> { match self.inner { MethodResponseKind::Batch(batch) => Some(batch), _ => None, } } + /// Extract the inner response. + pub fn as_inner(&self) -> &MethodResponseKind { + &self.inner + } + /// Get the subscription if this response is a subscription. - #[doc(hidden)] fn into_subscription(self) -> Option<(SubscriptionId<'static>, SubscriptionReceiver)> { match self.inner { MethodResponseKind::Subscription(s) => Some((s.sub_id, s.stream)), @@ -770,9 +780,84 @@ impl MethodResponse { } } -impl std::fmt::Display for MethodResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let json = self.to_json().expect("JSON serialization failed"); - f.write_str(json.get()) +impl ToJson for MethodResponse { + fn to_json(&self) -> Result { + match &self.inner { + MethodResponseKind::MethodCall(call) => call.to_json(), + MethodResponseKind::Notification => Ok(String::new()), + MethodResponseKind::Batch(json) => serde_json::to_string(json), + MethodResponseKind::Subscription(s) => serde_json::to_string(&s.rp), + } + } +} + +/// A raw JSON-RPC response object which can be either a JSON-RPC success or error response. +/// +/// This is a wrapper around the `jsonrpsee_types::Response` type for ease of use +/// for middleware client implementations. +#[derive(Debug)] +pub struct RawResponse<'a>(jsonrpsee_types::Response<'a, Box>); + +impl Serialize for RawResponse<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'a> From>> for RawResponse<'a> { + fn from(r: jsonrpsee_types::Response<'a, Box>) -> Self { + Self(r) + } +} + +impl<'a> RawResponse<'a> { + /// Whether this response is successful JSON-RPC response. + pub fn is_success(&self) -> bool { + match self.0.payload { + jsonrpsee_types::ResponsePayload::Success(_) => true, + jsonrpsee_types::ResponsePayload::Error(_) => false, + } + } + + /// Extract the error object from the response if it is an error. + pub fn as_error(&self) -> Option<&ErrorObject<'_>> { + match self.0.payload { + jsonrpsee_types::ResponsePayload::Error(ref err) => Some(err), + _ => None, + } + } + + // Extract the result field the response if it is a success. + /// + /// Omits JSON-RPC specific fields like `jsonrpc` and `id`. + pub fn as_success(&self) -> Option<&RawValue> { + match self.0.payload { + jsonrpsee_types::ResponsePayload::Success(ref res) => Some(res), + _ => None, + } + } + + /// Get the request ID. + pub fn id(&self) -> &Id<'a> { + &self.0.id + } + + /// Consume the response and extract the inner value. + pub fn into_inner(self) -> jsonrpsee_types::Response<'a, Box> { + self.0 + } + + /// Convert the response into an owned version. + pub fn into_owned(self) -> RawResponseOwned { + RawResponse(self.0.into_owned()) + } +} + +impl ToJson for RawResponse<'_> { + fn to_json(&self) -> Result { + serde_json::to_string(&self.0) } } diff --git a/core/src/middleware/layer/logger.rs b/core/src/middleware/layer/logger.rs index d473001567..e03fa2effc 100644 --- a/core/src/middleware/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -32,7 +32,7 @@ use std::{ }; use crate::{ - middleware::{Batch, Notification, RpcServiceT}, + middleware::{Batch, Notification, RpcServiceT, ToJson}, tracing::truncate_at_char_boundary, }; @@ -71,7 +71,7 @@ impl<'a, S> RpcServiceT<'a> for RpcLogger where S: RpcServiceT<'a>, S::Error: std::fmt::Debug + Send, - S::Response: std::fmt::Display, + S::Response: ToJson, { type Future = Instrumented>; type Error = S::Error; @@ -126,7 +126,7 @@ impl std::fmt::Debug for ResponseFuture { impl Future for ResponseFuture where F: Future>, - R: std::fmt::Display, + R: ToJson, E: std::fmt::Debug, { type Output = F::Output; @@ -137,8 +137,9 @@ where match fut.poll(cx) { Poll::Ready(Ok(rp)) => { - let json = rp.to_string(); - tracing::trace!(target: "jsonrpsee", "response = {}", truncate_at_char_boundary(&json, max as usize)); + let as_json = rp.to_json(); + let json = as_json.as_ref().map_or("", |j| j.as_str()); + tracing::trace!(target: "jsonrpsee", "response = {}", truncate_at_char_boundary(json, max as usize)); Poll::Ready(Ok(rp)) } Poll::Ready(Err(e)) => Poll::Ready(Err(e)), diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index c1aeeddd64..b110af94de 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -38,7 +38,7 @@ impl Serialize for Batch<'_> { } } -impl<'a> std::fmt::Display for Batch<'a> { +impl std::fmt::Display for Batch<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = serde_json::to_string(&self.inner).expect("Batch serialization failed"); f.write_str(&s) @@ -77,19 +77,19 @@ impl<'a> Batch<'a> { Ok(()) } - /// Mutable iterator over the batch entries. - pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, BatchEntry<'a>> { - self.inner.iter_mut() + /// Get an iterator over the batch entries. + pub fn as_batch_entries(&self) -> &[BatchEntry<'a>] { + &self.inner } - /// Immutable iterator over the batch entries. - pub fn iter(&self) -> std::slice::Iter<'_, BatchEntry<'a>> { - self.inner.iter() + /// Get a mutable iterator over the batch entries. + pub fn as_mut_batch_entries(&mut self) -> &mut [BatchEntry<'a>] { + &mut self.inner } - /// Consume the batch and return batch entries. - pub fn into_iter(self) -> std::vec::IntoIter> { - self.inner.into_iter() + /// Consume the batch and return the inner entries. + pub fn into_batch_entries(self) -> Vec> { + self.inner } /// Get the id range of the batch. @@ -159,7 +159,7 @@ impl<'a> InvalidRequest<'a> { } } -impl<'a> BatchEntry<'a> { +impl BatchEntry<'_> { /// Get a reference to extensions of the batch entry. pub fn extensions(&self) -> &Extensions { match self { @@ -214,7 +214,7 @@ pub trait RpcServiceT<'a> { type Error: std::fmt::Debug; /// The response type - type Response; + type Response: ToJson; /// Processes a single JSON-RPC call, which may be a subscription or regular call. fn call(&self, request: Request<'a>) -> Self::Future; @@ -226,19 +226,18 @@ pub trait RpcServiceT<'a> { /// of these methods. /// /// As a result, if you have custom logic for individual calls or notifications, - /// you must duplicate that logic here. - /// - // TODO: Investigate if the complete service can be invoked inside `RpcService`. + /// you must duplicate that implementation in this method or no middleware will be applied + /// for calls inside the batch. fn batch(&self, requests: Batch<'a>) -> Self::Future; /// Similar to `RpcServiceT::call` but processes a JSON-RPC notification. fn notification(&self, n: Notification<'a>) -> Self::Future; } -/// Interface for types that can be converted into a JSON value. -pub trait IntoJson { +/// Interface for types that can be serialized into JSON. +pub trait ToJson { /// Convert the type into a JSON value. - fn into_json(self) -> Result, serde_json::Error>; + fn to_json(&self) -> Result; } /// Similar to [`tower::ServiceBuilder`] but doesn't diff --git a/core/src/server/method_response.rs b/core/src/server/method_response.rs index 350d37c8c7..c554ede87e 100644 --- a/core/src/server/method_response.rs +++ b/core/src/server/method_response.rs @@ -40,6 +40,8 @@ use jsonrpsee_types::{ErrorObjectOwned, Id, Response, ResponsePayload as InnerRe use serde::Serialize; use serde_json::value::to_raw_value; +use crate::middleware::ToJson; + #[derive(Debug, Clone)] enum ResponseKind { MethodCall, @@ -75,6 +77,12 @@ impl AsRef for MethodResponse { } } +impl ToJson for MethodResponse { + fn to_json(&self) -> Result { + Ok(self.result.clone()) + } +} + impl std::fmt::Display for MethodResponse { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.result) diff --git a/examples/examples/rpc_middleware.rs b/examples/examples/rpc_middleware.rs index f9307e0f1d..bbc03a51eb 100644 --- a/examples/examples/rpc_middleware.rs +++ b/examples/examples/rpc_middleware.rs @@ -36,6 +36,11 @@ //! Contrary the HTTP middleware does only apply per HTTP request and //! may be handy in some scenarios such CORS but if you want to access //! to the actual JSON-RPC details this is the middleware to use. +//! +//! This example enables the same middleware for both the server and client which +//! can be confusing when one runs this but it is just to demonstrate the API. +//! +//! That the middleware is applied to the server and client in the same way. use std::net::SocketAddr; use std::sync::Arc; @@ -56,6 +61,7 @@ use jsonrpsee::ws_client::WsClientBuilder; pub struct CallsPerConn { service: S, count: Arc, + role: &'static str, } impl<'a, S> RpcServiceT<'a> for CallsPerConn @@ -69,18 +75,21 @@ where fn call(&self, req: Request<'a>) -> Self::Future { let count = self.count.clone(); let service = self.service.clone(); + let role = self.role; async move { let rp = service.call(req).await; count.fetch_add(1, Ordering::SeqCst); - let count = count.load(Ordering::SeqCst); - println!("the server has processed calls={count} on the connection"); + println!("{role} processed calls={} on the connection", count.load(Ordering::SeqCst)); rp } .boxed() } fn batch(&self, batch: Batch<'a>) -> Self::Future { + let len = batch.as_batch_entries().len(); + self.count.fetch_add(len, Ordering::SeqCst); + println!("{} processed calls={} on the connection", self.role, self.count.load(Ordering::SeqCst)); Box::pin(self.service.batch(batch)) } @@ -93,6 +102,7 @@ where pub struct GlobalCalls { service: S, count: Arc, + role: &'static str, } impl<'a, S> RpcServiceT<'a> for GlobalCalls @@ -106,18 +116,22 @@ where fn call(&self, req: Request<'a>) -> Self::Future { let count = self.count.clone(); let service = self.service.clone(); + let role = self.role; async move { let rp = service.call(req).await; count.fetch_add(1, Ordering::SeqCst); - let count = count.load(Ordering::SeqCst); - println!("the server has processed calls={count} in total"); + println!("{role} processed calls={} in total", count.load(Ordering::SeqCst)); + rp } .boxed() } fn batch(&self, batch: Batch<'a>) -> Self::Future { + let len = batch.as_batch_entries().len(); + self.count.fetch_add(len, Ordering::SeqCst); + println!("{}, processed calls={} in total", self.role, self.count.load(Ordering::SeqCst)); Box::pin(self.service.batch(batch)) } @@ -127,7 +141,10 @@ where } #[derive(Clone)] -pub struct Logger(S); +pub struct Logger { + service: S, + role: &'static str, +} impl<'a, S> RpcServiceT<'a> for Logger where @@ -138,15 +155,16 @@ where type Response = S::Response; fn call(&self, req: Request<'a>) -> Self::Future { - println!("logger middleware: method `{}`", req.method); - self.0.call(req) + println!("{} logger middleware: method `{}`", self.role, req.method); + self.service.call(req) } fn batch(&self, batch: Batch<'a>) -> Self::Future { - self.0.batch(batch) + println!("{} logger middleware: batch {batch}", self.role); + self.service.batch(batch) } fn notification(&self, n: Notification<'a>) -> Self::Future { - self.0.notification(n) + self.service.notification(n) } } @@ -161,7 +179,14 @@ async fn main() -> anyhow::Result<()> { let url = format!("ws://{}", addr); for _ in 0..2 { - let client = WsClientBuilder::default().build(&url).await?; + let global_cnt = Arc::new(AtomicUsize::new(0)); + let rpc_middleware = RpcServiceBuilder::new() + .layer_fn(|service| Logger { service, role: "client" }) + // This state is created per connection. + .layer_fn(|service| CallsPerConn { service, count: Default::default(), role: "client" }) + // This state is shared by all connections. + .layer_fn(move |service| GlobalCalls { service, count: global_cnt.clone(), role: "client" }); + let client = WsClientBuilder::new().set_rpc_middleware(rpc_middleware).build(&url).await?; let response: String = client.request("say_hello", rpc_params![]).await?; println!("response: {:?}", response); let _response: Result = client.request("unknown_method", rpc_params![]).await; @@ -178,11 +203,11 @@ async fn run_server() -> anyhow::Result { let global_cnt = Arc::new(AtomicUsize::new(0)); let rpc_middleware = RpcServiceBuilder::new() - .layer_fn(Logger) + .layer_fn(|service| Logger { service, role: "server" }) // This state is created per connection. - .layer_fn(|service| CallsPerConn { service, count: Default::default() }) + .layer_fn(|service| CallsPerConn { service, count: Default::default(), role: "server" }) // This state is shared by all connections. - .layer_fn(move |service| GlobalCalls { service, count: global_cnt.clone() }); + .layer_fn(move |service| GlobalCalls { service, count: global_cnt.clone(), role: "server" }); let server = Server::builder().set_rpc_middleware(rpc_middleware).build("127.0.0.1:0").await?; let mut module = RpcModule::new(()); module.register_method("say_hello", |_, _, _| "lo")?; diff --git a/server/src/middleware/rpc.rs b/server/src/middleware/rpc.rs index 41ffd26b25..da85bddc7b 100644 --- a/server/src/middleware/rpc.rs +++ b/server/src/middleware/rpc.rs @@ -156,7 +156,7 @@ impl<'a> RpcServiceT<'a> for RpcService { async move { let mut got_notification = false; - for batch_entry in batch.into_iter() { + for batch_entry in batch.into_batch_entries() { match batch_entry { BatchEntry::Call(req) => { let rp = match service.call(req).await { diff --git a/server/src/tests/http.rs b/server/src/tests/http.rs index 253161b285..19f460bc6f 100644 --- a/server/src/tests/http.rs +++ b/server/src/tests/http.rs @@ -76,7 +76,7 @@ where } fn batch(&self, mut batch: Batch<'a>) -> Self::Future { - if let Some(last) = batch.iter_mut().last() { + if let Some(last) = batch.as_mut_batch_entries().last_mut() { if last.method_name().contains("err") { last.extensions_mut().insert(StatusCode::IM_A_TEAPOT); } else { From 631d7c0cfca1c5fc8d2727e106204a9fd905f1e4 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 1 Apr 2025 15:44:51 +0200 Subject: [PATCH 26/52] fix wasm build --- client/transport/Cargo.toml | 3 +- client/wasm-client/Cargo.toml | 1 + client/wasm-client/src/lib.rs | 52 ++++++++++++++++++----------- core/src/client/async_client/mod.rs | 24 ++++++++----- core/src/client/mod.rs | 13 +++++--- 5 files changed, 59 insertions(+), 34 deletions(-) diff --git a/client/transport/Cargo.toml b/client/transport/Cargo.toml index 4c1e9d04a9..c641442d39 100644 --- a/client/transport/Cargo.toml +++ b/client/transport/Cargo.toml @@ -18,12 +18,12 @@ workspace = true [dependencies] jsonrpsee-core = { workspace = true, features = ["client"] } -tracing = { workspace = true } # optional thiserror = { workspace = true, optional = true } futures-util = { workspace = true, features = ["alloc"], optional = true } http = { workspace = true, optional = true } +tracing = { workspace = true, optional = true } tokio-util = { workspace = true, features = ["compat"], optional = true } tokio = { workspace = true, features = ["net", "time", "macros"], optional = true } pin-project = { workspace = true, optional = true } @@ -57,6 +57,7 @@ ws = [ "soketto", "pin-project", "thiserror", + "tracing", "url", ] web = [ diff --git a/client/wasm-client/Cargo.toml b/client/wasm-client/Cargo.toml index 574922b719..0d9b202ff8 100644 --- a/client/wasm-client/Cargo.toml +++ b/client/wasm-client/Cargo.toml @@ -20,6 +20,7 @@ workspace = true jsonrpsee-types = { workspace = true } jsonrpsee-client-transport = { workspace = true, features = ["web"] } jsonrpsee-core = { workspace = true, features = ["async-wasm-client"] } +tower = { workspace = true } [package.metadata.docs.rs] all-features = true diff --git a/client/wasm-client/src/lib.rs b/client/wasm-client/src/lib.rs index 5b9e8b97cc..e98a1b2eeb 100644 --- a/client/wasm-client/src/lib.rs +++ b/client/wasm-client/src/lib.rs @@ -36,7 +36,10 @@ pub use jsonrpsee_types as types; use std::time::Duration; use jsonrpsee_client_transport::web; -use jsonrpsee_core::client::{ClientBuilder, Error, IdKind}; +use jsonrpsee_core::client::async_client::RpcService; +use jsonrpsee_core::client::{Error, IdKind}; +use jsonrpsee_core::middleware::RpcServiceBuilder; +use tower::layer::util::Identity; /// Builder for [`Client`]. /// @@ -58,33 +61,35 @@ use jsonrpsee_core::client::{ClientBuilder, Error, IdKind}; /// } /// /// ``` -#[derive(Copy, Clone, Debug)] -pub struct WasmClientBuilder { +#[derive(Clone, Debug)] +pub struct WasmClientBuilder { id_kind: IdKind, max_concurrent_requests: usize, max_buffer_capacity_per_subscription: usize, - max_log_length: u32, request_timeout: Duration, + service_builder: RpcServiceBuilder, } -impl Default for WasmClientBuilder { +impl Default for WasmClientBuilder { fn default() -> Self { Self { id_kind: IdKind::Number, - max_log_length: 4096, max_concurrent_requests: 256, max_buffer_capacity_per_subscription: 1024, request_timeout: Duration::from_secs(60), + service_builder: RpcServiceBuilder::default(), } } } -impl WasmClientBuilder { +impl WasmClientBuilder { /// Create a new WASM client builder. - pub fn new() -> WasmClientBuilder { + pub fn new() -> WasmClientBuilder { WasmClientBuilder::default() } +} +impl WasmClientBuilder { /// See documentation [`ClientBuilder::request_timeout`] (default is 60 seconds). pub fn request_timeout(mut self, timeout: Duration) -> Self { self.request_timeout = timeout; @@ -109,32 +114,39 @@ impl WasmClientBuilder { self } - /// Set maximum length for logging calls and responses. - /// - /// Logs bigger than this limit will be truncated. - pub fn set_max_logging_length(mut self, max: u32) -> Self { - self.max_log_length = max; - self + /// See documentation for [`ClientBuilder::set_rpc_middleware`]. + pub fn set_rpc_middleware(self, middleware: RpcServiceBuilder) -> WasmClientBuilder { + WasmClientBuilder { + id_kind: self.id_kind, + max_concurrent_requests: self.max_concurrent_requests, + max_buffer_capacity_per_subscription: self.max_buffer_capacity_per_subscription, + request_timeout: self.request_timeout, + service_builder: middleware, + } } /// Build the client with specified URL to connect to. - pub async fn build(self, url: impl AsRef) -> Result { + pub async fn build(self, url: impl AsRef) -> Result, Error> + where + L: tower::Layer + Clone + Send + Sync + 'static, + { let Self { - max_log_length, id_kind, request_timeout, max_concurrent_requests, max_buffer_capacity_per_subscription, + service_builder, } = self; let (sender, receiver) = web::connect(url).await.map_err(|e| Error::Transport(e.into()))?; - let builder = ClientBuilder::default() - .set_max_logging_length(max_log_length) + let client = Client::builder() .request_timeout(request_timeout) .id_format(id_kind) .max_buffer_capacity_per_subscription(max_buffer_capacity_per_subscription) - .max_concurrent_requests(max_concurrent_requests); + .max_concurrent_requests(max_concurrent_requests) + .set_rpc_middleware(service_builder) + .build_with_wasm(sender, receiver); - Ok(builder.build_with_wasm(sender, receiver)) + Ok(client) } } diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index 3c591c1764..236e9ba91f 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -204,12 +204,14 @@ impl Default for ClientBuilder { } } -impl ClientBuilder { - /// Create a builder for the client. +impl ClientBuilder { + /// Create a new client builder. pub fn new() -> ClientBuilder { ClientBuilder::default() } +} +impl ClientBuilder { /// Set request timeout (default is 60 seconds). pub fn request_timeout(mut self, timeout: Duration) -> Self { self.request_timeout = timeout; @@ -273,7 +275,10 @@ impl ClientBuilder { self } - /// Set the rpc middleware service. + /// Configure the client to a specific RPC middleware which + /// runs for every JSON-RPC call. + /// + /// This is useful for adding a custom logger or something similar. pub fn set_rpc_middleware(self, service_builder: RpcServiceBuilder) -> ClientBuilder { ClientBuilder { request_timeout: self.request_timeout, @@ -363,10 +368,11 @@ impl ClientBuilder { /// Build the client with given transport. #[cfg(all(feature = "async-wasm-client", target_arch = "wasm32"))] #[cfg_attr(docsrs, doc(cfg(feature = "async-wasm-client")))] - pub fn build_with_wasm(self, sender: S, receiver: R) -> Client + pub fn build_with_wasm(self, sender: S, receiver: R) -> Client where S: TransportSenderT, R: TransportReceiverT, + L: tower::Layer + Clone + Send + Sync + 'static, { use futures_util::stream::Pending; @@ -410,10 +416,10 @@ impl ClientBuilder { Client { to_back: to_back.clone(), + service: self.service_builder.service(RpcService::new(to_back.clone(), self.request_timeout)), request_timeout: self.request_timeout, error: ErrorFromBack::new(to_back, disconnect_reason), id_manager: RequestIdManager::new(self.id_kind), - max_log_length: self.max_log_length, on_exit: Some(client_dropped_tx), } } @@ -421,7 +427,7 @@ impl ClientBuilder { /// Generic asynchronous client. #[derive(Debug)] -pub struct Client { +pub struct Client { /// Channel to send requests to the background task. to_back: mpsc::Sender, error: ErrorFromBack, @@ -434,12 +440,14 @@ pub struct Client { service: L, } -impl Client { - /// Create a builder for the server. +impl Client { + /// Create a builder for the client. pub fn builder() -> ClientBuilder { ClientBuilder::::new() } +} +impl Client { /// Checks if the client is connected to the target. pub fn is_connected(&self) -> bool { !self.to_back.is_closed() diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 6c70d4fc9f..9c6476e227 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -736,11 +736,6 @@ impl MethodResponse { } } - /// Extract the inner response. - pub fn as_inner(&self) -> &MethodResponseKind { - &self.inner - } - /// Get the subscription if this response is a subscription. fn into_subscription(self) -> Option<(SubscriptionId<'static>, SubscriptionReceiver)> { match self.inner { @@ -780,6 +775,14 @@ impl MethodResponse { } } +impl std::ops::Deref for MethodResponse { + type Target = MethodResponseKind; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + impl ToJson for MethodResponse { fn to_json(&self) -> Result { match &self.inner { From a7324d39c6f184eb57872429ed1323c4812a6580 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 1 Apr 2025 15:59:48 +0200 Subject: [PATCH 27/52] add client middleware example --- examples/examples/rpc_middleware_client.rs | 178 +++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 examples/examples/rpc_middleware_client.rs diff --git a/examples/examples/rpc_middleware_client.rs b/examples/examples/rpc_middleware_client.rs new file mode 100644 index 0000000000..27d9cc2599 --- /dev/null +++ b/examples/examples/rpc_middleware_client.rs @@ -0,0 +1,178 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! jsonrpsee supports two kinds of middlewares `http_middleware` and `rpc_middleware`. +//! +//! This example demonstrates how to use the `rpc_middleware` which applies for each +//! JSON-RPC method calls, notifications and batch requests. +//! +//! This example demonstrates how to use the `rpc_middleware` for the client +//! and you may benefit specifying the response type to `core::client::MethodResponse` +//! to actually inspect the response instead of using the serialized JSON-RPC response. + +use std::net::SocketAddr; +use std::ops::Deref; +use std::sync::{Arc, Mutex}; + +use futures::FutureExt; +use futures::future::BoxFuture; +use jsonrpsee::core::client::{ClientT, MethodResponse, MethodResponseKind}; +use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::rpc_params; +use jsonrpsee::server::{RpcModule, Server}; +use jsonrpsee::types::{ErrorCode, ErrorObject, Request}; +use jsonrpsee::ws_client::WsClientBuilder; + +#[derive(Default)] +struct InnerMetrics { + method_calls_success: usize, + method_calls_failure: usize, + notifications: usize, + batch_calls: usize, +} + +#[derive(Clone)] +pub struct Metrics { + service: S, + metrics: Arc>, +} + +impl std::fmt::Debug for InnerMetrics { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InnerMetrics") + .field("method_calls_success", &self.method_calls_success) + .field("method_calls_failure", &self.method_calls_failure) + .field("notifications", &self.notifications) + .field("batch_calls", &self.batch_calls) + .finish() + } +} + +impl Metrics { + pub fn new(service: S) -> Self { + Self { service, metrics: Arc::new(Mutex::new(InnerMetrics::default())) } + } +} + +// NOTE: We are MethodResponse as the response type here to be able to inspect the response +// and not just the serialized JSON-RPC response. This is not necessary if you only care about +// the serialized JSON-RPC response. +impl<'a, S> RpcServiceT<'a> for Metrics +where + S: RpcServiceT<'a, Response = MethodResponse> + Send + Sync + Clone + 'static, +{ + type Future = BoxFuture<'a, Result>; + type Error = S::Error; + type Response = S::Response; + + fn call(&self, req: Request<'a>) -> Self::Future { + let m = self.metrics.clone(); + let service = self.service.clone(); + + async move { + let rp = service.call(req).await; + + // Access to inner response via the deref implementation. + match rp.as_ref().map(|r| r.deref()) { + Ok(MethodResponseKind::MethodCall(r)) => { + if r.is_success() { + m.lock().unwrap().method_calls_success += 1; + } else { + m.lock().unwrap().method_calls_failure += 1; + } + } + Ok(e) => unreachable!("Unexpected response type {e:?}"), + Err(e) => { + m.lock().unwrap().method_calls_failure += 1; + tracing::error!("Error: {:?}", e); + } + } + + rp + } + .boxed() + } + + fn batch(&self, batch: Batch<'a>) -> Self::Future { + self.metrics.lock().unwrap().batch_calls += 1; + Box::pin(self.service.batch(batch)) + } + + fn notification(&self, n: Notification<'a>) -> Self::Future { + self.metrics.lock().unwrap().notifications += 1; + Box::pin(self.service.notification(n)) + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::FmtSubscriber::builder() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .try_init() + .expect("setting default subscriber failed"); + + let addr = run_server().await?; + let url = format!("ws://{}", addr); + + let metrics = Arc::new(Mutex::new(InnerMetrics::default())); + + for _ in 0..2 { + let metrics = metrics.clone(); + let rpc_middleware = + RpcServiceBuilder::new().layer_fn(move |s| Metrics { service: s, metrics: metrics.clone() }); + let client = WsClientBuilder::new().set_rpc_middleware(rpc_middleware).build(&url).await?; + let _: Result = client.request("say_hello", rpc_params![]).await; + let _: Result = client.request("unknown_method", rpc_params![]).await; + let _: Result = client.request("thready", rpc_params![4]).await; + let _: Result = client.request("mul", rpc_params![4]).await; + let _: Result = client.request("err", rpc_params![4]).await; + + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + } + + println!("Metrics: {:?}", metrics.lock().unwrap()); + + Ok(()) +} + +async fn run_server() -> anyhow::Result { + let server = Server::builder().build("127.0.0.1:0").await?; + let mut module = RpcModule::new(()); + module.register_method("say_hello", |_, _, _| "lo")?; + module.register_method("mul", |params, _, _| { + let count: usize = params.one().unwrap(); + count * 2 + })?; + module.register_method("error", |_, _, _| ErrorObject::from(ErrorCode::InternalError))?; + let addr = server.local_addr()?; + let handle = server.start(module); + + // In this example we don't care about doing shutdown so let's it run forever. + // You may use the `ServerHandle` to shut it down or manage it yourself. + tokio::spawn(handle.stopped()); + + Ok(addr) +} From 38b5261f9b2bd7d1c5ff559ebf5bbac7877f25b1 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 1 Apr 2025 17:01:46 +0200 Subject: [PATCH 28/52] Update examples/examples/rpc_middleware_client.rs --- examples/examples/rpc_middleware_client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/examples/rpc_middleware_client.rs b/examples/examples/rpc_middleware_client.rs index 27d9cc2599..a4f872a138 100644 --- a/examples/examples/rpc_middleware_client.rs +++ b/examples/examples/rpc_middleware_client.rs @@ -77,7 +77,7 @@ impl Metrics { } } -// NOTE: We are MethodResponse as the response type here to be able to inspect the response +// NOTE: We are using MethodResponse as the response type here to be able to inspect the response // and not just the serialized JSON-RPC response. This is not necessary if you only care about // the serialized JSON-RPC response. impl<'a, S> RpcServiceT<'a> for Metrics From 9640517844bbaf188389293a6afb58bf2d191425 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 2 Apr 2025 16:39:07 +0200 Subject: [PATCH 29/52] ToJson -> RawValue --- core/src/client/mod.rs | 12 ++-- core/src/middleware/layer/logger.rs | 6 +- core/src/middleware/mod.rs | 2 +- core/src/server/method_response.rs | 87 +++++++++++++++-------------- core/src/server/rpc_module.rs | 18 +++--- core/src/server/subscription.rs | 4 +- server/src/tests/http.rs | 4 +- server/src/transport/http.rs | 3 +- server/src/transport/ws.rs | 4 +- tests/tests/proc_macros.rs | 6 +- tests/tests/rpc_module.rs | 12 ++-- 11 files changed, 79 insertions(+), 79 deletions(-) diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 9c6476e227..ac486b6bc3 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -784,12 +784,12 @@ impl std::ops::Deref for MethodResponse { } impl ToJson for MethodResponse { - fn to_json(&self) -> Result { + fn to_json(&self) -> Result, serde_json::Error> { match &self.inner { MethodResponseKind::MethodCall(call) => call.to_json(), - MethodResponseKind::Notification => Ok(String::new()), - MethodResponseKind::Batch(json) => serde_json::to_string(json), - MethodResponseKind::Subscription(s) => serde_json::to_string(&s.rp), + MethodResponseKind::Notification => Ok(RawValue::NULL.to_owned()), + MethodResponseKind::Batch(json) => serde_json::value::to_raw_value(json), + MethodResponseKind::Subscription(s) => serde_json::value::to_raw_value(&s.rp), } } } @@ -860,7 +860,7 @@ impl<'a> RawResponse<'a> { } impl ToJson for RawResponse<'_> { - fn to_json(&self) -> Result { - serde_json::to_string(&self.0) + fn to_json(&self) -> Result, serde_json::Error> { + serde_json::value::to_raw_value(&self.0) } } diff --git a/core/src/middleware/layer/logger.rs b/core/src/middleware/layer/logger.rs index e03fa2effc..186d32e9c1 100644 --- a/core/src/middleware/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -137,9 +137,9 @@ where match fut.poll(cx) { Poll::Ready(Ok(rp)) => { - let as_json = rp.to_json(); - let json = as_json.as_ref().map_or("", |j| j.as_str()); - tracing::trace!(target: "jsonrpsee", "response = {}", truncate_at_char_boundary(json, max as usize)); + let json = rp.to_json(); + let json_str = json.as_ref().map_or("", |j| j.get()); + tracing::trace!(target: "jsonrpsee", "response = {}", truncate_at_char_boundary(json_str, max as usize)); Poll::Ready(Ok(rp)) } Poll::Ready(Err(e)) => Poll::Ready(Err(e)), diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index b110af94de..7ce6c58ae2 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -237,7 +237,7 @@ pub trait RpcServiceT<'a> { /// Interface for types that can be serialized into JSON. pub trait ToJson { /// Convert the type into a JSON value. - fn to_json(&self) -> Result; + fn to_json(&self) -> Result, serde_json::Error>; } /// Similar to [`tower::ServiceBuilder`] but doesn't diff --git a/core/src/server/method_response.rs b/core/src/server/method_response.rs index c554ede87e..fc3d8aece8 100644 --- a/core/src/server/method_response.rs +++ b/core/src/server/method_response.rs @@ -38,7 +38,7 @@ use jsonrpsee_types::error::{ }; use jsonrpsee_types::{ErrorObjectOwned, Id, Response, ResponsePayload as InnerResponsePayload}; use serde::Serialize; -use serde_json::value::to_raw_value; +use serde_json::value::{RawValue, to_raw_value}; use crate::middleware::ToJson; @@ -59,7 +59,7 @@ enum ResponseKind { #[derive(Debug)] pub struct MethodResponse { /// Serialized JSON-RPC response, - result: String, + json: Box, /// Indicates whether the call was successful or not. success_or_error: MethodResponseResult, /// Indicates whether the call was a subscription response. @@ -73,19 +73,19 @@ pub struct MethodResponse { impl AsRef for MethodResponse { fn as_ref(&self) -> &str { - self.as_result() + self.json.get() } } impl ToJson for MethodResponse { - fn to_json(&self) -> Result { - Ok(self.result.clone()) + fn to_json(&self) -> Result, serde_json::Error> { + Ok(self.json.clone()) } } impl std::fmt::Display for MethodResponse { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.result) + write!(f, "{}", self.json) } } @@ -120,19 +120,19 @@ impl MethodResponse { matches!(self.kind, ResponseKind::Batch) } - /// Consume the method response and extract the serialized response. - pub fn into_result(self) -> String { - self.result + /// Consume the method response and extract the serialized JSON response. + pub fn into_json(self) -> Box { + self.json } - /// Extract the serialized response as a String. - pub fn to_result(&self) -> String { - self.result.clone() + /// Get the serialized JSON response. + pub fn to_json(&self) -> Box { + self.json.clone() } /// Consume the method response and extract the parts. - pub fn into_parts(self) -> (String, Option, Extensions) { - (self.result, self.on_close, self.extensions) + pub fn into_parts(self) -> (Box, Option, Extensions) { + (self.json, self.on_close, self.extensions) } /// Get the error code @@ -142,15 +142,15 @@ impl MethodResponse { self.success_or_error.as_error_code() } - /// Get a reference to the serialized response. - pub fn as_result(&self) -> &str { - &self.result + /// Get a reference to the serialized JSON response. + pub fn as_json(&self) -> &RawValue { + &self.json } /// Create a method response from [`BatchResponse`]. pub fn from_batch(batch: BatchResponse) -> Self { Self { - result: batch.result, + json: batch.json, success_or_error: MethodResponseResult::Success, kind: ResponseKind::Batch, on_close: None, @@ -191,8 +191,9 @@ impl MethodResponse { Ok(_) => { // Safety - serde_json does not emit invalid UTF-8. let result = unsafe { String::from_utf8_unchecked(writer.into_bytes()) }; + let json = RawValue::from_string(result).expect("JSON serialization infallible; qed"); - Self { result, success_or_error, kind, on_close: rp.on_exit, extensions: Extensions::new() } + Self { json, success_or_error, kind, on_close: rp.on_exit, extensions: Extensions::new() } } Err(err) => { tracing::error!(target: LOG_TARGET, "Error serializing response: {:?}", err); @@ -206,11 +207,11 @@ impl MethodResponse { OVERSIZED_RESPONSE_MSG, data.as_deref(), )); - let result = - serde_json::to_string(&Response::new(err, id)).expect("JSON serialization infallible; qed"); + let json = serde_json::value::to_raw_value(&Response::new(err, id)) + .expect("JSON serialization infallible; qed"); Self { - result, + json, success_or_error: MethodResponseResult::Failed(err_code), kind, on_close: rp.on_exit, @@ -219,10 +220,10 @@ impl MethodResponse { } else { let err = ErrorCode::InternalError; let payload = jsonrpsee_types::ResponsePayload::<()>::error(err); - let result = - serde_json::to_string(&Response::new(payload, id)).expect("JSON serialization infallible; qed"); + let json = serde_json::value::to_raw_value(&Response::new(payload, id)) + .expect("JSON serialization infallible; qed"); Self { - result, + json, success_or_error: MethodResponseResult::Failed(err.code()), kind, on_close: rp.on_exit, @@ -246,9 +247,10 @@ impl MethodResponse { let err: ErrorObject = err.into(); let err_code = err.code(); let err = InnerResponsePayload::<()>::error_borrowed(err); - let result = serde_json::to_string(&Response::new(err, id)).expect("JSON serialization infallible; qed"); + let json = + serde_json::value::to_raw_value(&Response::new(err, id)).expect("JSON serialization infallible; qed"); Self { - result, + json, success_or_error: MethodResponseResult::Failed(err_code), kind: ResponseKind::MethodCall, on_close: None, @@ -259,7 +261,7 @@ impl MethodResponse { /// Create notification response which is a response that doesn't expect a reply. pub fn notification() -> Self { Self { - result: String::new(), + json: RawValue::NULL.to_owned(), success_or_error: MethodResponseResult::Success, kind: ResponseKind::Notification, on_close: None, @@ -341,13 +343,13 @@ impl BatchResponseBuilder { pub fn append(&mut self, response: MethodResponse) -> Result<(), MethodResponse> { // `,` will occupy one extra byte for each entry // on the last item the `,` is replaced by `]`. - let len = response.result.len() + self.result.len() + 1; + let len = response.json.get().len() + self.result.len() + 1; self.extensions.extend(response.extensions); if len > self.max_response_size { Err(MethodResponse::error(Id::Null, reject_too_big_batch_response(self.max_response_size))) } else { - self.result.push_str(&response.result); + self.result.push_str(response.json.get()); self.result.push(','); Ok(()) } @@ -362,13 +364,14 @@ impl BatchResponseBuilder { pub fn finish(mut self) -> BatchResponse { if self.result.len() == 1 { BatchResponse { - result: batch_response_error(Id::Null, ErrorObject::from(ErrorCode::InvalidRequest)), + json: batch_response_error(Id::Null, ErrorObject::from(ErrorCode::InvalidRequest)), extensions: self.extensions, } } else { self.result.pop(); self.result.push(']'); - BatchResponse { result: self.result, extensions: self.extensions } + let json = RawValue::from_string(self.result).expect("JSON serialization infallible; qed"); + BatchResponse { json, extensions: self.extensions } } } } @@ -376,14 +379,14 @@ impl BatchResponseBuilder { /// Serialized batch response. #[derive(Debug, Clone)] pub struct BatchResponse { - result: String, + json: Box, extensions: Extensions, } /// Create a JSON-RPC error response. -pub fn batch_response_error(id: Id, err: impl Into>) -> String { +pub fn batch_response_error(id: Id, err: impl Into>) -> Box { let err = InnerResponsePayload::<()>::error_borrowed(err); - serde_json::to_string(&Response::new(err, id)).expect("JSON serialization infallible; qed") + serde_json::value::to_raw_value(&Response::new(err, id)).expect("JSON serialization infallible; qed") } /// Similar to [`jsonrpsee_types::ResponsePayload`] but possible to with an async-like @@ -553,21 +556,21 @@ mod tests { #[test] fn batch_with_single_works() { let method = MethodResponse::response(Id::Number(1), ResponsePayload::success_borrowed(&"a"), usize::MAX); - assert_eq!(method.result.len(), 37); + assert_eq!(method.json.get().len(), 37); // Recall a batch appends two bytes for the `[]`. let mut builder = BatchResponseBuilder::new_with_limit(39); builder.append(method).unwrap(); let batch = builder.finish(); - assert_eq!(batch.result, r#"[{"jsonrpc":"2.0","id":1,"result":"a"}]"#) + assert_eq!(batch.json.get(), r#"[{"jsonrpc":"2.0","id":1,"result":"a"}]"#) } #[test] fn batch_with_multiple_works() { let m1 = MethodResponse::response(Id::Number(1), ResponsePayload::success_borrowed(&"a"), usize::MAX); let m11 = MethodResponse::response(Id::Number(1), ResponsePayload::success_borrowed(&"a"), usize::MAX); - assert_eq!(m1.result.len(), 37); + assert_eq!(m1.json.get().len(), 37); // Recall a batch appends two bytes for the `[]` and one byte for `,` to append a method call. // so it should be 2 + (37 * n) + (n-1) @@ -577,7 +580,7 @@ mod tests { builder.append(m11).unwrap(); let batch = builder.finish(); - assert_eq!(batch.result, r#"[{"jsonrpc":"2.0","id":1,"result":"a"},{"jsonrpc":"2.0","id":1,"result":"a"}]"#) + assert_eq!(batch.json.get(), r#"[{"jsonrpc":"2.0","id":1,"result":"a"},{"jsonrpc":"2.0","id":1,"result":"a"}]"#) } #[test] @@ -585,18 +588,18 @@ mod tests { let batch = BatchResponseBuilder::new_with_limit(1024).finish(); let exp_err = r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Invalid request"}}"#; - assert_eq!(batch.result, exp_err); + assert_eq!(batch.json.get(), exp_err); } #[test] fn batch_too_big() { let method = MethodResponse::response(Id::Number(1), ResponsePayload::success_borrowed(&"a".repeat(28)), 128); - assert_eq!(method.result.len(), 64); + assert_eq!(method.json.get().len(), 64); let batch = BatchResponseBuilder::new_with_limit(63).append(method).unwrap_err(); let exp_err = r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32011,"message":"The batch response was too large","data":"Exceeded max limit of 63"}}"#; - assert_eq!(batch.result, exp_err); + assert_eq!(batch.json.get(), exp_err); } #[test] diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 097edbbbbc..38937f13a8 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -48,6 +48,7 @@ use jsonrpsee_types::{ }; use rustc_hash::FxHashMap; use serde::de::DeserializeOwned; +use serde_json::value::RawValue; use tokio::sync::{mpsc, oneshot}; use super::IntoResponse; @@ -94,7 +95,7 @@ pub type MaxResponseSize = usize; /// A tuple containing: /// - Call result as a `String`, /// - a [`mpsc::UnboundedReceiver`] to receive future subscription results -pub type RawRpcResponse = (String, mpsc::Receiver); +pub type RawRpcResponse = (Box, mpsc::Receiver); /// The error that can occur when [`Methods::call`] or [`Methods::subscribe`] is invoked. #[derive(thiserror::Error, Debug)] @@ -310,7 +311,7 @@ impl Methods { tracing::trace!(target: LOG_TARGET, "[Methods::call] Method: {:?}, params: {:?}", method, params); let (rp, _) = self.inner_call(req, 1, mock_subscription_permit()).await; - let rp = serde_json::from_str::>(&rp)?; + let rp = serde_json::from_str::>(rp.get())?; ResponseSuccess::try_from(rp).map(|s| s.result).map_err(|e| MethodsError::JsonRpc(e.into_owned())) } @@ -338,7 +339,7 @@ impl Methods { /// }).unwrap(); /// let (resp, mut stream) = module.raw_json_request(r#"{"jsonrpc":"2.0","method":"hi","id":0}"#, 1).await.unwrap(); /// // If the response is an error converting it to `Success` will fail. - /// let resp: Success = serde_json::from_str::>(&resp).unwrap().try_into().unwrap(); + /// let resp: Success = serde_json::from_str::>(resp.get()).unwrap().try_into().unwrap(); /// let sub_resp = stream.recv().await.unwrap(); /// assert_eq!( /// format!(r#"{{"jsonrpc":"2.0","method":"hi","params":{{"subscription":{},"result":"one answer"}}}}"#, resp.result), @@ -350,7 +351,7 @@ impl Methods { &self, request: &str, buf_size: usize, - ) -> Result<(String, mpsc::Receiver), serde_json::Error> { + ) -> Result<(Box, mpsc::Receiver), serde_json::Error> { tracing::trace!("[Methods::raw_json_request] Request: {:?}", request); let req: Request = serde_json::from_str(request)?; let (resp, rx) = self.inner_call(req, buf_size, mock_subscription_permit()).await; @@ -459,13 +460,10 @@ impl Methods { tracing::trace!(target: LOG_TARGET, "[Methods::subscribe] Method: {}, params: {:?}", sub_method, params); let (resp, rx) = self.inner_call(req, buf_size, mock_subscription_permit()).await; + let as_success: ResponseSuccess<&RawValue> = serde_json::from_str::>(resp.get())?.try_into()?; + let sub_id: RpcSubscriptionId = serde_json::from_str(as_success.result.get())?; - // TODO: hack around the lifetime on the `SubscriptionId` by deserialize first to serde_json::Value. - let as_success: ResponseSuccess = serde_json::from_str::>(&resp)?.try_into()?; - - let sub_id = as_success.result.try_into().map_err(|_| MethodsError::InvalidSubscriptionId(resp.clone()))?; - - Ok(Subscription { sub_id, rx }) + Ok(Subscription { sub_id: sub_id.into_owned(), rx }) } /// Returns an `Iterator` with all the method names registered on this server. diff --git a/core/src/server/subscription.rs b/core/src/server/subscription.rs index fed1874cb3..6ffd5f3c71 100644 --- a/core/src/server/subscription.rs +++ b/core/src/server/subscription.rs @@ -259,7 +259,7 @@ impl PendingSubscriptionSink { /// once reject has been called. pub async fn reject(self, err: impl Into) { let err = MethodResponse::subscription_error(self.id, err.into()); - _ = self.inner.send(err.to_result()).await; + _ = self.inner.send(err.as_json().get().to_owned()).await; _ = self.subscribe.send(err); } @@ -282,7 +282,7 @@ impl PendingSubscriptionSink { // // The same message is sent twice here because one is sent directly to the transport layer and // the other one is sent internally to accept the subscription. - self.inner.send(response.to_result()).await.map_err(|_| PendingSubscriptionAcceptError)?; + self.inner.send(response.as_json().get().to_owned()).await.map_err(|_| PendingSubscriptionAcceptError)?; self.subscribe.send(response).map_err(|_| PendingSubscriptionAcceptError)?; if success { diff --git a/server/src/tests/http.rs b/server/src/tests/http.rs index 19f460bc6f..f84dd55905 100644 --- a/server/src/tests/http.rs +++ b/server/src/tests/http.rs @@ -314,7 +314,7 @@ async fn batched_notifications() { let response = http_request(req.into(), uri).with_default_timeout().await.unwrap().unwrap(); assert_eq!(response.status, StatusCode::OK); // Note: on HTTP acknowledge the notification with an empty response. - assert_eq!(response.body, ""); + assert_eq!(response.body, "null"); } #[tokio::test] @@ -510,7 +510,7 @@ async fn notif_works() { let req = r#"{"jsonrpc":"2.0","method":"bar"}"#; let response = http_request(req.into(), uri).with_default_timeout().await.unwrap().unwrap(); assert_eq!(response.status, StatusCode::OK); - assert_eq!(response.body, ""); + assert_eq!(response.body, "null"); } #[tokio::test] diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index 31268bc2d6..ee45312faa 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -189,8 +189,7 @@ pub mod response { /// This will include the body and extensions from the method response. pub fn from_method_response(rp: MethodResponse) -> HttpResponse { let (body, _, extensions) = rp.into_parts(); - - let mut rp = from_template(hyper::StatusCode::OK, body, JSON); + let mut rp = from_template(hyper::StatusCode::OK, String::from(Box::::from(body)), JSON); rp.extensions_mut().extend(extensions); rp } diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 73c68f84b5..d9853b42d1 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -164,10 +164,10 @@ where // "ordinary notifications" should not be sent back to the client. if rp.is_method_call() || rp.is_batch() { let is_success = rp.is_success(); - let (serialized_rp, mut on_close, _) = rp.into_parts(); + let (json, mut on_close, _) = rp.into_parts(); // The connection is closed, just quit. - if sink.send(serialized_rp).await.is_err() { + if sink.send(String::from(Box::::from(json))).await.is_err() { return; } diff --git a/tests/tests/proc_macros.rs b/tests/tests/proc_macros.rs index dbff0c70a5..474f1cf7ec 100644 --- a/tests/tests/proc_macros.rs +++ b/tests/tests/proc_macros.rs @@ -262,7 +262,7 @@ async fn macro_optional_param_parsing() { .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_optional_params","params":{"a":22,"c":50},"id":0}"#, 1) .await .unwrap(); - assert_eq!(resp, r#"{"jsonrpc":"2.0","id":0,"result":"Called with: 22, None, Some(50)"}"#); + assert_eq!(resp.get(), r#"{"jsonrpc":"2.0","id":0,"result":"Called with: 22, None, Some(50)"}"#); } #[tokio::test] @@ -286,14 +286,14 @@ async fn macro_zero_copy_cow() { .unwrap(); // std::borrow::Cow always deserialized to owned variant here - assert_eq!(resp, r#"{"jsonrpc":"2.0","id":0,"result":"Zero copy params: false"}"#); + assert_eq!(resp.get(), r#"{"jsonrpc":"2.0","id":0,"result":"Zero copy params: false"}"#); // serde_json will have to allocate a new string to replace `\t` with byte 0x09 (tab) let (resp, _) = module .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_zero_copy_cow","params":["\tfoo"],"id":0}"#, 1) .await .unwrap(); - assert_eq!(resp, r#"{"jsonrpc":"2.0","id":0,"result":"Zero copy params: false"}"#); + assert_eq!(resp.get(), r#"{"jsonrpc":"2.0","id":0,"result":"Zero copy params: false"}"#); } // Disabled on MacOS as GH CI timings on Mac vary wildly (~100ms) making this test fail. diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index 7f7e2d1e4b..ccbc8fb1cb 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -383,13 +383,13 @@ async fn subscribe_unsubscribe_without_server() { let unsub_req = format!("{{\"jsonrpc\":\"2.0\",\"method\":\"my_unsub\",\"params\":[{}],\"id\":1}}", ser_id); let (resp, _) = module.raw_json_request(&unsub_req, 1).await.unwrap(); - assert_eq!(resp, r#"{"jsonrpc":"2.0","id":1,"result":true}"#); + assert_eq!(resp.get(), r#"{"jsonrpc":"2.0","id":1,"result":true}"#); // Unsubscribe already performed; should be error. let unsub_req = format!("{{\"jsonrpc\":\"2.0\",\"method\":\"my_unsub\",\"params\":[{}],\"id\":1}}", ser_id); let (resp, _) = module.raw_json_request(&unsub_req, 2).await.unwrap(); - assert_eq!(resp, r#"{"jsonrpc":"2.0","id":1,"result":false}"#); + assert_eq!(resp.get(), r#"{"jsonrpc":"2.0","id":1,"result":false}"#); } let sub1 = subscribe_and_assert(&module); @@ -429,7 +429,7 @@ async fn reject_works() { .unwrap(); let (rp, mut stream) = module.raw_json_request(r#"{"jsonrpc":"2.0","method":"my_sub","id":0}"#, 1).await.unwrap(); - assert_eq!(rp, r#"{"jsonrpc":"2.0","id":0,"error":{"code":-32700,"message":"rejected"}}"#); + assert_eq!(rp.get(), r#"{"jsonrpc":"2.0","id":0,"error":{"code":-32700,"message":"rejected"}}"#); assert!(stream.recv().await.is_none()); } @@ -520,7 +520,7 @@ async fn serialize_sub_error_adds_extra_string_quotes() { .unwrap(); let (rp, mut stream) = module.raw_json_request(r#"{"jsonrpc":"2.0","method":"my_sub","id":0}"#, 1).await.unwrap(); - let resp = serde_json::from_str::>(&rp).unwrap(); + let resp = serde_json::from_str::>(rp.get()).unwrap(); let sub_resp = stream.recv().await.unwrap(); let resp = match resp.payload { @@ -565,7 +565,7 @@ async fn subscription_close_response_works() { { let (rp, mut stream) = module.raw_json_request(r#"{"jsonrpc":"2.0","method":"my_sub","params":[1],"id":0}"#, 1).await.unwrap(); - let resp = serde_json::from_str::>(&rp).unwrap(); + let resp = serde_json::from_str::>(rp.get()).unwrap(); let sub_id = match resp.payload { ResponsePayload::Success(val) => val, @@ -629,7 +629,7 @@ async fn method_response_notify_on_completion() { // Low level call should also work. let (rp, _) = module.raw_json_request(r#"{"jsonrpc":"2.0","method":"hey","params":["success"],"id":0}"#, 1).await.unwrap(); - assert_eq!(rp, r#"{"jsonrpc":"2.0","id":0,"result":"lo"}"#); + assert_eq!(rp.get(), r#"{"jsonrpc":"2.0","id":0,"result":"lo"}"#); assert!(matches!(rx.recv().await, Some(Ok(_)))); // Error call should return a failed notification. From b051bd697703059811bed5d878953f5eb8c96f43 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 2 Apr 2025 17:40:42 +0200 Subject: [PATCH 30/52] replace Future type with impl Trait --- CHANGELOG.md | 6 +- client/http-client/src/client.rs | 6 +- client/http-client/src/rpc_service.rs | 14 +++-- core/src/client/async_client/mod.rs | 4 +- core/src/client/async_client/rpc_service.rs | 14 +++-- core/src/middleware/layer/either.rs | 16 ++--- core/src/middleware/layer/logger.rs | 16 ++--- core/src/middleware/mod.rs | 14 ++--- examples/examples/http.rs | 20 ++++-- examples/examples/jsonrpsee_as_service.rs | 22 ++++--- .../jsonrpsee_server_low_level_api.rs | 21 ++++--- examples/examples/rpc_middleware.rs | 63 +++++++++++++------ examples/examples/rpc_middleware_client.rs | 21 ++++--- .../examples/rpc_middleware_modify_request.rs | 20 ++++-- .../examples/rpc_middleware_rate_limiting.rs | 23 ++++--- .../server_with_connection_details.rs | 18 ++++-- proc-macros/tests/ui/correct/basic.rs | 4 +- server/src/middleware/rpc.rs | 14 ++--- server/src/server.rs | 16 ++--- server/src/tests/http.rs | 16 ++--- server/src/transport/http.rs | 8 +-- server/src/transport/ws.rs | 8 +-- tests/tests/helpers.rs | 20 ++++-- tests/tests/metrics.rs | 21 ++++--- 24 files changed, 252 insertions(+), 153 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cb807e043..dbc0ad9697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -197,7 +197,7 @@ and it is possible to fetch it as follows: ```rust struct LogConnectionId(S); -impl<'a, S: RpcServiceT<'a>> RpcServiceT<'a> for LogConnectionId { +impl<'a, S: RpcServiceT> RpcServiceT for LogConnectionId { type Future = S::Future; fn call(&self, request: jsonrpsee::types::Request<'a>) -> Self::Future { @@ -435,9 +435,9 @@ An example how write such middleware: #[derive(Clone)] pub struct ModifyRequestIf(S); -impl<'a, S> RpcServiceT<'a> for ModifyRequestIf +impl<'a, S> RpcServiceT for ModifyRequestIf where - S: Send + Sync + RpcServiceT<'a>, + S: Send + Sync + RpcServiceT, { type Future = S::Future; diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 9c59b76e10..49d1042ac1 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -249,7 +249,7 @@ impl HttpClientBuilder HttpClientBuilder where RpcMiddleware: Layer, Service = S2>, - for<'a> >>::Service: RpcServiceT<'a>, + for<'a> >>::Service: RpcServiceT, HttpMiddleware: Layer, S: Service, Error = TransportError> + Clone, B: http_body::Body + Send + Unpin + 'static, @@ -351,7 +351,7 @@ impl HttpClient { #[async_trait] impl ClientT for HttpClient where - for<'a> S: RpcServiceT<'a, Error = Error, Response = MethodResponse> + Send + Sync, + S: RpcServiceT + Send + Sync, { async fn notification(&self, method: &str, params: Params) -> Result<(), Error> where @@ -458,7 +458,7 @@ where #[async_trait] impl SubscriptionClientT for HttpClient where - for<'a> S: RpcServiceT<'a, Error = Error, Response = MethodResponse> + Send + Sync, + S: RpcServiceT + Send + Sync, { /// Send a subscription request to the server. Not implemented for HTTP; will always return /// [`Error::HttpNotImplemented`]. diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index ac1723cb5b..bc7a13075b 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use futures_util::{FutureExt, future::BoxFuture}; +use futures_util::FutureExt; use hyper::{body::Bytes, http::Extensions}; use jsonrpsee_core::{ BoxError, JsonRawValue, @@ -26,7 +26,7 @@ impl RpcService { } } -impl<'a, B, HttpMiddleware> RpcServiceT<'a> for RpcService +impl RpcServiceT for RpcService where HttpMiddleware: Service, Error = TransportError> + Clone + Send + Sync + 'static, @@ -35,11 +35,10 @@ where B::Data: Send, B::Error: Into, { - type Future = BoxFuture<'a, Result>; type Error = Error; type Response = MethodResponse; - fn call(&self, request: Request<'a>) -> Self::Future { + fn call<'a>(&self, request: Request<'a>) -> impl Future> + Send + 'a { let service = self.service.clone(); async move { @@ -51,7 +50,7 @@ where .boxed() } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { let service = self.service.clone(); async move { @@ -73,7 +72,10 @@ where .boxed() } - fn notification(&self, notif: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + notif: Notification<'a>, + ) -> impl Future> + Send + 'a { let service = self.service.clone(); async move { diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index 236e9ba91f..99bde60c05 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -491,7 +491,7 @@ impl Drop for Client { #[async_trait] impl ClientT for Client where - for<'a> L: RpcServiceT<'a, Error = RpcServiceError, Response = MethodResponse> + Send + Sync, + L: RpcServiceT + Send + Sync, { async fn notification(&self, method: &str, params: Params) -> Result<(), Error> where @@ -568,7 +568,7 @@ where #[async_trait] impl SubscriptionClientT for Client where - for<'a> L: RpcServiceT<'a, Error = RpcServiceError, Response = MethodResponse> + Send + Sync, + L: RpcServiceT + Send + Sync, { /// Send a subscription request to the server. /// diff --git a/core/src/client/async_client/rpc_service.rs b/core/src/client/async_client/rpc_service.rs index 2e17e24b60..740428d079 100644 --- a/core/src/client/async_client/rpc_service.rs +++ b/core/src/client/async_client/rpc_service.rs @@ -5,7 +5,7 @@ use crate::{ BatchMessage, Error as ClientError, FrontToBack, MethodResponse, RequestMessage, SubscriptionMessage, SubscriptionResponse, async_client::helpers::call_with_timeout, }, - middleware::{Batch, IsSubscription, Notification, Request, ResponseBoxFuture, RpcServiceT}, + middleware::{Batch, IsSubscription, Notification, Request, RpcServiceT}, }; use futures_timer::Delay; @@ -59,12 +59,11 @@ impl RpcService { } } -impl<'a> RpcServiceT<'a> for RpcService { - type Future = ResponseBoxFuture<'a, Self::Response, Self::Error>; +impl RpcServiceT for RpcService { type Response = MethodResponse; type Error = Error; - fn call(&self, request: Request<'a>) -> Self::Future { + fn call<'a>(&self, request: Request<'a>) -> impl Future> + Send + 'a { let tx = self.tx.clone(); let request_timeout = self.request_timeout; @@ -116,7 +115,7 @@ impl<'a> RpcServiceT<'a> for RpcService { .boxed() } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { let tx = self.tx.clone(); let request_timeout = self.request_timeout; @@ -142,7 +141,10 @@ impl<'a> RpcServiceT<'a> for RpcService { .boxed() } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future> + Send + 'a { let tx = self.tx.clone(); let request_timeout = self.request_timeout; diff --git a/core/src/middleware/layer/either.rs b/core/src/middleware/layer/either.rs index feb55565d7..81fad8cf01 100644 --- a/core/src/middleware/layer/either.rs +++ b/core/src/middleware/layer/either.rs @@ -59,30 +59,32 @@ where } } -impl<'a, A, B> RpcServiceT<'a> for Either +impl RpcServiceT for Either where - A: RpcServiceT<'a> + Send + 'a, - B: RpcServiceT<'a, Error = A::Error, Response = A::Response> + Send + 'a, + A: RpcServiceT + Send, + B: RpcServiceT + Send, { - type Future = futures_util::future::Either; type Error = A::Error; type Response = A::Response; - fn call(&self, request: Request<'a>) -> Self::Future { + fn call<'a>(&self, request: Request<'a>) -> impl Future> + Send + 'a { match self { Either::Left(service) => futures_util::future::Either::Left(service.call(request)), Either::Right(service) => futures_util::future::Either::Right(service.call(request)), } } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { match self { Either::Left(service) => futures_util::future::Either::Left(service.batch(batch)), Either::Right(service) => futures_util::future::Either::Right(service.batch(batch)), } } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future> + Send + 'a { match self { Either::Left(service) => futures_util::future::Either::Left(service.notification(n)), Either::Right(service) => futures_util::future::Either::Right(service.notification(n)), diff --git a/core/src/middleware/layer/logger.rs b/core/src/middleware/layer/logger.rs index 186d32e9c1..edea76f3d3 100644 --- a/core/src/middleware/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -39,7 +39,7 @@ use crate::{ use futures_util::Future; use jsonrpsee_types::Request; use pin_project::pin_project; -use tracing::{Instrument, instrument::Instrumented}; +use tracing::Instrument; /// RPC logger layer. #[derive(Copy, Clone, Debug)] @@ -67,18 +67,17 @@ pub struct RpcLogger { service: S, } -impl<'a, S> RpcServiceT<'a> for RpcLogger +impl RpcServiceT for RpcLogger where - S: RpcServiceT<'a>, + S: RpcServiceT, S::Error: std::fmt::Debug + Send, S::Response: ToJson, { - type Future = Instrumented>; type Error = S::Error; type Response = S::Response; #[tracing::instrument(name = "method_call", skip_all, fields(method = request.method_name()), level = "trace")] - fn call(&self, request: Request<'a>) -> Self::Future { + fn call<'a>(&self, request: Request<'a>) -> impl Future> + Send + 'a { let json = serde_json::to_string(&request).unwrap_or_default(); tracing::trace!(target: "jsonrpsee", "request = {}", truncate_at_char_boundary(&json, self.max as usize)); @@ -86,7 +85,7 @@ where } #[tracing::instrument(name = "batch", skip_all, fields(method = "batch"), level = "trace")] - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { let json = serde_json::to_string(&batch).unwrap_or_default(); tracing::trace!(target: "jsonrpsee", "batch request = {}", truncate_at_char_boundary(&json, self.max as usize)); @@ -94,7 +93,10 @@ where } #[tracing::instrument(name = "notification", skip_all, fields(method = &*n.method), level = "trace")] - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future> + Send + 'a { let json = serde_json::to_string(&n).unwrap_or_default(); tracing::trace!(target: "jsonrpsee", "notification = {}", truncate_at_char_boundary(&json, self.max as usize)); diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 7ce6c58ae2..310d2da2ce 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -206,10 +206,7 @@ impl BatchEntry<'_> { /// /// In the server implementation, the error is infallible but in the client implementation, the error /// can occur due to I/O errors or JSON-RPC protocol errors. -pub trait RpcServiceT<'a> { - /// The future response value. - type Future: Future> + Send; - +pub trait RpcServiceT { /// The error type. type Error: std::fmt::Debug; @@ -217,7 +214,7 @@ pub trait RpcServiceT<'a> { type Response: ToJson; /// Processes a single JSON-RPC call, which may be a subscription or regular call. - fn call(&self, request: Request<'a>) -> Self::Future; + fn call<'a>(&self, request: Request<'a>) -> impl Future> + Send + 'a; /// Processes multiple JSON-RPC calls at once, similar to `RpcServiceT::call`. /// @@ -228,10 +225,13 @@ pub trait RpcServiceT<'a> { /// As a result, if you have custom logic for individual calls or notifications, /// you must duplicate that implementation in this method or no middleware will be applied /// for calls inside the batch. - fn batch(&self, requests: Batch<'a>) -> Self::Future; + fn batch<'a>(&self, requests: Batch<'a>) -> impl Future> + Send + 'a; /// Similar to `RpcServiceT::call` but processes a JSON-RPC notification. - fn notification(&self, n: Notification<'a>) -> Self::Future; + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future> + Send + 'a; } /// Interface for types that can be serialized into JSON. diff --git a/examples/examples/http.rs b/examples/examples/http.rs index 87bb39e0bc..84d4f2fef8 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -40,25 +40,33 @@ use tracing_subscriber::util::SubscriberInitExt; struct Logger(S); -impl<'a, S> RpcServiceT<'a> for Logger +impl RpcServiceT for Logger where - S: RpcServiceT<'a>, + S: RpcServiceT, { - type Future = S::Future; type Error = S::Error; type Response = S::Response; - fn call(&self, req: Request<'a>) -> Self::Future { + fn call<'a>( + &self, + req: Request<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { println!("logger layer : {:?}", req); self.0.call(req) } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>( + &self, + batch: Batch<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { println!("logger layer : {:?}", batch); self.0.batch(batch) } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { println!("logger layer : {:?}", n); self.0.notification(n) } diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index 121ba9951b..72ade9be44 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -70,17 +70,19 @@ struct AuthorizationMiddleware { transport_label: &'static str, } -impl<'a, S> RpcServiceT<'a> for AuthorizationMiddleware +impl RpcServiceT for AuthorizationMiddleware where - S: Send + Clone + Sync + RpcServiceT<'a, Response = MethodResponse>, - S::Error: Into + Send, + S: Send + Clone + Sync + RpcServiceT, + S::Error: Into + Send + 'static, S::Response: Send, { - type Future = ResponseFuture; type Error = S::Error; type Response = S::Response; - fn call(&self, req: Request<'a>) -> Self::Future { + fn call<'a>( + &self, + req: Request<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { if req.method_name() == "trusted_call" { let Some(Ok(_)) = self.headers.get(AUTHORIZATION).map(|auth| auth.to_str()) else { let rp = MethodResponse::error(req.id, ErrorObject::borrowed(-32000, "Authorization failed", None)); @@ -96,11 +98,17 @@ where } } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>( + &self, + batch: Batch<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { ResponseFuture::future(self.inner.batch(batch)) } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { ResponseFuture::future(self.inner.notification(n)) } } diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index 9ef1f815e6..aebd334d33 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -45,7 +45,6 @@ use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; use futures::FutureExt; -use futures::future::BoxFuture; use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::core::{BoxError, async_trait}; use jsonrpsee::http_client::HttpClient; @@ -73,16 +72,18 @@ struct CallLimit { state: mpsc::Sender<()>, } -impl<'a, S> RpcServiceT<'a> for CallLimit +impl RpcServiceT for CallLimit where - S: Send + Sync + RpcServiceT<'a, Response = MethodResponse> + Clone + 'static, + S: Send + Sync + RpcServiceT + Clone + 'static, S::Error: Into, { - type Future = BoxFuture<'a, Result>; type Error = S::Error; type Response = S::Response; - fn call(&self, req: Request<'a>) -> Self::Future { + fn call<'a>( + &self, + req: Request<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { let count = self.count.clone(); let state = self.state.clone(); let service = self.service.clone(); @@ -102,11 +103,17 @@ where .boxed() } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>( + &self, + batch: Batch<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { Box::pin(self.service.batch(batch)) } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { Box::pin(self.service.notification(n)) } } diff --git a/examples/examples/rpc_middleware.rs b/examples/examples/rpc_middleware.rs index bbc03a51eb..3e2242c75b 100644 --- a/examples/examples/rpc_middleware.rs +++ b/examples/examples/rpc_middleware.rs @@ -47,9 +47,8 @@ use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use futures::FutureExt; -use futures::future::BoxFuture; use jsonrpsee::core::client::ClientT; -use jsonrpsee::core::middleware::{Batch, Notification, ResponseBoxFuture, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, Server}; use jsonrpsee::types::Request; @@ -64,15 +63,17 @@ pub struct CallsPerConn { role: &'static str, } -impl<'a, S> RpcServiceT<'a> for CallsPerConn +impl RpcServiceT for CallsPerConn where - S: RpcServiceT<'a> + Send + Sync + Clone + 'static, + S: RpcServiceT + Send + Sync + Clone + 'static, { - type Future = BoxFuture<'a, Result>; type Error = S::Error; type Response = S::Response; - fn call(&self, req: Request<'a>) -> Self::Future { + fn call<'a>( + &self, + req: Request<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { let count = self.count.clone(); let service = self.service.clone(); let role = self.role; @@ -86,14 +87,20 @@ where .boxed() } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>( + &self, + batch: Batch<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { let len = batch.as_batch_entries().len(); self.count.fetch_add(len, Ordering::SeqCst); println!("{} processed calls={} on the connection", self.role, self.count.load(Ordering::SeqCst)); Box::pin(self.service.batch(batch)) } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { Box::pin(self.service.notification(n)) } } @@ -105,15 +112,17 @@ pub struct GlobalCalls { role: &'static str, } -impl<'a, S> RpcServiceT<'a> for GlobalCalls +impl RpcServiceT for GlobalCalls where - S: RpcServiceT<'a> + Send + Sync + Clone + 'static, + S: RpcServiceT + Send + Sync + Clone + 'static, { - type Future = ResponseBoxFuture<'a, Self::Response, Self::Error>; type Error = S::Error; type Response = S::Response; - fn call(&self, req: Request<'a>) -> Self::Future { + fn call<'a>( + &self, + req: Request<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { let count = self.count.clone(); let service = self.service.clone(); let role = self.role; @@ -128,14 +137,20 @@ where .boxed() } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>( + &self, + batch: Batch<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { let len = batch.as_batch_entries().len(); self.count.fetch_add(len, Ordering::SeqCst); println!("{}, processed calls={} in total", self.role, self.count.load(Ordering::SeqCst)); Box::pin(self.service.batch(batch)) } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { Box::pin(self.service.notification(n)) } } @@ -146,24 +161,32 @@ pub struct Logger { role: &'static str, } -impl<'a, S> RpcServiceT<'a> for Logger +impl RpcServiceT for Logger where - S: RpcServiceT<'a> + Send + Sync, + S: RpcServiceT + Send + Sync, { - type Future = S::Future; type Error = S::Error; type Response = S::Response; - fn call(&self, req: Request<'a>) -> Self::Future { + fn call<'a>( + &self, + req: Request<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { println!("{} logger middleware: method `{}`", self.role, req.method); self.service.call(req) } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>( + &self, + batch: Batch<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { println!("{} logger middleware: batch {batch}", self.role); self.service.batch(batch) } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { self.service.notification(n) } } diff --git a/examples/examples/rpc_middleware_client.rs b/examples/examples/rpc_middleware_client.rs index a4f872a138..91dccc5316 100644 --- a/examples/examples/rpc_middleware_client.rs +++ b/examples/examples/rpc_middleware_client.rs @@ -38,7 +38,6 @@ use std::ops::Deref; use std::sync::{Arc, Mutex}; use futures::FutureExt; -use futures::future::BoxFuture; use jsonrpsee::core::client::{ClientT, MethodResponse, MethodResponseKind}; use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::rpc_params; @@ -80,15 +79,17 @@ impl Metrics { // NOTE: We are using MethodResponse as the response type here to be able to inspect the response // and not just the serialized JSON-RPC response. This is not necessary if you only care about // the serialized JSON-RPC response. -impl<'a, S> RpcServiceT<'a> for Metrics +impl RpcServiceT for Metrics where - S: RpcServiceT<'a, Response = MethodResponse> + Send + Sync + Clone + 'static, + S: RpcServiceT + Send + Sync + Clone + 'static, { - type Future = BoxFuture<'a, Result>; type Error = S::Error; type Response = S::Response; - fn call(&self, req: Request<'a>) -> Self::Future { + fn call<'a>( + &self, + req: Request<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { let m = self.metrics.clone(); let service = self.service.clone(); @@ -116,12 +117,18 @@ where .boxed() } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>( + &self, + batch: Batch<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { self.metrics.lock().unwrap().batch_calls += 1; Box::pin(self.service.batch(batch)) } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { self.metrics.lock().unwrap().notifications += 1; Box::pin(self.service.notification(n)) } diff --git a/examples/examples/rpc_middleware_modify_request.rs b/examples/examples/rpc_middleware_modify_request.rs index c812f991a9..102d5aabd6 100644 --- a/examples/examples/rpc_middleware_modify_request.rs +++ b/examples/examples/rpc_middleware_modify_request.rs @@ -36,15 +36,17 @@ use std::net::SocketAddr; #[derive(Clone)] pub struct ModifyRequestIf(S); -impl<'a, S> RpcServiceT<'a> for ModifyRequestIf +impl RpcServiceT for ModifyRequestIf where - S: Send + Sync + RpcServiceT<'a>, + S: Send + Sync + RpcServiceT, { - type Future = S::Future; type Error = S::Error; type Response = S::Response; - fn call(&self, mut req: Request<'a>) -> Self::Future { + fn call<'a>( + &self, + mut req: Request<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { // Example how to modify the params in the call. if req.method == "say_hello" { // It's a bit awkward to create new params in the request @@ -60,11 +62,17 @@ where self.0.call(req) } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>( + &self, + batch: Batch<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { self.0.batch(batch) } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { self.0.notification(n) } } diff --git a/examples/examples/rpc_middleware_rate_limiting.rs b/examples/examples/rpc_middleware_rate_limiting.rs index 8414f06626..eaa3cbcd50 100644 --- a/examples/examples/rpc_middleware_rate_limiting.rs +++ b/examples/examples/rpc_middleware_rate_limiting.rs @@ -80,19 +80,18 @@ impl RateLimit { } } -impl<'a, S> RpcServiceT<'a> for RateLimit +impl RpcServiceT for RateLimit where - S: Send + RpcServiceT<'a, Response = MethodResponse> + 'static, + S: Send + RpcServiceT + 'static, S::Error: Send, { - // Instead of `Boxing` the future in this example - // we are using a jsonrpsee's ResponseFuture future - // type to avoid those extra allocations. - type Future = ResponseFuture; type Error = S::Error; type Response = S::Response; - fn call(&self, req: Request<'a>) -> Self::Future { + fn call<'a>( + &self, + req: Request<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { let now = Instant::now(); let is_denied = { @@ -130,11 +129,17 @@ where } } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>( + &self, + batch: Batch<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { ResponseFuture::future(self.service.batch(batch)) } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { ResponseFuture::future(self.service.notification(n)) } } diff --git a/examples/examples/server_with_connection_details.rs b/examples/examples/server_with_connection_details.rs index 9f5ed37e3e..fd8c7c77bb 100644 --- a/examples/examples/server_with_connection_details.rs +++ b/examples/examples/server_with_connection_details.rs @@ -49,23 +49,31 @@ pub trait Rpc { struct LoggingMiddleware(S); -impl<'a, S: RpcServiceT<'a>> RpcServiceT<'a> for LoggingMiddleware { - type Future = S::Future; +impl RpcServiceT for LoggingMiddleware { type Error = S::Error; type Response = S::Response; - fn call(&self, request: Request<'a>) -> Self::Future { + fn call<'a>( + &self, + request: Request<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { tracing::info!("Received request: {:?}", request); assert!(request.extensions().get::().is_some()); self.0.call(request) } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>( + &self, + batch: Batch<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { self.0.batch(batch) } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { self.0.notification(n) } } diff --git a/proc-macros/tests/ui/correct/basic.rs b/proc-macros/tests/ui/correct/basic.rs index e27363265a..c58cbb7ada 100644 --- a/proc-macros/tests/ui/correct/basic.rs +++ b/proc-macros/tests/ui/correct/basic.rs @@ -147,9 +147,9 @@ pub async fn server() -> SocketAddr { connection_id: u32, } - impl<'a, S> RpcServiceT<'a> for ConnectionDetails + impl<'a, S> RpcServiceT for ConnectionDetails where - S: RpcServiceT<'a>, + S: RpcServiceT, { type Future = S::Future; type Error = S::Error; diff --git a/server/src/middleware/rpc.rs b/server/src/middleware/rpc.rs index da85bddc7b..c2b15a33e1 100644 --- a/server/src/middleware/rpc.rs +++ b/server/src/middleware/rpc.rs @@ -76,14 +76,11 @@ impl RpcService { } } -impl<'a> RpcServiceT<'a> for RpcService { - // The rpc module is already boxing the futures and - // it's used to under the hood by the RpcService. - type Future = ResponseBoxFuture<'a, Self::Response, Self::Error>; +impl RpcServiceT for RpcService { type Error = Infallible; type Response = MethodResponse; - fn call(&self, req: Request<'a>) -> Self::Future { + fn call<'a>(&self, req: Request<'a>) -> impl Future> + Send + 'a { let conn_id = self.conn_id; let max_response_body_size = self.max_response_body_size; @@ -150,7 +147,7 @@ impl<'a> RpcServiceT<'a> for RpcService { } } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { let mut batch_rp = BatchResponseBuilder::new_with_limit(self.max_response_body_size); let service = self.clone(); async move { @@ -195,7 +192,10 @@ impl<'a> RpcServiceT<'a> for RpcService { .boxed() } - fn notification(&self, _: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + _: Notification<'a>, + ) -> impl Future> + Send + 'a { async move { Ok(MethodResponse::notification()) }.boxed() } } diff --git a/server/src/server.rs b/server/src/server.rs index 069c6e2714..40fcc73ed1 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -100,7 +100,7 @@ impl Server { impl Server where RpcMiddleware: tower::Layer + Clone + Send + 'static, - for<'a> >::Service: RpcServiceT<'a>, + for<'a> >::Service: RpcServiceT, HttpMiddleware: Layer> + Send + 'static, >>::Service: Send + Clone + Service, Error = BoxError>, @@ -673,8 +673,8 @@ impl Builder { /// count: Arc, /// } /// - /// impl<'a, S> RpcServiceT<'a> for MyMiddleware - /// where S: RpcServiceT<'a> + Send + Sync + Clone + 'static, + /// impl<'a, S> RpcServiceT for MyMiddleware + /// where S: RpcServiceT + Send + Sync + Clone + 'static, /// { /// type Future = ResponseBoxFuture<'a, Self::Response, Self::Error>; /// type Error = S::Error; @@ -937,7 +937,7 @@ impl Service tower::Layer + Clone, >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT<'a>, + for<'a> >::Service: RpcServiceT, HttpMiddleware: Layer> + Send + 'static, >>::Service: Send + Service, Response = HttpResponse, Error = Box<(dyn StdError + Send + Sync + 'static)>>, @@ -974,8 +974,8 @@ impl Service> for TowerServiceNoHttp tower::Layer, >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT<'a, Response = MethodResponse>, - for<'a> <>::Service as RpcServiceT<'a>>::Error: std::fmt::Debug, + >::Service: RpcServiceT, + <>::Service as RpcServiceT>::Error: std::fmt::Debug, Body: http_body::Body + Send + 'static, Body::Error: Into, { @@ -1241,8 +1241,8 @@ pub(crate) async fn handle_rpc_call( extensions: Extensions, ) -> MethodResponse where - for<'a> S: RpcServiceT<'a, Response = MethodResponse> + Send, - for<'a> >::Error: std::fmt::Debug, + S: RpcServiceT + Send, + ::Error: std::fmt::Debug, { // Single request or notification if is_single { diff --git a/server/src/tests/http.rs b/server/src/tests/http.rs index f84dd55905..33e3aa17ee 100644 --- a/server/src/tests/http.rs +++ b/server/src/tests/http.rs @@ -33,7 +33,7 @@ use crate::{ }; use futures_util::future::{Future, FutureExt}; use hyper::body::Bytes; -use jsonrpsee_core::middleware::{Batch, Notification, ResponseBoxFuture, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee_core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::{BoxError, RpcResult}; use jsonrpsee_test_utils::TimeoutFutureExt; use jsonrpsee_test_utils::helpers::*; @@ -57,15 +57,14 @@ struct InjectExt { service: S, } -impl<'a, S> RpcServiceT<'a> for InjectExt +impl RpcServiceT for InjectExt where - S: Send + Sync + RpcServiceT<'a> + Clone + 'static, + S: Send + Sync + RpcServiceT + Clone + 'static, { - type Future = ResponseBoxFuture<'a, Self::Response, Self::Error>; type Error = S::Error; type Response = S::Response; - fn call(&self, mut req: Request<'a>) -> Self::Future { + fn call<'a>(&self, mut req: Request<'a>) -> impl Future> + Send + 'a { if req.method_name().contains("err") { req.extensions_mut().insert(StatusCode::IM_A_TEAPOT); } else { @@ -75,7 +74,7 @@ where self.service.call(req).boxed() } - fn batch(&self, mut batch: Batch<'a>) -> Self::Future { + fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future> + Send + 'a { if let Some(last) = batch.as_mut_batch_entries().last_mut() { if last.method_name().contains("err") { last.extensions_mut().insert(StatusCode::IM_A_TEAPOT); @@ -87,7 +86,10 @@ where self.service.batch(batch).boxed() } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future> + Send + 'a { self.service.notification(n).boxed() } } diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index a6d5a27a85..b77aee85d3 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -45,8 +45,8 @@ where B::Error: Into, L: for<'a> tower::Layer, >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT<'a, Response = MethodResponse> + Send, - for<'a> <>::Service as RpcServiceT<'a>>::Error: std::fmt::Debug, + >::Service: RpcServiceT + Send, + <>::Service as RpcServiceT>::Error: std::fmt::Debug, { let ServerConfig { max_response_body_size, batch_requests_config, max_request_body_size, .. } = server_cfg; @@ -77,8 +77,8 @@ where B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into, - for<'a> S: RpcServiceT<'a, Response = MethodResponse> + Send, - for<'a> >::Error: std::fmt::Debug, + S: RpcServiceT + Send, + ::Error: std::fmt::Debug, { // Only the `POST` method is allowed. match *request.method() { diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 21ea711de0..c0b425234d 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -63,8 +63,8 @@ pub(crate) struct BackgroundTaskParams { pub(crate) async fn background_task(params: BackgroundTaskParams) where - for<'a> S: RpcServiceT<'a, Response = MethodResponse> + Send + Sync + 'static, - for<'a> >::Error: std::fmt::Debug, + S: RpcServiceT + Send + Sync + 'static, + ::Error: std::fmt::Debug, { let BackgroundTaskParams { server_cfg, @@ -421,9 +421,9 @@ pub async fn connect( rpc_middleware: RpcServiceBuilder, ) -> Result<(HttpResponse, impl Future), HttpResponse> where - L: for<'a> tower::Layer, + L: tower::Layer, >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT<'a, Response = MethodResponse>, + >::Service: RpcServiceT, { let mut server = soketto::handshake::http::Server::new(); diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index 5edb82ed6f..582c1fd9e6 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -149,24 +149,32 @@ pub async fn server() -> SocketAddr { connection_id: u32, } - impl<'a, S> RpcServiceT<'a> for ConnectionDetails + impl RpcServiceT for ConnectionDetails where - S: RpcServiceT<'a>, + S: RpcServiceT, { - type Future = S::Future; type Error = S::Error; type Response = S::Response; - fn call(&self, mut request: jsonrpsee::types::Request<'a>) -> Self::Future { + fn call<'a>( + &self, + mut request: jsonrpsee::types::Request<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { request.extensions_mut().insert(self.connection_id); self.inner.call(request) } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>( + &self, + batch: Batch<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { self.inner.batch(batch) } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { self.inner.notification(n) } } diff --git a/tests/tests/metrics.rs b/tests/tests/metrics.rs index 6e936f5bad..3338e9629b 100644 --- a/tests/tests/metrics.rs +++ b/tests/tests/metrics.rs @@ -35,7 +35,6 @@ use std::sync::{Arc, Mutex}; use std::time::Duration; use futures::FutureExt; -use futures::future::BoxFuture; use helpers::init_logger; use jsonrpsee::core::middleware::{Batch, Notification, Request, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::core::{ClientError, client::ClientT}; @@ -61,15 +60,17 @@ pub struct CounterMiddleware { counter: Arc>, } -impl<'a, S> RpcServiceT<'a> for CounterMiddleware +impl RpcServiceT for CounterMiddleware where - S: RpcServiceT<'a, Response = MethodResponse> + Send + Sync + Clone + 'static, + S: RpcServiceT + Send + Sync + Clone + 'static, { - type Future = BoxFuture<'a, Result>; type Error = S::Error; type Response = S::Response; - fn call(&self, request: Request<'a>) -> Self::Future { + fn call<'a>( + &self, + request: Request<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { let counter = self.counter.clone(); let service = self.service.clone(); @@ -99,11 +100,17 @@ where .boxed() } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>( + &self, + batch: Batch<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { self.service.batch(batch).boxed() } - fn notification(&self, n: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future::Response, ::Error>> + Send + 'a { self.service.notification(n).boxed() } } From f03371a6de13fcfbb9899e2b314c25b9c32f588a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 2 Apr 2025 17:44:07 +0200 Subject: [PATCH 31/52] revert changelog --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbc0ad9697..6cb807e043 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -197,7 +197,7 @@ and it is possible to fetch it as follows: ```rust struct LogConnectionId(S); -impl<'a, S: RpcServiceT> RpcServiceT for LogConnectionId { +impl<'a, S: RpcServiceT<'a>> RpcServiceT<'a> for LogConnectionId { type Future = S::Future; fn call(&self, request: jsonrpsee::types::Request<'a>) -> Self::Future { @@ -435,9 +435,9 @@ An example how write such middleware: #[derive(Clone)] pub struct ModifyRequestIf(S); -impl<'a, S> RpcServiceT for ModifyRequestIf +impl<'a, S> RpcServiceT<'a> for ModifyRequestIf where - S: Send + Sync + RpcServiceT, + S: Send + Sync + RpcServiceT<'a>, { type Future = S::Future; From cc08ebf495fb8f0e95d0e908598a3d45ceb917e6 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 2 Apr 2025 18:11:19 +0200 Subject: [PATCH 32/52] remove logger response future --- core/src/middleware/layer/logger.rs | 86 ++++++++++------------------- server/src/middleware/rpc.rs | 3 +- 2 files changed, 30 insertions(+), 59 deletions(-) diff --git a/core/src/middleware/layer/logger.rs b/core/src/middleware/layer/logger.rs index edea76f3d3..9e17bea8e2 100644 --- a/core/src/middleware/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -26,11 +26,6 @@ //! RPC Logger layer. -use std::{ - pin::Pin, - task::{Context, Poll}, -}; - use crate::{ middleware::{Batch, Notification, RpcServiceT, ToJson}, tracing::truncate_at_char_boundary, @@ -38,7 +33,6 @@ use crate::{ use futures_util::Future; use jsonrpsee_types::Request; -use pin_project::pin_project; use tracing::Instrument; /// RPC logger layer. @@ -69,7 +63,7 @@ pub struct RpcLogger { impl RpcServiceT for RpcLogger where - S: RpcServiceT, + S: RpcServiceT + Send + Sync + Clone + 'static, S::Error: std::fmt::Debug + Send, S::Response: ToJson, { @@ -80,16 +74,40 @@ where fn call<'a>(&self, request: Request<'a>) -> impl Future> + Send + 'a { let json = serde_json::to_string(&request).unwrap_or_default(); tracing::trace!(target: "jsonrpsee", "request = {}", truncate_at_char_boundary(&json, self.max as usize)); + let service = self.service.clone(); + let max = self.max; - ResponseFuture::new(self.service.call(request), self.max).in_current_span() + async move { + let rp = service.call(request).await; + + if let Ok(ref rp) = rp { + let json = rp.to_json(); + let json_str = json.as_ref().map_or("", |j| j.get()); + tracing::trace!(target: "jsonrpsee", "response = {}", truncate_at_char_boundary(json_str, max as usize)); + } + rp + } + .in_current_span() } #[tracing::instrument(name = "batch", skip_all, fields(method = "batch"), level = "trace")] fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { let json = serde_json::to_string(&batch).unwrap_or_default(); tracing::trace!(target: "jsonrpsee", "batch request = {}", truncate_at_char_boundary(&json, self.max as usize)); + let service = self.service.clone(); + let max = self.max; - ResponseFuture::new(self.service.batch(batch), self.max).in_current_span() + async move { + let rp = service.batch(batch).await; + + if let Ok(ref rp) = rp { + let json = rp.to_json(); + let json_str = json.as_ref().map_or("", |j| j.get()); + tracing::trace!(target: "jsonrpsee", "batch response = {}", truncate_at_char_boundary(json_str, max as usize)); + } + rp + } + .in_current_span() } #[tracing::instrument(name = "notification", skip_all, fields(method = &*n.method), level = "trace")] @@ -98,54 +116,8 @@ where n: Notification<'a>, ) -> impl Future> + Send + 'a { let json = serde_json::to_string(&n).unwrap_or_default(); - tracing::trace!(target: "jsonrpsee", "notification = {}", truncate_at_char_boundary(&json, self.max as usize)); + tracing::trace!(target: "jsonrpsee", "notification request = {}", truncate_at_char_boundary(&json, self.max as usize)); - ResponseFuture::new(self.service.notification(n), self.max).in_current_span() - } -} - -/// Response future to log the response for a method call. -#[pin_project] -pub struct ResponseFuture { - #[pin] - fut: F, - max: u32, -} - -impl ResponseFuture { - /// Create a new response future. - fn new(fut: F, max: u32) -> Self { - Self { fut, max } - } -} - -impl std::fmt::Debug for ResponseFuture { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("ResponseFuture") - } -} - -impl Future for ResponseFuture -where - F: Future>, - R: ToJson, - E: std::fmt::Debug, -{ - type Output = F::Output; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let max = self.max; - let fut = self.project().fut; - - match fut.poll(cx) { - Poll::Ready(Ok(rp)) => { - let json = rp.to_json(); - let json_str = json.as_ref().map_or("", |j| j.get()); - tracing::trace!(target: "jsonrpsee", "response = {}", truncate_at_char_boundary(json_str, max as usize)); - Poll::Ready(Ok(rp)) - } - Poll::Ready(Err(e)) => Poll::Ready(Err(e)), - Poll::Pending => Poll::Pending, - } + self.service.notification(n).in_current_span() } } diff --git a/server/src/middleware/rpc.rs b/server/src/middleware/rpc.rs index c2b15a33e1..b9219cd0f7 100644 --- a/server/src/middleware/rpc.rs +++ b/server/src/middleware/rpc.rs @@ -189,13 +189,12 @@ impl RpcServiceT for RpcService { Ok(MethodResponse::from_batch(batch_rp.finish())) } } - .boxed() } fn notification<'a>( &self, _: Notification<'a>, ) -> impl Future> + Send + 'a { - async move { Ok(MethodResponse::notification()) }.boxed() + async move { Ok(MethodResponse::notification()) } } } From 99366075a8ada6d5adc7c5626153d60a2e8cbe08 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 3 Apr 2025 17:47:56 +0200 Subject: [PATCH 33/52] some cleanup --- client/http-client/src/rpc_service.rs | 4 + core/src/client/async_client/rpc_service.rs | 3 + core/src/middleware/mod.rs | 140 +++++++++++++----- examples/examples/http.rs | 12 +- examples/examples/jsonrpsee_as_service.rs | 6 +- .../jsonrpsee_server_low_level_api.rs | 41 +++-- examples/examples/rpc_middleware.rs | 33 ++--- examples/examples/rpc_middleware_client.rs | 6 +- .../examples/rpc_middleware_modify_request.rs | 71 ++++++--- .../examples/rpc_middleware_rate_limiting.rs | 87 +++++------ .../server_with_connection_details.rs | 6 +- examples/examples/ws_pubsub_broadcast.rs | 13 +- proc-macros/tests/ui/correct/basic.rs | 15 +- server/src/middleware/rpc.rs | 9 +- server/src/server.rs | 34 ++--- server/src/tests/http.rs | 2 +- server/src/transport/ws.rs | 5 +- tests/tests/helpers.rs | 6 +- tests/tests/metrics.rs | 6 +- types/src/request.rs | 5 + 20 files changed, 308 insertions(+), 196 deletions(-) diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index bc7a13075b..4aef0b54b6 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -64,6 +64,10 @@ where let mut extensions = Extensions::new(); for call in batch.into_batch_entries() { + let Ok(call) = call else { + continue; + }; + extensions.extend(call.into_extensions()); } diff --git a/core/src/client/async_client/rpc_service.rs b/core/src/client/async_client/rpc_service.rs index 740428d079..34f997d816 100644 --- a/core/src/client/async_client/rpc_service.rs +++ b/core/src/client/async_client/rpc_service.rs @@ -133,6 +133,9 @@ impl RpcServiceT for RpcService { let mut extensions = Extensions::new(); for entry in batch.into_batch_entries() { + let Ok(entry) = entry else { + continue; + }; extensions.extend(entry.into_extensions()); } diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 310d2da2ce..13c0c930ed 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -2,8 +2,8 @@ pub mod layer; -use futures_util::future::{BoxFuture, Either, Future}; -use jsonrpsee_types::{Id, InvalidRequestId}; +use futures_util::future::{Either, Future}; +use jsonrpsee_types::{ErrorCode, ErrorObject, Id, InvalidRequestId, Response}; use pin_project::pin_project; use serde::Serialize; use serde_json::value::RawValue; @@ -18,13 +18,41 @@ use std::task::{Context, Poll}; pub type Notification<'a> = jsonrpsee_types::Notification<'a, Option>>; /// Re-export types from `jsonrpsee_types` crate for convenience. pub use jsonrpsee_types::{Extensions, Request}; -/// Type alias for a boxed future that resolves to Result. -pub type ResponseBoxFuture<'a, R, E> = BoxFuture<'a, Result>; + +#[derive(Debug)] +/// A JSON-RPC error response indicating an invalid request. +pub struct InvalidRequest<'a>(Response<'a, ()>); + +impl<'a> InvalidRequest<'a> { + /// Create a new `InvalidRequest` error response. + pub fn new(id: Id<'a>) -> Self { + let payload = jsonrpsee_types::ResponsePayload::error(ErrorObject::from(ErrorCode::InvalidRequest)); + let response = Response::new(payload.into(), id); + Self(response) + } + + /// Consume the `InvalidRequest` to get the error object and id. + pub fn into_parts(self) -> (ErrorObject<'a>, Id<'a>) { + match self.0.payload { + jsonrpsee_types::ResponsePayload::Error(err) => (err, self.0.id), + _ => unreachable!("InvalidRequest::new should only create an error response; qed"), + } + } +} + +impl Serialize for InvalidRequest<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} /// A batch of JSON-RPC calls and notifications. -#[derive(Clone, Debug, Default)] +#[derive(Debug, Default)] pub struct Batch<'a> { - inner: Vec>, + inner: Vec, InvalidRequest<'a>>>, id_range: Option>, } @@ -33,8 +61,20 @@ impl Serialize for Batch<'_> { where S: serde::Serializer, { - // Serialize the batch entries directly without the Batch wrapper. - serde::Serialize::serialize(&self.inner, serializer) + // Err(InvalidRequest) is just an internal error type and should not be serialized. + // + // It is used to indicate the RpcServiceT::batch method that the request is invalid + // and should replied with an error entry. + let only_ok: Vec<&BatchEntry> = self + .inner + .iter() + .filter_map(|entry| match entry { + Ok(entry) => Some(entry), + Err(_) => None, + }) + .collect(); + + serde::Serialize::serialize(&only_ok, serializer) } } @@ -52,7 +92,7 @@ impl<'a> Batch<'a> { } /// Create a new batch from a list of batch entries without an id range. - pub fn from_batch_entries(inner: Vec>) -> Self { + pub fn from_batch_entries(inner: Vec, InvalidRequest<'a>>>) -> Self { Self { inner, id_range: None } } @@ -72,23 +112,23 @@ impl<'a> Batch<'a> { self.id_range = Some(id..id); } } - self.inner.push(BatchEntry::Call(req)); + self.inner.push(Ok(BatchEntry::Call(req))); Ok(()) } /// Get an iterator over the batch entries. - pub fn as_batch_entries(&self) -> &[BatchEntry<'a>] { + pub fn as_batch_entries(&self) -> &[Result, InvalidRequest<'a>>] { &self.inner } /// Get a mutable iterator over the batch entries. - pub fn as_mut_batch_entries(&mut self) -> &mut [BatchEntry<'a>] { + pub fn as_mut_batch_entries(&mut self) -> &mut [Result, InvalidRequest<'a>>] { &mut self.inner } /// Consume the batch and return the inner entries. - pub fn into_batch_entries(self) -> Vec> { + pub fn into_batch_entries(self) -> Vec, InvalidRequest<'a>>> { self.inner } @@ -139,33 +179,14 @@ pub enum BatchEntry<'a> { Call(Request<'a>), /// A JSON-RPC notification. Notification(Notification<'a>), - /// The representation of an invalid request and kept to maintain the batch order. - /// - /// WARNING: This is an internal state and should be NOT be used by external users - /// - #[serde(skip)] - #[doc(hidden)] - InvalidRequest(Id<'a>), -} - -/// Internal InvalidRequest that can't be instantiated by the user. -#[derive(Debug, Clone)] -pub struct InvalidRequest<'a>(Id<'a>); - -impl<'a> InvalidRequest<'a> { - /// Consume the invalid request and extract the id. - pub fn into_id(self) -> Id<'a> { - self.0 - } } -impl BatchEntry<'_> { +impl<'a> BatchEntry<'a> { /// Get a reference to extensions of the batch entry. pub fn extensions(&self) -> &Extensions { match self { BatchEntry::Call(req) => req.extensions(), BatchEntry::Notification(n) => n.extensions(), - BatchEntry::InvalidRequest(_) => panic!("BatchEntry::InvalidRequest should not be used"), } } @@ -174,7 +195,6 @@ impl BatchEntry<'_> { match self { BatchEntry::Call(req) => req.extensions_mut(), BatchEntry::Notification(n) => n.extensions_mut(), - BatchEntry::InvalidRequest(_) => panic!("BatchEntry::InvalidRequest should not be used"), } } @@ -183,7 +203,38 @@ impl BatchEntry<'_> { match self { BatchEntry::Call(req) => req.method_name(), BatchEntry::Notification(n) => n.method_name(), - BatchEntry::InvalidRequest(_) => panic!("BatchEntry::InvalidRequest should not be used"), + } + } + + /// Set the method name of the batch entry. + pub fn set_method_name(&mut self, name: impl Into>) { + match self { + BatchEntry::Call(req) => { + req.method = name.into(); + } + BatchEntry::Notification(n) => { + n.method = name.into(); + } + } + } + + /// Get the params of the batch entry (may be empty). + pub fn params(&self) -> Option<&Cow<'a, RawValue>> { + match self { + BatchEntry::Call(req) => req.params.as_ref(), + BatchEntry::Notification(n) => n.params.as_ref(), + } + } + + /// Set the params of the batch entry. + pub fn set_params(&mut self, params: Option>) { + match self { + BatchEntry::Call(req) => { + req.params = params.map(Cow::Owned); + } + BatchEntry::Notification(n) => { + n.params = params.map(Cow::Owned); + } } } @@ -192,7 +243,6 @@ impl BatchEntry<'_> { match self { BatchEntry::Call(req) => req.extensions, BatchEntry::Notification(n) => n.extensions, - BatchEntry::InvalidRequest(_) => panic!("BatchEntry::InvalidRequest should not be used"), } } } @@ -350,4 +400,22 @@ mod tests { "{\"jsonrpc\":\"2.0\",\"method\":\"say_hello\",\"params\":null}", ); } + + #[test] + fn serialize_batch_works() { + use super::{Batch, BatchEntry, InvalidRequest, Notification, Request}; + use jsonrpsee_types::Id; + + let req = Request::borrowed("say_hello", None, Id::Number(1)); + let notification = Notification::new("say_hello".into(), None); + let batch = Batch::from_batch_entries(vec![ + Ok(BatchEntry::Call(req)), + Ok(BatchEntry::Notification(notification)), + Err(InvalidRequest::new(Id::Null)), + ]); + assert_eq!( + serde_json::to_string(&batch).unwrap(), + r#"[{"jsonrpc":"2.0","id":1,"method":"say_hello"},{"jsonrpc":"2.0","method":"say_hello","params":null}]"#, + ); + } } diff --git a/examples/examples/http.rs b/examples/examples/http.rs index 84d4f2fef8..a6f597c9e9 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -47,18 +47,12 @@ where type Error = S::Error; type Response = S::Response; - fn call<'a>( - &self, - req: Request<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + fn call<'a>(&self, req: Request<'a>) -> impl Future> + Send + 'a { println!("logger layer : {:?}", req); self.0.call(req) } - fn batch<'a>( - &self, - batch: Batch<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { println!("logger layer : {:?}", batch); self.0.batch(batch) } @@ -66,7 +60,7 @@ where fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { println!("logger layer : {:?}", n); self.0.notification(n) } diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index 72ade9be44..bab6380d35 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -82,7 +82,7 @@ where fn call<'a>( &self, req: Request<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { if req.method_name() == "trusted_call" { let Some(Ok(_)) = self.headers.get(AUTHORIZATION).map(|auth| auth.to_str()) else { let rp = MethodResponse::error(req.id, ErrorObject::borrowed(-32000, "Authorization failed", None)); @@ -101,14 +101,14 @@ where fn batch<'a>( &self, batch: Batch<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { ResponseFuture::future(self.inner.batch(batch)) } fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { ResponseFuture::future(self.inner.notification(n)) } } diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index aebd334d33..024c094b7d 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -53,7 +53,7 @@ use jsonrpsee::server::{ ConnectionGuard, ConnectionState, ServerConfig, ServerHandle, StopHandle, http, serve_with_graceful_shutdown, stop_channel, ws, }; -use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, Request}; +use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, Id, Request}; use jsonrpsee::ws_client::WsClientBuilder; use jsonrpsee::{MethodResponse, Methods}; use tokio::net::TcpListener; @@ -80,10 +80,7 @@ where type Error = S::Error; type Response = S::Response; - fn call<'a>( - &self, - req: Request<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + fn call<'a>(&self, req: Request<'a>) -> impl Future> + Send + 'a { let count = self.count.clone(); let state = self.state.clone(); let service = self.service.clone(); @@ -100,21 +97,39 @@ where rp } } - .boxed() } - fn batch<'a>( - &self, - batch: Batch<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { - Box::pin(self.service.batch(batch)) + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { + let count = self.count.clone(); + let state = self.state.clone(); + let service = self.service.clone(); + + async move { + let mut lock = count.lock().await; + + if *lock >= 10 { + let _ = state.try_send(()); + Ok(MethodResponse::error(Id::Null, ErrorObject::borrowed(-32000, "RPC rate limit", None))) + } else { + let rp = service.batch(batch).await; + *lock += 10; + rp + } + } } fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { - Box::pin(self.service.notification(n)) + ) -> impl Future> + Send + 'a { + let count = self.count.clone(); + let service = self.service.clone(); + + // A notification is not expected to return a response so the result here doesn't matter + // rather than other middlewares may not be invoked. + async move { + if *count.lock().await >= 10 { Ok(MethodResponse::notification()) } else { service.notification(n).await } + } } } diff --git a/examples/examples/rpc_middleware.rs b/examples/examples/rpc_middleware.rs index 3e2242c75b..dee8b62380 100644 --- a/examples/examples/rpc_middleware.rs +++ b/examples/examples/rpc_middleware.rs @@ -73,7 +73,7 @@ where fn call<'a>( &self, req: Request<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future::Error>> + Send + 'a { let count = self.count.clone(); let service = self.service.clone(); let role = self.role; @@ -87,10 +87,7 @@ where .boxed() } - fn batch<'a>( - &self, - batch: Batch<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { let len = batch.as_batch_entries().len(); self.count.fetch_add(len, Ordering::SeqCst); println!("{} processed calls={} on the connection", self.role, self.count.load(Ordering::SeqCst)); @@ -100,7 +97,7 @@ where fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { Box::pin(self.service.notification(n)) } } @@ -119,10 +116,7 @@ where type Error = S::Error; type Response = S::Response; - fn call<'a>( - &self, - req: Request<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + fn call<'a>(&self, req: Request<'a>) -> impl Future> + Send + 'a { let count = self.count.clone(); let service = self.service.clone(); let role = self.role; @@ -137,10 +131,7 @@ where .boxed() } - fn batch<'a>( - &self, - batch: Batch<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { let len = batch.as_batch_entries().len(); self.count.fetch_add(len, Ordering::SeqCst); println!("{}, processed calls={} in total", self.role, self.count.load(Ordering::SeqCst)); @@ -150,7 +141,7 @@ where fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { Box::pin(self.service.notification(n)) } } @@ -168,25 +159,19 @@ where type Error = S::Error; type Response = S::Response; - fn call<'a>( - &self, - req: Request<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + fn call<'a>(&self, req: Request<'a>) -> impl Future> + Send + 'a { println!("{} logger middleware: method `{}`", self.role, req.method); self.service.call(req) } - fn batch<'a>( - &self, - batch: Batch<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { println!("{} logger middleware: batch {batch}", self.role); self.service.batch(batch) } fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { self.service.notification(n) } } diff --git a/examples/examples/rpc_middleware_client.rs b/examples/examples/rpc_middleware_client.rs index 91dccc5316..55ff60b5db 100644 --- a/examples/examples/rpc_middleware_client.rs +++ b/examples/examples/rpc_middleware_client.rs @@ -89,7 +89,7 @@ where fn call<'a>( &self, req: Request<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { let m = self.metrics.clone(); let service = self.service.clone(); @@ -120,7 +120,7 @@ where fn batch<'a>( &self, batch: Batch<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { self.metrics.lock().unwrap().batch_calls += 1; Box::pin(self.service.batch(batch)) } @@ -128,7 +128,7 @@ where fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { self.metrics.lock().unwrap().notifications += 1; Box::pin(self.service.notification(n)) } diff --git a/examples/examples/rpc_middleware_modify_request.rs b/examples/examples/rpc_middleware_modify_request.rs index 102d5aabd6..7b7e23f68a 100644 --- a/examples/examples/rpc_middleware_modify_request.rs +++ b/examples/examples/rpc_middleware_modify_request.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use jsonrpsee::core::client::ClientT; -use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Batch, BatchEntry, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::server::Server; use jsonrpsee::types::Request; use jsonrpsee::ws_client::WsClientBuilder; @@ -33,6 +33,34 @@ use jsonrpsee::{RpcModule, rpc_params}; use std::borrow::Cow as StdCow; use std::net::SocketAddr; +fn modify_method_call(req: &mut Request<'_>) { + // Example how to modify the params in the call. + if req.method == "say_hello" { + // It's a bit awkward to create new params in the request + // but this shows how to do it. + let raw_value = serde_json::value::to_raw_value("myparams").unwrap(); + req.params = Some(StdCow::Owned(raw_value)); + } + // Re-direct all calls that isn't `say_hello` to `say_goodbye` + else if req.method != "say_hello" { + req.method = "say_goodbye".into(); + } +} + +fn modify_notif(n: &mut Notification<'_>) { + // Example how to modify the params in the notification. + if n.method == "say_hello" { + // It's a bit awkward to create new params in the request + // but this shows how to do it. + let raw_value = serde_json::value::to_raw_value("myparams").unwrap(); + n.params = Some(StdCow::Owned(raw_value)); + } + // Re-direct all notifs that isn't `say_hello` to `say_goodbye` + else if n.method != "say_hello" { + n.method = "say_goodbye".into(); + } +} + #[derive(Clone)] pub struct ModifyRequestIf(S); @@ -43,36 +71,33 @@ where type Error = S::Error; type Response = S::Response; - fn call<'a>( - &self, - mut req: Request<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { - // Example how to modify the params in the call. - if req.method == "say_hello" { - // It's a bit awkward to create new params in the request - // but this shows how to do it. - let raw_value = serde_json::value::to_raw_value("myparams").unwrap(); - req.params = Some(StdCow::Owned(raw_value)); - } - // Re-direct all calls that isn't `say_hello` to `say_goodbye` - else if req.method != "say_hello" { - req.method = "say_goodbye".into(); - } - + fn call<'a>(&self, mut req: Request<'a>) -> impl Future> + Send + 'a { + modify_method_call(&mut req); self.0.call(req) } - fn batch<'a>( - &self, - batch: Batch<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future> + Send + 'a { + for call in batch.as_mut_batch_entries() { + match call { + Ok(BatchEntry::Call(call)) => { + modify_method_call(call); + } + Ok(BatchEntry::Notification(n)) => { + modify_notif(n); + } + // Invalid request, we don't care about it. + Err(_err) => {} + } + } + self.0.batch(batch) } fn notification<'a>( &self, - n: Notification<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + mut n: Notification<'a>, + ) -> impl Future> + Send + 'a { + modify_notif(&mut n); self.0.notification(n) } } diff --git a/examples/examples/rpc_middleware_rate_limiting.rs b/examples/examples/rpc_middleware_rate_limiting.rs index eaa3cbcd50..21e8b654f6 100644 --- a/examples/examples/rpc_middleware_rate_limiting.rs +++ b/examples/examples/rpc_middleware_rate_limiting.rs @@ -78,6 +78,31 @@ impl RateLimit { state: Arc::new(Mutex::new(State::Allow { until: Instant::now() + period, rem: num + 1 })), } } + + fn rate_limit_deny(&self) -> bool { + let now = Instant::now(); + let mut lock = self.state.lock().unwrap(); + let next_state = match *lock { + State::Deny { until } => { + if now > until { + State::Allow { until: now + self.rate.period, rem: self.rate.num - 1 } + } else { + State::Deny { until } + } + } + State::Allow { until, rem } => { + if now > until { + State::Allow { until: now + self.rate.period, rem: self.rate.num - 1 } + } else { + let n = rem - 1; + if n > 0 { State::Allow { until: now + self.rate.period, rem: n } } else { State::Deny { until } } + } + } + }; + + *lock = next_state; + matches!(next_state, State::Deny { .. }) + } } impl RpcServiceT for RateLimit @@ -88,59 +113,37 @@ where type Error = S::Error; type Response = S::Response; - fn call<'a>( - &self, - req: Request<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { - let now = Instant::now(); - - let is_denied = { - let mut lock = self.state.lock().unwrap(); - let next_state = match *lock { - State::Deny { until } => { - if now > until { - State::Allow { until: now + self.rate.period, rem: self.rate.num - 1 } - } else { - State::Deny { until } - } - } - State::Allow { until, rem } => { - if now > until { - State::Allow { until: now + self.rate.period, rem: self.rate.num - 1 } - } else { - let n = rem - 1; - if n > 0 { - State::Allow { until: now + self.rate.period, rem: n } - } else { - State::Deny { until } - } - } - } - }; - - *lock = next_state; - matches!(next_state, State::Deny { .. }) - }; - - if is_denied { + fn call<'a>(&self, req: Request<'a>) -> impl Future> + Send + 'a { + if self.rate_limit_deny() { ResponseFuture::ready(MethodResponse::error(req.id, ErrorObject::borrowed(-32000, "RPC rate limit", None))) } else { ResponseFuture::future(self.service.call(req)) } } - fn batch<'a>( - &self, - batch: Batch<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { - ResponseFuture::future(self.service.batch(batch)) + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { + if self.rate_limit_deny() { + let error = ErrorObject::borrowed(-32000, "RPC rate limit", None); + let error = MethodResponse::error(jsonrpsee::types::Id::Null, error); + return ResponseFuture::ready(error); + } else { + // NOTE: this count as batch call a single call which may not + // be the desired behavior. + ResponseFuture::future(self.service.batch(batch)) + } } fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { - ResponseFuture::future(self.service.notification(n)) + ) -> impl Future> + Send + 'a { + if self.rate_limit_deny() { + // Notifications are not expected to return a response so just ignore + // if the rate limit is reached. + ResponseFuture::ready(MethodResponse::notification()) + } else { + ResponseFuture::future(self.service.notification(n)) + } } } diff --git a/examples/examples/server_with_connection_details.rs b/examples/examples/server_with_connection_details.rs index fd8c7c77bb..abcb685b00 100644 --- a/examples/examples/server_with_connection_details.rs +++ b/examples/examples/server_with_connection_details.rs @@ -56,7 +56,7 @@ impl RpcServiceT for LoggingMiddleware { fn call<'a>( &self, request: Request<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { tracing::info!("Received request: {:?}", request); assert!(request.extensions().get::().is_some()); @@ -66,14 +66,14 @@ impl RpcServiceT for LoggingMiddleware { fn batch<'a>( &self, batch: Batch<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { self.0.batch(batch) } fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { self.0.notification(n) } } diff --git a/examples/examples/ws_pubsub_broadcast.rs b/examples/examples/ws_pubsub_broadcast.rs index 1365ed5b9b..1e57f09905 100644 --- a/examples/examples/ws_pubsub_broadcast.rs +++ b/examples/examples/ws_pubsub_broadcast.rs @@ -32,6 +32,7 @@ use futures::StreamExt; use futures::future::{self, Either}; use jsonrpsee::PendingSubscriptionSink; use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; +use jsonrpsee::core::middleware::RpcServiceBuilder; use jsonrpsee::core::server::SubscriptionMessage; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, Server, ServerConfig}; @@ -51,8 +52,10 @@ async fn main() -> anyhow::Result<()> { let addr = run_server().await?; let url = format!("ws://{}", addr); - let client1 = WsClientBuilder::default().build(&url).await?; - let client2 = WsClientBuilder::default().build(&url).await?; + let client1 = + WsClientBuilder::default().set_rpc_middleware(RpcServiceBuilder::new().rpc_logger(1024)).build(&url).await?; + let client2 = + WsClientBuilder::default().set_rpc_middleware(RpcServiceBuilder::new().rpc_logger(1024)).build(&url).await?; let sub1: Subscription = client1.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await?; let sub2: Subscription = client2.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await?; @@ -67,7 +70,11 @@ async fn main() -> anyhow::Result<()> { async fn run_server() -> anyhow::Result { // let's configure the server only hold 5 messages in memory. let config = ServerConfig::builder().set_message_buffer_capacity(5).build(); - let server = Server::builder().set_config(config).build("127.0.0.1:0").await?; + let server = Server::builder() + .set_config(config) + .set_rpc_middleware(RpcServiceBuilder::new().rpc_logger(1024)) + .build("127.0.0.1:0") + .await?; let (tx, _rx) = broadcast::channel::(16); let mut module = RpcModule::new(tx.clone()); diff --git a/proc-macros/tests/ui/correct/basic.rs b/proc-macros/tests/ui/correct/basic.rs index c58cbb7ada..dc5180b86b 100644 --- a/proc-macros/tests/ui/correct/basic.rs +++ b/proc-macros/tests/ui/correct/basic.rs @@ -147,24 +147,29 @@ pub async fn server() -> SocketAddr { connection_id: u32, } - impl<'a, S> RpcServiceT for ConnectionDetails + impl RpcServiceT for ConnectionDetails where S: RpcServiceT, { - type Future = S::Future; type Error = S::Error; type Response = S::Response; - fn call(&self, mut request: jsonrpsee::types::Request<'a>) -> Self::Future { + fn call<'a>( + &self, + mut request: jsonrpsee::types::Request<'a>, + ) -> impl Future> + Send + 'a { request.extensions_mut().insert(self.connection_id); self.inner.call(request) } - fn batch(&self, batch: Batch<'a>) -> Self::Future { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { self.inner.batch(batch) } - fn notification(&self, notif: Notification<'a>) -> Self::Future { + fn notification<'a>( + &self, + notif: Notification<'a>, + ) -> impl Future> + Send + 'a { self.inner.notification(notif) } } diff --git a/server/src/middleware/rpc.rs b/server/src/middleware/rpc.rs index b9219cd0f7..ded9ba00ac 100644 --- a/server/src/middleware/rpc.rs +++ b/server/src/middleware/rpc.rs @@ -155,7 +155,7 @@ impl RpcServiceT for RpcService { for batch_entry in batch.into_batch_entries() { match batch_entry { - BatchEntry::Call(req) => { + Ok(BatchEntry::Call(req)) => { let rp = match service.call(req).await { Ok(rp) => rp, Err(e) => match e {}, @@ -164,15 +164,16 @@ impl RpcServiceT for RpcService { return Ok(err); } } - BatchEntry::Notification(n) => { + Ok(BatchEntry::Notification(n)) => { got_notification = true; match service.notification(n).await { Ok(rp) => rp, Err(e) => match e {}, }; } - BatchEntry::InvalidRequest(id) => { - let rp = MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidRequest)); + Err(err) => { + let (err, id) = err.into_parts(); + let rp = MethodResponse::error(id, err); if let Err(err) = batch_rp.append(rp) { return Ok(err); } diff --git a/server/src/server.rs b/server/src/server.rs index 40fcc73ed1..5afd041324 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -45,7 +45,7 @@ use futures_util::io::{BufReader, BufWriter}; use hyper::body::Bytes; use hyper_util::rt::{TokioExecutor, TokioIo}; use jsonrpsee_core::id_providers::RandomIntegerIdProvider; -use jsonrpsee_core::middleware::{Batch, BatchEntry, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee_core::middleware::{Batch, BatchEntry, InvalidRequest, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::server::helpers::prepare_error; use jsonrpsee_core::server::{BoundedSubscriptions, ConnectionId, MethodResponse, MethodSink, Methods}; use jsonrpsee_core::traits::IdProvider; @@ -55,7 +55,7 @@ use jsonrpsee_types::error::{ BATCHES_NOT_SUPPORTED_CODE, BATCHES_NOT_SUPPORTED_MSG, ErrorCode, reject_too_big_batch_request, rpc_middleware_error, }; -use jsonrpsee_types::{ErrorObject, Id, InvalidRequest, deserialize_with_ext}; +use jsonrpsee_types::{ErrorObject, Id, deserialize_with_ext}; use soketto::handshake::http::is_upgrade_request; use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; use tokio::sync::{OwnedSemaphorePermit, mpsc, watch}; @@ -664,7 +664,7 @@ impl Builder { /// use std::{time::Instant, net::SocketAddr, sync::Arc}; /// use std::sync::atomic::{Ordering, AtomicUsize}; /// - /// use jsonrpsee_server::middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceT, MethodResponse, ResponseBoxFuture, Notification, Request, Batch}; + /// use jsonrpsee_server::middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceT, MethodResponse, Notification, Request, Batch}; /// use jsonrpsee_server::ServerBuilder; /// /// #[derive(Clone)] @@ -673,32 +673,31 @@ impl Builder { /// count: Arc, /// } /// - /// impl<'a, S> RpcServiceT for MyMiddleware + /// impl RpcServiceT for MyMiddleware /// where S: RpcServiceT + Send + Sync + Clone + 'static, /// { - /// type Future = ResponseBoxFuture<'a, Self::Response, Self::Error>; /// type Error = S::Error; /// type Response = S::Response; /// - /// fn call(&self, req: Request<'a>) -> Self::Future { + /// fn call<'a>(&self, req: Request<'a>) -> impl Future> + Send + 'a { /// tracing::info!("MyMiddleware processed call {}", req.method); /// let count = self.count.clone(); /// let service = self.service.clone(); /// - /// Box::pin(async move { + /// async move { /// let rp = service.call(req).await; /// // Modify the state. /// count.fetch_add(1, Ordering::Relaxed); /// rp - /// }) + /// } /// } /// - /// fn batch(&self, batch: Batch<'a>) -> Self::Future { - /// Box::pin(self.service.batch(batch)) + /// fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { + /// self.service.batch(batch) /// } /// - /// fn notification(&self, notif: Notification<'a>) -> Self::Future { - /// Box::pin(self.service.notification(notif)) + /// fn notification<'a>(&self, notif: Notification<'a>) -> impl Future> + Send + 'a { + /// self.service.notification(notif) /// } /// /// } @@ -936,8 +935,7 @@ impl TowerService impl Service> for TowerService where RpcMiddleware: for<'a> tower::Layer + Clone, - >::Service: Send + Sync + 'static, - for<'a> >::Service: RpcServiceT, + >::Service: RpcServiceT + Send + Sync + 'static, HttpMiddleware: Layer> + Send + 'static, >>::Service: Send + Service, Response = HttpResponse, Error = Box<(dyn StdError + Send + Sync + 'static)>>, @@ -1290,16 +1288,16 @@ where for call in unchecked_batch { if let Ok(req) = deserialize_with_ext::call::from_str(call.get(), &extensions) { - batch.push(BatchEntry::Call(req)); + batch.push(Ok(BatchEntry::Call(req))); } else if let Ok(notif) = deserialize_with_ext::notif::from_str::(call.get(), &extensions) { - batch.push(BatchEntry::Notification(notif)); + batch.push(Ok(BatchEntry::Notification(notif))); } else { - let id = match serde_json::from_str::(call.get()) { + let id = match serde_json::from_str::(call.get()) { Ok(err) => err.id, Err(_) => Id::Null, }; - batch.push(BatchEntry::InvalidRequest(id)); + batch.push(Err(InvalidRequest::new(id))); } } diff --git a/server/src/tests/http.rs b/server/src/tests/http.rs index 33e3aa17ee..cd934536ef 100644 --- a/server/src/tests/http.rs +++ b/server/src/tests/http.rs @@ -75,7 +75,7 @@ where } fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future> + Send + 'a { - if let Some(last) = batch.as_mut_batch_entries().last_mut() { + if let Some(Ok(last)) = batch.as_mut_batch_entries().last_mut() { if last.method_name().contains("err") { last.extensions_mut().insert(StatusCode::IM_A_TEAPOT); } else { diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index c0b425234d..464228d4e7 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -393,9 +393,8 @@ async fn graceful_shutdown( /// mut disconnect: tokio::sync::mpsc::Receiver<()> /// ) -> HttpResponse /// where -/// L: for<'a> tower::Layer + 'static, -/// >::Service: Send + Sync + 'static, -/// for<'a> >::Service: RpcServiceT<'a, Response = MethodResponse> + 'static, +/// L: tower::Layer + 'static, +/// >::Service: RpcServiceT + Send + Sync + 'static, /// { /// match ws::connect(req, server_cfg, methods, conn, rpc_middleware).await { /// Ok((rp, conn_fut)) => { diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index 582c1fd9e6..594c737365 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -159,7 +159,7 @@ pub async fn server() -> SocketAddr { fn call<'a>( &self, mut request: jsonrpsee::types::Request<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { request.extensions_mut().insert(self.connection_id); self.inner.call(request) } @@ -167,14 +167,14 @@ pub async fn server() -> SocketAddr { fn batch<'a>( &self, batch: Batch<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { self.inner.batch(batch) } fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { self.inner.notification(n) } } diff --git a/tests/tests/metrics.rs b/tests/tests/metrics.rs index 3338e9629b..9f16d35464 100644 --- a/tests/tests/metrics.rs +++ b/tests/tests/metrics.rs @@ -70,7 +70,7 @@ where fn call<'a>( &self, request: Request<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { let counter = self.counter.clone(); let service = self.service.clone(); @@ -103,14 +103,14 @@ where fn batch<'a>( &self, batch: Batch<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { self.service.batch(batch).boxed() } fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future::Response, ::Error>> + Send + 'a { + ) -> impl Future> + Send + 'a { self.service.notification(n).boxed() } } diff --git a/types/src/request.rs b/types/src/request.rs index 117d7ef91e..ece555fc38 100644 --- a/types/src/request.rs +++ b/types/src/request.rs @@ -146,6 +146,11 @@ impl<'a, T> Notification<'a, T> { &self.extensions } + /// Get the params of the request. + pub fn params(&self) -> &T { + &self.params + } + /// Returns a reference to the associated extensions. pub fn extensions_mut(&mut self) -> &mut Extensions { &mut self.extensions From 4149181d6176835694e325fd1e4ff7844fd3680e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 3 Apr 2025 19:09:17 +0200 Subject: [PATCH 34/52] move request timeout from transport to client --- client/http-client/src/client.rs | 37 ++++++++++++---- client/http-client/src/transport.rs | 20 +-------- core/src/client/async_client/helpers.rs | 15 +------ core/src/client/async_client/mod.rs | 29 ++++++------ core/src/client/async_client/rpc_service.rs | 49 +++++---------------- 5 files changed, 58 insertions(+), 92 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 49d1042ac1..5a1e00c21d 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -280,7 +280,6 @@ where service_builder, #[cfg(feature = "tls")] certificate_store, - request_timeout, } .build(target) .map_err(|e| Error::Transport(e.into()))?; @@ -362,7 +361,13 @@ where None => None, }; let params = params.to_rpc_params()?.map(StdCow::Owned); - self.transport.notification(Notification::new(method.into(), params)).await?; + + run_future_until_timeout( + self.transport.notification(Notification::new(method.into(), params)), + self.request_timeout, + ) + .await + .map_err(|e| Error::Transport(e.into()))?; Ok(()) } @@ -378,10 +383,15 @@ where let id = self.id_manager.next_request_id(); let params = params.to_rpc_params()?; - let request = Request::borrowed(method, params.as_deref(), id.clone()); - let rp = - self.transport.call(request).await?.into_method_call().expect("Transport::call must return a method call"); - let rp = ResponseSuccess::try_from(rp.into_inner())?; + let method_response = run_future_until_timeout( + self.transport.call(Request::borrowed(method, params.as_deref(), id.clone())), + self.request_timeout, + ) + .await? + .into_method_call() + .expect("Method call must return a method call reponse; qed"); + + let rp = ResponseSuccess::try_from(method_response.into_inner())?; let result = serde_json::from_str(rp.result.get()).map_err(Error::ParseError)?; if rp.id == id { Ok(result) } else { Err(InvalidRequestId::NotPendingRequest(rp.id.to_string()).into()) } @@ -412,8 +422,8 @@ where batch_request.push(req)?; } - let json_rps = - self.transport.batch(batch_request).await?.into_batch().expect("Transport::batch must return a batch"); + let rp = run_future_until_timeout(self.transport.batch(batch_request), self.request_timeout).await?; + let json_rps = rp.into_batch().expect("Batch must return a batch reponse; qed"); let mut batch_response = Vec::new(); let mut success = 0; @@ -483,3 +493,14 @@ where Err(Error::HttpNotImplemented) } } + +async fn run_future_until_timeout(fut: F, timeout: Duration) -> Result +where + F: std::future::Future>, +{ + match tokio::time::timeout(timeout, fut).await { + Ok(Ok(r)) => Ok(r), + Err(_) => Err(Error::RequestTimeout), + Ok(Err(e)) => Err(e), + } +} diff --git a/client/http-client/src/transport.rs b/client/http-client/src/transport.rs index d364bc8022..d9dce06982 100644 --- a/client/http-client/src/transport.rs +++ b/client/http-client/src/transport.rs @@ -20,7 +20,6 @@ use jsonrpsee_core::{ use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; -use std::time::Duration; use thiserror::Error; use tower::layer::util::Identity; use tower::{Layer, Service, ServiceExt}; @@ -99,8 +98,6 @@ pub struct HttpTransportClientBuilder { pub(crate) service_builder: tower::ServiceBuilder, /// TCP_NODELAY pub(crate) tcp_no_delay: bool, - /// Request timeout - pub(crate) request_timeout: Duration, } impl Default for HttpTransportClientBuilder { @@ -120,7 +117,6 @@ impl HttpTransportClientBuilder { headers: HeaderMap::new(), service_builder: tower::ServiceBuilder::new(), tcp_no_delay: true, - request_timeout: Duration::from_secs(60), } } } @@ -171,7 +167,6 @@ impl HttpTransportClientBuilder { max_response_size: self.max_response_size, service_builder: service, tcp_no_delay: self.tcp_no_delay, - request_timeout: self.request_timeout, } } @@ -192,7 +187,6 @@ impl HttpTransportClientBuilder { headers, service_builder, tcp_no_delay, - request_timeout, } = self; let mut url = Url::parse(target.as_ref()).map_err(|e| Error::Url(format!("Invalid URL: {e}")))?; @@ -277,7 +271,6 @@ impl HttpTransportClientBuilder { max_request_size, max_response_size, headers: cached_headers, - request_timeout, }) } } @@ -295,8 +288,6 @@ pub struct HttpTransportClient { max_response_size: u32, /// Custom headers to pass with every request. headers: HeaderMap, - /// Request timeout - request_timeout: Duration, } impl HttpTransportClient @@ -328,8 +319,7 @@ where /// Send serialized message and wait until all bytes from the HTTP message body have been read. pub(crate) async fn send_and_read_body(&self, body: String) -> Result, Error> { - let response = - tokio::time::timeout(self.request_timeout, self.inner_send(body)).await.map_err(|_| Error::Timeout)??; + let response = self.inner_send(body).await?; let (parts, body) = response.into_parts(); let (body, _is_single) = http_helpers::read_body(&parts.headers, body, self.max_response_size).await?; @@ -339,9 +329,7 @@ where /// Send serialized message without reading the HTTP message body. pub(crate) async fn send(&self, body: String) -> Result<(), Error> { - let _ = - tokio::time::timeout(self.request_timeout, self.inner_send(body)).await.map_err(|_| Error::Timeout)??; - + self.inner_send(body).await?; Ok(()) } } @@ -371,10 +359,6 @@ pub enum Error { /// Invalid certificate store. #[error("Invalid certificate store")] InvalidCertficateStore, - - /// Timeout. - #[error("Request timed out")] - Timeout, } #[cfg(test)] diff --git a/core/src/client/async_client/helpers.rs b/core/src/client/async_client/helpers.rs index a07adbc9cb..34be025887 100644 --- a/core/src/client/async_client/helpers.rs +++ b/core/src/client/async_client/helpers.rs @@ -270,24 +270,11 @@ pub(crate) fn build_unsubscribe_message( /// Wait for a stream to complete within the given timeout. pub(crate) async fn call_with_timeout( - timeout: std::time::Duration, - rx: oneshot::Receiver>, -) -> Result, oneshot::error::RecvError> { - match future::select(rx, Delay::new(timeout)).await { - Either::Left((Ok(Ok(r)), _)) => Ok(Ok(r)), - Either::Left((Ok(Err(e)), _)) => Ok(Err(Error::InvalidRequestId(e))), - Either::Left((Err(e), _)) => Err(e), - Either::Right((_, _)) => Ok(Err(Error::RequestTimeout)), - } -} - -/// Wait for a stream to complete within the given timeout. -pub(crate) async fn call_with_timeout_sub( timeout: std::time::Duration, rx: oneshot::Receiver>, ) -> Result, oneshot::error::RecvError> { match future::select(rx, Delay::new(timeout)).await { - Either::Left((r, _)) => r, + Either::Left((res, _)) => res, Either::Right((_, _)) => Ok(Err(Error::RequestTimeout)), } } diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index 99bde60c05..e597993cad 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -52,7 +52,7 @@ use futures_util::Stream; use futures_util::future::{self, Either}; use futures_util::stream::StreamExt; use helpers::{ - build_unsubscribe_message, call_with_timeout_sub, process_batch_response, process_notification, + build_unsubscribe_message, call_with_timeout, process_batch_response, process_notification, process_single_response, process_subscription_response, stop_subscription, }; use http::Extensions; @@ -357,7 +357,7 @@ impl ClientBuilder { Client { to_back: to_back.clone(), - service: self.service_builder.service(RpcService::new(to_back.clone(), self.request_timeout)), + service: self.service_builder.service(RpcService::new(to_back.clone())), request_timeout: self.request_timeout, error: ErrorFromBack::new(to_back, disconnect_reason), id_manager: RequestIdManager::new(self.id_kind), @@ -453,14 +453,15 @@ impl Client { !self.to_back.is_closed() } - async fn map_rpc_service_err( + async fn run_future_until_timeout( &self, fut: impl Future>, ) -> Result { - match fut.await { - Ok(r) => Ok(r), - Err(RpcServiceError::Client(e)) => Err(e), - Err(RpcServiceError::FetchFromBackend) => Err(self.on_disconnect().await), + match tokio::time::timeout(self.request_timeout, fut).await { + Ok(Ok(r)) => Ok(r), + Ok(Err(RpcServiceError::Client(e))) => Err(e), + Ok(Err(RpcServiceError::FetchFromBackend)) => Err(self.on_disconnect().await), + Err(_) => Err(Error::RequestTimeout), } } @@ -501,7 +502,7 @@ where let _req_id = self.id_manager.next_request_id(); let params = params.to_rpc_params()?.map(StdCow::Owned); let fut = self.service.notification(jsonrpsee_types::Notification::new(method.into(), params)); - self.map_rpc_service_err(fut).await?; + self.run_future_until_timeout(fut).await?; Ok(()) } @@ -512,10 +513,8 @@ where { let id = self.id_manager.next_request_id(); let params = params.to_rpc_params()?; - - let request = Request::borrowed(method, params.as_deref(), id.clone()); - let fut = self.service.call(request); - let rp = self.map_rpc_service_err(fut).await?.into_method_call().expect("Method call response"); + let fut = self.service.call(Request::borrowed(method, params.as_deref(), id.clone())); + let rp = self.run_future_until_timeout(fut).await?.into_method_call().expect("Method call response"); let success = ResponseSuccess::try_from(rp.into_inner())?; serde_json::from_str(success.result.get()).map_err(Into::into) @@ -542,7 +541,7 @@ where } let fut = self.service.batch(b); - let json_values = self.map_rpc_service_err(fut).await?.into_batch().expect("Batch response"); + let json_values = self.run_future_until_timeout(fut).await?.into_batch().expect("Batch response"); let mut responses = Vec::with_capacity(json_values.len()); let mut successful_calls = 0; @@ -605,7 +604,7 @@ where let fut = self.service.call(req); let (sub_id, notifs_rx) = - self.map_rpc_service_err(fut).await?.into_subscription().expect("Subscription response"); + self.run_future_until_timeout(fut).await?.into_subscription().expect("Subscription response"); Ok(Subscription::new(self.to_back.clone(), notifs_rx, SubscriptionKind::Subscription(sub_id))) } @@ -629,7 +628,7 @@ where return Err(self.on_disconnect().await); } - let res = call_with_timeout_sub(self.request_timeout, send_back_rx).await; + let res = call_with_timeout(self.request_timeout, send_back_rx).await; let (rx, method) = match res { Ok(Ok(val)) => val, diff --git a/core/src/client/async_client/rpc_service.rs b/core/src/client/async_client/rpc_service.rs index 34f997d816..8c504d0a07 100644 --- a/core/src/client/async_client/rpc_service.rs +++ b/core/src/client/async_client/rpc_service.rs @@ -1,24 +1,15 @@ -use std::time::Duration; - use crate::{ client::{ BatchMessage, Error as ClientError, FrontToBack, MethodResponse, RequestMessage, SubscriptionMessage, - SubscriptionResponse, async_client::helpers::call_with_timeout, + SubscriptionResponse, }, middleware::{Batch, IsSubscription, Notification, Request, RpcServiceT}, }; -use futures_timer::Delay; -use futures_util::{ - FutureExt, - future::{self, Either}, -}; use http::Extensions; use jsonrpsee_types::{InvalidRequestId, Response, ResponsePayload}; use tokio::sync::{mpsc, oneshot}; -use super::helpers::call_with_timeout_sub; - /// RpcService error. #[derive(Debug, thiserror::Error)] pub enum Error { @@ -45,17 +36,14 @@ impl From for Error { /// RpcService implementation for the async client. #[derive(Debug, Clone)] -pub struct RpcService { - tx: mpsc::Sender, - request_timeout: Duration, -} +pub struct RpcService(mpsc::Sender); impl RpcService { // This is a private interface but we need to expose it for the async client // to be able to create the service. #[allow(private_interfaces)] - pub(crate) fn new(tx: mpsc::Sender, request_timeout: Duration) -> Self { - Self { tx, request_timeout } + pub(crate) fn new(tx: mpsc::Sender) -> Self { + Self(tx) } } @@ -64,8 +52,7 @@ impl RpcServiceT for RpcService { type Error = Error; fn call<'a>(&self, request: Request<'a>) -> impl Future> + Send + 'a { - let tx = self.tx.clone(); - let request_timeout = self.request_timeout; + let tx = self.0.clone(); async move { let raw = serde_json::to_string(&request).map_err(client_err)?; @@ -84,7 +71,7 @@ impl RpcServiceT for RpcService { })) .await?; - let (subscribe_rx, sub_id) = call_with_timeout_sub(request_timeout, send_back_rx).await??; + let (subscribe_rx, sub_id) = send_back_rx.await??; let s = serde_json::value::to_raw_value(&sub_id).map_err(client_err)?; @@ -106,18 +93,16 @@ impl RpcServiceT for RpcService { id: request.id.clone().into_owned(), })) .await?; - let rp = call_with_timeout(request_timeout, send_back_rx).await??; + let rp = send_back_rx.await?.map_err(|e| ClientError::InvalidRequestId(e))?; Ok(MethodResponse::method_call(rp, request.extensions)) } } } - .boxed() } fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { - let tx = self.tx.clone(); - let request_timeout = self.request_timeout; + let tx = self.0.clone(); async move { let (send_back_tx, send_back_rx) = oneshot::channel(); @@ -128,7 +113,7 @@ impl RpcServiceT for RpcService { )))?; tx.send(FrontToBack::Batch(BatchMessage { raw, ids: id_range, send_back: send_back_tx })).await?; - let json = call_with_timeout(request_timeout, send_back_rx).await??; + let json = send_back_rx.await?.map_err(|e| ClientError::InvalidRequestId(e))?; let mut extensions = Extensions::new(); @@ -141,29 +126,19 @@ impl RpcServiceT for RpcService { Ok(MethodResponse::batch(json, extensions)) } - .boxed() } fn notification<'a>( &self, n: Notification<'a>, ) -> impl Future> + Send + 'a { - let tx = self.tx.clone(); - let request_timeout = self.request_timeout; + let tx = self.0.clone(); async move { let raw = serde_json::to_string(&n).map_err(client_err)?; - let fut = tx.send(FrontToBack::Notification(raw)); - - tokio::pin!(fut); - - match future::select(fut, Delay::new(request_timeout)).await { - Either::Left((Ok(()), _)) => Ok(MethodResponse::notification(n.extensions)), - Either::Left((Err(e), _)) => Err(e.into()), - Either::Right((_, _)) => Err(ClientError::RequestTimeout.into()), - } + tx.send(FrontToBack::Notification(raw)).await?; + Ok(MethodResponse::notification(n.extensions)) } - .boxed() } } From 8161e01c858653f5b95c2080cc43649c3c8ed6da Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 3 Apr 2025 21:47:52 +0200 Subject: [PATCH 35/52] more nit fixing --- client/http-client/Cargo.toml | 1 - client/http-client/src/rpc_service.rs | 4 ---- client/http-client/src/tests.rs | 10 ++-------- client/ws-client/src/tests.rs | 10 ++-------- core/Cargo.toml | 2 +- core/src/client/mod.rs | 2 +- core/src/server/method_response.rs | 2 +- examples/examples/jsonrpsee_as_service.rs | 12 +++-------- examples/examples/rpc_middleware.rs | 9 +++------ examples/examples/rpc_middleware_client.rs | 18 +++++------------ .../examples/rpc_middleware_rate_limiting.rs | 2 +- .../server_with_connection_details.rs | 12 +++-------- server/src/tests/http.rs | 6 +++--- tests/tests/helpers.rs | 9 +++------ tests/tests/integration_tests.rs | 20 ++++--------------- tests/tests/metrics.rs | 20 ++++++------------- 16 files changed, 38 insertions(+), 101 deletions(-) diff --git a/client/http-client/Cargo.toml b/client/http-client/Cargo.toml index dbc2768e38..58d198ed8e 100644 --- a/client/http-client/Cargo.toml +++ b/client/http-client/Cargo.toml @@ -19,7 +19,6 @@ workspace = true [dependencies] async-trait = { workspace = true } base64 = { workspace = true } -futures-util = { workspace = true } hyper = { workspace = true, features = ["client", "http1", "http2"] } hyper-rustls = { workspace = true, features = ["http1", "http2", "tls12", "logging", "ring"], optional = true } hyper-util = { workspace = true, features = ["client", "client-legacy", "tokio", "http1", "http2"] } diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index 4aef0b54b6..a271564372 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use futures_util::FutureExt; use hyper::{body::Bytes, http::Extensions}; use jsonrpsee_core::{ BoxError, JsonRawValue, @@ -47,7 +46,6 @@ where let json_rp: Response> = serde_json::from_slice(&bytes)?; Ok(MethodResponse::method_call(json_rp.into_owned().into(), request.extensions)) } - .boxed() } fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { @@ -73,7 +71,6 @@ where Ok(MethodResponse::batch(rp, extensions)) } - .boxed() } fn notification<'a>( @@ -87,6 +84,5 @@ where service.send(raw).await.map_err(|e| Error::Transport(e.into()))?; Ok(MethodResponse::notification(notif.extensions)) } - .boxed() } } diff --git a/client/http-client/src/tests.rs b/client/http-client/src/tests.rs index 5b620cf1b4..f6af48e623 100644 --- a/client/http-client/src/tests.rs +++ b/client/http-client/src/tests.rs @@ -173,16 +173,10 @@ async fn batch_request_with_failed_call_works() { assert_eq!(res.len(), 3); let successful_calls: Vec<_> = res.iter().filter_map(|r| r.as_ref().ok()).collect(); - let failed_calls: Vec<_> = res - .iter() - .filter_map(|r| match r { - Err(e) => Some(e), - _ => None, - }) - .collect(); + let failed_calls: Vec<_> = res.iter().filter_map(|r| r.clone().err()).collect(); assert_eq!(successful_calls, vec!["hello", "here's your swag"]); - assert_eq!(failed_calls, vec![&ErrorObject::from(ErrorCode::MethodNotFound)]); + assert_eq!(failed_calls, vec![ErrorObject::from(ErrorCode::MethodNotFound)]); } #[tokio::test] diff --git a/client/ws-client/src/tests.rs b/client/ws-client/src/tests.rs index 51560ffd68..eb0fd25110 100644 --- a/client/ws-client/src/tests.rs +++ b/client/ws-client/src/tests.rs @@ -359,16 +359,10 @@ async fn batch_request_with_failed_call_works() { assert_eq!(res.len(), 3); let successful_calls: Vec<_> = res.iter().filter_map(|r| r.as_ref().ok()).collect(); - let failed_calls: Vec<_> = res - .iter() - .filter_map(|r| match r { - Err(e) => Some(e), - _ => None, - }) - .collect(); + let failed_calls: Vec<_> = res.iter().filter_map(|r| r.clone().err()).collect(); assert_eq!(successful_calls, vec!["hello", "here's your swag"]); - assert_eq!(failed_calls, vec![&ErrorObject::from(ErrorCode::MethodNotFound)]); + assert_eq!(failed_calls, vec![ErrorObject::from(ErrorCode::MethodNotFound)]); } #[tokio::test] diff --git a/core/Cargo.toml b/core/Cargo.toml index c5d323ac0b..5d1849366f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -21,7 +21,7 @@ async-trait = { workspace = true } jsonrpsee-types = { workspace = true } thiserror = { workspace = true } serde = { workspace = true } -serde_json = { workspace = true, features = ["std"] } +serde_json = { workspace = true, features = ["std", "raw_value"] } tracing = { workspace = true } # optional deps diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index ac486b6bc3..a4b1ddabef 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -787,7 +787,7 @@ impl ToJson for MethodResponse { fn to_json(&self) -> Result, serde_json::Error> { match &self.inner { MethodResponseKind::MethodCall(call) => call.to_json(), - MethodResponseKind::Notification => Ok(RawValue::NULL.to_owned()), + MethodResponseKind::Notification => Ok(Box::::default()), MethodResponseKind::Batch(json) => serde_json::value::to_raw_value(json), MethodResponseKind::Subscription(s) => serde_json::value::to_raw_value(&s.rp), } diff --git a/core/src/server/method_response.rs b/core/src/server/method_response.rs index fc3d8aece8..9e8d7dc9f2 100644 --- a/core/src/server/method_response.rs +++ b/core/src/server/method_response.rs @@ -261,7 +261,7 @@ impl MethodResponse { /// Create notification response which is a response that doesn't expect a reply. pub fn notification() -> Self { Self { - json: RawValue::NULL.to_owned(), + json: Box::::default(), success_or_error: MethodResponseResult::Success, kind: ResponseKind::Notification, on_close: None, diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index bab6380d35..a7d7836758 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -79,10 +79,7 @@ where type Error = S::Error; type Response = S::Response; - fn call<'a>( - &self, - req: Request<'a>, - ) -> impl Future> + Send + 'a { + fn call<'a>(&self, req: Request<'a>) -> impl Future> + Send + 'a { if req.method_name() == "trusted_call" { let Some(Ok(_)) = self.headers.get(AUTHORIZATION).map(|auth| auth.to_str()) else { let rp = MethodResponse::error(req.id, ErrorObject::borrowed(-32000, "Authorization failed", None)); @@ -98,17 +95,14 @@ where } } - fn batch<'a>( - &self, - batch: Batch<'a>, - ) -> impl Future> + Send + 'a { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { ResponseFuture::future(self.inner.batch(batch)) } fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future> + Send + 'a { + ) -> impl Future> + Send + 'a { ResponseFuture::future(self.inner.notification(n)) } } diff --git a/examples/examples/rpc_middleware.rs b/examples/examples/rpc_middleware.rs index dee8b62380..a3092fa0bd 100644 --- a/examples/examples/rpc_middleware.rs +++ b/examples/examples/rpc_middleware.rs @@ -46,7 +46,6 @@ use std::net::SocketAddr; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; -use futures::FutureExt; use jsonrpsee::core::client::ClientT; use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::rpc_params; @@ -84,14 +83,13 @@ where println!("{role} processed calls={} on the connection", count.load(Ordering::SeqCst)); rp } - .boxed() } fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { let len = batch.as_batch_entries().len(); self.count.fetch_add(len, Ordering::SeqCst); println!("{} processed calls={} on the connection", self.role, self.count.load(Ordering::SeqCst)); - Box::pin(self.service.batch(batch)) + self.service.batch(batch) } fn notification<'a>( @@ -128,21 +126,20 @@ where rp } - .boxed() } fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { let len = batch.as_batch_entries().len(); self.count.fetch_add(len, Ordering::SeqCst); println!("{}, processed calls={} in total", self.role, self.count.load(Ordering::SeqCst)); - Box::pin(self.service.batch(batch)) + self.service.batch(batch) } fn notification<'a>( &self, n: Notification<'a>, ) -> impl Future> + Send + 'a { - Box::pin(self.service.notification(n)) + self.service.notification(n) } } diff --git a/examples/examples/rpc_middleware_client.rs b/examples/examples/rpc_middleware_client.rs index 55ff60b5db..1ce6d9158f 100644 --- a/examples/examples/rpc_middleware_client.rs +++ b/examples/examples/rpc_middleware_client.rs @@ -37,7 +37,6 @@ use std::net::SocketAddr; use std::ops::Deref; use std::sync::{Arc, Mutex}; -use futures::FutureExt; use jsonrpsee::core::client::{ClientT, MethodResponse, MethodResponseKind}; use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::rpc_params; @@ -86,10 +85,7 @@ where type Error = S::Error; type Response = S::Response; - fn call<'a>( - &self, - req: Request<'a>, - ) -> impl Future> + Send + 'a { + fn call<'a>(&self, req: Request<'a>) -> impl Future> + Send + 'a { let m = self.metrics.clone(); let service = self.service.clone(); @@ -114,23 +110,19 @@ where rp } - .boxed() } - fn batch<'a>( - &self, - batch: Batch<'a>, - ) -> impl Future> + Send + 'a { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { self.metrics.lock().unwrap().batch_calls += 1; - Box::pin(self.service.batch(batch)) + self.service.batch(batch) } fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future> + Send + 'a { + ) -> impl Future> + Send + 'a { self.metrics.lock().unwrap().notifications += 1; - Box::pin(self.service.notification(n)) + self.service.notification(n) } } diff --git a/examples/examples/rpc_middleware_rate_limiting.rs b/examples/examples/rpc_middleware_rate_limiting.rs index 21e8b654f6..d1cb12ff70 100644 --- a/examples/examples/rpc_middleware_rate_limiting.rs +++ b/examples/examples/rpc_middleware_rate_limiting.rs @@ -125,7 +125,7 @@ where if self.rate_limit_deny() { let error = ErrorObject::borrowed(-32000, "RPC rate limit", None); let error = MethodResponse::error(jsonrpsee::types::Id::Null, error); - return ResponseFuture::ready(error); + ResponseFuture::ready(error) } else { // NOTE: this count as batch call a single call which may not // be the desired behavior. diff --git a/examples/examples/server_with_connection_details.rs b/examples/examples/server_with_connection_details.rs index abcb685b00..07c4f5ba78 100644 --- a/examples/examples/server_with_connection_details.rs +++ b/examples/examples/server_with_connection_details.rs @@ -53,27 +53,21 @@ impl RpcServiceT for LoggingMiddleware { type Error = S::Error; type Response = S::Response; - fn call<'a>( - &self, - request: Request<'a>, - ) -> impl Future> + Send + 'a { + fn call<'a>(&self, request: Request<'a>) -> impl Future> + Send + 'a { tracing::info!("Received request: {:?}", request); assert!(request.extensions().get::().is_some()); self.0.call(request) } - fn batch<'a>( - &self, - batch: Batch<'a>, - ) -> impl Future> + Send + 'a { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { self.0.batch(batch) } fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future> + Send + 'a { + ) -> impl Future> + Send + 'a { self.0.notification(n) } } diff --git a/server/src/tests/http.rs b/server/src/tests/http.rs index cd934536ef..a3016202f9 100644 --- a/server/src/tests/http.rs +++ b/server/src/tests/http.rs @@ -71,7 +71,7 @@ where req.extensions_mut().insert(StatusCode::OK); } - self.service.call(req).boxed() + self.service.call(req) } fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future> + Send + 'a { @@ -83,14 +83,14 @@ where } } - self.service.batch(batch).boxed() + self.service.batch(batch) } fn notification<'a>( &self, n: Notification<'a>, ) -> impl Future> + Send + 'a { - self.service.notification(n).boxed() + self.service.notification(n) } } diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index 594c737365..138b39da55 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -159,22 +159,19 @@ pub async fn server() -> SocketAddr { fn call<'a>( &self, mut request: jsonrpsee::types::Request<'a>, - ) -> impl Future> + Send + 'a { + ) -> impl Future> + Send + 'a { request.extensions_mut().insert(self.connection_id); self.inner.call(request) } - fn batch<'a>( - &self, - batch: Batch<'a>, - ) -> impl Future> + Send + 'a { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { self.inner.batch(batch) } fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future> + Send + 'a { + ) -> impl Future> + Send + 'a { self.inner.notification(n) } } diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index a17c21cf64..7c6d2741cc 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -767,15 +767,9 @@ async fn ws_batch_works() { assert_eq!(res.num_failed_calls(), 1); let ok_responses: Vec<_> = res.iter().filter_map(|r| r.as_ref().ok()).collect(); - let err_responses: Vec<_> = res - .iter() - .filter_map(|r| match r { - Err(e) => Some(e), - _ => None, - }) - .collect(); + let err_responses: Vec<_> = res.iter().filter_map(|r| r.clone().err()).collect(); assert_eq!(ok_responses, vec!["hello"]); - assert_eq!(err_responses, vec![&ErrorObject::borrowed(UNKNOWN_ERROR_CODE, "err", None)]); + assert_eq!(err_responses, vec![ErrorObject::borrowed(UNKNOWN_ERROR_CODE, "err", None)]); } #[tokio::test] @@ -807,15 +801,9 @@ async fn http_batch_works() { assert_eq!(res.num_failed_calls(), 1); let ok_responses: Vec<_> = res.iter().filter_map(|r| r.as_ref().ok()).collect(); - let err_responses: Vec<_> = res - .iter() - .filter_map(|r| match r { - Err(e) => Some(e), - _ => None, - }) - .collect(); + let err_responses: Vec<_> = res.iter().filter_map(|r| r.clone().err()).collect(); assert_eq!(ok_responses, vec!["hello"]); - assert_eq!(err_responses, vec![&ErrorObject::borrowed(UNKNOWN_ERROR_CODE, "err", None)]); + assert_eq!(err_responses, vec![ErrorObject::borrowed(UNKNOWN_ERROR_CODE, "err", None)]); } #[tokio::test] diff --git a/tests/tests/metrics.rs b/tests/tests/metrics.rs index 9f16d35464..c4369e8b75 100644 --- a/tests/tests/metrics.rs +++ b/tests/tests/metrics.rs @@ -34,7 +34,6 @@ use std::net::SocketAddr; use std::sync::{Arc, Mutex}; use std::time::Duration; -use futures::FutureExt; use helpers::init_logger; use jsonrpsee::core::middleware::{Batch, Notification, Request, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::core::{ClientError, client::ClientT}; @@ -67,10 +66,7 @@ where type Error = S::Error; type Response = S::Response; - fn call<'a>( - &self, - request: Request<'a>, - ) -> impl Future> + Send + 'a { + fn call<'a>(&self, request: Request<'a>) -> impl Future> + Send + 'a { let counter = self.counter.clone(); let service = self.service.clone(); @@ -90,28 +86,24 @@ where { let mut n = counter.lock().unwrap(); n.requests.1 += 1; - if rp.as_ref().map_or(false, |r| r.is_success()) { + if rp.as_ref().is_ok_and(|r| r.is_success()) { n.calls.get_mut(&name).unwrap().1.push(id.into_owned()); } } rp } - .boxed() } - fn batch<'a>( - &self, - batch: Batch<'a>, - ) -> impl Future> + Send + 'a { - self.service.batch(batch).boxed() + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { + self.service.batch(batch) } fn notification<'a>( &self, n: Notification<'a>, - ) -> impl Future> + Send + 'a { - self.service.notification(n).boxed() + ) -> impl Future> + Send + 'a { + self.service.notification(n) } } From 1f73b97726aed0aeaed4062d8380602fba19399f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 4 Apr 2025 10:56:54 +0200 Subject: [PATCH 36/52] have pass over examples --- Cargo.toml | 3 + core/src/client/async_client/mod.rs | 2 +- core/src/client/async_client/rpc_service.rs | 4 +- core/src/middleware/mod.rs | 16 ++- examples/examples/jsonrpsee_as_service.rs | 102 +++++++++++++----- .../jsonrpsee_server_low_level_api.rs | 3 +- examples/examples/rpc_middleware.rs | 2 +- .../server_with_connection_details.rs | 2 + 8 files changed, 102 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 81cae45861..ef8766237a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,3 +98,6 @@ rust_2024_compatibility = { level = "warn", priority = -1 } missing_docs = { level = "warn", priority = -1 } missing_debug_implementations = { level = "warn", priority = -1 } missing_copy_implementations = { level = "warn", priority = -1 } + +[workspace.lints.clippy] +manual_async_fn = { level = "allow", priority = -1 } \ No newline at end of file diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index e597993cad..c78f58460a 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -416,7 +416,7 @@ impl ClientBuilder { Client { to_back: to_back.clone(), - service: self.service_builder.service(RpcService::new(to_back.clone(), self.request_timeout)), + service: self.service_builder.service(RpcService::new(to_back.clone())), request_timeout: self.request_timeout, error: ErrorFromBack::new(to_back, disconnect_reason), id_manager: RequestIdManager::new(self.id_kind), diff --git a/core/src/client/async_client/rpc_service.rs b/core/src/client/async_client/rpc_service.rs index 8c504d0a07..9aa850d95e 100644 --- a/core/src/client/async_client/rpc_service.rs +++ b/core/src/client/async_client/rpc_service.rs @@ -93,7 +93,7 @@ impl RpcServiceT for RpcService { id: request.id.clone().into_owned(), })) .await?; - let rp = send_back_rx.await?.map_err(|e| ClientError::InvalidRequestId(e))?; + let rp = send_back_rx.await?.map_err(client_err)?; Ok(MethodResponse::method_call(rp, request.extensions)) } @@ -113,7 +113,7 @@ impl RpcServiceT for RpcService { )))?; tx.send(FrontToBack::Batch(BatchMessage { raw, ids: id_range, send_back: send_back_tx })).await?; - let json = send_back_rx.await?.map_err(|e| ClientError::InvalidRequestId(e))?; + let json = send_back_rx.await?.map_err(client_err)?; let mut extensions = Extensions::new(); diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 13c0c930ed..9a311ff776 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -27,7 +27,7 @@ impl<'a> InvalidRequest<'a> { /// Create a new `InvalidRequest` error response. pub fn new(id: Id<'a>) -> Self { let payload = jsonrpsee_types::ResponsePayload::error(ErrorObject::from(ErrorCode::InvalidRequest)); - let response = Response::new(payload.into(), id); + let response = Response::new(payload, id); Self(response) } @@ -65,6 +65,10 @@ impl Serialize for Batch<'_> { // // It is used to indicate the RpcServiceT::batch method that the request is invalid // and should replied with an error entry. + // + // This clippy lint is faulty because `Result::ok` consumes the value + // and we don't want to consume the value here. + #[allow(clippy::manual_ok_err)] let only_ok: Vec<&BatchEntry> = self .inner .iter() @@ -138,6 +142,16 @@ impl<'a> Batch<'a> { pub fn id_range(&self) -> Option> { self.id_range.clone() } + + /// Get the length of the batch. + pub fn len(&self) -> usize { + self.inner.len() + } + + /// Check if the batch is empty. + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } } #[derive(Debug, Clone)] diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index a7d7836758..d0e5beff1f 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -31,6 +31,7 @@ //! The typical use-case for this is when one wants to have //! access to HTTP related things. +use std::convert::Infallible; use std::net::SocketAddr; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -38,14 +39,14 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use futures::FutureExt; use hyper::HeaderMap; use hyper::header::AUTHORIZATION; -use jsonrpsee::core::middleware::{Batch, Notification, ResponseFuture, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Batch, BatchEntry, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::core::{BoxError, async_trait}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{ ServerConfig, ServerHandle, StopHandle, TowerServiceBuilder, serve_with_graceful_shutdown, stop_channel, }; -use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, Request}; +use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, Id, Request}; use jsonrpsee::ws_client::{HeaderValue, WsClientBuilder}; use jsonrpsee::{MethodResponse, Methods}; use tokio::net::TcpListener; @@ -62,6 +63,10 @@ struct Metrics { success_http_calls: Arc, } +fn auth_reject_error() -> ErrorObjectOwned { + ErrorObject::owned(-32999, "HTTP Authorization header is missing", None::<()>) +} + #[derive(Clone)] struct AuthorizationMiddleware { headers: HeaderMap, @@ -70,9 +75,37 @@ struct AuthorizationMiddleware { transport_label: &'static str, } +impl AuthorizationMiddleware { + /// Authorize the request by checking the `Authorization` header. + /// + /// + /// In this example for simplicity, the authorization value is not checked + // and used because it's just a toy example. + fn auth_method_call(&self, req: &Request<'_>) -> bool { + if req.method_name() == "trusted_call" { + let Some(Ok(_)) = self.headers.get(AUTHORIZATION).map(|auth| auth.to_str()) else { return false }; + } + + true + } + + /// Authorize the notification by checking the `Authorization` header. + /// + /// Because notifications are not expected to return a response, we + /// return a `MethodResponse` by injecting an error into the extensions + /// which could be read by other middleware or the server. + fn auth_notif(&self, notif: &Notification<'_>) -> bool { + if notif.method_name() == "trusted_call" { + let Some(Ok(_)) = self.headers.get(AUTHORIZATION).map(|auth| auth.to_str()) else { return false }; + } + + true + } +} + impl RpcServiceT for AuthorizationMiddleware where - S: Send + Clone + Sync + RpcServiceT, + S: Send + Clone + Sync + RpcServiceT + 'static, S::Error: Into + Send + 'static, S::Response: Send, { @@ -80,30 +113,53 @@ where type Response = S::Response; fn call<'a>(&self, req: Request<'a>) -> impl Future> + Send + 'a { - if req.method_name() == "trusted_call" { - let Some(Ok(_)) = self.headers.get(AUTHORIZATION).map(|auth| auth.to_str()) else { - let rp = MethodResponse::error(req.id, ErrorObject::borrowed(-32000, "Authorization failed", None)); - return ResponseFuture::ready(rp); - }; - - // In this example for simplicity, the authorization value is not checked - // and used because it's just a toy example. - - ResponseFuture::future(self.inner.call(req)) - } else { - ResponseFuture::future(self.inner.call(req)) + let this = self.clone(); + let auth_ok = this.auth_method_call(&req); + + async move { + // If the authorization header is missing, it's recommended to + // to return the response as MethodResponse::error instead of + // returning an error from the service. + // + // This way the error is returned as a JSON-RPC error + if !auth_ok { + return Ok(MethodResponse::error(req.id, auth_reject_error())); + } + this.inner.call(req).await } } - fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { - ResponseFuture::future(self.inner.batch(batch)) + fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future> + Send + 'a { + // Check the authorization header for each entry in the batch. + // If any entry is unauthorized, return an error. + // + // It might be better to return an error for each entry + // instead of returning an error for the whole batch. + for entry in batch.as_mut_batch_entries() { + match entry { + Ok(BatchEntry::Call(req)) => { + if self.auth_method_call(req) { + return async { Ok(MethodResponse::error(Id::Null, auth_reject_error())) }.boxed(); + } + } + Ok(BatchEntry::Notification(notif)) => { + if self.auth_notif(notif) { + return async { Ok(MethodResponse::error(Id::Null, auth_reject_error())) }.boxed(); + } + } + // Ignore parse error. + Err(_err) => {} + } + } + + self.inner.batch(batch).boxed() } fn notification<'a>( &self, n: Notification<'a>, ) -> impl Future> + Send + 'a { - ResponseFuture::future(self.inner.notification(n)) + self.inner.notification(n) } } @@ -248,10 +304,7 @@ async fn run_server(metrics: Metrics) -> anyhow::Result { async move { tracing::info!("Opened WebSocket connection"); metrics.opened_ws_connections.fetch_add(1, Ordering::Relaxed); - // https://github.com/rust-lang/rust/issues/102211 the error type can't be inferred - // to be `Box` so we need to convert it to a concrete type - // as workaround. - svc.call(req).await.map_err(|e| anyhow::anyhow!("{:?}", e)) + svc.call(req).await } .boxed() } else { @@ -266,10 +319,7 @@ async fn run_server(metrics: Metrics) -> anyhow::Result { } tracing::info!("Closed HTTP connection"); - // https://github.com/rust-lang/rust/issues/102211 the error type can't be inferred - // to be `Box` so we need to convert it to a concrete type - // as workaround. - rp.map_err(|e| anyhow::anyhow!("{:?}", e)) + rp } .boxed() } diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index 024c094b7d..509b3af215 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -111,8 +111,9 @@ where let _ = state.try_send(()); Ok(MethodResponse::error(Id::Null, ErrorObject::borrowed(-32000, "RPC rate limit", None))) } else { + let batch_len = batch.len(); let rp = service.batch(batch).await; - *lock += 10; + *lock += batch_len; rp } } diff --git a/examples/examples/rpc_middleware.rs b/examples/examples/rpc_middleware.rs index a3092fa0bd..cc2cb18838 100644 --- a/examples/examples/rpc_middleware.rs +++ b/examples/examples/rpc_middleware.rs @@ -96,7 +96,7 @@ where &self, n: Notification<'a>, ) -> impl Future> + Send + 'a { - Box::pin(self.service.notification(n)) + self.service.notification(n) } } diff --git a/examples/examples/server_with_connection_details.rs b/examples/examples/server_with_connection_details.rs index 07c4f5ba78..9291f45c0b 100644 --- a/examples/examples/server_with_connection_details.rs +++ b/examples/examples/server_with_connection_details.rs @@ -61,6 +61,7 @@ impl RpcServiceT for LoggingMiddleware { } fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { + tracing::info!("Received batch: {:?}", batch); self.0.batch(batch) } @@ -68,6 +69,7 @@ impl RpcServiceT for LoggingMiddleware { &self, n: Notification<'a>, ) -> impl Future> + Send + 'a { + tracing::info!("Received notif: {:?}", n); self.0.notification(n) } } From de1461d2c58171bb89c67c2c5deaf51dc20d3b3f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 4 Apr 2025 13:19:12 +0200 Subject: [PATCH 37/52] show proper batch middleware example --- core/src/middleware/mod.rs | 50 ++++++++++++----------- examples/examples/jsonrpsee_as_service.rs | 43 +++++++++++-------- server/src/server.rs | 4 +- 3 files changed, 54 insertions(+), 43 deletions(-) diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 9a311ff776..9d30b19aab 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -3,7 +3,7 @@ pub mod layer; use futures_util::future::{Either, Future}; -use jsonrpsee_types::{ErrorCode, ErrorObject, Id, InvalidRequestId, Response}; +use jsonrpsee_types::{ErrorObject, Id, InvalidRequestId}; use pin_project::pin_project; use serde::Serialize; use serde_json::value::RawValue; @@ -19,40 +19,42 @@ pub type Notification<'a> = jsonrpsee_types::Notification<'a, Option(Response<'a, ()>); - -impl<'a> InvalidRequest<'a> { - /// Create a new `InvalidRequest` error response. - pub fn new(id: Id<'a>) -> Self { - let payload = jsonrpsee_types::ResponsePayload::error(ErrorObject::from(ErrorCode::InvalidRequest)); - let response = Response::new(payload, id); +pub struct ErrorResponse<'a>(jsonrpsee_types::Response<'a, ()>); + +impl<'a> ErrorResponse<'a> { + /// Create a new error response. + pub fn new(id: Id<'a>, err: ErrorObject<'a>) -> Self { + let payload = jsonrpsee_types::ResponsePayload::Error(err); + let response = jsonrpsee_types::Response::new(payload, id); Self(response) } - /// Consume the `InvalidRequest` to get the error object and id. + /// Get the parts of the error response.q pub fn into_parts(self) -> (ErrorObject<'a>, Id<'a>) { - match self.0.payload { - jsonrpsee_types::ResponsePayload::Error(err) => (err, self.0.id), - _ => unreachable!("InvalidRequest::new should only create an error response; qed"), - } + let err = match self.0.payload { + jsonrpsee_types::ResponsePayload::Error(err) => err, + _ => unreachable!("ErrorResponse can only be created from error payload; qed"), + }; + (err, self.0.id) } } -impl Serialize for InvalidRequest<'_> { +impl<'a> Serialize for ErrorResponse<'a> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - self.0.serialize(serializer) + serde::Serialize::serialize(&self.0, serializer) } } /// A batch of JSON-RPC calls and notifications. #[derive(Debug, Default)] pub struct Batch<'a> { - inner: Vec, InvalidRequest<'a>>>, + inner: Vec, ErrorResponse<'a>>>, id_range: Option>, } @@ -96,7 +98,7 @@ impl<'a> Batch<'a> { } /// Create a new batch from a list of batch entries without an id range. - pub fn from_batch_entries(inner: Vec, InvalidRequest<'a>>>) -> Self { + pub fn from_batch_entries(inner: Vec, ErrorResponse<'a>>>) -> Self { Self { inner, id_range: None } } @@ -122,17 +124,17 @@ impl<'a> Batch<'a> { } /// Get an iterator over the batch entries. - pub fn as_batch_entries(&self) -> &[Result, InvalidRequest<'a>>] { + pub fn as_batch_entries(&self) -> &[Result, ErrorResponse<'a>>] { &self.inner } /// Get a mutable iterator over the batch entries. - pub fn as_mut_batch_entries(&mut self) -> &mut [Result, InvalidRequest<'a>>] { + pub fn as_mut_batch_entries(&mut self) -> &mut [Result, ErrorResponse<'a>>] { &mut self.inner } /// Consume the batch and return the inner entries. - pub fn into_batch_entries(self) -> Vec, InvalidRequest<'a>>> { + pub fn into_batch_entries(self) -> Vec, ErrorResponse<'a>>> { self.inner } @@ -395,6 +397,8 @@ where #[cfg(test)] mod tests { + use jsonrpsee_types::{ErrorCode, ErrorObject}; + #[test] fn serialize_batch_entry() { use super::{BatchEntry, Notification, Request}; @@ -417,7 +421,7 @@ mod tests { #[test] fn serialize_batch_works() { - use super::{Batch, BatchEntry, InvalidRequest, Notification, Request}; + use super::{Batch, BatchEntry, ErrorResponse, Notification, Request}; use jsonrpsee_types::Id; let req = Request::borrowed("say_hello", None, Id::Number(1)); @@ -425,7 +429,7 @@ mod tests { let batch = Batch::from_batch_entries(vec![ Ok(BatchEntry::Call(req)), Ok(BatchEntry::Notification(notification)), - Err(InvalidRequest::new(Id::Null)), + Err(ErrorResponse::new(Id::Number(2), ErrorObject::from(ErrorCode::InvalidRequest))), ]); assert_eq!( serde_json::to_string(&batch).unwrap(), diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index d0e5beff1f..06c5c66863 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -39,14 +39,14 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use futures::FutureExt; use hyper::HeaderMap; use hyper::header::AUTHORIZATION; -use jsonrpsee::core::middleware::{Batch, BatchEntry, Notification, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Batch, BatchEntry, ErrorResponse, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::core::{BoxError, async_trait}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{ ServerConfig, ServerHandle, StopHandle, TowerServiceBuilder, serve_with_graceful_shutdown, stop_channel, }; -use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, Id, Request}; +use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, Request}; use jsonrpsee::ws_client::{HeaderValue, WsClientBuilder}; use jsonrpsee::{MethodResponse, Methods}; use tokio::net::TcpListener; @@ -129,30 +129,37 @@ where } } - fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future> + Send + 'a { + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { // Check the authorization header for each entry in the batch. - // If any entry is unauthorized, return an error. - // - // It might be better to return an error for each entry - // instead of returning an error for the whole batch. - for entry in batch.as_mut_batch_entries() { - match entry { + let entries: Vec<_> = batch + .into_batch_entries() + .into_iter() + .filter_map(|entry| match entry { Ok(BatchEntry::Call(req)) => { - if self.auth_method_call(req) { - return async { Ok(MethodResponse::error(Id::Null, auth_reject_error())) }.boxed(); + if self.auth_method_call(&req) { + Some(Ok(BatchEntry::Call(req))) + } else { + // If the authorization header is missing, we return + // a JSON-RPC error instead of an error from the service. + Some(Err(ErrorResponse::new(req.id, auth_reject_error()))) } } Ok(BatchEntry::Notification(notif)) => { - if self.auth_notif(notif) { - return async { Ok(MethodResponse::error(Id::Null, auth_reject_error())) }.boxed(); + if self.auth_notif(¬if) { + Some(Ok(BatchEntry::Notification(notif))) + } else { + // Just filter out the notification if the auth fails + // because notifications are not expected to return a response. + None } } - // Ignore parse error. - Err(_err) => {} - } - } + // Errors which could happen such as invalid JSON-RPC call + // or invalid JSON are just passed through. + Err(err) => Some(Err(err)), + }) + .collect(); - self.inner.batch(batch).boxed() + self.inner.batch(Batch::from_batch_entries(entries)).boxed() } fn notification<'a>( diff --git a/server/src/server.rs b/server/src/server.rs index 5afd041324..10906c5d86 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -45,7 +45,7 @@ use futures_util::io::{BufReader, BufWriter}; use hyper::body::Bytes; use hyper_util::rt::{TokioExecutor, TokioIo}; use jsonrpsee_core::id_providers::RandomIntegerIdProvider; -use jsonrpsee_core::middleware::{Batch, BatchEntry, InvalidRequest, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee_core::middleware::{Batch, BatchEntry, ErrorResponse, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::server::helpers::prepare_error; use jsonrpsee_core::server::{BoundedSubscriptions, ConnectionId, MethodResponse, MethodSink, Methods}; use jsonrpsee_core::traits::IdProvider; @@ -1297,7 +1297,7 @@ where Err(_) => Id::Null, }; - batch.push(Err(InvalidRequest::new(id))); + batch.push(Err(ErrorResponse::new(id, ErrorCode::InvalidRequest.into()))); } } From a148135637d3c170143e6e7172ab8b1e88eeea4a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 4 Apr 2025 19:09:12 +0200 Subject: [PATCH 38/52] middleware: clean up batch type --- client/http-client/src/client.rs | 2 +- client/http-client/src/rpc_service.rs | 12 +- core/src/client/async_client/mod.rs | 6 +- core/src/client/async_client/rpc_service.rs | 22 +-- core/src/middleware/mod.rs | 154 ++++++++---------- examples/examples/jsonrpsee_as_service.rs | 3 +- examples/examples/rpc_middleware.rs | 4 +- .../examples/rpc_middleware_modify_request.rs | 2 +- server/src/middleware/rpc.rs | 2 +- server/src/server.rs | 2 +- server/src/tests/http.rs | 2 +- 11 files changed, 91 insertions(+), 120 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 5a1e00c21d..3fe30b949a 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -419,7 +419,7 @@ where id, extensions: Extensions::new(), }; - batch_request.push(req)?; + batch_request.push(req); } let rp = run_future_until_timeout(self.transport.batch(batch_request), self.request_timeout).await?; diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index a271564372..8bd3291bae 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use hyper::{body::Bytes, http::Extensions}; +use hyper::body::Bytes; use jsonrpsee_core::{ BoxError, JsonRawValue, client::{Error, MethodResponse}, @@ -59,15 +59,7 @@ where .map(|r| r.into_owned().into()) .collect(); - let mut extensions = Extensions::new(); - - for call in batch.into_batch_entries() { - let Ok(call) = call else { - continue; - }; - - extensions.extend(call.into_extensions()); - } + let (_, extensions) = batch.into_parts(); Ok(MethodResponse::batch(rp, extensions)) } diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index c78f58460a..ffde5b9fea 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -41,7 +41,7 @@ use crate::client::{ SubscriptionKind, TransportReceiverT, TransportSenderT, }; use crate::error::RegisterMethodError; -use crate::middleware::{Batch, IsSubscription, Request, RpcServiceBuilder, RpcServiceT}; +use crate::middleware::{Batch, IsBatch, IsSubscription, Request, RpcServiceBuilder, RpcServiceT}; use crate::params::{BatchRequestBuilder, EmptyBatchRequest}; use crate::traits::ToRpcParams; use std::borrow::Cow as StdCow; @@ -537,9 +537,11 @@ where method: method.into(), params: params.map(StdCow::Owned), extensions: Extensions::new(), - })?; + }); } + b.extensions_mut().insert(IsBatch { id_range }); + let fut = self.service.batch(b); let json_values = self.run_future_until_timeout(fut).await?.into_batch().expect("Batch response"); diff --git a/core/src/client/async_client/rpc_service.rs b/core/src/client/async_client/rpc_service.rs index 9aa850d95e..8cd3eef484 100644 --- a/core/src/client/async_client/rpc_service.rs +++ b/core/src/client/async_client/rpc_service.rs @@ -3,11 +3,10 @@ use crate::{ BatchMessage, Error as ClientError, FrontToBack, MethodResponse, RequestMessage, SubscriptionMessage, SubscriptionResponse, }, - middleware::{Batch, IsSubscription, Notification, Request, RpcServiceT}, + middleware::{Batch, IsBatch, IsSubscription, Notification, Request, RpcServiceT}, }; -use http::Extensions; -use jsonrpsee_types::{InvalidRequestId, Response, ResponsePayload}; +use jsonrpsee_types::{Response, ResponsePayload}; use tokio::sync::{mpsc, oneshot}; /// RpcService error. @@ -108,21 +107,16 @@ impl RpcServiceT for RpcService { let (send_back_tx, send_back_rx) = oneshot::channel(); let raw = serde_json::to_string(&batch).map_err(client_err)?; - let id_range = batch.id_range().ok_or(ClientError::InvalidRequestId(InvalidRequestId::Invalid( - "Batch request id range missing".to_owned(), - )))?; + let id_range = batch + .extensions() + .get::() + .map(|b| b.id_range.clone()) + .expect("Batch ID range must be set in extensions"); tx.send(FrontToBack::Batch(BatchMessage { raw, ids: id_range, send_back: send_back_tx })).await?; let json = send_back_rx.await?.map_err(client_err)?; - let mut extensions = Extensions::new(); - - for entry in batch.into_batch_entries() { - let Ok(entry) = entry else { - continue; - }; - extensions.extend(entry.into_extensions()); - } + let (_, extensions) = batch.into_parts(); Ok(MethodResponse::batch(json, extensions)) } diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 9d30b19aab..5ef62f9906 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -3,9 +3,10 @@ pub mod layer; use futures_util::future::{Either, Future}; -use jsonrpsee_types::{ErrorObject, Id, InvalidRequestId}; +use jsonrpsee_types::{ErrorObject, Id}; use pin_project::pin_project; use serde::Serialize; +use serde::ser::SerializeSeq; use serde_json::value::RawValue; use tower::layer::LayerFn; use tower::layer::util::{Identity, Stack}; @@ -42,117 +43,93 @@ impl<'a> ErrorResponse<'a> { } } -impl<'a> Serialize for ErrorResponse<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serde::Serialize::serialize(&self.0, serializer) - } -} - -/// A batch of JSON-RPC calls and notifications. -#[derive(Debug, Default)] +/// A batch of JSON-RPC requests. +#[derive(Debug)] pub struct Batch<'a> { inner: Vec, ErrorResponse<'a>>>, - id_range: Option>, -} - -impl Serialize for Batch<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - // Err(InvalidRequest) is just an internal error type and should not be serialized. - // - // It is used to indicate the RpcServiceT::batch method that the request is invalid - // and should replied with an error entry. - // - // This clippy lint is faulty because `Result::ok` consumes the value - // and we don't want to consume the value here. - #[allow(clippy::manual_ok_err)] - let only_ok: Vec<&BatchEntry> = self - .inner - .iter() - .filter_map(|entry| match entry { - Ok(entry) => Some(entry), - Err(_) => None, - }) - .collect(); - - serde::Serialize::serialize(&only_ok, serializer) - } + extensions: Extensions, } impl std::fmt::Display for Batch<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let s = serde_json::to_string(&self.inner).expect("Batch serialization failed"); - f.write_str(&s) + let fmt = serde_json::to_string(self).map_err(|_| std::fmt::Error)?; + f.write_str(&fmt) } } impl<'a> Batch<'a> { + /// Create a new batch from a vector of batch entries. + pub fn from(entries: Vec, ErrorResponse<'a>>>) -> Self { + let mut extensions = Extensions::new(); + for entry in &entries { + if let Ok(entry) = entry { + extensions.extend(entry.extensions().clone()); + } + } + + Self { inner: entries, extensions } + } + /// Create a new empty batch. pub fn new() -> Self { - Self::default() + Self { inner: Vec::new(), extensions: Extensions::new() } } - /// Create a new batch from a list of batch entries without an id range. - pub fn from_batch_entries(inner: Vec, ErrorResponse<'a>>>) -> Self { - Self { inner, id_range: None } + /// Push a new batch entry to the batch. + pub fn push(&mut self, req: Request<'a>) { + self.extensions.extend(req.extensions().clone()); + self.inner.push(Ok(BatchEntry::Call(req))); } - /// Insert a new batch entry into the batch. - /// - /// Fails if the request id is not a number or the id range overflows. - pub fn push(&mut self, req: Request<'a>) -> Result<(), InvalidRequestId> { - let id = req.id().try_parse_inner_as_number()?; - - match self.id_range { - Some(ref mut range) => { - debug_assert!(id + 1 > range.end); - range.end = - id.checked_add(1).ok_or_else(|| InvalidRequestId::Invalid("Id range overflow".to_string()))?; - } - None => { - self.id_range = Some(id..id); - } - } - self.inner.push(Ok(BatchEntry::Call(req))); + /// Get the length of the batch. + pub fn len(&self) -> usize { + self.inner.len() + } - Ok(()) + /// Get an iterator over the batch. + pub fn iter(&self) -> impl Iterator, ErrorResponse<'a>>> { + self.inner.iter() } - /// Get an iterator over the batch entries. - pub fn as_batch_entries(&self) -> &[Result, ErrorResponse<'a>>] { - &self.inner + /// Get a mutuable iterator over the batch. + pub fn iter_mut(&mut self) -> impl Iterator, ErrorResponse<'a>>> { + self.inner.iter_mut() } - /// Get a mutable iterator over the batch entries. - pub fn as_mut_batch_entries(&mut self) -> &mut [Result, ErrorResponse<'a>>] { - &mut self.inner + /// Convert the batch into an iterator. + pub fn into_iter(self) -> impl Iterator, ErrorResponse<'a>>> { + self.inner.into_iter() } - /// Consume the batch and return the inner entries. - pub fn into_batch_entries(self) -> Vec, ErrorResponse<'a>>> { - self.inner + /// Consume the batch and and return the parts. + pub fn into_parts(self) -> (Vec, ErrorResponse<'a>>>, Extensions) { + (self.inner, self.extensions) } - /// Get the id range of the batch. - /// - /// This is only available if the batch has been constructed using `Batch::push`. - pub fn id_range(&self) -> Option> { - self.id_range.clone() + /// Get a reference to the extensions of the batch. + pub fn extensions(&self) -> &Extensions { + &self.extensions } - /// Get the length of the batch. - pub fn len(&self) -> usize { - self.inner.len() + /// Get a mutable reference to the extensions of the batch. + pub fn extensions_mut(&mut self) -> &mut Extensions { + &mut self.extensions } +} - /// Check if the batch is empty. - pub fn is_empty(&self) -> bool { - self.inner.is_empty() +impl Serialize for Batch<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.inner.len()))?; + for entry in &self.inner { + match entry { + Ok(entry) => seq.serialize_element(entry)?, + Err(err) => seq.serialize_element(&err.0)?, + } + } + seq.end() } } @@ -186,6 +163,13 @@ impl IsSubscription { } } +/// An extension type for the [`RpcServiceT::batch`] for the expected id range of the batch entries. +#[derive(Debug, Clone)] +pub struct IsBatch { + /// The range of ids for the batch entries. + pub id_range: std::ops::Range, +} + /// A batch entry specific for the [`RpcServiceT::batch`] method to support both /// method calls and notifications. #[derive(Debug, Clone, Serialize)] @@ -426,14 +410,14 @@ mod tests { let req = Request::borrowed("say_hello", None, Id::Number(1)); let notification = Notification::new("say_hello".into(), None); - let batch = Batch::from_batch_entries(vec![ + let batch = Batch::from(vec![ Ok(BatchEntry::Call(req)), Ok(BatchEntry::Notification(notification)), Err(ErrorResponse::new(Id::Number(2), ErrorObject::from(ErrorCode::InvalidRequest))), ]); assert_eq!( serde_json::to_string(&batch).unwrap(), - r#"[{"jsonrpc":"2.0","id":1,"method":"say_hello"},{"jsonrpc":"2.0","method":"say_hello","params":null}]"#, + r#"[{"jsonrpc":"2.0","id":1,"method":"say_hello"},{"jsonrpc":"2.0","method":"say_hello","params":null},{"jsonrpc":"2.0","id":2,"error":{"code":-32600,"message":"Invalid request"}}]"#, ); } } diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index 06c5c66863..7f7a2dfd37 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -132,7 +132,6 @@ where fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { // Check the authorization header for each entry in the batch. let entries: Vec<_> = batch - .into_batch_entries() .into_iter() .filter_map(|entry| match entry { Ok(BatchEntry::Call(req)) => { @@ -159,7 +158,7 @@ where }) .collect(); - self.inner.batch(Batch::from_batch_entries(entries)).boxed() + self.inner.batch(Batch::from(entries)).boxed() } fn notification<'a>( diff --git a/examples/examples/rpc_middleware.rs b/examples/examples/rpc_middleware.rs index cc2cb18838..166c36d5d6 100644 --- a/examples/examples/rpc_middleware.rs +++ b/examples/examples/rpc_middleware.rs @@ -86,7 +86,7 @@ where } fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { - let len = batch.as_batch_entries().len(); + let len = batch.len(); self.count.fetch_add(len, Ordering::SeqCst); println!("{} processed calls={} on the connection", self.role, self.count.load(Ordering::SeqCst)); self.service.batch(batch) @@ -129,7 +129,7 @@ where } fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { - let len = batch.as_batch_entries().len(); + let len = batch.len(); self.count.fetch_add(len, Ordering::SeqCst); println!("{}, processed calls={} in total", self.role, self.count.load(Ordering::SeqCst)); self.service.batch(batch) diff --git a/examples/examples/rpc_middleware_modify_request.rs b/examples/examples/rpc_middleware_modify_request.rs index 7b7e23f68a..fb907e85bd 100644 --- a/examples/examples/rpc_middleware_modify_request.rs +++ b/examples/examples/rpc_middleware_modify_request.rs @@ -77,7 +77,7 @@ where } fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future> + Send + 'a { - for call in batch.as_mut_batch_entries() { + for call in batch.iter_mut() { match call { Ok(BatchEntry::Call(call)) => { modify_method_call(call); diff --git a/server/src/middleware/rpc.rs b/server/src/middleware/rpc.rs index ded9ba00ac..61b26dfd74 100644 --- a/server/src/middleware/rpc.rs +++ b/server/src/middleware/rpc.rs @@ -153,7 +153,7 @@ impl RpcServiceT for RpcService { async move { let mut got_notification = false; - for batch_entry in batch.into_batch_entries() { + for batch_entry in batch.into_iter() { match batch_entry { Ok(BatchEntry::Call(req)) => { let rp = match service.call(req).await { diff --git a/server/src/server.rs b/server/src/server.rs index 10906c5d86..dfc7a10c1c 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -1301,7 +1301,7 @@ where } } - match rpc_service.batch(Batch::from_batch_entries(batch)).await { + match rpc_service.batch(Batch::from(batch)).await { Ok(rp) => rp, Err(e) => MethodResponse::error(Id::Null, rpc_middleware_error(e)), } diff --git a/server/src/tests/http.rs b/server/src/tests/http.rs index a3016202f9..3e9ce2cad1 100644 --- a/server/src/tests/http.rs +++ b/server/src/tests/http.rs @@ -75,7 +75,7 @@ where } fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future> + Send + 'a { - if let Some(Ok(last)) = batch.as_mut_batch_entries().last_mut() { + if let Some(Ok(last)) = batch.iter_mut().last() { if last.method_name().contains("err") { last.extensions_mut().insert(StatusCode::IM_A_TEAPOT); } else { From 5285fc4dd92a0603bf8ef1266a75f65aaaf2e32e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 4 Apr 2025 23:24:30 +0200 Subject: [PATCH 39/52] fix wasm build --- core/src/client/async_client/mod.rs | 12 +++++++----- core/src/middleware/mod.rs | 7 ++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index ffde5b9fea..7d9cad241d 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -457,11 +457,13 @@ impl Client { &self, fut: impl Future>, ) -> Result { - match tokio::time::timeout(self.request_timeout, fut).await { - Ok(Ok(r)) => Ok(r), - Ok(Err(RpcServiceError::Client(e))) => Err(e), - Ok(Err(RpcServiceError::FetchFromBackend)) => Err(self.on_disconnect().await), - Err(_) => Err(Error::RequestTimeout), + tokio::pin!(fut); + + match futures_util::future::select(fut, futures_timer::Delay::new(self.request_timeout)).await { + Either::Left((Ok(r), _)) => Ok(r), + Either::Left((Err(RpcServiceError::Client(e)), _)) => Err(e), + Either::Left((Err(RpcServiceError::FetchFromBackend), _)) => Err(self.on_disconnect().await), + Either::Right(_) => Err(Error::RequestTimeout), } } diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 5ef62f9906..5b0dc9bc6e 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -44,7 +44,7 @@ impl<'a> ErrorResponse<'a> { } /// A batch of JSON-RPC requests. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Batch<'a> { inner: Vec, ErrorResponse<'a>>>, extensions: Extensions, @@ -86,6 +86,11 @@ impl<'a> Batch<'a> { self.inner.len() } + /// Returns whether the batch is empty. + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + /// Get an iterator over the batch. pub fn iter(&self) -> impl Iterator, ErrorResponse<'a>>> { self.inner.iter() From 54c7fe3fc109fa9e967ad8657668dd41ab4988af Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 5 Apr 2025 06:58:50 +0200 Subject: [PATCH 40/52] Update Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ef8766237a..583cb517f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,4 +100,4 @@ missing_debug_implementations = { level = "warn", priority = -1 } missing_copy_implementations = { level = "warn", priority = -1 } [workspace.lints.clippy] -manual_async_fn = { level = "allow", priority = -1 } \ No newline at end of file +manual_async_fn = { level = "allow", priority = -1 } From cc7807ed8715139aec08d63310f370707f72b3db Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 5 Apr 2025 10:31:35 +0200 Subject: [PATCH 41/52] doc: fix typo --- core/src/middleware/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 5b0dc9bc6e..ac34a79b76 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -252,7 +252,7 @@ impl<'a> BatchEntry<'a> { } } -/// Present a JSON-RPC service that can process JSON-RPC calls, notifications, and batch requests. +/// Represent a JSON-RPC service that can process JSON-RPC calls, notifications, and batch requests. /// /// This trait is similar to [`tower::Service`] but it's specialized for JSON-RPC operations. /// From f722acdae0a5d8d449975fe461d4db58ad710d7b Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 5 Apr 2025 10:57:05 +0200 Subject: [PATCH 42/52] core: remove tracing mod --- core/src/lib.rs | 2 - core/src/middleware/layer/logger.rs | 53 +++++++++--- core/src/tracing.rs | 124 ---------------------------- examples/examples/core_client.rs | 4 +- examples/examples/http.rs | 33 +------- tests/tests/integration_tests.rs | 2 +- 6 files changed, 48 insertions(+), 170 deletions(-) delete mode 100644 core/src/tracing.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index 1199417b57..374aae2fda 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -60,8 +60,6 @@ cfg_client_or_server! { pub mod middleware; } -/// Shared tracing helpers to trace RPC calls. -pub mod tracing; pub use async_trait::async_trait; pub use error::{RegisterMethodError, StringError}; diff --git a/core/src/middleware/layer/logger.rs b/core/src/middleware/layer/logger.rs index 9e17bea8e2..cd8366b1d0 100644 --- a/core/src/middleware/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -26,13 +26,11 @@ //! RPC Logger layer. -use crate::{ - middleware::{Batch, Notification, RpcServiceT, ToJson}, - tracing::truncate_at_char_boundary, -}; +use crate::middleware::{Batch, Notification, RpcServiceT, ToJson}; use futures_util::Future; use jsonrpsee_types::Request; +use serde_json::value::RawValue; use tracing::Instrument; /// RPC logger layer. @@ -72,8 +70,10 @@ where #[tracing::instrument(name = "method_call", skip_all, fields(method = request.method_name()), level = "trace")] fn call<'a>(&self, request: Request<'a>) -> impl Future> + Send + 'a { - let json = serde_json::to_string(&request).unwrap_or_default(); - tracing::trace!(target: "jsonrpsee", "request = {}", truncate_at_char_boundary(&json, self.max as usize)); + let json = serde_json::value::to_raw_value(&request); + let json_str = unwrap_json_str_or_invalid(&json); + tracing::trace!(target: "jsonrpsee", "request = {}", truncate_at_char_boundary(&json_str, self.max as usize)); + let service = self.service.clone(); let max = self.max; @@ -82,7 +82,7 @@ where if let Ok(ref rp) = rp { let json = rp.to_json(); - let json_str = json.as_ref().map_or("", |j| j.get()); + let json_str = unwrap_json_str_or_invalid(&json); tracing::trace!(target: "jsonrpsee", "response = {}", truncate_at_char_boundary(json_str, max as usize)); } rp @@ -102,7 +102,7 @@ where if let Ok(ref rp) = rp { let json = rp.to_json(); - let json_str = json.as_ref().map_or("", |j| j.get()); + let json_str = unwrap_json_str_or_invalid(&json); tracing::trace!(target: "jsonrpsee", "batch response = {}", truncate_at_char_boundary(json_str, max as usize)); } rp @@ -115,9 +115,42 @@ where &self, n: Notification<'a>, ) -> impl Future> + Send + 'a { - let json = serde_json::to_string(&n).unwrap_or_default(); - tracing::trace!(target: "jsonrpsee", "notification request = {}", truncate_at_char_boundary(&json, self.max as usize)); + let json = serde_json::value::to_raw_value(&n); + let json_str = unwrap_json_str_or_invalid(&json); + tracing::trace!(target: "jsonrpsee", "notification request = {}", truncate_at_char_boundary(json_str, self.max as usize)); self.service.notification(n).in_current_span() } } + +fn unwrap_json_str_or_invalid(json: &Result, serde_json::Error>) -> &str { + match json { + Ok(s) => s.get(), + Err(_) => "", + } +} + +/// Find the next char boundary to truncate at. +fn truncate_at_char_boundary(s: &str, max: usize) -> &str { + if s.len() < max { + return s; + } + + match s.char_indices().nth(max) { + None => s, + Some((idx, _)) => &s[..idx], + } +} + +#[cfg(test)] +mod tests { + use super::truncate_at_char_boundary; + + #[test] + fn truncate_at_char_boundary_works() { + assert_eq!(truncate_at_char_boundary("ボルテックス", 0), ""); + assert_eq!(truncate_at_char_boundary("ボルテックス", 4), "ボルテッ"); + assert_eq!(truncate_at_char_boundary("ボルテックス", 100), "ボルテックス"); + assert_eq!(truncate_at_char_boundary("hola-hola", 4), "hola"); + } +} diff --git a/core/src/tracing.rs b/core/src/tracing.rs deleted file mode 100644 index e9a775b800..0000000000 --- a/core/src/tracing.rs +++ /dev/null @@ -1,124 +0,0 @@ -use serde::Serialize; -use tracing::Level; - -const CLIENT: &str = "jsonrpsee-client"; -const SERVER: &str = "jsonrpsee-server"; - -/// Logging with jsonrpsee client target. -pub mod client { - use super::*; - - /// Helper for writing trace logs from str. - pub fn tx_log_from_str(s: impl AsRef, max: u32) { - if tracing::enabled!(Level::TRACE) { - let msg = truncate_at_char_boundary(s.as_ref(), max as usize); - tracing::trace!(target: CLIENT, send = msg); - } - } - - /// Helper for writing trace logs from JSON. - pub fn tx_log_from_json(s: &impl Serialize, max: u32) { - if tracing::enabled!(Level::TRACE) { - let json = serde_json::to_string(s).unwrap_or_default(); - let msg = truncate_at_char_boundary(&json, max as usize); - tracing::trace!(target: CLIENT, send = msg); - } - } - - /// Helper for writing trace logs from str. - pub fn rx_log_from_str(s: impl AsRef, max: u32) { - if tracing::enabled!(Level::TRACE) { - let msg = truncate_at_char_boundary(s.as_ref(), max as usize); - tracing::trace!(target: CLIENT, recv = msg); - } - } - - /// Helper for writing trace logs from JSON. - pub fn rx_log_from_json(s: &impl Serialize, max: u32) { - if tracing::enabled!(Level::TRACE) { - let res = serde_json::to_string(s).unwrap_or_default(); - let msg = truncate_at_char_boundary(res.as_str(), max as usize); - tracing::trace!(target: CLIENT, recv = msg); - } - } - - /// Helper for writing trace logs from bytes. - pub fn rx_log_from_bytes(bytes: &[u8], max: u32) { - if tracing::enabled!(Level::TRACE) { - let res = serde_json::from_slice::(bytes).unwrap_or_default(); - rx_log_from_json(&res, max); - } - } -} - -/// Logging with jsonrpsee server target. -pub mod server { - use super::*; - - /// Helper for writing trace logs from str. - pub fn tx_log_from_str(s: impl AsRef, max: u32) { - if tracing::enabled!(Level::TRACE) { - let msg = truncate_at_char_boundary(s.as_ref(), max as usize); - tracing::trace!(target: SERVER, send = msg); - } - } - - /// Helper for writing trace logs from JSON. - pub fn tx_log_from_json(s: &impl Serialize, max: u32) { - if tracing::enabled!(Level::TRACE) { - let json = serde_json::to_string(s).unwrap_or_default(); - let msg = truncate_at_char_boundary(&json, max as usize); - tracing::trace!(target: SERVER, send = msg); - } - } - - /// Helper for writing trace logs from str. - pub fn rx_log_from_str(s: impl AsRef, max: u32) { - if tracing::enabled!(Level::TRACE) { - let msg = truncate_at_char_boundary(s.as_ref(), max as usize); - tracing::trace!(target: SERVER, recv = msg); - } - } - - /// Helper for writing trace logs from JSON. - pub fn rx_log_from_json(s: &impl Serialize, max: u32) { - if tracing::enabled!(Level::TRACE) { - let res = serde_json::to_string(s).unwrap_or_default(); - let msg = truncate_at_char_boundary(res.as_str(), max as usize); - tracing::trace!(target: SERVER, recv = msg); - } - } - - /// Helper for writing trace logs from bytes. - pub fn rx_log_from_bytes(bytes: &[u8], max: u32) { - if tracing::enabled!(Level::TRACE) { - let res = serde_json::from_slice::(bytes).unwrap_or_default(); - rx_log_from_json(&res, max); - } - } -} - -/// Find the next char boundary to truncate at. -pub fn truncate_at_char_boundary(s: &str, max: usize) -> &str { - if s.len() < max { - return s; - } - - match s.char_indices().nth(max) { - None => s, - Some((idx, _)) => &s[..idx], - } -} - -#[cfg(test)] -mod tests { - use super::truncate_at_char_boundary; - - #[test] - fn truncate_at_char_boundary_works() { - assert_eq!(truncate_at_char_boundary("ボルテックス", 0), ""); - assert_eq!(truncate_at_char_boundary("ボルテックス", 4), "ボルテッ"); - assert_eq!(truncate_at_char_boundary("ボルテックス", 100), "ボルテックス"); - assert_eq!(truncate_at_char_boundary("hola-hola", 4), "hola"); - } -} diff --git a/examples/examples/core_client.rs b/examples/examples/core_client.rs index b12f5f4c60..7c7380bcb5 100644 --- a/examples/examples/core_client.rs +++ b/examples/examples/core_client.rs @@ -27,7 +27,7 @@ use std::net::SocketAddr; use jsonrpsee::client_transport::ws::{Url, WsTransportClientBuilder}; -use jsonrpsee::core::client::{Client, ClientBuilder, ClientT}; +use jsonrpsee::core::client::{ClientBuilder, ClientT}; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, Server}; @@ -42,7 +42,7 @@ async fn main() -> anyhow::Result<()> { let uri = Url::parse(&format!("ws://{}", addr))?; let (tx, rx) = WsTransportClientBuilder::default().build(uri).await?; - let client: Client<_> = ClientBuilder::default().build_with_tokio(tx, rx); + let client = ClientBuilder::default().build_with_tokio(tx, rx); let response: String = client.request("say_hello", rpc_params![]).await?; tracing::info!("response: {:?}", response); diff --git a/examples/examples/http.rs b/examples/examples/http.rs index a6f597c9e9..02722db1f7 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -29,43 +29,14 @@ use std::time::Duration; use hyper::body::Bytes; use jsonrpsee::core::client::ClientT; -use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::RpcServiceBuilder; use jsonrpsee::http_client::HttpClient; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, Server}; -use jsonrpsee::types::Request; use tower_http::LatencyUnit; use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}; use tracing_subscriber::util::SubscriberInitExt; -struct Logger(S); - -impl RpcServiceT for Logger -where - S: RpcServiceT, -{ - type Error = S::Error; - type Response = S::Response; - - fn call<'a>(&self, req: Request<'a>) -> impl Future> + Send + 'a { - println!("logger layer : {:?}", req); - self.0.call(req) - } - - fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { - println!("logger layer : {:?}", batch); - self.0.batch(batch) - } - - fn notification<'a>( - &self, - n: Notification<'a>, - ) -> impl Future> + Send + 'a { - println!("logger layer : {:?}", n); - self.0.notification(n) - } -} - #[tokio::main] async fn main() -> anyhow::Result<()> { let filter = tracing_subscriber::EnvFilter::try_from_default_env()? @@ -88,7 +59,7 @@ async fn main() -> anyhow::Result<()> { .on_response(DefaultOnResponse::new().include_headers(true).latency_unit(LatencyUnit::Micros)), ); - let rpc = RpcServiceBuilder::new().layer_fn(|service| Logger(service)).rpc_logger(1024); + let rpc = RpcServiceBuilder::new().rpc_logger(1024); let client = HttpClient::builder().set_http_middleware(middleware).set_rpc_middleware(rpc).build(url)?; let params = rpc_params![1_u64, 2, 3]; diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 7c6d2741cc..fde8af7a1c 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -43,11 +43,11 @@ use helpers::{ use http_body_util::BodyExt; use hyper::http::HeaderValue; use hyper_util::rt::TokioExecutor; +use jsonrpsee::core::StringError; use jsonrpsee::core::client::SubscriptionCloseReason; use jsonrpsee::core::client::{ClientT, Error, IdKind, Subscription, SubscriptionClientT}; use jsonrpsee::core::params::{ArrayParams, BatchRequestBuilder}; use jsonrpsee::core::server::SubscriptionMessage; -use jsonrpsee::core::{JsonValue, StringError}; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::server::middleware::http::HostFilterLayer; use jsonrpsee::server::{ConnectionGuard, ServerBuilder, ServerConfig, ServerHandle}; From ed4fb5b193ba3ef8103770caa61f89df7e4ffa1b Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 5 Apr 2025 11:51:20 +0200 Subject: [PATCH 43/52] fix more clippy --- client/http-client/src/client.rs | 2 +- client/http-client/src/rpc_service.rs | 4 +- core/src/client/async_client/mod.rs | 2 +- core/src/client/async_client/rpc_service.rs | 6 +- core/src/middleware/layer/logger.rs | 7 +- core/src/middleware/layer/mod.rs | 5 +- core/src/middleware/mod.rs | 73 ++++++++++++++------- proc-macros/src/lib.rs | 7 +- tests/tests/integration_tests.rs | 2 +- 9 files changed, 67 insertions(+), 41 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 3fe30b949a..a7c4082597 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -409,7 +409,7 @@ where let id = self.id_manager.next_request_id(); let id_range = generate_batch_id_range(id, batch.len() as u64)?; - let mut batch_request = Batch::new(); + let mut batch_request = Batch::with_capacity(batch.len()); for ((method, params), id) in batch.into_iter().zip(id_range.clone()) { let id = self.id_manager.as_id_kind().into_id(id); let req = Request { diff --git a/client/http-client/src/rpc_service.rs b/client/http-client/src/rpc_service.rs index 8bd3291bae..f0b5f3127c 100644 --- a/client/http-client/src/rpc_service.rs +++ b/client/http-client/src/rpc_service.rs @@ -59,9 +59,7 @@ where .map(|r| r.into_owned().into()) .collect(); - let (_, extensions) = batch.into_parts(); - - Ok(MethodResponse::batch(rp, extensions)) + Ok(MethodResponse::batch(rp, batch.into_extensions())) } } diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index 7d9cad241d..0e7d9e57a7 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -530,7 +530,7 @@ where let id = self.id_manager.next_request_id(); let id_range = generate_batch_id_range(id, batch.len() as u64)?; - let mut b = Batch::new(); + let mut b = Batch::with_capacity(batch.len()); for ((method, params), id) in batch.into_iter().zip(id_range.clone()) { b.push(Request { diff --git a/core/src/client/async_client/rpc_service.rs b/core/src/client/async_client/rpc_service.rs index 8cd3eef484..efcbe23451 100644 --- a/core/src/client/async_client/rpc_service.rs +++ b/core/src/client/async_client/rpc_service.rs @@ -100,7 +100,7 @@ impl RpcServiceT for RpcService { } } - fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { + fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future> + Send + 'a { let tx = self.0.clone(); async move { @@ -116,9 +116,7 @@ impl RpcServiceT for RpcService { tx.send(FrontToBack::Batch(BatchMessage { raw, ids: id_range, send_back: send_back_tx })).await?; let json = send_back_rx.await?.map_err(client_err)?; - let (_, extensions) = batch.into_parts(); - - Ok(MethodResponse::batch(json, extensions)) + Ok(MethodResponse::batch(json, batch.into_extensions())) } } diff --git a/core/src/middleware/layer/logger.rs b/core/src/middleware/layer/logger.rs index cd8366b1d0..344e6ff648 100644 --- a/core/src/middleware/layer/logger.rs +++ b/core/src/middleware/layer/logger.rs @@ -72,7 +72,7 @@ where fn call<'a>(&self, request: Request<'a>) -> impl Future> + Send + 'a { let json = serde_json::value::to_raw_value(&request); let json_str = unwrap_json_str_or_invalid(&json); - tracing::trace!(target: "jsonrpsee", "request = {}", truncate_at_char_boundary(&json_str, self.max as usize)); + tracing::trace!(target: "jsonrpsee", "request = {}", truncate_at_char_boundary(json_str, self.max as usize)); let service = self.service.clone(); let max = self.max; @@ -92,8 +92,9 @@ where #[tracing::instrument(name = "batch", skip_all, fields(method = "batch"), level = "trace")] fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { - let json = serde_json::to_string(&batch).unwrap_or_default(); - tracing::trace!(target: "jsonrpsee", "batch request = {}", truncate_at_char_boundary(&json, self.max as usize)); + let json = serde_json::value::to_raw_value(&batch); + let json_str = unwrap_json_str_or_invalid(&json); + tracing::trace!(target: "jsonrpsee", "batch request = {}", truncate_at_char_boundary(json_str, self.max as usize)); let service = self.service.clone(); let max = self.max; diff --git a/core/src/middleware/layer/mod.rs b/core/src/middleware/layer/mod.rs index e31c352ec9..f9b886bdd9 100644 --- a/core/src/middleware/layer/mod.rs +++ b/core/src/middleware/layer/mod.rs @@ -26,7 +26,8 @@ //! Specific middleware layer implementation provided by jsonrpsee. -pub mod either; -pub mod logger; +mod either; +mod logger; +pub use either::*; pub use logger::*; diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index ac34a79b76..3476efc8a6 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -47,7 +47,7 @@ impl<'a> ErrorResponse<'a> { #[derive(Debug, Default)] pub struct Batch<'a> { inner: Vec, ErrorResponse<'a>>>, - extensions: Extensions, + extensions: Option, } impl std::fmt::Display for Batch<'_> { @@ -57,27 +57,42 @@ impl std::fmt::Display for Batch<'_> { } } +impl<'a> IntoIterator for Batch<'a> { + type Item = Result, ErrorResponse<'a>>; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + impl<'a> Batch<'a> { /// Create a new batch from a vector of batch entries. pub fn from(entries: Vec, ErrorResponse<'a>>>) -> Self { - let mut extensions = Extensions::new(); - for entry in &entries { - if let Ok(entry) = entry { - extensions.extend(entry.extensions().clone()); - } - } - - Self { inner: entries, extensions } + Self { inner: entries, extensions: None } } /// Create a new empty batch. pub fn new() -> Self { - Self { inner: Vec::new(), extensions: Extensions::new() } + Self { inner: Vec::new(), extensions: None } + } + + /// Create a new empty batch with the at least capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self { inner: Vec::with_capacity(capacity), extensions: None } } /// Push a new batch entry to the batch. pub fn push(&mut self, req: Request<'a>) { - self.extensions.extend(req.extensions().clone()); + match self.extensions { + Some(ref mut ext) => { + ext.extend(req.extensions().clone()); + } + None => { + self.extensions = Some(req.extensions().clone()); + } + }; + self.inner.push(Ok(BatchEntry::Call(req))); } @@ -96,29 +111,43 @@ impl<'a> Batch<'a> { self.inner.iter() } - /// Get a mutuable iterator over the batch. + /// Get a mutable iterator over the batch. pub fn iter_mut(&mut self) -> impl Iterator, ErrorResponse<'a>>> { self.inner.iter_mut() } - /// Convert the batch into an iterator. - pub fn into_iter(self) -> impl Iterator, ErrorResponse<'a>>> { - self.inner.into_iter() - } - /// Consume the batch and and return the parts. - pub fn into_parts(self) -> (Vec, ErrorResponse<'a>>>, Extensions) { - (self.inner, self.extensions) + pub fn into_extensions(self) -> Extensions { + match self.extensions { + Some(ext) => ext, + None => self.extensions_from_iter(), + } } /// Get a reference to the extensions of the batch. - pub fn extensions(&self) -> &Extensions { - &self.extensions + pub fn extensions(&mut self) -> &Extensions { + if self.extensions.is_none() { + self.extensions = Some(self.extensions_from_iter()); + } + + self.extensions.as_ref().expect("Extensions inserted above; qed") } /// Get a mutable reference to the extensions of the batch. pub fn extensions_mut(&mut self) -> &mut Extensions { - &mut self.extensions + if self.extensions.is_none() { + self.extensions = Some(self.extensions_from_iter()); + } + + self.extensions.as_mut().expect("Extensions inserted above; qed") + } + + fn extensions_from_iter(&self) -> Extensions { + let mut ext = Extensions::new(); + for entry in self.inner.iter().flatten() { + ext.extend(entry.extensions().clone()); + } + ext } } diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index 6a716672fc..f885f122e3 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -170,8 +170,7 @@ pub(crate) mod visitor; /// /// - `name` (mandatory): name of the RPC method. Does not have to be the same as the Rust method name. /// - `aliases`: list of name aliases for the RPC method as a comma separated string. -/// Aliases are processed ignoring the namespace, so add the complete name, including the -/// namespace. +/// Aliases are processed ignoring the namespace, so add the complete name, including the namespace. /// - `blocking`: when set method execution will always spawn on a dedicated thread. Only usable with non-`async` methods. /// - `param_kind`: kind of structure to use for parameter passing. Can be "array" or "map", defaults to "array". /// @@ -191,9 +190,9 @@ pub(crate) mod visitor; /// /// - `name` (mandatory): name of the RPC method. Does not have to be the same as the Rust method name. /// - `unsubscribe` (optional): name of the RPC method to unsubscribe from the subscription. Must not be the same as `name`. -/// This is generated for you if the subscription name starts with `subscribe`. +/// This is generated for you if the subscription name starts with `subscribe`. /// - `aliases` (optional): aliases for `name`. Aliases are processed ignoring the namespace, -/// so add the complete name, including the namespace. +/// so add the complete name, including the namespace. /// - `unsubscribe_aliases` (optional): Similar to `aliases` but for `unsubscribe`. /// - `item` (mandatory): type of items yielded by the subscription. Note that it must be the type, not string. /// - `param_kind`: kind of structure to use for parameter passing. Can be "array" or "map", defaults to "array". diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index fde8af7a1c..7c6d2741cc 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -43,11 +43,11 @@ use helpers::{ use http_body_util::BodyExt; use hyper::http::HeaderValue; use hyper_util::rt::TokioExecutor; -use jsonrpsee::core::StringError; use jsonrpsee::core::client::SubscriptionCloseReason; use jsonrpsee::core::client::{ClientT, Error, IdKind, Subscription, SubscriptionClientT}; use jsonrpsee::core::params::{ArrayParams, BatchRequestBuilder}; use jsonrpsee::core::server::SubscriptionMessage; +use jsonrpsee::core::{JsonValue, StringError}; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::server::middleware::http::HostFilterLayer; use jsonrpsee::server::{ConnectionGuard, ServerBuilder, ServerConfig, ServerHandle}; From 52588f38fcd8e2a41f9b6ce777aa59f13cf91eb4 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 9 Apr 2025 10:49:10 +0200 Subject: [PATCH 44/52] remove middleware error --- core/src/middleware/mod.rs | 7 +++++++ examples/examples/jsonrpsee_as_service.rs | 5 ++--- .../examples/jsonrpsee_server_low_level_api.rs | 5 ++--- server/src/server.rs | 15 ++++++--------- server/src/transport/http.rs | 10 +++++----- server/src/transport/ws.rs | 8 ++++---- types/src/error.rs | 9 --------- 7 files changed, 26 insertions(+), 33 deletions(-) diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 3476efc8a6..7b00b8671e 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -290,6 +290,13 @@ impl<'a> BatchEntry<'a> { /// /// In the server implementation, the error is infallible but in the client implementation, the error /// can occur due to I/O errors or JSON-RPC protocol errors. +/// +/// Such that server implementations must use `std::convert::Infallible` as the error type because +/// the underlying service requires that and otherwise one will get a compiler error that tries to +/// explain that. +/// +/// The reason is really that the RPC server under the hood would need to convert that error into a +/// JSON-RPC error and it's better that the user take care of that. pub trait RpcServiceT { /// The error type. type Error: std::fmt::Debug; diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index 7f7a2dfd37..bd4d53ed8a 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -39,8 +39,8 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use futures::FutureExt; use hyper::HeaderMap; use hyper::header::AUTHORIZATION; +use jsonrpsee::core::async_trait; use jsonrpsee::core::middleware::{Batch, BatchEntry, ErrorResponse, Notification, RpcServiceBuilder, RpcServiceT}; -use jsonrpsee::core::{BoxError, async_trait}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{ @@ -105,8 +105,7 @@ impl AuthorizationMiddleware { impl RpcServiceT for AuthorizationMiddleware where - S: Send + Clone + Sync + RpcServiceT + 'static, - S::Error: Into + Send + 'static, + S: RpcServiceT + Send + Clone + Sync + 'static, S::Response: Send, { type Error = S::Error; diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index 509b3af215..705020a142 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -45,8 +45,8 @@ use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; use futures::FutureExt; +use jsonrpsee::core::async_trait; use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT}; -use jsonrpsee::core::{BoxError, async_trait}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{ @@ -74,8 +74,7 @@ struct CallLimit { impl RpcServiceT for CallLimit where - S: Send + Sync + RpcServiceT + Clone + 'static, - S::Error: Into, + S: RpcServiceT + Send + Sync + Clone + 'static, { type Error = S::Error; type Response = S::Response; diff --git a/server/src/server.rs b/server/src/server.rs index dfc7a10c1c..841524b4ae 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -24,6 +24,7 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::convert::Infallible; use std::error::Error as StdError; use std::future::Future; use std::net::{SocketAddr, TcpListener as StdTcpListener}; @@ -53,7 +54,6 @@ use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; use jsonrpsee_types::error::{ BATCHES_NOT_SUPPORTED_CODE, BATCHES_NOT_SUPPORTED_MSG, ErrorCode, reject_too_big_batch_request, - rpc_middleware_error, }; use jsonrpsee_types::{ErrorObject, Id, deserialize_with_ext}; use soketto::handshake::http::is_upgrade_request; @@ -971,9 +971,8 @@ pub struct TowerServiceNoHttp { impl Service> for TowerServiceNoHttp where RpcMiddleware: for<'a> tower::Layer, - >::Service: Send + Sync + 'static, - >::Service: RpcServiceT, - <>::Service as RpcServiceT>::Error: std::fmt::Debug, + >::Service: + RpcServiceT + Send + Sync + 'static, Body: http_body::Body + Send + 'static, Body::Error: Into, { @@ -1239,17 +1238,15 @@ pub(crate) async fn handle_rpc_call( extensions: Extensions, ) -> MethodResponse where - S: RpcServiceT + Send, + S: RpcServiceT + Send, ::Error: std::fmt::Debug, { // Single request or notification if is_single { if let Ok(req) = deserialize_with_ext::call::from_slice(body, &extensions) { - let id = req.id(); - match rpc_service.call(req).await { Ok(rp) => rp, - Err(err) => MethodResponse::error(id, rpc_middleware_error(err)), + Err(err) => match err {}, } } else if let Ok(notif) = deserialize_with_ext::notif::from_slice::(body, &extensions) { match rpc_service.notification(notif).await { @@ -1303,7 +1300,7 @@ where match rpc_service.batch(Batch::from(batch)).await { Ok(rp) => rp, - Err(e) => MethodResponse::error(Id::Null, rpc_middleware_error(e)), + Err(e) => match e {}, } } else { MethodResponse::error(Id::Null, ErrorObject::from(ErrorCode::ParseError)) diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index b77aee85d3..c0fa55121d 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -1,3 +1,5 @@ +use std::convert::Infallible; + use crate::{ BatchRequestConfig, ConnectionState, HttpRequest, HttpResponse, LOG_TARGET, middleware::rpc::{RpcService, RpcServiceCfg}, @@ -43,10 +45,8 @@ where B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into, - L: for<'a> tower::Layer, - >::Service: Send + Sync + 'static, - >::Service: RpcServiceT + Send, - <>::Service as RpcServiceT>::Error: std::fmt::Debug, + L: tower::Layer, + >::Service: RpcServiceT + Send, { let ServerConfig { max_response_body_size, batch_requests_config, max_request_body_size, .. } = server_cfg; @@ -77,7 +77,7 @@ where B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into, - S: RpcServiceT + Send, + S: RpcServiceT + Send, ::Error: std::fmt::Debug, { // Only the `POST` method is allowed. diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 464228d4e7..124a6b2f2f 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -1,3 +1,4 @@ +use std::convert::Infallible; use std::sync::Arc; use std::time::Instant; @@ -63,8 +64,7 @@ pub(crate) struct BackgroundTaskParams { pub(crate) async fn background_task(params: BackgroundTaskParams) where - S: RpcServiceT + Send + Sync + 'static, - ::Error: std::fmt::Debug, + S: RpcServiceT + Send + Sync + 'static, { let BackgroundTaskParams { server_cfg, @@ -421,8 +421,8 @@ pub async fn connect( ) -> Result<(HttpResponse, impl Future), HttpResponse> where L: tower::Layer, - >::Service: Send + Sync + 'static, - >::Service: RpcServiceT, + >::Service: + RpcServiceT + Send + Sync + 'static, { let mut server = soketto::handshake::http::Server::new(); diff --git a/types/src/error.rs b/types/src/error.rs index f3b30e67bf..5cc2777b14 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -145,8 +145,6 @@ pub const SERVER_IS_BUSY_CODE: i32 = -32009; pub const TOO_BIG_BATCH_REQUEST_CODE: i32 = -32010; /// Batch response limit was exceed. pub const TOO_BIG_BATCH_RESPONSE_CODE: i32 = -32011; -/// Middleware error code. -pub const MIDDLEWARE_ERROR_CODE: i32 = -32012; /// Parse error message pub const PARSE_ERROR_MSG: &str = "Parse error"; @@ -174,8 +172,6 @@ pub const TOO_MANY_SUBSCRIPTIONS_MSG: &str = "Too many subscriptions on the conn pub const TOO_BIG_BATCH_REQUEST_MSG: &str = "The batch request was too large"; /// Batch request response limit was exceed. pub const TOO_BIG_BATCH_RESPONSE_MSG: &str = "The batch response was too large"; -/// Middleware error message. -pub const MIDDLEWARE_ERROR_MSG: &str = "Middleware error"; /// JSONRPC error code #[derive(Error, Debug, PartialEq, Eq, Copy, Clone)] @@ -307,11 +303,6 @@ pub fn reject_too_big_batch_response(limit: usize) -> ErrorObjectOwned { ) } -/// Helper to return a `JSON-RPC` error object when middleware encounters an internal error. -pub fn rpc_middleware_error(err: impl std::fmt::Debug) -> ErrorObjectOwned { - ErrorObjectOwned::owned(MIDDLEWARE_ERROR_CODE, MIDDLEWARE_ERROR_MSG, Some(format!("{:?}", err))) -} - #[cfg(test)] mod tests { use super::{ErrorCode, ErrorObject}; From 39c1d1953aaac2f82d0e5d032921e7ff5db06294 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 9 Apr 2025 13:51:08 +0200 Subject: [PATCH 45/52] address review grumbles --- .../jsonrpsee_server_low_level_api.rs | 4 +-- .../examples/rpc_middleware_rate_limiting.rs | 30 +++++++++++++------ server/src/middleware/rpc.rs | 7 +++-- server/src/tests/http.rs | 4 +-- server/src/transport/http.rs | 1 - tests/tests/helpers.rs | 8 ++--- tests/tests/metrics.rs | 8 ++--- 7 files changed, 38 insertions(+), 24 deletions(-) diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index 705020a142..75d3f0bebc 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -105,12 +105,12 @@ where async move { let mut lock = count.lock().await; + let batch_len = batch.len(); - if *lock >= 10 { + if *lock >= 10 + batch_len { let _ = state.try_send(()); Ok(MethodResponse::error(Id::Null, ErrorObject::borrowed(-32000, "RPC rate limit", None))) } else { - let batch_len = batch.len(); let rp = service.batch(batch).await; *lock += batch_len; rp diff --git a/examples/examples/rpc_middleware_rate_limiting.rs b/examples/examples/rpc_middleware_rate_limiting.rs index d1cb12ff70..6b65881109 100644 --- a/examples/examples/rpc_middleware_rate_limiting.rs +++ b/examples/examples/rpc_middleware_rate_limiting.rs @@ -32,7 +32,9 @@ //! such as `Arc` use jsonrpsee::core::client::ClientT; -use jsonrpsee::core::middleware::{Batch, Notification, ResponseFuture, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{ + Batch, BatchEntry, ErrorResponse, Notification, ResponseFuture, RpcServiceBuilder, RpcServiceT, +}; use jsonrpsee::server::Server; use jsonrpsee::types::{ErrorObject, Request}; use jsonrpsee::ws_client::WsClientBuilder; @@ -121,16 +123,26 @@ where } } - fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { + fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future> + Send + 'a { + // If the rate limit is reached then we modify each entry + // in the batch to be a request with an error. + // + // This makes sure that the client will receive an error + // for each request in the batch. if self.rate_limit_deny() { - let error = ErrorObject::borrowed(-32000, "RPC rate limit", None); - let error = MethodResponse::error(jsonrpsee::types::Id::Null, error); - ResponseFuture::ready(error) - } else { - // NOTE: this count as batch call a single call which may not - // be the desired behavior. - ResponseFuture::future(self.service.batch(batch)) + for entry in batch.iter_mut() { + let id = match entry { + Ok(BatchEntry::Call(req)) => req.id.clone(), + Ok(BatchEntry::Notification(_)) => continue, + Err(_) => continue, + }; + + // This will create a new error response for batch and replace the method call + *entry = Err(ErrorResponse::new(id, ErrorObject::borrowed(-32000, "RPC rate limit", None))); + } } + + self.service.batch(batch) } fn notification<'a>( diff --git a/server/src/middleware/rpc.rs b/server/src/middleware/rpc.rs index 61b26dfd74..8b2180a122 100644 --- a/server/src/middleware/rpc.rs +++ b/server/src/middleware/rpc.rs @@ -194,8 +194,11 @@ impl RpcServiceT for RpcService { fn notification<'a>( &self, - _: Notification<'a>, + n: Notification<'a>, ) -> impl Future> + Send + 'a { - async move { Ok(MethodResponse::notification()) } + // The notification should not be replied to with a response + // but we propogate the extensions to the response which can be useful + // for example HTTP transport to set the headers. + async move { Ok(MethodResponse::notification().with_extensions(n.extensions)) } } } diff --git a/server/src/tests/http.rs b/server/src/tests/http.rs index 3e9ce2cad1..53ff655bb9 100644 --- a/server/src/tests/http.rs +++ b/server/src/tests/http.rs @@ -88,9 +88,9 @@ where fn notification<'a>( &self, - n: Notification<'a>, + _: Notification<'a>, ) -> impl Future> + Send + 'a { - self.service.notification(n) + async { panic!("Not used for tests") } } } diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index c0fa55121d..0c2562f3b0 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -78,7 +78,6 @@ where B::Data: Send, B::Error: Into, S: RpcServiceT + Send, - ::Error: std::fmt::Debug, { // Only the `POST` method is allowed. match *request.method() { diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index 138b39da55..0d307c736c 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -164,15 +164,15 @@ pub async fn server() -> SocketAddr { self.inner.call(request) } - fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { - self.inner.batch(batch) + fn batch<'a>(&self, _: Batch<'a>) -> impl Future> + Send + 'a { + async { panic!("Not used for tests") } } fn notification<'a>( &self, - n: Notification<'a>, + _: Notification<'a>, ) -> impl Future> + Send + 'a { - self.inner.notification(n) + async { panic!("Not used for tests") } } } diff --git a/tests/tests/metrics.rs b/tests/tests/metrics.rs index c4369e8b75..eb4c74dd4b 100644 --- a/tests/tests/metrics.rs +++ b/tests/tests/metrics.rs @@ -95,15 +95,15 @@ where } } - fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { - self.service.batch(batch) + fn batch<'a>(&self, _: Batch<'a>) -> impl Future> + Send + 'a { + async { panic!("Not used for tests") } } fn notification<'a>( &self, - n: Notification<'a>, + _: Notification<'a>, ) -> impl Future> + Send + 'a { - self.service.notification(n) + async { panic!("Not used for tests") } } } From f6dada4fd608ca22af1518ad62bec4ef11505121 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 9 Apr 2025 14:49:11 +0200 Subject: [PATCH 46/52] fix tests --- tests/tests/helpers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index 0d307c736c..11a600b81a 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -164,8 +164,8 @@ pub async fn server() -> SocketAddr { self.inner.call(request) } - fn batch<'a>(&self, _: Batch<'a>) -> impl Future> + Send + 'a { - async { panic!("Not used for tests") } + fn batch<'a>(&self, batch: Batch<'a>) -> impl Future> + Send + 'a { + self.inner.batch(batch) } fn notification<'a>( From 6484bd249c1d181551f91f9013b11f6905cad46c Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 9 Apr 2025 15:15:16 +0200 Subject: [PATCH 47/52] fix tests --- server/src/transport/ws.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 124a6b2f2f..ba55a807ab 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -383,6 +383,7 @@ async fn graceful_shutdown( /// ```no_run /// use jsonrpsee_server::{ws, ServerConfig, Methods, ConnectionState, HttpRequest, HttpResponse}; /// use jsonrpsee_server::middleware::rpc::{RpcServiceBuilder, RpcServiceT, RpcService, MethodResponse}; +/// use std::convert::Infallible; /// /// async fn handle_websocket_conn( /// req: HttpRequest, @@ -394,7 +395,7 @@ async fn graceful_shutdown( /// ) -> HttpResponse /// where /// L: tower::Layer + 'static, -/// >::Service: RpcServiceT + Send + Sync + 'static, +/// >::Service: RpcServiceT + Send + Sync + 'static, /// { /// match ws::connect(req, server_cfg, methods, conn, rpc_middleware).await { /// Ok((rp, conn_fut)) => { From cd34d2cd85bc8f5a66097c529544a4c4392d178d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 9 Apr 2025 15:28:12 +0200 Subject: [PATCH 48/52] ErrorResponse -> BatchEntryErr --- core/src/middleware/mod.rs | 20 +++++++++---------- examples/examples/jsonrpsee_as_service.rs | 2 +- .../examples/rpc_middleware_rate_limiting.rs | 4 ++-- server/src/server.rs | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 7b00b8671e..8e0ef38276 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -23,9 +23,9 @@ pub use jsonrpsee_types::{Extensions, Request}; /// Error response that can used to indicate an error in JSON-RPC request batch request. /// This is used in the [`Batch`] type to indicate an error in the batch entry. #[derive(Debug)] -pub struct ErrorResponse<'a>(jsonrpsee_types::Response<'a, ()>); +pub struct BatchEntryErr<'a>(jsonrpsee_types::Response<'a, ()>); -impl<'a> ErrorResponse<'a> { +impl<'a> BatchEntryErr<'a> { /// Create a new error response. pub fn new(id: Id<'a>, err: ErrorObject<'a>) -> Self { let payload = jsonrpsee_types::ResponsePayload::Error(err); @@ -37,7 +37,7 @@ impl<'a> ErrorResponse<'a> { pub fn into_parts(self) -> (ErrorObject<'a>, Id<'a>) { let err = match self.0.payload { jsonrpsee_types::ResponsePayload::Error(err) => err, - _ => unreachable!("ErrorResponse can only be created from error payload; qed"), + _ => unreachable!("BatchEntryErr can only be created from error payload; qed"), }; (err, self.0.id) } @@ -46,7 +46,7 @@ impl<'a> ErrorResponse<'a> { /// A batch of JSON-RPC requests. #[derive(Debug, Default)] pub struct Batch<'a> { - inner: Vec, ErrorResponse<'a>>>, + inner: Vec, BatchEntryErr<'a>>>, extensions: Option, } @@ -58,7 +58,7 @@ impl std::fmt::Display for Batch<'_> { } impl<'a> IntoIterator for Batch<'a> { - type Item = Result, ErrorResponse<'a>>; + type Item = Result, BatchEntryErr<'a>>; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { @@ -68,7 +68,7 @@ impl<'a> IntoIterator for Batch<'a> { impl<'a> Batch<'a> { /// Create a new batch from a vector of batch entries. - pub fn from(entries: Vec, ErrorResponse<'a>>>) -> Self { + pub fn from(entries: Vec, BatchEntryErr<'a>>>) -> Self { Self { inner: entries, extensions: None } } @@ -107,12 +107,12 @@ impl<'a> Batch<'a> { } /// Get an iterator over the batch. - pub fn iter(&self) -> impl Iterator, ErrorResponse<'a>>> { + pub fn iter(&self) -> impl Iterator, BatchEntryErr<'a>>> { self.inner.iter() } /// Get a mutable iterator over the batch. - pub fn iter_mut(&mut self) -> impl Iterator, ErrorResponse<'a>>> { + pub fn iter_mut(&mut self) -> impl Iterator, BatchEntryErr<'a>>> { self.inner.iter_mut() } @@ -446,7 +446,7 @@ mod tests { #[test] fn serialize_batch_works() { - use super::{Batch, BatchEntry, ErrorResponse, Notification, Request}; + use super::{Batch, BatchEntry, BatchEntryErr, Notification, Request}; use jsonrpsee_types::Id; let req = Request::borrowed("say_hello", None, Id::Number(1)); @@ -454,7 +454,7 @@ mod tests { let batch = Batch::from(vec![ Ok(BatchEntry::Call(req)), Ok(BatchEntry::Notification(notification)), - Err(ErrorResponse::new(Id::Number(2), ErrorObject::from(ErrorCode::InvalidRequest))), + Err(BatchEntryErr::new(Id::Number(2), ErrorObject::from(ErrorCode::InvalidRequest))), ]); assert_eq!( serde_json::to_string(&batch).unwrap(), diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index bd4d53ed8a..34b8cb020e 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -40,7 +40,7 @@ use futures::FutureExt; use hyper::HeaderMap; use hyper::header::AUTHORIZATION; use jsonrpsee::core::async_trait; -use jsonrpsee::core::middleware::{Batch, BatchEntry, ErrorResponse, Notification, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee::core::middleware::{Batch, BatchEntry, BatchEntryErr, Notification, RpcServiceBuilder, RpcServiceT}; use jsonrpsee::http_client::HttpClient; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{ diff --git a/examples/examples/rpc_middleware_rate_limiting.rs b/examples/examples/rpc_middleware_rate_limiting.rs index 6b65881109..c82fa549d0 100644 --- a/examples/examples/rpc_middleware_rate_limiting.rs +++ b/examples/examples/rpc_middleware_rate_limiting.rs @@ -33,7 +33,7 @@ use jsonrpsee::core::client::ClientT; use jsonrpsee::core::middleware::{ - Batch, BatchEntry, ErrorResponse, Notification, ResponseFuture, RpcServiceBuilder, RpcServiceT, + Batch, BatchEntry, BatchEntryErr, Notification, ResponseFuture, RpcServiceBuilder, RpcServiceT, }; use jsonrpsee::server::Server; use jsonrpsee::types::{ErrorObject, Request}; @@ -138,7 +138,7 @@ where }; // This will create a new error response for batch and replace the method call - *entry = Err(ErrorResponse::new(id, ErrorObject::borrowed(-32000, "RPC rate limit", None))); + *entry = Err(BatchEntryErr::new(id, ErrorObject::borrowed(-32000, "RPC rate limit", None))); } } diff --git a/server/src/server.rs b/server/src/server.rs index 841524b4ae..3765d49507 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -46,7 +46,7 @@ use futures_util::io::{BufReader, BufWriter}; use hyper::body::Bytes; use hyper_util::rt::{TokioExecutor, TokioIo}; use jsonrpsee_core::id_providers::RandomIntegerIdProvider; -use jsonrpsee_core::middleware::{Batch, BatchEntry, ErrorResponse, RpcServiceBuilder, RpcServiceT}; +use jsonrpsee_core::middleware::{Batch, BatchEntry, BatchEntryErr, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::server::helpers::prepare_error; use jsonrpsee_core::server::{BoundedSubscriptions, ConnectionId, MethodResponse, MethodSink, Methods}; use jsonrpsee_core::traits::IdProvider; @@ -1294,7 +1294,7 @@ where Err(_) => Id::Null, }; - batch.push(Err(ErrorResponse::new(id, ErrorCode::InvalidRequest.into()))); + batch.push(Err(BatchEntryErr::new(id, ErrorCode::InvalidRequest.into()))); } } From ac5f60a83b1fa3f7e7882e81e7dd60ad81166e9f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 10 Apr 2025 12:09:39 +0200 Subject: [PATCH 49/52] commit missing file --- examples/examples/jsonrpsee_as_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index 34b8cb020e..2a71ebda37 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -139,7 +139,7 @@ where } else { // If the authorization header is missing, we return // a JSON-RPC error instead of an error from the service. - Some(Err(ErrorResponse::new(req.id, auth_reject_error()))) + Some(Err(BatchEntryErr::new(req.id, auth_reject_error()))) } } Ok(BatchEntry::Notification(notif)) => { From 9feb968fdb1273333f15256132bfbc55da473c8f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 11 Apr 2025 14:55:41 +0200 Subject: [PATCH 50/52] move deserialize_with_ext to server again --- client/http-client/src/client.rs | 12 ++--- core/src/middleware/mod.rs | 3 -- server/src/server.rs | 3 +- server/src/utils.rs | 56 ++++++++++++++++++++ types/src/lib.rs | 88 -------------------------------- 5 files changed, 64 insertions(+), 98 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index a7c4082597..c0724beb1a 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -289,7 +289,7 @@ where .map(|max_concurrent_requests| Arc::new(Semaphore::new(max_concurrent_requests))); Ok(HttpClient { - transport: rpc_middleware.service(RpcService::new(http)), + service: rpc_middleware.service(RpcService::new(http)), id_manager: Arc::new(RequestIdManager::new(id_kind)), request_guard, request_timeout, @@ -325,8 +325,8 @@ impl HttpClientBuilder { /// JSON-RPC HTTP Client that provides functionality to perform method calls and notifications. #[derive(Debug, Clone)] pub struct HttpClient { - /// HTTP transport client. - transport: S, + /// HTTP service. + service: S, /// Request ID manager. id_manager: Arc, /// Concurrent requests limit guard. @@ -363,7 +363,7 @@ where let params = params.to_rpc_params()?.map(StdCow::Owned); run_future_until_timeout( - self.transport.notification(Notification::new(method.into(), params)), + self.service.notification(Notification::new(method.into(), params)), self.request_timeout, ) .await @@ -384,7 +384,7 @@ where let params = params.to_rpc_params()?; let method_response = run_future_until_timeout( - self.transport.call(Request::borrowed(method, params.as_deref(), id.clone())), + self.service.call(Request::borrowed(method, params.as_deref(), id.clone())), self.request_timeout, ) .await? @@ -422,7 +422,7 @@ where batch_request.push(req); } - let rp = run_future_until_timeout(self.transport.batch(batch_request), self.request_timeout).await?; + let rp = run_future_until_timeout(self.service.batch(batch_request), self.request_timeout).await?; let json_rps = rp.into_batch().expect("Batch must return a batch reponse; qed"); let mut batch_response = Vec::new(); diff --git a/core/src/middleware/mod.rs b/core/src/middleware/mod.rs index 8e0ef38276..180d2bdbd3 100644 --- a/core/src/middleware/mod.rs +++ b/core/src/middleware/mod.rs @@ -294,9 +294,6 @@ impl<'a> BatchEntry<'a> { /// Such that server implementations must use `std::convert::Infallible` as the error type because /// the underlying service requires that and otherwise one will get a compiler error that tries to /// explain that. -/// -/// The reason is really that the RPC server under the hood would need to convert that error into a -/// JSON-RPC error and it's better that the user take care of that. pub trait RpcServiceT { /// The error type. type Error: std::fmt::Debug; diff --git a/server/src/server.rs b/server/src/server.rs index 3765d49507..bd23c9a9a1 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -38,6 +38,7 @@ use crate::future::{ConnectionGuard, ServerHandle, SessionClose, SessionClosedFu use crate::middleware::rpc::{RpcService, RpcServiceCfg}; use crate::transport::ws::BackgroundTaskParams; use crate::transport::{http, ws}; +use crate::utils::deserialize_with_ext; use crate::{Extensions, HttpBody, HttpRequest, HttpResponse, LOG_TARGET}; use futures_util::future::{self, Either, FutureExt}; @@ -55,7 +56,7 @@ use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; use jsonrpsee_types::error::{ BATCHES_NOT_SUPPORTED_CODE, BATCHES_NOT_SUPPORTED_MSG, ErrorCode, reject_too_big_batch_request, }; -use jsonrpsee_types::{ErrorObject, Id, deserialize_with_ext}; +use jsonrpsee_types::{ErrorObject, Id}; use soketto::handshake::http::is_upgrade_request; use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; use tokio::sync::{OwnedSemaphorePermit, mpsc, watch}; diff --git a/server/src/utils.rs b/server/src/utils.rs index 8461f6681f..86902a84d6 100644 --- a/server/src/utils.rs +++ b/server/src/utils.rs @@ -140,3 +140,59 @@ where } } } + +/// Deserialize calls, notifications and responses with HTTP extensions. +pub mod deserialize_with_ext { + /// Method call. + pub mod call { + use jsonrpsee_types::Request; + + /// Wrapper over `serde_json::from_slice` that sets the extensions. + pub fn from_slice<'a>( + data: &'a [u8], + extensions: &'a http::Extensions, + ) -> Result, serde_json::Error> { + let mut req: Request = serde_json::from_slice(data)?; + *req.extensions_mut() = extensions.clone(); + Ok(req) + } + + /// Wrapper over `serde_json::from_str` that sets the extensions. + pub fn from_str<'a>(data: &'a str, extensions: &'a http::Extensions) -> Result, serde_json::Error> { + let mut req: Request = serde_json::from_str(data)?; + *req.extensions_mut() = extensions.clone(); + Ok(req) + } + } + + /// Notification. + pub mod notif { + use jsonrpsee_types::Notification; + + /// Wrapper over `serde_json::from_slice` that sets the extensions. + pub fn from_slice<'a, T>( + data: &'a [u8], + extensions: &'a http::Extensions, + ) -> Result, serde_json::Error> + where + T: serde::Deserialize<'a>, + { + let mut notif: Notification = serde_json::from_slice(data)?; + *notif.extensions_mut() = extensions.clone(); + Ok(notif) + } + + /// Wrapper over `serde_json::from_str` that sets the extensions. + pub fn from_str<'a, T>( + data: &'a str, + extensions: &http::Extensions, + ) -> Result, serde_json::Error> + where + T: serde::Deserialize<'a>, + { + let mut notif: Notification = serde_json::from_str(data)?; + *notif.extensions_mut() = extensions.clone(); + Ok(notif) + } + } +} diff --git a/types/src/lib.rs b/types/src/lib.rs index f1ee76d182..c5dcd5771a 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -48,91 +48,3 @@ pub use http::Extensions; pub use params::{Id, InvalidRequestId, Params, ParamsSequence, SubscriptionId, TwoPointZero}; pub use request::{InvalidRequest, Notification, Request}; pub use response::{Response, ResponsePayload, SubscriptionPayload, SubscriptionResponse, Success as ResponseSuccess}; - -/// Deserialize calls, notifications and responses with HTTP extensions. -pub mod deserialize_with_ext { - - /// Method call. - pub mod call { - use crate::Request; - - /// Wrapper over `serde_json::from_slice` that sets the extensions. - pub fn from_slice<'a>( - data: &'a [u8], - extensions: &'a http::Extensions, - ) -> Result, serde_json::Error> { - let mut req: Request = serde_json::from_slice(data)?; - *req.extensions_mut() = extensions.clone(); - Ok(req) - } - - /// Wrapper over `serde_json::from_str` that sets the extensions. - pub fn from_str<'a>(data: &'a str, extensions: &'a http::Extensions) -> Result, serde_json::Error> { - let mut req: Request = serde_json::from_str(data)?; - *req.extensions_mut() = extensions.clone(); - Ok(req) - } - } - - /// Notification. - pub mod notif { - use crate::Notification; - - /// Wrapper over `serde_json::from_slice` that sets the extensions. - pub fn from_slice<'a, T>( - data: &'a [u8], - extensions: &'a http::Extensions, - ) -> Result, serde_json::Error> - where - T: serde::Deserialize<'a>, - { - let mut notif: Notification = serde_json::from_slice(data)?; - *notif.extensions_mut() = extensions.clone(); - Ok(notif) - } - - /// Wrapper over `serde_json::from_str` that sets the extensions. - pub fn from_str<'a, T>( - data: &'a str, - extensions: &http::Extensions, - ) -> Result, serde_json::Error> - where - T: serde::Deserialize<'a>, - { - let mut notif: Notification = serde_json::from_str(data)?; - *notif.extensions_mut() = extensions.clone(); - Ok(notif) - } - } - - /// Response. - pub mod response { - use crate::Response; - - /// Wrapper over `serde_json::from_slice` that sets the extensions. - pub fn from_slice<'a, T>( - data: &'a [u8], - extensions: &'a http::Extensions, - ) -> Result, serde_json::Error> - where - T: serde::Deserialize<'a> + Clone, - { - let mut res: Response = serde_json::from_slice(data)?; - *res.extensions_mut() = extensions.clone(); - Ok(res) - } - - /// Wrapper over `serde_json::from_str` that sets the extensions. - pub fn from_str<'a, T>( - data: &'a str, - extensions: &'a http::Extensions, - ) -> Result, serde_json::Error> - where - T: serde::Deserialize<'a> + Clone, - { - let mut res: Response = serde_json::from_str(data)?; - *res.extensions_mut() = extensions.clone(); - Ok(res) - } - } -} From 2a67151d5e430d42fa3adfac5fd93049aca49afb Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 11 Apr 2025 15:00:49 +0200 Subject: [PATCH 51/52] Update core/src/client/mod.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- core/src/client/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index a4b1ddabef..93c27cfaa4 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -277,7 +277,7 @@ pub struct Subscription { is_closed: bool, /// Channel to send requests to the background task. to_back: mpsc::Sender, - /// Channel from which we receive notifications from the server, as encoded as JSON. + /// Channel from which we receive notifications from the server, as encoded JSON. rx: SubscriptionReceiver, /// Callback kind. kind: Option, From 84b4e1c2041e3e3b8985a9e19410fac20f1b5282 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 11 Apr 2025 15:20:03 +0200 Subject: [PATCH 52/52] client: enable default rpc logger --- client/http-client/src/client.rs | 15 +++++++++------ client/wasm-client/src/lib.rs | 15 ++++++++------- core/src/client/async_client/mod.rs | 17 ++++++++++------- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index c0724beb1a..ddd291b95f 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -39,6 +39,7 @@ use jsonrpsee_core::client::{ BatchResponse, ClientT, Error, IdKind, MethodResponse, RequestIdManager, Subscription, SubscriptionClientT, generate_batch_id_range, }; +use jsonrpsee_core::middleware::layer::RpcLoggerLayer; use jsonrpsee_core::middleware::{Batch, RpcServiceBuilder, RpcServiceT}; use jsonrpsee_core::params::BatchRequestBuilder; use jsonrpsee_core::traits::ToRpcParams; @@ -52,6 +53,8 @@ use tower::{Layer, Service}; #[cfg(feature = "tls")] use crate::{CertificateStore, CustomCertStore}; +type Logger = tower::layer::util::Stack; + /// HTTP client builder. /// /// # Examples @@ -76,7 +79,7 @@ use crate::{CertificateStore, CustomCertStore}; /// } /// ``` #[derive(Clone, Debug)] -pub struct HttpClientBuilder { +pub struct HttpClientBuilder { max_request_size: u32, max_response_size: u32, request_timeout: Duration, @@ -297,7 +300,7 @@ where } } -impl Default for HttpClientBuilder { +impl Default for HttpClientBuilder { fn default() -> Self { Self { max_request_size: TEN_MB_SIZE_BYTES, @@ -308,16 +311,16 @@ impl Default for HttpClientBuilder { id_kind: IdKind::Number, headers: HeaderMap::new(), service_builder: tower::ServiceBuilder::new(), - rpc_middleware: RpcServiceBuilder::default(), + rpc_middleware: RpcServiceBuilder::default().rpc_logger(1024), tcp_no_delay: true, max_concurrent_requests: None, } } } -impl HttpClientBuilder { +impl HttpClientBuilder { /// Create a new builder. - pub fn new() -> HttpClientBuilder { + pub fn new() -> HttpClientBuilder { HttpClientBuilder::default() } } @@ -337,7 +340,7 @@ pub struct HttpClient { impl HttpClient { /// Create a builder for the HttpClient. - pub fn builder() -> HttpClientBuilder { + pub fn builder() -> HttpClientBuilder { HttpClientBuilder::new() } diff --git a/client/wasm-client/src/lib.rs b/client/wasm-client/src/lib.rs index e98a1b2eeb..ee959331c6 100644 --- a/client/wasm-client/src/lib.rs +++ b/client/wasm-client/src/lib.rs @@ -38,8 +38,9 @@ use std::time::Duration; use jsonrpsee_client_transport::web; use jsonrpsee_core::client::async_client::RpcService; use jsonrpsee_core::client::{Error, IdKind}; -use jsonrpsee_core::middleware::RpcServiceBuilder; -use tower::layer::util::Identity; +use jsonrpsee_core::middleware::{RpcServiceBuilder, layer::RpcLoggerLayer}; + +type Logger = tower::layer::util::Stack; /// Builder for [`Client`]. /// @@ -62,7 +63,7 @@ use tower::layer::util::Identity; /// /// ``` #[derive(Clone, Debug)] -pub struct WasmClientBuilder { +pub struct WasmClientBuilder { id_kind: IdKind, max_concurrent_requests: usize, max_buffer_capacity_per_subscription: usize, @@ -70,21 +71,21 @@ pub struct WasmClientBuilder { service_builder: RpcServiceBuilder, } -impl Default for WasmClientBuilder { +impl Default for WasmClientBuilder { fn default() -> Self { Self { id_kind: IdKind::Number, max_concurrent_requests: 256, max_buffer_capacity_per_subscription: 1024, request_timeout: Duration::from_secs(60), - service_builder: RpcServiceBuilder::default(), + service_builder: RpcServiceBuilder::default().rpc_logger(1024), } } } -impl WasmClientBuilder { +impl WasmClientBuilder { /// Create a new WASM client builder. - pub fn new() -> WasmClientBuilder { + pub fn new() -> WasmClientBuilder { WasmClientBuilder::default() } } diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index 0e7d9e57a7..663510c6ab 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -41,6 +41,7 @@ use crate::client::{ SubscriptionKind, TransportReceiverT, TransportSenderT, }; use crate::error::RegisterMethodError; +use crate::middleware::layer::RpcLoggerLayer; use crate::middleware::{Batch, IsBatch, IsSubscription, Request, RpcServiceBuilder, RpcServiceT}; use crate::params::{BatchRequestBuilder, EmptyBatchRequest}; use crate::traits::ToRpcParams; @@ -70,6 +71,8 @@ use super::{FrontToBack, IdKind, MethodResponse, RequestIdManager, generate_batc pub(crate) type Notification<'a> = jsonrpsee_types::Notification<'a, Option>>; +type Logger = tower::layer::util::Stack; + const LOG_TARGET: &str = "jsonrpsee-client"; const NOT_POISONED: &str = "Not poisoned; qed"; @@ -114,7 +117,7 @@ impl PingConfig { } /// Configure how long to wait for the WebSocket pong. - /// When this limit is expired it's regarded as inresponsive. + /// When this limit is expired it's regarded as unresponsive. /// /// You may configure how many times the connection is allowed to /// be inactive by [`PingConfig::max_failures`]. @@ -180,7 +183,7 @@ impl ErrorFromBack { /// Builder for [`Client`]. #[derive(Debug, Clone)] -pub struct ClientBuilder { +pub struct ClientBuilder { request_timeout: Duration, max_concurrent_requests: usize, max_buffer_capacity_per_subscription: usize, @@ -190,7 +193,7 @@ pub struct ClientBuilder { service_builder: RpcServiceBuilder, } -impl Default for ClientBuilder { +impl Default for ClientBuilder { fn default() -> Self { Self { request_timeout: Duration::from_secs(60), @@ -199,14 +202,14 @@ impl Default for ClientBuilder { id_kind: IdKind::Number, ping_config: None, tcp_no_delay: true, - service_builder: RpcServiceBuilder::default(), + service_builder: RpcServiceBuilder::default().rpc_logger(1024), } } } impl ClientBuilder { /// Create a new client builder. - pub fn new() -> ClientBuilder { + pub fn new() -> ClientBuilder { ClientBuilder::default() } } @@ -442,8 +445,8 @@ pub struct Client { impl Client { /// Create a builder for the client. - pub fn builder() -> ClientBuilder { - ClientBuilder::::new() + pub fn builder() -> ClientBuilder { + ClientBuilder::new() } }