Bläddra i källkod

feat(fullstack): add `render_handler_with_state` (#1687)

* feat(fullstack): add `render_handler_with_state`

When using server functions, the current pattern to access state such as
database connections is to use `register_server_fns_with_handler` on an
Axum router and 'inject' the state into the context provided to the
server function. However, this only affects function calls which go via
the Axum router; SSR renders bypass this, and therefore don't have
access to any state.

This commit adds an alternative `render_handler` which accepts some
additional state. That state is injected into the context in a
similar manner to `register_server_fns_with_handler`. SSR renders can
then proceed to run in the same way as HTTP calls.

* Change state object to 'inject_state' callback

Also add a compiling doctest example.

* remove the explicit for<'a> lifetime

* remove unused assets_path from render_handler_with_context example

---------

Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
Ben Sully 1 år sedan
förälder
incheckning
04fd2487b3
1 ändrade filer med 62 tillägg och 4 borttagningar
  1. 62 4
      packages/fullstack/src/adapters/axum_adapter.rs

+ 62 - 4
packages/fullstack/src/adapters/axum_adapter.rs

@@ -369,15 +369,65 @@ fn apply_request_parts_to_response<B>(
     }
 }
 
-/// SSR renderer handler for Axum
-pub async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
-    State((cfg, ssr_state)): State<(ServeConfig<P>, SSRState)>,
+/// SSR renderer handler for Axum with added context injection.
+///
+/// # Example
+/// ```rust,no_run
+/// #![allow(non_snake_case)]
+/// use std::sync::{Arc, Mutex};
+///
+/// use axum::routing::get;
+/// use dioxus::prelude::*;
+/// use dioxus_fullstack::{axum_adapter::render_handler_with_context, prelude::*};
+///
+/// fn app(cx: Scope) -> Element {
+///     render! {
+///         "hello!"
+///     }
+/// }
+///
+/// #[tokio::main]
+/// async fn main() {
+///     let cfg = ServeConfigBuilder::new(app, ())
+///         .assets_path("dist")
+///         .build();
+///     let ssr_state = SSRState::new(&cfg);
+///
+///     // This could be any state you want to be accessible from your server
+///     // functions using `[DioxusServerContext::get]`.
+///     let state = Arc::new(Mutex::new("state".to_string()));
+///
+///     let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
+///     axum::Server::bind(&addr)
+///         .serve(
+///             axum::Router::new()
+///                 // Register server functions, etc.
+///                 // Note you probably want to use `register_server_fns_with_handler`
+///                 // to inject the context into server functions running outside
+///                 // of an SSR render context.
+///                 .fallback(get(render_handler_with_context).with_state((
+///                     move |ctx| ctx.insert(state.clone()).unwrap(),
+///                     cfg,
+///                     ssr_state,
+///                 )))
+///                 .into_make_service(),
+///         )
+///         .await
+///         .unwrap();
+/// }
+/// ```
+pub async fn render_handler_with_context<
+    P: Clone + serde::Serialize + Send + Sync + 'static,
+    F: FnMut(&mut DioxusServerContext),
+>(
+    State((mut inject_context, cfg, ssr_state)): State<(F, ServeConfig<P>, SSRState)>,
     request: Request<Body>,
 ) -> impl IntoResponse {
     let (parts, _) = request.into_parts();
     let url = parts.uri.path_and_query().unwrap().to_string();
     let parts: Arc<RwLock<http::request::Parts>> = Arc::new(RwLock::new(parts.into()));
-    let server_context = DioxusServerContext::new(parts.clone());
+    let mut server_context = DioxusServerContext::new(parts.clone());
+    inject_context(&mut server_context);
 
     match ssr_state.render(url, &cfg, &server_context).await {
         Ok(rendered) => {
@@ -395,6 +445,14 @@ pub async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>
     }
 }
 
+/// SSR renderer handler for Axum
+pub async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
+    State((cfg, ssr_state)): State<(ServeConfig<P>, SSRState)>,
+    request: Request<Body>,
+) -> impl IntoResponse {
+    render_handler_with_context(State((|_: &mut _| (), cfg, ssr_state)), request).await
+}
+
 fn report_err<E: std::fmt::Display>(e: E) -> Response<BoxBody> {
     Response::builder()
         .status(StatusCode::INTERNAL_SERVER_ERROR)