340 lines
9.6 KiB
Rust
340 lines
9.6 KiB
Rust
//! RPC server implementation.
|
|
|
|
use std::net::SocketAddr;
|
|
|
|
use jsonrpsee::server::{ServerBuilder, ServerHandle};
|
|
use jsonrpsee::RpcModule;
|
|
|
|
use crate::{DEFAULT_RPC_PORT, DEFAULT_WS_PORT, MAX_BATCH_SIZE, MAX_RESPONSE_SIZE};
|
|
|
|
/// RPC server configuration.
|
|
#[derive(Clone, Debug)]
|
|
pub struct RpcConfig {
|
|
/// HTTP bind address.
|
|
pub http_addr: SocketAddr,
|
|
/// WebSocket bind address.
|
|
pub ws_addr: SocketAddr,
|
|
/// Enable HTTP.
|
|
pub enable_http: bool,
|
|
/// Enable WebSocket.
|
|
pub enable_ws: bool,
|
|
/// Maximum batch size.
|
|
pub max_batch_size: usize,
|
|
/// Maximum response size.
|
|
pub max_response_size: usize,
|
|
/// Enable CORS.
|
|
pub enable_cors: bool,
|
|
/// Allowed origins for CORS.
|
|
pub cors_origins: Vec<String>,
|
|
/// Rate limit (requests per second).
|
|
pub rate_limit: Option<u32>,
|
|
/// Maximum connections.
|
|
pub max_connections: u32,
|
|
}
|
|
|
|
impl Default for RpcConfig {
|
|
fn default() -> Self {
|
|
RpcConfig {
|
|
http_addr: SocketAddr::from(([127, 0, 0, 1], DEFAULT_RPC_PORT)),
|
|
ws_addr: SocketAddr::from(([127, 0, 0, 1], DEFAULT_WS_PORT)),
|
|
enable_http: true,
|
|
enable_ws: true,
|
|
max_batch_size: MAX_BATCH_SIZE,
|
|
max_response_size: MAX_RESPONSE_SIZE,
|
|
enable_cors: true,
|
|
cors_origins: vec!["*".to_string()],
|
|
rate_limit: None,
|
|
max_connections: 100,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RpcConfig {
|
|
/// Creates config for local development.
|
|
pub fn local() -> Self {
|
|
RpcConfig::default()
|
|
}
|
|
|
|
/// Creates config for public access.
|
|
pub fn public() -> Self {
|
|
RpcConfig {
|
|
http_addr: SocketAddr::from(([0, 0, 0, 0], DEFAULT_RPC_PORT)),
|
|
ws_addr: SocketAddr::from(([0, 0, 0, 0], DEFAULT_WS_PORT)),
|
|
enable_http: true,
|
|
enable_ws: true,
|
|
max_batch_size: 50,
|
|
max_response_size: MAX_RESPONSE_SIZE,
|
|
enable_cors: true,
|
|
cors_origins: vec!["*".to_string()],
|
|
rate_limit: Some(100), // 100 req/s
|
|
max_connections: 1000,
|
|
}
|
|
}
|
|
|
|
/// Creates config for trusted internal network.
|
|
pub fn internal() -> Self {
|
|
RpcConfig {
|
|
http_addr: SocketAddr::from(([127, 0, 0, 1], DEFAULT_RPC_PORT)),
|
|
ws_addr: SocketAddr::from(([127, 0, 0, 1], DEFAULT_WS_PORT)),
|
|
enable_http: true,
|
|
enable_ws: true,
|
|
max_batch_size: MAX_BATCH_SIZE,
|
|
max_response_size: MAX_RESPONSE_SIZE * 10, // Higher limit
|
|
enable_cors: false,
|
|
cors_origins: vec![],
|
|
rate_limit: None,
|
|
max_connections: 50,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// RPC server.
|
|
pub struct RpcServer {
|
|
/// Configuration.
|
|
config: RpcConfig,
|
|
/// HTTP server handle.
|
|
http_handle: Option<ServerHandle>,
|
|
/// WebSocket server handle.
|
|
ws_handle: Option<ServerHandle>,
|
|
}
|
|
|
|
impl RpcServer {
|
|
/// Creates a new RPC server.
|
|
pub fn new(config: RpcConfig) -> Self {
|
|
RpcServer {
|
|
config,
|
|
http_handle: None,
|
|
ws_handle: None,
|
|
}
|
|
}
|
|
|
|
/// Starts the RPC server with the given module.
|
|
pub async fn start(&mut self, module: RpcModule<()>) -> Result<(), RpcServerError> {
|
|
if self.config.enable_http {
|
|
self.start_http(module.clone()).await?;
|
|
}
|
|
|
|
if self.config.enable_ws {
|
|
self.start_ws(module).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Starts the HTTP server.
|
|
async fn start_http(&mut self, module: RpcModule<()>) -> Result<(), RpcServerError> {
|
|
let server = ServerBuilder::default()
|
|
.max_request_body_size(self.config.max_response_size as u32)
|
|
.max_response_body_size(self.config.max_response_size as u32)
|
|
.max_connections(self.config.max_connections)
|
|
.build(self.config.http_addr)
|
|
.await
|
|
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
|
|
|
|
let addr = server
|
|
.local_addr()
|
|
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
|
|
tracing::info!("HTTP RPC server listening on {}", addr);
|
|
|
|
self.http_handle = Some(server.start(module));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Starts the WebSocket server.
|
|
async fn start_ws(&mut self, module: RpcModule<()>) -> Result<(), RpcServerError> {
|
|
// In jsonrpsee 0.21+, WebSocket is enabled by default
|
|
let server = ServerBuilder::default()
|
|
.max_request_body_size(self.config.max_response_size as u32)
|
|
.max_response_body_size(self.config.max_response_size as u32)
|
|
.max_connections(self.config.max_connections)
|
|
.build(self.config.ws_addr)
|
|
.await
|
|
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
|
|
|
|
let addr = server
|
|
.local_addr()
|
|
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
|
|
tracing::info!("WebSocket RPC server listening on {}", addr);
|
|
|
|
self.ws_handle = Some(server.start(module));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Stops the RPC server.
|
|
pub async fn stop(&mut self) {
|
|
if let Some(handle) = self.http_handle.take() {
|
|
handle.stop().unwrap();
|
|
tracing::info!("HTTP RPC server stopped");
|
|
}
|
|
|
|
if let Some(handle) = self.ws_handle.take() {
|
|
handle.stop().unwrap();
|
|
tracing::info!("WebSocket RPC server stopped");
|
|
}
|
|
}
|
|
|
|
/// Waits for the server to stop.
|
|
pub async fn wait(&self) {
|
|
if let Some(ref handle) = self.http_handle {
|
|
handle.clone().stopped().await;
|
|
}
|
|
if let Some(ref handle) = self.ws_handle {
|
|
handle.clone().stopped().await;
|
|
}
|
|
}
|
|
|
|
/// Returns the HTTP address.
|
|
pub fn http_addr(&self) -> Option<SocketAddr> {
|
|
if self.config.enable_http {
|
|
Some(self.config.http_addr)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Returns the WebSocket address.
|
|
pub fn ws_addr(&self) -> Option<SocketAddr> {
|
|
if self.config.enable_ws {
|
|
Some(self.config.ws_addr)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// RPC server errors.
|
|
#[derive(Debug, Clone, thiserror::Error)]
|
|
pub enum RpcServerError {
|
|
/// Failed to start server.
|
|
#[error("Failed to start server: {0}")]
|
|
StartFailed(String),
|
|
|
|
/// Server already running.
|
|
#[error("Server already running")]
|
|
AlreadyRunning,
|
|
|
|
/// Server not running.
|
|
#[error("Server not running")]
|
|
NotRunning,
|
|
}
|
|
|
|
/// Builder for RPC modules.
|
|
pub struct RpcModuleBuilder {
|
|
module: RpcModule<()>,
|
|
}
|
|
|
|
impl RpcModuleBuilder {
|
|
/// Creates a new module builder.
|
|
pub fn new() -> Self {
|
|
RpcModuleBuilder {
|
|
module: RpcModule::new(()),
|
|
}
|
|
}
|
|
|
|
/// Registers a method.
|
|
pub fn register_method<F, R>(mut self, name: &'static str, callback: F) -> Self
|
|
where
|
|
F: Fn(jsonrpsee::types::Params<'_>) -> R + Send + Sync + Clone + 'static,
|
|
R: jsonrpsee::IntoResponse + Send + 'static,
|
|
{
|
|
let _ = self
|
|
.module
|
|
.register_method(name, move |params, _| callback(params));
|
|
self
|
|
}
|
|
|
|
/// Registers an async method.
|
|
pub fn register_async_method<F, Fut, R>(mut self, name: &'static str, callback: F) -> Self
|
|
where
|
|
F: Fn(jsonrpsee::types::Params<'static>) -> Fut + Send + Sync + Clone + 'static,
|
|
Fut: std::future::Future<Output = R> + Send + 'static,
|
|
R: jsonrpsee::IntoResponse + Send + 'static,
|
|
{
|
|
let _ = self.module.register_async_method(name, move |params, _| {
|
|
let callback = callback.clone();
|
|
async move { callback(params).await }
|
|
});
|
|
self
|
|
}
|
|
|
|
/// Merges another module.
|
|
pub fn merge(mut self, other: RpcModule<()>) -> Result<Self, RpcServerError> {
|
|
self.module
|
|
.merge(other)
|
|
.map_err(|e| RpcServerError::StartFailed(e.to_string()))?;
|
|
Ok(self)
|
|
}
|
|
|
|
/// Builds the module.
|
|
pub fn build(self) -> RpcModule<()> {
|
|
self.module
|
|
}
|
|
}
|
|
|
|
impl Default for RpcModuleBuilder {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// Creates a basic RPC module with standard methods.
|
|
pub fn create_base_module() -> RpcModule<()> {
|
|
let mut module = RpcModule::new(());
|
|
|
|
// Register basic methods
|
|
module
|
|
.register_method("synor_getServerVersion", |_, _| {
|
|
serde_json::json!({
|
|
"version": env!("CARGO_PKG_VERSION"),
|
|
"name": "synord"
|
|
})
|
|
})
|
|
.expect("Failed to register method");
|
|
|
|
module
|
|
.register_method("synor_echo", |params, _| {
|
|
let message: String = params.one().unwrap_or_default();
|
|
message
|
|
})
|
|
.expect("Failed to register method");
|
|
|
|
module
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_rpc_config_default() {
|
|
let config = RpcConfig::default();
|
|
assert!(config.enable_http);
|
|
assert!(config.enable_ws);
|
|
assert_eq!(config.http_addr.port(), DEFAULT_RPC_PORT);
|
|
}
|
|
|
|
#[test]
|
|
fn test_rpc_config_public() {
|
|
let config = RpcConfig::public();
|
|
assert!(config.rate_limit.is_some());
|
|
assert!(config.enable_cors);
|
|
}
|
|
|
|
#[test]
|
|
fn test_base_module() {
|
|
let module = create_base_module();
|
|
// Module should have methods registered
|
|
assert!(module.method_names().count() > 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_module_builder() {
|
|
let module = RpcModuleBuilder::new()
|
|
.register_method("test_method", |_| "hello")
|
|
.build();
|
|
|
|
assert!(module.method_names().any(|n| n == "test_method"));
|
|
}
|
|
}
|