Ver Fonte

Improve documentation for the fullstack crate (#2952)

* improve documentation for the fullstack server context

* Add a section about axum integration to the crate root docs

* make serve_dioxus_application accept the cfg builder directly

* remove unused server_fn module

* improve fullstack config docs

* improve documentation for the server function macro

* fix axum router extension link

* Fix doc tests

* Fix launch builder

* Remove redundant server_only inside server function

* Fix isrg config path
Evan Almloff há 8 meses atrás
pai
commit
6a0c1c392b

+ 4 - 0
Cargo.lock

@@ -3102,10 +3102,14 @@ dependencies = [
 name = "dioxus_server_macro"
 version = "0.6.0-alpha.2"
 dependencies = [
+ "axum 0.7.5",
+ "dioxus",
  "proc-macro2",
  "quote",
+ "serde",
  "server_fn_macro",
  "syn 2.0.74",
+ "tower-http",
 ]
 
 [[package]]

+ 1 - 1
examples/fullstack-streaming/Cargo.toml

@@ -20,5 +20,5 @@ once_cell = "1.19.0"
 
 [features]
 default = []
-server = ["dioxus/axum", "tokio"]
+server = ["dioxus/axum", "dep:tokio"]
 web = ["dioxus/web"]

+ 4 - 3
packages/dioxus/src/launch.rs

@@ -279,10 +279,11 @@ mod current_platform {
     {
         fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {
             match config {
-                Some(config) => config.set_server_cfg(self),
+                Some(config) => config.set_server_config(self),
                 None => {
-                    *config =
-                        Some(crate::launch::current_platform::Config::new().with_server_cfg(self))
+                    *config = Some(
+                        crate::launch::current_platform::Config::new().with_server_config(self),
+                    )
                 }
             }
         }

+ 74 - 0
packages/fullstack/README.md

@@ -63,6 +63,80 @@ async fn get_meaning(of: String) -> Result<Option<u32>, ServerFnError> {
 }
 ```
 
+## Axum Integration
+
+If you have an existing Axum router or you need more control over the server, you can use the [`DioxusRouterExt`](https://docs.rs/dioxus-fullstack/0.6.0-alpha.2/dioxus_fullstack/prelude/trait.DioxusRouterExt.html) trait to integrate with your existing Axum router.
+
+First, make sure your `axum` dependency is optional and enabled by the server feature flag. Axum cannot be compiled to wasm, so if it is enabled by default, it will cause a compile error:
+
+```toml
+[dependencies]
+dioxus = { version = "*", features = ["fullstack"] }
+axum = { version = "0.7.0", optional = true }
+
+[features]
+server = ["dep:axum", "dioxus/server"]
+web = ["dioxus/web"]
+```
+
+Then we can set up dioxus with the axum server:
+
+```rust, no_run
+#![allow(non_snake_case)]
+use dioxus::prelude::*;
+
+// The entry point for the server
+#[cfg(feature = "server")]
+fn main() {
+    // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address
+    // and we use the generated address the CLI gives us
+    let cli_args = dioxus_cli_config::RuntimeCLIArguments::from_cli();
+    let address = cli_args
+        .as_ref()
+        .map(|args| args.fullstack_address().address())
+        .unwrap_or_else(SocketAddr::from(([127, 0, 0, 1], 8080)));
+
+    // Set up the axum router
+    let router = axum::Router::new()
+        // You can add a dioxus application to the router with the `serve_dioxus_application` method
+        // This will add a fallback route to the router that will serve your component and server functions
+        .serve_dioxus_application(ServeConfigBuilder::default(), App);
+
+    // Finally, we can launch the server
+    let router = router.into_make_service();
+    let listener = tokio::net::TcpListener::bind(address).await.unwrap();
+    axum::serve(listener, router).await.unwrap();
+}
+
+// For any other platform, we just launch the app
+#[cfg(not(feature = "server"))]
+fn main() {
+    launch(App);
+}
+
+#[component]
+fn App() -> Element {
+    let mut meaning = use_signal(|| None);
+
+    rsx! {
+        h1 { "Meaning of life: {meaning:?}" }
+        button {
+            onclick: move |_| async move {
+                if let Ok(data) = get_meaning("life the universe and everything".into()).await {
+                    meaning.set(data);
+                }
+            },
+            "Run a server function"
+        }
+    }
+}
+
+#[server]
+async fn get_meaning(of: String) -> Result<Option<u32>, ServerFnError> {
+    Ok(of.contains("life").then(|| 42))
+}
+```
+
 ## Getting Started
 
 To get started with full stack Dioxus, check out our [getting started guide](https://dioxuslabs.com/learn/0.5/getting_started), or the [full stack examples](https://github.com/DioxusLabs/dioxus/tree/master/examples).

+ 40 - 0
packages/fullstack/docs/request_origin.md

@@ -0,0 +1,40 @@
+This method interacts with information from the current request. The request may come from:
+
+1. The initial SSR render if this method called from a [`Component`](dioxus_lib::prelude::component) or a [`server`](crate::prelude::server) function that is called during the initial render
+
+```rust
+#[component]
+fn PrintHtmlRequestInfo() -> Element {
+    // The server context only exists on the server, so we need to put it behind a server_only! config
+    server_only! {
+        // Since we are calling this from a component, the server context that is returned will be from
+        // the html request for ssr rendering
+        println!("headers are {:?}", server_context().request_parts().headers);
+    }
+    rsx! {}
+}
+```
+
+2. A request to a [`server`](crate::prelude::server) function called directly from the client (either on desktop/mobile or on the web frontend after the initial render)
+
+```rust
+#[server]
+async fn read_headers() -> Result<(), ServerFnError> {
+    // Since we are calling this from a server function, the server context that is may be from the
+    // initial request or a request from the client
+    println!("headers are {:?}", server_context().request_parts().headers);
+    Ok(())
+}
+
+#[component]
+fn CallServerFunction() -> Element {
+    rsx! {
+        button {
+            // If you click the button, the server function will be called and the server context will be
+            // from the client request
+            onclick: move |_| read_headers().await,
+            "Call server function"
+        }
+    }
+}
+```

+ 23 - 12
packages/fullstack/src/axum_adapter.rs

@@ -21,7 +21,7 @@
 //!                         listener,
 //!                         axum::Router::new()
 //!                             // Server side render the application, serve static assets, and register server functions
-//!                             .serve_dioxus_application(ServeConfig::new().unwrap(), app)
+//!                             .serve_dioxus_application(ServeConfigBuilder::default(), app)
 //!                             .into_make_service(),
 //!                     )
 //!                     .await
@@ -160,8 +160,10 @@ pub trait DioxusRouterExt<S> {
     ///     rsx! { "Hello World" }
     /// }
     /// ```
-    fn serve_dioxus_application(self, cfg: impl Into<ServeConfig>, app: fn() -> Element) -> Self
+    fn serve_dioxus_application<Cfg, Error>(self, cfg: Cfg, app: fn() -> Element) -> Self
     where
+        Cfg: TryInto<ServeConfig, Error = Error>,
+        Error: std::error::Error,
         Self: Sized;
 }
 
@@ -243,18 +245,27 @@ where
         self
     }
 
-    fn serve_dioxus_application(self, cfg: impl Into<ServeConfig>, app: fn() -> Element) -> Self {
-        let cfg = cfg.into();
-
-        let ssr_state = SSRState::new(&cfg);
-
+    fn serve_dioxus_application<Cfg, Error>(self, cfg: Cfg, app: fn() -> Element) -> Self
+    where
+        Cfg: TryInto<ServeConfig, Error = Error>,
+        Error: std::error::Error,
+    {
         // Add server functions and render index.html
         let server = self.serve_static_assets().register_server_functions();
 
-        server.fallback(
-            get(render_handler)
-                .with_state(RenderHandleState::new(cfg, app).with_ssr_state(ssr_state)),
-        )
+        match cfg.try_into() {
+            Ok(cfg) => {
+                let ssr_state = SSRState::new(&cfg);
+                server.fallback(
+                    get(render_handler)
+                        .with_state(RenderHandleState::new(cfg, app).with_ssr_state(ssr_state)),
+                )
+            }
+            Err(err) => {
+                tracing::trace!("Failed to create render handler. This is expected if you are only using fullstack for desktop/mobile server functions: {}", err);
+                server
+            }
+        }
     }
 }
 
@@ -343,7 +354,7 @@ impl RenderHandleState {
 ///         // to inject the context into server functions running outside
 ///         // of an SSR render context.
 ///         .fallback(get(render_handler)
-///             .with_state(RenderHandleState::new(app))
+///             .with_state(RenderHandleState::new(ServeConfig::new().unwrap(), app))
 ///         )
 ///         .into_make_service();
 ///     let listener = tokio::net::TcpListener::bind(addr).await.unwrap();

+ 192 - 8
packages/fullstack/src/config.rs

@@ -5,6 +5,34 @@ use dioxus_lib::prelude::*;
 use std::sync::Arc;
 
 /// Settings for a fullstack app.
+///
+/// Depending on what features are enabled, you can pass in configurations for each client platform as well as the server:
+/// ```rust, no_run
+/// # fn app() -> Element { todo!() }
+/// use dioxus::prelude::*;
+///
+/// let mut cfg = dioxus::fullstack::Config::new();
+///
+/// // Only set the server config if the server feature is enabled
+/// server_only! {
+///     cfg = cfg.with_server_config(ServeConfigBuilder::default());
+/// }
+///
+/// // Only set the web config if the web feature is enabled
+/// web! {
+///     cfg = cfg.with_web_config(dioxus::web::Config::default());
+/// }
+///
+/// // Only set the desktop config if the desktop feature is enabled
+/// desktop! {
+///     cfg = cfg.with_desktop_config(dioxus::desktop::Config::default());
+/// }
+///
+/// // Finally, launch the app with the config
+/// LaunchBuilder::new()
+///     .with_cfg(cfg)
+///     .launch(app);
+/// ```
 pub struct Config {
     #[cfg(feature = "server")]
     pub(crate) server_cfg: ServeConfigBuilder,
@@ -41,7 +69,25 @@ impl Config {
         Self::default()
     }
 
-    /// Set the incremental renderer config.
+    /// Set the incremental renderer config. The incremental config can be used to improve
+    /// performance of heavy routes by caching the rendered html in memory and/or the file system.
+    ///
+    /// ```rust, no_run
+    /// # fn app() -> Element { todo!() }
+    /// use dioxus::prelude::*;
+    ///
+    /// let mut cfg = dioxus::fullstack::Config::new();
+    ///
+    /// // Only set the server config if the server feature is enabled
+    /// server_only! {
+    ///     cfg = cfg.incremental(IncrementalRendererConfig::default().with_memory_cache_limit(10000));
+    /// }
+    ///
+    /// // Finally, launch the app with the config
+    /// LaunchBuilder::new()
+    ///     .with_cfg(cfg)
+    ///     .launch(app);
+    /// ```
     #[cfg(feature = "server")]
     #[cfg_attr(docsrs, doc(cfg(feature = "server")))]
     pub fn incremental(self, cfg: IncrementalRendererConfig) -> Self {
@@ -51,28 +97,94 @@ impl Config {
         }
     }
 
-    /// Set the server config.
+    /// Set the server config
+    /// ```rust, no_run
+    /// # fn app() -> Element { todo!() }
+    /// use dioxus::prelude::*;
+    ///
+    /// let mut cfg = dioxus::fullstack::Config::new();
+    ///
+    /// // Only set the server config if the server feature is enabled
+    /// server_only! {
+    ///     cfg = cfg.with_server_config(ServeConfigBuilder::default());
+    /// }
+    ///
+    /// // Finally, launch the app with the config
+    /// LaunchBuilder::new()
+    ///     .with_cfg(cfg)
+    ///     .launch(app);
+    /// ```
     #[cfg(feature = "server")]
     #[cfg_attr(docsrs, doc(cfg(feature = "server")))]
-    pub fn with_server_cfg(self, server_cfg: ServeConfigBuilder) -> Self {
+    pub fn with_server_config(self, server_cfg: ServeConfigBuilder) -> Self {
         Self { server_cfg, ..self }
     }
 
-    /// Set the server config.
+    /// Set the server config by modifying the config in place
+    ///
+    /// ```rust, no_run
+    /// # fn app() -> Element { todo!() }
+    /// use dioxus::prelude::*;
+    ///
+    /// let mut cfg = dioxus::fullstack::Config::new();
+    ///
+    /// // Only set the server config if the server feature is enabled
+    /// server_only! {
+    ///     cfg.set_server_config(ServeConfigBuilder::default());
+    /// }
+    ///
+    /// // Finally, launch the app with the config
+    /// LaunchBuilder::new()
+    ///     .with_cfg(cfg)
+    ///     .launch(app);
+    /// ```
     #[cfg(feature = "server")]
     #[cfg_attr(docsrs, doc(cfg(feature = "server")))]
-    pub fn set_server_cfg(&mut self, server_cfg: ServeConfigBuilder) {
+    pub fn set_server_config(&mut self, server_cfg: ServeConfigBuilder) {
         self.server_cfg = server_cfg;
     }
 
-    /// Set the web config.
+    /// Set the web config
+    /// ```rust, no_run
+    /// # fn app() -> Element { todo!() }
+    /// use dioxus::prelude::*;
+    ///
+    /// let mut cfg = dioxus::fullstack::Config::new();
+    ///
+    /// // Only set the web config if the server feature is enabled
+    /// web! {
+    ///     cfg = cfg.with_web_config(dioxus::web::Config::default());
+    /// }
+    ///
+    /// // Finally, launch the app with the config
+    /// LaunchBuilder::new()
+    ///     .with_cfg(cfg)
+    ///     .launch(app);
+    /// ```
     #[cfg(feature = "web")]
     #[cfg_attr(docsrs, doc(cfg(feature = "web")))]
     pub fn with_web_config(self, web_cfg: dioxus_web::Config) -> Self {
         Self { web_cfg, ..self }
     }
 
-    /// Set the web config.
+    /// Set the server config by modifying the config in place
+    ///
+    /// ```rust, no_run
+    /// # fn app() -> Element { todo!() }
+    /// use dioxus::prelude::*;
+    ///
+    /// let mut cfg = dioxus::fullstack::Config::new();
+    ///
+    /// // Only set the web config if the server feature is enabled
+    /// web! {
+    ///     cfg.set_web_config(dioxus::web::Config::default());
+    /// }
+    ///
+    /// // Finally, launch the app with the config
+    /// LaunchBuilder::new()
+    ///     .with_cfg(cfg)
+    ///     .launch(app);
+    /// ```
     #[cfg(feature = "web")]
     #[cfg_attr(docsrs, doc(cfg(feature = "web")))]
     pub fn set_web_config(&mut self, web_cfg: dioxus_web::Config) {
@@ -80,6 +192,22 @@ impl Config {
     }
 
     /// Set the desktop config
+    /// ```rust, no_run
+    /// # fn app() -> Element { todo!() }
+    /// use dioxus::prelude::*;
+    ///
+    /// let mut cfg = dioxus::fullstack::Config::new();
+    ///
+    /// // Only set the desktop config if the server feature is enabled
+    /// desktop! {
+    ///     cfg = cfg.with_desktop_config(dioxus::desktop::Config::default());
+    /// }
+    ///
+    /// // Finally, launch the app with the config
+    /// LaunchBuilder::new()
+    ///     .with_cfg(cfg)
+    ///     .launch(app);
+    /// ```
     #[cfg(feature = "desktop")]
     #[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
     pub fn with_desktop_config(self, desktop_cfg: dioxus_desktop::Config) -> Self {
@@ -89,17 +217,73 @@ impl Config {
         }
     }
 
-    /// Set the desktop config.
+    /// Set the desktop config by modifying the config in place
+    ///
+    /// ```rust, no_run
+    /// # fn app() -> Element { todo!() }
+    /// use dioxus::prelude::*;
+    ///
+    /// let mut cfg = dioxus::fullstack::Config::new();
+    ///
+    /// // Only set the desktop config if the server feature is enabled
+    /// desktop! {
+    ///     cfg.set_desktop_config(dioxus::desktop::Config::default());
+    /// }
+    ///
+    /// // Finally, launch the app with the config
+    /// LaunchBuilder::new()
+    ///     .with_cfg(cfg)
+    ///     .launch(app);
+    /// ```
     #[cfg(feature = "desktop")]
     #[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
     pub fn set_desktop_config(&mut self, desktop_cfg: dioxus_desktop::Config) {
         self.desktop_cfg = desktop_cfg;
     }
 
+    /// Set the mobile config
+    /// ```rust, no_run
+    /// # fn app() -> Element { todo!() }
+    /// use dioxus::prelude::*;
+    ///
+    /// let mut cfg = dioxus::fullstack::Config::new();
+    ///
+    /// // Only set the mobile config if the server feature is enabled
+    /// mobile! {
+    ///     cfg = cfg.with_mobile_cfg(dioxus::mobile::Config::default());
+    /// }
+    ///
+    /// // Finally, launch the app with the config
+    /// LaunchBuilder::new()
+    ///     .with_cfg(cfg)
+    ///     .launch(app);
     /// Set the mobile config.
     #[cfg(feature = "mobile")]
     #[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
     pub fn with_mobile_cfg(self, mobile_cfg: dioxus_mobile::Config) -> Self {
         Self { mobile_cfg, ..self }
     }
+
+    /// Set the mobile config by modifying the config in place
+    /// ```rust, no_run
+    /// # fn app() -> Element { todo!() }
+    /// use dioxus::prelude::*;
+    ///
+    /// let mut cfg = dioxus::fullstack::Config::new();
+    ///
+    /// // Only set the mobile config if the server feature is enabled
+    /// mobile! {
+    ///     cfg.set_mobile_cfg(dioxus::mobile::Config::default());
+    /// }
+    ///
+    /// // Finally, launch the app with the config
+    /// LaunchBuilder::new()
+    ///     .with_cfg(cfg)
+    ///     .launch(app);
+    /// Set the mobile config.
+    #[cfg(feature = "mobile")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
+    pub fn set_mobile_cfg(&mut self, mobile_cfg: dioxus_mobile::Config) {
+        self.mobile_cfg = mobile_cfg;
+    }
 }

+ 73 - 3
packages/fullstack/src/serve_config.rs

@@ -15,7 +15,7 @@ pub struct ServeConfigBuilder {
 }
 
 impl ServeConfigBuilder {
-    /// Create a new ServeConfigBuilder with the root component and props to render on the server.
+    /// Create a new ServeConfigBuilder with incremental static generation disabled and the default index.html settings
     pub fn new() -> Self {
         Self {
             root_id: None,
@@ -25,7 +25,25 @@ impl ServeConfigBuilder {
         }
     }
 
-    /// Enable incremental static generation
+    /// Enable incremental static generation. Incremental static generation caches the
+    /// rendered html in memory and/or the file system. It can be used to improve performance of heavy routes.
+    ///
+    /// ```rust, no_run
+    /// # fn app() -> Element { todo!() }
+    /// use dioxus::prelude::*;
+    ///
+    /// let mut cfg = dioxus::fullstack::Config::new();
+    ///
+    /// // Only set the server config if the server feature is enabled
+    /// server_only! {
+    ///     cfg = cfg.with_server_cfg(ServeConfigBuilder::default().incremental(IncrementalRendererConfig::default()));
+    /// }
+    ///
+    /// // Finally, launch the app with the config
+    /// LaunchBuilder::new()
+    ///     .with_cfg(cfg)
+    ///     .launch(app);
+    /// ```
     pub fn incremental(mut self, cfg: dioxus_isrg::IncrementalRendererConfig) -> Self {
         self.incremental = Some(cfg);
         self
@@ -44,12 +62,56 @@ impl ServeConfigBuilder {
     }
 
     /// Set the id of the root element in the index.html file to place the prerendered content into. (defaults to main)
+    ///
+    /// # Example
+    ///
+    /// If your index.html file looks like this:
+    /// ```html
+    /// <!DOCTYPE html>
+    /// <html>
+    ///     <head>
+    ///         <title>My App</title>
+    ///     </head>
+    ///     <body>
+    ///         <div id="my-custom-root"></div>
+    ///     </body>
+    /// </html>
+    /// ```
+    ///
+    /// You can set the root id to `"my-custom-root"` to render the app into that element:
+    ///
+    /// ```rust, no_run
+    /// # fn app() -> Element { todo!() }
+    /// use dioxus::prelude::*;
+    ///
+    /// let mut cfg = dioxus::fullstack::Config::new();
+    ///
+    /// // Only set the server config if the server feature is enabled
+    /// server_only! {
+    ///     cfg = cfg.with_server_cfg(ServeConfigBuilder::default().root_id("app"));
+    /// }
+    ///
+    /// // You also need to set the root id in your web config
+    /// web! {
+    ///     cfg = cfg.with_web_config(dioxus::web::Config::default().rootname("app"));
+    /// }
+    ///
+    /// // And desktop config
+    /// desktop! {
+    ///     cfg = cfg.with_desktop_config(dioxus::desktop::Config::default().with_root_name("app"));
+    /// }
+    ///
+    /// // Finally, launch the app with the config
+    /// LaunchBuilder::new()
+    ///     .with_cfg(cfg)
+    ///     .launch(app);
+    /// ```
     pub fn root_id(mut self, root_id: &'static str) -> Self {
         self.root_id = Some(root_id);
         self
     }
 
-    /// Build the ServeConfig
+    /// Build the ServeConfig. This may fail if the index.html file is not found.
     pub fn build(self) -> Result<ServeConfig, UnableToLoadIndex> {
         // The CLI always bundles static assets into the exe/public directory
         let public_path = public_path();
@@ -75,6 +137,14 @@ impl ServeConfigBuilder {
     }
 }
 
+impl TryInto<ServeConfig> for ServeConfigBuilder {
+    type Error = UnableToLoadIndex;
+
+    fn try_into(self) -> Result<ServeConfig, Self::Error> {
+        self.build()
+    }
+}
+
 /// Get the path to the public assets directory to serve static files from
 pub(crate) fn public_path() -> PathBuf {
     // The CLI always bundles static assets into the exe/public directory

+ 135 - 17
packages/fullstack/src/server_context.rs

@@ -7,9 +7,21 @@ type SendSyncAnyMap =
     std::collections::HashMap<std::any::TypeId, Box<dyn Any + Send + Sync + 'static>>;
 
 /// A shared context for server functions that contains information about the request and middleware state.
-/// This allows you to pass data between your server framework and the server functions. This can be used to pass request information or information about the state of the server. For example, you could pass authentication data though this context to your server functions.
 ///
-/// You should not construct this directly inside components. Instead use the `HasServerContext` trait to get the server context from the scope.
+/// You should not construct this directly inside components or server functions. Instead use [`server_context()`] to get the server context from the current request.
+///
+/// # Example
+///
+/// ```rust, no_run
+/// # use dioxus::prelude::*;
+/// #[server]
+/// async fn read_headers() -> Result<(), ServerFnError> {
+///     let server_context = server_context();
+///     let headers: http::HeaderMap = server_context.extract().await?;
+///     println!("{:?}", headers);
+///     Ok(())
+/// }
+/// ```
 #[derive(Clone)]
 pub struct DioxusServerContext {
     shared_context: std::sync::Arc<RwLock<SendSyncAnyMap>>,
@@ -59,7 +71,34 @@ mod server_fn_impl {
             }
         }
 
-        /// Clone a value from the shared server context
+        /// Clone a value from the shared server context. If you are using [`DioxusRouterExt`](crate::prelude::DioxusRouterExt), any values you insert into
+        /// the launch context will also be available in the server context.
+        ///
+        /// Example:
+        /// ```rust, no_run
+        /// use dioxus::prelude::*;
+        ///
+        /// LaunchBuilder::new()
+        ///     // You can provide context to your whole app (including server functions) with the `with_context` method on the launch builder
+        ///     .with_context(server_only! {
+        ///         1234567890u32
+        ///     })
+        ///     .launch(app);
+        ///
+        /// #[server]
+        /// async fn read_context() -> Result<u32, ServerFnError> {
+        ///     // You can extract values from the server context with the `extract` function
+        ///     let FromContext(value) = extract().await?;
+        ///     Ok(value)
+        /// }
+        ///
+        /// fn app() -> Element {
+        ///     let future = use_resource(read_context);
+        ///     rsx! {
+        ///         h1 { "{future:?}" }
+        ///     }
+        /// }
+        /// ```
         pub fn get<T: Any + Send + Sync + Clone + 'static>(&self) -> Option<T> {
             self.shared_context
                 .read()
@@ -68,10 +107,10 @@ mod server_fn_impl {
         }
 
         /// Insert a value into the shared server context
+        ///
+        ///
         pub fn insert<T: Any + Send + Sync + 'static>(&self, value: T) {
-            self.shared_context
-                .write()
-                .insert(TypeId::of::<T>(), Box::new(value));
+            self.insert_any(Box::new(value));
         }
 
         /// Insert a Boxed `Any` value into the shared server context
@@ -82,33 +121,112 @@ mod server_fn_impl {
         }
 
         /// Get the response parts from the server context
+        ///
+        #[doc = include_str!("../docs/request_origin.md")]
+        ///
+        /// # Example
+        ///
+        /// ```rust, no_run
+        /// # use dioxus::prelude::*;
+        /// #[server]
+        /// async fn set_headers() -> Result<(), ServerFnError> {
+        ///     let server_context = server_context();
+        ///     let cookies = server_context.response_parts()
+        ///         .headers()
+        ///         .get("Cookie")
+        ///         .ok_or_else(|| ServerFnError::msg("failed to find Cookie header in the response"))?;
+        ///     println!("{:?}", cookies);
+        ///     Ok(())
+        /// }
+        /// ```
         pub fn response_parts(&self) -> RwLockReadGuard<'_, http::response::Parts> {
             self.response_parts.read()
         }
 
         /// Get the response parts from the server context
+        ///
+        #[doc = include_str!("../docs/request_origin.md")]
+        ///
+        /// # Example
+        ///
+        /// ```rust, no_run
+        /// # use dioxus::prelude::*;
+        /// #[server]
+        /// async fn set_headers() -> Result<(), ServerFnError> {
+        ///     let server_context = server_context();
+        ///     server_context.response_parts_mut()
+        ///         .headers_mut()
+        ///         .insert("Cookie", "dioxus=fullstack");
+        ///     Ok(())
+        /// }
+        /// ```
         pub fn response_parts_mut(&self) -> RwLockWriteGuard<'_, http::response::Parts> {
             self.response_parts.write()
         }
 
-        /// Get the request that triggered:
-        /// - The initial SSR render if called from a ScopeState or ServerFn
-        /// - The server function to be called if called from a server function after the initial render
+        /// Get the request parts
+        ///
+        #[doc = include_str!("../docs/request_origin.md")]
+        ///
+        /// # Example
+        ///
+        /// ```rust, no_run
+        /// # use dioxus::prelude::*;
+        /// #[server]
+        /// async fn read_headers() -> Result<(), ServerFnError> {
+        ///     let server_context = server_context();
+        ///     let id: &i32 = server_context.request_parts()
+        ///         .extensions
+        ///         .get()
+        ///         .ok_or_else(|| ServerFnError::msg("failed to find i32 extension in the request"))?;
+        ///     println!("{:?}", id);
+        ///     Ok(())
+        /// }
+        /// ```
         pub fn request_parts(&self) -> parking_lot::RwLockReadGuard<'_, http::request::Parts> {
             self.parts.read()
         }
 
-        /// Get the request that triggered:
-        /// - The initial SSR render if called from a ScopeState or ServerFn
-        /// - The server function to be called if called from a server function after the initial render
+        /// Get the request parts mutably
+        ///
+        #[doc = include_str!("../docs/request_origin.md")]
+        ///
+        /// # Example
+        ///
+        /// ```rust, no_run
+        /// # use dioxus::prelude::*;
+        /// #[server]
+        /// async fn read_headers() -> Result<(), ServerFnError> {
+        ///     let server_context = server_context();
+        ///     let id: i32 = server_context.request_parts_mut()
+        ///         .extensions
+        ///         .remove()
+        ///         .ok_or_else(|| ServerFnError::msg("failed to find i32 extension in the request"))?;
+        ///     println!("{:?}", id);
+        ///     Ok(())
+        /// }
+        /// ```
         pub fn request_parts_mut(&self) -> parking_lot::RwLockWriteGuard<'_, http::request::Parts> {
             self.parts.write()
         }
 
-        /// Extract some part from the request
-        pub async fn extract<R: std::error::Error, T: FromServerContext<Rejection = R>>(
-            &self,
-        ) -> Result<T, R> {
+        /// Extract part of the request.
+        ///
+        #[doc = include_str!("../docs/request_origin.md")]
+        ///
+        /// # Example
+        ///
+        /// ```rust, no_run
+        /// # use dioxus::prelude::*;
+        /// #[server]
+        /// async fn read_headers() -> Result<(), ServerFnError> {
+        ///     let server_context = server_context();
+        ///     let headers: http::HeaderMap = server_context.extract().await?;
+        ///     println!("{:?}", headers);
+        ///     Ok(())
+        /// }
+        /// ```
+        pub async fn extract<M, T: FromServerContext<M>>(&self) -> Result<T, T::Rejection> {
             T::from_request(self).await
         }
     }
@@ -175,7 +293,7 @@ impl<F: std::future::Future> std::future::Future for ProvideServerContext<F> {
 #[async_trait::async_trait]
 pub trait FromServerContext<I = ()>: Sized {
     /// The error type returned when extraction fails. This type must implement `std::error::Error`.
-    type Rejection: std::error::Error;
+    type Rejection;
 
     /// Extract this type from the server context.
     async fn from_request(req: &DioxusServerContext) -> Result<Self, Self::Rejection>;

+ 0 - 174
packages/fullstack/src/server_fn/collection.rs

@@ -1,174 +0,0 @@
-#[cfg(feature = "server")]
-#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
-#[derive(Clone)]
-/// A trait object for a function that be called on serializable arguments and returns a serializable result.
-pub struct ServerFnTraitObj(server_fn::ServerFnTraitObj<()>);
-
-#[cfg(feature = "server")]
-impl std::ops::Deref for ServerFnTraitObj {
-    type Target = server_fn::ServerFnTraitObj<()>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-#[cfg(feature = "server")]
-impl std::ops::DerefMut for ServerFnTraitObj {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
-    }
-}
-
-#[cfg(feature = "server")]
-impl ServerFnTraitObj {
-    fn new(
-        prefix: &'static str,
-        url: &'static str,
-        encoding: server_fn::Encoding,
-        run: ServerFunction,
-    ) -> Self {
-        Self(server_fn::ServerFnTraitObj::new(prefix, url, encoding, run))
-    }
-
-    /// Create a new `ServerFnTraitObj` from a `server_fn::ServerFnTraitObj`.
-    pub const fn from_generic_server_fn(server_fn: server_fn::ServerFnTraitObj<()>) -> Self {
-        Self(server_fn)
-    }
-}
-
-#[cfg(feature = "server")]
-server_fn::inventory::collect!(ServerFnTraitObj);
-
-#[cfg(feature = "server")]
-#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
-/// Middleware for a server function
-pub struct ServerFnMiddleware {
-    /// The prefix of the server function.
-    pub prefix: &'static str,
-    /// The url of the server function.
-    pub url: &'static str,
-    /// The middleware layers.
-    pub middleware: fn() -> Vec<std::sync::Arc<dyn crate::layer::Layer>>,
-}
-
-#[cfg(feature = "server")]
-pub(crate) static MIDDLEWARE: once_cell::sync::Lazy<
-    std::collections::HashMap<
-        (&'static str, &'static str),
-        Vec<std::sync::Arc<dyn crate::layer::Layer>>,
-    >,
-> = once_cell::sync::Lazy::new(|| {
-    let mut map: std::collections::HashMap<
-        (&'static str, &'static str),
-        Vec<std::sync::Arc<dyn crate::layer::Layer>>,
-    > = std::collections::HashMap::new();
-    for middleware in server_fn::inventory::iter::<ServerFnMiddleware> {
-        map.entry((middleware.prefix, middleware.url))
-            .or_default()
-            .extend((middleware.middleware)().iter().cloned());
-    }
-    map
-});
-
-#[cfg(feature = "server")]
-server_fn::inventory::collect!(ServerFnMiddleware);
-
-#[cfg(feature = "server")]
-#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
-/// A server function that can be called on serializable arguments and returns a serializable result.
-pub type ServerFunction = server_fn::SerializedFnTraitObj<()>;
-
-#[cfg(feature = "server")]
-#[allow(clippy::type_complexity)]
-static REGISTERED_SERVER_FUNCTIONS: once_cell::sync::Lazy<
-    std::sync::Arc<std::sync::RwLock<std::collections::HashMap<&'static str, ServerFnTraitObj>>>,
-> = once_cell::sync::Lazy::new(|| {
-    let mut map = std::collections::HashMap::new();
-    for server_fn in server_fn::inventory::iter::<ServerFnTraitObj> {
-        map.insert(server_fn.0.url(), server_fn.clone());
-    }
-    std::sync::Arc::new(std::sync::RwLock::new(map))
-});
-
-#[cfg(feature = "server")]
-#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
-/// The registry of all Dioxus server functions.
-pub struct DioxusServerFnRegistry;
-
-#[cfg(feature = "server")]
-impl server_fn::ServerFunctionRegistry<()> for DioxusServerFnRegistry {
-    type Error = ServerRegistrationFnError;
-
-    fn register_explicit(
-        prefix: &'static str,
-        url: &'static str,
-        server_function: ServerFunction,
-        encoding: server_fn::Encoding,
-    ) -> Result<(), Self::Error> {
-        // store it in the hashmap
-        let mut write = REGISTERED_SERVER_FUNCTIONS
-            .write()
-            .map_err(|e| ServerRegistrationFnError::Poisoned(e.to_string()))?;
-        let prev = write.insert(
-            url,
-            ServerFnTraitObj::new(prefix, url, encoding, server_function),
-        );
-
-        // if there was already a server function with this key,
-        // return Err
-        match prev {
-            Some(_) => Err(ServerRegistrationFnError::AlreadyRegistered(format!(
-                "There was already a server function registered at {:?}. \
-                     This can happen if you use the same server function name \
-                     in two different modules
-                on `stable` or in `release` mode.",
-                url
-            ))),
-            None => Ok(()),
-        }
-    }
-
-    /// Returns the server function registered at the given URL, or `None` if no function is registered at that URL.
-    fn get(url: &str) -> Option<server_fn::ServerFnTraitObj<()>> {
-        REGISTERED_SERVER_FUNCTIONS
-            .read()
-            .ok()
-            .and_then(|fns| fns.get(url).map(|inner| inner.0.clone()))
-    }
-
-    /// Returns the server function registered at the given URL, or `None` if no function is registered at that URL.
-    fn get_trait_obj(url: &str) -> Option<server_fn::ServerFnTraitObj<()>> {
-        Self::get(url)
-    }
-
-    fn get_encoding(url: &str) -> Option<server_fn::Encoding> {
-        REGISTERED_SERVER_FUNCTIONS
-            .read()
-            .ok()
-            .and_then(|fns| fns.get(url).map(|f| f.encoding()))
-    }
-
-    /// Returns a list of all registered server functions.
-    fn paths_registered() -> Vec<&'static str> {
-        REGISTERED_SERVER_FUNCTIONS
-            .read()
-            .ok()
-            .map(|fns| fns.keys().cloned().collect())
-            .unwrap_or_default()
-    }
-}
-
-#[cfg(feature = "server")]
-#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
-/// Errors that can occur when registering a server function.
-#[derive(thiserror::Error, Debug, Clone, serde::Serialize, serde::Deserialize)]
-#[non_exhaustive]
-pub enum ServerRegistrationFnError {
-    /// The server function is already registered.
-    #[error("The server function {0} is already registered")]
-    AlreadyRegistered(String),
-    /// The server function registry is poisoned.
-    #[error("The server function registry is poisoned: {0}")]
-    Poisoned(String),
-}

+ 6 - 0
packages/server-macro/Cargo.toml

@@ -18,6 +18,12 @@ quote = "^1.0.26"
 syn = { workspace = true, features = ["full"] }
 server_fn_macro = "0.6.11"
 
+[dev-dependencies]
+dioxus = { workspace = true, features = ["fullstack"] }
+serde = { workspace = true, features = ["derive"] }
+tower-http = { workspace = true, features = ["timeout"]}
+axum = { workspace = true }
+
 [lib]
 proc-macro = true
 

+ 73 - 0
packages/server-macro/src/lib.rs

@@ -15,6 +15,10 @@ use syn::__private::ToTokens;
 ///
 /// ## Usage
 /// ```rust,ignore
+/// # use dioxus::prelude::*;
+/// # #[derive(serde::Deserialize, serde::Serialize)]
+/// # pub struct BlogPost;
+/// # async fn load_posts(category: &str) -> Result<Vec<BlogPost>, ServerFnError> { unimplemented!() }
 /// #[server]
 /// pub async fn blog_posts(
 ///     category: String,
@@ -68,6 +72,75 @@ use syn::__private::ToTokens;
 ///   // etc.
 /// }
 /// ```
+///
+/// ## Adding layers to server functions
+///
+/// Layers allow you to transform the request and response of a server function. You can use layers
+/// to add authentication, logging, or other functionality to your server functions. Server functions integrate
+/// with the tower ecosystem, so you can use any layer that is compatible with tower.
+///
+/// Common layers include:
+/// - [`tower_http::trace::TraceLayer`](https://docs.rs/tower-http/latest/tower_http/trace/struct.TraceLayer.html) for tracing requests and responses
+/// - [`tower_http::compression::CompressionLayer`](https://docs.rs/tower-http/latest/tower_http/compression/struct.CompressionLayer.html) for compressing large responses
+/// - [`tower_http::cors::CorsLayer`](https://docs.rs/tower-http/latest/tower_http/cors/struct.CorsLayer.html) for adding CORS headers to responses
+/// - [`tower_http::timeout::TimeoutLayer`](https://docs.rs/tower-http/latest/tower_http/timeout/struct.TimeoutLayer.html) for adding timeouts to requests
+/// - [`tower_sessions::service::SessionManagerLayer`](https://docs.rs/tower-sessions/0.13.0/tower_sessions/service/struct.SessionManagerLayer.html) for adding session management to requests
+///
+/// You can add a tower [`Layer`](https://docs.rs/tower/latest/tower/trait.Layer.html) to your server function with the middleware attribute:
+///
+/// ```rust,ignore
+/// # use dioxus::prelude::*;
+/// #[server]
+/// // The TraceLayer will log all requests to the console
+/// #[middleware(tower_http::timeout::TimeoutLayer::new(std::time::Duration::from_secs(5)))]
+/// pub async fn my_wacky_server_fn(input: Vec<String>) -> Result<usize, ServerFnError> {
+///     unimplemented!()
+/// }
+/// ```
+///
+/// ## Extracting additional data from requests
+///
+/// Server functions automatically handle serialization and deserialization of arguments and responses.
+/// However, you may want to extract additional data from the request, such as the user's session or
+/// authentication information. You can do this with the `extract` function. This function returns any
+/// type that implements the [`FromRequestParts`](https://docs.rs/axum/latest/axum/extract/trait.FromRequestParts.html)
+/// trait:
+///
+/// ```rust,ignore
+/// # use dioxus::prelude::*;
+/// #[server]
+/// pub async fn my_wacky_server_fn(input: Vec<String>) -> Result<String, ServerFnError> {
+///     let headers: axum::http::header::HeaderMap = extract().await?;
+///     Ok(format!("The server got a request with headers: {:?}", headers))
+/// }
+/// ```
+///
+/// ## Sharing data with server functions
+///
+/// You may need to share context with your server functions like a database pool. Server
+/// functions can access any context provided through the launch builder. You can access
+/// this context with the `FromContext` extractor:
+///
+/// ```rust,ignore
+/// # use dioxus::prelude::*;
+/// # fn app() -> Element { unimplemented!() }
+/// #[derive(Clone, Copy, Debug)]
+/// struct DatabasePool;
+///
+/// fn main() {
+///     LaunchBuilder::new()
+///         .with_context(server_only! {
+///             DatabasePool
+///         })
+///         .launch(app);
+/// }
+///
+/// #[server]
+/// pub async fn my_wacky_server_fn(input: Vec<String>) -> Result<String, ServerFnError> {
+///     let FromContext(pool): FromContext<DatabasePool> = extract().await?;
+///     Ok(format!("The server read {:?} from the shared context", pool))
+/// }
+/// ```
 #[proc_macro_attribute]
 pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
     match server_macro_impl(