diff --git a/docs/guide/examples/hydration_props.rs b/docs/guide/examples/hydration_props.rs index 6e3b9e694..4e6bb1752 100644 --- a/docs/guide/examples/hydration_props.rs +++ b/docs/guide/examples/hydration_props.rs @@ -31,11 +31,11 @@ fn main() { // Render the application. This will serialize the root props (the intial count) into the HTML .route( "/", - get(move |Path(intial_count): Path, State(ssr_state): State| async move { axum::body::Full::from( + get(move | State(ssr_state): State| async move { axum::body::Full::from( ssr_state.render( &ServeConfigBuilder::new( app, - intial_count, + 0, ) .build(), ) diff --git a/docs/guide/examples/server_context.rs b/docs/guide/examples/server_context.rs index d803ea60d..e3524a3a9 100644 --- a/docs/guide/examples/server_context.rs +++ b/docs/guide/examples/server_context.rs @@ -35,11 +35,11 @@ fn main() { // Render the application. This will serialize the root props (the intial count) into the HTML .route( "/", - get(move |Path(intial_count): Path, State(ssr_state): State| async move { axum::body::Full::from( + get(move |State(ssr_state): State| async move { axum::body::Full::from( ssr_state.render( &ServeConfigBuilder::new( app, - intial_count, + 0, ) .build(), ) @@ -77,9 +77,10 @@ fn app(cx: Scope) -> Element { button { onclick: move |_| { to_owned![count]; + let sc = cx.sc(); async move { // Call the server function just like a local async function - if let Ok(new_count) = double_server(*count.current()).await { + if let Ok(new_count) = double_server(sc, *count.current()).await { count.set(new_count); } } @@ -89,11 +90,23 @@ fn app(cx: Scope) -> Element { }) } -#[server(DoubleServer)] -async fn double_server(number: usize) -> Result { +// We use the "getcbor" encoding to make caching easier +#[server(DoubleServer, "", "getcbor")] +async fn double_server(cx: DioxusServerContext, number: usize) -> Result { // Perform some expensive computation or access a database on the server tokio::time::sleep(std::time::Duration::from_secs(1)).await; let result = number * 2; + + println!( + "User Agent {:?}", + cx.request_parts().headers.get("User-Agent") + ); + + // Set the cache control header to 1 hour on the post request + cx.responce_headers_mut() + .insert("Cache-Control", "max-age=3600".parse().unwrap()); + println!("server calculated {result}"); + Ok(result) } diff --git a/docs/guide/examples/server_context_state.rs b/docs/guide/examples/server_context_state.rs new file mode 100644 index 000000000..905d1ffc3 --- /dev/null +++ b/docs/guide/examples/server_context_state.rs @@ -0,0 +1,137 @@ +#![allow(non_snake_case, unused)] +use dioxus::prelude::*; +use dioxus_fullstack::prelude::*; + +#[cfg(feature = "ssr")] +#[derive(Default, Clone)] +struct ServerFunctionState { + call_count: std::sync::Arc, +} + +fn main() { + #[cfg(feature = "web")] + dioxus_web::launch_with_props( + app, + // Get the root props from the document + get_root_props_from_document().unwrap_or_default(), + dioxus_web::Config::new().hydrate(true), + ); + #[cfg(feature = "ssr")] + { + use axum::body::Body; + use axum::extract::Path; + use axum::extract::State; + use axum::http::Request; + use axum::routing::get; + use std::sync::Arc; + + // Register the server function before starting the server + DoubleServer::register().unwrap(); + + tokio::runtime::Runtime::new() + .unwrap() + .block_on(async move { + let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080)); + axum::Server::bind(&addr) + .serve( + axum::Router::new() + // Serve the dist folder with the static javascript and WASM files created by the dixous CLI + .serve_static_assets("./dist") + // Register server functions + .register_server_fns_with_handler("", |func| { + move |State(server_fn_state): State, req: Request| async move { + let (parts, body) = req.into_parts(); + let parts: Arc = Arc::new(parts.into()); + let mut server_context = DioxusServerContext::new(parts.clone()); + server_context.insert(server_fn_state); + server_fn_handler(server_context, func.clone(), parts, body).await + } + }) + .with_state(ServerFunctionState::default()) + // Connect to the hot reload server in debug mode + .connect_hot_reload() + // Render the application. This will serialize the root props (the intial count) into the HTML + .route( + "/", + get(move |State(ssr_state): State| async move { axum::body::Full::from( + ssr_state.render( + &ServeConfigBuilder::new( + app, + 0, + ) + .build(), + ) + )}), + ) + // Render the application with a different intial count + .route( + "/:initial_count", + get(move |Path(intial_count): Path, State(ssr_state): State| async move { axum::body::Full::from( + ssr_state.render( + &ServeConfigBuilder::new( + app, + intial_count, + ) + .build(), + ) + )}), + ) + .with_state(SSRState::default()) + .into_make_service(), + ) + .await + .unwrap(); + }); + } +} + +fn app(cx: Scope) -> Element { + let mut count = use_state(cx, || *cx.props); + + cx.render(rsx! { + h1 { "High-Five counter: {count}" } + button { onclick: move |_| count += 1, "Up high!" } + button { onclick: move |_| count -= 1, "Down low!" } + button { + onclick: move |_| { + to_owned![count]; + let sc = cx.sc(); + async move { + // Call the server function just like a local async function + if let Ok(new_count) = double_server(sc, *count.current()).await { + count.set(new_count); + } + } + }, + "Double" + } + }) +} + +// We use the "getcbor" encoding to make caching easier +#[server(DoubleServer, "", "getcbor")] +async fn double_server(cx: DioxusServerContext, number: usize) -> Result { + // Perform some expensive computation or access a database on the server + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + let result = number * 2; + + println!( + "User Agent {:?}", + cx.request_parts().headers.get("User-Agent") + ); + + // Set the cache control header to 1 hour on the post request + cx.responce_headers_mut() + .insert("Cache-Control", "max-age=3600".parse().unwrap()); + + // Get the server function state + let server_fn_state = cx.get::().unwrap(); + let call_count = server_fn_state + .call_count + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + println!("server functions have been called {call_count} times"); + + println!("server calculated {result}"); + + Ok(result) +} diff --git a/packages/fullstack/src/adapters/axum_adapter.rs b/packages/fullstack/src/adapters/axum_adapter.rs index 1092137d5..e3c56b41d 100644 --- a/packages/fullstack/src/adapters/axum_adapter.rs +++ b/packages/fullstack/src/adapters/axum_adapter.rs @@ -91,9 +91,11 @@ pub trait DioxusRouterExt { /// .serve( /// axum::Router::new() /// .register_server_fns_with_handler("", |func| { - /// move |headers: HeaderMap, body: Request| async move { - /// // Add the headers to the context - /// server_fn_handler((headers.clone(),), func.clone(), headers, body).await + /// move |req: Request| async move { + /// let (parts, body) = req.into_parts(); + /// let parts: Arc = Arc::new(parts.into()); + /// let server_context = DioxusServerContext::new(parts.clone()); + /// server_fn_handler(server_context, func.clone(), parts, body).await /// } /// }) /// .into_make_service(), @@ -366,7 +368,7 @@ async fn render_handler( Full::from(rendered) } -/// A default handler for server functions. It will deserialize the request body, call the server function, and serialize the response. +/// A default handler for server functions. It will deserialize the request, call the server function, and serialize the response. pub async fn server_fn_handler( server_context: DioxusServerContext, function: ServerFunction,