Skip to content

Commit

Permalink
add ClientConnectorWasm
Browse files Browse the repository at this point in the history
  • Loading branch information
UkoeHB committed Oct 7, 2023
1 parent 379eb55 commit 9e13354
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Use [`tokio-tungstenite-wasm`](https://github.com/TannerRogalsky/tokio-tungstenite-wasm) errors internally to better support cross-platform clients.
- Use [`enfync`](https://github.com/UkoeHB/enfync) runtime handles internally to better support cross-platform clients. Default clients continue to use tokio.
- Add `ClientConnector` abstraction for connecting clients and add `ezsockets::client::connect_with`.
- Add `ClientConnectorWasm` and `wasm_client` feature.


Migration guide:
Expand Down
7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ base64 = "0.21.0"
enfync = "0.1.0"
futures = "0.3.21"
http = "0.2.8"
tokio = { version = "1.17.0", features = ["sync", "rt", "macros", "time"] }
tokio = { version = "1.17.0", features = ["sync", "macros", "time"] }
tracing = "0.1.31"
url = "2.2.2"
cfg-if = "1.0.0"
Expand All @@ -42,11 +42,12 @@ tokio-native-tls = { version = "0.3.1", optional = true }
default = ["native_client", "server"]

client = ["tokio-tungstenite-wasm"]
native_client = ["client", "tokio-tungstenite"]
native_client = ["client", "tokio-tungstenite", "tokio/rt"]
wasm_client = ["client", "tokio-tungstenite-wasm"]

tungstenite_common = ["tokio-tungstenite"]

server = ["tungstenite_common", "tokio-tungstenite-wasm"]
server = ["tungstenite_common", "tokio-tungstenite-wasm", "tokio/rt"]
tungstenite = ["server"]
axum = ["server", "dep:axum", "axum-core", "bytes", "futures-util", "http-body", "hyper", "sha-1"]

Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ Creating a WebSocket server or a client in Rust can be troublesome. This crate f
- Traits to allow declarative and event-based programming.
- Easy concurrency with Tokio and async/await. Server sessions are Clone'able and can be shared between tasks.
- Heartbeat mechanism to keep the connection alive.
- Automatic reconnection of WebSocket Client.
- Support for multiple back-ends such as Axum or Tungstenite.
- Automatic reconnection of WebSocket Clients.
- Support for arbitrary client back-ends, with built-in native and WASM client connectors.
- Support for multiple server back-ends such as Axum or Tungstenite.
- TLS support for servers with `rustls` and `native-tls`.

## Documentation
Expand All @@ -25,14 +26,14 @@ View the full documentation at [docs.rs/ezsockets](http://docs.rs/ezsockets)

## Client

[`tokio-tungstenite`](https://github.com/snapview/tokio-tungstenite) is being used under the hood.
By default clients use [`tokio-tungstenite`](https://github.com/snapview/tokio-tungstenite) under the hood. Disable default features and enable `wasm_client` to run clients on WASM targets.

See [examples/simple-client](https://github.com/gbaranski/ezsockets/tree/master/examples/simple-client) for a simple usage
and [docs.rs/ezsockets/server](https://docs.rs/ezsockets/latest/ezsockets/client/index.html) for documentation.

## Server

WebSocket server can use one of supported back-ends:
WebSocket server can use one of the supported back-ends:
- [`tokio-tungstenite`](https://github.com/snapview/tokio-tungstenite) - the simplest way to get started.
- [`axum`](https://github.com/tokio-rs/axum) - ergonomic and modular web framework built with Tokio, Tower, and Hyper
- [`actix-web`](https://github.com/actix/actix-web) - Work in progress at [#22](https://github.com/gbaranski/ezsockets/issues/22)
Expand All @@ -46,4 +47,4 @@ Licensed under [MIT](https://choosealicense.com/licenses/mit/).

# Contact

Reach me out on Discord `gbaranski#5119`, or mail me at [email protected].
Reach me out on Discord `gbaranski#5119`, or mail me at [email protected].
25 changes: 20 additions & 5 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,13 @@ impl ClientConfig {
self
}

fn connect_http_request(&self) -> Request {
/// Get the config's headers.
pub fn headers(&self) -> &http::HeaderMap {
&self.headers
}

/// Extract a Websockets HTTP request.
pub fn connect_http_request(&self) -> Request {
let mut http_request = Request::builder()
.uri(self.url.as_str())
.method("GET")
Expand All @@ -207,6 +213,13 @@ impl ClientConfig {
}
http_request
}

/// Extract the URL request.
///
/// This is needed for WASM clients, where building HTTP requests is deferred to the `web_sys::Websocket` implementation.
pub fn connect_url(&self) -> &str {
self.url.as_str()
}
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -275,7 +288,10 @@ pub trait ClientConnector {
/// Connect to a websocket server.
///
/// Returns `Err` if the request is invalid.
async fn connect(&self, request: Request) -> Result<Self::Socket, Self::ConnectError>;
async fn connect(
&self,
client_config: &ClientConfig,
) -> Result<Self::Socket, Self::ConnectError>;
}

/// An `ezsockets` client.
Expand Down Expand Up @@ -381,7 +397,7 @@ pub async fn connect<E: ClientExt + 'static>(
client_fn: impl FnOnce(Client<E>) -> E,
config: ClientConfig,
) -> (Client<E>, impl Future<Output = Result<(), Error>>) {
let client_connector = crate::client_connector_tokio::ClientConnectorTokio::default();
let client_connector = crate::ClientConnectorTokio::default();
let (handle, mut future) = connect_with(client_fn, config, client_connector);
let future = async move {
future
Expand Down Expand Up @@ -551,8 +567,7 @@ async fn client_connect<E: ClientExt, Connector: ClientConnector>(
for i in 1.. {
// connection attempt
tracing::info!("connecting attempt no: {}...", i);
let connect_http_request = config.connect_http_request();
let result = client_connector.connect(connect_http_request).await;
let result = client_connector.connect(config).await;
match result {
Ok(socket_impl) => {
tracing::info!("successfully connected");
Expand Down
12 changes: 9 additions & 3 deletions src/client_connectors/client_connector_tokio.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::client::ClientConnector;
use crate::Request;
use crate::client::{ClientConfig, ClientConnector};
use enfync::TryAdopt;
use tokio_tungstenite::tungstenite;

Expand Down Expand Up @@ -27,6 +26,12 @@ impl Default for ClientConnectorTokio {
}
}

impl From<enfync::builtin::native::TokioHandle> for ClientConnectorTokio {
fn from(handle: enfync::builtin::native::TokioHandle) -> Self {
Self { handle }
}
}

#[async_trait::async_trait]
impl ClientConnector for ClientConnectorTokio {
type Handle = enfync::builtin::native::TokioHandle;
Expand All @@ -45,7 +50,8 @@ impl ClientConnector for ClientConnectorTokio {
/// Connect to a websocket server.
///
/// Returns `Err` if the request is invalid.
async fn connect(&self, request: Request) -> Result<Self::Socket, Self::ConnectError> {
async fn connect(&self, config: &ClientConfig) -> Result<Self::Socket, Self::ConnectError> {
let request = config.connect_http_request();
let (socket, _) = tokio_tungstenite::connect_async(request).await?;
Ok(socket)
}
Expand Down
43 changes: 43 additions & 0 deletions src/client_connectors/client_connector_wasm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::client::{ClientConfig, ClientConnector};

/// Implementation of [`ClientConnector`] for tokio runtimes.
#[derive(Clone)]
pub struct ClientConnectorWasm {
handle: enfync::builtin::wasm::WASMHandle,
}

impl Default for ClientConnectorWasm {
fn default() -> Self {
let handle = enfync::builtin::wasm::WASMHandle::default();
Self { handle }
}
}

#[async_trait::async_trait]
impl ClientConnector for ClientConnectorWasm {
type Handle = enfync::builtin::wasm::WASMHandle;
type Message = tokio_tungstenite_wasm::Message;
type WSError = tokio_tungstenite_wasm::Error;
type Socket = tokio_tungstenite_wasm::WebSocketStream;
type ConnectError = tokio_tungstenite_wasm::Error;

/// Get the connector's runtime handle.
fn handle(&self) -> Self::Handle {
self.handle.clone()
}

/// Connect to a websocket server.
///
/// Returns `Err` if the request is invalid.
///
/// Panics if any headers were added to the client config. Websockets on browser does not support
/// additional headers (use [`ClientConfig::query_parameter()`] instead).
async fn connect(&self, config: &ClientConfig) -> Result<Self::Socket, Self::ConnectError> {
if config.headers().len() > 0 {
panic!("client may not submit HTTP headers in WASM connection requests");
}
let request_url = config.connect_url();
let (socket, _) = tokio_tungstenite_wasm::connect(request_url).await?;
Ok(socket)
}
}
16 changes: 12 additions & 4 deletions src/client_connectors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
#[cfg(feature = "native_client")]
pub mod client_connector_tokio;
cfg_if::cfg_if! {
if #[cfg(all(feature = "native_client", not(target_family = "wasm")))] {
mod client_connector_tokio;
pub use client_connector_tokio::*;
}
}

//#[cfg(feature = "wasm_client")]
//pub mod client_connector_wasm;
cfg_if::cfg_if! {
if #[cfg(all(feature = "wasm_client", target_family = "wasm"))] {
mod client_connector_wasm;
pub use client_connector_wasm::*;
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ cfg_if::cfg_if! {
pub mod client;

pub use client::connect;
pub use client::connect_with;
pub use client::ClientConfig;
pub use client::ClientExt;
pub use client::Client;
Expand Down

0 comments on commit 9e13354

Please sign in to comment.