Updates
This commit is contained in:
@@ -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)
|
||||
}
|
65
reference_jsonrpsee_crate_examples/core_client.rs
Normal file
65
reference_jsonrpsee_crate_examples/core_client.rs
Normal 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)
|
||||
}
|
104
reference_jsonrpsee_crate_examples/cors_server.rs
Normal file
104
reference_jsonrpsee_crate_examples/cors_server.rs
Normal 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)
|
||||
}
|
83
reference_jsonrpsee_crate_examples/host_filter_middleware.rs
Normal file
83
reference_jsonrpsee_crate_examples/host_filter_middleware.rs
Normal 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)
|
||||
}
|
65
reference_jsonrpsee_crate_examples/http.rs
Normal file
65
reference_jsonrpsee_crate_examples/http.rs
Normal 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)
|
||||
}
|
129
reference_jsonrpsee_crate_examples/http_middleware.rs
Normal file
129
reference_jsonrpsee_crate_examples/http_middleware.rs
Normal 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)
|
||||
}
|
109
reference_jsonrpsee_crate_examples/http_proxy_middleware.rs
Normal file
109
reference_jsonrpsee_crate_examples/http_proxy_middleware.rs
Normal 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)
|
||||
}
|
380
reference_jsonrpsee_crate_examples/jsonrpsee_as_service.rs
Normal file
380
reference_jsonrpsee_crate_examples/jsonrpsee_as_service.rs
Normal 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(¬if) {
|
||||
Some(Ok(BatchEntry::Notification(notif)))
|
||||
} else {
|
||||
// Just filter out the notification if the auth fails
|
||||
// because notifications are not expected to return a response.
|
||||
None
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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)
|
||||
}
|
123
reference_jsonrpsee_crate_examples/proc_macro.rs
Normal file
123
reference_jsonrpsee_crate_examples/proc_macro.rs
Normal 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)
|
||||
}
|
95
reference_jsonrpsee_crate_examples/proc_macro_bounds.rs
Normal file
95
reference_jsonrpsee_crate_examples/proc_macro_bounds.rs
Normal 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)
|
||||
}
|
@@ -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)
|
||||
}
|
265
reference_jsonrpsee_crate_examples/rpc_middleware.rs
Normal file
265
reference_jsonrpsee_crate_examples/rpc_middleware.rs
Normal 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)
|
||||
}
|
173
reference_jsonrpsee_crate_examples/rpc_middleware_client.rs
Normal file
173
reference_jsonrpsee_crate_examples/rpc_middleware_client.rs
Normal 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)
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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)
|
||||
}
|
69
reference_jsonrpsee_crate_examples/tokio_console.rs
Normal file
69
reference_jsonrpsee_crate_examples/tokio_console.rs
Normal 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)
|
||||
}
|
67
reference_jsonrpsee_crate_examples/ws.rs
Normal file
67
reference_jsonrpsee_crate_examples/ws.rs
Normal 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)
|
||||
}
|
122
reference_jsonrpsee_crate_examples/ws_dual_stack.rs
Normal file
122
reference_jsonrpsee_crate_examples/ws_dual_stack.rs
Normal 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,
|
||||
}
|
149
reference_jsonrpsee_crate_examples/ws_pubsub_broadcast.rs
Normal file
149
reference_jsonrpsee_crate_examples/ws_pubsub_broadcast.rs
Normal 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);
|
||||
}
|
||||
}
|
138
reference_jsonrpsee_crate_examples/ws_pubsub_with_params.rs
Normal file
138
reference_jsonrpsee_crate_examples/ws_pubsub_with_params.rs
Normal 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(_)) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user