web_dev_server/
internal_scope.rs1use actix_web::{HttpRequest, HttpResponse, web};
2use actix_ws::Message;
3use futures_util::StreamExt;
4
5use crate::startup::AppState;
6
7pub fn build_internal_scope() -> actix_web::Scope {
8 web::scope("/_live")
9 .route("/health", web::get().to(|| async { "OK" }))
10 .route("/script.js", web::get().to(script))
11 .route("/ws", web::get().to(ws_handler))
12}
13
14async fn script() -> HttpResponse {
15 HttpResponse::Ok()
16 .append_header(("Cache-Control", "no-store, max-age=0"))
17 .content_type("application/javascript")
18 .body(include_str!("./js/script.js"))
19}
20
21async fn ws_handler(
22 req: HttpRequest,
23 stream: web::Payload,
24 state: web::Data<AppState>,
25) -> actix_web::Result<HttpResponse> {
26 let (response, mut session, mut msg_stream) = actix_ws::handle(&req, stream)?;
27 let mut rx = state.broadcaster.subscribe();
28
29 let mut session_for_messages = session.clone();
30
31 actix_web::rt::spawn(async move {
32 while let Some(Ok(message)) = msg_stream.next().await {
33 match message {
34 Message::Ping(bytes) => {
35 if session_for_messages.pong(&bytes).await.is_err() {
36 break;
37 }
38 }
39 Message::Close(reason) => {
40 let _ = session_for_messages.close(reason).await;
41 break;
42 }
43 Message::Text(_)
44 | Message::Binary(_)
45 | Message::Continuation(_)
46 | Message::Pong(_) => {}
47 Message::Nop => {}
48 }
49 }
50 });
51
52 actix_web::rt::spawn(async move {
53 while let Ok(event) = rx.recv().await {
54 match serde_json::to_string(&event) {
55 Ok(payload) => {
56 if session.text(payload).await.is_err() {
57 break;
58 }
59 }
60 Err(error) => {
61 eprintln!("[web-dev-server] failed to serialize live message: {error}")
62 }
63 }
64 }
65 });
66
67 Ok(response)
68}