You can intercept every JSON-RPC call by providing custom RPC middleware. This is done via set_rpc_middleware on the ServerBuilder.
Unlike standard tower middleware, RpcMiddleware uses a service trait that takes &self instead of &mut self. Consequently, any state required by your middleware must use interior mutability (e.g., Arc<Mutex<T>> or Arc<AtomicUsize>).
To implement it, define a struct that implements RpcServiceT and use RpcServiceBuilder::layer_fn to wrap it.
use jsonrpsee_server::middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceT, Request, MethodResponse, Notification, Batch};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Clone)]
struct MyMiddleware<S> {
service: S,
count: Arc<AtomicUsize>,
}
impl<S> RpcServiceT for MyMiddleware<S>
where
S: RpcServiceT + Clone + Send + Sync + '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();
async move {
let rp = service.call(req).await;
count.fetch_add(1, Ordering::Relaxed);
rp
}
}
fn batch<'a>(&self, batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
self.service.batch(batch)
}
fn notification<'a>(&self, notif: Notification<'a>) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
self.service.notification(notif)
}
}
// Usage:
// let m = RpcServiceBuilder::new().layer_fn(move |service: ()| MyMiddleware { service, count: Arc::new(AtomicUsize::new(0)) });
// let builder = ServerBuilder::default().set_rpc_middleware(m);