This commit is contained in:
Maxime Van Hees
2025-08-14 14:14:34 +02:00
parent 04a1af2423
commit 0ebda7c1aa
59 changed files with 6950 additions and 354 deletions

View File

@@ -0,0 +1,127 @@
// 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::net::SocketAddr;
use std::time::Duration;
use futures::{Stream, StreamExt};
use jsonrpsee::core::DeserializeOwned;
use jsonrpsee::core::client::{Subscription, SubscriptionClientT};
use jsonrpsee::rpc_params;
use jsonrpsee::server::{RpcModule, Server};
use jsonrpsee::ws_client::WsClientBuilder;
use tokio_stream::wrappers::BroadcastStream;
use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let addr = run_server().await?;
let url = format!("ws://{}", addr);
let client = WsClientBuilder::default().build(&url).await?;
let sub: Subscription<i32> = client.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await?;
// drop oldest messages from subscription:
let mut sub = drop_oldest_when_lagging(sub, 10);
// Simulate that polling takes a long time.
tokio::time::sleep(Duration::from_secs(1)).await;
// The subscription starts from zero but you can
// notice that many items have been replaced
// because the subscription wasn't polled.
for _ in 0..10 {
match sub.next().await.unwrap() {
Ok(n) => {
tracing::info!("recv={n}");
}
Err(e) => {
tracing::info!("{e}");
}
};
}
Ok(())
}
fn drop_oldest_when_lagging<T: Clone + DeserializeOwned + Send + Sync + 'static>(
mut sub: Subscription<T>,
buffer_size: usize,
) -> impl Stream<Item = Result<T, BroadcastStreamRecvError>> {
let (tx, rx) = tokio::sync::broadcast::channel(buffer_size);
tokio::spawn(async move {
// Poll the subscription which ignores errors.
while let Some(n) = sub.next().await {
let msg = match n {
Ok(msg) => msg,
Err(e) => {
tracing::error!("Failed to decode the subscription message: {e}");
continue;
}
};
if tx.send(msg).is_err() {
return;
}
}
});
BroadcastStream::new(rx)
}
async fn run_server() -> anyhow::Result<SocketAddr> {
let server = Server::builder().build("127.0.0.1:0").await?;
let mut module = RpcModule::new(());
module
.register_subscription("subscribe_hello", "s_hello", "unsubscribe_hello", |_, pending, _, _| async move {
let sub = pending.accept().await.unwrap();
for i in 0..usize::MAX {
let json = serde_json::value::to_raw_value(&i).unwrap();
sub.send(json).await.unwrap();
tokio::time::sleep(Duration::from_millis(10)).await;
}
Ok(())
})
.unwrap();
let addr = server.local_addr()?;
let handle = server.start(module);
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,65 @@
// 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::net::SocketAddr;
use jsonrpsee::client_transport::ws::{Url, WsTransportClientBuilder};
use jsonrpsee::core::client::{ClientBuilder, ClientT};
use jsonrpsee::rpc_params;
use jsonrpsee::server::{RpcModule, Server};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let addr = run_server().await?;
let uri = Url::parse(&format!("ws://{}", addr))?;
let (tx, rx) = WsTransportClientBuilder::default().build(uri).await?;
let client = ClientBuilder::default().build_with_tokio(tx, rx);
let response: String = client.request("say_hello", rpc_params![]).await?;
tracing::info!("response: {:?}", response);
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
let server = Server::builder().build("127.0.0.1:0").await?;
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.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,104 @@
// Copyright 2019-2022 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.
//! This example adds upstream CORS layers to the RPC service,
//! with access control allowing requests from all hosts.
use hyper::Method;
use jsonrpsee::server::{RpcModule, Server};
use std::net::SocketAddr;
use tower_http::cors::{Any, CorsLayer};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
// Start up a JSON-RPC server that allows cross origin requests.
let server_addr = run_server().await?;
// Print instructions for testing CORS from a browser.
println!("Run the following snippet in the developer console in any Website.");
println!(
r#"
fetch("http://{}", {{
method: 'POST',
mode: 'cors',
headers: {{ 'Content-Type': 'application/json' }},
body: JSON.stringify({{
jsonrpc: '2.0',
method: 'say_hello',
id: 1
}})
}}).then(res => {{
console.log("Response:", res);
return res.text()
}}).then(body => {{
console.log("Response Body:", body)
}});
"#,
server_addr
);
futures::future::pending().await
}
async fn run_server() -> anyhow::Result<SocketAddr> {
// Add a CORS middleware for handling HTTP requests.
// This middleware does affect the response, including appropriate
// headers to satisfy CORS. Because any origins are allowed, the
// "Access-Control-Allow-Origin: *" header is appended to the response.
let cors = CorsLayer::new()
// Allow `POST` when accessing the resource
.allow_methods([Method::POST])
// Allow requests from any origin
.allow_origin(Any)
.allow_headers([hyper::header::CONTENT_TYPE]);
let middleware = tower::ServiceBuilder::new().layer(cors);
// The RPC exposes the access control for filtering and the middleware for
// modifying requests / responses. These features are independent of one another
// and can also be used separately.
// In this example, we use both features.
let server = Server::builder().set_http_middleware(middleware).build("127.0.0.1:0".parse::<SocketAddr>()?).await?;
let mut module = RpcModule::new(());
module.register_method("say_hello", |_, _, _| {
println!("say_hello method called!");
"Hello there!!"
})?;
let addr = server.local_addr()?;
let handle = server.start(module);
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,83 @@
// Copyright 2019-2022 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.
//! This example shows how to configure `host filtering` by tower middleware on the jsonrpsee server.
//!
//! The server whitelist's only `example.com` and any call from localhost will be
//! rejected both by HTTP and WebSocket transports.
use std::net::SocketAddr;
use jsonrpsee::core::client::ClientT;
use jsonrpsee::http_client::HttpClient;
use jsonrpsee::rpc_params;
use jsonrpsee::server::middleware::http::HostFilterLayer;
use jsonrpsee::server::{RpcModule, Server};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let addr = run_server().await?;
let url = format!("http://{}", addr);
// Use RPC client to get the response of `say_hello` method.
let client = HttpClient::builder().build(&url)?;
// This call will be denied because only `example.com` URIs/hosts are allowed by the host filter.
let response = client.request::<String, _>("say_hello", rpc_params![]).await.unwrap_err();
println!("[main]: response: {}", response);
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
// Custom tower service to handle the RPC requests
let service_builder = tower::ServiceBuilder::new()
// For this example we only want to permit requests from `example.com`
// all other request are denied.
//
// `HostFilerLayer::new` only fails on invalid URIs..
.layer(HostFilterLayer::new(["example.com"]).unwrap());
let server =
Server::builder().set_http_middleware(service_builder).build("127.0.0.1:0".parse::<SocketAddr>()?).await?;
let addr = server.local_addr()?;
let mut module = RpcModule::new(());
module.register_method("say_hello", |_, _, _| "lo").unwrap();
let handle = server.start(module);
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,65 @@
// 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::net::SocketAddr;
use jsonrpsee::core::client::ClientT;
use jsonrpsee::http_client::HttpClient;
use jsonrpsee::rpc_params;
use jsonrpsee::server::{RpcModule, Server};
use tracing_subscriber::util::SubscriberInitExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let filter = tracing_subscriber::EnvFilter::try_from_default_env()?
.add_directive("jsonrpsee[method_call{name = \"say_hello\"}]=trace".parse()?);
tracing_subscriber::FmtSubscriber::builder().with_env_filter(filter).finish().try_init()?;
let server_addr = run_server().await?;
let url = format!("http://{}", server_addr);
let client = HttpClient::builder().build(url)?;
let params = rpc_params![1_u64, 2, 3];
let response: Result<String, _> = client.request("say_hello", params).await;
tracing::info!("r: {:?}", response);
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
let server = Server::builder().build("127.0.0.1:0".parse::<SocketAddr>()?).await?;
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.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,129 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
//
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the
// Software without restriction, including without
// limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//! jsonrpsee supports two kinds of middlewares `http_middleware` and `rpc_middleware`.
//!
//! This example demonstrates how to use the `http_middleware` which applies for each
//! HTTP request.
//!
//! A typical use-case for this it to apply a specific CORS policy which applies both
//! for HTTP and WebSocket.
//!
use hyper::Method;
use hyper::body::Bytes;
use hyper::http::HeaderValue;
use jsonrpsee::rpc_params;
use std::iter::once;
use std::net::SocketAddr;
use std::time::Duration;
use tower_http::LatencyUnit;
use tower_http::compression::CompressionLayer;
use tower_http::cors::CorsLayer;
use tower_http::sensitive_headers::SetSensitiveRequestHeadersLayer;
use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer};
use jsonrpsee::core::client::ClientT;
use jsonrpsee::http_client::HttpClient;
use jsonrpsee::server::{RpcModule, Server};
use jsonrpsee::ws_client::WsClientBuilder;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let addr = run_server().await?;
// WebSocket.
{
let client = WsClientBuilder::default().build(format!("ws://{}", addr)).await?;
let response: String = client.request("say_hello", rpc_params![]).await?;
println!("[main]: ws response: {:?}", response);
let _response: Result<String, _> = client.request("unknown_method", rpc_params![]).await;
let _ = client.request::<String, _>("say_hello", rpc_params![]).await?;
}
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
// HTTP.
{
let client = HttpClient::builder().build(format!("http://{}", addr))?;
let response: String = client.request("say_hello", rpc_params![]).await?;
println!("[main]: http response: {:?}", response);
let _response: Result<String, _> = client.request("unknown_method", rpc_params![]).await;
let _ = client.request::<String, _>("say_hello", rpc_params![]).await?;
}
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
let cors = CorsLayer::new()
// Allow `POST` when accessing the resource
.allow_methods([Method::POST])
// Allow requests from any origin
.allow_origin(HeaderValue::from_str("http://example.com").unwrap())
.allow_headers([hyper::header::CONTENT_TYPE]);
// Custom tower service to handle the RPC requests
let service_builder = tower::ServiceBuilder::new()
// 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_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| {
tracing::info!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk")
})
.make_span_with(DefaultMakeSpan::new().include_headers(true))
.on_response(DefaultOnResponse::new().include_headers(true).latency_unit(LatencyUnit::Micros)),
)
// Mark the `Authorization` request header as sensitive so it doesn't show in logs
.layer(SetSensitiveRequestHeadersLayer::new(once(hyper::header::AUTHORIZATION)))
.layer(cors)
.layer(CompressionLayer::new())
.timeout(Duration::from_secs(2));
let server =
Server::builder().set_http_middleware(service_builder).build("127.0.0.1:0".parse::<SocketAddr>()?).await?;
let addr = server.local_addr()?;
let mut module = RpcModule::new(());
module.register_method("say_hello", |_, _, _| "lo").unwrap();
let handle = server.start(module);
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,109 @@
// Copyright 2019-2022 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.
//! This example utilizes the `ProxyRequest` layer for redirecting
//! `GET /path` requests to internal RPC methods.
//!
//! The RPC server registers a method named `system_health` which
//! returns `serde_json::Value`. Redirect any `GET /health`
//! requests to the internal method, and return only the method's
//! response in the body (ie, without any jsonRPC 2.0 overhead).
//!
//! # Note
//!
//! This functionality is useful for services which would
//! like to query a certain `URI` path for statistics.
use hyper_util::client::legacy::Client;
use hyper_util::rt::TokioExecutor;
use std::net::SocketAddr;
use std::time::Duration;
use jsonrpsee::core::client::ClientT;
use jsonrpsee::http_client::HttpClient;
use jsonrpsee::rpc_params;
use jsonrpsee::server::middleware::http::ProxyGetRequestLayer;
use jsonrpsee::server::{RpcModule, Server};
type EmptyBody = http_body_util::Empty<hyper::body::Bytes>;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let addr = run_server().await?;
let url = format!("http://{}", addr);
// Use RPC client to get the response of `say_hello` method.
let client = HttpClient::builder().build(&url)?;
let response: String = client.request("say_hello", rpc_params![]).await?;
println!("[main]: response: {:?}", response);
// Use hyper client to manually submit a `GET /health` request.
let http_client = Client::builder(TokioExecutor::new()).build_http();
let uri = format!("http://{}/health", addr);
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);
// Interpret the response as String.
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}");
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
// Custom tower service to handle the RPC requests
let service_builder = tower::ServiceBuilder::new()
// Proxy `GET /health` requests to internal `system_health` method.
.layer(ProxyGetRequestLayer::new([("/health", "system_health")])?)
.timeout(Duration::from_secs(2));
let server =
Server::builder().set_http_middleware(service_builder).build("127.0.0.1:0".parse::<SocketAddr>()?).await?;
let addr = server.local_addr()?;
let mut module = RpcModule::new(());
module.register_method("say_hello", |_, _, _| "lo").unwrap();
module.register_method("system_health", |_, _, _| serde_json::json!({ "health": true })).unwrap();
let handle = server.start(module);
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,380 @@
// 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.
//! This example shows how to use the `jsonrpsee::server` as
//! a tower service such that it's possible to get access
//! HTTP related things by launching a `hyper::service_fn`.
//!
//! The typical use-case for this is when one wants to have
//! access to HTTP related things.
use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use futures::FutureExt;
use hyper::HeaderMap;
use hyper::header::AUTHORIZATION;
use jsonrpsee::core::async_trait;
use jsonrpsee::core::middleware::{Batch, BatchEntry, BatchEntryErr, Notification, RpcServiceBuilder, RpcServiceT};
use jsonrpsee::http_client::HttpClient;
use jsonrpsee::proc_macros::rpc;
use jsonrpsee::server::middleware::http::{HostFilterLayer, ProxyGetRequestLayer};
use jsonrpsee::server::{
ServerConfig, ServerHandle, StopHandle, TowerServiceBuilder, serve_with_graceful_shutdown, stop_channel,
};
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;
#[derive(Clone)]
struct IdentityLayer;
impl<S> tower::Layer<S> for IdentityLayer
where
S: RpcServiceT + Send + Sync + Clone + 'static,
{
type Service = Identity<S>;
fn layer(&self, inner: S) -> Self::Service {
Identity(inner)
}
}
#[derive(Clone)]
struct Identity<S>(S);
impl<S> RpcServiceT for Identity<S>
where
S: RpcServiceT + Send + Sync + Clone + 'static,
{
type MethodResponse = S::MethodResponse;
type BatchResponse = S::BatchResponse;
type NotificationResponse = S::NotificationResponse;
fn batch<'a>(&self, batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
self.0.batch(batch)
}
fn call<'a>(&self, request: Request<'a>) -> impl Future<Output = Self::MethodResponse> + Send + 'a {
self.0.call(request)
}
fn notification<'a>(&self, n: Notification<'a>) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
self.0.notification(n)
}
}
#[derive(Default, Clone, Debug)]
struct Metrics {
opened_ws_connections: Arc<AtomicUsize>,
closed_ws_connections: Arc<AtomicUsize>,
http_calls: Arc<AtomicUsize>,
success_http_calls: Arc<AtomicUsize>,
}
fn auth_reject_error() -> ErrorObjectOwned {
ErrorObject::owned(-32999, "HTTP Authorization header is missing", None::<()>)
}
#[derive(Clone)]
struct AuthorizationMiddleware<S> {
headers: HeaderMap,
inner: S,
#[allow(unused)]
transport_label: &'static str,
}
impl<S> AuthorizationMiddleware<S> {
/// Authorize the request by checking the `Authorization` header.
///
///
/// In this example for simplicity, the authorization value is not checked
// and used because it's just a toy example.
fn auth_method_call(&self, req: &Request<'_>) -> bool {
if req.method_name() == "trusted_call" {
let Some(Ok(_)) = self.headers.get(AUTHORIZATION).map(|auth| auth.to_str()) else { return false };
}
true
}
/// Authorize the notification by checking the `Authorization` header.
///
/// Because notifications are not expected to return a response, we
/// return a `MethodResponse` by injecting an error into the extensions
/// which could be read by other middleware or the server.
fn auth_notif(&self, notif: &Notification<'_>) -> bool {
if notif.method_name() == "trusted_call" {
let Some(Ok(_)) = self.headers.get(AUTHORIZATION).map(|auth| auth.to_str()) else { return false };
}
true
}
}
impl<S> RpcServiceT for AuthorizationMiddleware<S>
where
// We need to specify the concrete types here because otherwise we return an error or specific response
// in the middleware implementation.
S: RpcServiceT<MethodResponse = MethodResponse, BatchResponse = MethodResponse> + Send + Sync + Clone + 'static,
{
type MethodResponse = S::MethodResponse;
type BatchResponse = S::BatchResponse;
type NotificationResponse = S::NotificationResponse;
fn call<'a>(&self, req: Request<'a>) -> impl Future<Output = Self::MethodResponse> + Send + 'a {
let this = self.clone();
let auth_ok = this.auth_method_call(&req);
async move {
// If the authorization header is missing, it's recommended to
// to return the response as MethodResponse::error instead of
// returning an error from the service.
//
// This way the error is returned as a JSON-RPC error
if !auth_ok {
return MethodResponse::error(req.id, auth_reject_error());
}
this.inner.call(req).await
}
}
fn batch<'a>(&self, batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
// Check the authorization header for each entry in the batch.
let entries: Vec<_> = batch
.into_iter()
.filter_map(|entry| match entry {
Ok(BatchEntry::Call(req)) => {
if self.auth_method_call(&req) {
Some(Ok(BatchEntry::Call(req)))
} else {
// If the authorization header is missing, we return
// a JSON-RPC error instead of an error from the service.
Some(Err(BatchEntryErr::new(req.id, auth_reject_error())))
}
}
Ok(BatchEntry::Notification(notif)) => {
if self.auth_notif(&notif) {
Some(Ok(BatchEntry::Notification(notif)))
} else {
// Just filter out the notification if the auth fails
// because notifications are not expected to return a response.
None
}
}
// Errors which could happen such as invalid JSON-RPC call
// or invalid JSON are just passed through.
Err(err) => Some(Err(err)),
})
.collect();
self.inner.batch(Batch::from(entries))
}
fn notification<'a>(&self, n: Notification<'a>) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
self.inner.notification(n)
}
}
#[rpc(server, client)]
pub trait Rpc {
#[method(name = "trusted_call")]
async fn trusted_call(&self) -> Result<String, ErrorObjectOwned>;
}
#[async_trait]
impl RpcServer for () {
async fn trusted_call(&self) -> Result<String, ErrorObjectOwned> {
Ok("mysecret".to_string())
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let filter = tracing_subscriber::EnvFilter::try_from_default_env()?;
tracing_subscriber::FmtSubscriber::builder().with_env_filter(filter).finish().try_init()?;
let metrics = Metrics::default();
let handle = run_server(metrics.clone()).await?;
tokio::spawn(handle.stopped());
{
let client = HttpClient::builder().build("http://127.0.0.1:9944").unwrap();
// Fails because the authorization header is missing.
let x = client.trusted_call().await.unwrap_err();
tracing::info!("response: {x}");
}
{
let client = WsClientBuilder::default().build("ws://127.0.0.1:9944").await.unwrap();
// Fails because the authorization header is missing.
let x = client.trusted_call().await.unwrap_err();
tracing::info!("response: {x}");
}
{
let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, HeaderValue::from_static("don't care in this example"));
let client = HttpClient::builder().set_headers(headers).build("http://127.0.0.1:9944").unwrap();
let x = client.trusted_call().await.unwrap();
tracing::info!("response: {x}");
}
tracing::info!("{:?}", metrics);
Ok(())
}
async fn run_server(metrics: Metrics) -> anyhow::Result<ServerHandle> {
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
// be relatively cheap to clone them.
//
// Make sure that nothing expensive is cloned here
// when doing this or use an `Arc`.
#[derive(Clone)]
struct PerConnection<RpcMiddleware, HttpMiddleware> {
methods: Methods,
stop_handle: StopHandle,
metrics: Metrics,
svc_builder: TowerServiceBuilder<RpcMiddleware, HttpMiddleware>,
}
// Each RPC call/connection get its own `stop_handle`
// to able to determine whether the server has been stopped or not.
//
// To keep the server running the `server_handle`
// must be kept and it can also be used to stop the server.
let (stop_handle, server_handle) = stop_channel();
let per_conn = PerConnection {
methods: ().into_rpc().into(),
stop_handle: stop_handle.clone(),
metrics,
svc_builder: jsonrpsee::server::Server::builder()
.set_config(ServerConfig::builder().max_connections(33).build())
.set_http_middleware(
tower::ServiceBuilder::new()
.layer(CorsLayer::permissive())
.layer(ProxyGetRequestLayer::new(vec![("trusted_call", "foo")]).unwrap())
.layer(HostFilterLayer::new(["example.com"]).unwrap()),
)
.to_service_builder(),
};
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 = 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_conn2 = per_conn.clone();
let svc = tower::service_fn(move |req: hyper::Request<hyper::body::Incoming>| {
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,
})
.option_layer(Some(IdentityLayer));
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);
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);
}
tracing::info!("Closed HTTP connection");
rp
}
.boxed()
}
});
tokio::spawn(serve_with_graceful_shutdown(sock, svc, stop_handle.clone().shutdown()));
}
});
Ok(server_handle)
}

