From 93c7dc293934f3e80fb24da2cd479907be02344c Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 10 May 2024 18:37:49 +0200 Subject: [PATCH 01/22] upgrade to hyper v1.0 --- client/http-client/Cargo.toml | 15 +- client/http-client/src/client.rs | 34 ++- client/http-client/src/lib.rs | 2 + client/http-client/src/transport.rs | 45 ++-- client/transport/Cargo.toml | 6 +- client/ws-client/Cargo.toml | 2 +- core/Cargo.toml | 15 +- core/src/http_helpers.rs | 33 +-- examples/Cargo.toml | 8 +- examples/examples/http.rs | 4 +- examples/examples/http_middleware.rs | 2 +- examples/examples/http_proxy_middleware.rs | 14 +- examples/examples/jsonrpsee_as_service.rs | 167 ++++++++----- .../jsonrpsee_server_low_level_api.rs | 229 ++++++++++-------- examples/examples/ws.rs | 1 + examples/examples/ws_dual_stack.rs | 6 +- proc-macros/Cargo.toml | 3 +- server/Cargo.toml | 9 +- server/src/lib.rs | 3 + server/src/middleware/http/authority.rs | 33 ++- server/src/middleware/http/host_filter.rs | 12 +- .../src/middleware/http/proxy_get_request.rs | 30 ++- server/src/server.rs | 92 ++++--- server/src/tests/helpers.rs | 139 +++++++---- server/src/tests/ws.rs | 2 +- server/src/transport/http.rs | 53 ++-- server/src/transport/ws.rs | 34 +-- test-utils/Cargo.toml | 6 +- test-utils/src/helpers.rs | 73 ++++-- test-utils/src/mocks.rs | 61 +++-- tests/Cargo.toml | 6 +- tests/tests/integration_tests.rs | 40 +-- 32 files changed, 705 insertions(+), 474 deletions(-) diff --git a/client/http-client/Cargo.toml b/client/http-client/Cargo.toml index 73c9570c06..2b196a9112 100644 --- a/client/http-client/Cargo.toml +++ b/client/http-client/Cargo.toml @@ -15,13 +15,16 @@ publish = true [dependencies] async-trait = "0.1" -hyper = { version = "0.14.10", features = ["client", "http1", "http2", "tcp"] } -hyper-rustls = { version = "0.24", optional = true, default-features = false, features = ["http1", "http2", "tls12", "logging"] } +hyper = { version = "1.3", features = ["client", "http1", "http2"] } +hyper-rustls = { version = "0.27.1", optional = true, default-features = false, features = ["http1", "http2", "tls12", "logging"] } +hyper-util = { version = "*", features = ["client", "client-legacy"] } +http-body = "1" +http-body-util = "0.1.1" jsonrpsee-types = { workspace = true } jsonrpsee-core = { workspace = true, features = ["client", "http-helpers"] } serde = { version = "1.0", default-features = false, features = ["derive"] } -serde_json = "1.0" -thiserror = "1.0" +serde_json = "1" +thiserror = "1" tokio = { version = "1.16", features = ["time"] } tracing = "0.1.34" tower = { version = "0.4.13", features = ["util"] } @@ -34,8 +37,8 @@ tokio = { version = "1.16", features = ["net", "rt-multi-thread", "macros"] } [features] default = ["native-tls"] -native-tls = ["hyper-rustls/native-tokio", "__tls"] -webpki-tls = ["hyper-rustls/webpki-tokio", "__tls"] +native-tls = ["hyper-rustls/native-tokio", "hyper-rustls/aws-lc-rs", "__tls"] +webpki-tls = ["hyper-rustls/webpki-tokio", "hyper-rustls/aws-lc-rs", "__tls"] # Internal feature to indicate whether TLS is enabled. # Does nothing on its own. diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 1a80944b3d..0ad17df6c1 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -30,12 +30,11 @@ use std::fmt; use std::sync::Arc; use std::time::Duration; -use crate::transport::{self, Error as TransportError, HttpBackend, HttpTransportClient, HttpTransportClientBuilder}; +use crate::transport::{self, Error as TransportError, HttpTransportClient, HttpTransportClientBuilder}; use crate::types::{NotificationSer, RequestSer, Response}; +use crate::ResponseBody; use async_trait::async_trait; -use hyper::body::HttpBody; use hyper::http::HeaderMap; -use hyper::Body; use jsonrpsee_core::client::{ generate_batch_id_range, BatchResponse, CertificateStore, ClientT, Error, IdKind, RequestIdManager, Subscription, SubscriptionClientT, @@ -189,8 +188,8 @@ impl HttpClientBuilder { impl HttpClientBuilder where L: Layer, - S: Service, Response = hyper::Response, Error = TransportError> + Clone, - B: HttpBody + Send + 'static, + S: Service, Response = hyper::Response, Error = TransportError> + Clone, + B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into>, { @@ -207,6 +206,7 @@ where max_log_length, service_builder, tcp_no_delay, + .. } = self; let transport = HttpTransportClientBuilder::new() @@ -254,7 +254,7 @@ 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. @@ -265,7 +265,7 @@ pub struct HttpClient { impl HttpClient { /// Create a builder for the HttpClient. - pub fn builder() -> HttpClientBuilder { + pub fn builder() -> HttpClientBuilder { HttpClientBuilder::new() } } @@ -273,9 +273,13 @@ impl HttpClient { #[async_trait] impl ClientT for HttpClient where - S: Service, Response = hyper::Response, Error = TransportError> + Send + Sync + Clone, - >>::Future: Send, - B: HttpBody + Send + 'static, + S: Service, Response = hyper::Response, Error = TransportError> + + Send + + Sync + + Clone, + >>::Future: Send, + B: http_body::Body + Send + Unpin + 'static, + B::Error: Into>, B::Data: Send, { #[instrument(name = "notification", skip(self, params), level = "trace")] @@ -405,10 +409,14 @@ where #[async_trait] impl SubscriptionClientT for HttpClient where - S: Service, Response = hyper::Response, Error = TransportError> + Send + Sync + Clone, - >>::Future: Send, - B: HttpBody + Send + 'static, + S: Service, Response = hyper::Response, Error = TransportError> + + Send + + Sync + + Clone, + >>::Future: Send, + B: http_body::Body + Send + Unpin + 'static, B::Data: Send, + B::Error: Into>, { /// 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 e2693112e8..852a9fa2ba 100644 --- a/client/http-client/src/lib.rs +++ b/client/http-client/src/lib.rs @@ -46,3 +46,5 @@ mod tests; pub use client::{HttpClient, HttpClientBuilder}; pub use hyper::http::{HeaderMap, HeaderValue}; pub use jsonrpsee_types as types; + +pub(crate) type ResponseBody = http_body_util::Full; diff --git a/client/http-client/src/transport.rs b/client/http-client/src/transport.rs index 2f7f4cdaae..058c69e7f2 100644 --- a/client/http-client/src/transport.rs +++ b/client/http-client/src/transport.rs @@ -6,9 +6,10 @@ // that we need to be guaranteed that hyper doesn't re-use an existing connection if we ever reset // the JSON-RPC request id to a value that might have already been used. -use hyper::body::{Body, HttpBody}; -use hyper::client::{Client, HttpConnector}; use hyper::http::{HeaderMap, HeaderValue}; +use hyper_util::client::legacy::connect::HttpConnector; +use hyper_util::client::legacy::Client; +use hyper_util::rt::TokioExecutor; use jsonrpsee_core::client::CertificateStore; use jsonrpsee_core::tracing::client::{rx_log_from_bytes, tx_log_from_str}; use jsonrpsee_core::{ @@ -24,11 +25,13 @@ use tower::layer::util::Identity; use tower::{Layer, Service, ServiceExt}; use url::Url; +use crate::ResponseBody; + const CONTENT_TYPE_JSON: &str = "application/json"; /// Wrapper over HTTP transport and connector. #[derive(Debug)] -pub enum HttpBackend { +pub enum HttpBackend { /// Hyper client with https connector. #[cfg(feature = "__tls")] Https(Client, B>), @@ -36,7 +39,7 @@ pub enum HttpBackend { Http(Client), } -impl Clone for HttpBackend { +impl Clone for HttpBackend { fn clone(&self) -> Self { match self { Self::Http(inner) => Self::Http(inner.clone()), @@ -48,11 +51,11 @@ impl Clone for HttpBackend { impl tower::Service> for HttpBackend where - B: HttpBody + Send + 'static, + B: http_body::Body + Send + 'static + Unpin, B::Data: Send, B::Error: Into>, { - type Response = hyper::Response; + type Response = hyper::Response; type Error = Error; type Future = Pin> + Send>>; @@ -62,7 +65,7 @@ where #[cfg(feature = "__tls")] Self::Https(inner) => inner.poll_ready(ctx), } - .map_err(|e| Error::Http(e.into())) + .map_err(|e| Error::Http(HttpError::Stream(e.into()))) } fn call(&mut self, req: hyper::Request) -> Self::Future { @@ -72,7 +75,7 @@ where Self::Https(inner) => inner.call(req), }; - Box::pin(async move { resp.await.map_err(|e| Error::Http(e.into())) }) + Box::pin(async move { resp.await.map_err(|e| Error::Http(HttpError::Stream(e.into()))) }) } } @@ -177,9 +180,9 @@ impl HttpTransportClientBuilder { /// Build a [`HttpTransportClient`]. pub fn build(self, target: impl AsRef) -> Result, Error> where - L: Layer, Service = S>, - S: Service, Response = hyper::Response, Error = Error> + Clone, - B: HttpBody + Send + 'static, + L: Layer, + S: Service, Response = hyper::Response, Error = Error> + Clone, + B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into>, { @@ -202,7 +205,7 @@ impl HttpTransportClientBuilder { "http" => { let mut connector = HttpConnector::new(); connector.set_nodelay(tcp_no_delay); - HttpBackend::Http(Client::builder().build(connector)) + HttpBackend::Http(Client::builder(TokioExecutor::new()).build(connector)) } #[cfg(feature = "__tls")] "https" => { @@ -214,9 +217,8 @@ impl HttpTransportClientBuilder { #[cfg(feature = "native-tls")] CertificateStore::Native => hyper_rustls::HttpsConnectorBuilder::new() .with_native_roots() - .https_or_http() - .enable_all_versions() - .wrap_connector(http_conn), + .map(|c| c.https_or_http().enable_all_versions().wrap_connector(http_conn)) + .map_err(|_| Error::InvalidCertficateStore)?, #[cfg(feature = "webpki-tls")] CertificateStore::WebPki => hyper_rustls::HttpsConnectorBuilder::new() .with_webpki_roots() @@ -226,7 +228,7 @@ impl HttpTransportClientBuilder { _ => return Err(Error::InvalidCertficateStore), }; - HttpBackend::Https(Client::builder().build::<_, hyper::Body>(https_conn)) + HttpBackend::Https(Client::builder(TokioExecutor::new()).build(https_conn)) } _ => { #[cfg(feature = "__tls")] @@ -281,9 +283,10 @@ pub struct HttpTransportClient { impl HttpTransportClient where - S: Service, Response = hyper::Response, Error = Error> + Clone, - B: HttpBody + Send + 'static, + S: Service, Response = hyper::Response, Error = Error> + Clone, + B: http_body::Body + Send + Unpin + 'static, B::Data: Send, + B::Error: Into>, { async fn inner_send(&self, body: String) -> Result, Error> { if body.len() > self.max_request_size as usize { @@ -294,7 +297,8 @@ where if let Some(headers) = req.headers_mut() { *headers = self.headers.clone(); } - let req = req.body(From::from(body)).expect("URI and request headers are valid; qed"); + + let req = req.body(body.into()).expect("Failed to create request"); let response = self.client.clone().ready().await?.call(req).await?; if response.status().is_success() { @@ -310,7 +314,8 @@ where let response = self.inner_send(body).await?; let (parts, body) = response.into_parts(); - let (body, _) = http_helpers::read_body(&parts.headers, body, self.max_response_size).await?; + + 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); diff --git a/client/transport/Cargo.toml b/client/transport/Cargo.toml index 56a4bb2877..cfb95e1af4 100644 --- a/client/transport/Cargo.toml +++ b/client/transport/Cargo.toml @@ -21,7 +21,7 @@ tracing = "0.1.34" thiserror = { version = "1", optional = true } futures-channel = { version = "0.3.14", default-features = false, optional = true } futures-util = { version = "0.3.14", default-features = false, features = ["alloc"], optional = true } -http = { version = "0.2", optional = true } +http = { version = "1", optional = true } tokio-util = { version = "0.7", features = ["compat"], optional = true } tokio = { version = "1.16", features = ["net", "time", "macros"], optional = true } pin-project = { version = "1", optional = true } @@ -30,11 +30,11 @@ url = { version = "2.4.0", optional = true } # tls rustls-native-certs = { version = "0.7", optional = true } webpki-roots = { version = "0.26", optional = true } -tokio-rustls = { version = "0.25", optional = true } +tokio-rustls = { version = "0.26", optional = true } rustls-pki-types = { version = "1", optional = true } # ws -soketto = { version = "0.7.1", optional = true } +soketto = { version = "0.8", optional = true } # web-sys gloo-net = { version = "0.5.0", default-features = false, features = ["json", "websocket"], optional = true } diff --git a/client/ws-client/Cargo.toml b/client/ws-client/Cargo.toml index f0947af370..85b14d16b2 100644 --- a/client/ws-client/Cargo.toml +++ b/client/ws-client/Cargo.toml @@ -14,7 +14,7 @@ readme.workspace = true publish = true [dependencies] -http = "0.2.0" +http = "1" jsonrpsee-types = { workspace = true } jsonrpsee-client-transport = { workspace = true, features = ["ws"] } jsonrpsee-core = { workspace = true, features = ["async-client"] } diff --git a/core/Cargo.toml b/core/Cargo.toml index 3566807b1a..0f5b918b93 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -25,7 +25,8 @@ tracing = "0.1.34" # optional deps futures-util = { version = "0.3.14", default-features = false, optional = true } -hyper = { version = "0.14.10", default-features = false, features = ["stream"], optional = true } +hyper = { version = "1.3", default-features = false, optional = true } +http-body-util = "0.1.1" rustc-hash = { version = "1", optional = true } rand = { version = "0.8", optional = true } parking_lot = { version = "0.12", optional = true } @@ -38,16 +39,7 @@ pin-project = { version = "1", optional = true } [features] default = [] http-helpers = ["hyper", "futures-util"] -server = [ - "futures-util/alloc", - "rustc-hash/std", - "parking_lot", - "rand", - "tokio/rt", - "tokio/sync", - "tokio/macros", - "tokio/time", -] +server = ["futures-util/alloc", "rustc-hash/std", "parking_lot", "rand", "tokio/rt", "tokio/sync", "tokio/macros", "tokio/time"] client = ["futures-util/sink", "tokio/sync"] async-client = [ "client", @@ -75,6 +67,7 @@ async-wasm-client = [ serde_json = "1.0" tokio = { version = "1.16", features = ["macros", "rt"] } jsonrpsee = { path = "../jsonrpsee", features = ["server", "macros"] } +http-body-util = "0.1.1" [package.metadata.docs.rs] all-features = true diff --git a/core/src/http_helpers.rs b/core/src/http_helpers.rs index d354240b6f..a37b5bd2cc 100644 --- a/core/src/http_helpers.rs +++ b/core/src/http_helpers.rs @@ -26,7 +26,8 @@ //! Utility methods relying on hyper -use hyper::body::{Buf, HttpBody}; +use http_body_util::{BodyExt, Limited}; +use hyper::body::{Body, Buf}; /// Represents error that can when reading with a HTTP body. #[derive(Debug, thiserror::Error)] @@ -39,7 +40,7 @@ pub enum HttpError { Malformed, /// Represents error that can happen when dealing with HTTP streams. #[error("{0}")] - Stream(#[from] hyper::Error), + Stream(#[from] Box), } /// Read a data from [`hyper::body::HttpBody`] and return the data if it is valid JSON and within the allowed size range. @@ -49,8 +50,9 @@ pub enum HttpError { /// Returns `Err` if the body was too large or the body couldn't be read. pub async fn read_body(headers: &hyper::HeaderMap, body: B, max_body_size: u32) -> Result<(Vec, bool), HttpError> where - B: HttpBody + Send + 'static, + B: Body + Send + 'static, B::Data: Send, + B::Error: Into>, { // NOTE(niklasad1): Values bigger than `u32::MAX` will be turned into zero here. This is unlikely to occur in // practice and in that case we fallback to allocating in the while-loop below instead of pre-allocating. @@ -64,10 +66,15 @@ where // Allocate up to 16KB initially. let mut received_data = Vec::with_capacity(std::cmp::min(body_size as usize, 16 * 1024)); + let mut limited_body = Limited::new(body, max_body_size as usize); + let mut is_single = None; - while let Some(d) = body.data().await { - let data = d.map_err(HttpError::Stream)?; + while let Some(frame_or_err) = limited_body.frame().await { + let frame = frame_or_err.map_err(HttpError::Stream)?; + let Some(data) = frame.data_ref() else { + continue; + }; // If it's the first chunk, trim the whitespaces to determine whether it's valid JSON-RPC call. if received_data.is_empty() { @@ -86,17 +93,9 @@ where _ => return Err(HttpError::Malformed), }; - if data.chunk().len() - skip > max_body_size as usize { - return Err(HttpError::TooLarge); - } - // ignore whitespace as these doesn't matter just makes the JSON decoding slower. received_data.extend_from_slice(&data.chunk()[skip..]); } else { - if data.chunk().len() + received_data.len() > max_body_size as usize { - return Err(HttpError::TooLarge); - } - received_data.extend_from_slice(data.chunk()); } } @@ -145,12 +144,16 @@ pub fn read_header_values<'a>( #[cfg(test)] mod tests { - use super::{read_body, read_header_content_length}; + use super::{read_body, read_header_content_length, HttpError}; + use http_body_util::BodyExt; + + type Body = http_body_util::Full; #[tokio::test] async fn body_to_bytes_size_limit_works() { let headers = hyper::header::HeaderMap::new(); - let body = hyper::Body::from(vec![0; 128]); + let full_body = Body::from(vec![0; 128]); + let body = full_body.map_err(|e| HttpError::Stream(e.into())); assert!(read_body(&headers, body, 127).await.is_err()); } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 3882a7cd72..afe81cb577 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -9,6 +9,7 @@ publish = false [dev-dependencies] anyhow = "1" +http-body-util = "0.1" futures = "0.3" jsonrpsee = { path = "../jsonrpsee", features = ["server", "http-client", "ws-client", "macros", "client-ws-transport-native-tls"] } tracing = "0.1.34" @@ -16,7 +17,8 @@ tracing-subscriber = { version = "0.3.3", features = ["env-filter"] } tokio = { version = "1.16", features = ["full"] } tokio-stream = { version = "0.1", features = ["sync"] } serde_json = { version = "1" } -tower-http = { version = "0.4.0", features = ["full"] } +tower-http = { version = "0.5.2", features = ["full"] } tower = { version = "0.4.13", features = ["full"] } -hyper = "0.14.20" -console-subscriber = "0.2.0" +hyper = "1.3" +hyper-util = { version = "0.1.3", features = ["client", "client-legacy"]} +console-subscriber = "0.2.0" \ No newline at end of file diff --git a/examples/examples/http.rs b/examples/examples/http.rs index ac941f136e..229debdfd3 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -36,6 +36,8 @@ use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}; use tower_http::LatencyUnit; use tracing_subscriber::util::SubscriberInitExt; +type ResponseBody = http_body_util::Full; + #[tokio::main] async fn main() -> anyhow::Result<()> { let filter = tracing_subscriber::EnvFilter::try_from_default_env()? @@ -49,7 +51,7 @@ async fn main() -> anyhow::Result<()> { .layer( TraceLayer::new_for_http() .on_request( - |request: &hyper::Request, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), + |request: &hyper::Request, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), ) .on_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| { tracing::info!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk") diff --git a/examples/examples/http_middleware.rs b/examples/examples/http_middleware.rs index 0130eff248..8e1c02749e 100644 --- a/examples/examples/http_middleware.rs +++ b/examples/examples/http_middleware.rs @@ -96,7 +96,7 @@ async fn run_server() -> anyhow::Result { .layer( TraceLayer::new_for_http() .on_request( - |request: &hyper::Request, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), + |request: &hyper::Request, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), ) .on_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| { tracing::info!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk") diff --git a/examples/examples/http_proxy_middleware.rs b/examples/examples/http_proxy_middleware.rs index 33322d5855..04f83cfc02 100644 --- a/examples/examples/http_proxy_middleware.rs +++ b/examples/examples/http_proxy_middleware.rs @@ -37,7 +37,9 @@ //! This functionality is useful for services which would //! like to query a certain `URI` path for statistics. -use hyper::{Body, Client, Request}; +use hyper::Request; +use hyper_util::client::legacy::Client; +use hyper_util::rt::TokioExecutor; use std::net::SocketAddr; use std::time::Duration; @@ -47,6 +49,8 @@ use jsonrpsee::rpc_params; use jsonrpsee::server::middleware::http::ProxyGetRequestLayer; use jsonrpsee::server::{RpcModule, Server}; +type EmptyBody = http_body_util::Empty; + #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::FmtSubscriber::builder() @@ -63,17 +67,17 @@ async fn main() -> anyhow::Result<()> { println!("[main]: response: {:?}", response); // Use hyper client to manually submit a `GET /health` request. - let http_client = Client::new(); + let http_client = Client::builder(TokioExecutor::new()).build_http(); let uri = format!("http://{}/health", addr); - let req = Request::builder().method("GET").uri(&uri).body(Body::empty())?; + let req = Request::builder().method("GET").uri(&uri).body(EmptyBody::new())?; println!("[main]: Submit proxy request: {:?}", req); let res = http_client.request(req).await?; println!("[main]: Received proxy response: {:?}", res); // Interpret the response as String. - let bytes = hyper::body::to_bytes(res.into_body()).await.unwrap(); - let out = String::from_utf8(bytes.to_vec()).unwrap(); + let collected = http_body_util::BodyExt::collect(res.into_body()).await?; + let out = String::from_utf8(collected.to_bytes().to_vec()).unwrap(); println!("[main]: Interpret proxy response: {:?}", out); assert_eq!(out.as_str(), "{\"health\":true}"); diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index a87da4f730..0c2bbe5157 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -31,15 +31,17 @@ //! The typical use-case for this is when one wants to have //! access to HTTP related things. -use std::error::Error as StdError; +use std::convert::Infallible; use std::net::SocketAddr; +use std::pin::Pin; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; +use futures::future::{self, Either}; use futures::FutureExt; use hyper::header::AUTHORIZATION; -use hyper::server::conn::AddrStream; use hyper::HeaderMap; +use hyper_util::rt::TokioIo; use jsonrpsee::core::async_trait; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::proc_macros::rpc; @@ -48,6 +50,7 @@ use jsonrpsee::server::{stop_channel, ServerHandle, StopHandle, TowerServiceBuil use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, Request}; use jsonrpsee::ws_client::{HeaderValue, WsClientBuilder}; use jsonrpsee::{MethodResponse, Methods}; +use tokio::net::TcpListener; use tower::Service; use tower_http::cors::CorsLayer; use tracing_subscriber::util::SubscriberInitExt; @@ -111,7 +114,7 @@ async fn main() -> anyhow::Result<()> { let metrics = Metrics::default(); - let handle = run_server(metrics.clone()); + let handle = run_server(metrics.clone()).await?; tokio::spawn(handle.stopped()); { @@ -145,10 +148,10 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -fn run_server(metrics: Metrics) -> ServerHandle { - use hyper::service::{make_service_fn, service_fn}; +async fn run_server(metrics: Metrics) -> anyhow::Result { + use hyper::service::service_fn; - let addr = SocketAddr::from(([127, 0, 0, 1], 9944)); + let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 9944))).await?; // This state is cloned for every connection // all these types based on Arcs and it should @@ -181,70 +184,108 @@ fn run_server(metrics: Metrics) -> ServerHandle { .to_service_builder(), }; - // And a MakeService to handle each connection... - let make_service = make_service_fn(move |_conn: &AddrStream| { - let per_conn = per_conn.clone(); - - async move { - Ok::<_, Box>(service_fn(move |req| { - let is_websocket = jsonrpsee::server::ws::is_upgrade_request(&req); - let transport_label = if is_websocket { "ws" } else { "http" }; - let PerConnection { methods, stop_handle, metrics, svc_builder } = per_conn.clone(); - - // 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 mut svc = svc_builder.set_rpc_middleware(rpc_middleware).build(methods, stop_handle); - - if is_websocket { - // Utilize the session close future to know when the actual WebSocket - // session was closed. - let session_close = svc.on_session_closed(); - - // A little bit weird API but the response to HTTP request must be returned below - // and we spawn a task to register when the session is closed. - tokio::spawn(async move { - session_close.await; - tracing::info!("Closed WebSocket connection"); - metrics.closed_ws_connections.fetch_add(1, Ordering::Relaxed); + tokio::spawn(async move { + loop { + // The `tokio::select!` macro is used to wait for either of the + // listeners to accept a new connection or for the server to be + // stopped. + let stream = tokio::select! { + res = listener.accept() => { + match res { + Ok((stream, _remote_addr)) => stream, + Err(e) => { + tracing::error!("failed to accept v4 connection: {:?}", e); + continue; + } + } + } + _ = per_conn.stop_handle.clone().shutdown() => break, + }; + let per_conn = per_conn.clone(); + + tokio::spawn(async move { + let stop_handle2 = per_conn.stop_handle.clone(); + let per_conn = per_conn.clone(); + + let svc = service_fn(move |req| { + let is_websocket = jsonrpsee::server::ws::is_upgrade_request(&req); + let transport_label = if is_websocket { "ws" } else { "http" }; + let PerConnection { methods, stop_handle, metrics, svc_builder } = per_conn.clone(); + + // 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 } }); - async move { - tracing::info!("Opened WebSocket connection"); - metrics.opened_ws_connections.fetch_add(1, Ordering::Relaxed); - svc.call(req).await - } - .boxed() - } else { - // HTTP. - async move { - tracing::info!("Opened HTTP connection"); - metrics.http_calls.fetch_add(1, Ordering::Relaxed); - let rp = svc.call(req).await; - - if rp.is_ok() { - metrics.success_http_calls.fetch_add(1, Ordering::Relaxed); + let mut svc = svc_builder.set_rpc_middleware(rpc_middleware).build(methods, stop_handle); + + if is_websocket { + // Utilize the session close future to know when the actual WebSocket + // session was closed. + let session_close = svc.on_session_closed(); + + // A little bit weird API but the response to HTTP request must be returned below + // and we spawn a task to register when the session is closed. + tokio::spawn(async move { + session_close.await; + tracing::info!("Closed WebSocket connection"); + metrics.closed_ws_connections.fetch_add(1, Ordering::Relaxed); + }); + + async move { + tracing::info!("Opened WebSocket connection"); + metrics.opened_ws_connections.fetch_add(1, Ordering::Relaxed); + let rp = svc.call(req).await.unwrap(); + Ok::<_, Infallible>(rp) + } + .boxed() + } else { + // HTTP. + async move { + tracing::info!("Opened HTTP connection"); + metrics.http_calls.fetch_add(1, Ordering::Relaxed); + let rp = svc.call(req).await; + + if rp.is_ok() { + metrics.success_http_calls.fetch_add(1, Ordering::Relaxed); + } + + tracing::info!("Closed HTTP connection"); + // TODO: fix weird lifetime error. + Ok::<_, Infallible>(rp.unwrap()) } + .boxed() + } + }); - tracing::info!("Closed HTTP connection"); - rp + let conn = hyper::server::conn::http1::Builder::new() + .serve_connection(TokioIo::new(stream), svc) + .with_upgrades(); + let stopped = stop_handle2.shutdown(); + + // Pin the future so that it can be polled. + tokio::pin!(stopped); + + let res = match future::select(conn, stopped).await { + // Return the connection if not stopped. + Either::Left((conn, _)) => conn, + // If the server is stopped, we should gracefully shutdown + // the connection and poll it until it finishes. + Either::Right((_, mut conn)) => { + Pin::new(&mut conn).graceful_shutdown(); + conn.await } - .boxed() + }; + + // Log any errors that might have occurred. + if let Err(err) = res { + tracing::error!(err=?err, "HTTP connection failed"); } - })) + }); } }); - let server = hyper::Server::bind(&addr).serve(make_service); - - tokio::spawn(async move { - let graceful = server.with_graceful_shutdown(async move { stop_handle.shutdown().await }); - graceful.await.unwrap() - }); - - server_handle + Ok(server_handle) } diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index e09255b502..e712609ba2 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -41,12 +41,13 @@ use std::collections::HashSet; use std::convert::Infallible; use std::net::{IpAddr, SocketAddr}; +use std::pin::Pin; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; -use futures::future::BoxFuture; +use futures::future::{self, BoxFuture, Either}; use futures::FutureExt; -use hyper::server::conn::AddrStream; +use hyper_util::rt::TokioIo; use jsonrpsee::core::async_trait; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::proc_macros::rpc; @@ -57,6 +58,7 @@ use jsonrpsee::server::{ use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, Request}; use jsonrpsee::ws_client::WsClientBuilder; use jsonrpsee::{MethodResponse, Methods}; +use tokio::net::TcpListener; use tokio::sync::mpsc; use tokio::sync::Mutex as AsyncMutex; use tracing_subscriber::util::SubscriberInitExt; @@ -120,7 +122,7 @@ async fn main() -> anyhow::Result<()> { // Make a bunch of WebSocket calls to be blacklisted by server. { let mut i = 0; - let handle = run_server(); + let handle = run_server().await?; let client = WsClientBuilder::default().build("ws://127.0.0.1:9944").await.unwrap(); while client.is_connected() { @@ -141,7 +143,7 @@ async fn main() -> anyhow::Result<()> { // Make a bunch of HTTP calls to be blacklisted by server. { let mut i = 0; - let handle = run_server(); + let handle = run_server().await?; let client = HttpClientBuilder::default().build("http://127.0.0.1:9944").unwrap(); while client.say_hello().await.is_ok() { @@ -156,11 +158,11 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -fn run_server() -> ServerHandle { - use hyper::service::{make_service_fn, service_fn}; +async fn run_server() -> anyhow::Result { + use hyper::service::service_fn; // Construct our SocketAddr to listen on... - let addr = SocketAddr::from(([127, 0, 0, 1], 9944)); + let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 9944))).await?; // Each RPC call/connection get its own `stop_handle` // to able to determine whether the server has been stopped or not. @@ -201,102 +203,139 @@ fn run_server() -> ServerHandle { global_http_rate_limit: Default::default(), }; - // And a MakeService to handle each connection... - let make_service = make_service_fn(move |conn: &AddrStream| { - let per_conn = per_conn.clone(); - let remote_addr = conn.remote_addr(); - - async move { - Ok::<_, Infallible>(service_fn(move |req| { - let PerConnection { - methods, - stop_handle, - conn_guard, - conn_id, - blacklisted_peers, - global_http_rate_limit, - } = per_conn.clone(); - - // jsonrpsee expects a `conn permit` for each connection. - // - // This may be omitted if don't want to limit the number of connections - // to the server. - let Some(conn_permit) = conn_guard.try_acquire() else { - return async { Ok::<_, Infallible>(http::response::too_many_requests()) }.boxed(); - }; - - // The IP addr was blacklisted. - if blacklisted_peers.lock().unwrap().get(&remote_addr.ip()).is_some() { - return async { Ok(http::response::denied()) }.boxed(); + tokio::spawn(async move { + loop { + // The `tokio::select!` macro is used to wait for either of the + // listeners to accept a new connection or for the server to be + // stopped. + let (sock, remote_addr) = tokio::select! { + res = listener.accept() => { + match res { + Ok(sock) => sock, + Err(e) => { + tracing::error!("failed to accept v4 connection: {:?}", e); + continue; + } + } } + _ = per_conn.stop_handle.clone().shutdown() => break, + }; + let per_conn = per_conn.clone(); + + tokio::spawn(async move { + let stop_handle2 = per_conn.stop_handle.clone(); + let per_conn = per_conn.clone(); + + let svc = service_fn(move |req| { + let PerConnection { + methods, + stop_handle, + conn_guard, + conn_id, + blacklisted_peers, + global_http_rate_limit, + } = per_conn.clone(); + + // jsonrpsee expects a `conn permit` for each connection. + // + // This may be omitted if don't want to limit the number of connections + // to the server. + let Some(conn_permit) = conn_guard.try_acquire() else { + return async { Ok::<_, Infallible>(http::response::too_many_requests()) }.boxed(); + }; + + // The IP addr was blacklisted. + if blacklisted_peers.lock().unwrap().get(&remote_addr.ip()).is_some() { + return async { Ok(http::response::denied()) }.boxed(); + } - if ws::is_upgrade_request(&req) { - let (tx, mut disconnect) = mpsc::channel(1); - let rpc_service = RpcServiceBuilder::new().layer_fn(move |service| CallLimit { - service, - count: Default::default(), - state: tx.clone(), - }); - - let conn = ConnectionState::new(stop_handle, conn_id.fetch_add(1, Ordering::Relaxed), conn_permit); - - // Establishes the websocket connection - // and if the `CallLimit` middleware triggers the hard limit - // then the connection is closed i.e, the `conn_fut` is dropped. - async move { - match ws::connect(req, ServerConfig::default(), methods, conn, rpc_service).await { - Ok((rp, conn_fut)) => { - tokio::spawn(async move { - tokio::select! { - _ = conn_fut => (), - _ = disconnect.recv() => { - blacklisted_peers.lock().unwrap().insert(remote_addr.ip()); - }, - } - }); - Ok(rp) + if ws::is_upgrade_request(&req) { + let (tx, mut disconnect) = mpsc::channel(1); + let rpc_service = RpcServiceBuilder::new().layer_fn(move |service| CallLimit { + service, + count: Default::default(), + state: tx.clone(), + }); + + let conn = + ConnectionState::new(stop_handle, conn_id.fetch_add(1, Ordering::Relaxed), conn_permit); + + // Establishes the websocket connection + // and if the `CallLimit` middleware triggers the hard limit + // then the connection is closed i.e, the `conn_fut` is dropped. + async move { + match ws::connect(req, ServerConfig::default(), methods, conn, rpc_service).await { + Ok((rp, conn_fut)) => { + tokio::spawn(async move { + tokio::select! { + _ = conn_fut => (), + _ = disconnect.recv() => { + blacklisted_peers.lock().unwrap().insert(remote_addr.ip()); + }, + } + }); + Ok(rp) + } + Err(rp) => Ok(rp), } - Err(rp) => Ok(rp), } - } - .boxed() - } else if !ws::is_upgrade_request(&req) { - let (tx, mut disconnect) = mpsc::channel(1); - - let rpc_service = RpcServiceBuilder::new().layer_fn(move |service| CallLimit { - service, - count: global_http_rate_limit.clone(), - state: tx.clone(), - }); - - let server_cfg = ServerConfig::default(); - let conn = ConnectionState::new(stop_handle, conn_id.fetch_add(1, Ordering::Relaxed), conn_permit); - - // There is another API for making call with just a service as well. - // - // See [`jsonrpsee::server::http::call_with_service`] - async move { - tokio::select! { - // Rpc call finished successfully. - res = http::call_with_service_builder(req, server_cfg, conn, methods, rpc_service) => Ok(res), - // Deny the call if the call limit is exceeded. - _ = disconnect.recv() => Ok(http::response::denied()), + .boxed() + } else if !ws::is_upgrade_request(&req) { + let (tx, mut disconnect) = mpsc::channel(1); + + let rpc_service = RpcServiceBuilder::new().layer_fn(move |service| CallLimit { + service, + count: global_http_rate_limit.clone(), + state: tx.clone(), + }); + + let server_cfg = ServerConfig::default(); + let conn = + ConnectionState::new(stop_handle, conn_id.fetch_add(1, Ordering::Relaxed), conn_permit); + + // There is another API for making call with just a service as well. + // + // See [`jsonrpsee::server::http::call_with_service`] + async move { + tokio::select! { + // Rpc call finished successfully. + res = http::call_with_service_builder(req, server_cfg, conn, methods, rpc_service) => Ok(res), + // Deny the call if the call limit is exceeded. + _ = disconnect.recv() => Ok(http::response::denied()), + } } + .boxed() + } else { + async { Ok(http::response::denied()) }.boxed() + } + }); + + let conn = hyper::server::conn::http1::Builder::new() + .serve_connection(TokioIo::new(sock), svc) + .with_upgrades(); + let stopped = stop_handle2.shutdown(); + + // Pin the future so that it can be polled. + tokio::pin!(stopped); + + let res = match future::select(conn, stopped).await { + // Return the connection if not stopped. + Either::Left((conn, _)) => conn, + // If the server is stopped, we should gracefully shutdown + // the connection and poll it until it finishes. + Either::Right((_, mut conn)) => { + Pin::new(&mut conn).graceful_shutdown(); + conn.await } - .boxed() - } else { - async { Ok(http::response::denied()) }.boxed() + }; + + // Log any errors that might have occurred. + if let Err(err) = res { + tracing::error!(err=?err, "HTTP connection failed"); } - })) + }); } }); - let server = hyper::Server::bind(&addr).serve(make_service); - - tokio::spawn(async move { - let graceful = server.with_graceful_shutdown(async move { stop_handle.shutdown().await }); - graceful.await.unwrap() - }); - - server_handle + Ok(server_handle) } diff --git a/examples/examples/ws.rs b/examples/examples/ws.rs index 70d1950076..9ad57f13dd 100644 --- a/examples/examples/ws.rs +++ b/examples/examples/ws.rs @@ -55,6 +55,7 @@ async fn run_server() -> anyhow::Result { let mut module = RpcModule::new(()); module.register_method("say_hello", |_, _| "lo")?; 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. diff --git a/examples/examples/ws_dual_stack.rs b/examples/examples/ws_dual_stack.rs index 9f582d82f8..d6ec373f96 100644 --- a/examples/examples/ws_dual_stack.rs +++ b/examples/examples/ws_dual_stack.rs @@ -25,6 +25,8 @@ // DEALINGS IN THE SOFTWARE. use futures::future::{self, Either}; +use hyper_util::rt::TokioIo; +use hyper_util::service::TowerToHyperService; use jsonrpsee::core::client::ClientT; use jsonrpsee::server::{stop_channel, ServerHandle}; use jsonrpsee::ws_client::WsClientBuilder; @@ -117,7 +119,9 @@ async fn run_server() -> anyhow::Result<(ServerHandle, Addrs)> { // Spawn a new task to serve each respective (Hyper) connection. tokio::spawn(async move { - let conn = hyper::server::conn::Http::new().serve_connection(stream, svc).with_upgrades(); + let conn = hyper::server::conn::http1::Builder::new() + .serve_connection(TokioIo::new(stream), TowerToHyperService::new(svc)) + .with_upgrades(); let stopped = stop_hdl2.shutdown(); diff --git a/proc-macros/Cargo.toml b/proc-macros/Cargo.toml index 12f574719a..6b6460cc43 100644 --- a/proc-macros/Cargo.toml +++ b/proc-macros/Cargo.toml @@ -24,7 +24,8 @@ proc-macro-crate = "3" heck = "0.5.0" [dev-dependencies] -jsonrpsee = { path = "../jsonrpsee", features = ["server", "client-core", "http-client", "ws-client", "macros"] } +# jsonrpsee = { path = "../jsonrpsee", features = ["server", "client-core", "http-client", "ws-client", "macros"] } +jsonrpsee = { path = "../jsonrpsee", features = ["server", "client-core", "ws-client", "macros"] } trybuild = "1.0" tokio = { version = "1.16", features = ["rt", "macros"] } futures-channel = { version = "0.3.14", default-features = false } diff --git a/server/Cargo.toml b/server/Cargo.toml index 5820c80584..119b85d187 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -20,15 +20,18 @@ jsonrpsee-core = { workspace = true, features = ["server", "http-helpers"] } tracing = "0.1.34" serde = "1" serde_json = { version = "1", features = ["raw_value"] } -soketto = { version = "0.7.1", features = ["http"] } +soketto = { version = "0.8", features = ["http"] } tokio = { version = "1.16", features = ["net", "rt-multi-thread", "macros", "time"] } tokio-util = { version = "0.7", features = ["compat"] } tokio-stream = { version = "0.1.7", features = ["sync"] } -hyper = { version = "0.14", features = ["server", "http1", "http2"] } +hyper = { version = "1.3", features = ["server", "http1", "http2"] } +hyper-util = { version = "0.1", features = ["tokio", "service"] } +http = "1" +http-body = "1" +http-body-util = "0.1.0" tower = "0.4.13" thiserror = "1" route-recognizer = "0.3.1" -http = "0.2.9" pin-project = "1.1.3" [dev-dependencies] diff --git a/server/src/lib.rs b/server/src/lib.rs index 8301f6d690..f0f4be0be6 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -56,3 +56,6 @@ pub use transport::http; pub use transport::ws; pub(crate) const LOG_TARGET: &str = "jsonrpsee-server"; + +pub(crate) type HttpRequest = hyper::Request; +pub(crate) type ResponseBody = http_body_util::Full; diff --git a/server/src/middleware/http/authority.rs b/server/src/middleware/http/authority.rs index f10c3260da..d8fdbb53f9 100644 --- a/server/src/middleware/http/authority.rs +++ b/server/src/middleware/http/authority.rs @@ -27,7 +27,7 @@ //! Utility and types related to the authority of an URI. use http::uri::{InvalidUri, Uri}; -use hyper::{Body, Request}; +use hyper::Request; use jsonrpsee_core::http_helpers; /// Represent the http URI scheme that is returned by the HTTP host header @@ -96,7 +96,7 @@ impl Authority { /// /// The `Authority` can be sent by the client in the `Host header` or in the `URI` /// such that both must be checked. - pub fn from_http_request(request: &Request) -> Option { + pub fn from_http_request(request: &Request) -> Option { // NOTE: we use our own `Authority type` here because an invalid port number would return `None` here // and that should be denied. let host_header = @@ -161,22 +161,30 @@ fn default_port(scheme: Option<&str>) -> Option { mod tests { use super::{Authority, Port}; use hyper::header::HOST; - use hyper::Body; fn authority(host: &str, port: Port) -> Authority { Authority { host: host.to_owned(), port } } + type EmptyBody = http_body_util::Empty; + #[test] fn should_parse_valid_authority() { assert_eq!(Authority::try_from("http://parity.io").unwrap(), authority("parity.io", Port::Default)); assert_eq!(Authority::try_from("https://parity.io:8443").unwrap(), authority("parity.io", Port::Fixed(8443))); assert_eq!(Authority::try_from("chrome-extension://124.0.0.1").unwrap(), authority("124.0.0.1", Port::Default)); - assert_eq!(Authority::try_from("http://*.domain:*/somepath").unwrap(), authority("*.domain", Port::Any)); + assert_eq!( + Authority::try_from( + "http://*.domain:*/ +somepath" + ) + .unwrap(), + authority("*.domain", Port::Any) + ); assert_eq!(Authority::try_from("parity.io").unwrap(), authority("parity.io", Port::Default)); assert_eq!(Authority::try_from("127.0.0.1:8845").unwrap(), authority("127.0.0.1", Port::Fixed(8845))); assert_eq!( - Authority::try_from("http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:9933/").unwrap(), + Authority::try_from("http: //[2001:db8:85a3:8d3:1319:8a2e:370:7348]:9933/").unwrap(), authority("[2001:db8:85a3:8d3:1319:8a2e:370:7348]", Port::Fixed(9933)) ); assert_eq!( @@ -200,13 +208,13 @@ mod tests { #[test] fn authority_from_http_only_host_works() { - let req = hyper::Request::builder().header(HOST, "example.com").body(Body::empty()).unwrap(); + let req = hyper::Request::builder().header(HOST, "example.com").body(EmptyBody::new()).unwrap(); assert!(Authority::from_http_request(&req).is_some()); } #[test] fn authority_only_uri_works() { - let req = hyper::Request::builder().uri("example.com").body(Body::empty()).unwrap(); + let req = hyper::Request::builder().uri("example.com").body(EmptyBody::new()).unwrap(); assert!(Authority::from_http_request(&req).is_some()); } @@ -215,21 +223,24 @@ mod tests { let req = hyper::Request::builder() .header(HOST, "example.com:9999") .uri("example.com:9999") - .body(Body::empty()) + .body(EmptyBody::new()) .unwrap(); assert!(Authority::from_http_request(&req).is_some()); } #[test] fn authority_host_and_uri_mismatch() { - let req = - hyper::Request::builder().header(HOST, "example.com:9999").uri("example.com").body(Body::empty()).unwrap(); + let req = hyper::Request::builder() + .header(HOST, "example.com:9999") + .uri("example.com") + .body(EmptyBody::new()) + .unwrap(); assert!(Authority::from_http_request(&req).is_none()); } #[test] fn authority_missing_host_and_uri() { - let req = hyper::Request::builder().body(Body::empty()).unwrap(); + let req = hyper::Request::builder().body(EmptyBody::new()).unwrap(); assert!(Authority::from_http_request(&req).is_none()); } } diff --git a/server/src/middleware/http/host_filter.rs b/server/src/middleware/http/host_filter.rs index f70ee33d65..c54024b401 100644 --- a/server/src/middleware/http/host_filter.rs +++ b/server/src/middleware/http/host_filter.rs @@ -28,9 +28,9 @@ use crate::middleware::http::authority::{Authority, AuthorityError, Port}; use crate::transport::http; -use crate::LOG_TARGET; +use crate::{HttpRequest, ResponseBody, LOG_TARGET}; use futures_util::{Future, FutureExt, TryFutureExt}; -use hyper::{Body, Request, Response}; +use hyper::Response; use route_recognizer::Router; use std::collections::BTreeMap; use std::error::Error as StdError; @@ -92,15 +92,15 @@ impl Layer for HostFilterLayer { } /// Middleware to enable host filtering. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct HostFilter { inner: S, filter: Option>, } -impl Service> for HostFilter +impl Service for HostFilter where - S: Service, Response = Response>, + S: Service>, S::Response: 'static, S::Error: Into> + 'static, S::Future: Send + 'static, @@ -113,7 +113,7 @@ where self.inner.poll_ready(cx).map_err(Into::into) } - fn call(&mut self, request: Request) -> Self::Future { + fn call(&mut self, request: HttpRequest) -> Self::Future { let Some(authority) = Authority::from_http_request(&request) else { return async { Ok(http::response::malformed()) }.boxed(); }; diff --git a/server/src/middleware/http/proxy_get_request.rs b/server/src/middleware/http/proxy_get_request.rs index 4964d1b0b3..dc23970260 100644 --- a/server/src/middleware/http/proxy_get_request.rs +++ b/server/src/middleware/http/proxy_get_request.rs @@ -28,9 +28,12 @@ //! RPC method calls. use crate::transport::http; +use crate::ResponseBody; + +use http_body_util::BodyExt; use hyper::header::{ACCEPT, CONTENT_TYPE}; use hyper::http::HeaderValue; -use hyper::{Body, Method, Request, Response, Uri}; +use hyper::{Method, Request, Response, Uri}; use jsonrpsee_types::{Id, RequestSer}; use std::error::Error; use std::future::Future; @@ -109,9 +112,9 @@ impl ProxyGetRequest { } } -impl Service> for ProxyGetRequest +impl Service> for ProxyGetRequest where - S: Service, Response = Response>, + S: Service, Response = Response>, S::Response: 'static, S::Error: Into> + 'static, S::Future: Send + 'static, @@ -125,7 +128,7 @@ where self.inner.poll_ready(cx).map_err(Into::into) } - fn call(&mut self, mut req: Request) -> Self::Future { + fn call(&mut self, mut req: Request) -> Self::Future { let modify = self.path.as_ref() == req.uri() && req.method() == Method::GET; // Proxy the request to the appropriate method call. @@ -140,11 +143,10 @@ where req.headers_mut().insert(ACCEPT, HeaderValue::from_static("application/json")); // Adjust the body to reflect the method call. - let body = Body::from( - serde_json::to_string(&RequestSer::borrowed(&Id::Number(0), &self.method, None)) - .expect("Valid request; qed"), - ); - req = req.map(|_| body); + let body = serde_json::to_vec(&RequestSer::borrowed(&Id::Number(0), &self.method, None)) + .expect("Valid request; qed"); + + req.body_mut().map_frame(|f| f.map_data(|_| body.as_slice())); } // Call the inner service and get a future that resolves to the response. @@ -159,8 +161,14 @@ where return Ok(res); } - let body = res.into_body(); - let bytes = hyper::body::to_bytes(body).await?; + let mut body = http_body_util::BodyStream::new(res.into_body()); + let mut bytes = Vec::new(); + + while let Some(frame) = body.frame().await { + // TODO error handling + let data = frame.unwrap().into_data().unwrap(); + bytes.extend(data); + } #[derive(serde::Deserialize, Debug)] struct RpcPayload<'a> { diff --git a/server/src/server.rs b/server/src/server.rs index 922269b0f1..78028eb4cf 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -37,13 +37,13 @@ use crate::future::{session_close, ConnectionGuard, ServerHandle, SessionClose, use crate::middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}; use crate::transport::ws::BackgroundTaskParams; use crate::transport::{http, ws}; -use crate::LOG_TARGET; +use crate::{HttpRequest, ResponseBody, LOG_TARGET}; use futures_util::future::{self, Either, FutureExt}; use futures_util::io::{BufReader, BufWriter}; -use hyper::body::HttpBody; - +use hyper_util::rt::TokioIo; +use hyper_util::service::TowerToHyperService; use jsonrpsee_core::id_providers::RandomIntegerIdProvider; use jsonrpsee_core::server::helpers::prepare_error; use jsonrpsee_core::server::{BatchResponseBuilder, BoundedSubscriptions, MethodResponse, MethodSink, Methods}; @@ -101,16 +101,12 @@ where for<'a> >::Service: RpcServiceT<'a>, HttpMiddleware: Layer> + Send + 'static, >>::Service: Send - + Service< - hyper::Request, - Response = hyper::Response, - Error = Box<(dyn StdError + Send + Sync + 'static)>, - >, - <>>::Service as Service>>::Future: - Send, - B: HttpBody + Send + 'static, - ::Error: Send + Sync + StdError, - ::Data: Send, + + Clone + + Service, Error = Box<(dyn StdError + Send + Sync + 'static)>>, + <>>::Service as Service>::Future: Send, + B: http_body::Body + Send + 'static, + ::Error: Send + Sync + StdError, + ::Data: Send, { /// Start responding to connections requests. /// @@ -142,7 +138,6 @@ where loop { match try_accept_conn(&listener, stopped).await { AcceptConnection::Established { socket, remote_addr, stop } => { - process_connection(ProcessConnection { http_middleware: &self.http_middleware, rpc_middleware: self.rpc_middleware.clone(), @@ -960,8 +955,7 @@ impl TowerService } } -impl hyper::service::Service> - for TowerService +impl Service for TowerService where RpcMiddleware: for<'a> tower::Layer + Clone, >::Service: Send + Sync + 'static, @@ -969,23 +963,22 @@ where HttpMiddleware: Layer> + Send + 'static, >>::Service: Send + Service< - hyper::Request, - Response = hyper::Response, + HttpRequest, + Response = hyper::Response, Error = Box<(dyn StdError + Send + Sync + 'static)>, >, - <>>::Service as Service>>::Future: + <>>::Service as Service>::Future: Send + 'static, { - type Response = hyper::Response; + type Response = hyper::Response; type Error = Box; type Future = Pin> + Send>>; - /// Opens door for back pressure implementation. - fn poll_ready(&mut self, _: &mut std::task::Context) -> Poll> { + fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn call(&mut self, request: hyper::Request) -> Self::Future { + fn call(&mut self, request: HttpRequest) -> Self::Future { Box::pin(self.http_middleware.service(self.rpc_middleware.clone()).call(request)) } } @@ -1001,13 +994,13 @@ pub struct TowerServiceNoHttp { on_session_close: Option, } -impl hyper::service::Service> for TowerServiceNoHttp +impl Service for TowerServiceNoHttp where RpcMiddleware: for<'a> tower::Layer, >::Service: Send + Sync + 'static, for<'a> >::Service: RpcServiceT<'a>, { - type Response = hyper::Response; + type Response = hyper::Response; // The following associated type is required by the `impl Server` bounds. // It satisfies the server's bounds when the `tower::ServiceBuilder` is not set (ie `B: Identity`). @@ -1015,12 +1008,11 @@ where type Future = Pin> + Send>>; - /// Opens door for back pressure implementation. - fn poll_ready(&mut self, _: &mut std::task::Context) -> Poll> { + fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll> { Poll::Ready(Ok(())) } - fn call(&mut self, request: hyper::Request) -> Self::Future { + fn call(&mut self, request: HttpRequest) -> Self::Future { let conn_guard = &self.inner.conn_guard; let stop_handle = self.inner.stop_handle.clone(); let conn_id = self.inner.conn_id; @@ -1083,7 +1075,9 @@ where } }; - let stream = BufReader::new(BufWriter::new(upgraded.compat())); + let io = hyper_util::rt::TokioIo::new(upgraded); + + let stream = BufReader::new(BufWriter::new(io.compat())); let mut ws_builder = server.into_builder(stream); ws_builder.set_max_message_size(this.server_cfg.max_request_body_size as usize); let (sender, receiver) = ws_builder.finish(); @@ -1105,11 +1099,11 @@ where .in_current_span(), ); - response.map(|()| hyper::Body::empty()) + response.map(|()| ResponseBody::default()) } Err(e) => { tracing::debug!(target: LOG_TARGET, "Could not upgrade connection: {}", e); - hyper::Response::new(hyper::Body::from(format!("Could not upgrade connection: {e}"))) + hyper::Response::new(ResponseBody::from(format!("Could not upgrade connection: {e}"))) } }; @@ -1152,24 +1146,19 @@ struct ProcessConnection<'a, HttpMiddleware, RpcMiddleware> { } #[instrument(name = "connection", skip_all, fields(remote_addr = %params.remote_addr, conn_id = %params.conn_id), level = "INFO")] -fn process_connection<'a, RpcMiddleware, HttpMiddleware, U>( - params: ProcessConnection - -) where +fn process_connection<'a, RpcMiddleware, HttpMiddleware, B>(params: ProcessConnection) +where RpcMiddleware: 'static, HttpMiddleware: Layer> + Send + 'static, >>::Service: Send + 'static - + Service< - hyper::Request, - Response = hyper::Response, - Error = Box<(dyn StdError + Send + Sync + 'static)>, - >, - <>>::Service as Service>>::Future: + + Clone + + Service, Error = Box<(dyn StdError + Send + Sync + 'static)>>, + <>>::Service as Service>::Future: Send + 'static, - U: HttpBody + Send + 'static, - ::Error: Send + Sync + StdError, - ::Data: Send, + B: http_body::Body + Send + 'static, + ::Error: Send + Sync + StdError, + ::Data: Send, { let ProcessConnection { http_middleware, @@ -1212,14 +1201,19 @@ fn process_connection<'a, RpcMiddleware, HttpMiddleware, U>( // Attempts to create a HTTP connection from a socket. async fn to_http_service(socket: TcpStream, service: S, stop_handle: StopHandle) where - S: Service, Response = hyper::Response> + Send + 'static, + S: Service> + Send + 'static + Clone, S::Error: Into>, S::Future: Send, - B: HttpBody + Send + 'static, - ::Error: Send + Sync + StdError, - ::Data: Send, + B: http_body::Body + Send + 'static, + ::Error: Send + Sync + StdError, + ::Data: Send, { - let conn = hyper::server::conn::Http::new().serve_connection(socket, service).with_upgrades(); + // this requires Clone. + let service = TowerToHyperService::new(service); + let io = TokioIo::new(socket); + + let conn = hyper::server::conn::http1::Builder::new().serve_connection(io, service).with_upgrades(); + let stopped = stop_handle.shutdown(); tokio::pin!(stopped); diff --git a/server/src/tests/helpers.rs b/server/src/tests/helpers.rs index 968f22d81b..2a3bf00a49 100644 --- a/server/src/tests/helpers.rs +++ b/server/src/tests/helpers.rs @@ -1,17 +1,20 @@ -use std::error::Error as StdError; +use std::convert::Infallible; use std::net::SocketAddr; +use std::pin::Pin; use std::sync::atomic::Ordering; use std::sync::Arc; use std::{fmt, sync::atomic::AtomicUsize}; use crate::{stop_channel, RpcModule, Server, ServerBuilder, ServerHandle}; -use futures_util::FutureExt; -use hyper::server::conn::AddrStream; +use futures_util::future::Either; +use futures_util::{future, FutureExt}; +use hyper_util::rt::TokioIo; use jsonrpsee_core::server::Methods; use jsonrpsee_core::{DeserializeOwned, RpcResult, StringError}; use jsonrpsee_test_utils::TimeoutFutureExt; use jsonrpsee_types::{error::ErrorCode, ErrorObject, ErrorObjectOwned, Response, ResponseSuccess}; +use tokio::net::TcpListener; use tower::Service; use tracing_subscriber::{EnvFilter, FmtSubscriber}; @@ -208,56 +211,98 @@ pub(crate) struct Metrics { pub(crate) ws_sessions_closed: Arc, } -pub(crate) fn ws_server_with_stats(metrics: Metrics) -> SocketAddr { - use hyper::service::{make_service_fn, service_fn}; +pub(crate) async fn ws_server_with_stats(metrics: Metrics) -> SocketAddr { + use hyper::service::service_fn; - let addr = SocketAddr::from(([127, 0, 0, 1], 0)); + let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))).await.unwrap(); + let addr = listener.local_addr().unwrap(); let (stop_handle, server_handle) = stop_channel(); - let stop_handle2 = stop_handle.clone(); - - // And a MakeService to handle each connection... - let make_service = make_service_fn(move |_conn: &AddrStream| { - let stop_handle = stop_handle2.clone(); - let metrics = metrics.clone(); - - async move { - Ok::<_, Box>(service_fn(move |req| { - let is_websocket = crate::ws::is_upgrade_request(&req); - let metrics = metrics.clone(); - let stop_handle = stop_handle.clone(); - - let mut svc = - Server::builder().max_connections(33).to_service_builder().build(Methods::new(), stop_handle); - - if is_websocket { - // This should work for each callback. - let session_close1 = svc.on_session_closed(); - let session_close2 = svc.on_session_closed(); - - tokio::spawn(async move { - metrics.ws_sessions_opened.fetch_add(1, Ordering::SeqCst); - tokio::join!(session_close2, session_close1); - metrics.ws_sessions_closed.fetch_add(1, Ordering::SeqCst); - }); - - async move { svc.call(req).await }.boxed() - } else { - // HTTP. - async move { svc.call(req).await }.boxed() - } - })) - } - }); - - let server = hyper::Server::bind(&addr).serve(make_service); + let metrics = metrics.clone(); - let addr = server.local_addr(); + let rpc_svc = Server::builder().max_connections(33).to_service_builder().build(Methods::new(), stop_handle.clone()); tokio::spawn(async move { - let graceful = server.with_graceful_shutdown(async move { stop_handle.shutdown().await }); - graceful.await.unwrap(); - drop(server_handle) + loop { + let sock = tokio::select! { + res = listener.accept() => { + match res { + Ok((stream, _remote_addr)) => stream, + Err(e) => { + tracing::error!("failed to accept v4 connection: {:?}", e); + continue; + } + } + } + _ = stop_handle.clone().shutdown() => break, + }; + + let rpc_svc = rpc_svc.clone(); + let metrics = metrics.clone(); + let stop_handle = stop_handle.clone(); + + tokio::spawn(async move { + let rpc_svc = rpc_svc.clone(); + + let svc = service_fn(move |req| { + let is_websocket = crate::ws::is_upgrade_request(&req); + let metrics = metrics.clone(); + let mut rpc_svc = rpc_svc.clone(); + + if is_websocket { + // This should work for each callback. + let session_close1 = rpc_svc.on_session_closed(); + let session_close2 = rpc_svc.on_session_closed(); + + tokio::spawn(async move { + metrics.ws_sessions_opened.fetch_add(1, Ordering::SeqCst); + tokio::join!(session_close2, session_close1); + metrics.ws_sessions_closed.fetch_add(1, Ordering::SeqCst); + }); + + async move { + let rp = rpc_svc.call(req).await.unwrap(); + // TODO: fix the weird lifetime error here. + Ok::<_, Infallible>(rp) + } + .boxed() + } else { + // HTTP. + async move { + let rp = rpc_svc.call(req).await.unwrap(); + Ok::<_, Infallible>(rp) + } + .boxed() + } + }); + + let conn = hyper::server::conn::http1::Builder::new() + .serve_connection(TokioIo::new(sock), svc) + .with_upgrades(); + let stopped = stop_handle.shutdown(); + + // Pin the future so that it can be polled. + tokio::pin!(stopped); + + let res = match future::select(conn, stopped).await { + // Return the connection if not stopped. + Either::Left((conn, _)) => conn, + // If the server is stopped, we should gracefully shutdown + // the connection and poll it until it finishes. + Either::Right((_, mut conn)) => { + Pin::new(&mut conn).graceful_shutdown(); + conn.await + } + }; + + // Log any errors that might have occurred. + if let Err(err) = res { + tracing::error!(err=?err, "HTTP connection failed"); + } + }); + } }); + tokio::spawn(server_handle.stopped()); + addr } diff --git a/server/src/tests/ws.rs b/server/src/tests/ws.rs index f7c1381d63..f7d80d168e 100644 --- a/server/src/tests/ws.rs +++ b/server/src/tests/ws.rs @@ -880,7 +880,7 @@ async fn server_notify_on_conn_close() { init_logger(); let metrics = Metrics::default(); - let addr = ws_server_with_stats(metrics.clone()); + let addr = ws_server_with_stats(metrics.clone()).await; let mut client = WebSocketTestClient::new(addr).with_default_timeout().await.unwrap().unwrap(); diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index 57599db4cb..ffb7fbaffe 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -1,4 +1,5 @@ use http::Method; +use hyper::body::Body; use jsonrpsee_core::{ http_helpers::{read_body, HttpError}, server::Methods, @@ -7,11 +8,11 @@ use jsonrpsee_core::{ use crate::{ middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}, server::{handle_rpc_call, ServerConfig}, - BatchRequestConfig, ConnectionState, LOG_TARGET, + BatchRequestConfig, ConnectionState, ResponseBody, LOG_TARGET, }; /// Checks that content type of received request is valid for JSON-RPC. -pub fn content_type_is_json(request: &hyper::Request) -> bool { +pub fn content_type_is_json(request: &hyper::Request) -> bool { is_json(request.headers().get(hyper::header::CONTENT_TYPE)) } @@ -31,12 +32,12 @@ pub fn is_json(content_type: Option<&hyper::header::HeaderValue>) -> bool { /// /// Fails if the HTTP request was a malformed JSON-RPC request. pub async fn call_with_service_builder( - request: hyper::Request, + request: hyper::Request, server_cfg: ServerConfig, conn: ConnectionState, methods: impl Into, rpc_service: RpcServiceBuilder, -) -> hyper::Response +) -> hyper::Response where L: for<'a> tower::Layer, >::Service: Send + Sync + 'static, @@ -64,12 +65,12 @@ where /// /// Fails if the HTTP request was a malformed JSON-RPC request. pub async fn call_with_service( - request: hyper::Request, + request: hyper::Request, batch_config: BatchRequestConfig, max_request_size: u32, rpc_service: S, max_response_size: u32, -) -> hyper::Response +) -> hyper::Response where for<'a> S: RpcServiceT<'a> + Send, { @@ -105,11 +106,13 @@ pub mod response { use jsonrpsee_types::error::{reject_too_big_request, ErrorCode}; use jsonrpsee_types::{ErrorObjectOwned, Id, Response, ResponsePayload}; + use crate::ResponseBody; + const JSON: &str = "application/json; charset=utf-8"; const TEXT: &str = "text/plain"; /// Create a response for json internal error. - pub fn internal_error() -> hyper::Response { + pub fn internal_error() -> hyper::Response { let err = ResponsePayload::<()>::error(ErrorObjectOwned::from(ErrorCode::InternalError)); let rp = Response::new(err, Id::Null); let error = serde_json::to_string(&rp).expect("built from known-good data; qed"); @@ -118,21 +121,21 @@ pub mod response { } /// Create a text/plain response for not allowed hosts. - pub fn host_not_allowed() -> hyper::Response { - from_template(hyper::StatusCode::FORBIDDEN, "Provided Host header is not whitelisted.\n".to_owned(), TEXT) + pub fn host_not_allowed() -> hyper::Response { + from_template(hyper::StatusCode::FORBIDDEN, "Provided Host header is not whitelisted.\n", TEXT) } /// Create a text/plain response for disallowed method used. - pub fn method_not_allowed() -> hyper::Response { + pub fn method_not_allowed() -> hyper::Response { from_template( hyper::StatusCode::METHOD_NOT_ALLOWED, - "Used HTTP Method is not allowed. POST or OPTIONS is required\n".to_owned(), + "Used HTTP Method is not allowed. POST or OPTIONS is required\n", TEXT, ) } /// Create a json response for oversized requests (413) - pub fn too_large(limit: u32) -> hyper::Response { + pub fn too_large(limit: u32) -> hyper::Response { let err = ResponsePayload::<()>::error(reject_too_big_request(limit)); let rp = Response::new(err, Id::Null); let error = serde_json::to_string(&rp).expect("JSON serialization infallible; qed"); @@ -141,7 +144,7 @@ pub mod response { } /// Create a json response for empty or malformed requests (400) - pub fn malformed() -> hyper::Response { + pub fn malformed() -> hyper::Response { let rp = Response::new(ResponsePayload::<()>::error(ErrorCode::ParseError), Id::Null); let error = serde_json::to_string(&rp).expect("JSON serialization infallible; qed"); @@ -149,11 +152,11 @@ pub mod response { } /// Create a response body. - fn from_template>( + fn from_template( status: hyper::StatusCode, - body: S, + body: impl Into, content_type: &'static str, - ) -> hyper::Response { + ) -> hyper::Response { hyper::Response::builder() .status(status) .header("content-type", hyper::header::HeaderValue::from_static(content_type)) @@ -164,30 +167,26 @@ pub mod response { } /// Create a valid JSON response. - pub fn ok_response(body: String) -> hyper::Response { + pub fn ok_response(body: impl Into) -> hyper::Response { from_template(hyper::StatusCode::OK, body, JSON) } /// Create a response for unsupported content type. - pub fn unsupported_content_type() -> hyper::Response { + pub fn unsupported_content_type() -> hyper::Response { from_template( hyper::StatusCode::UNSUPPORTED_MEDIA_TYPE, - "Supplied content type is not allowed. Content-Type: application/json is required\n".to_owned(), + "Supplied content type is not allowed. Content-Type: application/json is required\n", TEXT, ) } /// Create a response for when the server is busy and can't accept more requests. - pub fn too_many_requests() -> hyper::Response { - from_template( - hyper::StatusCode::TOO_MANY_REQUESTS, - "Too many connections. Please try again later.".to_owned(), - TEXT, - ) + pub fn too_many_requests() -> hyper::Response { + from_template(hyper::StatusCode::TOO_MANY_REQUESTS, "Too many connections. Please try again later.", TEXT) } /// Create a response for when the server denied the request. - pub fn denied() -> hyper::Response { - from_template(hyper::StatusCode::FORBIDDEN, "".to_owned(), TEXT) + pub fn denied() -> hyper::Response { + from_template(hyper::StatusCode::FORBIDDEN, ResponseBody::default(), TEXT) } } diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index cdac32fe3a..04b644a3af 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -4,12 +4,13 @@ use std::time::Instant; use crate::future::{IntervalStream, SessionClose}; use crate::middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}; use crate::server::{handle_rpc_call, ConnectionState, ServerConfig}; -use crate::{PingConfig, LOG_TARGET}; +use crate::{PingConfig, ResponseBody, LOG_TARGET}; use futures_util::future::{self, Either}; use futures_util::io::{BufReader, BufWriter}; use futures_util::{Future, StreamExt, TryStreamExt}; use hyper::upgrade::Upgraded; +use hyper_util::rt::TokioIo; use jsonrpsee_core::server::{BoundedSubscriptions, MethodSink, Methods}; use jsonrpsee_types::error::{reject_too_big_request, ErrorCode}; use jsonrpsee_types::Id; @@ -21,8 +22,8 @@ use tokio::time::{interval, interval_at}; use tokio_stream::wrappers::ReceiverStream; use tokio_util::compat::{Compat, TokioAsyncReadCompatExt}; -pub(crate) type Sender = soketto::Sender>>>; -pub(crate) type Receiver = soketto::Receiver>>>; +pub(crate) type Sender = soketto::Sender>>>>; +pub(crate) type Receiver = soketto::Receiver>>>>; pub use soketto::handshake::http::is_upgrade_request; @@ -400,12 +401,12 @@ async fn graceful_shutdown( /// } /// ``` pub async fn connect( - req: hyper::Request, + req: hyper::Request, server_cfg: ServerConfig, methods: impl Into, conn: ConnectionState, rpc_middleware: RpcServiceBuilder, -) -> Result<(hyper::Response, impl Future), hyper::Response> +) -> Result<(hyper::Response, impl Future), hyper::Response> where L: for<'a> tower::Layer, >::Service: Send + Sync + 'static, @@ -415,6 +416,15 @@ where match server.receive_request(&req) { Ok(response) => { + let upgraded = match hyper::upgrade::on(req).await { + Ok(u) => u, + Err(e) => { + tracing::debug!(target: LOG_TARGET, "WS upgrade handshake failed: {}", e); + return Err(hyper::Response::new(ResponseBody::from(format!("WS upgrade handshake failed {e}")))); + } + }; + + let io = TokioIo::new(upgraded); let (tx, rx) = mpsc::channel::(server_cfg.message_buffer_capacity as usize); let sink = MethodSink::new(tx); @@ -440,15 +450,7 @@ where let rpc_service = rpc_middleware.service(rpc_service); let fut = async move { - let upgraded = match hyper::upgrade::on(req).await { - Ok(u) => u, - Err(e) => { - tracing::debug!(target: LOG_TARGET, "WS upgrade handshake failed: {}", e); - return; - } - }; - - let stream = BufReader::new(BufWriter::new(upgraded.compat())); + let stream = BufReader::new(BufWriter::new(io.compat())); let mut ws_builder = server.into_builder(stream); ws_builder.set_max_message_size(server_cfg.max_response_body_size as usize); let (sender, receiver) = ws_builder.finish(); @@ -468,11 +470,11 @@ where background_task(params).await; }; - Ok((response.map(|()| hyper::Body::empty()), fut)) + Ok((response.map(|()| ResponseBody::default()), fut)) // empty body here } Err(e) => { tracing::debug!(target: LOG_TARGET, "WS upgrade handshake failed: {}", e); - Err(hyper::Response::new(hyper::Body::from(format!("WS upgrade handshake failed: {e}")))) + Err(hyper::Response::new(ResponseBody::from(format!("WS upgrade handshake failed: {e}")))) } } } diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index d998976b21..3adf41b36f 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -9,10 +9,12 @@ publish = false [dependencies] futures-channel = "0.3.14" futures-util = "0.3.14" -hyper = { version = "0.14.10", features = ["full"] } +hyper = { version = "1.3.0", features = ["full"] } +hyper-util = { version = "0.1", features = ["full"] } +http-body-util = "0.1.0" tracing = "0.1.34" serde = { version = "1", default-features = false, features = ["derive"] } serde_json = "1" -soketto = { version = "0.7.1", features = ["http"] } +soketto = { version = "0.8.0", features = ["http"] } tokio = { version = "1.16", features = ["net", "rt-multi-thread", "macros", "time"] } tokio-util = { version = "0.7", features = ["compat"] } diff --git a/test-utils/src/helpers.rs b/test-utils/src/helpers.rs index 5ec7bc05f5..d7ff17fb61 100644 --- a/test-utils/src/helpers.rs +++ b/test-utils/src/helpers.rs @@ -27,12 +27,18 @@ use std::convert::Infallible; use std::net::SocketAddr; -use crate::mocks::{Body, HttpResponse, Id, Uri}; -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Client, Request, Response, Server}; +use crate::mocks::{HttpResponse, Id, Uri}; +use http_body_util::BodyExt; +use hyper::{service::service_fn, Request, Response}; +use hyper_util::{ + client::legacy::Client, + rt::{TokioExecutor, TokioIo}, +}; use serde::Serialize; use serde_json::Value; +pub type Body = http_body_util::Full; + pub const PARSE_ERROR: &str = "Parse error"; pub const INTERNAL_ERROR: &str = "Internal error"; pub const INVALID_PARAMS: &str = "Invalid params"; @@ -187,18 +193,18 @@ pub fn server_notification(method: &str, params: Value) -> String { } pub async fn http_request(body: Body, uri: Uri) -> Result { - let client = hyper::Client::new(); + let client = hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build_http(); http_post(client, body, uri).await } pub async fn http2_request(body: Body, uri: Uri) -> Result { - let client = hyper::Client::builder().http2_only(true).build_http(); + let client = hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build_http(); http_post(client, body, uri).await } async fn http_post(client: Client, body: Body, uri: Uri) -> Result where - C: hyper::client::connect::Connect + Clone + Send + Sync + 'static, + C: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static, { let r = hyper::Request::post(uri) .header(hyper::header::CONTENT_TYPE, hyper::header::HeaderValue::from_static("application/json")) @@ -206,8 +212,14 @@ where .expect("uri and request headers are valid; qed"); let res = client.request(r).await.map_err(|e| format!("{e:?}"))?; - let (parts, body) = res.into_parts(); - let bytes = hyper::body::to_bytes(body).await.unwrap(); + let (parts, mut body) = res.into_parts(); + let mut bytes = Vec::new(); + + while let Some(frame) = body.frame().await { + let data = frame.unwrap().into_data().unwrap(); + bytes.extend(data); + } + Ok(HttpResponse { status: parts.status, header: parts.headers, body: String::from_utf8(bytes.to_vec()).unwrap() }) } @@ -215,27 +227,40 @@ where // // NOTE: This must be spawned on tokio because hyper only works with tokio. pub async fn http_server_with_hardcoded_response(response: String) -> SocketAddr { - async fn process_request(_req: Request, response: String) -> Result, Infallible> { - Ok(Response::new(hyper::Body::from(response))) + async fn process_request( + _req: Request, + response: String, + ) -> Result, Infallible> { + Ok(Response::new(Body::from(response))) } - let make_service = make_service_fn(move |_| { - let response = response.clone(); - async move { - Ok::<_, Infallible>(service_fn(move |req| { - let response = response.clone(); - async move { Ok::<_, Infallible>(process_request(req, response).await.unwrap()) } - })) - } - }); - let (tx, rx) = futures_channel::oneshot::channel::(); - tokio::spawn(async { + tokio::spawn(async move { let addr = SocketAddr::from(([127, 0, 0, 1], 0)); - let server = Server::bind(&addr).serve(make_service); - tx.send(server.local_addr()).unwrap(); - server.await.unwrap() + let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + tx.send(listener.local_addr().unwrap()).unwrap(); + + loop { + let Ok((sock, _addr)) = listener.accept().await else { + continue; + }; + + let response = response.clone(); + tokio::spawn(async move { + let io = TokioIo::new(sock); + + let conn = hyper::server::conn::http1::Builder::new().serve_connection( + io, + service_fn(move |req| { + let rp = response.clone(); + async move { Ok::<_, Infallible>(process_request(req, rp).await.unwrap()) } + }), + ); + + conn.await.unwrap(); + }); + } }); rx.await.unwrap() diff --git a/test-utils/src/mocks.rs b/test-utils/src/mocks.rs index 7f19f5538a..c7dfc0ef84 100644 --- a/test-utils/src/mocks.rs +++ b/test-utils/src/mocks.rs @@ -24,8 +24,11 @@ // 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::io; use std::net::SocketAddr; +use std::net::TcpListener; use std::time::Duration; use futures_channel::mpsc; @@ -35,14 +38,16 @@ use futures_util::io::{BufReader, BufWriter}; use futures_util::sink::SinkExt; use futures_util::stream::{self, StreamExt}; use futures_util::{pin_mut, select}; +use hyper_util::rt::TokioIo; use serde::{Deserialize, Serialize}; use soketto::handshake::{self, http::is_upgrade_request, server::Response, Error as SokettoError, Server}; use tokio::net::TcpStream; use tokio_util::compat::{Compat, TokioAsyncReadCompatExt}; -pub use hyper::{Body, HeaderMap, StatusCode, Uri}; +pub use hyper::{HeaderMap, StatusCode, Uri}; -type Error = Box; +type Error = Box; +type EmptyBody = http_body_util::Empty; /// Request Id #[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] @@ -319,29 +324,45 @@ async fn connection_task(socket: tokio::net::TcpStream, mode: ServerMode, mut ex // Run a WebSocket server running on localhost that redirects requests for testing. // Requests to any url except for `/myblock/two` will redirect one or two times (HTTP 301) and eventually end up in `/myblock/two`. pub fn ws_server_with_redirect(other_server: String) -> String { - let addr = ([127, 0, 0, 1], 0).into(); - - let service = hyper::service::make_service_fn(move |_| { - let other_server = other_server.clone(); - async move { - Ok::<_, hyper::Error>(hyper::service::service_fn(move |req| { - let other_server = other_server.clone(); - async move { handler(req, other_server).await } - })) + let addr: SocketAddr = ([127, 0, 0, 1], 0).into(); + let listener = TcpListener::bind(addr).unwrap(); + let addr = listener.local_addr().unwrap(); + + tokio::spawn(async move { + let listener = tokio::net::TcpListener::from_std(listener).unwrap(); + + loop { + let Ok((stream, _addr)) = listener.accept().await else { + continue; + }; + + let other_server = other_server.clone(); + tokio::spawn(async { + let io = TokioIo::new(stream); + + let conn = hyper::server::conn::http1::Builder::new().serve_connection( + io, + hyper::service::service_fn(move |req| { + let other_server = other_server.clone(); + async move { + let res = handler(req, other_server).await.unwrap(); + Ok::<_, Infallible>(res) + } + }), + ); + + conn.await.unwrap(); + }); } }); - let server = hyper::Server::bind(&addr).serve(service); - let addr = server.local_addr(); - - tokio::spawn(server); format!("ws://{addr}") } /// Handle incoming HTTP Requests. async fn handler( - req: hyper::Request, + req: hyper::Request, other_server: String, -) -> Result, soketto::BoxedError> { +) -> Result, Box> { if is_upgrade_request(&req) { tracing::debug!("{:?}", req); @@ -350,20 +371,20 @@ async fn handler( let response = hyper::Response::builder() .status(301) .header("Location", other_server) - .body(Body::empty()) + .body(EmptyBody::new()) .unwrap(); Ok(response) } "/myblock/one" => { let response = - hyper::Response::builder().status(301).header("Location", "two").body(Body::empty()).unwrap(); + hyper::Response::builder().status(301).header("Location", "two").body(EmptyBody::new()).unwrap(); Ok(response) } _ => { let response = hyper::Response::builder() .status(301) .header("Location", "/myblock/one") - .body(Body::empty()) + .body(EmptyBody::new()) .unwrap(); Ok(response) } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 8a87cd1e7d..568b9bff50 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -13,7 +13,9 @@ beef = { version = "0.5.1", features = ["impl_serde"] } fast-socks5 = { version = "0.9.1" } futures = { version = "0.3.14", default-features = false, features = ["std"] } futures-util = { version = "0.3.14", default-features = false, features = ["alloc"]} -hyper = { version = "0.14", features = ["http1", "client"] } +http-body-util = "0.1" +hyper = { version = "1.3" } +hyper-util = { version = "0.1.3", features = ["http1", "client", "client-legacy"] } jsonrpsee = { path = "../jsonrpsee", features = ["server", "client-core", "http-client", "ws-client", "macros"] } jsonrpsee-test-utils = { path = "../test-utils" } serde = "1" @@ -22,7 +24,7 @@ tokio = { version = "1.16", features = ["full"] } tokio-stream = "0.1" tokio-util = { version = "0.7", features = ["compat"]} tower = { version = "0.4.13", features = ["full"] } -tower-http = { version = "0.4.0", features = ["full"] } +tower-http = { version = "0.5.2", features = ["full"] } tracing = "0.1.34" tracing-subscriber = { version = "0.3.3", features = ["env-filter"] } pin-project = "1" diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index d317f5b22e..672dfb782e 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -39,7 +39,9 @@ use helpers::{ connect_over_socks_stream, init_logger, pipe_from_stream_and_drop, server, server_with_cors, server_with_health_api, server_with_subscription, server_with_subscription_and_handle, }; +use http_body_util::BodyExt; use hyper::http::HeaderValue; +use hyper_util::rt::TokioExecutor; use jsonrpsee::core::client::SubscriptionCloseReason; use jsonrpsee::core::client::{ClientT, Error, IdKind, Subscription, SubscriptionClientT}; use jsonrpsee::core::params::{ArrayParams, BatchRequestBuilder}; @@ -58,6 +60,8 @@ use tower_http::cors::CorsLayer; use crate::helpers::server_with_sleeping_subscription; +type HttpBody = http_body_util::Full; + #[tokio::test] async fn ws_subscription_works() { init_logger(); @@ -934,12 +938,13 @@ async fn ws_server_unsub_methods_should_ignore_sub_limit() { #[tokio::test] async fn http_unsupported_methods_dont_work() { - use hyper::{Body, Client, Method, Request}; + use hyper::{Method, Request}; + use hyper_util::client::legacy::Client; init_logger(); let server_addr = server().await; - let http_client = Client::new(); + let http_client = Client::builder(TokioExecutor::new()).build_http(); let uri = format!("http://{}", server_addr); let req_is_client_error = |method| async { @@ -947,7 +952,7 @@ async fn http_unsupported_methods_dont_work() { .method(method) .uri(&uri) .header("content-type", "application/json") - .body(Body::from(r#"{ "jsonrpc": "2.0", method: "say_hello", "id": 1 }"#)) + .body(HttpBody::from(r#"{ "jsonrpc": "2.0", method: "say_hello", "id": 1 }"#)) .expect("request builder"); let res = http_client.request(req).await.unwrap(); @@ -962,19 +967,20 @@ async fn http_unsupported_methods_dont_work() { #[tokio::test] async fn http_correct_content_type_required() { - use hyper::{Body, Client, Method, Request}; + use hyper::{Method, Request}; + use hyper_util::client::legacy::Client; init_logger(); let server_addr = server().await; - let http_client = Client::new(); + let http_client = Client::builder(TokioExecutor::new()).build_http(); let uri = format!("http://{}", server_addr); // We don't set content-type at all let req = Request::builder() .method(Method::POST) .uri(&uri) - .body(Body::from(r#"{ "jsonrpc": "2.0", method: "say_hello", "id": 1 }"#)) + .body(HttpBody::from(r#"{ "jsonrpc": "2.0", method: "say_hello", "id": 1 }"#)) .expect("request builder"); let res = http_client.request(req).await.unwrap(); @@ -985,7 +991,7 @@ async fn http_correct_content_type_required() { .method(Method::POST) .uri(&uri) .header("content-type", "application/text") - .body(Body::from(r#"{ "jsonrpc": "2.0", method: "say_hello", "id": 1 }"#)) + .body(HttpBody::from(r#"{ "jsonrpc": "2.0", method: "say_hello", "id": 1 }"#)) .expect("request builder"); let res = http_client.request(req).await.unwrap(); @@ -996,7 +1002,7 @@ async fn http_correct_content_type_required() { .method(Method::POST) .uri(&uri) .header("content-type", "application/json") - .body(Body::from(r#"{ "jsonrpc": "2.0", method: "say_hello", "id": 1 }"#)) + .body(HttpBody::from(r#"{ "jsonrpc": "2.0", method: "say_hello", "id": 1 }"#)) .expect("request builder"); let res = http_client.request(req).await.unwrap(); @@ -1005,7 +1011,8 @@ async fn http_correct_content_type_required() { #[tokio::test] async fn http_cors_preflight_works() { - use hyper::{Body, Client, Method, Request}; + use hyper::{Method, Request}; + use hyper_util::client::legacy::Client; init_logger(); @@ -1015,7 +1022,7 @@ async fn http_cors_preflight_works() { .allow_headers([hyper::header::CONTENT_TYPE]); let (server_addr, _handle) = server_with_cors(cors).await; - let http_client = Client::new(); + let http_client = Client::builder(TokioExecutor::new()).build_http(); let uri = format!("http://{}", server_addr); // First, make a preflight request. @@ -1028,7 +1035,7 @@ async fn http_cors_preflight_works() { .header("origin", "https://foo.com") // <- where request is being sent _from_ .header("access-control-request-method", "POST") .header("access-control-request-headers", "content-type") - .body(Body::empty()) + .body(HttpBody::default()) .expect("preflight request builder"); let has = |v: &[String], s| v.iter().any(|v| v == s); @@ -1056,7 +1063,7 @@ async fn http_cors_preflight_works() { .header("host", "bar.com") .header("origin", "https://foo.com") .header("content-type", "application/json") - .body(Body::from(r#"{ "jsonrpc": "2.0", method: "say_hello", "id": 1 }"#)) + .body(HttpBody::from(r#"{ "jsonrpc": "2.0", method: "say_hello", "id": 1 }"#)) .expect("actual request builder"); let res = http_client.request(req).await.unwrap(); @@ -1090,21 +1097,22 @@ async fn ws_subscribe_with_bad_params() { #[tokio::test] async fn http_health_api_works() { - use hyper::{Body, Client, Request}; + use hyper::Request; + use hyper_util::client::legacy::Client; init_logger(); let (server_addr, _handle) = server_with_health_api().await; - let http_client = Client::new(); + let http_client = Client::builder(TokioExecutor::new()).build_http(); let uri = format!("http://{}/health", server_addr); - let req = Request::builder().method("GET").uri(&uri).body(Body::empty()).expect("request builder"); + let req = Request::builder().method("GET").uri(&uri).body(HttpBody::default()).expect("request builder"); let res = http_client.request(req).await.unwrap(); assert!(res.status().is_success()); - let bytes = hyper::body::to_bytes(res.into_body()).await.unwrap(); + let bytes = res.into_body().collect().await.unwrap().to_bytes(); let out = String::from_utf8(bytes.to_vec()).unwrap(); assert_eq!(out.as_str(), "{\"health\":true}"); } From 38bcfd431eb84a6b0cc9789db7bbb7e6f9e74853 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 14 May 2024 20:07:57 +0200 Subject: [PATCH 02/22] some progress --- examples/examples/http_middleware.rs | 6 +- examples/examples/http_proxy_middleware.rs | 3 +- examples/examples/jsonrpsee_as_service.rs | 2 +- .../jsonrpsee_server_low_level_api.rs | 3 + examples/examples/proc_macro.rs | 6 +- server/Cargo.toml | 3 +- server/src/lib.rs | 2 +- .../src/middleware/http/proxy_get_request.rs | 16 +-- server/src/middleware/mod.rs | 106 ++++++++++++++++++ server/src/server.rs | 72 ++++++------ server/src/transport/http.rs | 16 +-- server/src/transport/ws.rs | 4 +- 12 files changed, 170 insertions(+), 69 deletions(-) diff --git a/examples/examples/http_middleware.rs b/examples/examples/http_middleware.rs index 8e1c02749e..31156a2f9f 100644 --- a/examples/examples/http_middleware.rs +++ b/examples/examples/http_middleware.rs @@ -95,9 +95,9 @@ async fn run_server() -> anyhow::Result { // Add high level tracing/logging to all requests .layer( TraceLayer::new_for_http() - .on_request( - |request: &hyper::Request, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), - ) + /*.on_request( + |request, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), + )*/ .on_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| { tracing::info!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk") }) diff --git a/examples/examples/http_proxy_middleware.rs b/examples/examples/http_proxy_middleware.rs index 04f83cfc02..c923c7d60c 100644 --- a/examples/examples/http_proxy_middleware.rs +++ b/examples/examples/http_proxy_middleware.rs @@ -37,7 +37,6 @@ //! This functionality is useful for services which would //! like to query a certain `URI` path for statistics. -use hyper::Request; use hyper_util::client::legacy::Client; use hyper_util::rt::TokioExecutor; use std::net::SocketAddr; @@ -70,7 +69,7 @@ async fn main() -> anyhow::Result<()> { let http_client = Client::builder(TokioExecutor::new()).build_http(); let uri = format!("http://{}/health", addr); - let req = Request::builder().method("GET").uri(&uri).body(EmptyBody::new())?; + let req = hyper::Request::builder().method("GET").uri(&uri).body(EmptyBody::new())?; println!("[main]: Submit proxy request: {:?}", req); let res = http_client.request(req).await?; println!("[main]: Received proxy response: {:?}", res); diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index 0c2bbe5157..7b417b88ad 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -207,7 +207,7 @@ async fn run_server(metrics: Metrics) -> anyhow::Result { let stop_handle2 = per_conn.stop_handle.clone(); let per_conn = per_conn.clone(); - let svc = service_fn(move |req| { + let svc = service_fn(move |req: hyper::Request| { let is_websocket = jsonrpsee::server::ws::is_upgrade_request(&req); let transport_label = if is_websocket { "ws" } else { "http" }; let PerConnection { methods, stop_handle, metrics, svc_builder } = per_conn.clone(); diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index e712609ba2..27be1e1a2e 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -52,6 +52,7 @@ use jsonrpsee::core::async_trait; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::middleware::rpc::RpcServiceT; +use jsonrpsee::server::middleware::Body; use jsonrpsee::server::{ http, stop_channel, ws, ConnectionGuard, ConnectionState, RpcServiceBuilder, ServerConfig, ServerHandle, StopHandle, }; @@ -227,6 +228,8 @@ async fn run_server() -> anyhow::Result { let per_conn = per_conn.clone(); let svc = service_fn(move |req| { + let req = req.map(Body::new); + let PerConnection { methods, stop_handle, diff --git a/examples/examples/proc_macro.rs b/examples/examples/proc_macro.rs index d0b8b7228c..6e7ecdb214 100644 --- a/examples/examples/proc_macro.rs +++ b/examples/examples/proc_macro.rs @@ -42,11 +42,7 @@ where { /// Async method call example. #[method(name = "getKeys")] - async fn storage_keys( - &self, - storage_key: StorageKey, - hash: Option, - ) -> Result, ErrorObjectOwned>; + async fn storage_keys(&self, storage_key: usize, r#type: usize) -> Result, ErrorObjectOwned>; /// Subscription that takes a `StorageKey` as input and produces a `Vec`. #[subscription(name = "subscribeStorage" => "override", item = Vec)] diff --git a/server/Cargo.toml b/server/Cargo.toml index 119b85d187..26e61bc06b 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -14,6 +14,7 @@ readme.workspace = true publish = true [dependencies] +anyhow = "1" futures-util = { version = "0.3.14", default-features = false, features = ["io", "async-await-macro"] } jsonrpsee-types = { workspace = true } jsonrpsee-core = { workspace = true, features = ["server", "http-helpers"] } @@ -25,7 +26,7 @@ tokio = { version = "1.16", features = ["net", "rt-multi-thread", "macros", "tim tokio-util = { version = "0.7", features = ["compat"] } tokio-stream = { version = "0.1.7", features = ["sync"] } hyper = { version = "1.3", features = ["server", "http1", "http2"] } -hyper-util = { version = "0.1", features = ["tokio", "service"] } +hyper-util = { version = "0.1", features = ["tokio", "service", "tokio"] } http = "1" http-body = "1" http-body-util = "0.1.0" diff --git a/server/src/lib.rs b/server/src/lib.rs index f0f4be0be6..0cc0519b2d 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -57,5 +57,5 @@ pub use transport::ws; pub(crate) const LOG_TARGET: &str = "jsonrpsee-server"; -pub(crate) type HttpRequest = hyper::Request; +pub use crate::middleware::{Body, Request as HttpRequest}; pub(crate) type ResponseBody = http_body_util::Full; diff --git a/server/src/middleware/http/proxy_get_request.rs b/server/src/middleware/http/proxy_get_request.rs index dc23970260..38f6669729 100644 --- a/server/src/middleware/http/proxy_get_request.rs +++ b/server/src/middleware/http/proxy_get_request.rs @@ -27,6 +27,7 @@ //! Middleware that proxies requests at a specified URI to internal //! RPC method calls. +use crate::middleware::Body; use crate::transport::http; use crate::ResponseBody; @@ -112,9 +113,9 @@ impl ProxyGetRequest { } } -impl Service> for ProxyGetRequest +impl Service> for ProxyGetRequest where - S: Service, Response = Response>, + S: Service, Response = Response>, S::Response: 'static, S::Error: Into> + 'static, S::Future: Send + 'static, @@ -128,7 +129,7 @@ where self.inner.poll_ready(cx).map_err(Into::into) } - fn call(&mut self, mut req: Request) -> Self::Future { + fn call(&mut self, mut req: Request) -> Self::Future { let modify = self.path.as_ref() == req.uri() && req.method() == Method::GET; // Proxy the request to the appropriate method call. @@ -143,10 +144,12 @@ where req.headers_mut().insert(ACCEPT, HeaderValue::from_static("application/json")); // Adjust the body to reflect the method call. - let body = serde_json::to_vec(&RequestSer::borrowed(&Id::Number(0), &self.method, None)) + let bytes = serde_json::to_vec(&RequestSer::borrowed(&Id::Number(0), &self.method, None)) .expect("Valid request; qed"); - req.body_mut().map_frame(|f| f.map_data(|_| body.as_slice())); + let body = Body::new(http_body_util::Full::new(bytes.into())); + + req = req.map(|_| body); } // Call the inner service and get a future that resolves to the response. @@ -165,8 +168,7 @@ where let mut bytes = Vec::new(); while let Some(frame) = body.frame().await { - // TODO error handling - let data = frame.unwrap().into_data().unwrap(); + let data = frame?.into_data().map_err(|e| anyhow::anyhow!("{:?}", e))?; bytes.extend(data); } diff --git a/server/src/middleware/mod.rs b/server/src/middleware/mod.rs index dff6948aa3..fe85d35583 100644 --- a/server/src/middleware/mod.rs +++ b/server/src/middleware/mod.rs @@ -26,7 +26,113 @@ //! jsonrpsee-server middleware +use std::{ + error::Error as StdError, + pin::Pin, + task::{Context, Poll}, +}; + +use http_body::Frame; +use http_body_util::BodyExt; +use hyper::body::Bytes; +use pin_project::pin_project; +use tower::util::Oneshot; +use tower::ServiceExt; + /// HTTP related middleware. pub mod http; /// JSON-RPC specific middleware. pub mod rpc; + +/// A HTTP request body. +#[derive(Debug, Default)] +pub struct Body(http_body_util::combinators::BoxBody>); + +impl Body { + /// Create an empty body. + pub fn empty() -> Self { + Self::default() + } + + /// Create a new body. + pub fn new(body: B) -> Self + where + B: http_body::Body + Send + Sync + 'static, + B::Data: Send + 'static, + B::Error: Into>, + { + Self(body.map_err(|e| e.into()).boxed()) + } +} + +impl http_body::Body for Body { + type Data = Bytes; + type Error = Box; + + #[inline] + fn poll_frame( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + Pin::new(&mut self.0).poll_frame(cx) + } + + #[inline] + fn size_hint(&self) -> http_body::SizeHint { + self.0.size_hint() + } + + #[inline] + fn is_end_stream(&self) -> bool { + self.0.is_end_stream() + } +} + +/// A HTTP request. +pub type Request = hyper::Request; + +#[derive(Debug, Copy, Clone)] +pub(crate) struct TowerToHyperService { + service: S, +} + +impl TowerToHyperService { + pub(crate) fn new(service: S) -> Self { + Self { service } + } +} + +impl hyper::service::Service> for TowerToHyperService +where + S: tower::Service + Clone, +{ + type Response = S::Response; + type Error = S::Error; + type Future = TowerToHyperServiceFuture; + + fn call(&self, req: hyper::Request) -> Self::Future { + let req = req.map(Body::new); + TowerToHyperServiceFuture { future: self.service.clone().oneshot(req) } + } +} + +#[pin_project] +pub(crate) struct TowerToHyperServiceFuture +where + S: tower::Service, +{ + #[pin] + future: Oneshot, +} + +impl std::future::Future for TowerToHyperServiceFuture +where + S: tower::Service, +{ + type Output = Result; + + #[inline] + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.project().future.poll(cx) + } +} diff --git a/server/src/server.rs b/server/src/server.rs index 78028eb4cf..3a74b0ebf0 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -35,6 +35,7 @@ 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::{Body, TowerToHyperService}; use crate::transport::ws::BackgroundTaskParams; use crate::transport::{http, ws}; use crate::{HttpRequest, ResponseBody, LOG_TARGET}; @@ -42,8 +43,8 @@ use crate::{HttpRequest, ResponseBody, LOG_TARGET}; use futures_util::future::{self, Either, FutureExt}; use futures_util::io::{BufReader, BufWriter}; +use hyper::body::Bytes; use hyper_util::rt::TokioIo; -use hyper_util::service::TowerToHyperService; use jsonrpsee_core::id_providers::RandomIntegerIdProvider; use jsonrpsee_core::server::helpers::prepare_error; use jsonrpsee_core::server::{BatchResponseBuilder, BoundedSubscriptions, MethodResponse, MethodSink, Methods}; @@ -955,7 +956,7 @@ impl TowerService } } -impl Service for TowerService +impl Service> for TowerService where RpcMiddleware: for<'a> tower::Layer + Clone, >::Service: Send + Sync + 'static, @@ -963,12 +964,14 @@ where HttpMiddleware: Layer> + Send + 'static, >>::Service: Send + Service< - HttpRequest, + hyper::Request, Response = hyper::Response, Error = Box<(dyn StdError + Send + Sync + 'static)>, >, - <>>::Service as Service>::Future: + <>>::Service as Service>>::Future: Send + 'static, + B: http_body::Body + Send + Sync + 'static, + B::Error: Into>, { type Response = hyper::Response; type Error = Box; @@ -978,7 +981,7 @@ where Poll::Ready(Ok(())) } - fn call(&mut self, request: HttpRequest) -> Self::Future { + fn call(&mut self, request: hyper::Request) -> Self::Future { Box::pin(self.http_middleware.service(self.rpc_middleware.clone()).call(request)) } } @@ -994,11 +997,13 @@ pub struct TowerServiceNoHttp { on_session_close: Option, } -impl Service for TowerServiceNoHttp +impl Service> for TowerServiceNoHttp where RpcMiddleware: for<'a> tower::Layer, >::Service: Send + Sync + 'static, for<'a> >::Service: RpcServiceT<'a>, + B: http_body::Body + Send + Sync + 'static, + B::Error: Into>, { type Response = hyper::Response; @@ -1012,7 +1017,9 @@ where Poll::Ready(Ok(())) } - fn call(&mut self, request: HttpRequest) -> Self::Future { + fn call(&mut self, request: hyper::Request) -> Self::Future { + let request = request.map(Body::new); + let conn_guard = &self.inner.conn_guard; let stop_handle = self.inner.stop_handle.clone(); let conn_id = self.inner.conn_id; @@ -1193,44 +1200,31 @@ where let service = http_middleware.service(tower_service); tokio::spawn(async { - to_http_service(socket, service, stop_handle).in_current_span().await; - drop(drop_on_completion) - }); -} + // this requires Clone. + let service = TowerToHyperService::new(service); + let io = TokioIo::new(socket); -// Attempts to create a HTTP connection from a socket. -async fn to_http_service(socket: TcpStream, service: S, stop_handle: StopHandle) -where - S: Service> + Send + 'static + Clone, - S::Error: Into>, - S::Future: Send, - B: http_body::Body + Send + 'static, - ::Error: Send + Sync + StdError, - ::Data: Send, -{ - // this requires Clone. - let service = TowerToHyperService::new(service); - let io = TokioIo::new(socket); + let conn = hyper::server::conn::http1::Builder::new().serve_connection(io, service).with_upgrades(); - let conn = hyper::server::conn::http1::Builder::new().serve_connection(io, service).with_upgrades(); + let stopped = stop_handle.shutdown(); - let stopped = stop_handle.shutdown(); + tokio::pin!(stopped); - tokio::pin!(stopped); + let res = match future::select(conn, stopped).await { + Either::Left((conn, _)) => conn, + Either::Right((_, mut conn)) => { + // NOTE: the connection should continue to be polled until shutdown can finish. + // Thus, both lines below are needed and not a nit. + Pin::new(&mut conn).graceful_shutdown(); + conn.await + } + }; - let res = match future::select(conn, stopped).await { - Either::Left((conn, _)) => conn, - Either::Right((_, mut conn)) => { - // NOTE: the connection should continue to be polled until shutdown can finish. - // Thus, both lines below are needed and not a nit. - Pin::new(&mut conn).graceful_shutdown(); - conn.await + if let Err(e) = res { + tracing::debug!(target: LOG_TARGET, "HTTP serve connection failed {:?}", e); } - }; - - if let Err(e) = res { - tracing::debug!(target: LOG_TARGET, "HTTP serve connection failed {:?}", e); - } + drop(drop_on_completion) + }); } enum AcceptConnection { diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index ffb7fbaffe..0d69867ee3 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -1,3 +1,9 @@ +use crate::{ + middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}, + middleware::Request as HttpRequest, + server::{handle_rpc_call, ServerConfig}, + BatchRequestConfig, ConnectionState, ResponseBody, LOG_TARGET, +}; use http::Method; use hyper::body::Body; use jsonrpsee_core::{ @@ -5,12 +11,6 @@ use jsonrpsee_core::{ server::Methods, }; -use crate::{ - middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}, - server::{handle_rpc_call, ServerConfig}, - BatchRequestConfig, ConnectionState, ResponseBody, LOG_TARGET, -}; - /// Checks that content type of received request is valid for JSON-RPC. pub fn content_type_is_json(request: &hyper::Request) -> bool { is_json(request.headers().get(hyper::header::CONTENT_TYPE)) @@ -32,7 +32,7 @@ pub fn is_json(content_type: Option<&hyper::header::HeaderValue>) -> bool { /// /// Fails if the HTTP request was a malformed JSON-RPC request. pub async fn call_with_service_builder( - request: hyper::Request, + request: HttpRequest, server_cfg: ServerConfig, conn: ConnectionState, methods: impl Into, @@ -65,7 +65,7 @@ where /// /// Fails if the HTTP request was a malformed JSON-RPC request. pub async fn call_with_service( - request: hyper::Request, + request: HttpRequest, batch_config: BatchRequestConfig, max_request_size: u32, rpc_service: S, diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 04b644a3af..fe9df78752 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -4,7 +4,7 @@ use std::time::Instant; use crate::future::{IntervalStream, SessionClose}; use crate::middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}; use crate::server::{handle_rpc_call, ConnectionState, ServerConfig}; -use crate::{PingConfig, ResponseBody, LOG_TARGET}; +use crate::{HttpRequest, PingConfig, ResponseBody, LOG_TARGET}; use futures_util::future::{self, Either}; use futures_util::io::{BufReader, BufWriter}; @@ -401,7 +401,7 @@ async fn graceful_shutdown( /// } /// ``` pub async fn connect( - req: hyper::Request, + req: HttpRequest, server_cfg: ServerConfig, methods: impl Into, conn: ConnectionState, From 5ffd811f7b7105e38cd4a0ed1f4a62be517b85fa Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 15 May 2024 12:07:32 +0200 Subject: [PATCH 03/22] fix nits --- client/ws-client/src/tests.rs | 6 +++++- examples/examples/jsonrpsee_as_service.rs | 17 ++++++---------- .../jsonrpsee_server_low_level_api.rs | 15 +++++--------- examples/examples/proc_macro.rs | 6 +++++- examples/examples/ws_dual_stack.rs | 16 +++++---------- server/src/middleware/http/authority.rs | 11 ++-------- server/src/server.rs | 11 +++++----- server/src/tests/helpers.rs | 15 +++++--------- test-utils/Cargo.toml | 4 ++-- test-utils/src/helpers.rs | 3 ++- test-utils/src/mocks.rs | 20 +++++++------------ 11 files changed, 49 insertions(+), 75 deletions(-) diff --git a/client/ws-client/src/tests.rs b/client/ws-client/src/tests.rs index 9da58eec80..e550854d2a 100644 --- a/client/ws-client/src/tests.rs +++ b/client/ws-client/src/tests.rs @@ -461,6 +461,8 @@ fn assert_error_response(err: Error, exp: ErrorObjectOwned) { #[tokio::test] async fn redirections() { + init_logger(); + let expected = "abc 123"; let server = WebSocketTestServer::with_hardcoded_response( "127.0.0.1:0".parse().unwrap(), @@ -471,10 +473,12 @@ async fn redirections() { .unwrap(); let server_url = format!("ws://{}", server.local_addr()); - let redirect_url = jsonrpsee_test_utils::mocks::ws_server_with_redirect(server_url); + let redirect_url = + jsonrpsee_test_utils::mocks::ws_server_with_redirect(server_url).with_default_timeout().await.unwrap(); // The client will first connect to a server that only performs re-directions and finally // redirect to another server to complete the handshake. + println!("Connecting to {redirect_url}"); let client = WsClientBuilder::default().build(&redirect_url).with_default_timeout().await; // It's an ok client let client = match client { diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index 7b417b88ad..cf22d1f65c 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -33,7 +33,6 @@ use std::convert::Infallible; use std::net::SocketAddr; -use std::pin::Pin; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -41,7 +40,7 @@ use futures::future::{self, Either}; use futures::FutureExt; use hyper::header::AUTHORIZATION; use hyper::HeaderMap; -use hyper_util::rt::TokioIo; +use hyper_util::rt::{TokioExecutor, TokioIo}; use jsonrpsee::core::async_trait; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::proc_macros::rpc; @@ -189,7 +188,7 @@ async fn run_server(metrics: Metrics) -> anyhow::Result { // The `tokio::select!` macro is used to wait for either of the // listeners to accept a new connection or for the server to be // stopped. - let stream = tokio::select! { + let sock = tokio::select! { res = listener.accept() => { match res { Ok((stream, _remote_addr)) => stream, @@ -260,23 +259,19 @@ async fn run_server(metrics: Metrics) -> anyhow::Result { } }); - let conn = hyper::server::conn::http1::Builder::new() - .serve_connection(TokioIo::new(stream), svc) - .with_upgrades(); + let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); + let conn = builder.serve_connection_with_upgrades(TokioIo::new(sock), svc); let stopped = stop_handle2.shutdown(); // Pin the future so that it can be polled. - tokio::pin!(stopped); + tokio::pin!(stopped, conn); let res = match future::select(conn, stopped).await { // Return the connection if not stopped. Either::Left((conn, _)) => conn, // If the server is stopped, we should gracefully shutdown // the connection and poll it until it finishes. - Either::Right((_, mut conn)) => { - Pin::new(&mut conn).graceful_shutdown(); - conn.await - } + Either::Right((_, conn)) => conn.await, }; // Log any errors that might have occurred. diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index 27be1e1a2e..26ace42178 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -41,13 +41,12 @@ use std::collections::HashSet; use std::convert::Infallible; use std::net::{IpAddr, SocketAddr}; -use std::pin::Pin; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; use futures::future::{self, BoxFuture, Either}; use futures::FutureExt; -use hyper_util::rt::TokioIo; +use hyper_util::rt::{TokioExecutor, TokioIo}; use jsonrpsee::core::async_trait; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::proc_macros::rpc; @@ -313,23 +312,19 @@ async fn run_server() -> anyhow::Result { } }); - let conn = hyper::server::conn::http1::Builder::new() - .serve_connection(TokioIo::new(sock), svc) - .with_upgrades(); + let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); + let conn = builder.serve_connection_with_upgrades(TokioIo::new(sock), svc); let stopped = stop_handle2.shutdown(); // Pin the future so that it can be polled. - tokio::pin!(stopped); + tokio::pin!(stopped, conn); let res = match future::select(conn, stopped).await { // Return the connection if not stopped. Either::Left((conn, _)) => conn, // If the server is stopped, we should gracefully shutdown // the connection and poll it until it finishes. - Either::Right((_, mut conn)) => { - Pin::new(&mut conn).graceful_shutdown(); - conn.await - } + Either::Right((_, conn)) => conn.await, }; // Log any errors that might have occurred. diff --git a/examples/examples/proc_macro.rs b/examples/examples/proc_macro.rs index 6e7ecdb214..d0b8b7228c 100644 --- a/examples/examples/proc_macro.rs +++ b/examples/examples/proc_macro.rs @@ -42,7 +42,11 @@ where { /// Async method call example. #[method(name = "getKeys")] - async fn storage_keys(&self, storage_key: usize, r#type: usize) -> Result, ErrorObjectOwned>; + async fn storage_keys( + &self, + storage_key: StorageKey, + hash: Option, + ) -> Result, ErrorObjectOwned>; /// Subscription that takes a `StorageKey` as input and produces a `Vec`. #[subscription(name = "subscribeStorage" => "override", item = Vec)] diff --git a/examples/examples/ws_dual_stack.rs b/examples/examples/ws_dual_stack.rs index d6ec373f96..269e32f7a1 100644 --- a/examples/examples/ws_dual_stack.rs +++ b/examples/examples/ws_dual_stack.rs @@ -25,14 +25,13 @@ // DEALINGS IN THE SOFTWARE. use futures::future::{self, Either}; -use hyper_util::rt::TokioIo; +use hyper_util::rt::{TokioExecutor, TokioIo}; use hyper_util::service::TowerToHyperService; use jsonrpsee::core::client::ClientT; use jsonrpsee::server::{stop_channel, ServerHandle}; use jsonrpsee::ws_client::WsClientBuilder; use jsonrpsee::{rpc_params, RpcModule}; use std::net::SocketAddr; -use std::pin::Pin; use tokio::net::TcpListener; use tracing_subscriber::util::SubscriberInitExt; @@ -119,24 +118,19 @@ async fn run_server() -> anyhow::Result<(ServerHandle, Addrs)> { // Spawn a new task to serve each respective (Hyper) connection. tokio::spawn(async move { - let conn = hyper::server::conn::http1::Builder::new() - .serve_connection(TokioIo::new(stream), TowerToHyperService::new(svc)) - .with_upgrades(); - + let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); + let conn = builder.serve_connection_with_upgrades(TokioIo::new(stream), TowerToHyperService::new(svc)); let stopped = stop_hdl2.shutdown(); // Pin the future so that it can be polled. - tokio::pin!(stopped); + tokio::pin!(stopped, conn); let res = match future::select(conn, stopped).await { // Return the connection if not stopped. Either::Left((conn, _)) => conn, // If the server is stopped, we should gracefully shutdown // the connection and poll it until it finishes. - Either::Right((_, mut conn)) => { - Pin::new(&mut conn).graceful_shutdown(); - conn.await - } + Either::Right((_, conn)) => conn.await, }; // Log any errors that might have occurred. diff --git a/server/src/middleware/http/authority.rs b/server/src/middleware/http/authority.rs index d8fdbb53f9..6ef4145be3 100644 --- a/server/src/middleware/http/authority.rs +++ b/server/src/middleware/http/authority.rs @@ -173,18 +173,11 @@ mod tests { assert_eq!(Authority::try_from("http://parity.io").unwrap(), authority("parity.io", Port::Default)); assert_eq!(Authority::try_from("https://parity.io:8443").unwrap(), authority("parity.io", Port::Fixed(8443))); assert_eq!(Authority::try_from("chrome-extension://124.0.0.1").unwrap(), authority("124.0.0.1", Port::Default)); - assert_eq!( - Authority::try_from( - "http://*.domain:*/ -somepath" - ) - .unwrap(), - authority("*.domain", Port::Any) - ); + assert_eq!(Authority::try_from("http://*.domain:*/somepath").unwrap(), authority("*.domain", Port::Any)); assert_eq!(Authority::try_from("parity.io").unwrap(), authority("parity.io", Port::Default)); assert_eq!(Authority::try_from("127.0.0.1:8845").unwrap(), authority("127.0.0.1", Port::Fixed(8845))); assert_eq!( - Authority::try_from("http: //[2001:db8:85a3:8d3:1319:8a2e:370:7348]:9933/").unwrap(), + Authority::try_from("http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:9933/").unwrap(), authority("[2001:db8:85a3:8d3:1319:8a2e:370:7348]", Port::Fixed(9933)) ); assert_eq!( diff --git a/server/src/server.rs b/server/src/server.rs index 3a74b0ebf0..29e877fe49 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -44,7 +44,7 @@ use futures_util::future::{self, Either, FutureExt}; use futures_util::io::{BufReader, BufWriter}; use hyper::body::Bytes; -use hyper_util::rt::TokioIo; +use hyper_util::rt::{TokioExecutor, TokioIo}; use jsonrpsee_core::id_providers::RandomIntegerIdProvider; use jsonrpsee_core::server::helpers::prepare_error; use jsonrpsee_core::server::{BatchResponseBuilder, BoundedSubscriptions, MethodResponse, MethodSink, Methods}; @@ -1203,19 +1203,18 @@ where // this requires Clone. let service = TowerToHyperService::new(service); let io = TokioIo::new(socket); + let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); - let conn = hyper::server::conn::http1::Builder::new().serve_connection(io, service).with_upgrades(); - + let conn = builder.serve_connection_with_upgrades(io, service); let stopped = stop_handle.shutdown(); - tokio::pin!(stopped); + tokio::pin!(stopped, conn); let res = match future::select(conn, stopped).await { Either::Left((conn, _)) => conn, - Either::Right((_, mut conn)) => { + Either::Right((_, conn)) => { // NOTE: the connection should continue to be polled until shutdown can finish. // Thus, both lines below are needed and not a nit. - Pin::new(&mut conn).graceful_shutdown(); conn.await } }; diff --git a/server/src/tests/helpers.rs b/server/src/tests/helpers.rs index 2a3bf00a49..a0114cebed 100644 --- a/server/src/tests/helpers.rs +++ b/server/src/tests/helpers.rs @@ -1,6 +1,5 @@ use std::convert::Infallible; use std::net::SocketAddr; -use std::pin::Pin; use std::sync::atomic::Ordering; use std::sync::Arc; use std::{fmt, sync::atomic::AtomicUsize}; @@ -9,7 +8,7 @@ use crate::{stop_channel, RpcModule, Server, ServerBuilder, ServerHandle}; use futures_util::future::Either; use futures_util::{future, FutureExt}; -use hyper_util::rt::TokioIo; +use hyper_util::rt::{TokioExecutor, TokioIo}; use jsonrpsee_core::server::Methods; use jsonrpsee_core::{DeserializeOwned, RpcResult, StringError}; use jsonrpsee_test_utils::TimeoutFutureExt; @@ -275,23 +274,19 @@ pub(crate) async fn ws_server_with_stats(metrics: Metrics) -> SocketAddr { } }); - let conn = hyper::server::conn::http1::Builder::new() - .serve_connection(TokioIo::new(sock), svc) - .with_upgrades(); + let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); + let conn = builder.serve_connection_with_upgrades(TokioIo::new(sock), svc); let stopped = stop_handle.shutdown(); // Pin the future so that it can be polled. - tokio::pin!(stopped); + tokio::pin!(stopped, conn); let res = match future::select(conn, stopped).await { // Return the connection if not stopped. Either::Left((conn, _)) => conn, // If the server is stopped, we should gracefully shutdown // the connection and poll it until it finishes. - Either::Right((_, mut conn)) => { - Pin::new(&mut conn).graceful_shutdown(); - conn.await - } + Either::Right((_, conn)) => conn.await, }; // Log any errors that might have occurred. diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index 3adf41b36f..60235e046e 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -9,8 +9,8 @@ publish = false [dependencies] futures-channel = "0.3.14" futures-util = "0.3.14" -hyper = { version = "1.3.0", features = ["full"] } -hyper-util = { version = "0.1", features = ["full"] } +hyper = { version = "1.3.0", features = [] } +hyper-util = { version = "0.1", features = ["server-auto", "tokio", "client-legacy"] } http-body-util = "0.1.0" tracing = "0.1.34" serde = { version = "1", default-features = false, features = ["derive"] } diff --git a/test-utils/src/helpers.rs b/test-utils/src/helpers.rs index d7ff17fb61..4ee4f5653f 100644 --- a/test-utils/src/helpers.rs +++ b/test-utils/src/helpers.rs @@ -249,8 +249,9 @@ pub async fn http_server_with_hardcoded_response(response: String) -> SocketAddr let response = response.clone(); tokio::spawn(async move { let io = TokioIo::new(sock); + let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); - let conn = hyper::server::conn::http1::Builder::new().serve_connection( + let conn = builder.serve_connection_with_upgrades( io, service_fn(move |req| { let rp = response.clone(); diff --git a/test-utils/src/mocks.rs b/test-utils/src/mocks.rs index c7dfc0ef84..c131e3222e 100644 --- a/test-utils/src/mocks.rs +++ b/test-utils/src/mocks.rs @@ -25,10 +25,8 @@ // DEALINGS IN THE SOFTWARE. use std::convert::Infallible; -use std::error::Error as StdError; use std::io; use std::net::SocketAddr; -use std::net::TcpListener; use std::time::Duration; use futures_channel::mpsc; @@ -38,6 +36,7 @@ use futures_util::io::{BufReader, BufWriter}; use futures_util::sink::SinkExt; use futures_util::stream::{self, StreamExt}; use futures_util::{pin_mut, select}; +use hyper_util::rt::TokioExecutor; use hyper_util::rt::TokioIo; use serde::{Deserialize, Serialize}; use soketto::handshake::{self, http::is_upgrade_request, server::Response, Error as SokettoError, Server}; @@ -323,14 +322,11 @@ async fn connection_task(socket: tokio::net::TcpStream, mode: ServerMode, mut ex // Run a WebSocket server running on localhost that redirects requests for testing. // Requests to any url except for `/myblock/two` will redirect one or two times (HTTP 301) and eventually end up in `/myblock/two`. -pub fn ws_server_with_redirect(other_server: String) -> String { - let addr: SocketAddr = ([127, 0, 0, 1], 0).into(); - let listener = TcpListener::bind(addr).unwrap(); +pub async fn ws_server_with_redirect(other_server: String) -> String { + let listener = tokio::net::TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))).await.unwrap(); let addr = listener.local_addr().unwrap(); tokio::spawn(async move { - let listener = tokio::net::TcpListener::from_std(listener).unwrap(); - loop { let Ok((stream, _addr)) = listener.accept().await else { continue; @@ -339,15 +335,13 @@ pub fn ws_server_with_redirect(other_server: String) -> String { let other_server = other_server.clone(); tokio::spawn(async { let io = TokioIo::new(stream); + let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); - let conn = hyper::server::conn::http1::Builder::new().serve_connection( + let conn = builder.serve_connection_with_upgrades( io, hyper::service::service_fn(move |req| { let other_server = other_server.clone(); - async move { - let res = handler(req, other_server).await.unwrap(); - Ok::<_, Infallible>(res) - } + handler(req, other_server) }), ); @@ -362,7 +356,7 @@ pub fn ws_server_with_redirect(other_server: String) -> String { async fn handler( req: hyper::Request, other_server: String, -) -> Result, Box> { +) -> Result, Infallible> { if is_upgrade_request(&req) { tracing::debug!("{:?}", req); From 4a61aedea2909c0e815e8ea49fc5ceeb1950a66a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 15 May 2024 16:55:48 +0200 Subject: [PATCH 04/22] more cleanup --- .../jsonrpsee_server_low_level_api.rs | 4 +- server/src/lib.rs | 5 +- server/src/middleware/http/host_filter.rs | 4 +- .../src/middleware/http/proxy_get_request.rs | 15 +- server/src/middleware/mod.rs | 106 -------------- server/src/server.rs | 31 ++-- server/src/transport/http.rs | 35 +++-- server/src/transport/ws.rs | 10 +- server/src/utils.rs | 135 ++++++++++++++++++ test-utils/Cargo.toml | 2 +- 10 files changed, 184 insertions(+), 163 deletions(-) create mode 100644 server/src/utils.rs diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index 26ace42178..85fb9e5d84 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -51,7 +51,7 @@ use jsonrpsee::core::async_trait; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::middleware::rpc::RpcServiceT; -use jsonrpsee::server::middleware::Body; +use jsonrpsee::server::HttpBody; use jsonrpsee::server::{ http, stop_channel, ws, ConnectionGuard, ConnectionState, RpcServiceBuilder, ServerConfig, ServerHandle, StopHandle, }; @@ -227,7 +227,7 @@ async fn run_server() -> anyhow::Result { let per_conn = per_conn.clone(); let svc = service_fn(move |req| { - let req = req.map(Body::new); + let req = req.map(HttpBody::new); let PerConnection { methods, diff --git a/server/src/lib.rs b/server/src/lib.rs index 0cc0519b2d..c733eec135 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -34,6 +34,7 @@ mod future; mod server; mod transport; +mod utils; pub mod middleware; @@ -52,10 +53,8 @@ pub use server::{ }; pub use tracing; +pub use crate::utils::{HttpBody, HttpRequest, HttpResponse, HttpResponseBody}; pub use transport::http; pub use transport::ws; pub(crate) const LOG_TARGET: &str = "jsonrpsee-server"; - -pub use crate::middleware::{Body, Request as HttpRequest}; -pub(crate) type ResponseBody = http_body_util::Full; diff --git a/server/src/middleware/http/host_filter.rs b/server/src/middleware/http/host_filter.rs index c54024b401..a3e0fa1a7e 100644 --- a/server/src/middleware/http/host_filter.rs +++ b/server/src/middleware/http/host_filter.rs @@ -28,7 +28,7 @@ use crate::middleware::http::authority::{Authority, AuthorityError, Port}; use crate::transport::http; -use crate::{HttpRequest, ResponseBody, LOG_TARGET}; +use crate::{HttpRequest, HttpResponseBody, LOG_TARGET}; use futures_util::{Future, FutureExt, TryFutureExt}; use hyper::Response; use route_recognizer::Router; @@ -100,7 +100,7 @@ pub struct HostFilter { impl Service for HostFilter where - S: Service>, + S: Service>, S::Response: 'static, S::Error: Into> + 'static, S::Future: Send + 'static, diff --git a/server/src/middleware/http/proxy_get_request.rs b/server/src/middleware/http/proxy_get_request.rs index 38f6669729..9c5409f78f 100644 --- a/server/src/middleware/http/proxy_get_request.rs +++ b/server/src/middleware/http/proxy_get_request.rs @@ -26,15 +26,14 @@ //! Middleware that proxies requests at a specified URI to internal //! RPC method calls. - -use crate::middleware::Body; +//! use crate::transport::http; -use crate::ResponseBody; +use crate::{HttpBody, HttpRequest, HttpResponse}; use http_body_util::BodyExt; use hyper::header::{ACCEPT, CONTENT_TYPE}; use hyper::http::HeaderValue; -use hyper::{Method, Request, Response, Uri}; +use hyper::{Method, Uri}; use jsonrpsee_types::{Id, RequestSer}; use std::error::Error; use std::future::Future; @@ -113,9 +112,9 @@ impl ProxyGetRequest { } } -impl Service> for ProxyGetRequest +impl Service for ProxyGetRequest where - S: Service, Response = Response>, + S: Service, S::Response: 'static, S::Error: Into> + 'static, S::Future: Send + 'static, @@ -129,7 +128,7 @@ where self.inner.poll_ready(cx).map_err(Into::into) } - fn call(&mut self, mut req: Request) -> Self::Future { + fn call(&mut self, mut req: HttpRequest) -> Self::Future { let modify = self.path.as_ref() == req.uri() && req.method() == Method::GET; // Proxy the request to the appropriate method call. @@ -147,7 +146,7 @@ where let bytes = serde_json::to_vec(&RequestSer::borrowed(&Id::Number(0), &self.method, None)) .expect("Valid request; qed"); - let body = Body::new(http_body_util::Full::new(bytes.into())); + let body = HttpBody::new(http_body_util::Full::new(bytes.into())); req = req.map(|_| body); } diff --git a/server/src/middleware/mod.rs b/server/src/middleware/mod.rs index fe85d35583..dff6948aa3 100644 --- a/server/src/middleware/mod.rs +++ b/server/src/middleware/mod.rs @@ -26,113 +26,7 @@ //! jsonrpsee-server middleware -use std::{ - error::Error as StdError, - pin::Pin, - task::{Context, Poll}, -}; - -use http_body::Frame; -use http_body_util::BodyExt; -use hyper::body::Bytes; -use pin_project::pin_project; -use tower::util::Oneshot; -use tower::ServiceExt; - /// HTTP related middleware. pub mod http; /// JSON-RPC specific middleware. pub mod rpc; - -/// A HTTP request body. -#[derive(Debug, Default)] -pub struct Body(http_body_util::combinators::BoxBody>); - -impl Body { - /// Create an empty body. - pub fn empty() -> Self { - Self::default() - } - - /// Create a new body. - pub fn new(body: B) -> Self - where - B: http_body::Body + Send + Sync + 'static, - B::Data: Send + 'static, - B::Error: Into>, - { - Self(body.map_err(|e| e.into()).boxed()) - } -} - -impl http_body::Body for Body { - type Data = Bytes; - type Error = Box; - - #[inline] - fn poll_frame( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll, Self::Error>>> { - Pin::new(&mut self.0).poll_frame(cx) - } - - #[inline] - fn size_hint(&self) -> http_body::SizeHint { - self.0.size_hint() - } - - #[inline] - fn is_end_stream(&self) -> bool { - self.0.is_end_stream() - } -} - -/// A HTTP request. -pub type Request = hyper::Request; - -#[derive(Debug, Copy, Clone)] -pub(crate) struct TowerToHyperService { - service: S, -} - -impl TowerToHyperService { - pub(crate) fn new(service: S) -> Self { - Self { service } - } -} - -impl hyper::service::Service> for TowerToHyperService -where - S: tower::Service + Clone, -{ - type Response = S::Response; - type Error = S::Error; - type Future = TowerToHyperServiceFuture; - - fn call(&self, req: hyper::Request) -> Self::Future { - let req = req.map(Body::new); - TowerToHyperServiceFuture { future: self.service.clone().oneshot(req) } - } -} - -#[pin_project] -pub(crate) struct TowerToHyperServiceFuture -where - S: tower::Service, -{ - #[pin] - future: Oneshot, -} - -impl std::future::Future for TowerToHyperServiceFuture -where - S: tower::Service, -{ - type Output = Result; - - #[inline] - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.project().future.poll(cx) - } -} diff --git a/server/src/server.rs b/server/src/server.rs index 29e877fe49..d560d493a8 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -35,10 +35,9 @@ 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::{Body, TowerToHyperService}; use crate::transport::ws::BackgroundTaskParams; use crate::transport::{http, ws}; -use crate::{HttpRequest, ResponseBody, LOG_TARGET}; +use crate::{HttpBody, HttpRequest, HttpResponse, HttpResponseBody, LOG_TARGET}; use futures_util::future::{self, Either, FutureExt}; use futures_util::io::{BufReader, BufWriter}; @@ -103,7 +102,7 @@ where HttpMiddleware: Layer> + Send + 'static, >>::Service: Send + Clone - + Service, Error = Box<(dyn StdError + Send + Sync + 'static)>>, + + Service, Error = Box<(dyn StdError + Send + Sync + 'static)>>, <>>::Service as Service>::Future: Send, B: http_body::Body + Send + 'static, ::Error: Send + Sync + StdError, @@ -956,24 +955,20 @@ impl TowerService } } -impl Service> for TowerService +impl Service> for TowerService where RpcMiddleware: for<'a> tower::Layer + Clone, >::Service: Send + Sync + 'static, for<'a> >::Service: RpcServiceT<'a>, HttpMiddleware: Layer> + Send + 'static, - >>::Service: Send - + Service< - hyper::Request, - Response = hyper::Response, - Error = Box<(dyn StdError + Send + Sync + 'static)>, - >, - <>>::Service as Service>>::Future: + >>::Service: + Send + Service, Response = HttpResponse, Error = Box<(dyn StdError + Send + Sync + 'static)>>, + <>>::Service as Service>>::Future: Send + 'static, B: http_body::Body + Send + Sync + 'static, B::Error: Into>, { - type Response = hyper::Response; + type Response = HttpResponse; type Error = Box; type Future = Pin> + Send>>; @@ -997,7 +992,7 @@ pub struct TowerServiceNoHttp { on_session_close: Option, } -impl Service> for TowerServiceNoHttp +impl Service> for TowerServiceNoHttp where RpcMiddleware: for<'a> tower::Layer, >::Service: Send + Sync + 'static, @@ -1005,7 +1000,7 @@ where B: http_body::Body + Send + Sync + 'static, B::Error: Into>, { - type Response = hyper::Response; + type Response = HttpResponse; // The following associated type is required by the `impl Server` bounds. // It satisfies the server's bounds when the `tower::ServiceBuilder` is not set (ie `B: Identity`). @@ -1018,7 +1013,7 @@ where } fn call(&mut self, request: hyper::Request) -> Self::Future { - let request = request.map(Body::new); + let request = request.map(HttpBody::new); let conn_guard = &self.inner.conn_guard; let stop_handle = self.inner.stop_handle.clone(); @@ -1106,11 +1101,11 @@ where .in_current_span(), ); - response.map(|()| ResponseBody::default()) + response.map(|()| HttpResponseBody::default()) } Err(e) => { tracing::debug!(target: LOG_TARGET, "Could not upgrade connection: {}", e); - hyper::Response::new(ResponseBody::from(format!("Could not upgrade connection: {e}"))) + HttpResponse::new(HttpResponseBody::from(format!("Could not upgrade connection: {e}"))) } }; @@ -1201,7 +1196,7 @@ where tokio::spawn(async { // this requires Clone. - let service = TowerToHyperService::new(service); + let service = crate::utils::TowerToHyperService::new(service); let io = TokioIo::new(socket); let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index 0d69867ee3..92041b9f80 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -1,8 +1,7 @@ use crate::{ middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}, - middleware::Request as HttpRequest, server::{handle_rpc_call, ServerConfig}, - BatchRequestConfig, ConnectionState, ResponseBody, LOG_TARGET, + BatchRequestConfig, ConnectionState, HttpRequest, HttpResponse, LOG_TARGET, }; use http::Method; use hyper::body::Body; @@ -37,7 +36,7 @@ pub async fn call_with_service_builder( conn: ConnectionState, methods: impl Into, rpc_service: RpcServiceBuilder, -) -> hyper::Response +) -> HttpResponse where L: for<'a> tower::Layer, >::Service: Send + Sync + 'static, @@ -70,7 +69,7 @@ pub async fn call_with_service( max_request_size: u32, rpc_service: S, max_response_size: u32, -) -> hyper::Response +) -> HttpResponse where for<'a> S: RpcServiceT<'a> + Send, { @@ -106,13 +105,13 @@ pub mod response { use jsonrpsee_types::error::{reject_too_big_request, ErrorCode}; use jsonrpsee_types::{ErrorObjectOwned, Id, Response, ResponsePayload}; - use crate::ResponseBody; + use crate::{HttpResponse, HttpResponseBody}; const JSON: &str = "application/json; charset=utf-8"; const TEXT: &str = "text/plain"; /// Create a response for json internal error. - pub fn internal_error() -> hyper::Response { + pub fn internal_error() -> HttpResponse { let err = ResponsePayload::<()>::error(ErrorObjectOwned::from(ErrorCode::InternalError)); let rp = Response::new(err, Id::Null); let error = serde_json::to_string(&rp).expect("built from known-good data; qed"); @@ -121,12 +120,12 @@ pub mod response { } /// Create a text/plain response for not allowed hosts. - pub fn host_not_allowed() -> hyper::Response { + pub fn host_not_allowed() -> HttpResponse { from_template(hyper::StatusCode::FORBIDDEN, "Provided Host header is not whitelisted.\n", TEXT) } /// Create a text/plain response for disallowed method used. - pub fn method_not_allowed() -> hyper::Response { + pub fn method_not_allowed() -> HttpResponse { from_template( hyper::StatusCode::METHOD_NOT_ALLOWED, "Used HTTP Method is not allowed. POST or OPTIONS is required\n", @@ -135,7 +134,7 @@ pub mod response { } /// Create a json response for oversized requests (413) - pub fn too_large(limit: u32) -> hyper::Response { + pub fn too_large(limit: u32) -> HttpResponse { let err = ResponsePayload::<()>::error(reject_too_big_request(limit)); let rp = Response::new(err, Id::Null); let error = serde_json::to_string(&rp).expect("JSON serialization infallible; qed"); @@ -144,7 +143,7 @@ pub mod response { } /// Create a json response for empty or malformed requests (400) - pub fn malformed() -> hyper::Response { + pub fn malformed() -> HttpResponse { let rp = Response::new(ResponsePayload::<()>::error(ErrorCode::ParseError), Id::Null); let error = serde_json::to_string(&rp).expect("JSON serialization infallible; qed"); @@ -154,10 +153,10 @@ pub mod response { /// Create a response body. fn from_template( status: hyper::StatusCode, - body: impl Into, + body: impl Into, content_type: &'static str, - ) -> hyper::Response { - hyper::Response::builder() + ) -> HttpResponse { + HttpResponse::builder() .status(status) .header("content-type", hyper::header::HeaderValue::from_static(content_type)) .body(body.into()) @@ -167,12 +166,12 @@ pub mod response { } /// Create a valid JSON response. - pub fn ok_response(body: impl Into) -> hyper::Response { + pub fn ok_response(body: impl Into) -> HttpResponse { from_template(hyper::StatusCode::OK, body, JSON) } /// Create a response for unsupported content type. - pub fn unsupported_content_type() -> hyper::Response { + pub fn unsupported_content_type() -> HttpResponse { from_template( hyper::StatusCode::UNSUPPORTED_MEDIA_TYPE, "Supplied content type is not allowed. Content-Type: application/json is required\n", @@ -181,12 +180,12 @@ pub mod response { } /// Create a response for when the server is busy and can't accept more requests. - pub fn too_many_requests() -> hyper::Response { + pub fn too_many_requests() -> HttpResponse { from_template(hyper::StatusCode::TOO_MANY_REQUESTS, "Too many connections. Please try again later.", TEXT) } /// Create a response for when the server denied the request. - pub fn denied() -> hyper::Response { - from_template(hyper::StatusCode::FORBIDDEN, ResponseBody::default(), TEXT) + pub fn denied() -> HttpResponse { + from_template(hyper::StatusCode::FORBIDDEN, HttpResponseBody::default(), TEXT) } } diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index fe9df78752..70c3b34343 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -4,7 +4,7 @@ use std::time::Instant; use crate::future::{IntervalStream, SessionClose}; use crate::middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}; use crate::server::{handle_rpc_call, ConnectionState, ServerConfig}; -use crate::{HttpRequest, PingConfig, ResponseBody, LOG_TARGET}; +use crate::{HttpRequest, HttpResponse, HttpResponseBody, PingConfig, LOG_TARGET}; use futures_util::future::{self, Either}; use futures_util::io::{BufReader, BufWriter}; @@ -406,7 +406,7 @@ pub async fn connect( methods: impl Into, conn: ConnectionState, rpc_middleware: RpcServiceBuilder, -) -> Result<(hyper::Response, impl Future), hyper::Response> +) -> Result<(HttpResponse, impl Future), HttpResponse> where L: for<'a> tower::Layer, >::Service: Send + Sync + 'static, @@ -420,7 +420,7 @@ where Ok(u) => u, Err(e) => { tracing::debug!(target: LOG_TARGET, "WS upgrade handshake failed: {}", e); - return Err(hyper::Response::new(ResponseBody::from(format!("WS upgrade handshake failed {e}")))); + return Err(HttpResponse::new(HttpResponseBody::from(format!("WS upgrade handshake failed {e}")))); } }; @@ -470,11 +470,11 @@ where background_task(params).await; }; - Ok((response.map(|()| ResponseBody::default()), fut)) // empty body here + Ok((response.map(|()| HttpResponseBody::default()), fut)) // empty body here } Err(e) => { tracing::debug!(target: LOG_TARGET, "WS upgrade handshake failed: {}", e); - Err(hyper::Response::new(ResponseBody::from(format!("WS upgrade handshake failed: {e}")))) + Err(HttpResponse::new(HttpResponseBody::from(format!("WS upgrade handshake failed: {e}")))) } } } diff --git a/server/src/utils.rs b/server/src/utils.rs new file mode 100644 index 0000000000..cfaaf23576 --- /dev/null +++ b/server/src/utils.rs @@ -0,0 +1,135 @@ +// 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. + +use std::error::Error as StdError; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use http_body::Frame; +use http_body_util::BodyExt; +use hyper::body::Bytes; +use pin_project::pin_project; +use tower::util::Oneshot; +use tower::ServiceExt; + +/// HTTP request type. +pub type HttpRequest = hyper::Request; + +/// HTTP response body. +pub type HttpResponseBody = http_body_util::Full; + +/// HTTP response type. +pub type HttpResponse = hyper::Response; + +/// A HTTP request body. +#[derive(Debug, Default)] +pub struct HttpBody(http_body_util::combinators::BoxBody>); + +impl HttpBody { + /// Create an empty body. + pub fn empty() -> Self { + Self::default() + } + + /// Create a new body. + pub fn new(body: B) -> Self + where + B: http_body::Body + Send + Sync + 'static, + B::Data: Send + 'static, + B::Error: Into>, + { + Self(body.map_err(|e| e.into()).boxed()) + } +} + +impl http_body::Body for HttpBody { + type Data = Bytes; + type Error = Box; + + #[inline] + fn poll_frame( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + Pin::new(&mut self.0).poll_frame(cx) + } + + #[inline] + fn size_hint(&self) -> http_body::SizeHint { + self.0.size_hint() + } + + #[inline] + fn is_end_stream(&self) -> bool { + self.0.is_end_stream() + } +} + +#[derive(Debug, Copy, Clone)] +pub(crate) struct TowerToHyperService { + service: S, +} + +impl TowerToHyperService { + pub(crate) fn new(service: S) -> Self { + Self { service } + } +} + +impl hyper::service::Service> for TowerToHyperService +where + S: tower::Service + Clone, +{ + type Response = S::Response; + type Error = S::Error; + type Future = TowerToHyperServiceFuture; + + fn call(&self, req: hyper::Request) -> Self::Future { + let req = req.map(HttpBody::new); + TowerToHyperServiceFuture { future: self.service.clone().oneshot(req) } + } +} + +#[pin_project] +pub(crate) struct TowerToHyperServiceFuture +where + S: tower::Service, +{ + #[pin] + future: Oneshot, +} + +impl std::future::Future for TowerToHyperServiceFuture +where + S: tower::Service, +{ + type Output = Result; + + #[inline] + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.project().future.poll(cx) + } +} diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index 60235e046e..66d362db36 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -9,7 +9,7 @@ publish = false [dependencies] futures-channel = "0.3.14" futures-util = "0.3.14" -hyper = { version = "1.3.0", features = [] } +hyper = { version = "1.3.1", features = [] } hyper-util = { version = "0.1", features = ["server-auto", "tokio", "client-legacy"] } http-body-util = "0.1.0" tracing = "0.1.34" From 58b725c47402bb216bde7bc03f655a811f8705a7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 16 May 2024 10:04:03 +0200 Subject: [PATCH 05/22] Update client/http-client/Cargo.toml --- client/http-client/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/http-client/Cargo.toml b/client/http-client/Cargo.toml index 2b196a9112..c34a20a519 100644 --- a/client/http-client/Cargo.toml +++ b/client/http-client/Cargo.toml @@ -17,7 +17,7 @@ publish = true async-trait = "0.1" hyper = { version = "1.3", features = ["client", "http1", "http2"] } hyper-rustls = { version = "0.27.1", optional = true, default-features = false, features = ["http1", "http2", "tls12", "logging"] } -hyper-util = { version = "*", features = ["client", "client-legacy"] } +hyper-util = { version = "0.1.1", features = ["client", "client-legacy"] } http-body = "1" http-body-util = "0.1.1" jsonrpsee-types = { workspace = true } From efc92be4e8e37ed7b97d086000344f56b6ecfc8e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 16 May 2024 10:45:59 +0200 Subject: [PATCH 06/22] make all examples compile --- client/http-client/src/client.rs | 18 ++---- client/http-client/src/lib.rs | 7 ++- client/http-client/src/transport.rs | 16 ++--- core/Cargo.toml | 8 ++- core/src/http_helpers.rs | 91 +++++++++++++++++++++++----- examples/examples/http.rs | 6 +- examples/examples/http_middleware.rs | 8 +-- server/src/lib.rs | 4 +- server/src/server.rs | 2 +- server/src/transport/http.rs | 2 +- server/src/utils.rs | 63 ++----------------- 11 files changed, 115 insertions(+), 110 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 0ad17df6c1..d604636799 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -32,7 +32,7 @@ use std::time::Duration; use crate::transport::{self, Error as TransportError, HttpTransportClient, HttpTransportClientBuilder}; use crate::types::{NotificationSer, RequestSer, Response}; -use crate::ResponseBody; +use crate::{HttpBody, HttpRequest}; use async_trait::async_trait; use hyper::http::HeaderMap; use jsonrpsee_core::client::{ @@ -188,7 +188,7 @@ impl HttpClientBuilder { impl HttpClientBuilder where L: Layer, - S: Service, Response = hyper::Response, Error = TransportError> + Clone, + S: Service, Response = hyper::Response, Error = TransportError> + Clone, B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into>, @@ -273,11 +273,8 @@ impl HttpClient { #[async_trait] impl ClientT for HttpClient where - S: Service, Response = hyper::Response, Error = TransportError> - + Send - + Sync - + Clone, - >>::Future: Send, + S: Service, Response = hyper::Response, Error = TransportError> + Send + Sync + Clone, + >>::Future: Send, B: http_body::Body + Send + Unpin + 'static, B::Error: Into>, B::Data: Send, @@ -409,11 +406,8 @@ where #[async_trait] impl SubscriptionClientT for HttpClient where - S: Service, Response = hyper::Response, Error = TransportError> - + Send - + Sync - + Clone, - >>::Future: Send, + S: Service, Response = hyper::Response, Error = TransportError> + Send + Sync + Clone, + >>::Future: Send, B: http_body::Body + Send + Unpin + 'static, B::Data: Send, B::Error: Into>, diff --git a/client/http-client/src/lib.rs b/client/http-client/src/lib.rs index 852a9fa2ba..cc76d3ac16 100644 --- a/client/http-client/src/lib.rs +++ b/client/http-client/src/lib.rs @@ -47,4 +47,9 @@ pub use client::{HttpClient, HttpClientBuilder}; pub use hyper::http::{HeaderMap, HeaderValue}; pub use jsonrpsee_types as types; -pub(crate) type ResponseBody = http_body_util::Full; +/// Default HTTP body for the client. +pub type HttpBody = http_body_util::Full; +/// HTTP request with default body. +pub type HttpRequest = jsonrpsee_core::http_helpers::Request; +/// HTTP response with default body. +pub type HttpResponse = jsonrpsee_core::http_helpers::Response; diff --git a/client/http-client/src/transport.rs b/client/http-client/src/transport.rs index 058c69e7f2..b719cc2345 100644 --- a/client/http-client/src/transport.rs +++ b/client/http-client/src/transport.rs @@ -25,13 +25,13 @@ use tower::layer::util::Identity; use tower::{Layer, Service, ServiceExt}; use url::Url; -use crate::ResponseBody; +use crate::{HttpBody, HttpRequest, HttpResponse}; const CONTENT_TYPE_JSON: &str = "application/json"; /// Wrapper over HTTP transport and connector. #[derive(Debug)] -pub enum HttpBackend { +pub enum HttpBackend { /// Hyper client with https connector. #[cfg(feature = "__tls")] Https(Client, B>), @@ -49,13 +49,13 @@ impl Clone for HttpBackend { } } -impl tower::Service> for HttpBackend +impl tower::Service> for HttpBackend where B: http_body::Body + Send + 'static + Unpin, B::Data: Send, B::Error: Into>, { - type Response = hyper::Response; + type Response = HttpResponse; type Error = Error; type Future = Pin> + Send>>; @@ -68,7 +68,7 @@ where .map_err(|e| Error::Http(HttpError::Stream(e.into()))) } - fn call(&mut self, req: hyper::Request) -> Self::Future { + fn call(&mut self, req: HttpRequest) -> Self::Future { let resp = match self { Self::Http(inner) => inner.call(req), #[cfg(feature = "__tls")] @@ -181,7 +181,7 @@ impl HttpTransportClientBuilder { pub fn build(self, target: impl AsRef) -> Result, Error> where L: Layer, - S: Service, Response = hyper::Response, Error = Error> + Clone, + S: Service, Response = HttpResponse, Error = Error> + Clone, B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into>, @@ -283,7 +283,7 @@ pub struct HttpTransportClient { impl HttpTransportClient where - S: Service, Response = hyper::Response, Error = Error> + Clone, + S: Service, Error = Error> + Clone, B: http_body::Body + Send + Unpin + 'static, B::Data: Send, B::Error: Into>, @@ -293,7 +293,7 @@ where return Err(Error::RequestTooLarge); } - let mut req = hyper::Request::post(&self.target); + let mut req = HttpRequest::post(&self.target); if let Some(headers) = req.headers_mut() { *headers = self.headers.clone(); } diff --git a/core/Cargo.toml b/core/Cargo.toml index 0f5b918b93..7d1b329889 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -25,8 +25,10 @@ tracing = "0.1.34" # optional deps futures-util = { version = "0.3.14", default-features = false, optional = true } -hyper = { version = "1.3", default-features = false, optional = true } -http-body-util = "0.1.1" +http = { version = "1.1", default-features = false, optional = true } +bytes = { version = "1.6", optional = true } +http-body = { version = "1", optional = true } +http-body-util = { version = "0.1.1", optional = true } rustc-hash = { version = "1", optional = true } rand = { version = "0.8", optional = true } parking_lot = { version = "0.12", optional = true } @@ -38,7 +40,7 @@ pin-project = { version = "1", optional = true } [features] default = [] -http-helpers = ["hyper", "futures-util"] +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"] client = ["futures-util/sink", "tokio/sync"] async-client = [ diff --git a/core/src/http_helpers.rs b/core/src/http_helpers.rs index a37b5bd2cc..7847641720 100644 --- a/core/src/http_helpers.rs +++ b/core/src/http_helpers.rs @@ -24,10 +24,69 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! Utility methods relying on hyper +//! Shared HTTP utilities. +use bytes::{Buf, Bytes}; +use http_body::Frame; use http_body_util::{BodyExt, Limited}; -use hyper::body::{Body, Buf}; +use std::{ + error::Error as StdError, + pin::Pin, + task::{Context, Poll}, +}; + +/// HTTP request type. +pub type Request = http::Request; + +/// HTTP response body. +pub type ResponseBody = http_body_util::Full; + +/// HTTP response type. +pub type Response = http::Response; + +/// A HTTP request body. +#[derive(Debug, Default)] +pub struct Body(http_body_util::combinators::BoxBody>); + +impl Body { + /// Create an empty body. + pub fn empty() -> Self { + Self::default() + } + + /// Create a new body. + pub fn new(body: B) -> Self + where + B: http_body::Body + Send + Sync + 'static, + B::Data: Send + 'static, + B::Error: Into>, + { + Self(body.map_err(|e| e.into()).boxed()) + } +} + +impl http_body::Body for Body { + type Data = Bytes; + type Error = Box; + + #[inline] + fn poll_frame( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + Pin::new(&mut self.0).poll_frame(cx) + } + + #[inline] + fn size_hint(&self) -> http_body::SizeHint { + self.0.size_hint() + } + + #[inline] + fn is_end_stream(&self) -> bool { + self.0.is_end_stream() + } +} /// Represents error that can when reading with a HTTP body. #[derive(Debug, thiserror::Error)] @@ -48,9 +107,9 @@ pub enum HttpError { /// Returns `Ok((bytes, single))` if the body was in valid size range; and a bool indicating whether the JSON-RPC /// request is a single or a batch. /// Returns `Err` if the body was too large or the body couldn't be read. -pub async fn read_body(headers: &hyper::HeaderMap, body: B, max_body_size: u32) -> Result<(Vec, bool), HttpError> +pub async fn read_body(headers: &http::HeaderMap, body: B, max_body_size: u32) -> Result<(Vec, bool), HttpError> where - B: Body + Send + 'static, + B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into>, { @@ -117,14 +176,14 @@ where /// /// NOTE: There's no specific hard limit on `Content_length` in HTTP specification. /// Thus this method might reject valid `content_length` -fn read_header_content_length(headers: &hyper::header::HeaderMap) -> Option { - let length = read_header_value(headers, hyper::header::CONTENT_LENGTH)?; +fn read_header_content_length(headers: &http::header::HeaderMap) -> Option { + let length = read_header_value(headers, http::header::CONTENT_LENGTH)?; // HTTP Content-Length indicates number of bytes in decimal. length.parse::().ok() } /// Returns a string value when there is exactly one value for the given header. -pub fn read_header_value(headers: &hyper::header::HeaderMap, header_name: hyper::header::HeaderName) -> Option<&str> { +pub fn read_header_value(headers: &http::header::HeaderMap, header_name: http::header::HeaderName) -> Option<&str> { let mut values = headers.get_all(header_name).iter(); let val = values.next()?; if values.next().is_none() { @@ -136,9 +195,9 @@ pub fn read_header_value(headers: &hyper::header::HeaderMap, header_name: hyper: /// Returns an iterator of all values for a given a header name pub fn read_header_values<'a>( - headers: &'a hyper::header::HeaderMap, + headers: &'a http::header::HeaderMap, header_name: &str, -) -> hyper::header::GetAll<'a, hyper::header::HeaderValue> { +) -> http::header::GetAll<'a, http::header::HeaderValue> { headers.get_all(header_name) } @@ -147,11 +206,11 @@ mod tests { use super::{read_body, read_header_content_length, HttpError}; use http_body_util::BodyExt; - type Body = http_body_util::Full; + type Body = http_body_util::Full; #[tokio::test] async fn body_to_bytes_size_limit_works() { - let headers = hyper::header::HeaderMap::new(); + let headers = http::header::HeaderMap::new(); let full_body = Body::from(vec![0; 128]); let body = full_body.map_err(|e| HttpError::Stream(e.into())); assert!(read_body(&headers, body, 127).await.is_err()); @@ -159,18 +218,18 @@ mod tests { #[test] fn read_content_length_works() { - let mut headers = hyper::header::HeaderMap::new(); - headers.insert(hyper::header::CONTENT_LENGTH, "177".parse().unwrap()); + let mut headers = http::header::HeaderMap::new(); + headers.insert(http::header::CONTENT_LENGTH, "177".parse().unwrap()); assert_eq!(read_header_content_length(&headers), Some(177)); - headers.append(hyper::header::CONTENT_LENGTH, "999".parse().unwrap()); + headers.append(http::header::CONTENT_LENGTH, "999".parse().unwrap()); assert_eq!(read_header_content_length(&headers), None); } #[test] fn read_content_length_too_big_value() { - let mut headers = hyper::header::HeaderMap::new(); - headers.insert(hyper::header::CONTENT_LENGTH, "18446744073709551616".parse().unwrap()); + let mut headers = http::header::HeaderMap::new(); + headers.insert(http::header::CONTENT_LENGTH, "18446744073709551616".parse().unwrap()); assert_eq!(read_header_content_length(&headers), None); } } diff --git a/examples/examples/http.rs b/examples/examples/http.rs index 229debdfd3..69e24337c6 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -29,15 +29,13 @@ use std::time::Duration; use hyper::body::Bytes; use jsonrpsee::core::client::ClientT; -use jsonrpsee::http_client::HttpClientBuilder; +use jsonrpsee::http_client::{HttpClientBuilder, HttpRequest}; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, Server}; use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}; use tower_http::LatencyUnit; use tracing_subscriber::util::SubscriberInitExt; -type ResponseBody = http_body_util::Full; - #[tokio::main] async fn main() -> anyhow::Result<()> { let filter = tracing_subscriber::EnvFilter::try_from_default_env()? @@ -51,7 +49,7 @@ async fn main() -> anyhow::Result<()> { .layer( TraceLayer::new_for_http() .on_request( - |request: &hyper::Request, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), + |request: &HttpRequest, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), ) .on_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| { tracing::info!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk") diff --git a/examples/examples/http_middleware.rs b/examples/examples/http_middleware.rs index 31156a2f9f..9966d99af5 100644 --- a/examples/examples/http_middleware.rs +++ b/examples/examples/http_middleware.rs @@ -47,7 +47,7 @@ use tower_http::LatencyUnit; use jsonrpsee::core::client::ClientT; use jsonrpsee::http_client::HttpClientBuilder; -use jsonrpsee::server::{RpcModule, Server}; +use jsonrpsee::server::{HttpRequest, RpcModule, Server}; use jsonrpsee::ws_client::WsClientBuilder; #[tokio::main] @@ -95,9 +95,9 @@ async fn run_server() -> anyhow::Result { // Add high level tracing/logging to all requests .layer( TraceLayer::new_for_http() - /*.on_request( - |request, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), - )*/ + .on_request( + |request: &HttpRequest<_>, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), + ) .on_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| { tracing::info!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk") }) diff --git a/server/src/lib.rs b/server/src/lib.rs index c733eec135..b96a59f7bd 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -53,7 +53,9 @@ pub use server::{ }; pub use tracing; -pub use crate::utils::{HttpBody, HttpRequest, HttpResponse, HttpResponseBody}; +pub use jsonrpsee_core::http_helpers::{ + Body as HttpBody, Request as HttpRequest, Response as HttpResponse, ResponseBody as HttpResponseBody, +}; pub use transport::http; pub use transport::ws; diff --git a/server/src/server.rs b/server/src/server.rs index d560d493a8..63933c3df1 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -976,7 +976,7 @@ where Poll::Ready(Ok(())) } - fn call(&mut self, request: hyper::Request) -> Self::Future { + fn call(&mut self, request: HttpRequest) -> Self::Future { Box::pin(self.http_middleware.service(self.rpc_middleware.clone()).call(request)) } } diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index 92041b9f80..1be9c4fcb8 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -11,7 +11,7 @@ use jsonrpsee_core::{ }; /// Checks that content type of received request is valid for JSON-RPC. -pub fn content_type_is_json(request: &hyper::Request) -> bool { +pub fn content_type_is_json(request: &HttpRequest) -> bool { is_json(request.headers().get(hyper::header::CONTENT_TYPE)) } diff --git a/server/src/utils.rs b/server/src/utils.rs index cfaaf23576..93214e5a70 100644 --- a/server/src/utils.rs +++ b/server/src/utils.rs @@ -24,70 +24,15 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::error::Error as StdError; use std::pin::Pin; use std::task::{Context, Poll}; -use http_body::Frame; -use http_body_util::BodyExt; -use hyper::body::Bytes; +use crate::{HttpBody, HttpRequest}; + use pin_project::pin_project; use tower::util::Oneshot; use tower::ServiceExt; -/// HTTP request type. -pub type HttpRequest = hyper::Request; - -/// HTTP response body. -pub type HttpResponseBody = http_body_util::Full; - -/// HTTP response type. -pub type HttpResponse = hyper::Response; - -/// A HTTP request body. -#[derive(Debug, Default)] -pub struct HttpBody(http_body_util::combinators::BoxBody>); - -impl HttpBody { - /// Create an empty body. - pub fn empty() -> Self { - Self::default() - } - - /// Create a new body. - pub fn new(body: B) -> Self - where - B: http_body::Body + Send + Sync + 'static, - B::Data: Send + 'static, - B::Error: Into>, - { - Self(body.map_err(|e| e.into()).boxed()) - } -} - -impl http_body::Body for HttpBody { - type Data = Bytes; - type Error = Box; - - #[inline] - fn poll_frame( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll, Self::Error>>> { - Pin::new(&mut self.0).poll_frame(cx) - } - - #[inline] - fn size_hint(&self) -> http_body::SizeHint { - self.0.size_hint() - } - - #[inline] - fn is_end_stream(&self) -> bool { - self.0.is_end_stream() - } -} - #[derive(Debug, Copy, Clone)] pub(crate) struct TowerToHyperService { service: S, @@ -99,7 +44,7 @@ impl TowerToHyperService { } } -impl hyper::service::Service> for TowerToHyperService +impl hyper::service::Service> for TowerToHyperService where S: tower::Service + Clone, { @@ -107,7 +52,7 @@ where type Error = S::Error; type Future = TowerToHyperServiceFuture; - fn call(&self, req: hyper::Request) -> Self::Future { + fn call(&self, req: HttpRequest) -> Self::Future { let req = req.map(HttpBody::new); TowerToHyperServiceFuture { future: self.service.clone().oneshot(req) } } From 103add0472619ea4137e4e5714dd67f155c76b59 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 16 May 2024 10:49:34 +0200 Subject: [PATCH 07/22] remove nits --- proc-macros/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proc-macros/Cargo.toml b/proc-macros/Cargo.toml index 6b6460cc43..12f574719a 100644 --- a/proc-macros/Cargo.toml +++ b/proc-macros/Cargo.toml @@ -24,8 +24,7 @@ proc-macro-crate = "3" heck = "0.5.0" [dev-dependencies] -# jsonrpsee = { path = "../jsonrpsee", features = ["server", "client-core", "http-client", "ws-client", "macros"] } -jsonrpsee = { path = "../jsonrpsee", features = ["server", "client-core", "ws-client", "macros"] } +jsonrpsee = { path = "../jsonrpsee", features = ["server", "client-core", "http-client", "ws-client", "macros"] } trybuild = "1.0" tokio = { version = "1.16", features = ["rt", "macros"] } futures-channel = { version = "0.3.14", default-features = false } From 0d9be0035f19017b043c1e8319528daca455c0c1 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 16 May 2024 15:18:14 +0200 Subject: [PATCH 08/22] unify http request --- client/http-client/src/client.rs | 19 +++++++++-------- client/http-client/src/transport.rs | 13 ++++++------ core/src/http_helpers.rs | 4 ++-- examples/examples/http.rs | 4 ++-- examples/examples/http_middleware.rs | 4 ++-- server/src/middleware/http/authority.rs | 21 ++++++++----------- server/src/middleware/http/host_filter.rs | 10 ++++++--- .../src/middleware/http/proxy_get_request.rs | 16 +++++++++----- server/src/server.rs | 6 +++--- server/src/transport/http.rs | 16 +++++++++----- server/src/transport/ws.rs | 10 ++++----- 11 files changed, 69 insertions(+), 54 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index d604636799..880c8052a6 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -32,8 +32,9 @@ use std::time::Duration; use crate::transport::{self, Error as TransportError, HttpTransportClient, HttpTransportClientBuilder}; use crate::types::{NotificationSer, RequestSer, Response}; -use crate::{HttpBody, HttpRequest}; +use crate::{HttpRequest, HttpResponse}; use async_trait::async_trait; +use hyper::body::Bytes; use hyper::http::HeaderMap; use jsonrpsee_core::client::{ generate_batch_id_range, BatchResponse, CertificateStore, ClientT, Error, IdKind, RequestIdManager, Subscription, @@ -188,8 +189,8 @@ impl HttpClientBuilder { impl HttpClientBuilder where L: Layer, - S: Service, Response = hyper::Response, Error = TransportError> + Clone, - B: http_body::Body + Send + 'static, + S: Service, Error = TransportError> + Clone, + B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into>, { @@ -273,9 +274,9 @@ impl HttpClient { #[async_trait] impl ClientT for HttpClient where - S: Service, Response = hyper::Response, Error = TransportError> + Send + Sync + Clone, - >>::Future: Send, - B: http_body::Body + Send + Unpin + 'static, + S: Service, Error = TransportError> + Send + Sync + Clone, + >::Future: Send, + B: http_body::Body + Send + Unpin + 'static, B::Error: Into>, B::Data: Send, { @@ -406,9 +407,9 @@ where #[async_trait] impl SubscriptionClientT for HttpClient where - S: Service, Response = hyper::Response, Error = TransportError> + Send + Sync + Clone, - >>::Future: Send, - B: http_body::Body + Send + Unpin + 'static, + S: Service, Error = TransportError> + Send + Sync + Clone, + >::Future: Send, + B: http_body::Body + Send + Unpin + 'static, B::Data: Send, B::Error: Into>, { diff --git a/client/http-client/src/transport.rs b/client/http-client/src/transport.rs index b719cc2345..33fcb69d12 100644 --- a/client/http-client/src/transport.rs +++ b/client/http-client/src/transport.rs @@ -6,6 +6,7 @@ // that we need to be guaranteed that hyper doesn't re-use an existing connection if we ever reset // the JSON-RPC request id to a value that might have already been used. +use hyper::body::Bytes; use hyper::http::{HeaderMap, HeaderValue}; use hyper_util::client::legacy::connect::HttpConnector; use hyper_util::client::legacy::Client; @@ -51,7 +52,7 @@ impl Clone for HttpBackend { impl tower::Service> for HttpBackend where - B: http_body::Body + Send + 'static + Unpin, + B: http_body::Body + Send + 'static + Unpin, B::Data: Send, B::Error: Into>, { @@ -181,8 +182,8 @@ impl HttpTransportClientBuilder { pub fn build(self, target: impl AsRef) -> Result, Error> where L: Layer, - S: Service, Response = HttpResponse, Error = Error> + Clone, - B: http_body::Body + Send + 'static, + S: Service, Error = Error> + Clone, + B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into>, { @@ -283,12 +284,12 @@ pub struct HttpTransportClient { impl HttpTransportClient where - S: Service, Error = Error> + Clone, - B: http_body::Body + Send + Unpin + 'static, + S: Service, Error = Error> + Clone, + B: http_body::Body + Send + Unpin + 'static, B::Data: Send, B::Error: Into>, { - async fn inner_send(&self, body: String) -> Result, Error> { + async fn inner_send(&self, body: String) -> Result, Error> { if body.len() > self.max_request_size as usize { return Err(Error::RequestTooLarge); } diff --git a/core/src/http_helpers.rs b/core/src/http_helpers.rs index 7847641720..717c291c8e 100644 --- a/core/src/http_helpers.rs +++ b/core/src/http_helpers.rs @@ -218,11 +218,11 @@ mod tests { #[test] fn read_content_length_works() { - let mut headers = http::header::HeaderMap::new(); + let mut headers = hyper::header::HeaderMap::new(); headers.insert(http::header::CONTENT_LENGTH, "177".parse().unwrap()); assert_eq!(read_header_content_length(&headers), Some(177)); - headers.append(http::header::CONTENT_LENGTH, "999".parse().unwrap()); + headers.append(hyper::header::CONTENT_LENGTH, "999".parse().unwrap()); assert_eq!(read_header_content_length(&headers), None); } diff --git a/examples/examples/http.rs b/examples/examples/http.rs index 69e24337c6..3bb56100fb 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::http_client::{HttpClientBuilder, HttpRequest}; +use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, Server}; use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}; @@ -49,7 +49,7 @@ async fn main() -> anyhow::Result<()> { .layer( TraceLayer::new_for_http() .on_request( - |request: &HttpRequest, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), + |request: &hyper::Request<_>, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), ) .on_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| { tracing::info!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk") diff --git a/examples/examples/http_middleware.rs b/examples/examples/http_middleware.rs index 9966d99af5..18fff4d3b2 100644 --- a/examples/examples/http_middleware.rs +++ b/examples/examples/http_middleware.rs @@ -47,7 +47,7 @@ use tower_http::LatencyUnit; use jsonrpsee::core::client::ClientT; use jsonrpsee::http_client::HttpClientBuilder; -use jsonrpsee::server::{HttpRequest, RpcModule, Server}; +use jsonrpsee::server::{RpcModule, Server}; use jsonrpsee::ws_client::WsClientBuilder; #[tokio::main] @@ -96,7 +96,7 @@ async fn run_server() -> anyhow::Result { .layer( TraceLayer::new_for_http() .on_request( - |request: &HttpRequest<_>, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), + |request: &hyper::Request<_>, _span: &tracing::Span| tracing::info!(request = ?request, "on_request"), ) .on_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| { tracing::info!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk") diff --git a/server/src/middleware/http/authority.rs b/server/src/middleware/http/authority.rs index 6ef4145be3..e12168f4b5 100644 --- a/server/src/middleware/http/authority.rs +++ b/server/src/middleware/http/authority.rs @@ -26,8 +26,8 @@ //! Utility and types related to the authority of an URI. +use crate::HttpRequest; use http::uri::{InvalidUri, Uri}; -use hyper::Request; use jsonrpsee_core::http_helpers; /// Represent the http URI scheme that is returned by the HTTP host header @@ -96,7 +96,7 @@ impl Authority { /// /// The `Authority` can be sent by the client in the `Host header` or in the `URI` /// such that both must be checked. - pub fn from_http_request(request: &Request) -> Option { + pub fn from_http_request(request: &HttpRequest) -> Option { // NOTE: we use our own `Authority type` here because an invalid port number would return `None` here // and that should be denied. let host_header = @@ -159,7 +159,7 @@ fn default_port(scheme: Option<&str>) -> Option { #[cfg(test)] mod tests { - use super::{Authority, Port}; + use super::{Authority, HttpRequest, Port}; use hyper::header::HOST; fn authority(host: &str, port: Port) -> Authority { @@ -201,19 +201,19 @@ mod tests { #[test] fn authority_from_http_only_host_works() { - let req = hyper::Request::builder().header(HOST, "example.com").body(EmptyBody::new()).unwrap(); + let req = HttpRequest::builder().header(HOST, "example.com").body(EmptyBody::new()).unwrap(); assert!(Authority::from_http_request(&req).is_some()); } #[test] fn authority_only_uri_works() { - let req = hyper::Request::builder().uri("example.com").body(EmptyBody::new()).unwrap(); + let req = HttpRequest::builder().uri("example.com").body(EmptyBody::new()).unwrap(); assert!(Authority::from_http_request(&req).is_some()); } #[test] fn authority_host_and_uri_works() { - let req = hyper::Request::builder() + let req = HttpRequest::builder() .header(HOST, "example.com:9999") .uri("example.com:9999") .body(EmptyBody::new()) @@ -223,17 +223,14 @@ mod tests { #[test] fn authority_host_and_uri_mismatch() { - let req = hyper::Request::builder() - .header(HOST, "example.com:9999") - .uri("example.com") - .body(EmptyBody::new()) - .unwrap(); + let req = + HttpRequest::builder().header(HOST, "example.com:9999").uri("example.com").body(EmptyBody::new()).unwrap(); assert!(Authority::from_http_request(&req).is_none()); } #[test] fn authority_missing_host_and_uri() { - let req = hyper::Request::builder().body(EmptyBody::new()).unwrap(); + let req = HttpRequest::builder().body(EmptyBody::new()).unwrap(); assert!(Authority::from_http_request(&req).is_none()); } } diff --git a/server/src/middleware/http/host_filter.rs b/server/src/middleware/http/host_filter.rs index a3e0fa1a7e..2c6c187b49 100644 --- a/server/src/middleware/http/host_filter.rs +++ b/server/src/middleware/http/host_filter.rs @@ -30,6 +30,7 @@ use crate::middleware::http::authority::{Authority, AuthorityError, Port}; use crate::transport::http; use crate::{HttpRequest, HttpResponseBody, LOG_TARGET}; use futures_util::{Future, FutureExt, TryFutureExt}; +use hyper::body::Bytes; use hyper::Response; use route_recognizer::Router; use std::collections::BTreeMap; @@ -98,12 +99,15 @@ pub struct HostFilter { filter: Option>, } -impl Service for HostFilter +impl Service> for HostFilter where - S: Service>, + S: Service, Response = Response>, S::Response: 'static, S::Error: Into> + 'static, S::Future: Send + 'static, + B: http_body::Body + Send + std::fmt::Debug + 'static, + B::Data: Send, + B::Error: Into>, { type Response = S::Response; type Error = Box; @@ -113,7 +117,7 @@ where self.inner.poll_ready(cx).map_err(Into::into) } - fn call(&mut self, request: HttpRequest) -> Self::Future { + fn call(&mut self, request: HttpRequest) -> Self::Future { let Some(authority) = Authority::from_http_request(&request) else { return async { Ok(http::response::malformed()) }.boxed(); }; diff --git a/server/src/middleware/http/proxy_get_request.rs b/server/src/middleware/http/proxy_get_request.rs index 9c5409f78f..bbdfee0d59 100644 --- a/server/src/middleware/http/proxy_get_request.rs +++ b/server/src/middleware/http/proxy_get_request.rs @@ -31,6 +31,7 @@ use crate::transport::http; use crate::{HttpBody, HttpRequest, HttpResponse}; use http_body_util::BodyExt; +use hyper::body::Bytes; use hyper::header::{ACCEPT, CONTENT_TYPE}; use hyper::http::HeaderValue; use hyper::{Method, Uri}; @@ -112,12 +113,15 @@ impl ProxyGetRequest { } } -impl Service for ProxyGetRequest +impl Service> for ProxyGetRequest where S: Service, S::Response: 'static, S::Error: Into> + 'static, S::Future: Send + 'static, + B: http_body::Body + Send + Sync + 'static, + B::Data: Send, + B::Error: Into>, { type Response = S::Response; type Error = Box; @@ -128,11 +132,11 @@ where self.inner.poll_ready(cx).map_err(Into::into) } - fn call(&mut self, mut req: HttpRequest) -> Self::Future { + fn call(&mut self, mut req: HttpRequest) -> Self::Future { let modify = self.path.as_ref() == req.uri() && req.method() == Method::GET; // Proxy the request to the appropriate method call. - if modify { + let req = if modify { // RPC methods are accessed with `POST`. *req.method_mut() = Method::POST; // Precautionary remove the URI. @@ -148,8 +152,10 @@ where let body = HttpBody::new(http_body_util::Full::new(bytes.into())); - req = req.map(|_| body); - } + req.map(|_| body) + } else { + req.map(HttpBody::new) + }; // Call the inner service and get a future that resolves to the response. let fut = self.inner.call(req); diff --git a/server/src/server.rs b/server/src/server.rs index 63933c3df1..aa41b0a3e7 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -104,7 +104,7 @@ where + Clone + Service, Error = Box<(dyn StdError + Send + Sync + 'static)>>, <>>::Service as Service>::Future: Send, - B: http_body::Body + Send + 'static, + B: http_body::Body + Send + 'static, ::Error: Send + Sync + StdError, ::Data: Send, { @@ -1012,7 +1012,7 @@ where Poll::Ready(Ok(())) } - fn call(&mut self, request: hyper::Request) -> Self::Future { + fn call(&mut self, request: HttpRequest) -> Self::Future { let request = request.map(HttpBody::new); let conn_guard = &self.inner.conn_guard; @@ -1155,7 +1155,7 @@ where >>::Service: Send + 'static + Clone - + Service, Error = Box<(dyn StdError + Send + Sync + 'static)>>, + + Service, Error = Box<(dyn StdError + Send + Sync + 'static)>>, <>>::Service as Service>::Future: Send + 'static, B: http_body::Body + Send + 'static, diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index 1be9c4fcb8..fa7dc6402a 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -4,7 +4,7 @@ use crate::{ BatchRequestConfig, ConnectionState, HttpRequest, HttpResponse, LOG_TARGET, }; use http::Method; -use hyper::body::Body; +use hyper::body::{Body, Bytes}; use jsonrpsee_core::{ http_helpers::{read_body, HttpError}, server::Methods, @@ -30,14 +30,17 @@ pub fn is_json(content_type: Option<&hyper::header::HeaderValue>) -> bool { /// Make JSON-RPC HTTP call with a [`RpcServiceBuilder`] /// /// Fails if the HTTP request was a malformed JSON-RPC request. -pub async fn call_with_service_builder( - request: HttpRequest, +pub async fn call_with_service_builder( + request: HttpRequest, server_cfg: ServerConfig, conn: ConnectionState, methods: impl Into, rpc_service: RpcServiceBuilder, ) -> HttpResponse where + B: http_body::Body + Send + 'static, + B::Data: Send, + B::Error: Into>, L: for<'a> tower::Layer, >::Service: Send + Sync + 'static, for<'a> >::Service: RpcServiceT<'a>, @@ -63,14 +66,17 @@ where /// Make JSON-RPC HTTP call with a service [`RpcServiceT`] /// /// Fails if the HTTP request was a malformed JSON-RPC request. -pub async fn call_with_service( - request: HttpRequest, +pub async fn call_with_service( + request: HttpRequest, batch_config: BatchRequestConfig, max_request_size: u32, rpc_service: S, max_response_size: u32, ) -> HttpResponse where + B: http_body::Body + Send + 'static, + B::Data: Send, + B::Error: Into>, for<'a> S: RpcServiceT<'a> + Send, { // Only the `POST` method is allowed. diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 70c3b34343..be9b76e2e9 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -368,17 +368,17 @@ async fn graceful_shutdown( /// to complete the HTTP request. /// /// ```no_run -/// use jsonrpsee_server::{ws, ServerConfig, Methods, ConnectionState}; +/// use jsonrpsee_server::{ws, ServerConfig, Methods, ConnectionState, HttpRequest, HttpResponse}; /// use jsonrpsee_server::middleware::rpc::{RpcServiceBuilder, RpcServiceT, RpcService}; /// /// async fn handle_websocket_conn( -/// req: hyper::Request, +/// req: HttpRequest, /// server_cfg: ServerConfig, /// methods: impl Into + 'static, /// conn: ConnectionState, /// rpc_middleware: RpcServiceBuilder, /// mut disconnect: tokio::sync::mpsc::Receiver<()> -/// ) -> hyper::Response +/// ) -> HttpResponse /// where /// L: for<'a> tower::Layer + 'static, /// >::Service: Send + Sync + 'static, @@ -400,8 +400,8 @@ async fn graceful_shutdown( /// } /// } /// ``` -pub async fn connect( - req: HttpRequest, +pub async fn connect( + req: HttpRequest, server_cfg: ServerConfig, methods: impl Into, conn: ConnectionState, From 3232128a3bd286a7170581dfbd2a8a2b40e7f1d7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 17 May 2024 09:32:35 +0200 Subject: [PATCH 09/22] introduce BoxError --- client/http-client/src/client.rs | 9 ++++----- client/http-client/src/transport.rs | 8 ++++---- core/src/http_helpers.rs | 12 +++++++----- core/src/lib.rs | 3 +++ examples/examples/jsonrpsee_as_service.rs | 12 +++++++----- server/src/middleware/http/host_filter.rs | 5 +++-- server/src/middleware/http/proxy_get_request.rs | 5 +++-- server/src/server.rs | 6 +++--- server/src/tests/helpers.rs | 14 ++------------ server/src/transport/http.rs | 5 +++-- 10 files changed, 39 insertions(+), 40 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 880c8052a6..f38ad6a220 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -25,7 +25,6 @@ // DEALINGS IN THE SOFTWARE. use std::borrow::Cow as StdCow; -use std::error::Error as StdError; use std::fmt; use std::sync::Arc; use std::time::Duration; @@ -42,7 +41,7 @@ use jsonrpsee_core::client::{ }; use jsonrpsee_core::params::BatchRequestBuilder; use jsonrpsee_core::traits::ToRpcParams; -use jsonrpsee_core::{JsonRawValue, TEN_MB_SIZE_BYTES}; +use jsonrpsee_core::{BoxError, JsonRawValue, TEN_MB_SIZE_BYTES}; use jsonrpsee_types::{ErrorObject, InvalidRequestId, ResponseSuccess, TwoPointZero}; use serde::de::DeserializeOwned; use tower::layer::util::Identity; @@ -192,7 +191,7 @@ where S: Service, Error = TransportError> + Clone, B: http_body::Body + Send + 'static, B::Data: Send, - B::Error: Into>, + B::Error: Into, { /// Build the HTTP client with target to connect to. pub fn build(self, target: impl AsRef) -> Result, Error> { @@ -277,7 +276,7 @@ where S: Service, Error = TransportError> + Send + Sync + Clone, >::Future: Send, B: http_body::Body + Send + Unpin + 'static, - B::Error: Into>, + B::Error: Into, B::Data: Send, { #[instrument(name = "notification", skip(self, params), level = "trace")] @@ -411,7 +410,7 @@ where >::Future: Send, B: http_body::Body + Send + Unpin + 'static, B::Data: Send, - B::Error: Into>, + B::Error: Into, { /// Send a subscription request to the server. Not implemented for HTTP; will always return /// [`Error::HttpNotImplemented`]. diff --git a/client/http-client/src/transport.rs b/client/http-client/src/transport.rs index 33fcb69d12..56eca20337 100644 --- a/client/http-client/src/transport.rs +++ b/client/http-client/src/transport.rs @@ -13,11 +13,11 @@ use hyper_util::client::legacy::Client; use hyper_util::rt::TokioExecutor; use jsonrpsee_core::client::CertificateStore; use jsonrpsee_core::tracing::client::{rx_log_from_bytes, tx_log_from_str}; +use jsonrpsee_core::BoxError; use jsonrpsee_core::{ http_helpers::{self, HttpError}, TEN_MB_SIZE_BYTES, }; -use std::error::Error as StdError; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; @@ -54,7 +54,7 @@ impl tower::Service> for HttpBackend where B: http_body::Body + Send + 'static + Unpin, B::Data: Send, - B::Error: Into>, + B::Error: Into, { type Response = HttpResponse; type Error = Error; @@ -185,7 +185,7 @@ impl HttpTransportClientBuilder { S: Service, Error = Error> + Clone, B: http_body::Body + Send + 'static, B::Data: Send, - B::Error: Into>, + B::Error: Into, { let Self { certificate_store, @@ -287,7 +287,7 @@ where S: Service, Error = Error> + Clone, B: http_body::Body + Send + Unpin + 'static, B::Data: Send, - B::Error: Into>, + B::Error: Into, { async fn inner_send(&self, body: String) -> Result, Error> { if body.len() > self.max_request_size as usize { diff --git a/core/src/http_helpers.rs b/core/src/http_helpers.rs index 717c291c8e..1e3091d2ac 100644 --- a/core/src/http_helpers.rs +++ b/core/src/http_helpers.rs @@ -35,6 +35,8 @@ use std::{ task::{Context, Poll}, }; +use crate::BoxError; + /// HTTP request type. pub type Request = http::Request; @@ -46,7 +48,7 @@ pub type Response = http::Response; /// A HTTP request body. #[derive(Debug, Default)] -pub struct Body(http_body_util::combinators::BoxBody>); +pub struct Body(http_body_util::combinators::BoxBody); impl Body { /// Create an empty body. @@ -59,7 +61,7 @@ impl Body { where B: http_body::Body + Send + Sync + 'static, B::Data: Send + 'static, - B::Error: Into>, + B::Error: Into, { Self(body.map_err(|e| e.into()).boxed()) } @@ -111,7 +113,7 @@ pub async fn read_body(headers: &http::HeaderMap, body: B, max_body_size: u32 where B: http_body::Body + Send + 'static, B::Data: Send, - B::Error: Into>, + B::Error: Into, { // NOTE(niklasad1): Values bigger than `u32::MAX` will be turned into zero here. This is unlikely to occur in // practice and in that case we fallback to allocating in the while-loop below instead of pre-allocating. @@ -218,11 +220,11 @@ mod tests { #[test] fn read_content_length_works() { - let mut headers = hyper::header::HeaderMap::new(); + let mut headers = http::header::HeaderMap::new(); headers.insert(http::header::CONTENT_LENGTH, "177".parse().unwrap()); assert_eq!(read_header_content_length(&headers), Some(177)); - headers.append(hyper::header::CONTENT_LENGTH, "999".parse().unwrap()); + headers.append(http::header::CONTENT_LENGTH, "999".parse().unwrap()); assert_eq!(read_header_content_length(&headers), None); } diff --git a/core/src/lib.rs b/core/src/lib.rs index 4c5ed868fa..b7d03908c1 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -94,3 +94,6 @@ pub const TEN_MB_SIZE_BYTES: u32 = 10 * 1024 * 1024; /// The return type if the subscription wants to return `Result`. pub type SubscriptionResult = Result<(), StringError>; + +/// Type erased error. +pub type BoxError = Box; diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index cf22d1f65c..9e256c2f4e 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -31,7 +31,6 @@ //! 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::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -236,8 +235,9 @@ async fn run_server(metrics: Metrics) -> anyhow::Result { async move { tracing::info!("Opened WebSocket connection"); metrics.opened_ws_connections.fetch_add(1, Ordering::Relaxed); - let rp = svc.call(req).await.unwrap(); - Ok::<_, Infallible>(rp) + // 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. + svc.call(req).await.map_err(|e| anyhow::anyhow!("{:?}", e)) } .boxed() } else { @@ -252,8 +252,10 @@ async fn run_server(metrics: Metrics) -> anyhow::Result { } tracing::info!("Closed HTTP connection"); - // TODO: fix weird lifetime error. - Ok::<_, Infallible>(rp.unwrap()) + + // 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. + rp.map_err(|e| anyhow::anyhow!("{:?}", e)) } .boxed() } diff --git a/server/src/middleware/http/host_filter.rs b/server/src/middleware/http/host_filter.rs index 2c6c187b49..bb1010bc1d 100644 --- a/server/src/middleware/http/host_filter.rs +++ b/server/src/middleware/http/host_filter.rs @@ -32,6 +32,7 @@ use crate::{HttpRequest, HttpResponseBody, LOG_TARGET}; use futures_util::{Future, FutureExt, TryFutureExt}; use hyper::body::Bytes; use hyper::Response; +use jsonrpsee_core::BoxError; use route_recognizer::Router; use std::collections::BTreeMap; use std::error::Error as StdError; @@ -103,11 +104,11 @@ impl Service> for HostFilter where S: Service, Response = Response>, S::Response: 'static, - S::Error: Into> + 'static, + S::Error: Into + 'static, S::Future: Send + 'static, B: http_body::Body + Send + std::fmt::Debug + 'static, B::Data: Send, - B::Error: Into>, + B::Error: Into, { type Response = S::Response; type Error = Box; diff --git a/server/src/middleware/http/proxy_get_request.rs b/server/src/middleware/http/proxy_get_request.rs index bbdfee0d59..46aa229083 100644 --- a/server/src/middleware/http/proxy_get_request.rs +++ b/server/src/middleware/http/proxy_get_request.rs @@ -35,6 +35,7 @@ use hyper::body::Bytes; use hyper::header::{ACCEPT, CONTENT_TYPE}; use hyper::http::HeaderValue; use hyper::{Method, Uri}; +use jsonrpsee_core::BoxError; use jsonrpsee_types::{Id, RequestSer}; use std::error::Error; use std::future::Future; @@ -117,11 +118,11 @@ impl Service> for ProxyGetRequest where S: Service, S::Response: 'static, - S::Error: Into> + 'static, + S::Error: Into + 'static, S::Future: Send + 'static, B: http_body::Body + Send + Sync + 'static, B::Data: Send, - B::Error: Into>, + B::Error: Into, { type Response = S::Response; type Error = Box; diff --git a/server/src/server.rs b/server/src/server.rs index aa41b0a3e7..d5e472a088 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -48,7 +48,7 @@ use jsonrpsee_core::id_providers::RandomIntegerIdProvider; use jsonrpsee_core::server::helpers::prepare_error; use jsonrpsee_core::server::{BatchResponseBuilder, BoundedSubscriptions, MethodResponse, MethodSink, Methods}; use jsonrpsee_core::traits::IdProvider; -use jsonrpsee_core::{JsonRawValue, TEN_MB_SIZE_BYTES}; +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, @@ -966,7 +966,7 @@ where <>>::Service as Service>>::Future: Send + 'static, B: http_body::Body + Send + Sync + 'static, - B::Error: Into>, + B::Error: Into, { type Response = HttpResponse; type Error = Box; @@ -998,7 +998,7 @@ where >::Service: Send + Sync + 'static, for<'a> >::Service: RpcServiceT<'a>, B: http_body::Body + Send + Sync + 'static, - B::Error: Into>, + B::Error: Into, { type Response = HttpResponse; diff --git a/server/src/tests/helpers.rs b/server/src/tests/helpers.rs index a0114cebed..d3d4445eb3 100644 --- a/server/src/tests/helpers.rs +++ b/server/src/tests/helpers.rs @@ -1,4 +1,3 @@ -use std::convert::Infallible; use std::net::SocketAddr; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -258,19 +257,10 @@ pub(crate) async fn ws_server_with_stats(metrics: Metrics) -> SocketAddr { metrics.ws_sessions_closed.fetch_add(1, Ordering::SeqCst); }); - async move { - let rp = rpc_svc.call(req).await.unwrap(); - // TODO: fix the weird lifetime error here. - Ok::<_, Infallible>(rp) - } - .boxed() + async move { rpc_svc.call(req).await.map_err(|e| anyhow::anyhow!("{:?}", e)) }.boxed() } else { // HTTP. - async move { - let rp = rpc_svc.call(req).await.unwrap(); - Ok::<_, Infallible>(rp) - } - .boxed() + async move { rpc_svc.call(req).await.map_err(|e| anyhow::anyhow!("{:?}", e)) }.boxed() } }); diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index fa7dc6402a..7ee57167d5 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -8,6 +8,7 @@ use hyper::body::{Body, Bytes}; use jsonrpsee_core::{ http_helpers::{read_body, HttpError}, server::Methods, + BoxError, }; /// Checks that content type of received request is valid for JSON-RPC. @@ -40,7 +41,7 @@ pub async fn call_with_service_builder( where B: http_body::Body + Send + 'static, B::Data: Send, - B::Error: Into>, + B::Error: Into, L: for<'a> tower::Layer, >::Service: Send + Sync + 'static, for<'a> >::Service: RpcServiceT<'a>, @@ -76,7 +77,7 @@ pub async fn call_with_service( where B: http_body::Body + Send + 'static, B::Data: Send, - B::Error: Into>, + B::Error: Into, for<'a> S: RpcServiceT<'a> + Send, { // Only the `POST` method is allowed. From 642d964873d9ec55dcb25de58f86a39071016139 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 17 May 2024 09:40:38 +0200 Subject: [PATCH 10/22] more boxerror --- core/src/http_helpers.rs | 8 +++----- server/src/middleware/http/host_filter.rs | 3 +-- server/src/middleware/http/proxy_get_request.rs | 3 +-- server/src/server.rs | 4 ++-- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/core/src/http_helpers.rs b/core/src/http_helpers.rs index 1e3091d2ac..d609276c13 100644 --- a/core/src/http_helpers.rs +++ b/core/src/http_helpers.rs @@ -26,17 +26,15 @@ //! Shared HTTP utilities. +use crate::BoxError; use bytes::{Buf, Bytes}; use http_body::Frame; use http_body_util::{BodyExt, Limited}; use std::{ - error::Error as StdError, pin::Pin, task::{Context, Poll}, }; -use crate::BoxError; - /// HTTP request type. pub type Request = http::Request; @@ -69,7 +67,7 @@ impl Body { impl http_body::Body for Body { type Data = Bytes; - type Error = Box; + type Error = BoxError; #[inline] fn poll_frame( @@ -101,7 +99,7 @@ pub enum HttpError { Malformed, /// Represents error that can happen when dealing with HTTP streams. #[error("{0}")] - Stream(#[from] Box), + Stream(#[from] BoxError), } /// Read a data from [`hyper::body::HttpBody`] and return the data if it is valid JSON and within the allowed size range. diff --git a/server/src/middleware/http/host_filter.rs b/server/src/middleware/http/host_filter.rs index bb1010bc1d..0877b71de8 100644 --- a/server/src/middleware/http/host_filter.rs +++ b/server/src/middleware/http/host_filter.rs @@ -35,7 +35,6 @@ use hyper::Response; use jsonrpsee_core::BoxError; use route_recognizer::Router; use std::collections::BTreeMap; -use std::error::Error as StdError; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; @@ -111,7 +110,7 @@ where B::Error: Into, { type Response = S::Response; - type Error = Box; + type Error = BoxError; type Future = Pin> + Send + 'static>>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { diff --git a/server/src/middleware/http/proxy_get_request.rs b/server/src/middleware/http/proxy_get_request.rs index 46aa229083..533b88b89c 100644 --- a/server/src/middleware/http/proxy_get_request.rs +++ b/server/src/middleware/http/proxy_get_request.rs @@ -37,7 +37,6 @@ use hyper::http::HeaderValue; use hyper::{Method, Uri}; use jsonrpsee_core::BoxError; use jsonrpsee_types::{Id, RequestSer}; -use std::error::Error; use std::future::Future; use std::pin::Pin; use std::sync::Arc; @@ -125,7 +124,7 @@ where B::Error: Into, { type Response = S::Response; - type Error = Box; + type Error = BoxError; type Future = Pin> + Send + 'static>>; #[inline] diff --git a/server/src/server.rs b/server/src/server.rs index d5e472a088..d8ac79d510 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -969,7 +969,7 @@ where B::Error: Into, { type Response = HttpResponse; - type Error = Box; + type Error = BoxError; type Future = Pin> + Send>>; fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> Poll> { @@ -1004,7 +1004,7 @@ where // The following associated type is required by the `impl Server` bounds. // It satisfies the server's bounds when the `tower::ServiceBuilder` is not set (ie `B: Identity`). - type Error = Box; + type Error = BoxError; type Future = Pin> + Send>>; From 1af05117e8a91319b5d6849208592af727b79cf3 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 17 May 2024 15:16:08 +0200 Subject: [PATCH 11/22] fix tests --- examples/examples/jsonrpsee_as_service.rs | 16 +- .../jsonrpsee_server_low_level_api.rs | 172 +++++++++--------- examples/examples/ws_dual_stack.rs | 5 +- server/Cargo.toml | 2 +- server/src/server.rs | 122 +++++++++---- server/src/tests/helpers.rs | 5 +- 6 files changed, 190 insertions(+), 132 deletions(-) diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index 9e256c2f4e..06b4a17d60 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -236,7 +236,10 @@ async fn run_server(metrics: Metrics) -> anyhow::Result { 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. + // to be `Box` so we need to convert it to a concrete type + // as workaround. + // + // You can also write your own wrapper TowerService type to avoid this. svc.call(req).await.map_err(|e| anyhow::anyhow!("{:?}", e)) } .boxed() @@ -252,9 +255,11 @@ 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. + // to be `Box` so we need to convert it to a concrete type + // as workaround. + // + // You can also write your own wrapper TowerService type to avoid this. rp.map_err(|e| anyhow::anyhow!("{:?}", e)) } .boxed() @@ -273,7 +278,10 @@ async fn run_server(metrics: Metrics) -> anyhow::Result { Either::Left((conn, _)) => conn, // If the server is stopped, we should gracefully shutdown // the connection and poll it until it finishes. - Either::Right((_, conn)) => conn.await, + Either::Right((_, mut conn)) => { + conn.as_mut().graceful_shutdown(); + conn.await + } }; // Log any errors that might have occurred. diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index 85fb9e5d84..e5538457e3 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -222,96 +222,95 @@ async fn run_server() -> anyhow::Result { }; let per_conn = per_conn.clone(); - tokio::spawn(async move { - let stop_handle2 = per_conn.stop_handle.clone(); - let per_conn = per_conn.clone(); - - let svc = service_fn(move |req| { - let req = req.map(HttpBody::new); - - let PerConnection { - methods, - stop_handle, - conn_guard, - conn_id, - blacklisted_peers, - global_http_rate_limit, - } = per_conn.clone(); - - // jsonrpsee expects a `conn permit` for each connection. - // - // This may be omitted if don't want to limit the number of connections - // to the server. - let Some(conn_permit) = conn_guard.try_acquire() else { - return async { Ok::<_, Infallible>(http::response::too_many_requests()) }.boxed(); - }; - - // The IP addr was blacklisted. - if blacklisted_peers.lock().unwrap().get(&remote_addr.ip()).is_some() { - return async { Ok(http::response::denied()) }.boxed(); - } + // Create a service handler. + let stop_handle2 = per_conn.stop_handle.clone(); + let per_conn = per_conn.clone(); + let svc = service_fn(move |req| { + let req = req.map(HttpBody::new); + + let PerConnection { + methods, + stop_handle, + conn_guard, + conn_id, + blacklisted_peers, + global_http_rate_limit, + } = per_conn.clone(); + + // jsonrpsee expects a `conn permit` for each connection. + // + // This may be omitted if don't want to limit the number of connections + // to the server. + let Some(conn_permit) = conn_guard.try_acquire() else { + return async { Ok::<_, Infallible>(http::response::too_many_requests()) }.boxed(); + }; - if ws::is_upgrade_request(&req) { - let (tx, mut disconnect) = mpsc::channel(1); - let rpc_service = RpcServiceBuilder::new().layer_fn(move |service| CallLimit { - service, - count: Default::default(), - state: tx.clone(), - }); - - let conn = - ConnectionState::new(stop_handle, conn_id.fetch_add(1, Ordering::Relaxed), conn_permit); - - // Establishes the websocket connection - // and if the `CallLimit` middleware triggers the hard limit - // then the connection is closed i.e, the `conn_fut` is dropped. - async move { - match ws::connect(req, ServerConfig::default(), methods, conn, rpc_service).await { - Ok((rp, conn_fut)) => { - tokio::spawn(async move { - tokio::select! { - _ = conn_fut => (), - _ = disconnect.recv() => { - blacklisted_peers.lock().unwrap().insert(remote_addr.ip()); - }, - } - }); - Ok(rp) - } - Err(rp) => Ok(rp), + // The IP addr was blacklisted. + if blacklisted_peers.lock().unwrap().get(&remote_addr.ip()).is_some() { + return async { Ok(http::response::denied()) }.boxed(); + } + + if ws::is_upgrade_request(&req) { + let (tx, mut disconnect) = mpsc::channel(1); + let rpc_service = RpcServiceBuilder::new().layer_fn(move |service| CallLimit { + service, + count: Default::default(), + state: tx.clone(), + }); + + let conn = ConnectionState::new(stop_handle, conn_id.fetch_add(1, Ordering::Relaxed), conn_permit); + + // Establishes the websocket connection + // and if the `CallLimit` middleware triggers the hard limit + // then the connection is closed i.e, the `conn_fut` is dropped. + async move { + match ws::connect(req, ServerConfig::default(), methods, conn, rpc_service).await { + Ok((rp, conn_fut)) => { + tokio::spawn(async move { + tokio::select! { + _ = conn_fut => (), + _ = disconnect.recv() => { + blacklisted_peers.lock().unwrap().insert(remote_addr.ip()); + }, + } + }); + Ok(rp) } + Err(rp) => Ok(rp), } - .boxed() - } else if !ws::is_upgrade_request(&req) { - let (tx, mut disconnect) = mpsc::channel(1); - - let rpc_service = RpcServiceBuilder::new().layer_fn(move |service| CallLimit { - service, - count: global_http_rate_limit.clone(), - state: tx.clone(), - }); - - let server_cfg = ServerConfig::default(); - let conn = - ConnectionState::new(stop_handle, conn_id.fetch_add(1, Ordering::Relaxed), conn_permit); - - // There is another API for making call with just a service as well. - // - // See [`jsonrpsee::server::http::call_with_service`] - async move { - tokio::select! { - // Rpc call finished successfully. - res = http::call_with_service_builder(req, server_cfg, conn, methods, rpc_service) => Ok(res), - // Deny the call if the call limit is exceeded. - _ = disconnect.recv() => Ok(http::response::denied()), - } + } + .boxed() + } else if !ws::is_upgrade_request(&req) { + let (tx, mut disconnect) = mpsc::channel(1); + + let rpc_service = RpcServiceBuilder::new().layer_fn(move |service| CallLimit { + service, + count: global_http_rate_limit.clone(), + state: tx.clone(), + }); + + let server_cfg = ServerConfig::default(); + let conn = ConnectionState::new(stop_handle, conn_id.fetch_add(1, Ordering::Relaxed), conn_permit); + + // There is another API for making call with just a service as well. + // + // See [`jsonrpsee::server::http::call_with_service`] + async move { + tokio::select! { + // Rpc call finished successfully. + res = http::call_with_service_builder(req, server_cfg, conn, methods, rpc_service) => Ok(res), + // Deny the call if the call limit is exceeded. + _ = disconnect.recv() => Ok(http::response::denied()), } - .boxed() - } else { - async { Ok(http::response::denied()) }.boxed() } - }); + .boxed() + } else { + async { Ok(http::response::denied()) }.boxed() + } + }); + // Upgrade the connection to a HTTP service. + tokio::spawn(async move { let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); let conn = builder.serve_connection_with_upgrades(TokioIo::new(sock), svc); let stopped = stop_handle2.shutdown(); @@ -324,7 +323,10 @@ async fn run_server() -> anyhow::Result { Either::Left((conn, _)) => conn, // If the server is stopped, we should gracefully shutdown // the connection and poll it until it finishes. - Either::Right((_, conn)) => conn.await, + Either::Right((_, mut conn)) => { + conn.as_mut().graceful_shutdown(); + conn.await + } }; // Log any errors that might have occurred. diff --git a/examples/examples/ws_dual_stack.rs b/examples/examples/ws_dual_stack.rs index 269e32f7a1..e29edcae78 100644 --- a/examples/examples/ws_dual_stack.rs +++ b/examples/examples/ws_dual_stack.rs @@ -130,7 +130,10 @@ async fn run_server() -> anyhow::Result<(ServerHandle, Addrs)> { Either::Left((conn, _)) => conn, // If the server is stopped, we should gracefully shutdown // the connection and poll it until it finishes. - Either::Right((_, conn)) => conn.await, + Either::Right((_, mut conn)) => { + conn.as_mut().graceful_shutdown(); + conn.await + } }; // Log any errors that might have occurred. diff --git a/server/Cargo.toml b/server/Cargo.toml index 26e61bc06b..ee8465ff10 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -26,7 +26,7 @@ tokio = { version = "1.16", features = ["net", "rt-multi-thread", "macros", "tim tokio-util = { version = "0.7", features = ["compat"] } tokio-stream = { version = "0.1.7", features = ["sync"] } hyper = { version = "1.3", features = ["server", "http1", "http2"] } -hyper-util = { version = "0.1", features = ["tokio", "service", "tokio"] } +hyper-util = { version = "0.1", features = ["tokio", "service", "tokio", "server"] } http = "1" http-body = "1" http-body-util = "0.1.0" diff --git a/server/src/server.rs b/server/src/server.rs index d8ac79d510..e1962e82df 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -782,54 +782,95 @@ impl Builder { /// # Examples /// /// ```no_run - /// use hyper::service::{make_service_fn, service_fn}; - /// use hyper::server::conn::AddrStream; /// use jsonrpsee_server::{Methods, ServerHandle, ws, stop_channel}; /// use tower::Service; /// use std::{error::Error as StdError, net::SocketAddr}; + /// use futures_util::future::{self, Either}; + /// use hyper_util::rt::{TokioIo, TokioExecutor}; /// - /// fn run_server() -> ServerHandle { - /// let addr = SocketAddr::from(([127, 0, 0, 1], 0)); + /// async fn run_server() -> ServerHandle { + /// let listener = tokio::net::TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))).await.unwrap(); /// let (stop_handle, server_handle) = stop_channel(); /// let svc_builder = jsonrpsee_server::Server::builder().max_connections(33).to_service_builder(); /// let methods = Methods::new(); - /// let stop_handle2 = stop_handle.clone(); - /// - /// let make_service = make_service_fn(move |_conn: &AddrStream| { - /// // You may use `conn` or the actual HTTP request to get connection related details. - /// let stop_handle = stop_handle2.clone(); - /// let svc_builder = svc_builder.clone(); - /// let methods = methods.clone(); - /// - /// async move { - /// Ok::<_, Box>(service_fn(move |req| { - /// let stop_handle = stop_handle.clone(); - /// let svc_builder = svc_builder.clone(); - /// let methods = methods.clone(); - /// let mut svc = svc_builder.build(methods, stop_handle); - /// - /// // It's not possible to know whether the websocket upgrade handshake failed or not here. - /// let is_websocket = ws::is_upgrade_request(&req); - /// - /// if is_websocket { - /// println!("websocket") - /// } else { - /// println!("http") - /// } - /// - /// /// Call the jsonrpsee service which - /// /// may upgrade it to a WebSocket connection - /// /// or treat it as "ordinary HTTP request". - /// svc.call(req) - /// })) - /// } - /// }); - /// - /// let server = hyper::Server::bind(&addr).serve(make_service); + /// let stop_handle = stop_handle.clone(); /// /// tokio::spawn(async move { - /// let graceful = server.with_graceful_shutdown(async move { stop_handle.shutdown().await }); - /// graceful.await.unwrap() + /// loop { + /// // The `tokio::select!` macro is used to wait for either of the + /// // listeners to accept a new connection or for the server to be + /// // stopped. + /// let (sock, remote_addr) = tokio::select! { + /// res = listener.accept() => { + /// match res { + /// Ok(sock) => sock, + /// Err(e) => { + /// tracing::error!("failed to accept v4 connection: {:?}", e); + /// continue; + /// } + /// } + /// } + /// _ = stop_handle.clone().shutdown() => break, + /// }; + /// + /// + /// let stop_handle2 = stop_handle.clone(); + /// let svc_builder2 = svc_builder.clone(); + /// let methods2 = methods.clone(); + /// + /// let svc = hyper::service::service_fn(move |req| { + /// let stop_handle = stop_handle2.clone(); + /// let svc_builder = svc_builder2.clone(); + /// let methods = methods2.clone(); + /// + /// let mut svc = svc_builder.build(methods, stop_handle.clone()); + /// + /// // It's not possible to know whether the websocket upgrade handshake failed or not here. + /// let is_websocket = ws::is_upgrade_request(&req); + /// + /// if is_websocket { + /// println!("websocket") + /// } else { + /// println!("http") + /// } + /// + /// // Call the jsonrpsee service which + /// // may upgrade it to a WebSocket connection + /// // or treat it as "ordinary HTTP request". + /// // + /// // 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. + /// async move { svc.call(req).await.map_err(|e| anyhow::anyhow!("e")) } + /// }); + /// + /// let stop_handle = stop_handle.clone(); + /// // Upgrade the connection to a HTTP service with graceful shutdown. + /// tokio::spawn(async move { + /// let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); + /// let conn = builder.serve_connection_with_upgrades(TokioIo::new(sock), svc); + /// let stopped = stop_handle.shutdown(); + /// + /// // Pin the future so that it can be polled. + /// tokio::pin!(stopped, conn); + /// + /// let res = match future::select(conn, stopped).await { + /// // Return the connection if not stopped. + /// Either::Left((conn, _)) => conn, + /// // If the server is stopped, we should gracefully shutdown + /// // the connection and poll it until it finishes. + /// Either::Right((_, mut conn)) => { + /// conn.as_mut().graceful_shutdown(); + /// conn.await + /// } + /// }; + /// + /// // Log any errors that might have occurred. + /// if let Err(err) = res { + /// tracing::error!(err=?err, "HTTP connection failed"); + /// } + /// }); + /// } /// }); /// /// server_handle @@ -1207,9 +1248,10 @@ where let res = match future::select(conn, stopped).await { Either::Left((conn, _)) => conn, - Either::Right((_, conn)) => { + Either::Right((_, mut conn)) => { // NOTE: the connection should continue to be polled until shutdown can finish. // Thus, both lines below are needed and not a nit. + conn.as_mut().graceful_shutdown(); conn.await } }; diff --git a/server/src/tests/helpers.rs b/server/src/tests/helpers.rs index d3d4445eb3..6f1fbd57bb 100644 --- a/server/src/tests/helpers.rs +++ b/server/src/tests/helpers.rs @@ -276,7 +276,10 @@ pub(crate) async fn ws_server_with_stats(metrics: Metrics) -> SocketAddr { Either::Left((conn, _)) => conn, // If the server is stopped, we should gracefully shutdown // the connection and poll it until it finishes. - Either::Right((_, conn)) => conn.await, + Either::Right((_, mut conn)) => { + conn.as_mut().graceful_shutdown(); + conn.await + } }; // Log any errors that might have occurred. From 8c6a5eaea76559c8a91bb1c73f0254501d86741c Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 17 May 2024 16:07:54 +0200 Subject: [PATCH 12/22] remove sync requirement --- client/http-client/src/client.rs | 2 +- client/http-client/src/transport.rs | 4 +- core/src/http_helpers.rs | 8 +- examples/examples/jsonrpsee_as_service.rs | 122 +++++++++--------- .../src/middleware/http/proxy_get_request.rs | 2 +- server/src/server.rs | 21 ++- 6 files changed, 78 insertions(+), 81 deletions(-) diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index f38ad6a220..7e4fcd1cdf 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -189,7 +189,7 @@ impl HttpClientBuilder where L: Layer, S: Service, Error = TransportError> + Clone, - B: http_body::Body + Send + 'static, + B: http_body::Body + Send + Unpin + 'static, B::Data: Send, B::Error: Into, { diff --git a/client/http-client/src/transport.rs b/client/http-client/src/transport.rs index 56eca20337..a340f0304c 100644 --- a/client/http-client/src/transport.rs +++ b/client/http-client/src/transport.rs @@ -52,7 +52,7 @@ impl Clone for HttpBackend { impl tower::Service> for HttpBackend where - B: http_body::Body + Send + 'static + Unpin, + B: http_body::Body + Send + Unpin + 'static, B::Data: Send, B::Error: Into, { @@ -285,7 +285,7 @@ pub struct HttpTransportClient { impl HttpTransportClient where S: Service, Error = Error> + Clone, - B: http_body::Body + Send + Unpin + 'static, + B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into, { diff --git a/core/src/http_helpers.rs b/core/src/http_helpers.rs index d609276c13..531fa5b606 100644 --- a/core/src/http_helpers.rs +++ b/core/src/http_helpers.rs @@ -46,7 +46,7 @@ pub type Response = http::Response; /// A HTTP request body. #[derive(Debug, Default)] -pub struct Body(http_body_util::combinators::BoxBody); +pub struct Body(http_body_util::combinators::UnsyncBoxBody); impl Body { /// Create an empty body. @@ -57,11 +57,11 @@ impl Body { /// Create a new body. pub fn new(body: B) -> Self where - B: http_body::Body + Send + Sync + 'static, + B: http_body::Body + Send + 'static, B::Data: Send + 'static, B::Error: Into, { - Self(body.map_err(|e| e.into()).boxed()) + Self(body.map_err(|e| e.into()).boxed_unsync()) } } @@ -109,7 +109,7 @@ pub enum HttpError { /// Returns `Err` if the body was too large or the body couldn't be read. pub async fn read_body(headers: &http::HeaderMap, body: B, max_body_size: u32) -> Result<(Vec, bool), HttpError> where - B: http_body::Body + Send + 'static, + B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into, { diff --git a/examples/examples/jsonrpsee_as_service.rs b/examples/examples/jsonrpsee_as_service.rs index 06b4a17d60..db7b41922b 100644 --- a/examples/examples/jsonrpsee_as_service.rs +++ b/examples/examples/jsonrpsee_as_service.rs @@ -199,72 +199,72 @@ async fn run_server(metrics: Metrics) -> anyhow::Result { } _ = per_conn.stop_handle.clone().shutdown() => break, }; - let per_conn = per_conn.clone(); + let per_conn2 = per_conn.clone(); + + let svc = service_fn(move |req: hyper::Request| { + let is_websocket = jsonrpsee::server::ws::is_upgrade_request(&req); + let transport_label = if is_websocket { "ws" } else { "http" }; + let PerConnection { methods, stop_handle, metrics, svc_builder } = per_conn2.clone(); + + // 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 } + }); - tokio::spawn(async move { - let stop_handle2 = per_conn.stop_handle.clone(); - let per_conn = per_conn.clone(); - - let svc = service_fn(move |req: hyper::Request| { - let is_websocket = jsonrpsee::server::ws::is_upgrade_request(&req); - let transport_label = if is_websocket { "ws" } else { "http" }; - let PerConnection { methods, stop_handle, metrics, svc_builder } = per_conn.clone(); - - // 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 mut svc = svc_builder.set_rpc_middleware(rpc_middleware).build(methods, stop_handle); + + if is_websocket { + // Utilize the session close future to know when the actual WebSocket + // session was closed. + let session_close = svc.on_session_closed(); + + // A little bit weird API but the response to HTTP request must be returned below + // and we spawn a task to register when the session is closed. + tokio::spawn(async move { + session_close.await; + tracing::info!("Closed WebSocket connection"); + metrics.closed_ws_connections.fetch_add(1, Ordering::Relaxed); }); - let mut svc = svc_builder.set_rpc_middleware(rpc_middleware).build(methods, stop_handle); - - if is_websocket { - // Utilize the session close future to know when the actual WebSocket - // session was closed. - let session_close = svc.on_session_closed(); - - // A little bit weird API but the response to HTTP request must be returned below - // and we spawn a task to register when the session is closed. - tokio::spawn(async move { - session_close.await; - tracing::info!("Closed WebSocket connection"); - metrics.closed_ws_connections.fetch_add(1, Ordering::Relaxed); - }); - - 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. - // - // You can also write your own wrapper TowerService type to avoid this. - svc.call(req).await.map_err(|e| anyhow::anyhow!("{:?}", e)) - } - .boxed() - } else { - // HTTP. - async move { - tracing::info!("Opened HTTP connection"); - metrics.http_calls.fetch_add(1, Ordering::Relaxed); - let rp = svc.call(req).await; - - if rp.is_ok() { - metrics.success_http_calls.fetch_add(1, Ordering::Relaxed); - } - - 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. - // - // You can also write your own wrapper TowerService type to avoid this. - rp.map_err(|e| anyhow::anyhow!("{:?}", e)) + 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. + // + // You can also write your own wrapper TowerService type to avoid this. + svc.call(req).await.map_err(|e| anyhow::anyhow!("{:?}", e)) + } + .boxed() + } else { + // HTTP. + async move { + tracing::info!("Opened HTTP connection"); + metrics.http_calls.fetch_add(1, Ordering::Relaxed); + let rp = svc.call(req).await; + + if rp.is_ok() { + metrics.success_http_calls.fetch_add(1, Ordering::Relaxed); } - .boxed() + + 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. + // + // You can also write your own wrapper TowerService type to avoid this. + rp.map_err(|e| anyhow::anyhow!("{:?}", e)) } - }); + .boxed() + } + }); + + let per_conn = per_conn.clone(); + tokio::spawn(async move { + let stop_handle2 = per_conn.stop_handle.clone(); let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); let conn = builder.serve_connection_with_upgrades(TokioIo::new(sock), svc); diff --git a/server/src/middleware/http/proxy_get_request.rs b/server/src/middleware/http/proxy_get_request.rs index 533b88b89c..a722e2f793 100644 --- a/server/src/middleware/http/proxy_get_request.rs +++ b/server/src/middleware/http/proxy_get_request.rs @@ -119,7 +119,7 @@ where S::Response: 'static, S::Error: Into + 'static, S::Future: Send + 'static, - B: http_body::Body + Send + Sync + 'static, + B: http_body::Body + Send + 'static, B::Data: Send, B::Error: Into, { diff --git a/server/src/server.rs b/server/src/server.rs index e1962e82df..765dfc843b 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -100,12 +100,11 @@ where RpcMiddleware: tower::Layer + Clone + Send + 'static, for<'a> >::Service: RpcServiceT<'a>, HttpMiddleware: Layer> + Send + 'static, - >>::Service: Send - + Clone - + Service, Error = Box<(dyn StdError + Send + Sync + 'static)>>, + >>::Service: + Send + Clone + Service, Error = BoxError>, <>>::Service as Service>::Future: Send, B: http_body::Body + Send + 'static, - ::Error: Send + Sync + StdError, + ::Error: Into, ::Data: Send, { /// Start responding to connections requests. @@ -1006,7 +1005,7 @@ where Send + Service, Response = HttpResponse, Error = Box<(dyn StdError + Send + Sync + 'static)>>, <>>::Service as Service>>::Future: Send + 'static, - B: http_body::Body + Send + Sync + 'static, + B: http_body::Body + Send + 'static, B::Error: Into, { type Response = HttpResponse; @@ -1038,7 +1037,7 @@ where RpcMiddleware: for<'a> tower::Layer, >::Service: Send + Sync + 'static, for<'a> >::Service: RpcServiceT<'a>, - B: http_body::Body + Send + Sync + 'static, + B: http_body::Body + Send + 'static, B::Error: Into, { type Response = HttpResponse; @@ -1193,14 +1192,12 @@ fn process_connection<'a, RpcMiddleware, HttpMiddleware, B>(params: ProcessConne where RpcMiddleware: 'static, HttpMiddleware: Layer> + Send + 'static, - >>::Service: Send - + 'static - + Clone - + Service, Error = Box<(dyn StdError + Send + Sync + 'static)>>, + >>::Service: + Send + 'static + Clone + Service, Error = BoxError>, <>>::Service as Service>::Future: Send + 'static, - B: http_body::Body + Send + 'static, - ::Error: Send + Sync + StdError, + B: http_body::Body + Send + 'static, + ::Error: Into, ::Data: Send, { let ProcessConnection { From 0fe58e28ce8a9a8f7076190a580e610f04fbee76 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 17 May 2024 16:14:14 +0200 Subject: [PATCH 13/22] fix nits --- core/src/http_helpers.rs | 2 +- server/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/http_helpers.rs b/core/src/http_helpers.rs index 531fa5b606..bccb7b5032 100644 --- a/core/src/http_helpers.rs +++ b/core/src/http_helpers.rs @@ -102,7 +102,7 @@ pub enum HttpError { Stream(#[from] BoxError), } -/// Read a data from [`hyper::body::HttpBody`] and return the data if it is valid JSON and within the allowed size range. +/// Read data from a HTTP body and return the data if it is valid JSON and within the allowed size range. /// /// Returns `Ok((bytes, single))` if the body was in valid size range; and a bool indicating whether the JSON-RPC /// request is a single or a batch. diff --git a/server/Cargo.toml b/server/Cargo.toml index ee8465ff10..a3ead80281 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -26,7 +26,7 @@ tokio = { version = "1.16", features = ["net", "rt-multi-thread", "macros", "tim tokio-util = { version = "0.7", features = ["compat"] } tokio-stream = { version = "0.1.7", features = ["sync"] } hyper = { version = "1.3", features = ["server", "http1", "http2"] } -hyper-util = { version = "0.1", features = ["tokio", "service", "tokio", "server"] } +hyper-util = { version = "0.1", features = ["tokio", "service", "tokio", "server-auto"] } http = "1" http-body = "1" http-body-util = "0.1.0" From b8d1295f3fe68cce63b3130283e13263ec27a4cb Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 17 May 2024 16:56:45 +0200 Subject: [PATCH 14/22] use ring crypto backend --- client/http-client/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/http-client/Cargo.toml b/client/http-client/Cargo.toml index c34a20a519..b414b1365e 100644 --- a/client/http-client/Cargo.toml +++ b/client/http-client/Cargo.toml @@ -37,8 +37,8 @@ tokio = { version = "1.16", features = ["net", "rt-multi-thread", "macros"] } [features] default = ["native-tls"] -native-tls = ["hyper-rustls/native-tokio", "hyper-rustls/aws-lc-rs", "__tls"] -webpki-tls = ["hyper-rustls/webpki-tokio", "hyper-rustls/aws-lc-rs", "__tls"] +native-tls = ["hyper-rustls/native-tokio", "hyper-rustls/ring", "__tls"] +webpki-tls = ["hyper-rustls/webpki-tokio", "hyper-rustls/ring", "__tls"] # Internal feature to indicate whether TLS is enabled. # Does nothing on its own. From 4ff7805b3c479a23b1d9351cff0717ae9ea72391 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 17 May 2024 17:26:24 +0200 Subject: [PATCH 15/22] more ring --- client/transport/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/transport/Cargo.toml b/client/transport/Cargo.toml index cfb95e1af4..9d6a2c47c5 100644 --- a/client/transport/Cargo.toml +++ b/client/transport/Cargo.toml @@ -30,7 +30,7 @@ url = { version = "2.4.0", optional = true } # tls rustls-native-certs = { version = "0.7", optional = true } webpki-roots = { version = "0.26", optional = true } -tokio-rustls = { version = "0.26", optional = true } +tokio-rustls = { version = "0.26", default-features = false, optional = true, features = ["logging", "tls12", "ring"] } rustls-pki-types = { version = "1", optional = true } # ws From d357e1d09ec9044a6e7c1a88eb709ad77179385a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 17 May 2024 17:33:06 +0200 Subject: [PATCH 16/22] Update client/ws-client/src/tests.rs --- 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 e550854d2a..45b1bf517b 100644 --- a/client/ws-client/src/tests.rs +++ b/client/ws-client/src/tests.rs @@ -478,7 +478,6 @@ async fn redirections() { // The client will first connect to a server that only performs re-directions and finally // redirect to another server to complete the handshake. - println!("Connecting to {redirect_url}"); let client = WsClientBuilder::default().build(&redirect_url).with_default_timeout().await; // It's an ok client let client = match client { From abc94bd88f08ef297ba15a46f74c14523472a9f1 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 17 May 2024 17:36:12 +0200 Subject: [PATCH 17/22] Update client/http-client/src/transport.rs --- client/http-client/src/transport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/http-client/src/transport.rs b/client/http-client/src/transport.rs index a340f0304c..0e5dc6f576 100644 --- a/client/http-client/src/transport.rs +++ b/client/http-client/src/transport.rs @@ -299,7 +299,7 @@ where *headers = self.headers.clone(); } - let req = req.body(body.into()).expect("Failed to create request"); + let req = req.body(body.into()).expect("URI and request headers are valid; qed"); let response = self.client.clone().ready().await?.call(req).await?; if response.status().is_success() { From ec9f3203302f79d9259a715d5aedd4753f4b32f2 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 17 May 2024 17:44:29 +0200 Subject: [PATCH 18/22] Update server/src/middleware/http/proxy_get_request.rs --- server/src/middleware/http/proxy_get_request.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/middleware/http/proxy_get_request.rs b/server/src/middleware/http/proxy_get_request.rs index a722e2f793..143a761713 100644 --- a/server/src/middleware/http/proxy_get_request.rs +++ b/server/src/middleware/http/proxy_get_request.rs @@ -26,7 +26,6 @@ //! Middleware that proxies requests at a specified URI to internal //! RPC method calls. -//! use crate::transport::http; use crate::{HttpBody, HttpRequest, HttpResponse}; From 1e7bc490fdc6928ea1cf626f0ee5606714234fb8 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 17 May 2024 17:51:18 +0200 Subject: [PATCH 19/22] Update server/src/transport/ws.rs --- server/src/transport/ws.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index be9b76e2e9..056bd0e8cb 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -470,7 +470,7 @@ where background_task(params).await; }; - Ok((response.map(|()| HttpResponseBody::default()), fut)) // empty body here + Ok((response.map(|()| HttpResponseBody::default()), fut)) } Err(e) => { tracing::debug!(target: LOG_TARGET, "WS upgrade handshake failed: {}", e); From ae860bdac80b662318f378429fddb347c4f971c6 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 17 May 2024 17:46:08 +0200 Subject: [PATCH 20/22] simplify code --- test-utils/src/helpers.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/test-utils/src/helpers.rs b/test-utils/src/helpers.rs index 4ee4f5653f..4c47a39f33 100644 --- a/test-utils/src/helpers.rs +++ b/test-utils/src/helpers.rs @@ -227,13 +227,6 @@ where // // NOTE: This must be spawned on tokio because hyper only works with tokio. pub async fn http_server_with_hardcoded_response(response: String) -> SocketAddr { - async fn process_request( - _req: Request, - response: String, - ) -> Result, Infallible> { - Ok(Response::new(Body::from(response))) - } - let (tx, rx) = futures_channel::oneshot::channel::(); tokio::spawn(async move { @@ -253,13 +246,13 @@ pub async fn http_server_with_hardcoded_response(response: String) -> SocketAddr let conn = builder.serve_connection_with_upgrades( io, - service_fn(move |req| { - let rp = response.clone(); - async move { Ok::<_, Infallible>(process_request(req, rp).await.unwrap()) } + service_fn(move |_| { + let rp = Response::new(Body::from(response.clone())); + async move { Ok::<_, Infallible>(rp) } }), ); - conn.await.unwrap(); + let _ = conn.await; }); } }); From 0e791676dacff6070a6e9358e4d3697802430ea4 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 21 May 2024 10:40:02 +0200 Subject: [PATCH 21/22] address grumbles --- client/http-client/src/lib.rs | 2 +- core/src/http_helpers.rs | 16 +- .../jsonrpsee_server_low_level_api.rs | 4 +- server/src/lib.rs | 9 +- server/src/middleware/http/host_filter.rs | 4 +- .../src/middleware/http/proxy_get_request.rs | 6 +- server/src/server.rs | 156 +++++++++--------- server/src/transport/http.rs | 12 +- server/src/transport/ws.rs | 10 +- server/src/utils.rs | 4 +- test-utils/src/helpers.rs | 2 +- 11 files changed, 113 insertions(+), 112 deletions(-) diff --git a/client/http-client/src/lib.rs b/client/http-client/src/lib.rs index cc76d3ac16..3c868ab547 100644 --- a/client/http-client/src/lib.rs +++ b/client/http-client/src/lib.rs @@ -48,7 +48,7 @@ pub use hyper::http::{HeaderMap, HeaderValue}; pub use jsonrpsee_types as types; /// Default HTTP body for the client. -pub type HttpBody = http_body_util::Full; +pub type HttpBody = jsonrpsee_core::http_helpers::Body; /// HTTP request with default body. pub type HttpRequest = jsonrpsee_core::http_helpers::Request; /// HTTP response with default body. diff --git a/core/src/http_helpers.rs b/core/src/http_helpers.rs index bccb7b5032..c6d72364ea 100644 --- a/core/src/http_helpers.rs +++ b/core/src/http_helpers.rs @@ -36,19 +36,19 @@ use std::{ }; /// HTTP request type. -pub type Request = http::Request; - -/// HTTP response body. -pub type ResponseBody = http_body_util::Full; +pub use http::Request; /// HTTP response type. -pub type Response = http::Response; +pub use http::Response; + +/// HTTP response body. +pub type Body = http_body_util::Full; /// A HTTP request body. #[derive(Debug, Default)] -pub struct Body(http_body_util::combinators::UnsyncBoxBody); +pub struct BoxBody(http_body_util::combinators::UnsyncBoxBody); -impl Body { +impl BoxBody { /// Create an empty body. pub fn empty() -> Self { Self::default() @@ -65,7 +65,7 @@ impl Body { } } -impl http_body::Body for Body { +impl http_body::Body for BoxBody { type Data = Bytes; type Error = BoxError; diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index e5538457e3..6c16e6faf8 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -51,7 +51,7 @@ use jsonrpsee::core::async_trait; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::middleware::rpc::RpcServiceT; -use jsonrpsee::server::HttpBody; +use jsonrpsee::server::HttpBoxBody; use jsonrpsee::server::{ http, stop_channel, ws, ConnectionGuard, ConnectionState, RpcServiceBuilder, ServerConfig, ServerHandle, StopHandle, }; @@ -226,7 +226,7 @@ async fn run_server() -> anyhow::Result { let stop_handle2 = per_conn.stop_handle.clone(); let per_conn = per_conn.clone(); let svc = service_fn(move |req| { - let req = req.map(HttpBody::new); + let req = req.map(HttpBoxBody::new); let PerConnection { methods, diff --git a/server/src/lib.rs b/server/src/lib.rs index b96a59f7bd..2555f8e15e 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -53,10 +53,13 @@ pub use server::{ }; pub use tracing; -pub use jsonrpsee_core::http_helpers::{ - Body as HttpBody, Request as HttpRequest, Response as HttpResponse, ResponseBody as HttpResponseBody, -}; +pub use jsonrpsee_core::http_helpers::{Body as HttpBody, BoxBody as HttpBoxBody}; pub use transport::http; pub use transport::ws; +/// HTTP request with boxed body. +pub type HttpRequest = jsonrpsee_core::http_helpers::Request; +/// HTTP response with concrete body. +pub type HttpResponse = jsonrpsee_core::http_helpers::Response; + pub(crate) const LOG_TARGET: &str = "jsonrpsee-server"; diff --git a/server/src/middleware/http/host_filter.rs b/server/src/middleware/http/host_filter.rs index 0877b71de8..4ab78fb5cd 100644 --- a/server/src/middleware/http/host_filter.rs +++ b/server/src/middleware/http/host_filter.rs @@ -28,7 +28,7 @@ use crate::middleware::http::authority::{Authority, AuthorityError, Port}; use crate::transport::http; -use crate::{HttpRequest, HttpResponseBody, LOG_TARGET}; +use crate::{HttpBody, HttpRequest, LOG_TARGET}; use futures_util::{Future, FutureExt, TryFutureExt}; use hyper::body::Bytes; use hyper::Response; @@ -101,7 +101,7 @@ pub struct HostFilter { impl Service> for HostFilter where - S: Service, Response = Response>, + S: Service, Response = Response>, S::Response: 'static, S::Error: Into + 'static, S::Future: Send + 'static, diff --git a/server/src/middleware/http/proxy_get_request.rs b/server/src/middleware/http/proxy_get_request.rs index 143a761713..96c50edbe2 100644 --- a/server/src/middleware/http/proxy_get_request.rs +++ b/server/src/middleware/http/proxy_get_request.rs @@ -27,7 +27,7 @@ //! Middleware that proxies requests at a specified URI to internal //! RPC method calls. use crate::transport::http; -use crate::{HttpBody, HttpRequest, HttpResponse}; +use crate::{HttpBoxBody, HttpRequest, HttpResponse}; use http_body_util::BodyExt; use hyper::body::Bytes; @@ -149,11 +149,11 @@ where let bytes = serde_json::to_vec(&RequestSer::borrowed(&Id::Number(0), &self.method, None)) .expect("Valid request; qed"); - let body = HttpBody::new(http_body_util::Full::new(bytes.into())); + let body = HttpBoxBody::new(http_body_util::Full::new(bytes.into())); req.map(|_| body) } else { - req.map(HttpBody::new) + req.map(HttpBoxBody::new) }; // Call the inner service and get a future that resolves to the response. diff --git a/server/src/server.rs b/server/src/server.rs index 765dfc843b..b40352418a 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -37,7 +37,7 @@ use crate::future::{session_close, ConnectionGuard, ServerHandle, SessionClose, use crate::middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}; use crate::transport::ws::BackgroundTaskParams; use crate::transport::{http, ws}; -use crate::{HttpBody, HttpRequest, HttpResponse, HttpResponseBody, LOG_TARGET}; +use crate::{HttpBody, HttpBoxBody, HttpRequest, HttpResponse, LOG_TARGET}; use futures_util::future::{self, Either, FutureExt}; use futures_util::io::{BufReader, BufWriter}; @@ -787,89 +787,89 @@ impl Builder { /// use futures_util::future::{self, Either}; /// use hyper_util::rt::{TokioIo, TokioExecutor}; /// - /// async fn run_server() -> ServerHandle { - /// let listener = tokio::net::TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))).await.unwrap(); + /// fn run_server() -> ServerHandle { /// let (stop_handle, server_handle) = stop_channel(); /// let svc_builder = jsonrpsee_server::Server::builder().max_connections(33).to_service_builder(); /// let methods = Methods::new(); /// let stop_handle = stop_handle.clone(); /// /// tokio::spawn(async move { + /// let listener = tokio::net::TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))).await.unwrap(); + /// /// loop { - /// // The `tokio::select!` macro is used to wait for either of the - /// // listeners to accept a new connection or for the server to be - /// // stopped. - /// let (sock, remote_addr) = tokio::select! { - /// res = listener.accept() => { - /// match res { - /// Ok(sock) => sock, - /// Err(e) => { - /// tracing::error!("failed to accept v4 connection: {:?}", e); - /// continue; - /// } - /// } - /// } - /// _ = stop_handle.clone().shutdown() => break, - /// }; - /// - /// - /// let stop_handle2 = stop_handle.clone(); - /// let svc_builder2 = svc_builder.clone(); - /// let methods2 = methods.clone(); - /// - /// let svc = hyper::service::service_fn(move |req| { - /// let stop_handle = stop_handle2.clone(); - /// let svc_builder = svc_builder2.clone(); - /// let methods = methods2.clone(); - /// - /// let mut svc = svc_builder.build(methods, stop_handle.clone()); - /// - /// // It's not possible to know whether the websocket upgrade handshake failed or not here. - /// let is_websocket = ws::is_upgrade_request(&req); - /// - /// if is_websocket { - /// println!("websocket") - /// } else { - /// println!("http") - /// } - /// - /// // Call the jsonrpsee service which - /// // may upgrade it to a WebSocket connection - /// // or treat it as "ordinary HTTP request". - /// // - /// // 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. - /// async move { svc.call(req).await.map_err(|e| anyhow::anyhow!("e")) } - /// }); - /// - /// let stop_handle = stop_handle.clone(); - /// // Upgrade the connection to a HTTP service with graceful shutdown. - /// tokio::spawn(async move { - /// let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); - /// let conn = builder.serve_connection_with_upgrades(TokioIo::new(sock), svc); - /// let stopped = stop_handle.shutdown(); - /// - /// // Pin the future so that it can be polled. - /// tokio::pin!(stopped, conn); - /// - /// let res = match future::select(conn, stopped).await { - /// // Return the connection if not stopped. - /// Either::Left((conn, _)) => conn, - /// // If the server is stopped, we should gracefully shutdown - /// // the connection and poll it until it finishes. - /// Either::Right((_, mut conn)) => { - /// conn.as_mut().graceful_shutdown(); - /// conn.await - /// } + /// // The `tokio::select!` macro is used to wait for either of the + /// // listeners to accept a new connection or for the server to be + /// // stopped. + /// let (sock, remote_addr) = tokio::select! { + /// res = listener.accept() => { + /// match res { + /// Ok(sock) => sock, + /// Err(e) => { + /// tracing::error!("failed to accept v4 connection: {:?}", e); + /// continue; + /// } + /// } + /// } + /// _ = stop_handle.clone().shutdown() => break, /// }; /// - /// // Log any errors that might have occurred. - /// if let Err(err) = res { - /// tracing::error!(err=?err, "HTTP connection failed"); - /// } - /// }); - /// } + /// let stop_handle2 = stop_handle.clone(); + /// let svc_builder2 = svc_builder.clone(); + /// let methods2 = methods.clone(); + /// + /// let svc = hyper::service::service_fn(move |req| { + /// let stop_handle = stop_handle2.clone(); + /// let svc_builder = svc_builder2.clone(); + /// let methods = methods2.clone(); + /// + /// let mut svc = svc_builder.build(methods, stop_handle.clone()); + /// + /// // It's not possible to know whether the websocket upgrade handshake failed or not here. + /// let is_websocket = ws::is_upgrade_request(&req); + /// + /// if is_websocket { + /// println!("websocket") + /// } else { + /// println!("http") + /// } + /// + /// // Call the jsonrpsee service which + /// // may upgrade it to a WebSocket connection + /// // or treat it as "ordinary HTTP request". + /// // + /// // 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. + /// async move { svc.call(req).await.map_err(|e| anyhow::anyhow!("{:?}", e)) } + /// }); + /// + /// let stop_handle = stop_handle.clone(); + /// // Upgrade the connection to a HTTP service with graceful shutdown. + /// tokio::spawn(async move { + /// let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); + /// let conn = builder.serve_connection_with_upgrades(TokioIo::new(sock), svc); + /// let stopped = stop_handle.shutdown(); + /// + /// // Pin the future so that it can be polled. + /// tokio::pin!(stopped, conn); + /// + /// let res = match future::select(conn, stopped).await { + /// // Return the connection if not stopped. + /// Either::Left((conn, _)) => conn, + /// // If the server is stopped, we should gracefully shutdown + /// // the connection and poll it until it finishes. + /// Either::Right((_, mut conn)) => { + /// conn.as_mut().graceful_shutdown(); + /// conn.await + /// } + /// }; + /// + /// // Log any errors that might have occurred. + /// if let Err(err) = res { + /// tracing::error!(err=?err, "HTTP connection failed"); + /// } + /// }); + /// } /// }); /// /// server_handle @@ -1053,7 +1053,7 @@ where } fn call(&mut self, request: HttpRequest) -> Self::Future { - let request = request.map(HttpBody::new); + let request = request.map(HttpBoxBody::new); let conn_guard = &self.inner.conn_guard; let stop_handle = self.inner.stop_handle.clone(); @@ -1141,11 +1141,11 @@ where .in_current_span(), ); - response.map(|()| HttpResponseBody::default()) + response.map(|()| HttpBody::default()) } Err(e) => { tracing::debug!(target: LOG_TARGET, "Could not upgrade connection: {}", e); - HttpResponse::new(HttpResponseBody::from(format!("Could not upgrade connection: {e}"))) + HttpResponse::new(HttpBody::from(format!("Could not upgrade connection: {e}"))) } }; diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index 7ee57167d5..b23ec133cb 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -112,7 +112,7 @@ pub mod response { use jsonrpsee_types::error::{reject_too_big_request, ErrorCode}; use jsonrpsee_types::{ErrorObjectOwned, Id, Response, ResponsePayload}; - use crate::{HttpResponse, HttpResponseBody}; + use crate::{HttpBody, HttpResponse}; const JSON: &str = "application/json; charset=utf-8"; const TEXT: &str = "text/plain"; @@ -158,11 +158,7 @@ pub mod response { } /// Create a response body. - fn from_template( - status: hyper::StatusCode, - body: impl Into, - content_type: &'static str, - ) -> HttpResponse { + fn from_template(status: hyper::StatusCode, body: impl Into, content_type: &'static str) -> HttpResponse { HttpResponse::builder() .status(status) .header("content-type", hyper::header::HeaderValue::from_static(content_type)) @@ -173,7 +169,7 @@ pub mod response { } /// Create a valid JSON response. - pub fn ok_response(body: impl Into) -> HttpResponse { + pub fn ok_response(body: impl Into) -> HttpResponse { from_template(hyper::StatusCode::OK, body, JSON) } @@ -193,6 +189,6 @@ pub mod response { /// Create a response for when the server denied the request. pub fn denied() -> HttpResponse { - from_template(hyper::StatusCode::FORBIDDEN, HttpResponseBody::default(), TEXT) + from_template(hyper::StatusCode::FORBIDDEN, HttpBody::default(), TEXT) } } diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 056bd0e8cb..a1e3d673a1 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -4,7 +4,7 @@ use std::time::Instant; use crate::future::{IntervalStream, SessionClose}; use crate::middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}; use crate::server::{handle_rpc_call, ConnectionState, ServerConfig}; -use crate::{HttpRequest, HttpResponse, HttpResponseBody, PingConfig, LOG_TARGET}; +use crate::{HttpBody, HttpRequest, HttpResponse, PingConfig, LOG_TARGET}; use futures_util::future::{self, Either}; use futures_util::io::{BufReader, BufWriter}; @@ -420,7 +420,7 @@ where Ok(u) => u, Err(e) => { tracing::debug!(target: LOG_TARGET, "WS upgrade handshake failed: {}", e); - return Err(HttpResponse::new(HttpResponseBody::from(format!("WS upgrade handshake failed {e}")))); + return Err(HttpResponse::new(HttpBody::from(format!("WS upgrade handshake failed {e}")))); } }; @@ -449,6 +449,8 @@ where let rpc_service = rpc_middleware.service(rpc_service); + // Note: This can't possibly be fulfilled until the HTTP response + // is returned below, so that's why it's a separate async block let fut = async move { let stream = BufReader::new(BufWriter::new(io.compat())); let mut ws_builder = server.into_builder(stream); @@ -470,11 +472,11 @@ where background_task(params).await; }; - Ok((response.map(|()| HttpResponseBody::default()), fut)) + Ok((response.map(|()| HttpBody::default()), fut)) } Err(e) => { tracing::debug!(target: LOG_TARGET, "WS upgrade handshake failed: {}", e); - Err(HttpResponse::new(HttpResponseBody::from(format!("WS upgrade handshake failed: {e}")))) + Err(HttpResponse::new(HttpBody::from(format!("WS upgrade handshake failed: {e}")))) } } } diff --git a/server/src/utils.rs b/server/src/utils.rs index 93214e5a70..dd377ca677 100644 --- a/server/src/utils.rs +++ b/server/src/utils.rs @@ -27,7 +27,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; -use crate::{HttpBody, HttpRequest}; +use crate::{HttpBoxBody, HttpRequest}; use pin_project::pin_project; use tower::util::Oneshot; @@ -53,7 +53,7 @@ where type Future = TowerToHyperServiceFuture; fn call(&self, req: HttpRequest) -> Self::Future { - let req = req.map(HttpBody::new); + let req = req.map(HttpBoxBody::new); TowerToHyperServiceFuture { future: self.service.clone().oneshot(req) } } } diff --git a/test-utils/src/helpers.rs b/test-utils/src/helpers.rs index 4c47a39f33..275c23c883 100644 --- a/test-utils/src/helpers.rs +++ b/test-utils/src/helpers.rs @@ -29,7 +29,7 @@ use std::net::SocketAddr; use crate::mocks::{HttpResponse, Id, Uri}; use http_body_util::BodyExt; -use hyper::{service::service_fn, Request, Response}; +use hyper::{service::service_fn, Response}; use hyper_util::{ client::legacy::Client, rt::{TokioExecutor, TokioIo}, From bb3dbd59f7d39f04743347b8d6c0ef6d224efc02 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 21 May 2024 15:35:35 +0200 Subject: [PATCH 22/22] unify http body in client/server --- client/http-client/Cargo.toml | 1 - client/ws-client/src/tests.rs | 2 - core/src/http_helpers.rs | 37 +++++++++++---- .../jsonrpsee_server_low_level_api.rs | 3 -- server/src/lib.rs | 7 +-- .../src/middleware/http/proxy_get_request.rs | 6 +-- server/src/server.rs | 46 +++++++++---------- server/src/utils.rs | 4 +- 8 files changed, 56 insertions(+), 50 deletions(-) diff --git a/client/http-client/Cargo.toml b/client/http-client/Cargo.toml index b414b1365e..9ebe0dd712 100644 --- a/client/http-client/Cargo.toml +++ b/client/http-client/Cargo.toml @@ -19,7 +19,6 @@ hyper = { version = "1.3", features = ["client", "http1", "http2"] } hyper-rustls = { version = "0.27.1", optional = true, default-features = false, features = ["http1", "http2", "tls12", "logging"] } hyper-util = { version = "0.1.1", features = ["client", "client-legacy"] } http-body = "1" -http-body-util = "0.1.1" jsonrpsee-types = { workspace = true } jsonrpsee-core = { workspace = true, features = ["client", "http-helpers"] } serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/client/ws-client/src/tests.rs b/client/ws-client/src/tests.rs index 45b1bf517b..25b7d7d12c 100644 --- a/client/ws-client/src/tests.rs +++ b/client/ws-client/src/tests.rs @@ -461,8 +461,6 @@ fn assert_error_response(err: Error, exp: ErrorObjectOwned) { #[tokio::test] async fn redirections() { - init_logger(); - let expected = "abc 123"; let server = WebSocketTestServer::with_hardcoded_response( "127.0.0.1:0".parse().unwrap(), diff --git a/core/src/http_helpers.rs b/core/src/http_helpers.rs index c6d72364ea..e0b0cf873a 100644 --- a/core/src/http_helpers.rs +++ b/core/src/http_helpers.rs @@ -36,19 +36,15 @@ use std::{ }; /// HTTP request type. -pub use http::Request; - +pub type Request = http::Request; /// HTTP response type. -pub use http::Response; - -/// HTTP response body. -pub type Body = http_body_util::Full; +pub type Response = http::Response; -/// A HTTP request body. +/// Default HTTP body used by jsonrpsee. #[derive(Debug, Default)] -pub struct BoxBody(http_body_util::combinators::UnsyncBoxBody); +pub struct Body(http_body_util::combinators::UnsyncBoxBody); -impl BoxBody { +impl Body { /// Create an empty body. pub fn empty() -> Self { Self::default() @@ -65,7 +61,28 @@ impl BoxBody { } } -impl http_body::Body for BoxBody { +impl From for Body { + fn from(s: String) -> Self { + let body = http_body_util::Full::from(s); + Self::new(body) + } +} + +impl From<&'static str> for Body { + fn from(s: &'static str) -> Self { + let body = http_body_util::Full::from(s); + Self::new(body) + } +} + +impl From> for Body { + fn from(bytes: Vec) -> Self { + let body = http_body_util::Full::from(bytes); + Self::new(body) + } +} + +impl http_body::Body for Body { type Data = Bytes; type Error = BoxError; diff --git a/examples/examples/jsonrpsee_server_low_level_api.rs b/examples/examples/jsonrpsee_server_low_level_api.rs index 6c16e6faf8..9d6e194c3f 100644 --- a/examples/examples/jsonrpsee_server_low_level_api.rs +++ b/examples/examples/jsonrpsee_server_low_level_api.rs @@ -51,7 +51,6 @@ use jsonrpsee::core::async_trait; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::middleware::rpc::RpcServiceT; -use jsonrpsee::server::HttpBoxBody; use jsonrpsee::server::{ http, stop_channel, ws, ConnectionGuard, ConnectionState, RpcServiceBuilder, ServerConfig, ServerHandle, StopHandle, }; @@ -226,8 +225,6 @@ async fn run_server() -> anyhow::Result { let stop_handle2 = per_conn.stop_handle.clone(); let per_conn = per_conn.clone(); let svc = service_fn(move |req| { - let req = req.map(HttpBoxBody::new); - let PerConnection { methods, stop_handle, diff --git a/server/src/lib.rs b/server/src/lib.rs index 2555f8e15e..7c0d710f0d 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -53,13 +53,8 @@ pub use server::{ }; pub use tracing; -pub use jsonrpsee_core::http_helpers::{Body as HttpBody, BoxBody as HttpBoxBody}; +pub use jsonrpsee_core::http_helpers::{Body as HttpBody, Request as HttpRequest, Response as HttpResponse}; pub use transport::http; pub use transport::ws; -/// HTTP request with boxed body. -pub type HttpRequest = jsonrpsee_core::http_helpers::Request; -/// HTTP response with concrete body. -pub type HttpResponse = jsonrpsee_core::http_helpers::Response; - pub(crate) const LOG_TARGET: &str = "jsonrpsee-server"; diff --git a/server/src/middleware/http/proxy_get_request.rs b/server/src/middleware/http/proxy_get_request.rs index 96c50edbe2..a2491cf8a6 100644 --- a/server/src/middleware/http/proxy_get_request.rs +++ b/server/src/middleware/http/proxy_get_request.rs @@ -27,7 +27,7 @@ //! Middleware that proxies requests at a specified URI to internal //! RPC method calls. use crate::transport::http; -use crate::{HttpBoxBody, HttpRequest, HttpResponse}; +use crate::{HttpBody, HttpRequest, HttpResponse}; use http_body_util::BodyExt; use hyper::body::Bytes; @@ -149,11 +149,11 @@ where let bytes = serde_json::to_vec(&RequestSer::borrowed(&Id::Number(0), &self.method, None)) .expect("Valid request; qed"); - let body = HttpBoxBody::new(http_body_util::Full::new(bytes.into())); + let body = HttpBody::from(bytes); req.map(|_| body) } else { - req.map(HttpBoxBody::new) + req.map(HttpBody::new) }; // Call the inner service and get a future that resolves to the response. diff --git a/server/src/server.rs b/server/src/server.rs index b40352418a..06dd28bbe5 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -37,7 +37,7 @@ use crate::future::{session_close, ConnectionGuard, ServerHandle, SessionClose, use crate::middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceCfg, RpcServiceT}; use crate::transport::ws::BackgroundTaskParams; use crate::transport::{http, ws}; -use crate::{HttpBody, HttpBoxBody, HttpRequest, HttpResponse, LOG_TARGET}; +use crate::{HttpBody, HttpRequest, HttpResponse, LOG_TARGET}; use futures_util::future::{self, Either, FutureExt}; use futures_util::io::{BufReader, BufWriter}; @@ -95,17 +95,17 @@ impl Server { } } -impl Server +impl Server where RpcMiddleware: tower::Layer + Clone + Send + 'static, for<'a> >::Service: RpcServiceT<'a>, HttpMiddleware: Layer> + Send + 'static, >>::Service: - Send + Clone + Service, Error = BoxError>, + Send + Clone + Service, Error = BoxError>, <>>::Service as Service>::Future: Send, - B: http_body::Body + Send + 'static, - ::Error: Into, - ::Data: Send, + Body: http_body::Body + Send + 'static, + ::Error: Into, + ::Data: Send, { /// Start responding to connections requests. /// @@ -995,18 +995,18 @@ impl TowerService } } -impl Service> for TowerService +impl Service> for TowerService where RpcMiddleware: for<'a> tower::Layer + Clone, >::Service: Send + Sync + 'static, for<'a> >::Service: RpcServiceT<'a>, HttpMiddleware: Layer> + Send + 'static, >>::Service: - Send + Service, Response = HttpResponse, Error = Box<(dyn StdError + Send + Sync + 'static)>>, - <>>::Service as Service>>::Future: + Send + Service, Response = HttpResponse, Error = Box<(dyn StdError + Send + Sync + 'static)>>, + <>>::Service as Service>>::Future: Send + 'static, - B: http_body::Body + Send + 'static, - B::Error: Into, + Body: http_body::Body + Send + 'static, + Body::Error: Into, { type Response = HttpResponse; type Error = BoxError; @@ -1016,7 +1016,7 @@ where Poll::Ready(Ok(())) } - fn call(&mut self, request: HttpRequest) -> Self::Future { + fn call(&mut self, request: HttpRequest) -> Self::Future { Box::pin(self.http_middleware.service(self.rpc_middleware.clone()).call(request)) } } @@ -1032,13 +1032,13 @@ pub struct TowerServiceNoHttp { on_session_close: Option, } -impl Service> for TowerServiceNoHttp +impl Service> for TowerServiceNoHttp where RpcMiddleware: for<'a> tower::Layer, >::Service: Send + Sync + 'static, for<'a> >::Service: RpcServiceT<'a>, - B: http_body::Body + Send + 'static, - B::Error: Into, + Body: http_body::Body + Send + 'static, + Body::Error: Into, { type Response = HttpResponse; @@ -1052,8 +1052,8 @@ where Poll::Ready(Ok(())) } - fn call(&mut self, request: HttpRequest) -> Self::Future { - let request = request.map(HttpBoxBody::new); + fn call(&mut self, request: HttpRequest) -> Self::Future { + let request = request.map(HttpBody::new); let conn_guard = &self.inner.conn_guard; let stop_handle = self.inner.stop_handle.clone(); @@ -1141,7 +1141,7 @@ where .in_current_span(), ); - response.map(|()| HttpBody::default()) + response.map(|()| HttpBody::empty()) } Err(e) => { tracing::debug!(target: LOG_TARGET, "Could not upgrade connection: {}", e); @@ -1188,17 +1188,17 @@ struct ProcessConnection<'a, HttpMiddleware, RpcMiddleware> { } #[instrument(name = "connection", skip_all, fields(remote_addr = %params.remote_addr, conn_id = %params.conn_id), level = "INFO")] -fn process_connection<'a, RpcMiddleware, HttpMiddleware, B>(params: ProcessConnection) +fn process_connection<'a, RpcMiddleware, HttpMiddleware, Body>(params: ProcessConnection) where RpcMiddleware: 'static, HttpMiddleware: Layer> + Send + 'static, >>::Service: - Send + 'static + Clone + Service, Error = BoxError>, + Send + 'static + Clone + Service, Error = BoxError>, <>>::Service as Service>::Future: Send + 'static, - B: http_body::Body + Send + 'static, - ::Error: Into, - ::Data: Send, + Body: http_body::Body + Send + 'static, + ::Error: Into, + ::Data: Send, { let ProcessConnection { http_middleware, diff --git a/server/src/utils.rs b/server/src/utils.rs index dd377ca677..93214e5a70 100644 --- a/server/src/utils.rs +++ b/server/src/utils.rs @@ -27,7 +27,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; -use crate::{HttpBoxBody, HttpRequest}; +use crate::{HttpBody, HttpRequest}; use pin_project::pin_project; use tower::util::Oneshot; @@ -53,7 +53,7 @@ where type Future = TowerToHyperServiceFuture; fn call(&self, req: HttpRequest) -> Self::Future { - let req = req.map(HttpBoxBody::new); + let req = req.map(HttpBody::new); TowerToHyperServiceFuture { future: self.service.clone().oneshot(req) } } }