Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Options::additional_headers and subprotocols #27

Merged
merged 30 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7baa740
fix: add Origin Header in native app
Its-Just-Nans Feb 13, 2024
39c1600
correct cargo.toml
Its-Just-Nans Feb 13, 2024
722c5f3
Merge branch 'main' into origin-header
Its-Just-Nans May 13, 2024
6629215
update
Its-Just-Nans May 13, 2024
f6d612e
add trunk
Its-Just-Nans May 13, 2024
e79b007
rm manifest
Its-Just-Nans May 13, 2024
bf3e070
rm
Its-Just-Nans May 13, 2024
a45012a
Merge branch 'main' into origin-header
Its-Just-Nans Jun 2, 2024
164d67d
bump version and clippy
Its-Just-Nans Jun 2, 2024
68c2b0a
bump echo_server and TODO
Its-Just-Nans Jun 2, 2024
aa122db
todo done
Its-Just-Nans Jun 2, 2024
a1c33bb
correct if
Its-Just-Nans Jun 2, 2024
1f1fe14
docs
Its-Just-Nans Jun 2, 2024
2154c81
bump to 0.23 !
Its-Just-Nans Jun 2, 2024
66a3534
docs
Its-Just-Nans Jun 2, 2024
f8abaa7
Update main.rs
Its-Just-Nans Jun 2, 2024
34fba9d
remove useless
Its-Just-Nans Jun 2, 2024
c89118e
Merge branch 'origin-header' of github.com:Its-Just-Nans/ewebsock int…
Its-Just-Nans Jun 2, 2024
d1545f3
typos
Its-Just-Nans Jun 3, 2024
d1d5c09
solve problems
Its-Just-Nans Jun 5, 2024
a60d82c
Merge branch 'main' into origin-header
Its-Just-Nans Jun 5, 2024
0ac302f
add for native tokio
Its-Just-Nans Jun 6, 2024
4443a06
clean
Its-Just-Nans Jun 6, 2024
c70c985
add links
Its-Just-Nans Jun 6, 2024
baa5bbb
add back
Its-Just-Nans Jun 6, 2024
8462ebf
Merge branch 'main' into origin-header
emilk Oct 10, 2024
b58c796
Revert Cargo.lock
emilk Oct 10, 2024
4eb7f00
Write which URL was bad in error message
emilk Oct 10, 2024
bf27223
Better URL parse error message
emilk Oct 10, 2024
19ad569
document that this is native-only
emilk Oct 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
804 changes: 428 additions & 376 deletions Cargo.lock

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,29 @@ This is a simple [WebSocket](https://en.wikipedia.org/wiki/WebSocket) library fo
## Usage

``` rust
let (mut sender, receiver) = ewebsock::connect("ws://example.com").unwrap();
let options = ewebsock::Options::default();
// add header and subprotocol to options
Its-Just-Nans marked this conversation as resolved.
Show resolved Hide resolved
let (mut sender, receiver) = ewebsock::connect("ws://example.com", options).unwrap();
sender.send(ewebsock::WsMessage::Text("Hello!".into()));
while let Some(event) = receiver.try_recv() {
println!("Received {:?}", event);
}
```

## Testing

First start the example echo server with:

```sh
cargo r -p echo_server
```

Then test the native library with:
Then test the library with:

```sh
# native mode
cargo run -p example_app
```

And the web library with:
```sh
./example_app/start_server.sh &
./example_app/build_web.sh --open
# web mode
cd example_app/ && trunk serve
```
2 changes: 1 addition & 1 deletion echo_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ publish = false


[dependencies]
tungstenite = { version = ">=0.17, <=0.21" }
tungstenite = { version = "0.23" }
14 changes: 9 additions & 5 deletions echo_server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#![allow(deprecated)] // TODO(emilk): Remove when we update tungstenite
#![allow(clippy::unwrap_used, clippy::disallowed_methods)] // We are just testing here.

use std::{net::TcpListener, thread::spawn};
Expand All @@ -11,14 +10,19 @@ fn main() {
spawn(move || {
let mut websocket = tungstenite::accept(stream.unwrap()).unwrap();
eprintln!("New client connected");
while let Ok(msg) = websocket.read_message() {
loop {
let msg = websocket.read().unwrap();
Its-Just-Nans marked this conversation as resolved.
Show resolved Hide resolved

// We do not want to send back ping/pong messages.
if msg.is_binary() || msg.is_text() {
if websocket.write_message(msg).is_ok() {
eprintln!("Responded.");
} else {
if let Err(err) = websocket.send(msg) {
eprintln!("Error sending message: {err}");
break;
} else {
eprintln!("Responded.");
}
} else {
eprintln!("Message received not text or binary.");
}
}
eprintln!("Client left.");
Expand Down
4 changes: 2 additions & 2 deletions ewebsock/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ log = "0.4"

# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tungstenite = { version = ">=0.17, <=0.21" }
tungstenite = { version = "0.23" }

# Optional dependencies for feature "tokio":
async-stream = { version = "0.3", optional = true }
Expand All @@ -57,7 +57,7 @@ futures-util = { version = "0.3", optional = true, default-features = false, fea
"std",
] }
tokio = { version = "1.16", features = ["rt", "sync"], optional = true }
tokio-tungstenite = { version = ">=0.17, <=0.21", optional = true }
tokio-tungstenite = { version = "0.23", optional = true }

# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
Expand Down
14 changes: 13 additions & 1 deletion ewebsock/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ pub type Result<T> = std::result::Result<T, Error>;
pub(crate) type EventHandler = Box<dyn Send + Fn(WsEvent) -> 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.
///
Expand All @@ -133,12 +133,24 @@ pub struct Options {
///
/// Ignored on Web.
pub max_incoming_frame_size: usize,

/// Additional Request headers
Its-Just-Nans marked this conversation as resolved.
Show resolved Hide resolved
pub additional_headers: Option<Vec<(String, String)>>,
Its-Just-Nans marked this conversation as resolved.
Show resolved Hide resolved

/// Additional subprotocols
Its-Just-Nans marked this conversation as resolved.
Show resolved Hide resolved
pub subprotocols: Option<Vec<String>>,

/// Delay blocking in ms - default 10ms
pub delay_blocking: Option<std::time::Duration>,
Its-Just-Nans marked this conversation as resolved.
Show resolved Hide resolved
}

impl Default for Options {
fn default() -> Self {
Self {
max_incoming_frame_size: 64 * 1024 * 1024,
additional_headers: None,
subprotocols: None,
delay_blocking: None,
}
}
}
Expand Down
86 changes: 60 additions & 26 deletions ewebsock/src/native_tungstenite.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
#![allow(deprecated)] // TODO(emilk): Remove when we update tungstenite

use std::{
ops::ControlFlow,
sync::mpsc::{Receiver, TryRecvError},
};

use crate::{EventHandler, Options, Result, WsEvent, WsMessage};

/// transform uri and options into a request builder
pub fn into_requester(
uri: tungstenite::http::Uri,
options: Options,
) -> tungstenite::client::ClientRequestBuilder {
let mut client_request = tungstenite::client::ClientRequestBuilder::new(uri);
if let Some(headers) = options.additional_headers {
for (key, value) in headers {
client_request = client_request.with_header(key, value);
}
}
if let Some(subprotocols) = options.subprotocols {
for subprotocol in subprotocols {
client_request = client_request.with_sub_protocol(subprotocol);
}
}
client_request
}

/// This is how you send [`WsMessage`]s to the server.
///
/// When the last clone of this is dropped, the connection is closed.
Expand Down Expand Up @@ -64,21 +81,28 @@ 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 = match url.parse() {
Ok(uri) => uri,
Err(err) => return Err(format!("Failed to parse URI: {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(
crate::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!(
Expand All @@ -95,7 +119,7 @@ pub fn ws_receiver_blocking(url: &str, options: Options, on_event: &EventHandler
}

loop {
let control = match socket.read_message() {
let control = match socket.read() {
Ok(incoming_msg) => match incoming_msg {
tungstenite::protocol::Message::Text(text) => {
on_event(WsEvent::Message(WsMessage::Text(text)))
Expand Down Expand Up @@ -155,7 +179,7 @@ pub(crate) fn ws_connect_impl(

/// Connect and call the given event handler on each received event.
///
/// This is a blocking variant of [`ws_connect`], only available on native.
/// This is a blocking variant of [`crate::ws_connect`], only available on native.
///
/// # Errors
/// All errors are returned to the caller, and NOT reported via `on_event`.
Expand All @@ -165,15 +189,25 @@ pub fn ws_connect_blocking(
on_event: &EventHandler,
rx: &Receiver<WsMessage>,
) -> Result<()> {
let config = tungstenite::protocol::WebSocketConfig::from(options);
let delay = options
.delay_blocking
.unwrap_or(std::time::Duration::from_millis(10)); // default value 10ms
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 = match url.parse() {
Ok(uri) => uri,
Err(err) => return Err(format!("Failed to parse URI: {err}")),
};
Its-Just-Nans marked this conversation as resolved.
Show resolved Hide resolved
let (mut socket, response) = match tungstenite::client::connect_with_config(
crate::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!(
Expand Down Expand Up @@ -216,22 +250,22 @@ 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_message(outgoing_message) {
if let Err(err) = socket.send(outgoing_message) {
socket.close(None).ok();
socket.write_pending().ok();
socket.flush().ok();
return Err(format!("send: {err}"));
}
}
Err(TryRecvError::Disconnected) => {
log::debug!("WsSender dropped - closing connection.");
socket.close(None).ok();
socket.write_pending().ok();
socket.flush().ok();
return Ok(());
}
Err(TryRecvError::Empty) => {}
};

let control = match socket.read_message() {
let control = match socket.read() {
Ok(incoming_msg) => {
did_work = true;
match incoming_msg {
Expand Down Expand Up @@ -273,7 +307,7 @@ pub fn ws_connect_blocking(
}

if !did_work {
std::thread::sleep(std::time::Duration::from_millis(10)); // TODO(emilk): make configurable
std::thread::sleep(delay);
}
}
}
1 change: 1 addition & 0 deletions ewebsock/src/tungstenite_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ impl From<crate::Options> for tungstenite::protocol::WebSocketConfig {
fn from(options: crate::Options) -> Self {
let crate::Options {
max_incoming_frame_size,
..
} = options;

Self {
Expand Down
1 change: 1 addition & 0 deletions example_app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
5 changes: 1 addition & 4 deletions example_app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
publish = false


[lib]
crate-type = ["cdylib", "rlib"]


[features]
default = []

Expand All @@ -25,6 +21,7 @@ ewebsock = { path = "../ewebsock", features = ["tls"] }

eframe = "0.26.2" # Gives us egui, epi and web+native backends
log = "0.4"
env_logger = "0.10"

# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
Expand Down
75 changes: 0 additions & 75 deletions example_app/build_web.sh

This file was deleted.

Loading