Skip to main content

musli_web/
axum08.rs

1//! The server implementation for [axum].
2//!
3//! Use [`server()`] to set up the server and feed it incoming requests.
4//!
5//! [axum]: <https://docs.rs/axum>
6
7use alloc::string::String;
8
9use core::pin::Pin;
10use core::task::Poll;
11use core::task::{Context, ready};
12
13use bytes::Bytes;
14
15use axum_core05::Error;
16use axum08::extract::ws::{CloseFrame, Message, Utf8Bytes, WebSocket};
17use futures_core03::Stream;
18use futures_sink03::Sink;
19
20use crate::ws::{self, Connect, Handler, ServerImpl, SocketImpl};
21
22/// Construct a new axum connection with the specified handler.
23///
24/// The returned [`Connect`] cannot send anything. Call [`Connect::connect`] to
25/// perform the [negotiation protocol], which hands back the [`Server`] that
26/// can. This is what makes it impossible to write a message before the client
27/// has agreed on the [`Format`] it will be encoded with.
28///
29/// [`Format`]: crate::api::Format
30/// [`Server`]: crate::ws::Server
31/// [negotiation protocol]: crate::api#negotiating-the-format
32///
33/// # Examples
34///
35/// ```
36/// # extern crate axum08 as axum;
37/// use std::error::Error;
38/// use std::pin::pin;
39///
40/// use axum::Router;
41/// use axum::extract::State;
42/// use axum::extract::ws::{WebSocket, WebSocketUpgrade};
43/// use axum::response::Response;
44/// use axum::routing::any;
45/// use tokio::sync::broadcast::Sender;
46/// use tokio::time::{self, Duration};
47///
48/// use musli_web::api::MessageId;
49/// use musli_web::axum08;
50/// use musli_web::ws;
51///
52/// mod api {
53///     use musli::{Decode, Encode};
54///     use musli_web::api;
55///
56///     #[derive(Encode, Decode)]
57///     pub struct HelloRequest<'de> {
58///         pub message: &'de str,
59///     }
60///
61///     #[derive(Encode, Decode)]
62///     pub struct HelloResponse<'de> {
63///         pub message: &'de str,
64///     }
65///
66///     #[derive(Encode, Decode)]
67///     pub struct TickEvent<'de> {
68///         pub message: &'de str,
69///         pub tick: u32,
70///     }
71///
72///     api::define! {
73///         pub type Hello;
74///
75///         impl Endpoint for Hello {
76///             impl<'de> Request for HelloRequest<'de>;
77///             type Response<'de> = HelloResponse<'de>;
78///         }
79///
80///         pub type Tick;
81///
82///         impl Broadcast for Tick {
83///             impl<'de> Event for TickEvent<'de>;
84///         }
85///     }
86/// }
87///
88/// #[derive(Debug, Clone)]
89/// enum Broadcast {
90///     Tick { tick: u32 },
91/// }
92///
93/// #[derive(Clone)]
94/// struct MyHandler;
95///
96/// impl ws::Handler for MyHandler {
97///     type Id = api::Request;
98///     type Response = Option<()>;
99///
100///     async fn handle(
101///         &self,
102///         id: Self::Id,
103///         incoming: &mut ws::Incoming<'_>,
104///         outgoing: &mut ws::Outgoing<'_>,
105///     ) -> Self::Response {
106///         tracing::info!("Handling: {id:?}");
107///
108///         match id {
109///             api::Request::Hello => {
110///                 let request = incoming.read::<api::HelloRequest<'_>>()?;
111///
112///                 outgoing.write(api::HelloResponse {
113///                     message: request.message,
114///                 });
115///
116///                 Some(())
117///             }
118///             api::Request::Unknown(id) => {
119///                 None
120///             }
121///         }
122///     }
123/// }
124///
125/// async fn handler(ws: WebSocketUpgrade, State(sender): State<Sender<Broadcast>>) -> Response {
126///     ws.on_upgrade(move |socket: WebSocket| async move {
127///         let mut subscribe = sender.subscribe();
128///
129///         // NB: Nothing can be sent until the client has negotiated a format,
130///         // which is what this step waits for.
131///         let mut server = match axum08::server(socket, MyHandler).connect().await {
132///             Ok(server) => server,
133///             Err(error) => {
134///                 tracing::error!("Failed to negotiate: {error}");
135///                 return;
136///             }
137///         };
138///
139///         loop {
140///             tokio::select! {
141///                 m = subscribe.recv() => {
142///                     let Ok(message) = m else {
143///                         continue;
144///                     };
145///
146///                     let result = match message {
147///                         Broadcast::Tick { tick } => {
148///                             server.broadcast(api::TickEvent { message: "tick", tick })
149///                         },
150///                     };
151///
152///                     if let Err(error) = result {
153///                         tracing::error!("Broadcast failed: {error}");
154///
155///                         let mut error = error.source();
156///
157///                         while let Some(e) = error.take() {
158///                             tracing::error!("Caused by: {e}");
159///                             error = e.source();
160///                         }
161///                     }
162///                 }
163///                 result = server.run() => {
164///                     if let Err(error) = result {
165///                         tracing::error!("Websocket error: {error}");
166///
167///                         let mut error = error.source();
168///
169///                         while let Some(e) = error.take() {
170///                             tracing::error!("Caused by: {e}");
171///                             error = e.source();
172///                         }
173///                     }
174///
175///                     break;
176///                 }
177///             }
178///         }
179///     })
180/// }
181/// ```
182#[inline]
183pub fn server<H>(socket: WebSocket, handler: H) -> Connect<AxumServer, H>
184where
185    H: Handler,
186{
187    Connect::new(socket, handler)
188}
189
190impl crate::ws::server_sealed::Sealed for AxumServer {}
191
192/// Marker type used in combination with [`Server`] to indicate that the
193/// implementation uses axum.
194///
195/// See [`server()`] for how this is constructed and used.
196///
197/// [`Server`]: crate::ws::Server
198#[non_exhaustive]
199pub enum AxumServer {}
200
201impl ServerImpl for AxumServer {
202    type Error = Error;
203    type Message = Message;
204    type Socket = WebSocket;
205
206    #[inline]
207    fn ping(data: Bytes) -> Self::Message {
208        Message::Ping(data)
209    }
210
211    #[inline]
212    fn pong(data: Bytes) -> Self::Message {
213        Message::Pong(data)
214    }
215
216    #[inline]
217    fn binary(data: &[u8]) -> Self::Message {
218        Message::Binary(Bytes::from(data.to_vec()))
219    }
220
221    #[inline]
222    fn text(data: &[u8]) -> Self::Message {
223        // NB: A text frame is only ever produced in a mode which guarantees
224        // that everything written into it is valid UTF-8, so the lossy path is
225        // never taken.
226        Message::Text(Utf8Bytes::from(&*String::from_utf8_lossy(data)))
227    }
228
229    #[inline]
230    fn close(code: u16, reason: &str) -> Self::Message {
231        Message::Close(Some(CloseFrame {
232            code,
233            reason: reason.into(),
234        }))
235    }
236}
237
238impl crate::ws::socket_sealed::Sealed for WebSocket {}
239
240impl SocketImpl for WebSocket {
241    type Message = Message;
242    type Error = Error;
243
244    #[inline]
245    #[allow(private_interfaces)]
246    fn poll_next(
247        self: Pin<&mut Self>,
248        ctx: &mut Context<'_>,
249    ) -> Poll<Option<Result<ws::Message, Self::Error>>> {
250        let Some(result) = ready!(Stream::poll_next(self, ctx)) else {
251            return Poll::Ready(None);
252        };
253
254        let message = match result {
255            Ok(message) => message,
256            Err(err) => return Poll::Ready(Some(Err(err))),
257        };
258
259        let message = match message {
260            Message::Text(data) => ws::Message::Text(Bytes::from(data)),
261            Message::Binary(data) => ws::Message::Binary(data),
262            Message::Ping(data) => ws::Message::Ping(data),
263            Message::Pong(data) => ws::Message::Pong(data),
264            Message::Close(..) => ws::Message::Close,
265        };
266
267        Poll::Ready(Some(Ok(message)))
268    }
269
270    #[inline]
271    fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
272        Sink::poll_ready(self, cx)
273    }
274
275    #[inline]
276    fn start_send(self: Pin<&mut Self>, message: Self::Message) -> Result<(), Self::Error> {
277        Sink::start_send(self, message)
278    }
279
280    #[inline]
281    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
282        Sink::poll_flush(self, cx)
283    }
284}