View File

@@ -0,0 +1,222 @@
// Copyright 2024 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.
//! This example shows how to use the low-level server API
//! in jsonrpsee and inject a `mpsc::Sender<()>` into the
//! request extensions to be able to close the connection from
//! a rpc handler (method call or subscription).
use std::convert::Infallible;
use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use futures::FutureExt;
use jsonrpsee::core::middleware::RpcServiceBuilder;
use jsonrpsee::core::{SubscriptionResult, async_trait};
use jsonrpsee::proc_macros::rpc;
use jsonrpsee::server::{
ConnectionGuard, ConnectionState, HttpRequest, ServerConfig, ServerHandle, StopHandle, http,
serve_with_graceful_shutdown, stop_channel, ws,
};
use jsonrpsee::types::ErrorObjectOwned;
use jsonrpsee::ws_client::WsClientBuilder;
use jsonrpsee::{Extensions, Methods, PendingSubscriptionSink};
use tokio::net::TcpListener;
use tokio::sync::mpsc;
use tracing_subscriber::util::SubscriberInitExt;
#[rpc(server, client)]
pub trait Rpc {
#[method(name = "closeConn", with_extensions)]
async fn close_conn(&self) -> Result<(), ErrorObjectOwned>;
#[subscription(name = "subscribeCloseConn", item = String, with_extensions)]
async fn close_conn_from_sub(&self) -> SubscriptionResult;
}
#[async_trait]
impl RpcServer for () {
async fn close_conn(&self, ext: &Extensions) -> Result<(), ErrorObjectOwned> {
let tx = ext.get::<mpsc::Sender<()>>().unwrap();
tx.send(()).await.unwrap();
Ok(())
}
async fn close_conn_from_sub(&self, _pending: PendingSubscriptionSink, ext: &Extensions) -> SubscriptionResult {
let tx = ext.get::<mpsc::Sender<()>>().unwrap();
tx.send(()).await.unwrap();
Ok(())
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let filter = tracing_subscriber::EnvFilter::try_from_default_env()?;
tracing_subscriber::FmtSubscriber::builder().with_env_filter(filter).finish().try_init()?;
let handle = run_server().await?;
{
let client = WsClientBuilder::default().build("ws://127.0.0.1:9944").await?;
let _ = client.close_conn().await;
client.on_disconnect().await;
eprintln!("Connection closed from RPC call");
}
{
let client = WsClientBuilder::default().build("ws://127.0.0.1:9944").await?;
let _ = client.close_conn_from_sub().await;
client.on_disconnect().await;
eprintln!("Connection closed from RPC subscription");
}
let _ = handle.stop();
handle.stopped().await;
Ok(())
}
async fn run_server() -> anyhow::Result<ServerHandle> {
// Construct our SocketAddr to listen on...
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.
//
// To keep the server running the `server_handle`
// must be kept and it can also be used to stop the server.
let (stop_handle, server_handle) = stop_channel();
// This state is cloned for every connection
// all these types based on Arcs and it should
// be relatively cheap to clone them.
//
// Make sure that nothing expensive is cloned here
// when doing this or use an `Arc`.
#[derive(Clone)]
struct PerConnection {
methods: Methods,
stop_handle: StopHandle,
conn_id: Arc<AtomicU32>,
conn_guard: ConnectionGuard,
}
let per_conn = PerConnection {
methods: ().into_rpc().into(),
stop_handle: stop_handle.clone(),
conn_id: Default::default(),
conn_guard: ConnectionGuard::new(100),
};
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, _) = 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();
// Create a service handler.
let stop_handle2 = per_conn.stop_handle.clone();
let per_conn = per_conn.clone();
let svc = tower::service_fn(move |mut req: HttpRequest<hyper::body::Incoming>| {
let PerConnection { methods, stop_handle, conn_guard, conn_id } = per_conn.clone();
let (tx, mut disconnect) = mpsc::channel::<()>(1);
// Insert the `tx` into the request extensions to be able to close the connection
// from method or subscription handlers.
req.extensions_mut().insert(tx.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();
};
let conn = ConnectionState::new(stop_handle, conn_id.fetch_add(1, Ordering::Relaxed), conn_permit);
if ws::is_upgrade_request(&req) {
let rpc_service = RpcServiceBuilder::new();
// Establishes the websocket connection
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() => {
eprintln!("Server closed connection");
},
}
});
Ok(rp)
}
Err(rp) => Ok(rp),
}
}
.boxed()
} else if !ws::is_upgrade_request(&req) {
// 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, ServerConfig::default(), conn, methods, RpcServiceBuilder::new()) => Ok(res),
// The connection was closed by a RPC handler
_ = disconnect.recv() => Ok(http::response::denied()),
}
}
.boxed()
} else {
async { Ok(http::response::denied()) }.boxed()
}
});
// Upgrade the connection to a HTTP service.
tokio::spawn(serve_with_graceful_shutdown(sock, svc, stop_handle2.shutdown()));
}
});
Ok(server_handle)
}

