tf_rust_engineio/lib.rs
1//! # Rust-engineio-client
2//!
3//! An implementation of a engine.io client written in the rust programming language. This implementation currently
4//! supports revision 4 of the engine.io protocol. If you have any connection issues with this client,
5//! make sure the server uses at least revision 4 of the engine.io protocol.
6//!
7//! ## Example usage
8//!
9//! ``` rust
10//! use tf_rust_engineio::{ClientBuilder, Client, packet::{Packet, PacketId}};
11//! use url::Url;
12//! use bytes::Bytes;
13//!
14//! // get a client with an `on_open` callback
15//! let client: Client = ClientBuilder::new(Url::parse("http://localhost:4201").unwrap())
16//! .on_open(|_| println!("Connection opened!"))
17//! .build()
18//! .expect("Creating client failed");
19//!
20//! // connect to the server
21//! client.connect().expect("Connection failed");
22//!
23//! // create a packet, in this case a message packet and emit it
24//! let packet = Packet::new(PacketId::Message, Bytes::from_static(b"Hello World"));
25//! client.emit(packet).expect("Server unreachable");
26//!
27//! // disconnect from the server
28//! client.disconnect().expect("Disconnect failed")
29//! ```
30//!
31//! The main entry point for using this crate is the [`ClientBuilder`] (or [`asynchronous::ClientBuilder`] respectively)
32//! which provides the opportunity to define how you want to connect to a certain endpoint.
33//! The following connection methods are available:
34//! * `build`: Build websocket if allowed, if not fall back to polling. Standard configuration.
35//! * `build_polling`: enforces a `polling` transport.
36//! * `build_websocket_with_upgrade`: Build socket with a polling transport then upgrade to websocket transport (if possible).
37//! * `build_websocket`: Build socket with only a websocket transport, crashes when websockets are not allowed.
38//!
39//!
40//! ## Current features
41//!
42//! This implementation now supports all of the features of the engine.io protocol mentioned [here](https://github.com/socketio/engine.io-protocol).
43//! This includes various transport options, the possibility of sending engine.io packets and registering the
44//! common engine.io event callbacks:
45//! * on_open
46//! * on_close
47//! * on_data
48//! * on_error
49//! * on_packet
50//!
51//! It is also possible to pass in custom tls configurations via the `TlsConnector` as well
52//! as custom headers for the opening request.
53//!
54//! ## Async version
55//!
56//! The crate also ships with an asynchronous version that can be enabled with a feature flag.
57//! The async version implements the same features mentioned above.
58//! The asynchronous version has a similar API, just with async functions. Currently the futures
59//! can only be executed with [`tokio`](https://tokio.rs). In the first benchmarks the async version
60//! showed improvements of up to 93% in speed.
61//! To make use of the async version, import the crate as follows:
62//! ```toml
63//! [dependencies]
64//! tf-rust-engineio = { version = "0.6.1", features = ["async"] }
65//! ```
66//!
67#![allow(clippy::rc_buffer)]
68#![warn(clippy::complexity)]
69#![warn(clippy::style)]
70#![warn(clippy::perf)]
71#![warn(clippy::correctness)]
72// Allow large error types - Error enum contains rich context (HTTP responses, URLs, etc.)
73// which is intentional for debugging. Boxing would add indirection overhead.
74#![allow(clippy::result_large_err)]
75/// A small macro that spawns a scoped thread. Used for calling the callback
76/// functions.
77macro_rules! spawn_scoped {
78 ($e:expr) => {
79 std::thread::scope(|s| {
80 s.spawn(|| $e);
81 });
82 };
83}
84
85pub mod asynchronous;
86mod callback;
87pub mod client;
88/// Generic header map
89pub mod header;
90pub mod packet;
91mod socket;
92pub mod transport;
93pub mod transports;
94
95pub const ENGINE_IO_VERSION: i32 = 4;
96
97/// Contains the error type which will be returned with every result in this
98/// crate. Handles all kinds of errors.
99pub mod error;
100
101pub use client::{Client, ClientBuilder};
102pub use error::Error;
103pub use packet::{Packet, PacketId};
104
105#[cfg(test)]
106pub(crate) mod test {
107 use super::*;
108 use native_tls::TlsConnector;
109 const CERT_PATH: &str = "../ci/cert/ca.crt";
110 use native_tls::Certificate;
111 use std::fs::File;
112 use std::io::Read;
113
114 pub(crate) fn tls_connector() -> error::Result<TlsConnector> {
115 let cert_path = std::env::var("CA_CERT_PATH").unwrap_or_else(|_| CERT_PATH.to_owned());
116 let mut cert_file = File::open(cert_path)?;
117 let mut buf = vec![];
118 cert_file.read_to_end(&mut buf)?;
119 let cert: Certificate = Certificate::from_pem(&buf[..]).unwrap();
120 Ok(TlsConnector::builder()
121 // ONLY USE FOR TESTING!
122 .danger_accept_invalid_hostnames(true)
123 .add_root_certificate(cert)
124 .build()
125 .unwrap())
126 }
127 /// The `engine.io` server for testing runs on port 4201
128 const SERVER_URL: &str = "http://localhost:4201";
129 /// The `engine.io` server that refuses upgrades runs on port 4203
130 const SERVER_POLLING_URL: &str = "http://localhost:4203";
131 const SERVER_URL_SECURE: &str = "https://localhost:4202";
132 use url::Url;
133
134 pub(crate) fn engine_io_server() -> crate::error::Result<Url> {
135 let url = std::env::var("ENGINE_IO_SERVER").unwrap_or_else(|_| SERVER_URL.to_owned());
136 Ok(Url::parse(&url)?)
137 }
138
139 pub(crate) fn engine_io_polling_server() -> crate::error::Result<Url> {
140 let url = std::env::var("ENGINE_IO_POLLING_SERVER")
141 .unwrap_or_else(|_| SERVER_POLLING_URL.to_owned());
142 Ok(Url::parse(&url)?)
143 }
144
145 pub(crate) fn engine_io_server_secure() -> crate::error::Result<Url> {
146 let url = std::env::var("ENGINE_IO_SECURE_SERVER")
147 .unwrap_or_else(|_| SERVER_URL_SECURE.to_owned());
148 Ok(Url::parse(&url)?)
149 }
150
151 /// Spawns a minimal one-shot HTTP server on a random local port that always
152 /// replies with the given status code and body. Used for verifying that the
153 /// polling transport surfaces server-side error bodies via
154 /// [`crate::Error::HttpErrorWithBody`]. Accepts a small number of
155 /// connections so a single test can drive both GET and POST paths.
156 pub(crate) fn spawn_http_error_mock(status: u16, body: &'static str) -> Url {
157 use std::io::{Read, Write};
158 use std::net::TcpListener;
159
160 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
161 let port = listener.local_addr().unwrap().port();
162
163 std::thread::spawn(move || {
164 for _ in 0..8 {
165 let Ok((mut stream, _)) = listener.accept() else {
166 break;
167 };
168 let mut buf = [0u8; 2048];
169 let _ = stream.read(&mut buf);
170 let response = format!(
171 "HTTP/1.1 {status} ERR\r\nContent-Type: application/json\r\nContent-Length: {len}\r\nConnection: close\r\n\r\n{body}",
172 status = status,
173 len = body.len(),
174 body = body
175 );
176 let _ = stream.write_all(response.as_bytes());
177 }
178 });
179
180 Url::parse(&format!("http://127.0.0.1:{}/", port)).unwrap()
181 }
182}