1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! Client side implementation for [`tokio-tungstenite`] `0.30.x`.
//!
//! This allows non-browser clients to talk to the same websocket API which is
//! served by [`ws::Server`], such as through the [`axum08`] integration.
//!
//! [`axum08`]: <https://docs.rs/musli-web/latest/musli_web/axum08/>
//! [`tokio-tungstenite`]: <https://docs.rs/tokio-tungstenite/0.30>
//! [`ws::Server`]: <https://docs.rs/musli-web/latest/musli_web/ws/struct.Server.html>
//!
//! # Examples
//!
//! ```no_run
//! use musli_web::tungstenite030::prelude::*;
//!
//! mod api {
//! use musli::{Decode, Encode};
//! use musli_web::api;
//!
//! #[derive(Encode, Decode)]
//! pub struct HelloRequest<'de> {
//! pub message: &'de str,
//! }
//!
//! #[derive(Encode, Decode)]
//! pub struct HelloResponse<'de> {
//! pub message: &'de str,
//! }
//!
//! api::define! {
//! pub type Hello;
//!
//! impl Endpoint for Hello {
//! impl<'de> Request for HelloRequest<'de>;
//! type Response<'de> = HelloResponse<'de>;
//! }
//! }
//! }
//!
//! # async fn example() -> Result<(), Box<dyn core::error::Error>> {
//! let mut service = ws::connect("ws://localhost:3000/ws")
//! .on_error(|error| {
//! tracing::error!("WebSocket error: {error}");
//! })
//! .build();
//!
//! let handle = service.handle().clone();
//!
//! tokio::spawn(async move {
//! if let Err(error) = service.run().await {
//! tracing::error!("WebSocket service error: {error}");
//! }
//! });
//!
//! handle.wait_until_open().await?;
//!
//! let packet = handle
//! .request()
//! .body(api::HelloRequest { message: "Hello!" })
//! .send()
//! .await?;
//!
//! let response = packet.decode()?;
//! println!("Response: {}", response.message);
//! # Ok(())
//! # }
//! ```
//!
//! # Sending headers with the handshake
//!
//! A server which authenticates the websocket upgrade, through a session
//! cookie or a token, reads it from the headers of the HTTP request which opens
//! the websocket. [`ServiceBuilder::header`] adds such a header, and it is sent
//! again every time the service reconnects:
//!
//! ```no_run
//! use musli_web::tungstenite030::prelude::*;
//!
//! # async fn example(token: &str) -> Result<(), Box<dyn core::error::Error>> {
//! let mut service = ws::connect("ws://localhost:3000/ws")
//! .header("Cookie", "session=4b0c2d")
//! .header("Authorization", format!("Bearer {token}"))
//! .build();
//!
//! tokio::spawn(async move {
//! if let Err(error) = service.run().await {
//! tracing::error!("WebSocket service error: {error}");
//! }
//! });
//! # Ok(())
//! # }
//! ```
//!
//! A header which cannot be sent, such as one with a line break in its value,
//! fails [`Service::run`] and [`Service::try_connect`] right away rather than
//! being retried, see [`Error::is_invalid_header`].
//!
//! [`ServiceBuilder::header`]: crate::client::ServiceBuilder::header
//! [`Service::run`]: crate::client::Service::run
//! [`Error::is_invalid_header`]: crate::client::Error::is_invalid_header
//!
//! # Connecting over any stream
//!
//! [`connect_with()`] establishes the websocket over a stream the caller
//! opens, such as a [`UnixStream`], instead of connecting to the url. Combined
//! with [`Service::try_connect`] a caller can fail fast if there is nothing to
//! connect to, rather than retrying in the background:
//!
//! ```no_run
//! use musli_web::tungstenite030::prelude::*;
//!
//! # async fn example() -> Result<(), Box<dyn core::error::Error>> {
//! let mut service = ws::connect_with("ws://localhost/ws", || {
//! tokio::net::UnixStream::connect("/run/user/1000/app.sock")
//! })
//! .reconnect(false)
//! .build();
//!
//! service.try_connect().await?;
//!
//! tokio::spawn(async move {
//! if let Err(error) = service.run().await {
//! tracing::error!("WebSocket service error: {error}");
//! }
//! });
//! # Ok(())
//! # }
//! ```
//!
//! [`UnixStream`]: <https://docs.rs/tokio/1/tokio/net/struct.UnixStream.html>
//! [`Service::try_connect`]: crate::client::Service::try_connect
tungstenite_impl!;