View File

@@ -0,0 +1,349 @@
// 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.
//! This example shows how to use the low-level server API
//! in jsonrpsee.
//!
//! The particular example disconnects peers that
//! makes more than ten RPC calls and bans the IP addr.
//!
//! NOTE:
//!
//! Enabling tower middleware in this example doesn't work,
//! to do so then the low-level API in hyper must be used.
//!
//! See <https://docs.rs/hyper/latest/hyper/server/conn/index.html>
//! for further information regarding the "low-level API" in hyper.
use std::collections::HashSet;
use std::convert::Infallible;
use std::net::{IpAddr, SocketAddr};
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
use futures::FutureExt;
use jsonrpsee::core::async_trait;
use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT};
use jsonrpsee::http_client::HttpClient;
use jsonrpsee::proc_macros::rpc;
use jsonrpsee::server::{
ConnectionGuard, ConnectionState, ServerConfig, ServerHandle, StopHandle, http, serve_with_graceful_shutdown,
stop_channel, ws,
};
use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, Id, Request};
use jsonrpsee::ws_client::WsClientBuilder;
use jsonrpsee::{MethodResponse, Methods};
use tokio::net::TcpListener;
use tokio::sync::Mutex as AsyncMutex;
use tokio::sync::mpsc;
use tracing_subscriber::util::SubscriberInitExt;
/// This is just a counter to limit
/// the number of calls per connection.
/// Once the limit has been exceeded
/// all future calls are rejected.
#[derive(Clone)]
struct CallLimit<S> {
service: S,
count: Arc<AsyncMutex<usize>>,
state: mpsc::Sender<()>,
}
impl<S> RpcServiceT for CallLimit<S>
where
S: RpcServiceT<
MethodResponse = MethodResponse,
BatchResponse = MethodResponse,
NotificationResponse = MethodResponse,
> + Send
+ Sync
+ Clone
+ 'static,
{
type MethodResponse = S::MethodResponse;
type NotificationResponse = S::NotificationResponse;
type BatchResponse = S::BatchResponse;
fn call<'a>(&self, req: Request<'a>) -> impl Future<Output = Self::MethodResponse> + Send + 'a {
let count = self.count.clone();
let state = self.state.clone();
let service = self.service.clone();
async move {
let mut lock = count.lock().await;
if *lock >= 10 {
let _ = state.try_send(());
MethodResponse::error(req.id, ErrorObject::borrowed(-32000, "RPC rate limit", None))
} else {
let rp = service.call(req).await;
*lock += 1;
rp
}
}
}
fn batch<'a>(&self, batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
let count = self.count.clone();
let state = self.state.clone();
let service = self.service.clone();
async move {
let mut lock = count.lock().await;
let batch_len = batch.len();
if *lock >= 10 + batch_len {
let _ = state.try_send(());
MethodResponse::error(Id::Null, ErrorObject::borrowed(-32000, "RPC rate limit", None))
} else {
let rp = service.batch(batch).await;
*lock += batch_len;
rp
}
}
}
fn notification<'a>(&self, n: Notification<'a>) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
let count = self.count.clone();
let service = self.service.clone();
// A notification is not expected to return a response so the result here doesn't matter
// rather than other middlewares may not be invoked.
async move { if *count.lock().await >= 10 { MethodResponse::notification() } else { service.notification(n).await } }
}
}
#[rpc(server, client)]
pub trait Rpc {
#[method(name = "say_hello")]
async fn say_hello(&self) -> Result<String, ErrorObjectOwned>;
}
#[async_trait]
impl RpcServer for () {
async fn say_hello(&self) -> Result<String, ErrorObjectOwned> {
Ok("lo".to_string())
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let filter = tracing_subscriber::EnvFilter::try_from_default_env()?;
tracing_subscriber::FmtSubscriber::builder().with_env_filter(filter).finish().try_init()?;
// Make a bunch of WebSocket calls to be blacklisted by server.
{
let mut i = 0;
let handle = run_server().await?;
let client = WsClientBuilder::default().build("ws://127.0.0.1:9944").await.unwrap();
while client.is_connected() {
let rp: Result<String, _> = client.say_hello().await;
if rp.is_ok() {
i += 1;
}
}
// After the server has blacklisted the IP address, the connection is denied.
assert!(WsClientBuilder::default().build("ws://127.0.0.1:9944").await.is_err());
tracing::info!("WS client made {i} successful calls before getting blacklisted");
handle.stop().unwrap();
handle.stopped().await;
}
// Make a bunch of HTTP calls to be blacklisted by server.
{
let mut i = 0;
let handle = run_server().await?;
let client = HttpClient::builder().build("http://127.0.0.1:9944").unwrap();
while client.say_hello().await.is_ok() {
i += 1;
}
tracing::info!("HTTP client made {i} successful calls before getting blacklisted");
handle.stop().unwrap();
handle.stopped().await;
}
Ok(())
}
async fn run_server() -> anyhow::Result<ServerHandle> {
// Construct our SocketAddr to listen on...
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.
//
// To keep the server running the `server_handle`
// must be kept and it can also be used to stop the server.
let (stop_handle, server_handle) = stop_channel();
// This state is cloned for every connection
// all these types based on Arcs and it should
// be relatively cheap to clone them.
//
// Make sure that nothing expensive is cloned here
// when doing this or use an `Arc`.
#[derive(Clone)]
struct PerConnection {
methods: Methods,
stop_handle: StopHandle,
conn_id: Arc<AtomicU32>,
conn_guard: ConnectionGuard,
blacklisted_peers: Arc<Mutex<HashSet<IpAddr>>>,
// HTTP rate limit that is shared by all connections.
//
// This is just a toy-example and one not should "limit" HTTP connections
// like this because the actual IP addr of each request is not checked.
//
// Because it's possible to blacklist a peer which has only made one or
// a few calls.
global_http_rate_limit: Arc<AsyncMutex<usize>>,
}
let per_conn = PerConnection {
methods: ().into_rpc().into(),
stop_handle: stop_handle.clone(),
conn_id: Default::default(),
conn_guard: ConnectionGuard::new(100),
blacklisted_peers: Default::default(),
global_http_rate_limit: Default::default(),
};
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();
// Create a service handler.
let stop_handle2 = per_conn.stop_handle.clone();
let per_conn = per_conn.clone();
let svc = tower::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)
}
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 {
async { Ok(http::response::denied()) }.boxed()
}
});
// Upgrade the connection to a HTTP service.
tokio::spawn(serve_with_graceful_shutdown(sock, svc, stop_handle2.shutdown()));
}
});
Ok(server_handle)
}

