1use crate::{FullstackState, ServeConfig, server::DioxusRouterExt};
4use anyhow::Context;
5use axum::{
6 Router,
7 body::Body,
8 extract::{Request, State},
9 routing::IntoMakeService,
10};
11use dioxus_cli_config::base_path;
12use dioxus_core::{ComponentFunction, Element};
13
14use dioxus_devtools::{DevserverMsg, HotReloadMsg};
15use futures_util::{StreamExt, stream::FusedStream};
16use hyper::body::Incoming;
17use hyper_util::server::conn::auto::Builder as HyperBuilder;
18use hyper_util::{
19 rt::{TokioExecutor, TokioIo},
20 service::TowerToHyperService,
21};
22use std::{any::Any, net::SocketAddr, prelude::rust_2024::Future};
23use std::{pin::Pin, sync::Arc};
24use subsecond::HotFn;
25use tokio_util::either::Either;
26use tower::{Service, ServiceExt as _};
27
28#[cfg(not(target_arch = "wasm32"))]
29use {
30 dioxus_core::{RenderError, VNode},
31 tokio::net::TcpListener,
32};
33
34type ContextList = Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>;
35type BaseComp = fn() -> Element;
36
37pub fn launch(root: BaseComp) -> ! {
39 launch_cfg(root, vec![], vec![])
40}
41
42#[allow(unused)]
44pub fn launch_cfg(root: BaseComp, contexts: ContextList, platform_config: Vec<Box<dyn Any>>) -> ! {
45 #[cfg(not(target_arch = "wasm32"))]
46 tokio::runtime::Runtime::new()
47 .unwrap()
48 .block_on(async move { serve_server(root, contexts, platform_config).await });
49
50 unreachable!("Launching a fullstack app should never return")
51}
52
53#[cfg(not(target_arch = "wasm32"))]
54async fn serve_server(
55 original_root: fn() -> Result<VNode, RenderError>,
56 contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
57 platform_config: Vec<Box<dyn Any>>,
58) {
59 let mut cfg = platform_config
60 .into_iter()
61 .find_map(|cfg| cfg.downcast::<ServeConfig>().ok().map(|b| *b))
62 .unwrap_or_else(ServeConfig::new);
63
64 for ctx in contexts {
66 let arced = Arc::new(ctx) as Arc<dyn Fn() -> Box<dyn Any> + Send + Sync>;
67 cfg.context_providers.push(arced);
68 }
69
70 let cb = move || {
71 let cfg = cfg.clone();
72 Box::pin(async move {
73 Ok(apply_base_path(
74 Router::new().serve_dioxus_application(cfg.clone(), original_root),
75 original_root,
76 cfg.clone(),
77 base_path().map(|s| s.to_string()),
78 ))
79 }) as _
80 };
81
82 serve_router(cb, dioxus_cli_config::fullstack_address_or_localhost()).await;
83}
84
85pub fn router(app: fn() -> Element) -> Router {
97 let cfg = ServeConfig::new();
98 apply_base_path(
99 Router::new().serve_dioxus_application(cfg.clone(), app),
100 app,
101 cfg,
102 base_path().map(|s| s.to_string()),
103 )
104}
105
106pub fn serve<F>(mut serve_it: impl FnMut() -> F) -> !
116where
117 F: Future<Output = Result<Router, anyhow::Error>> + 'static,
118{
119 let cb = move || Box::pin(serve_it()) as _;
120
121 block_on(
122 async move { serve_router(cb, dioxus_cli_config::fullstack_address_or_localhost()).await },
123 );
124
125 unreachable!("Serving a fullstack app should never return")
126}
127
128pub async fn serve_router(
135 mut serve_callback: impl FnMut() -> Pin<Box<dyn Future<Output = Result<Router, anyhow::Error>>>>,
136 addr: SocketAddr,
137) {
138 dioxus_logger::initialize_default();
139
140 let listener = TcpListener::bind(addr)
141 .await
142 .with_context(|| format!("Failed to bind to address {addr}"))
143 .unwrap();
144
145 if !cfg!(debug_assertions) {
147 axum::serve(listener, serve_callback().await.unwrap())
148 .await
149 .unwrap();
150 return;
151 }
152
153 let (devtools_tx, mut devtools_rx) = futures_channel::mpsc::unbounded();
155 dioxus_devtools::connect(move |msg| _ = devtools_tx.unbounded_send(msg));
156
157 let mut hot_serve_callback = HotFn::current(serve_callback);
158 let mut make_service = hot_serve_callback
159 .call(())
160 .await
161 .map(|router| router.into_make_service())
162 .unwrap();
163
164 let (shutdown_tx, _) = tokio::sync::broadcast::channel(1);
165 let our_build_id = Some(dioxus_cli_config::build_id());
166
167 loop {
169 let res = tokio::select! {
170 res = listener.accept() => Either::Left(res),
171 Some(msg) = devtools_rx.next(), if !devtools_rx.is_terminated() => Either::Right(msg),
172 else => continue
173 };
174
175 match res {
176 Either::Left(Ok((tcp_stream, _remote_addr))) => {
177 let mut make_service = make_service.clone();
178 let mut shutdown_rx = shutdown_tx.subscribe();
179
180 tokio::task::spawn(async move {
181 let tcp_stream = TokioIo::new(tcp_stream);
182
183 std::future::poll_fn(|cx| {
184 <IntoMakeService<Router> as tower::Service<Request>>::poll_ready(
185 &mut make_service,
186 cx,
187 )
188 })
189 .await
190 .expect("Infallible");
191
192 let builder = HyperBuilder::new(TokioExecutor::new());
194 let connection = builder.serve_connection_with_upgrades(
195 tcp_stream,
196 TowerToHyperService::new(
197 make_service
198 .call(())
199 .await
200 .unwrap()
201 .map_request(|req: Request<Incoming>| req.map(Body::new)),
202 ),
203 );
204
205 tokio::select! {
206 res = connection => {
207 if let Err(_err) = res {
208 }
214 }
215 _res = shutdown_rx.recv() => {}
216 }
217 });
218 }
219
220 Either::Right(DevserverMsg::HotReload(HotReloadMsg {
225 jump_table: Some(table),
226 for_build_id,
227 ..
228 })) if for_build_id == our_build_id => {
229 unsafe { dioxus_devtools::subsecond::apply_patch(table).unwrap() };
231
232 make_service = hot_serve_callback
235 .call(())
236 .await
237 .expect("Failed to create new router after hot-patch!")
238 .into_make_service();
239
240 crate::document::reset_renderer();
242
243 _ = shutdown_tx.send(());
244 }
245
246 _ => {}
249 }
250 }
251}
252
253fn block_on<T>(app_future: impl Future<Output = T>) {
254 if let Ok(handle) = tokio::runtime::Handle::try_current() {
255 handle.block_on(app_future);
256 } else {
257 tokio::runtime::Builder::new_multi_thread()
258 .enable_all()
259 .build()
260 .unwrap()
261 .block_on(app_future);
262 }
263}
264
265fn apply_base_path<M: 'static>(
266 mut router: Router,
267 root: impl ComponentFunction<(), M> + Send + Sync,
268 cfg: ServeConfig,
269 base_path: Option<String>,
270) -> Router {
271 if let Some(base_path) = base_path {
272 let base_path = base_path.trim_matches('/');
273
274 router = Router::new().nest(&format!("/{base_path}/"), router).route(
277 &format!("/{base_path}"),
278 axum::routing::method_routing::get(
279 |state: State<FullstackState>, mut request: Request<Body>| async move {
280 *request.uri_mut() = "/".parse().unwrap();
282 FullstackState::render_handler(state, request).await
283 },
284 )
285 .with_state(FullstackState::new(cfg, root)),
286 )
287 }
288
289 router
290}