1use crate::error::{Error, Result};
33use crate::handler::Handler;
34use crate::request::Request;
35use crate::response::Response;
36use async_trait::async_trait;
37use futures_util::{SinkExt, StreamExt};
38use hyper::upgrade::{OnUpgrade, Upgraded};
39use hyper_util::rt::TokioIo;
40use std::future::Future;
41use std::sync::Arc;
42use tokio_tungstenite::tungstenite::handshake::derive_accept_key;
43use tokio_tungstenite::tungstenite::protocol::{Role, WebSocketConfig};
44use tokio_tungstenite::tungstenite::{self, Message as WsMessage};
45use tokio_tungstenite::WebSocketStream;
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum Message {
50 Text(String),
51 Binary(Vec<u8>),
52 Ping(Vec<u8>),
54 Pong(Vec<u8>),
55 Close,
57}
58
59#[derive(Debug, Clone)]
61pub struct WsOptions {
62 pub max_message_size: usize,
64 pub allowed_origins: Vec<String>,
67 pub ping_interval: Option<std::time::Duration>,
71 pub idle_timeout: Option<std::time::Duration>,
74}
75
76impl Default for WsOptions {
77 fn default() -> Self {
78 Self {
79 max_message_size: 1024 * 1024,
80 allowed_origins: Vec::new(),
81 ping_interval: Some(std::time::Duration::from_secs(30)),
82 idle_timeout: Some(std::time::Duration::from_secs(90)),
83 }
84 }
85}
86
87pub struct WebSocket {
89 inner: WebSocketStream<TokioIo<Upgraded>>,
90 ping: Option<tokio::time::Interval>,
91 idle_timeout: Option<std::time::Duration>,
92 last_seen: std::time::Instant,
93}
94
95impl WebSocket {
96 fn new(inner: WebSocketStream<TokioIo<Upgraded>>, options: &WsOptions) -> Self {
97 let ping = options.ping_interval.map(|every| {
98 let mut i = tokio::time::interval_at(tokio::time::Instant::now() + every, every);
99 i.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
100 i
101 });
102 Self {
103 inner,
104 ping,
105 idle_timeout: options.idle_timeout,
106 last_seen: std::time::Instant::now(),
107 }
108 }
109
110 pub async fn recv(&mut self) -> Option<Result<Message>> {
116 loop {
117 let Self {
118 inner,
119 ping,
120 idle_timeout,
121 last_seen,
122 } = self;
123 let tick = async {
124 match ping {
125 Some(interval) => {
126 interval.tick().await;
127 }
128 None => std::future::pending::<()>().await,
129 }
130 };
131 tokio::select! {
132 incoming = inner.next() => {
133 *last_seen = std::time::Instant::now();
134 return match incoming? {
135 Ok(WsMessage::Text(t)) => Some(Ok(Message::Text(t.as_str().to_owned()))),
136 Ok(WsMessage::Binary(b)) => Some(Ok(Message::Binary(b.to_vec()))),
137 Ok(WsMessage::Ping(b)) => Some(Ok(Message::Ping(b.to_vec()))),
138 Ok(WsMessage::Pong(b)) => Some(Ok(Message::Pong(b.to_vec()))),
139 Ok(WsMessage::Close(_)) => Some(Ok(Message::Close)),
140 Ok(WsMessage::Frame(_)) => continue,
141 Err(tungstenite::Error::ConnectionClosed | tungstenite::Error::AlreadyClosed) => None,
142 Err(e) => Some(Err(Error::bad_request(format!("WebSocket: {}", e)))),
143 };
144 }
145 _ = tick => {
146 if idle_timeout.is_some_and(|limit| last_seen.elapsed() >= limit) {
147 let _ = inner.close(None).await;
148 return None;
149 }
150 if inner.send(WsMessage::Ping(Vec::new().into())).await.is_err() {
151 return None;
152 }
153 }
154 }
155 }
156 }
157
158 pub async fn send_json<T: serde::Serialize>(&mut self, value: &T) -> Result<()> {
160 let text = serde_json::to_string(value)
161 .map_err(|e| Error::internal(format!("WebSocket JSON: {}", e)))?;
162 self.send_text(text).await
163 }
164
165 pub async fn close_with(&mut self, code: u16, reason: &str) -> Result<()> {
168 use tungstenite::protocol::frame::{coding::CloseCode, CloseFrame};
169 self.inner
170 .close(Some(CloseFrame {
171 code: CloseCode::from(code),
172 reason: reason.to_string().into(),
173 }))
174 .await
175 .map_err(|e| Error::internal(format!("WebSocket close: {}", e)))
176 }
177
178 pub async fn send(&mut self, message: Message) -> Result<()> {
179 let msg = match message {
180 Message::Text(t) => WsMessage::text(t),
181 Message::Binary(b) => WsMessage::binary(b),
182 Message::Ping(b) => WsMessage::Ping(b.into()),
183 Message::Pong(b) => WsMessage::Pong(b.into()),
184 Message::Close => WsMessage::Close(None),
185 };
186 self.inner
187 .send(msg)
188 .await
189 .map_err(|e| Error::internal(format!("WebSocket send: {}", e)))
190 }
191
192 pub async fn send_text(&mut self, text: impl Into<String>) -> Result<()> {
193 self.send(Message::Text(text.into())).await
194 }
195
196 pub async fn send_binary(&mut self, data: impl Into<Vec<u8>>) -> Result<()> {
197 self.send(Message::Binary(data.into())).await
198 }
199
200 pub async fn close(&mut self) -> Result<()> {
202 self.inner
203 .close(None)
204 .await
205 .map_err(|e| Error::internal(format!("WebSocket close: {}", e)))
206 }
207}
208
209pub(crate) struct WsRoute<F> {
211 handler: Arc<F>,
212 options: WsOptions,
213}
214
215impl<F> WsRoute<F> {
216 pub(crate) fn new(handler: F, options: WsOptions) -> Self {
217 Self {
218 handler: Arc::new(handler),
219 options,
220 }
221 }
222}
223
224fn header<'a>(req: &'a Request, name: &str) -> Option<&'a str> {
225 req.headers.get(name).and_then(|v| v.to_str().ok())
226}
227
228fn has_token(req: &Request, name: &str, token: &str) -> bool {
230 req.headers
231 .get_all(name)
232 .iter()
233 .filter_map(|v| v.to_str().ok())
234 .any(|v| v.split(',').any(|t| t.trim().eq_ignore_ascii_case(token)))
235}
236
237fn origin_allowed(req: &Request, options: &WsOptions) -> bool {
239 let Some(origin) = header(req, "origin") else {
240 return true;
241 };
242 if options.allowed_origins.iter().any(|o| o == origin) {
243 return true;
244 }
245 let authority = origin
246 .split_once("://")
247 .map(|(_, rest)| rest)
248 .unwrap_or(origin);
249 header(req, "host")
250 .map(|h| h.eq_ignore_ascii_case(authority))
251 .unwrap_or(false)
252}
253
254#[async_trait]
255impl<F, Fut> Handler for WsRoute<F>
256where
257 F: Fn(Request, WebSocket) -> Fut + Send + Sync + 'static,
258 Fut: Future<Output = ()> + Send + 'static,
259{
260 async fn call(&self, mut req: Request) -> Result<Response> {
261 if !has_token(&req, "connection", "upgrade")
262 || !header(&req, "upgrade").is_some_and(|v| v.eq_ignore_ascii_case("websocket"))
263 {
264 return Err(Error::bad_request("Expected a WebSocket upgrade request"));
265 }
266 if header(&req, "sec-websocket-version") != Some("13") {
267 return Ok(
268 Response::from_error(Error::custom(426, "Unsupported WebSocket version"))
269 .with_header("sec-websocket-version", "13"),
270 );
271 }
272 let Some(key) = header(&req, "sec-websocket-key").filter(|k| !k.is_empty()) else {
273 return Err(Error::bad_request("Missing Sec-WebSocket-Key"));
274 };
275 let accept = derive_accept_key(key.as_bytes());
276 if !origin_allowed(&req, &self.options) {
277 return Err(Error::forbidden("Origin not allowed"));
278 }
279 let Some(on_upgrade) = req.extensions.remove::<OnUpgrade>() else {
280 return Err(Error::internal("Connection cannot be upgraded"));
281 };
282
283 let handler = self.handler.clone();
284 let options = self.options.clone();
285 let config = WebSocketConfig::default()
286 .max_message_size(Some(self.options.max_message_size))
287 .max_frame_size(Some(self.options.max_message_size));
288 tokio::spawn(async move {
289 let Ok(upgraded) = on_upgrade.await else {
290 return;
291 };
292 let inner = WebSocketStream::from_raw_socket(
293 TokioIo::new(upgraded),
294 Role::Server,
295 Some(config),
296 )
297 .await;
298 handler(req, WebSocket::new(inner, &options)).await;
299 });
300
301 let mut res = Response::new(http::StatusCode::SWITCHING_PROTOCOLS)
302 .with_header("upgrade", "websocket")
303 .with_header("connection", "Upgrade")
304 .with_header("sec-websocket-accept", accept);
305 res.body = bytes::Bytes::new();
306 Ok(res)
307 }
308}