View File

@@ -0,0 +1,123 @@
// 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::net::SocketAddr;
use jsonrpsee::core::{SubscriptionResult, async_trait, client::Subscription};
use jsonrpsee::proc_macros::rpc;
use jsonrpsee::server::{PendingSubscriptionSink, Server};
use jsonrpsee::types::ErrorObjectOwned;
use jsonrpsee::ws_client::WsClientBuilder;
type ExampleHash = [u8; 32];
type ExampleStorageKey = Vec<u8>;
#[rpc(server, client, namespace = "state")]
pub trait Rpc<Hash, StorageKey>
where
Hash: std::fmt::Debug,
{
/// Async method call example.
#[method(name = "getKeys")]
async fn storage_keys(
&self,
storage_key: StorageKey,
hash: Option<Hash>,
) -> Result<Vec<StorageKey>, ErrorObjectOwned>;
/// Subscription that takes a `StorageKey` as input and produces a `Vec<Hash>`.
#[subscription(name = "subscribeStorage" => "override", item = Vec<Hash>)]
async fn subscribe_storage(&self, keys: Option<Vec<StorageKey>>) -> SubscriptionResult;
#[subscription(name = "subscribeSync" => "sync", item = Vec<Hash>)]
fn s(&self, keys: Option<Vec<StorageKey>>);
}
pub struct RpcServerImpl;
#[async_trait]
impl RpcServer<ExampleHash, ExampleStorageKey> for RpcServerImpl {
async fn storage_keys(
&self,
storage_key: ExampleStorageKey,
_hash: Option<ExampleHash>,
) -> Result<Vec<ExampleStorageKey>, ErrorObjectOwned> {
Ok(vec![storage_key])
}
async fn subscribe_storage(
&self,
pending: PendingSubscriptionSink,
_keys: Option<Vec<ExampleStorageKey>>,
) -> SubscriptionResult {
let sink = pending.accept().await?;
let json = serde_json::value::to_raw_value(&vec![[0; 32]])?;
sink.send(json).await?;
Ok(())
}
fn s(&self, pending: PendingSubscriptionSink, _keys: Option<Vec<ExampleStorageKey>>) {
tokio::spawn(async move {
let sink = pending.accept().await.unwrap();
let json = serde_json::value::to_raw_value(&vec![[0; 32]]).unwrap();
sink.send(json).await.unwrap();
});
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let server_addr = run_server().await?;
let url = format!("ws://{}", server_addr);
let client = WsClientBuilder::default().build(&url).await?;
assert_eq!(client.storage_keys(vec![1, 2, 3, 4], None::<ExampleHash>).await.unwrap(), vec![vec![1, 2, 3, 4]]);
let mut sub: Subscription<Vec<ExampleHash>> =
RpcClient::<ExampleHash, ExampleStorageKey>::subscribe_storage(&client, None).await.unwrap();
assert_eq!(Some(vec![[0; 32]]), sub.next().await.transpose().unwrap());
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
let server = Server::builder().build("127.0.0.1:0").await?;
let addr = server.local_addr()?;
let handle = server.start(RpcServerImpl.into_rpc());
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,95 @@
// 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::net::SocketAddr;
use jsonrpsee::core::async_trait;
use jsonrpsee::proc_macros::rpc;
use jsonrpsee::server::Server;
use jsonrpsee::types::ErrorObjectOwned;
use jsonrpsee::ws_client::WsClientBuilder;
type ExampleHash = [u8; 32];
pub trait Config {
type Hash: Send + Sync + 'static;
}
impl Config for ExampleHash {
type Hash = Self;
}
/// The RPC macro requires `DeserializeOwned` for output types for the client implementation, while the
/// server implementation requires output types to be bounded by `Serialize`.
///
/// In this example, we don't want the `Conf` to be bounded by default to
/// `Conf : Send + Sync + 'static + jsonrpsee::core::DeserializeOwned` for client implementation and
/// `Conf : Send + Sync + 'static + jsonrpsee::core::Serialize` for server implementation.
///
/// Explicitly, specify client and server bounds to handle the `Serialize` and `DeserializeOwned` cases
/// just for the `Conf::hash` part.
#[rpc(server, client, namespace = "foo", client_bounds(T::Hash: jsonrpsee::core::DeserializeOwned), server_bounds(T::Hash: jsonrpsee::core::Serialize + Clone))]
pub trait Rpc<T: Config> {
#[method(name = "bar")]
fn method(&self) -> Result<T::Hash, ErrorObjectOwned>;
}
pub struct RpcServerImpl;
#[async_trait]
impl RpcServer<ExampleHash> for RpcServerImpl {
fn method(&self) -> Result<<ExampleHash as Config>::Hash, ErrorObjectOwned> {
Ok([0u8; 32])
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let server_addr = run_server().await?;
let url = format!("ws://{}", server_addr);
let client = WsClientBuilder::default().build(&url).await?;
assert_eq!(RpcClient::<ExampleHash>::method(&client).await.unwrap(), [0u8; 32]);
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
let server = Server::builder().build("127.0.0.1:0").await?;
let addr = server.local_addr()?;
let handle = server.start(RpcServerImpl.into_rpc());
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,84 @@
// 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::net::SocketAddr;
use jsonrpsee::core::client::ClientT;
use jsonrpsee::proc_macros::rpc;
use jsonrpsee::server::Server;
use jsonrpsee::ws_client::WsClientBuilder;
use jsonrpsee::{ResponsePayload, rpc_params};
#[rpc(client, server, namespace = "state")]
pub trait Rpc {
/// Async method call example.
#[method(name = "getKeys")]
fn storage_keys(&self) -> ResponsePayload<'static, String>;
}
pub struct RpcServerImpl;
impl RpcServer for RpcServerImpl {
fn storage_keys(&self) -> ResponsePayload<'static, String> {
let (rp, rp_future) = ResponsePayload::success("ehheeheh".to_string()).notify_on_completion();
tokio::spawn(async move {
rp_future.await.unwrap();
println!("Method response to `state_getKeys` finished");
});
rp
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let server_addr = run_server().await?;
let url = format!("ws://{}", server_addr);
let client = WsClientBuilder::default().build(&url).await?;
assert_eq!("ehheeheh".to_string(), client.request::<String, _>("state_getKeys", rpc_params![]).await.unwrap());
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
let server = Server::builder().build("127.0.0.1:0").await?;
let addr = server.local_addr()?;
let handle = server.start(RpcServerImpl.into_rpc());
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,265 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
//
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the
// Software without restriction, including without
// limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//! jsonrpsee supports two kinds of middlewares `http_middleware` and `rpc_middleware`.
//!
//! This example demonstrates how to use the `rpc_middleware` which applies for each
//! JSON-RPC method call and batch requests may call the middleware more than once.
//!
//! A typical use-case for this is to implement rate-limiting based on the actual
//! number of JSON-RPC methods calls and a request could potentially be made
//! by HTTP or WebSocket which this middleware is agnostic to.
//!
//! Contrary the HTTP middleware does only apply per HTTP request and
//! may be handy in some scenarios such CORS but if you want to access
//! to the actual JSON-RPC details this is the middleware to use.
//!
//! This example enables the same middleware for both the server and client which
//! can be confusing when one runs this but it is just to demonstrate the API.
//!
//! That the middleware is applied to the server and client in the same way.
use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use jsonrpsee::core::client::ClientT;
use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT};
use jsonrpsee::rpc_params;
use jsonrpsee::server::{RpcModule, Server};
use jsonrpsee::types::Request;
use jsonrpsee::ws_client::WsClientBuilder;
#[derive(Clone)]
struct IdentityLayer;
impl<S> tower::Layer<S> for IdentityLayer
where
S: RpcServiceT + Send + Sync + Clone + 'static,
{
type Service = Identity<S>;
fn layer(&self, inner: S) -> Self::Service {
Identity(inner)
}
}
#[derive(Clone)]
struct Identity<S>(S);
impl<S> RpcServiceT for Identity<S>
where
S: RpcServiceT + Send + Sync + Clone + 'static,
{
type MethodResponse = S::MethodResponse;
type NotificationResponse = S::NotificationResponse;
type BatchResponse = S::BatchResponse;
fn batch<'a>(&self, batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
self.0.batch(batch)
}
fn call<'a>(&self, request: Request<'a>) -> impl Future<Output = Self::MethodResponse> + Send + 'a {
self.0.call(request)
}
fn notification<'a>(&self, n: Notification<'a>) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
self.0.notification(n)
}
}
// It's possible to access the connection ID
// by using the low-level API.
#[derive(Clone)]
pub struct CallsPerConn<S> {
service: S,
count: Arc<AtomicUsize>,
role: &'static str,
}
impl<S> RpcServiceT for CallsPerConn<S>
where
S: RpcServiceT + Send + Sync + Clone + 'static,
{
type MethodResponse = S::MethodResponse;
type NotificationResponse = S::NotificationResponse;
type BatchResponse = S::BatchResponse;
fn call<'a>(&self, req: Request<'a>) -> impl Future<Output = Self::MethodResponse> + Send + 'a {
let count = self.count.clone();
let service = self.service.clone();
let role = self.role;
async move {
let rp = service.call(req).await;
count.fetch_add(1, Ordering::SeqCst);
println!("{role} processed calls={} on the connection", count.load(Ordering::SeqCst));
rp
}
}
fn batch<'a>(&self, batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
let len = batch.len();
self.count.fetch_add(len, Ordering::SeqCst);
println!("{} processed calls={} on the connection", self.role, self.count.load(Ordering::SeqCst));
self.service.batch(batch)
}
fn notification<'a>(&self, n: Notification<'a>) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
self.service.notification(n)
}
}
#[derive(Clone)]
pub struct GlobalCalls<S> {
service: S,
count: Arc<AtomicUsize>,
role: &'static str,
}
impl<S> RpcServiceT for GlobalCalls<S>
where
S: RpcServiceT + Send + Sync + Clone + 'static,
{
type MethodResponse = S::MethodResponse;
type NotificationResponse = S::NotificationResponse;
type BatchResponse = S::BatchResponse;
fn call<'a>(&self, req: Request<'a>) -> impl Future<Output = Self::MethodResponse> + Send + 'a {
let count = self.count.clone();
let service = self.service.clone();
let role = self.role;
async move {
let rp = service.call(req).await;
count.fetch_add(1, Ordering::SeqCst);
println!("{role} processed calls={} in total", count.load(Ordering::SeqCst));
rp
}
}
fn batch<'a>(&self, batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
let len = batch.len();
self.count.fetch_add(len, Ordering::SeqCst);
println!("{}, processed calls={} in total", self.role, self.count.load(Ordering::SeqCst));
self.service.batch(batch)
}
fn notification<'a>(&self, n: Notification<'a>) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
self.service.notification(n)
}
}
#[derive(Clone)]
pub struct Logger<S> {
service: S,
role: &'static str,
}
impl<S> RpcServiceT for Logger<S>
where
S: RpcServiceT + Send + Sync + Clone + 'static,
{
type MethodResponse = S::MethodResponse;
type NotificationResponse = S::NotificationResponse;
type BatchResponse = S::BatchResponse;
fn call<'a>(&self, req: Request<'a>) -> impl Future<Output = Self::MethodResponse> + Send + 'a {
println!("{} logger middleware: method `{}`", self.role, req.method);
self.service.call(req)
}
fn batch<'a>(&self, batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
println!("{} logger middleware: batch {batch}", self.role);
self.service.batch(batch)
}
fn notification<'a>(&self, n: Notification<'a>) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
self.service.notification(n)
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let addr = run_server().await?;
let url = format!("ws://{}", addr);
for _ in 0..2 {
let global_cnt = Arc::new(AtomicUsize::new(0));
let rpc_middleware = RpcServiceBuilder::new()
.layer_fn(|service| Logger { service, role: "client" })
// This state is created per connection.
.layer_fn(|service| CallsPerConn { service, count: Default::default(), role: "client" })
// This state is shared by all connections.
.layer_fn(move |service| GlobalCalls { service, count: global_cnt.clone(), role: "client" });
let client = WsClientBuilder::new().set_rpc_middleware(rpc_middleware).build(&url).await?;
let response: String = client.request("say_hello", rpc_params![]).await?;
println!("response: {:?}", response);
let _response: Result<String, _> = client.request("unknown_method", rpc_params![]).await;
let _: String = client.request("say_hello", rpc_params![]).await?;
let _: String = client.request("thready", rpc_params![4]).await?;
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
let global_cnt = Arc::new(AtomicUsize::new(0));
let rpc_middleware = RpcServiceBuilder::new()
.layer_fn(|service| Logger { service, role: "server" })
// This state is created per connection.
.layer_fn(|service| CallsPerConn { service, count: Default::default(), role: "server" })
// This state is shared by all connections.
.layer_fn(move |service| GlobalCalls { service, count: global_cnt.clone(), role: "server" })
// Optional layer that does nothing, just an example to be useful if one has an optional layer.
.option_layer(Some(IdentityLayer));
let server = Server::builder().set_rpc_middleware(rpc_middleware).build("127.0.0.1:0").await?;
let mut module = RpcModule::new(());
module.register_method("say_hello", |_, _, _| "lo")?;
module.register_method("thready", |params, _, _| {
let thread_count: usize = params.one().unwrap();
for _ in 0..thread_count {
std::thread::spawn(|| std::thread::sleep(std::time::Duration::from_secs(1)));
}
""
})?;
let addr = server.local_addr()?;
let handle = server.start(module);
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,173 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
//
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the
// Software without restriction, including without
// limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//! jsonrpsee supports two kinds of middlewares `http_middleware` and `rpc_middleware`.
//!
//! This example demonstrates how to use the `rpc_middleware` which applies for each
//! JSON-RPC method calls, notifications and batch requests.
//!
//! This example demonstrates how to use the `rpc_middleware` for the client
//! and you may benefit specifying the response type to `core::client::MethodResponse`
//! to actually inspect the response instead of using the serialized JSON-RPC response.
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use jsonrpsee::core::client::{ClientT, MiddlewareMethodResponse, error::Error};
use jsonrpsee::core::middleware::{Batch, Notification, RpcServiceBuilder, RpcServiceT};
use jsonrpsee::rpc_params;
use jsonrpsee::server::{RpcModule, Server};
use jsonrpsee::types::{ErrorCode, ErrorObject, Request};
use jsonrpsee::ws_client::WsClientBuilder;
#[derive(Default)]
struct InnerMetrics {
method_calls_success: usize,
method_calls_failure: usize,
notifications: usize,
batch_calls: usize,
}
#[derive(Clone)]
pub struct Metrics<S> {
service: S,
metrics: Arc<Mutex<InnerMetrics>>,
}
impl std::fmt::Debug for InnerMetrics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InnerMetrics")
.field("method_calls_success", &self.method_calls_success)
.field("method_calls_failure", &self.method_calls_failure)
.field("notifications", &self.notifications)
.field("batch_calls", &self.batch_calls)
.finish()
}
}
impl<S> Metrics<S> {
pub fn new(service: S) -> Self {
Self { service, metrics: Arc::new(Mutex::new(InnerMetrics::default())) }
}
}
// NOTE: We are using MethodResponse as the response type here to be able to inspect the response
// and not just the serialized JSON-RPC response. This is not necessary if you only care about
// the serialized JSON-RPC response.
impl<S> RpcServiceT for Metrics<S>
where
S: RpcServiceT<MethodResponse = Result<MiddlewareMethodResponse, Error>> + Send + Sync + Clone + 'static,
{
type MethodResponse = Result<MiddlewareMethodResponse, Error>;
type NotificationResponse = S::NotificationResponse;
type BatchResponse = S::BatchResponse;
fn call<'a>(&self, req: Request<'a>) -> impl Future<Output = Self::MethodResponse> + Send + 'a {
let m = self.metrics.clone();
let service = self.service.clone();
async move {
let rp = service.call(req).await;
// Access to inner response via the deref implementation.
match &rp {
Ok(rp) => {
if rp.is_success() {
m.lock().unwrap().method_calls_success += 1;
} else {
m.lock().unwrap().method_calls_failure += 1;
}
}
Err(e) => {
m.lock().unwrap().method_calls_failure += 1;
tracing::error!("Error: {:?}", e);
}
}
rp
}
}
fn batch<'a>(&self, batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
self.metrics.lock().unwrap().batch_calls += 1;
self.service.batch(batch)
}
fn notification<'a>(&self, n: Notification<'a>) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
self.metrics.lock().unwrap().notifications += 1;
self.service.notification(n)
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let addr = run_server().await?;
let url = format!("ws://{}", addr);
let metrics = Arc::new(Mutex::new(InnerMetrics::default()));
for _ in 0..2 {
let metrics = metrics.clone();
let rpc_middleware =
RpcServiceBuilder::new().layer_fn(move |s| Metrics { service: s, metrics: metrics.clone() });
let client = WsClientBuilder::new().set_rpc_middleware(rpc_middleware).build(&url).await?;
let _: Result<String, _> = client.request("say_hello", rpc_params![]).await;
let _: Result<String, _> = client.request("unknown_method", rpc_params![]).await;
let _: Result<String, _> = client.request("thready", rpc_params![4]).await;
let _: Result<String, _> = client.request("mul", rpc_params![4]).await;
let _: Result<String, _> = client.request("err", rpc_params![4]).await;
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
println!("Metrics: {:?}", metrics.lock().unwrap());
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
let server = Server::builder().build("127.0.0.1:0").await?;
let mut module = RpcModule::new(());
module.register_method("say_hello", |_, _, _| "lo")?;
module.register_method("mul", |params, _, _| {
let count: usize = params.one().unwrap();
count * 2
})?;
module.register_method("error", |_, _, _| ErrorObject::from(ErrorCode::InternalError))?;
let addr = server.local_addr()?;
let handle = server.start(module);
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,139 @@
// 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 jsonrpsee::core::client::ClientT;
use jsonrpsee::core::middleware::{Batch, BatchEntry, Notification, RpcServiceBuilder, RpcServiceT};
use jsonrpsee::server::Server;
use jsonrpsee::types::Request;
use jsonrpsee::ws_client::WsClientBuilder;
use jsonrpsee::{RpcModule, rpc_params};
use std::borrow::Cow as StdCow;
use std::net::SocketAddr;
fn modify_method_call(req: &mut Request<'_>) {
// Example how to modify the params in the call.
if req.method == "say_hello" {
// It's a bit awkward to create new params in the request
// but this shows how to do it.
let raw_value = serde_json::value::to_raw_value("myparams").unwrap();
req.params = Some(StdCow::Owned(raw_value));
}
// Re-direct all calls that isn't `say_hello` to `say_goodbye`
else if req.method != "say_hello" {
req.method = "say_goodbye".into();
}
}
fn modify_notif(n: &mut Notification<'_>) {
// Example how to modify the params in the notification.
if n.method == "say_hello" {
// It's a bit awkward to create new params in the request
// but this shows how to do it.
let raw_value = serde_json::value::to_raw_value("myparams").unwrap();
n.params = Some(StdCow::Owned(raw_value));
}
// Re-direct all notifs that isn't `say_hello` to `say_goodbye`
else if n.method != "say_hello" {
n.method = "say_goodbye".into();
}
}
#[derive(Clone)]
pub struct ModifyRequestIf<S>(S);
impl<S> RpcServiceT for ModifyRequestIf<S>
where
S: RpcServiceT + Send + Sync + Clone + 'static,
{
type MethodResponse = S::MethodResponse;
type NotificationResponse = S::NotificationResponse;
type BatchResponse = S::BatchResponse;
fn call<'a>(&self, mut req: Request<'a>) -> impl Future<Output = Self::MethodResponse> + Send + 'a {
modify_method_call(&mut req);
self.0.call(req)
}
fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
for call in batch.iter_mut() {
match call {
Ok(BatchEntry::Call(call)) => {
modify_method_call(call);
}
Ok(BatchEntry::Notification(n)) => {
modify_notif(n);
}
// Invalid request, we don't care about it.
Err(_err) => {}
}
}
self.0.batch(batch)
}
fn notification<'a>(
&self,
mut n: Notification<'a>,
) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
modify_notif(&mut n);
self.0.notification(n)
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let addr = run_server().await?;
let url = format!("ws://{}", addr);
let client = WsClientBuilder::default().build(&url).await?;
let _response: String = client.request("say_hello", rpc_params![]).await?;
let _response: Result<String, _> = client.request("unknown_method", rpc_params![]).await;
let _: String = client.request("say_hello", rpc_params![]).await?;
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
let rpc_middleware = RpcServiceBuilder::new().layer_fn(ModifyRequestIf);
let server = Server::builder().set_rpc_middleware(rpc_middleware).build("127.0.0.1:0").await?;
let mut module = RpcModule::new(());
module.register_method("say_hello", |_, _, _| "lo")?;
module.register_method("say_goodbye", |_, _, _| "goodbye")?;
let addr = server.local_addr()?;
let handle = server.start(module);
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,218 @@
// 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.
//! Example middleware to rate limit based on the number
//! JSON-RPC calls.
//!
//! As demonstrated in this example any state must be
//! stored in something to provide interior mutability
//! such as `Arc<Mutex>`
use jsonrpsee::core::client::ClientT;
use jsonrpsee::core::middleware::{
Batch, BatchEntry, BatchEntryErr, Notification, ResponseFuture, RpcServiceBuilder, RpcServiceT,
};
use jsonrpsee::server::Server;
use jsonrpsee::types::{ErrorObject, Request};
use jsonrpsee::ws_client::WsClientBuilder;
use jsonrpsee::{MethodResponse, RpcModule, rpc_params};
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
#[derive(Debug, Copy, Clone)]
struct Rate {
num: u64,
period: Duration,
}
#[derive(Debug, Copy, Clone)]
enum State {
Deny { until: Instant },
Allow { until: Instant, rem: u64 },
}
/// Depending on how the rate limit is instantiated
/// it's possible to select whether the rate limit
/// is be applied per connection or shared by
/// all connections.
///
/// Have a look at `async fn run_server` below which
/// shows how do it.
#[derive(Clone)]
pub struct RateLimit<S> {
service: S,
state: Arc<Mutex<State>>,
rate: Rate,
}
impl<S> RateLimit<S> {
fn new(service: S, rate: Rate) -> Self {
let period = rate.period;
let num = rate.num;
Self {
service,
rate,
state: Arc::new(Mutex::new(State::Allow { until: Instant::now() + period, rem: num + 1 })),
}
}
fn rate_limit_deny(&self) -> bool {
let now = Instant::now();
let mut lock = self.state.lock().unwrap();
let next_state = match *lock {
State::Deny { until } => {
if now > until {
State::Allow { until: now + self.rate.period, rem: self.rate.num - 1 }
} else {
State::Deny { until }
}
}
State::Allow { until, rem } => {
if now > until {
State::Allow { until: now + self.rate.period, rem: self.rate.num - 1 }
} else {
let n = rem - 1;
if n > 0 { State::Allow { until: now + self.rate.period, rem: n } } else { State::Deny { until } }
}
}
};
*lock = next_state;
matches!(next_state, State::Deny { .. })
}
}
impl<S> RpcServiceT for RateLimit<S>
where
S: RpcServiceT<
MethodResponse = MethodResponse,
BatchResponse = MethodResponse,
NotificationResponse = MethodResponse,
> + Send
+ Sync
+ Clone
+ 'static,
{
type MethodResponse = S::MethodResponse;
type NotificationResponse = S::NotificationResponse;
type BatchResponse = S::BatchResponse;
fn call<'a>(&self, req: Request<'a>) -> impl Future<Output = Self::MethodResponse> + Send + 'a {
if self.rate_limit_deny() {
ResponseFuture::ready(MethodResponse::error(req.id, ErrorObject::borrowed(-32000, "RPC rate limit", None)))
} else {
ResponseFuture::future(self.service.call(req))
}
}
fn batch<'a>(&self, mut batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
// If the rate limit is reached then we modify each entry
// in the batch to be a request with an error.
//
// This makes sure that the client will receive an error
// for each request in the batch.
if self.rate_limit_deny() {
for entry in batch.iter_mut() {
let id = match entry {
Ok(BatchEntry::Call(req)) => req.id.clone(),
Ok(BatchEntry::Notification(_)) => continue,
Err(_) => continue,
};
// This will create a new error response for batch and replace the method call
*entry = Err(BatchEntryErr::new(id, ErrorObject::borrowed(-32000, "RPC rate limit", None)));
}
}
self.service.batch(batch)
}
fn notification<'a>(&self, n: Notification<'a>) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
if self.rate_limit_deny() {
// Notifications are not expected to return a response so just ignore
// if the rate limit is reached.
ResponseFuture::ready(MethodResponse::notification())
} else {
ResponseFuture::future(self.service.notification(n))
}
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let addr = run_server().await?;
let url = format!("ws://{}", addr);
let client1 = WsClientBuilder::default().build(&url).await?;
let _response: String = client1.request("say_hello", rpc_params![]).await?;
// The rate limit should trigger an error here.
let _response = client1.request::<String, _>("unknown_method", rpc_params![]).await.unwrap_err();
// Make a new connection and the server will allow it because our `RateLimit`
// applies per connection and not globally on the server.
let client2 = WsClientBuilder::default().build(&url).await?;
let _response: String = client2.request("say_hello", rpc_params![]).await?;
// The first connection should allow a call now again.
tokio::time::sleep(Duration::from_secs(2)).await;
let _response: String = client1.request("say_hello", rpc_params![]).await?;
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
// This will create a new `RateLimit` per connection.
//
// In this particular example the server will only
// allow one RPC call per second.
//
// Have a look at the `rpc_middleware example` if you want see an example
// how to share state of the "middleware" for all connections on the server.
let rpc_middleware = RpcServiceBuilder::new()
.layer_fn(|service| RateLimit::new(service, Rate { num: 1, period: Duration::from_secs(1) }));
let server = Server::builder().set_rpc_middleware(rpc_middleware).build("127.0.0.1:0").await?;
let mut module = RpcModule::new(());
module.register_method("say_hello", |_, _, _| "lo")?;
module.register_method("say_goodbye", |_, _, _| "goodbye")?;
let addr = server.local_addr()?;
let handle = server.start(module);
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,156 @@
// 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::net::SocketAddr;
use jsonrpsee::core::middleware::{Batch, Notification, Request, RpcServiceT};
use jsonrpsee::core::{SubscriptionResult, async_trait};
use jsonrpsee::proc_macros::rpc;
use jsonrpsee::server::PendingSubscriptionSink;
use jsonrpsee::types::{ErrorObject, ErrorObjectOwned};
use jsonrpsee::ws_client::WsClientBuilder;
use jsonrpsee::{ConnectionId, Extensions};
#[rpc(server, client)]
pub trait Rpc {
/// method with connection ID.
#[method(name = "connectionIdMethod", with_extensions)]
async fn method(&self) -> Result<usize, ErrorObjectOwned>;
#[subscription(name = "subscribeConnectionId", item = usize, with_extensions)]
async fn sub(&self) -> SubscriptionResult;
#[subscription(name = "subscribeSyncConnectionId", item = usize, with_extensions)]
fn sub2(&self) -> SubscriptionResult;
}
struct LoggingMiddleware<S>(S);
impl<S> RpcServiceT for LoggingMiddleware<S>
where
S: RpcServiceT,
{
type MethodResponse = S::MethodResponse;
type NotificationResponse = S::NotificationResponse;
type BatchResponse = S::BatchResponse;
fn call<'a>(&self, request: Request<'a>) -> impl Future<Output = Self::MethodResponse> + Send + 'a {
tracing::info!("Received request: {:?}", request);
assert!(request.extensions().get::<ConnectionId>().is_some());
self.0.call(request)
}
fn batch<'a>(&self, batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
tracing::info!("Received batch: {:?}", batch);
self.0.batch(batch)
}
fn notification<'a>(&self, n: Notification<'a>) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
tracing::info!("Received notif: {:?}", n);
self.0.notification(n)
}
}
pub struct RpcServerImpl;
#[async_trait]
impl RpcServer for RpcServerImpl {
async fn method(&self, ext: &Extensions) -> Result<usize, ErrorObjectOwned> {
let conn_id = ext
.get::<ConnectionId>()
.cloned()
.ok_or_else(|| ErrorObject::owned(0, "No connection details found", None::<()>))?;
Ok(conn_id.0)
}
async fn sub(&self, pending: PendingSubscriptionSink, ext: &Extensions) -> SubscriptionResult {
let sink = pending.accept().await?;
let conn_id = ext
.get::<ConnectionId>()
.cloned()
.ok_or_else(|| ErrorObject::owned(0, "No connection details found", None::<()>))?;
let json = serde_json::value::to_raw_value(&conn_id)
.map_err(|e| ErrorObject::owned(0, format!("Failed to serialize connection ID: {e}"), None::<()>))?;
sink.send(json).await?;
Ok(())
}
fn sub2(&self, pending: PendingSubscriptionSink, ext: &Extensions) -> SubscriptionResult {
let conn_id = ext.get::<ConnectionId>().cloned().unwrap();
tokio::spawn(async move {
let sink = pending.accept().await.unwrap();
let json = serde_json::value::to_raw_value(&conn_id).unwrap();
sink.send(json).await.unwrap();
});
Ok(())
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let server_addr = run_server().await?;
let url = format!("ws://{}", server_addr);
let client = WsClientBuilder::default().build(&url).await?;
let connection_id_first = client.method().await.unwrap();
// Second call from the same connection ID.
assert_eq!(client.method().await.unwrap(), connection_id_first);
// Second client will increment the connection ID.
let client2 = WsClientBuilder::default().build(&url).await?;
let connection_id_second = client2.method().await.unwrap();
assert_ne!(connection_id_first, connection_id_second);
let mut sub = client.sub().await.unwrap();
assert_eq!(connection_id_first, sub.next().await.transpose().unwrap().unwrap());
let mut sub = client2.sub().await.unwrap();
assert_eq!(connection_id_second, sub.next().await.transpose().unwrap().unwrap());
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
let rpc_middleware = jsonrpsee::server::middleware::rpc::RpcServiceBuilder::new().layer_fn(LoggingMiddleware);
let server = jsonrpsee::server::Server::builder().set_rpc_middleware(rpc_middleware).build("127.0.0.1:0").await?;
let addr = server.local_addr()?;
let handle = server.start(RpcServerImpl.into_rpc());
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,69 @@
// Copyright 2022 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.
//! Example how to use `tokio-console` to debug async tasks `jsonrpsee`.
//! For further information see https://docs.rs/console-subscriber.
//!
//! To run it:
//! `$ cargo install --locked tokio-console`
//! `$ RUSTFLAGS="--cfg tokio_unstable" cargo run --example tokio_console`
//! `$ tokio-console`
//!
//! It will start a server on http://127.0.0.1:6669 for `tokio-console` to connect to.
use std::net::SocketAddr;
use jsonrpsee::RpcModule;
use jsonrpsee::server::Server;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
console_subscriber::init();
let _ = run_server().await?;
futures::future::pending().await
}
async fn run_server() -> anyhow::Result<SocketAddr> {
let server = Server::builder().build("127.0.0.1:9944").await?;
let mut module = RpcModule::new(());
module.register_method("say_hello", |_, _, _| "lo")?;
module.register_method("memory_call", |_, _, _| "A".repeat(1024 * 1024))?;
module.register_async_method("sleep", |_, _, _| async {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
"lo"
})?;
let addr = server.local_addr()?;
let handle = server.start(module);
// In this example we don't care about doing a stopping the server so let it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,67 @@
// 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::net::SocketAddr;
use jsonrpsee::core::client::ClientT;
use jsonrpsee::core::middleware::RpcServiceBuilder;
use jsonrpsee::server::Server;
use jsonrpsee::ws_client::{WsClient, WsClientBuilder};
use jsonrpsee::{RpcModule, rpc_params};
use tracing_subscriber::util::SubscriberInitExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let filter = tracing_subscriber::EnvFilter::try_from_default_env()?
.add_directive("jsonrpsee[method_call{name = \"say_hello\"}]=trace".parse()?);
tracing_subscriber::FmtSubscriber::builder().with_env_filter(filter).finish().try_init()?;
let addr = run_server().await?;
let url = format!("ws://{}", addr);
let client: WsClient = WsClientBuilder::new().build(&url).await?;
let response: String = client.request("say_hello", rpc_params![]).await?;
tracing::info!("response: {:?}", response);
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
let rpc_middleware = RpcServiceBuilder::new().rpc_logger(1024);
let server = Server::builder().set_rpc_middleware(rpc_middleware).build("127.0.0.1:0").await?;
let mut module = RpcModule::new(());
module.register_method("say_hello", |_, _, _| "lo")?;
let addr = server.local_addr()?;
let handle = server.start(module);
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}

View File

@@ -0,0 +1,122 @@
// 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 jsonrpsee::core::client::ClientT;
use jsonrpsee::server::{ServerHandle, serve_with_graceful_shutdown, stop_channel};
use jsonrpsee::ws_client::WsClientBuilder;
use jsonrpsee::{RpcModule, rpc_params};
use std::net::SocketAddr;
use tokio::net::TcpListener;
use tracing_subscriber::util::SubscriberInitExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let filter = tracing_subscriber::EnvFilter::try_from_default_env()?
.add_directive("jsonrpsee[method_call{name = \"say_hello\"}]=trace".parse()?)
.add_directive("jsonrpsee-client=trace".parse()?);
tracing_subscriber::FmtSubscriber::builder().with_env_filter(filter).finish().try_init()?;
let (_server_hdl, addrs) = run_server().await?;
let url_v4 = format!("ws://{}", addrs.v4);
let url_v6 = format!("ws://{}", addrs.v6);
let client_v4 = WsClientBuilder::default().build(&url_v4).await?;
let client_v6 = WsClientBuilder::default().build(&url_v6).await?;
let response_v4: String = client_v4.request("say_hello", rpc_params![]).await?;
let response_v6: String = client_v6.request("say_hello", rpc_params![]).await?;
tracing::info!("response V4: {:?}", response_v4);
tracing::info!("response V6: {:?}", response_v6);
Ok(())
}
async fn run_server() -> anyhow::Result<(ServerHandle, Addrs)> {
let port = 9944;
// V4 address
let v4_addr = SocketAddr::from(([127, 0, 0, 1], port));
// V6 address
let v6_addr = SocketAddr::new("::1".parse().unwrap(), port);
let mut module = RpcModule::new(());
module.register_method("say_hello", |_, _, _| "lo")?;
// Bind to both IPv4 and IPv6 addresses.
let listener_v4 = TcpListener::bind(&v4_addr).await?;
let listener_v6 = TcpListener::bind(&v6_addr).await?;
// Each RPC call/connection get its own `stop_handle`
// to able to determine whether the server has been stopped or not.
//
// To keep the server running the `server_handle`
// must be kept and it can also be used to stop the server.
let (stop_hdl, server_hdl) = stop_channel();
// Create and finalize a server configuration from a TowerServiceBuilder
// given an RpcModule and the stop handle.
let svc = jsonrpsee::server::Server::builder().to_service_builder().build(module, stop_hdl.clone());
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_v4.accept() => {
match res {
Ok((stream, _remote_addr)) => stream,
Err(e) => {
tracing::error!("failed to accept v4 connection: {:?}", e);
continue;
}
}
}
res = listener_v6.accept() => {
match res {
Ok((stream, _remote_addr)) => stream,
Err(e) => {
tracing::error!("failed to accept v6 connection: {:?}", e);
continue;
}
}
}
_ = stop_hdl.clone().shutdown() => break,
};
// Spawn a new task to serve each respective (Hyper) connection.
tokio::spawn(serve_with_graceful_shutdown(stream, svc.clone(), stop_hdl.clone().shutdown()));
}
});
Ok((server_hdl, Addrs { v4: v4_addr, v6: v6_addr }))
}
struct Addrs {
v4: SocketAddr,
v6: SocketAddr,
}

