create_rust_app/util/
actix_web_utils.rs1#[cfg(debug_assertions)]
2use std::str::FromStr;
3#[cfg(debug_assertions)]
4use std::sync::Mutex;
5
6use super::template_utils::SinglePageApplication;
7use super::workspace_utils::frontend_dir;
8use crate::util::template_utils::{to_template_name, DEFAULT_TEMPLATE, TEMPLATES};
9use actix_files::NamedFile;
10#[cfg(debug_assertions)]
11use actix_http::Uri;
12use actix_web::http::StatusCode;
13use actix_web::{web, HttpRequest, HttpResponse, Scope};
14use tera::Context;
15
16pub fn render_single_page_application(route: &str, view: &str) -> Scope {
19 use actix_web::web::Data;
20
21 let route = route.strip_prefix('/').unwrap_or(route);
22 let view = view.strip_prefix('/').unwrap_or(view);
23
24 actix_web::web::scope(&format!("/{route}{{tail:(/.*)?}}"))
25 .app_data(Data::new(SinglePageApplication {
26 view_name: view.to_string(),
27 }))
28 .route("", web::get().to(render_spa_handler))
29}
30
31#[allow(clippy::future_not_send)]
32async fn render_spa_handler(
33 req: HttpRequest,
34 spa_info: web::Data<SinglePageApplication>,
35) -> HttpResponse {
36 let content = TEMPLATES
37 .render(spa_info.view_name.as_str(), &Context::new())
38 .unwrap();
39 template_response(&req, content)
40}
41
42#[allow(clippy::mutex_integer)] #[cfg(debug_assertions)]
45static REQUEST_REFRESH_COUNT: Mutex<i32> = Mutex::new(0);
46
47#[allow(clippy::future_not_send)]
61pub async fn render_views(req: HttpRequest) -> HttpResponse {
62 let path = req.path();
63
64 #[cfg(debug_assertions)]
65 {
66 if path.eq("/__vite_ping") {
67 println!("The vite dev server seems to be down...");
68 }
69
70 if path.eq("/__vite_ping") {
74 #[cfg(feature = "plugin_dev")]
75 {
76 crate::dev::vitejs_ping_down().await;
77 }
78 #[allow(clippy::mutex_integer)]
79 let mut count = REQUEST_REFRESH_COUNT.lock().unwrap();
80 if *count < 3 {
81 *count += 1;
82 println!("The vite dev server seems to be down... refreshing page ({count}).");
83 drop(count);
84 return HttpResponse::build(StatusCode::TEMPORARY_REDIRECT)
85 .append_header(("Location", "."))
86 .finish();
87 }
88 println!("The vite dev server is down.");
89 return HttpResponse::NotFound().finish();
90 }
91 #[cfg(feature = "plugin_dev")]
93 {
94 crate::dev::vitejs_ping_up().await;
95 }
96 #[allow(clippy::mutex_integer)]
97 let mut count = REQUEST_REFRESH_COUNT.lock().unwrap();
98 *count = 0;
99 }
100
101 let mut template_path = to_template_name(req.path());
102 let mut content_result = TEMPLATES.render(template_path, &Context::new());
104
105 if content_result.is_err() {
109 #[cfg(debug_assertions)]
110 {
111 let asset_path = &format!("{frontend_dir}{path}", frontend_dir = frontend_dir());
113 if std::path::PathBuf::from(asset_path).is_file() {
114 println!("ASSET_FILE {path} => {asset_path}");
115 return NamedFile::open(asset_path).unwrap().into_response(&req);
116 }
117
118 let public_path =
119 &format!("{frontend_dir}/public{path}", frontend_dir = frontend_dir());
120 if std::path::PathBuf::from(public_path).is_file() {
121 println!("PUBLIC_FILE {path} => {public_path}");
122 return NamedFile::open(public_path).unwrap().into_response(&req);
123 }
124 }
125
126 #[cfg(not(debug_assertions))]
127 {
128 let static_path = &format!("{frontend_dir}/dist{path}", frontend_dir = frontend_dir());
130 if std::path::PathBuf::from(static_path).is_file() {
131 return NamedFile::open(static_path).unwrap().into_response(&req);
132 }
133 }
134
135 content_result = TEMPLATES.render(DEFAULT_TEMPLATE, &Context::new());
136 template_path = DEFAULT_TEMPLATE;
137 if content_result.is_err() {
138 return HttpResponse::NotFound().finish();
140 }
141 }
142
143 println!("TEMPLATE_FILE {path} => {template_path}");
144
145 let content = content_result.unwrap();
146
147 template_response(&req, content)
148}
149
150#[allow(unused_variables)]
151fn template_response(req: &HttpRequest, content: String) -> HttpResponse {
152 #[cfg(not(debug_assertions))]
153 let content = content;
154 #[cfg(debug_assertions)]
155 let mut content = content;
156 #[cfg(debug_assertions)]
157 {
158 let uri = Uri::from_str(req.connection_info().host());
159 let hostname = uri
160 .as_ref()
161 .map_or("localhost", |uri| uri.host().unwrap_or("localhost"));
162
163 let inject: &str = &format!(
164 r##"
165 <!-- development mode -->
166 <script type="module">
167 import RefreshRuntime from 'http://{hostname}:21012/@react-refresh'
168 RefreshRuntime.injectIntoGlobalHook(window)
169 window.$RefreshReg$ = () => {{}}
170 window.$RefreshSig$ = () => (type) => type
171 window.__vite_plugin_react_preamble_installed__ = true
172 </script>
173 <script type="module" src="http://{hostname}:21012/src/dev.tsx"></script>
174 "##
175 );
176
177 if content.contains("<body>") {
178 content = content.replace("<body>", &format!("<body>{inject}"));
179 } else {
180 content = format!("{inject}{content}");
181 }
182 }
183
184 HttpResponse::build(StatusCode::OK)
185 .content_type("text/html")
186 .body(content)
187}