1use crate::call::Call;
24use crate::error::{Error, Result};
25use crate::extract::FromCallParts;
26use crate::response::Response;
27use async_trait::async_trait;
28use futures_util::{SinkExt, StreamExt};
29use http::header::{
30 CONNECTION, SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL,
31 SEC_WEBSOCKET_VERSION, UPGRADE,
32};
33use http::{HeaderMap, HeaderValue, StatusCode};
34use hyper::upgrade::OnUpgrade;
35use hyper_util::rt::TokioIo;
36use std::future::Future;
37use std::sync::{Arc, Mutex};
38use tokio_tungstenite::tungstenite::protocol::{Role, WebSocketConfig};
39use tokio_tungstenite::WebSocketStream;
40
41#[derive(Clone)]
45pub struct OnUpgradeHandle(Arc<Mutex<Option<OnUpgrade>>>);
46
47#[derive(Debug, Clone, Copy)]
54pub struct WsLimits {
55 pub max_frame_bytes: usize,
57 pub max_message_bytes: usize,
59}
60
61impl OnUpgradeHandle {
62 pub fn new(on_upgrade: OnUpgrade) -> Self {
64 Self(Arc::new(Mutex::new(Some(on_upgrade))))
65 }
66
67 pub(crate) fn take(&self) -> Option<OnUpgrade> {
69 self.0.lock().ok().and_then(|mut guard| guard.take())
70 }
71}
72
73impl std::fmt::Debug for OnUpgradeHandle {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.write_str("OnUpgradeHandle")
76 }
77}
78
79pub(crate) fn is_upgrade_request(headers: &HeaderMap) -> bool {
82 let connection_upgrade = headers
83 .get(CONNECTION)
84 .and_then(|v| v.to_str().ok())
85 .map(|v| {
86 v.to_ascii_lowercase()
87 .split(',')
88 .any(|p| p.trim() == "upgrade")
89 })
90 .unwrap_or(false);
91 let upgrade_websocket = headers
92 .get(UPGRADE)
93 .and_then(|v| v.to_str().ok())
94 .map(|v| v.eq_ignore_ascii_case("websocket"))
95 .unwrap_or(false);
96 connection_upgrade && upgrade_websocket
97}
98
99use tokio_tungstenite::tungstenite::Message as TMessage;
100
101#[derive(Debug, Clone, PartialEq, Eq)]
104pub enum Message {
105 Text(String),
107 Binary(Vec<u8>),
109 Ping(Vec<u8>),
111 Pong(Vec<u8>),
113 Close,
115}
116
117impl From<Message> for TMessage {
118 fn from(m: Message) -> Self {
119 match m {
120 Message::Text(s) => TMessage::Text(s.into()),
121 Message::Binary(b) => TMessage::Binary(b.into()),
122 Message::Ping(b) => TMessage::Ping(b.into()),
123 Message::Pong(b) => TMessage::Pong(b.into()),
124 Message::Close => TMessage::Close(None),
125 }
126 }
127}
128
129impl From<TMessage> for Message {
130 fn from(m: TMessage) -> Self {
131 match m {
132 TMessage::Text(s) => Message::Text(s.to_string()),
133 TMessage::Binary(b) => Message::Binary(b.to_vec()),
134 TMessage::Ping(b) => Message::Ping(b.to_vec()),
135 TMessage::Pong(b) => Message::Pong(b.to_vec()),
136 TMessage::Close(_) => Message::Close,
137 _ => Message::Close,
139 }
140 }
141}
142
143#[derive(Debug, Clone, Copy)]
147pub struct WsIdleTimeout(pub u64);
148
149pub struct WebSocket {
151 inner: WebSocketStream<TokioIo<hyper::upgrade::Upgraded>>,
152 activity: std::sync::Arc<WsActivity>,
157}
158
159pub(crate) struct WsActivity {
161 last_ms: std::sync::atomic::AtomicU64,
162 origin: tokio::time::Instant,
163}
164
165impl WsActivity {
166 pub(crate) fn new() -> Self {
167 Self {
168 last_ms: std::sync::atomic::AtomicU64::new(0),
169 origin: tokio::time::Instant::now(),
170 }
171 }
172
173 fn touch(&self) {
174 self.last_ms.store(
175 self.origin.elapsed().as_millis() as u64,
176 std::sync::atomic::Ordering::Relaxed,
177 );
178 }
179
180 pub(crate) fn idle_for(&self, idle_ms: u64) -> Option<std::time::Duration> {
183 let last = self.last_ms.load(std::sync::atomic::Ordering::Relaxed);
184 let quiet = (self.origin.elapsed().as_millis() as u64).saturating_sub(last);
185 match idle_ms.checked_sub(quiet) {
186 Some(0) | None => None,
187 Some(remaining) => Some(std::time::Duration::from_millis(remaining)),
188 }
189 }
190}
191
192impl WebSocket {
193 pub async fn recv(&mut self) -> Option<Result<Message>> {
195 let got = self.inner.next().await;
196 self.activity.touch();
197 match got {
198 Some(Ok(msg)) => Some(Ok(msg.into())),
199 Some(Err(e)) => Some(Err(Error::internal(format!("websocket recv: {e}")))),
200 None => None,
201 }
202 }
203
204 pub async fn send(&mut self, msg: Message) -> Result<()> {
206 let out = self
207 .inner
208 .send(msg.into())
209 .await
210 .map_err(|e| Error::internal(format!("websocket send: {e}")));
211 self.activity.touch();
212 out
213 }
214
215 pub async fn send_text(&mut self, text: impl Into<String>) -> Result<()> {
217 self.send(Message::Text(text.into())).await
218 }
219
220 pub async fn send_binary(&mut self, bytes: impl Into<Vec<u8>>) -> Result<()> {
222 self.send(Message::Binary(bytes.into())).await
223 }
224
225 pub async fn close(&mut self) -> Result<()> {
227 self.inner
228 .close(None)
229 .await
230 .map_err(|e| Error::internal(format!("websocket close: {e}")))
231 }
232}
233
234pub struct WebSocketUpgrade {
240 on_upgrade: OnUpgrade,
241 accept_key: HeaderValue,
242 protocol: Option<HeaderValue>,
243 limits: WsLimits,
244 conn_guard: Option<crate::engine::ConnGuard>,
247 idle_timeout_ms: u64,
249}
250
251#[async_trait]
252impl FromCallParts for WebSocketUpgrade {
253 async fn from_call_parts(call: &mut Call) -> Result<Self> {
254 let version_ok = call
255 .header(SEC_WEBSOCKET_VERSION.as_str())
256 .map(|v| v == "13")
257 .unwrap_or(false);
258 if !is_upgrade_request(call.headers()) || !version_ok {
259 return Err(Error::new(
260 StatusCode::UPGRADE_REQUIRED,
261 "expected a WebSocket upgrade request",
262 )
263 .with_response_header(UPGRADE, HeaderValue::from_static("websocket")));
264 }
265
266 let key = call
267 .header(SEC_WEBSOCKET_KEY.as_str())
268 .ok_or_else(|| Error::bad_request("missing Sec-WebSocket-Key"))?;
269 let accept = tokio_tungstenite::tungstenite::handshake::derive_accept_key(key.as_bytes());
270 let accept_key =
271 HeaderValue::from_str(&accept).map_err(|_| Error::internal("invalid accept key"))?;
272
273 let protocol = call
274 .header(SEC_WEBSOCKET_PROTOCOL.as_str())
275 .and_then(|p| p.split(',').next())
276 .and_then(|p| HeaderValue::from_str(p.trim()).ok());
277
278 let handle = call.get::<OnUpgradeHandle>().ok_or_else(|| {
279 Error::new(
280 StatusCode::UPGRADE_REQUIRED,
281 "WebSocket upgrade unavailable (no pending connection upgrade)",
282 )
283 })?;
284 let on_upgrade = handle
285 .take()
286 .ok_or_else(|| Error::internal("WebSocket upgrade already consumed"))?;
287
288 let limits = call.get::<WsLimits>().unwrap_or(WsLimits {
291 max_frame_bytes: 1 << 20,
292 max_message_bytes: 4 << 20,
293 });
294
295 let conn_guard = call.get::<crate::engine::ConnGuard>();
301 let idle_timeout_ms = call
304 .get::<WsIdleTimeout>()
305 .map(|WsIdleTimeout(ms)| ms)
306 .unwrap_or(300_000);
307
308 Ok(WebSocketUpgrade {
309 on_upgrade,
310 limits,
311 accept_key,
312 protocol,
313 conn_guard,
314 idle_timeout_ms,
315 })
316 }
317}
318
319impl WebSocketUpgrade {
320 pub fn on_upgrade<F, Fut>(self, callback: F) -> Response
324 where
325 F: FnOnce(WebSocket) -> Fut + Send + 'static,
326 Fut: Future<Output = ()> + Send + 'static,
327 {
328 let WebSocketUpgrade {
329 on_upgrade,
330 accept_key,
331 protocol,
332 limits,
333 conn_guard,
334 idle_timeout_ms,
335 } = self;
336
337 tokio::spawn(async move {
338 let _conn_guard = conn_guard;
341 if let Ok(upgraded) = on_upgrade.await {
342 let mut ws_config = WebSocketConfig::default();
346 ws_config.max_frame_size = Some(limits.max_frame_bytes);
347 ws_config.max_message_size = Some(limits.max_message_bytes);
348
349 let stream = WebSocketStream::from_raw_socket(
350 TokioIo::new(upgraded),
351 Role::Server,
352 Some(ws_config),
353 )
354 .await;
355
356 let activity = std::sync::Arc::new(WsActivity::new());
357 let socket = WebSocket {
358 inner: stream,
359 activity: activity.clone(),
360 };
361
362 if idle_timeout_ms == 0 {
373 callback(socket).await;
374 } else {
375 let work = callback(socket);
376 tokio::pin!(work);
377 let reaper =
378 tokio::time::sleep(std::time::Duration::from_millis(idle_timeout_ms));
379 tokio::pin!(reaper);
380 loop {
381 tokio::select! {
382 _ = &mut work => break,
383 _ = reaper.as_mut() => match activity.idle_for(idle_timeout_ms) {
384 None => {
386 tracing::debug!("closing an idle WebSocket");
387 break;
388 }
389 Some(remaining) => reaper
391 .as_mut()
392 .reset(tokio::time::Instant::now() + remaining),
393 },
394 }
395 }
396 }
397 }
398 });
399
400 let mut res = Response::new(StatusCode::SWITCHING_PROTOCOLS);
401 res.headers
402 .insert(UPGRADE, HeaderValue::from_static("websocket"));
403 res.headers
404 .insert(CONNECTION, HeaderValue::from_static("upgrade"));
405 res.headers.insert(SEC_WEBSOCKET_ACCEPT, accept_key);
406 if let Some(p) = protocol {
407 res.headers.insert(SEC_WEBSOCKET_PROTOCOL, p);
408 }
409 res
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416 use crate::{Churust, TestClient};
417 use tokio_tungstenite::tungstenite::Message as TMessage;
418
419 #[tokio::test]
420 async fn plain_get_to_ws_route_is_426() {
421 let app = Churust::server()
422 .routing(|r| {
423 r.get("/ws", |ws: WebSocketUpgrade| async move {
424 ws.on_upgrade(|_sock| async {})
425 });
426 })
427 .build();
428 let res = TestClient::new(app).get("/ws").send().await;
430 assert_eq!(res.status(), http::StatusCode::UPGRADE_REQUIRED);
431 }
432
433 #[test]
434 fn message_round_trips_through_tungstenite() {
435 let cases = [
436 Message::Text("hi".into()),
437 Message::Binary(vec![1, 2, 3]),
438 Message::Ping(vec![9]),
439 Message::Pong(vec![8]),
440 Message::Close,
441 ];
442 for m in cases {
443 let t: TMessage = m.clone().into();
444 let back: Message = t.into();
445 assert_eq!(m, back);
446 }
447 }
448
449 #[test]
450 fn accept_key_matches_rfc6455_example() {
451 let accept = tokio_tungstenite::tungstenite::handshake::derive_accept_key(
453 b"dGhlIHNhbXBsZSBub25jZQ==",
454 );
455 assert_eq!(accept, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
456 }
457}