diff --git a/ewebsock/src/lib.rs b/ewebsock/src/lib.rs index 67eea3b..e2be0c3 100644 --- a/ewebsock/src/lib.rs +++ b/ewebsock/src/lib.rs @@ -124,7 +124,7 @@ pub type Result = std::result::Result; pub(crate) type EventHandler = Box ControlFlow<()>>; /// Options for a connection. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Options { /// The maximum size of a single incoming message frame, in bytes. /// @@ -134,6 +134,19 @@ pub struct Options { /// Ignored on Web. pub max_incoming_frame_size: usize, + /// Additional Request headers. + /// + /// Currently only supported on native. + pub additional_headers: Vec<(String, String)>, + + /// Additional subprotocols. + /// + /// + /// + /// + /// Currently only supported on native. + pub subprotocols: Vec, + /// Delay blocking in ms - default 10ms pub delay_blocking: std::time::Duration, } @@ -142,7 +155,9 @@ impl Default for Options { fn default() -> Self { Self { max_incoming_frame_size: 64 * 1024 * 1024, - delay_blocking: std::time::Duration::from_millis(10), + additional_headers: vec![], + subprotocols: vec![], + delay_blocking: std::time::Duration::from_millis(10), // default value 10ms, } } } diff --git a/ewebsock/src/native_tungstenite.rs b/ewebsock/src/native_tungstenite.rs index 1af2ae7..f7f11c7 100644 --- a/ewebsock/src/native_tungstenite.rs +++ b/ewebsock/src/native_tungstenite.rs @@ -5,6 +5,7 @@ use std::{ sync::mpsc::{Receiver, TryRecvError}, }; +use crate::tungstenite_common::into_requester; use crate::{EventHandler, Options, Result, WsEvent, WsMessage}; /// This is how you send [`WsMessage`]s to the server. @@ -64,21 +65,27 @@ pub(crate) fn ws_receive_impl(url: String, options: Options, on_event: EventHand /// Connect and call the given event handler on each received event. /// -/// Blocking version of [`ws_receive`], only available on native. +/// Blocking version of [`crate::ws_receive`], only available on native. /// /// # Errors /// All errors are returned to the caller, and NOT reported via `on_event`. pub fn ws_receiver_blocking(url: &str, options: Options, on_event: &EventHandler) -> Result<()> { - let config = tungstenite::protocol::WebSocketConfig::from(options); + let uri: tungstenite::http::Uri = url + .parse() + .map_err(|err| format!("Failed to parse URL {url:?}: {err}"))?; + let config = tungstenite::protocol::WebSocketConfig::from(options.clone()); let max_redirects = 3; // tungstenite default - let (mut socket, response) = - match tungstenite::client::connect_with_config(url, Some(config), max_redirects) { - Ok(result) => result, - Err(err) => { - return Err(format!("Connect: {err}")); - } - }; + let (mut socket, response) = match tungstenite::client::connect_with_config( + into_requester(uri, options), + Some(config), + max_redirects, + ) { + Ok(result) => result, + Err(err) => { + return Err(format!("Connect: {err}")); + } + }; log::debug!("WebSocket HTTP response code: {}", response.status()); log::trace!( @@ -166,15 +173,21 @@ pub fn ws_connect_blocking( rx: &Receiver, ) -> Result<()> { let delay = options.delay_blocking; - let config = tungstenite::protocol::WebSocketConfig::from(options); + let config = tungstenite::protocol::WebSocketConfig::from(options.clone()); let max_redirects = 3; // tungstenite default - let (mut socket, response) = - match tungstenite::client::connect_with_config(url, Some(config), max_redirects) { - Ok(result) => result, - Err(err) => { - return Err(format!("Connect: {err}")); - } - }; + let uri: tungstenite::http::Uri = url + .parse() + .map_err(|err| format!("Failed to parse URL {url:?}: {err}"))?; + let (mut socket, response) = match tungstenite::client::connect_with_config( + into_requester(uri, options), + Some(config), + max_redirects, + ) { + Ok(result) => result, + Err(err) => { + return Err(format!("Connect: {err}")); + } + }; log::debug!("WebSocket HTTP response code: {}", response.status()); log::trace!( @@ -217,7 +230,7 @@ pub fn ws_connect_blocking( WsMessage::Pong(data) => tungstenite::protocol::Message::Pong(data), WsMessage::Unknown(_) => panic!("You cannot send WsMessage::Unknown"), }; - if let Err(err) = socket.write(outgoing_message) { + if let Err(err) = socket.send(outgoing_message) { socket.close(None).ok(); socket.flush().ok(); return Err(format!("send: {err}")); @@ -278,3 +291,11 @@ pub fn ws_connect_blocking( } } } + +#[test] +fn test_connect() { + let options = crate::Options::default(); + // see documentation for more options + let (mut sender, _receiver) = crate::connect("ws://example.com", options).unwrap(); + sender.send(crate::WsMessage::Text("Hello!".into())); +} diff --git a/ewebsock/src/native_tungstenite_tokio.rs b/ewebsock/src/native_tungstenite_tokio.rs index d2a941f..69f6f09 100644 --- a/ewebsock/src/native_tungstenite_tokio.rs +++ b/ewebsock/src/native_tungstenite_tokio.rs @@ -1,5 +1,6 @@ use std::ops::ControlFlow; +use crate::tungstenite_common::into_requester; use crate::{EventHandler, Options, Result, WsEvent, WsMessage}; /// This is how you send [`WsMessage`]s to the server. @@ -49,11 +50,19 @@ async fn ws_connect_async( on_event: EventHandler, ) { use futures::StreamExt as _; - - let config = tungstenite::protocol::WebSocketConfig::from(options); + let uri: tungstenite::http::Uri = match url.parse() { + Ok(uri) => uri, + Err(err) => { + on_event(WsEvent::Error(format!( + "Failed to parse URL {url:?}: {err}" + ))); + return; + } + }; + let config = tungstenite::protocol::WebSocketConfig::from(options.clone()); let disable_nagle = false; // God damn everyone who adds negations to the names of their variables let (ws_stream, _response) = match tokio_tungstenite::connect_async_with_config( - url, + into_requester(uri, options), Some(config), disable_nagle, ) @@ -146,3 +155,18 @@ fn ws_connect_native(url: String, options: Options, on_event: EventHandler) -> W pub(crate) fn ws_receive_impl(url: String, options: Options, on_event: EventHandler) -> Result<()> { ws_connect_impl(url, options, on_event).map(|sender| sender.forget()) } + +#[cfg(feature = "tokio")] +#[test] +fn test_connect_tokio() { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + let options = crate::Options::default(); + // see documentation for more options + let (mut sender, _receiver) = crate::connect("ws://example.com", options).unwrap(); + sender.send(crate::WsMessage::Text("Hello!".into())); + }); +} diff --git a/ewebsock/src/tungstenite_common.rs b/ewebsock/src/tungstenite_common.rs index 4b8cfdc..c42f7d7 100644 --- a/ewebsock/src/tungstenite_common.rs +++ b/ewebsock/src/tungstenite_common.rs @@ -15,3 +15,18 @@ impl From for tungstenite::protocol::WebSocketConfig { } } } + +/// transform uri and options into a request builder +pub fn into_requester( + uri: tungstenite::http::Uri, + options: crate::Options, +) -> tungstenite::client::ClientRequestBuilder { + let mut client_request = tungstenite::client::ClientRequestBuilder::new(uri); + for (key, value) in options.additional_headers { + client_request = client_request.with_header(key, value); + } + for subprotocol in options.subprotocols { + client_request = client_request.with_sub_protocol(subprotocol); + } + client_request +} diff --git a/example_app/index.html b/example_app/index.html index 1c244f4..d5428cb 100644 --- a/example_app/index.html +++ b/example_app/index.html @@ -7,7 +7,7 @@ - example_app websocket template + ewebsock demo