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 ClientConnectorWasm #92

Merged
merged 1 commit into from
Oct 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading