//! 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, /// Rate limit (requests per second). pub rate_limit: Option, /// 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, /// WebSocket server handle. ws_handle: Option, } 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 { if self.config.enable_http { Some(self.config.http_addr) } else { None } } /// Returns the WebSocket address. pub fn ws_addr(&self) -> Option { 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(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(mut self, name: &'static str, callback: F) -> Self where F: Fn(jsonrpsee::types::Params<'static>) -> Fut + Send + Sync + Clone + 'static, Fut: std::future::Future + 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.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")); } }