View File

@@ -0,0 +1,149 @@
// 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.
//! Example that shows how to broadcast to all active subscriptions using `tokio::sync::broadcast`.
use std::net::SocketAddr;
use futures::StreamExt;
use futures::future::{self, Either};
use jsonrpsee::PendingSubscriptionSink;
use jsonrpsee::core::client::{Subscription, SubscriptionClientT};
use jsonrpsee::core::middleware::RpcServiceBuilder;
use jsonrpsee::rpc_params;
use jsonrpsee::server::{RpcModule, Server, ServerConfig};
use jsonrpsee::ws_client::WsClientBuilder;
use tokio::sync::broadcast;
use tokio_stream::wrappers::BroadcastStream;
const NUM_SUBSCRIPTION_RESPONSES: usize = 5;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let addr = run_server().await?;
let url = format!("ws://{}", addr);
let client1 =
WsClientBuilder::default().set_rpc_middleware(RpcServiceBuilder::new().rpc_logger(1024)).build(&url).await?;
let client2 =
WsClientBuilder::default().set_rpc_middleware(RpcServiceBuilder::new().rpc_logger(1024)).build(&url).await?;
let sub1: Subscription<i32> = client1.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await?;
let sub2: Subscription<i32> = client2.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await?;
let fut1 = sub1.take(NUM_SUBSCRIPTION_RESPONSES).for_each(|r| async move { tracing::info!("sub1 rx: {:?}", r) });
let fut2 = sub2.take(NUM_SUBSCRIPTION_RESPONSES).for_each(|r| async move { tracing::info!("sub2 rx: {:?}", r) });
future::join(fut1, fut2).await;
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
// let's configure the server only hold 5 messages in memory.
let config = ServerConfig::builder().set_message_buffer_capacity(5).build();
let server = Server::builder()
.set_config(config)
.set_rpc_middleware(RpcServiceBuilder::new().rpc_logger(1024))
.build("127.0.0.1:0")
.await?;
let (tx, _rx) = broadcast::channel::<usize>(16);
let mut module = RpcModule::new(tx.clone());
std::thread::spawn(move || produce_items(tx));
module
.register_subscription("subscribe_hello", "s_hello", "unsubscribe_hello", |_, pending, tx, _| async move {
let rx = tx.subscribe();
let stream = BroadcastStream::new(rx);
pipe_from_stream_with_bounded_buffer(pending, stream).await?;
Ok(())
})
.unwrap();
let addr = server.local_addr()?;
let handle = server.start(module);
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}
async fn pipe_from_stream_with_bounded_buffer(
pending: PendingSubscriptionSink,
stream: BroadcastStream<usize>,
) -> Result<(), anyhow::Error> {
let sink = pending.accept().await?;
let closed = sink.closed();
futures::pin_mut!(closed, stream);
loop {
match future::select(closed, stream.next()).await {
// subscription closed.
Either::Left((_, _)) => break Ok(()),
// received new item from the stream.
Either::Right((Some(Ok(item)), c)) => {
let msg = serde_json::value::to_raw_value(&item)?;
// NOTE: this will block until there a spot in the queue
// and you might want to do something smarter if it's
// critical that "the most recent item" must be sent when it is produced.
if sink.send(msg).await.is_err() {
break Ok(());
}
closed = c;
}
// Send back back the error.
Either::Right((Some(Err(e)), _)) => break Err(e.into()),
// Stream is closed.
Either::Right((None, _)) => break Ok(()),
}
}
}
// Naive example that broadcasts the produced values to all active subscribers.
fn produce_items(tx: broadcast::Sender<usize>) {
for c in 1..=100 {
std::thread::sleep(std::time::Duration::from_millis(1));
// This might fail if no receivers are alive, could occur if no subscriptions are active...
// Also be aware that this will succeed when at least one receiver is alive
// Thus, clients connecting at different point in time will not receive
// the items sent before the subscription got established.
let _ = tx.send(c);
}
}

