From 9634d566476a7422d330169b12e69a8cd4670288 Mon Sep 17 00:00:00 2001 From: CQ Xiao Date: Tue, 18 Jun 2024 18:37:49 +0800 Subject: [PATCH 1/9] Add WSS support. --- rumqttc/CHANGELOG.md | 1 + rumqttc/Cargo.toml | 9 +++- rumqttc/examples/wss.rs | 89 +++++++++++++++++++++++++++++++++++++ rumqttc/src/eventloop.rs | 7 ++- rumqttc/src/lib.rs | 13 +++--- rumqttc/src/v5/eventloop.rs | 7 ++- 6 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 rumqttc/examples/wss.rs diff --git a/rumqttc/CHANGELOG.md b/rumqttc/CHANGELOG.md index 34f5438c..e2c82c1c 100644 --- a/rumqttc/CHANGELOG.md +++ b/rumqttc/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Made `DisconnectProperties` struct public. * Replace `Vec>` with `FixedBitSet` for managing packet ids of released QoS 2 publishes and incoming QoS 2 publishes in `MqttState`. * Accept `native_tls::TlsConnector` as input for `Transport::tls_with_config`. +* Accept `native_tls::TlsConnector` as input for `Transport::wss_with_config`. ### Deprecated diff --git a/rumqttc/Cargo.toml b/rumqttc/Cargo.toml index 2e2bae14..88538fdb 100644 --- a/rumqttc/Cargo.toml +++ b/rumqttc/Cargo.toml @@ -17,8 +17,8 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["use-rustls"] -use-rustls = ["dep:tokio-rustls", "dep:rustls-webpki", "dep:rustls-pemfile", "dep:rustls-native-certs"] -use-native-tls = ["dep:tokio-native-tls", "dep:native-tls"] +use-rustls = ["dep:tokio-rustls", "dep:rustls-webpki", "dep:rustls-pemfile", "dep:rustls-native-certs", "async-tungstenite/tokio-rustls-native-certs"] +use-native-tls = ["dep:tokio-native-tls", "dep:native-tls", "async-tungstenite/tokio-native-tls"] websocket = ["dep:async-tungstenite", "dep:ws_stream_tungstenite", "dep:http"] proxy = ["dep:async-http-proxy"] @@ -83,3 +83,8 @@ required-features = ["websocket"] name = "websocket_proxy" path = "examples/websocket_proxy.rs" required-features = ["websocket", "proxy"] + +[[example]] +name = "wss" +path = "examples/wss.rs" +required-features = ["websocket"] diff --git a/rumqttc/examples/wss.rs b/rumqttc/examples/wss.rs new file mode 100644 index 00000000..e45701a1 --- /dev/null +++ b/rumqttc/examples/wss.rs @@ -0,0 +1,89 @@ +use rumqttc::{AsyncClient, MqttOptions, QoS}; +use std::{error::Error, time::Duration}; +use tokio::{task, time}; + +#[cfg(any(feature = "use-rustls", feature = "use-native-tls"))] +use rumqttc::Transport; +#[cfg(all(feature = "use-rustls", not(feature = "use-native-tls")))] +use tokio_rustls::rustls::ClientConfig; + +#[cfg(feature = "use-native-tls")] +use tokio_native_tls::native_tls; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<(), Box> { + pretty_env_logger::init(); + + // port parameter is ignored when scheme is websocket + let mut mqttoptions = MqttOptions::new( + "test-1", + "wss://test.mosquitto.org:8081", 8081, + ); + + #[cfg(feature = "use-native-tls")] + { + // Use native-tls to load root certificates from the operating system. + println!("Using native-tls to load root certificates from the operating system."); + let mut builder = native_tls::TlsConnector::builder(); + let pem = vec![1, 2, 3]; + // let pem = include_bytes!("native-tls-cert.pem"); + let cert = native_tls::Certificate::from_pem(&pem)?; + builder.add_root_certificate(cert); + let connector = builder.build()?; + mqttoptions.set_transport(Transport::wss_with_config(connector.into())); + } + #[cfg(all(feature = "use-rustls", not(feature = "use-native-tls")))] + { + // Use rustls-native-certs to load root certificates from the operating system. + println!("Using rustls-native-certs to load root certificates from the operating system."); + let mut root_cert_store = tokio_rustls::rustls::RootCertStore::empty(); + root_cert_store.add_parsable_certificates( + rustls_native_certs::load_native_certs().expect("could not load platform certs"), + ); + + let client_config = ClientConfig::builder() + .with_root_certificates(root_cert_store) + .with_no_client_auth(); + + mqttoptions.set_transport(Transport::wss_with_config(client_config.into())); + } + + mqttoptions.set_keep_alive(Duration::from_secs(60)); + + let (client, mut eventloop) = AsyncClient::new(mqttoptions, 10); + task::spawn(async move { + requests(client).await; + time::sleep(Duration::from_secs(3)).await; + }); + + loop { + let event = eventloop.poll().await; + match event { + Ok(notif) => { + println!("Event = {notif:?}"); + } + Err(err) => { + println!("Error = {err:?}"); + return Ok(()); + } + } + } +} + +async fn requests(client: AsyncClient) { + client + .subscribe("hello/world", QoS::AtMostOnce) + .await + .unwrap(); + + for i in 1..=10 { + client + .publish("hello/world", QoS::ExactlyOnce, false, vec![1; i]) + .await + .unwrap(); + + time::sleep(Duration::from_secs(1)).await; + } + + time::sleep(Duration::from_secs(120)).await; +} diff --git a/rumqttc/src/eventloop.rs b/rumqttc/src/eventloop.rs index d31690d9..8e1c6f2d 100644 --- a/rumqttc/src/eventloop.rs +++ b/rumqttc/src/eventloop.rs @@ -450,7 +450,7 @@ async fn network_connect( options.max_outgoing_packet_size, ) } - #[cfg(all(feature = "use-rustls", feature = "websocket"))] + #[cfg(all(any(feature = "use-rustls", feature = "use-native-tls"), feature = "websocket"))] Transport::Wss(tls_config) => { let mut request = options.broker_addr.as_str().into_client_request()?; request @@ -461,6 +461,11 @@ async fn network_connect( request = request_modifier(request).await; } + #[cfg(feature = "use-native-tls")] + let connector = tls::native_tls_connector(&tls_config).await?; + + // cfg: "use-native-tls" overwrites "use-rustls" + #[cfg(all(feature = "use-rustls", not(feature = "use-native-tls")))] let connector = tls::rustls_connector(&tls_config).await?; let (socket, response) = async_tungstenite::tokio::client_async_tls_with_connector( diff --git a/rumqttc/src/lib.rs b/rumqttc/src/lib.rs index 0c2d67fe..dd663636 100644 --- a/rumqttc/src/lib.rs +++ b/rumqttc/src/lib.rs @@ -100,7 +100,10 @@ extern crate log; use std::fmt::{self, Debug, Formatter}; -#[cfg(any(feature = "use-rustls", feature = "websocket"))] +#[cfg(any( + feature = "use-rustls", + feature = "websocket" +))] use std::sync::Arc; use std::time::Duration; @@ -233,8 +236,8 @@ pub enum Transport { #[cfg(feature = "websocket")] #[cfg_attr(docsrs, doc(cfg(feature = "websocket")))] Ws, - #[cfg(all(feature = "use-rustls", feature = "websocket"))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "use-rustls", feature = "websocket"))))] + #[cfg(all(any(feature = "use-rustls", feature = "use-native-tls"), feature = "websocket"))] + #[cfg_attr(docsrs, doc(cfg(all(any(feature = "use-rustls", feature = "use-native-tls"), feature = "websocket"))))] Wss(TlsConfiguration), } @@ -305,8 +308,8 @@ impl Transport { Self::wss_with_config(config) } - #[cfg(all(feature = "use-rustls", feature = "websocket"))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "use-rustls", feature = "websocket"))))] + #[cfg(all(any(feature = "use-rustls", feature = "use-native-tls"), feature = "websocket"))] + #[cfg_attr(docsrs, doc(cfg(all(any(feature = "use-rustls", feature = "use-native-tls"), feature = "websocket"))))] pub fn wss_with_config(tls_config: TlsConfiguration) -> Self { Self::Wss(tls_config) } diff --git a/rumqttc/src/v5/eventloop.rs b/rumqttc/src/v5/eventloop.rs index cd0568ad..0ec53571 100644 --- a/rumqttc/src/v5/eventloop.rs +++ b/rumqttc/src/v5/eventloop.rs @@ -366,7 +366,7 @@ async fn network_connect(options: &MqttOptions) -> Result { let mut request = options.broker_addr.as_str().into_client_request()?; request @@ -377,6 +377,11 @@ async fn network_connect(options: &MqttOptions) -> Result Date: Wed, 19 Jun 2024 12:58:21 +0800 Subject: [PATCH 2/9] Improve app. --- rumqttc/examples/wss.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rumqttc/examples/wss.rs b/rumqttc/examples/wss.rs index e45701a1..844df38b 100644 --- a/rumqttc/examples/wss.rs +++ b/rumqttc/examples/wss.rs @@ -25,9 +25,9 @@ async fn main() -> Result<(), Box> { // Use native-tls to load root certificates from the operating system. println!("Using native-tls to load root certificates from the operating system."); let mut builder = native_tls::TlsConnector::builder(); - let pem = vec![1, 2, 3]; + let pem = &vec![1, 2, 3]; // let pem = include_bytes!("native-tls-cert.pem"); - let cert = native_tls::Certificate::from_pem(&pem)?; + let cert = native_tls::Certificate::from_pem(pem)?; builder.add_root_certificate(cert); let connector = builder.build()?; mqttoptions.set_transport(Transport::wss_with_config(connector.into())); From 2cc591081c0a1fe5a6654f53de0650476c44e1a6 Mon Sep 17 00:00:00 2001 From: CQ Xiao Date: Wed, 19 Jun 2024 14:55:59 +0800 Subject: [PATCH 3/9] Make example easier to test. --- rumqttc/examples/wss.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rumqttc/examples/wss.rs b/rumqttc/examples/wss.rs index 844df38b..2a28c1aa 100644 --- a/rumqttc/examples/wss.rs +++ b/rumqttc/examples/wss.rs @@ -25,9 +25,9 @@ async fn main() -> Result<(), Box> { // Use native-tls to load root certificates from the operating system. println!("Using native-tls to load root certificates from the operating system."); let mut builder = native_tls::TlsConnector::builder(); - let pem = &vec![1, 2, 3]; - // let pem = include_bytes!("native-tls-cert.pem"); - let cert = native_tls::Certificate::from_pem(pem)?; + let _pem = &vec![1, 2, 3]; + // let _pem = include_bytes!("native-tls-cert.pem"); + let cert = native_tls::Certificate::from_pem(_pem)?; builder.add_root_certificate(cert); let connector = builder.build()?; mqttoptions.set_transport(Transport::wss_with_config(connector.into())); From b4423cf9d299962da73c6a5a991ed581c2902a53 Mon Sep 17 00:00:00 2001 From: CQ Xiao Date: Wed, 19 Jun 2024 14:56:36 +0800 Subject: [PATCH 4/9] Refine dependencies. --- rumqttc/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rumqttc/Cargo.toml b/rumqttc/Cargo.toml index 88538fdb..34ffea85 100644 --- a/rumqttc/Cargo.toml +++ b/rumqttc/Cargo.toml @@ -17,9 +17,9 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["use-rustls"] -use-rustls = ["dep:tokio-rustls", "dep:rustls-webpki", "dep:rustls-pemfile", "dep:rustls-native-certs", "async-tungstenite/tokio-rustls-native-certs"] +use-rustls = ["dep:tokio-rustls", "dep:rustls-webpki", "dep:rustls-pemfile", "dep:rustls-native-certs"] use-native-tls = ["dep:tokio-native-tls", "dep:native-tls", "async-tungstenite/tokio-native-tls"] -websocket = ["dep:async-tungstenite", "dep:ws_stream_tungstenite", "dep:http"] +websocket = ["async-tungstenite", "dep:ws_stream_tungstenite", "dep:http"] proxy = ["dep:async-http-proxy"] [dependencies] From 61efb0235b97c16f4ace322b02ed4c4f3af01929 Mon Sep 17 00:00:00 2001 From: CQ Xiao Date: Wed, 19 Jun 2024 15:10:57 +0800 Subject: [PATCH 5/9] Improve examples, add example for v5. --- rumqttc/Cargo.toml | 5 ++ rumqttc/examples/wss.rs | 11 ++++- rumqttc/examples/wss_v5.rs | 96 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 rumqttc/examples/wss_v5.rs diff --git a/rumqttc/Cargo.toml b/rumqttc/Cargo.toml index 34ffea85..5bb738e1 100644 --- a/rumqttc/Cargo.toml +++ b/rumqttc/Cargo.toml @@ -88,3 +88,8 @@ required-features = ["websocket", "proxy"] name = "wss" path = "examples/wss.rs" required-features = ["websocket"] + +[[example]] +name = "wss_v5" +path = "examples/wss_v5.rs" +required-features = ["websocket"] diff --git a/rumqttc/examples/wss.rs b/rumqttc/examples/wss.rs index 2a28c1aa..1a4b90ec 100644 --- a/rumqttc/examples/wss.rs +++ b/rumqttc/examples/wss.rs @@ -2,7 +2,6 @@ use rumqttc::{AsyncClient, MqttOptions, QoS}; use std::{error::Error, time::Duration}; use tokio::{task, time}; -#[cfg(any(feature = "use-rustls", feature = "use-native-tls"))] use rumqttc::Transport; #[cfg(all(feature = "use-rustls", not(feature = "use-native-tls")))] use tokio_rustls::rustls::ClientConfig; @@ -17,7 +16,11 @@ async fn main() -> Result<(), Box> { // port parameter is ignored when scheme is websocket let mut mqttoptions = MqttOptions::new( "test-1", - "wss://test.mosquitto.org:8081", 8081, + #[cfg(any(feature = "use-rustls", feature = "use-native-tls"))] + "wss://test.mosquitto.org:8081", + #[cfg(not(any(feature = "use-rustls", feature = "use-native-tls")))] + "wss://test.mosquitto.org:8080", + 8080, ); #[cfg(feature = "use-native-tls")] @@ -47,6 +50,10 @@ async fn main() -> Result<(), Box> { mqttoptions.set_transport(Transport::wss_with_config(client_config.into())); } + #[cfg(not(any(feature = "use-rustls", feature = "use-native-tls")))] + { + mqttoptions.set_transport(Transport::Ws); + } mqttoptions.set_keep_alive(Duration::from_secs(60)); diff --git a/rumqttc/examples/wss_v5.rs b/rumqttc/examples/wss_v5.rs new file mode 100644 index 00000000..1a4b90ec --- /dev/null +++ b/rumqttc/examples/wss_v5.rs @@ -0,0 +1,96 @@ +use rumqttc::{AsyncClient, MqttOptions, QoS}; +use std::{error::Error, time::Duration}; +use tokio::{task, time}; + +use rumqttc::Transport; +#[cfg(all(feature = "use-rustls", not(feature = "use-native-tls")))] +use tokio_rustls::rustls::ClientConfig; + +#[cfg(feature = "use-native-tls")] +use tokio_native_tls::native_tls; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<(), Box> { + pretty_env_logger::init(); + + // port parameter is ignored when scheme is websocket + let mut mqttoptions = MqttOptions::new( + "test-1", + #[cfg(any(feature = "use-rustls", feature = "use-native-tls"))] + "wss://test.mosquitto.org:8081", + #[cfg(not(any(feature = "use-rustls", feature = "use-native-tls")))] + "wss://test.mosquitto.org:8080", + 8080, + ); + + #[cfg(feature = "use-native-tls")] + { + // Use native-tls to load root certificates from the operating system. + println!("Using native-tls to load root certificates from the operating system."); + let mut builder = native_tls::TlsConnector::builder(); + let _pem = &vec![1, 2, 3]; + // let _pem = include_bytes!("native-tls-cert.pem"); + let cert = native_tls::Certificate::from_pem(_pem)?; + builder.add_root_certificate(cert); + let connector = builder.build()?; + mqttoptions.set_transport(Transport::wss_with_config(connector.into())); + } + #[cfg(all(feature = "use-rustls", not(feature = "use-native-tls")))] + { + // Use rustls-native-certs to load root certificates from the operating system. + println!("Using rustls-native-certs to load root certificates from the operating system."); + let mut root_cert_store = tokio_rustls::rustls::RootCertStore::empty(); + root_cert_store.add_parsable_certificates( + rustls_native_certs::load_native_certs().expect("could not load platform certs"), + ); + + let client_config = ClientConfig::builder() + .with_root_certificates(root_cert_store) + .with_no_client_auth(); + + mqttoptions.set_transport(Transport::wss_with_config(client_config.into())); + } + #[cfg(not(any(feature = "use-rustls", feature = "use-native-tls")))] + { + mqttoptions.set_transport(Transport::Ws); + } + + mqttoptions.set_keep_alive(Duration::from_secs(60)); + + let (client, mut eventloop) = AsyncClient::new(mqttoptions, 10); + task::spawn(async move { + requests(client).await; + time::sleep(Duration::from_secs(3)).await; + }); + + loop { + let event = eventloop.poll().await; + match event { + Ok(notif) => { + println!("Event = {notif:?}"); + } + Err(err) => { + println!("Error = {err:?}"); + return Ok(()); + } + } + } +} + +async fn requests(client: AsyncClient) { + client + .subscribe("hello/world", QoS::AtMostOnce) + .await + .unwrap(); + + for i in 1..=10 { + client + .publish("hello/world", QoS::ExactlyOnce, false, vec![1; i]) + .await + .unwrap(); + + time::sleep(Duration::from_secs(1)).await; + } + + time::sleep(Duration::from_secs(120)).await; +} From fa314ed4784b09ff1c2a3931d6b6ed42c83d8552 Mon Sep 17 00:00:00 2001 From: CQ Xiao Date: Wed, 19 Jun 2024 15:11:40 +0800 Subject: [PATCH 6/9] Improve feature configuration --- rumqttc/src/eventloop.rs | 7 +++---- rumqttc/src/v5/eventloop.rs | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/rumqttc/src/eventloop.rs b/rumqttc/src/eventloop.rs index 8e1c6f2d..c0b72120 100644 --- a/rumqttc/src/eventloop.rs +++ b/rumqttc/src/eventloop.rs @@ -387,7 +387,7 @@ async fn network_connect( let (domain, port) = match options.transport() { #[cfg(feature = "websocket")] Transport::Ws => split_url(&options.broker_addr)?, - #[cfg(all(feature = "use-rustls", feature = "websocket"))] + #[cfg(all(any(feature = "use-rustls", feature = "use-native-tls"), feature = "websocket"))] Transport::Wss(_) => split_url(&options.broker_addr)?, _ => options.broker_address(), }; @@ -461,11 +461,10 @@ async fn network_connect( request = request_modifier(request).await; } + // Ensure only one of the tls features is enabled to avoid conflicts #[cfg(feature = "use-native-tls")] let connector = tls::native_tls_connector(&tls_config).await?; - - // cfg: "use-native-tls" overwrites "use-rustls" - #[cfg(all(feature = "use-rustls", not(feature = "use-native-tls")))] + #[cfg(feature = "use-rustls")] let connector = tls::rustls_connector(&tls_config).await?; let (socket, response) = async_tungstenite::tokio::client_async_tls_with_connector( diff --git a/rumqttc/src/v5/eventloop.rs b/rumqttc/src/v5/eventloop.rs index 0ec53571..af6df145 100644 --- a/rumqttc/src/v5/eventloop.rs +++ b/rumqttc/src/v5/eventloop.rs @@ -311,7 +311,7 @@ async fn network_connect(options: &MqttOptions) -> Result split_url(&options.broker_addr)?, - #[cfg(all(feature = "use-rustls", feature = "websocket"))] + #[cfg(all(any(feature = "use-rustls", feature = "use-native-tls"), feature = "websocket"))] Transport::Wss(_) => split_url(&options.broker_addr)?, _ => options.broker_address(), }; @@ -377,11 +377,11 @@ async fn network_connect(options: &MqttOptions) -> Result Date: Wed, 19 Jun 2024 15:15:12 +0800 Subject: [PATCH 7/9] Minimize changes. --- rumqttc/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rumqttc/Cargo.toml b/rumqttc/Cargo.toml index 5bb738e1..7de14a78 100644 --- a/rumqttc/Cargo.toml +++ b/rumqttc/Cargo.toml @@ -19,7 +19,7 @@ rustdoc-args = ["--cfg", "docsrs"] default = ["use-rustls"] use-rustls = ["dep:tokio-rustls", "dep:rustls-webpki", "dep:rustls-pemfile", "dep:rustls-native-certs"] use-native-tls = ["dep:tokio-native-tls", "dep:native-tls", "async-tungstenite/tokio-native-tls"] -websocket = ["async-tungstenite", "dep:ws_stream_tungstenite", "dep:http"] +websocket = ["dep:async-tungstenite", "dep:ws_stream_tungstenite", "dep:http"] proxy = ["dep:async-http-proxy"] [dependencies] From a6d64ae210eeaaa55944778f4668ef469a28f54b Mon Sep 17 00:00:00 2001 From: CQ Xiao Date: Wed, 19 Jun 2024 15:53:42 +0800 Subject: [PATCH 8/9] Fix testing issue. --- rumqttc/src/eventloop.rs | 5 +++-- rumqttc/src/v5/eventloop.rs | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/rumqttc/src/eventloop.rs b/rumqttc/src/eventloop.rs index c0b72120..2371de26 100644 --- a/rumqttc/src/eventloop.rs +++ b/rumqttc/src/eventloop.rs @@ -461,10 +461,11 @@ async fn network_connect( request = request_modifier(request).await; } - // Ensure only one of the tls features is enabled to avoid conflicts + // Accept only one of tls features to avoid conflicts. + // When native-tls is enabled, rustls is as disabled. #[cfg(feature = "use-native-tls")] let connector = tls::native_tls_connector(&tls_config).await?; - #[cfg(feature = "use-rustls")] + #[cfg(all(feature = "use-rustls", not(feature = "use-native-tls")))] let connector = tls::rustls_connector(&tls_config).await?; let (socket, response) = async_tungstenite::tokio::client_async_tls_with_connector( diff --git a/rumqttc/src/v5/eventloop.rs b/rumqttc/src/v5/eventloop.rs index af6df145..d7cf15e4 100644 --- a/rumqttc/src/v5/eventloop.rs +++ b/rumqttc/src/v5/eventloop.rs @@ -377,11 +377,11 @@ async fn network_connect(options: &MqttOptions) -> Result Date: Tue, 25 Jun 2024 14:45:40 +0800 Subject: [PATCH 9/9] Enable tokio-native-tls only if async-tungstenite is enabled. --- rumqttc/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rumqttc/Cargo.toml b/rumqttc/Cargo.toml index 7de14a78..d5ba18d9 100644 --- a/rumqttc/Cargo.toml +++ b/rumqttc/Cargo.toml @@ -18,7 +18,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["use-rustls"] use-rustls = ["dep:tokio-rustls", "dep:rustls-webpki", "dep:rustls-pemfile", "dep:rustls-native-certs"] -use-native-tls = ["dep:tokio-native-tls", "dep:native-tls", "async-tungstenite/tokio-native-tls"] +use-native-tls = ["dep:tokio-native-tls", "dep:native-tls", "async-tungstenite?/tokio-native-tls"] websocket = ["dep:async-tungstenite", "dep:ws_stream_tungstenite", "dep:http"] proxy = ["dep:async-http-proxy"]