diff --git a/Cargo.lock b/Cargo.lock index 68139ff..15201fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1617,7 +1617,7 @@ dependencies = [ [[package]] name = "hyperware_process_lib" -version = "2.3.1" +version = "3.0.0" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 666ab45..77dc266 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "hyperware_process_lib" authors = ["Sybil Technologies AG"] -version = "2.3.1" +version = "3.0.0" edition = "2021" description = "A library for writing Hyperware processes in Rust." homepage = "https://hyperware.ai" diff --git a/src/http/server.rs b/src/http/server.rs index 18bb940..8255730 100644 --- a/src/http/server.rs +++ b/src/http/server.rs @@ -20,6 +20,11 @@ pub enum HttpServerRequest { WebSocketOpen { path: String, channel_id: u32, + #[serde(default)] + source_socket_addr: Option, + /// IP address from proxy headers (X-Forwarded-For, X-Real-IP, Cf-Connecting-Ip) + #[serde(default)] + forwarded_for: Option, }, /// Processes can both SEND and RECEIVE this kind of [`crate::Request`] /// (send as [`HttpServerAction::WebSocketPush`]). @@ -300,6 +305,8 @@ pub struct HttpServer { ws_paths: HashMap, /// A mapping of WebSocket paths to the channels that are open on them. ws_channels: HashMap>, + /// A mapping of WebSocket channel IDs to their client socket addresses. + ws_channel_addrs: HashMap, /// The timeout given for `http-server:distro:sys` to respond to a configuration request. pub timeout: u64, } @@ -451,6 +458,7 @@ impl HttpServer { http_paths: HashMap::new(), ws_paths: HashMap::new(), ws_channels: HashMap::new(), + ws_channel_addrs: HashMap::new(), timeout, } } @@ -983,11 +991,27 @@ impl HttpServer { } /// Handle a WebSocket open event from the HTTP server. - pub fn handle_websocket_open(&mut self, path: &str, channel_id: u32) { + pub fn handle_websocket_open( + &mut self, + path: &str, + channel_id: u32, + source_socket_addr: Option, + forwarded_for: Option, + ) { self.ws_channels .entry(path.to_string()) .or_insert(HashSet::new()) .insert(channel_id); + // Store the client IP, preferring forwarded_for (from proxy headers) over socket addr + let client_ip = forwarded_for.or(source_socket_addr); + if let Some(ip) = client_ip { + self.ws_channel_addrs.insert(channel_id, ip); + } + } + + /// Get the socket address for a WebSocket channel. + pub fn get_ws_channel_addr(&self, channel_id: u32) -> Option<&String> { + self.ws_channel_addrs.get(&channel_id) } /// Handle a WebSocket close event from the HTTP server. @@ -995,6 +1019,7 @@ impl HttpServer { self.ws_channels.iter_mut().for_each(|(_, channels)| { channels.remove(&channel_id); }); + self.ws_channel_addrs.remove(&channel_id); } pub fn parse_request(&self, body: &[u8]) -> Result { @@ -1024,8 +1049,13 @@ impl HttpServer { channel_id, message_type, } => ws_handler(channel_id, message_type, last_blob().unwrap_or_default()), - HttpServerRequest::WebSocketOpen { path, channel_id } => { - self.handle_websocket_open(&path, channel_id); + HttpServerRequest::WebSocketOpen { + path, + channel_id, + source_socket_addr, + forwarded_for, + } => { + self.handle_websocket_open(&path, channel_id, source_socket_addr, forwarded_for); } HttpServerRequest::WebSocketClose(channel_id) => { self.handle_websocket_close(channel_id); diff --git a/src/hyperapp.rs b/src/hyperapp.rs index a73287f..5990311 100644 --- a/src/hyperapp.rs +++ b/src/hyperapp.rs @@ -72,6 +72,22 @@ pub fn get_server() -> Option<&'static mut HttpServer> { APP_HELPERS.with(|ctx| ctx.borrow().current_server.map(|ptr| unsafe { &mut *ptr })) } +/// Get the socket address for a WebSocket channel by its ID. +/// Returns None if the channel doesn't exist or has no recorded address. +pub fn get_ws_channel_addr(channel_id: u32) -> Option { + get_server().and_then(|server| server.get_ws_channel_addr(channel_id).cloned()) +} + +pub fn get_http_request() -> Option { + APP_HELPERS.with(|helpers| { + helpers + .borrow() + .current_http_context + .as_ref() + .map(|ctx| ctx.request.clone()) + }) +} + pub fn get_http_method() -> Option { APP_HELPERS.with(|helpers| { helpers