tf_rust_socketio/lib.rs
1//! Rust-socket.io is a socket.io client written in the Rust Programming Language.
2//! ## Example usage
3//!
4//! ``` rust
5//! use tf_rust_socketio::{ClientBuilder, Payload, RawClient};
6//! use serde_json::json;
7//! use std::time::Duration;
8//!
9//! // define a callback which is called when a payload is received
10//! // this callback gets the payload as well as an instance of the
11//! // socket to communicate with the server
12//! let callback = |payload: Payload, socket: RawClient| {
13//! match payload {
14//! Payload::Text(values, _) => println!("Received: {:#?}", values),
15//! Payload::Binary(bin_data, _) => println!("Received bytes: {:#?}", bin_data),
16//! // This variant is deprecated, use Payload::Text instead
17//! #[allow(deprecated)]
18//! Payload::String(str, _) => println!("Received: {}", str),
19//! }
20//! socket.emit("test", json!({"got ack": true})).expect("Server unreachable")
21//! };
22//!
23//! // get a socket that is connected to the admin namespace
24//! let mut socket = ClientBuilder::new("http://localhost:4200/")
25//! .namespace("/admin")
26//! .on("test", callback)
27//! .on("error", |err, _| eprintln!("Error: {:#?}", err))
28//! .connect()
29//! .expect("Connection failed");
30//!
31//! // emit to the "foo" event
32//! let json_payload = json!({"token": 123});
33//!
34//! socket.emit("foo", json_payload).expect("Server unreachable");
35//!
36//! // define a callback, that's executed when the ack got acked
37//! let ack_callback = |message: Payload, _: RawClient| {
38//! println!("Yehaa! My ack got acked?");
39//! println!("Ack data: {:#?}", message);
40//! };
41//!
42//! let json_payload = json!({"myAckData": 123});
43//!
44//! // emit with an ack
45//! let ack = socket
46//! .emit_with_ack("test", json_payload, Duration::from_secs(2), ack_callback)
47//! .expect("Server unreachable");
48//! ```
49//!
50//! The main entry point for using this crate is the [`ClientBuilder`] which provides
51//! a way to easily configure a socket in the needed way. When the `connect` method
52//! is called on the builder, it returns a connected client which then could be used
53//! to emit messages to certain events. One client can only be connected to one namespace.
54//! If you need to listen to the messages in different namespaces you need to
55//! allocate multiple sockets.
56//!
57//! ## Current features
58//!
59//! This implementation now supports all of the features of the socket.io protocol mentioned
60//! [here](https://github.com/socketio/socket.io-protocol).
61//! It generally tries to make use of websockets as often as possible. This means most times
62//! only the opening request uses http and as soon as the server mentions that he is able to use
63//! websockets, an upgrade is performed. But if this upgrade is not successful or the server
64//! does not mention an upgrade possibility, http-long polling is used (as specified in the protocol specs).
65//!
66//! Here's an overview of possible use-cases:
67//!
68//! - connecting to a server.
69//! - register callbacks for the following event types:
70//! - open
71//! - close
72//! - error
73//! - message
74//! - custom events like "foo", "on_payment", etc.
75//! - send JSON data to the server (via `serde_json` which provides safe
76//! handling).
77//! - send JSON data to the server and receive an `ack`.
78//! - send and handle Binary data.
79#![cfg_attr(
80 feature = "async",
81 doc = r#"
82## Async version
83This library provides an ability for being executed in an asynchronous context using `tokio` as
84the execution runtime.
85Please note that the current async implementation is in beta, the interface can be object to
86drastic changes.
87The async `Client` and `ClientBuilder` support a similar interface to the sync version and live
88in the [`asynchronous`] module. In order to enable the support, you need to enable the `async`
89feature flag:
90```toml
91tf_rust_socketio = { version = "^0.4.1", features = ["async"] }
92```
93
94The following code shows the example above in async fashion:
95
96``` rust
97use futures_util::FutureExt;
98use tf_rust_socketio::{
99 asynchronous::{Client, ClientBuilder},
100 Payload,
101};
102use serde_json::json;
103use std::time::Duration;
104
105#[tokio::main]
106async fn main() {
107 // define a callback which is called when a payload is received
108 // this callback gets the payload as well as an instance of the
109 // socket to communicate with the server
110 let callback = |payload: Payload, socket: Client| {
111 async move {
112 match payload {
113 Payload::Text(values, _) => println!("Received: {:#?}", values),
114 Payload::Binary(bin_data, _) => println!("Received bytes: {:#?}", bin_data),
115 // This is deprecated use Payload::Text instead
116 #[allow(deprecated)]
117 Payload::String(str, _) => println!("Received: {}", str),
118 }
119 socket
120 .emit("test", json!({"got ack": true}))
121 .await
122 .expect("Server unreachable");
123 }
124 .boxed()
125 };
126
127 // get a socket that is connected to the admin namespace
128 let socket = ClientBuilder::new("http://localhost:4200/")
129 .namespace("/admin")
130 .on("test", callback)
131 .on("error", |err, _| {
132 async move { eprintln!("Error: {:#?}", err) }.boxed()
133 })
134 .connect()
135 .await
136 .expect("Connection failed");
137
138 // emit to the "foo" event
139 let json_payload = json!({"token": 123});
140 socket
141 .emit("foo", json_payload)
142 .await
143 .expect("Server unreachable");
144
145 // define a callback, that's executed when the ack got acked
146 let ack_callback = |message: Payload, _: Client| {
147 async move {
148 println!("Yehaa! My ack got acked?");
149 println!("Ack data: {:#?}", message);
150 }
151 .boxed()
152 };
153
154 let json_payload = json!({"myAckData": 123});
155 // emit with an ack
156 socket
157 .emit_with_ack("test", json_payload, Duration::from_secs(2), ack_callback)
158 .await
159 .expect("Server unreachable");
160
161 socket.disconnect().await.expect("Disconnect failed");
162}
163```"#
164)]
165#![allow(clippy::rc_buffer)]
166#![warn(clippy::complexity)]
167#![warn(clippy::style)]
168#![warn(clippy::perf)]
169#![warn(clippy::correctness)]
170// Allow large error types - Error enum contains rich context from engineio
171// which is intentional for debugging. Boxing would add indirection overhead.
172#![allow(clippy::result_large_err)]
173
174/// Defines client only structs
175pub mod client;
176/// Deprecated import since 0.3.0-alpha-2, use Event in the crate root instead.
177/// Defines the events that could be sent or received.
178pub mod event;
179pub(crate) mod packet;
180/// Deprecated import since 0.3.0-alpha-2, use Event in the crate root instead.
181/// Defines the types of payload (binary or string), that
182/// could be sent or received.
183pub mod payload;
184mod socket;
185
186/// Deprecated import since 0.3.0-alpha-2, use Error in the crate root instead.
187/// Contains the error type which will be returned with every result in this
188/// crate.
189pub mod error;
190
191#[cfg(test)]
192mod test_concurrent_ack;
193
194#[cfg(test)]
195#[cfg(feature = "async")]
196mod test_async_concurrent_ack;
197
198#[cfg(feature = "async")]
199/// Asynchronous version of the socket.io client. This module contains the async
200/// [`crate::asynchronous::Client`] as well as a builder
201/// ([`crate::asynchronous::ClientBuilder`]) that allows for configuring a client.
202pub mod asynchronous;
203
204pub use error::Error;
205
206pub use {event::CloseReason, event::Event, payload::Payload};
207
208pub use client::{ClientBuilder, RawClient, TransportType};
209
210// TODO: 0.4.0 remove
211#[deprecated(since = "0.3.0-alpha-2", note = "Socket renamed to Client")]
212pub use client::{ClientBuilder as SocketBuilder, RawClient as Socket};
213
214#[cfg(test)]
215pub(crate) mod test {
216 use url::Url;
217
218 /// The socket.io server for testing runs on port 4200
219 const SERVER_URL: &str = "http://localhost:4200";
220
221 pub(crate) fn socket_io_server() -> Url {
222 let url = std::env::var("SOCKET_IO_SERVER").unwrap_or_else(|_| SERVER_URL.to_owned());
223 let mut url = Url::parse(&url).unwrap();
224
225 if url.path() == "/" {
226 url.set_path("/socket.io/");
227 }
228
229 url
230 }
231
232 // The socket.io auth server for testing runs on port 4204
233 const AUTH_SERVER_URL: &str = "http://localhost:4204";
234
235 pub(crate) fn socket_io_auth_server() -> Url {
236 let url =
237 std::env::var("SOCKET_IO_AUTH_SERVER").unwrap_or_else(|_| AUTH_SERVER_URL.to_owned());
238 let mut url = Url::parse(&url).unwrap();
239
240 if url.path() == "/" {
241 url.set_path("/socket.io/");
242 }
243
244 url
245 }
246
247 // The socket.io restart server for testing runs on port 4205
248 const RESTART_SERVER_URL: &str = "http://localhost:4205";
249
250 pub(crate) fn socket_io_restart_server() -> Url {
251 let url = std::env::var("SOCKET_IO_RESTART_SERVER")
252 .unwrap_or_else(|_| RESTART_SERVER_URL.to_owned());
253 let mut url = Url::parse(&url).unwrap();
254
255 if url.path() == "/" {
256 url.set_path("/socket.io/");
257 }
258
259 url
260 }
261
262 // The socket.io restart url auth server for testing runs on port 4206
263 const RESTART_URL_AUTH_SERVER_URL: &str = "http://localhost:4206";
264
265 pub(crate) fn socket_io_restart_url_auth_server() -> Url {
266 let url = std::env::var("SOCKET_IO_RESTART_URL_AUTH_SERVER")
267 .unwrap_or_else(|_| RESTART_URL_AUTH_SERVER_URL.to_owned());
268 let mut url = Url::parse(&url).unwrap();
269
270 if url.path() == "/" {
271 url.set_path("/socket.io/");
272 }
273
274 url
275 }
276}