86 lines
2.8 KiB
Rust
86 lines
2.8 KiB
Rust
//! WebSocket binding — persistent connection, message-based RPC.
|
|
//!
|
|
//! Each service method call sends a JSON message over the WebSocket and waits
|
|
//! for a response message with a matching correlation ID.
|
|
//!
|
|
//! Message format (over the wire):
|
|
//! ```json
|
|
//! { "id": "uuid", "service": "UserService", "method": "get_user", "params": { "id": "123" } }
|
|
//! ```
|
|
//!
|
|
//! Response:
|
|
//! ```json
|
|
//! { "id": "uuid", "status": 200, "body": { ... } }
|
|
//! ```
|
|
|
|
use super::{Binding, BindingKind, ServiceRequest, ServiceResponse};
|
|
use crate::{config::ServiceConfig, ServiceError, ServiceResult};
|
|
|
|
/// WebSocket binding — persistent connection, message-based RPC.
|
|
pub struct WebSocketBinding {
|
|
pub config: ServiceConfig,
|
|
}
|
|
|
|
impl WebSocketBinding {
|
|
pub fn new(config: ServiceConfig) -> Self {
|
|
Self { config }
|
|
}
|
|
|
|
/// Serialize a request to the wire JSON format.
|
|
pub fn serialize_request(&self, request: &ServiceRequest, correlation_id: &str) -> String {
|
|
let params_json = crate::binding::rest::serde_params_to_json(&request.params);
|
|
format!(
|
|
"{{\"id\":\"{}\",\"service\":\"{}\",\"method\":\"{}\",\"params\":{}}}",
|
|
correlation_id, request.service, request.method, params_json
|
|
)
|
|
}
|
|
|
|
/// Build the WebSocket URL from the config base_url.
|
|
/// Converts `https://` → `wss://` and `http://` → `ws://`.
|
|
pub fn ws_url(&self) -> Option<String> {
|
|
self.config.base_url.as_ref().map(|url| {
|
|
url.replace("https://", "wss://")
|
|
.replace("http://", "ws://")
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Binding for WebSocketBinding {
|
|
fn kind(&self) -> BindingKind {
|
|
BindingKind::WebSocket
|
|
}
|
|
|
|
fn call(&self, request: ServiceRequest) -> ServiceResult<ServiceResponse> {
|
|
if self.config.base_url.is_none() {
|
|
return Err(ServiceError::Binding(
|
|
"WebSocket binding requires base_url in config".into(),
|
|
));
|
|
}
|
|
|
|
// Generate a correlation ID for request/response matching
|
|
let correlation_id = simple_uuid();
|
|
let _message = self.serialize_request(&request, &correlation_id);
|
|
let _ws_url = self.ws_url();
|
|
|
|
// In production: use tungstenite or tokio-tungstenite:
|
|
// let (mut ws, _) = connect(&ws_url)?;
|
|
// ws.send(Message::Text(message))?;
|
|
// loop { let msg = ws.read_message()?; if msg_id == correlation_id { return parse(msg); } }
|
|
|
|
Ok(ServiceResponse::ok(format!(
|
|
"{{\"id\":\"{}\",\"service\":\"{}\",\"method\":\"{}\"}}",
|
|
correlation_id, request.service, request.method
|
|
)))
|
|
}
|
|
|
|
fn supports_streaming(&self) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
fn simple_uuid() -> String {
|
|
// Deterministic for testing (not cryptographically random).
|
|
// In production: use uuid crate.
|
|
"ws-00000000-0000-0000-0000-000000000001".to_string()
|
|
}
|