View File

@@ -0,0 +1,138 @@
// 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::net::SocketAddr;
use std::time::Duration;
use futures::{Stream, StreamExt};
use jsonrpsee::core::Serialize;
use jsonrpsee::core::client::{Subscription, SubscriptionClientT};
use jsonrpsee::server::{RpcModule, Server, ServerConfig, TrySendError};
use jsonrpsee::ws_client::WsClientBuilder;
use jsonrpsee::{PendingSubscriptionSink, rpc_params};
use tokio::time::interval;
use tokio_stream::wrappers::IntervalStream;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
.expect("setting default subscriber failed");
let addr = run_server().await?;
let url = format!("ws://{}", addr);
let client = WsClientBuilder::default().build(&url).await?;
// Subscription with a single parameter
let mut sub_params_one: Subscription<Option<char>> =
client.subscribe("sub_one_param", rpc_params![3], "unsub_one_param").await?;
tracing::info!("subscription with one param: {:?}", sub_params_one.next().await);
// Subscription with multiple parameters
let mut sub_params_two: Subscription<String> =
client.subscribe("sub_params_two", rpc_params![2, 5], "unsub_params_two").await?;
tracing::info!("subscription with two params: {:?}", sub_params_two.next().await);
Ok(())
}
async fn run_server() -> anyhow::Result<SocketAddr> {
const LETTERS: &str = "abcdefghijklmnopqrstuvxyz";
let config = ServerConfig::builder().set_message_buffer_capacity(10).build();
let server = Server::builder().set_config(config).build("127.0.0.1:0").await?;
let mut module = RpcModule::new(());
module
.register_subscription(
"sub_one_param",
"sub_one_param",
"unsub_one_param",
|params, pending, _, _| async move {
// we are doing this verbose way to get a customized reject error on the subscription.
let idx = match params.one::<usize>() {
Ok(p) => p,
Err(e) => {
let _ = pending.reject(e).await;
return Ok(());
}
};
let item = LETTERS.chars().nth(idx);
let interval = interval(Duration::from_millis(200));
let stream = IntervalStream::new(interval).map(move |_| item);
pipe_from_stream_and_drop(pending, stream).await.map_err(Into::into)
},
)
.unwrap();
module
.register_subscription("sub_params_two", "params_two", "unsub_params_two", |params, pending, _, _| async move {
let (one, two) = params.parse::<(usize, usize)>()?;
let item = &LETTERS[one..two];
let interval = interval(Duration::from_millis(200));
let stream = IntervalStream::new(interval).map(move |_| item);
pipe_from_stream_and_drop(pending, stream).await.map_err(Into::into)
})
.unwrap();
let addr = server.local_addr()?;
let handle = server.start(module);
// In this example we don't care about doing shutdown so let's it run forever.
// You may use the `ServerHandle` to shut it down or manage it yourself.
tokio::spawn(handle.stopped());
Ok(addr)
}
pub async fn pipe_from_stream_and_drop<T: Serialize>(
pending: PendingSubscriptionSink,
mut stream: impl Stream<Item = T> + Unpin,
) -> Result<(), anyhow::Error> {
let mut sink = pending.accept().await?;
loop {
tokio::select! {
_ = sink.closed() => break Err(anyhow::anyhow!("Subscription was closed")),
maybe_item = stream.next() => {
let item = match maybe_item {
Some(item) => item,
None => break Err(anyhow::anyhow!("Subscription was closed")),
};
let msg = serde_json::value::to_raw_value(&item)?;
match sink.try_send(msg) {
Ok(_) => (),
Err(TrySendError::Closed(_)) => break Err(anyhow::anyhow!("Subscription was closed")),
// channel is full, let's be naive an just drop the message.
Err(TrySendError::Full(_)) => (),
}
}
}
}
}