Browse Source

bump server functions

Evan Almloff 1 year ago
parent
commit
e745212157

+ 78 - 26
Cargo.lock

@@ -622,6 +622,7 @@ dependencies = [
  "matchit",
  "memchr",
  "mime",
+ "multer",
  "percent-encoding",
  "pin-project-lite",
  "rustversion",
@@ -2692,7 +2693,7 @@ dependencies = [
  "dioxus-router-macro",
  "dioxus-ssr",
  "gloo",
- "gloo-utils",
+ "gloo-utils 0.1.7",
  "js-sys",
  "serde",
  "serde_json",
@@ -4086,7 +4087,7 @@ dependencies = [
  "gloo-render",
  "gloo-storage",
  "gloo-timers",
- "gloo-utils",
+ "gloo-utils 0.1.7",
  "gloo-worker",
 ]
 
@@ -4096,7 +4097,7 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f"
 dependencies = [
- "gloo-utils",
+ "gloo-utils 0.1.7",
  "js-sys",
  "serde",
  "wasm-bindgen",
@@ -4142,7 +4143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f"
 dependencies = [
  "gloo-events",
- "gloo-utils",
+ "gloo-utils 0.1.7",
  "serde",
  "serde-wasm-bindgen",
  "serde_urlencoded",
@@ -4153,14 +4154,15 @@ dependencies = [
 
 [[package]]
 name = "gloo-net"
-version = "0.2.6"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10"
+checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620"
 dependencies = [
  "futures-channel",
  "futures-core",
  "futures-sink",
- "gloo-utils",
+ "gloo-utils 0.1.7",
+ "http 0.2.11",
  "js-sys",
  "pin-project",
  "serde",
@@ -4173,14 +4175,14 @@ dependencies = [
 
 [[package]]
 name = "gloo-net"
-version = "0.3.1"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620"
+checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173"
 dependencies = [
  "futures-channel",
  "futures-core",
  "futures-sink",
- "gloo-utils",
+ "gloo-utils 0.2.0",
  "http 0.2.11",
  "js-sys",
  "pin-project",
@@ -4208,7 +4210,7 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480"
 dependencies = [
- "gloo-utils",
+ "gloo-utils 0.1.7",
  "js-sys",
  "serde",
  "serde_json",
@@ -4240,6 +4242,19 @@ dependencies = [
  "web-sys",
 ]
 
+[[package]]
+name = "gloo-utils"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
+dependencies = [
+ "js-sys",
+ "serde",
+ "serde_json",
+ "wasm-bindgen",
+ "web-sys",
+]
+
 [[package]]
 name = "gloo-worker"
 version = "0.2.1"
@@ -4249,7 +4264,7 @@ dependencies = [
  "anymap2",
  "bincode",
  "gloo-console",
- "gloo-utils",
+ "gloo-utils 0.1.7",
  "js-sys",
  "serde",
  "wasm-bindgen",
@@ -6008,6 +6023,24 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "multer"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a15d522be0a9c3e46fd2632e272d178f56387bdb5c9fbb3a36c649062e9b5219"
+dependencies = [
+ "bytes",
+ "encoding_rs",
+ "futures-util",
+ "http 1.0.0",
+ "httparse",
+ "log",
+ "memchr",
+ "mime",
+ "spin 0.9.8",
+ "version_check",
+]
+
 [[package]]
 name = "names"
 version = "0.14.0"
@@ -7608,6 +7641,7 @@ dependencies = [
  "js-sys",
  "log",
  "mime",
+ "mime_guess",
  "native-tls",
  "once_cell",
  "percent-encoding",
@@ -8097,6 +8131,15 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "send_wrapper"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
+dependencies = [
+ "futures-core",
+]
+
 [[package]]
 name = "separator"
 version = "0.4.1"
@@ -8260,49 +8303,58 @@ dependencies = [
 
 [[package]]
 name = "server_fn"
-version = "0.5.7"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c265de965fe48e09ad8899d0ab1ffebdfa1a9914e4de5ff107b07bd94cf7541"
+checksum = "97fab54d9dd2d7e9eba4efccac41d2ec3e7c6e9973d14c0486d662a32662320c"
 dependencies = [
- "ciborium",
+ "axum",
+ "bytes",
  "const_format",
- "gloo-net 0.2.6",
+ "dashmap",
+ "futures",
+ "gloo-net 0.5.0",
+ "http 1.0.0",
+ "http-body-util",
+ "hyper 1.1.0",
  "inventory",
  "js-sys",
- "lazy_static",
  "once_cell",
- "proc-macro2",
- "quote",
  "reqwest",
+ "send_wrapper",
  "serde",
  "serde_json",
  "serde_qs",
  "server_fn_macro_default",
- "syn 2.0.49",
  "thiserror",
+ "tower",
+ "tower-layer",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-streams",
+ "web-sys",
  "xxhash-rust",
 ]
 
 [[package]]
 name = "server_fn_macro"
-version = "0.5.7"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f77000541a62ceeec01eef3ee0f86c155c33dac5fae750ad04a40852c6d5469a"
+checksum = "3be6011b586a0665546b7ced372b0be690d9e005d3f8524795da2843274d7720"
 dependencies = [
  "const_format",
- "proc-macro-error",
+ "convert_case 0.6.0",
  "proc-macro2",
  "quote",
- "serde",
  "syn 2.0.49",
  "xxhash-rust",
 ]
 
 [[package]]
 name = "server_fn_macro_default"
-version = "0.5.7"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a3353f22e2bcc451074d4feaa37317d9d17dff11d4311928384734ea17ab9ca"
+checksum = "752ed78ec49132d154b922cf5ab6485680cab039a75740c48ea2db621ad481da"
 dependencies = [
  "server_fn_macro",
  "syn 2.0.49",

+ 3 - 4
packages/fullstack/Cargo.toml

@@ -12,9 +12,8 @@ resolver = "2"
 
 [dependencies]
 # server functions
-server_fn = { version = "0.5.2", default-features = false }
-dioxus_server_macro = { workspace = true }
-
+server_fn = { version = "0.6.5", features = ["json", "url", "browser"], default-features = false }
+dioxus_server_macro = { workspace = true, version = "0.6.5", default-features = false }
 
 # axum
 axum = { workspace = true, features = ["ws", "macros"], default-features = false, optional = true }
@@ -71,7 +70,7 @@ desktop = ["dioxus-desktop"]
 mobile = ["dioxus-mobile"]
 default-tls = ["server_fn/default-tls"]
 rustls = ["server_fn/rustls"]
-axum = ["dep:axum", "tower-http", "server"]
+axum = ["dep:axum", "tower-http", "server", "server_fn/axum", "dioxus_server_macro/axum"]
 server = [
     "server_fn/ssr",
     "dioxus_server_macro/server",

+ 119 - 87
packages/fullstack/src/axum_adapter.rs

@@ -57,61 +57,25 @@
 use axum::{
     body::{self, Body},
     extract::State,
-    handler::Handler,
     http::{Request, Response, StatusCode},
     response::IntoResponse,
     routing::{get, post},
     Router,
 };
 use dioxus_lib::prelude::VirtualDom;
-use server_fn::{Encoding, ServerFunctionRegistry};
+use server_fn::error::NoCustomError;
+use server_fn::error::ServerFnErrorSerde;
 use std::sync::Arc;
 use std::sync::RwLock;
+use axum::routing::*;
+use http::header::*;
 
 use crate::{
     prelude::*, render::SSRState, serve_config::ServeConfig, server_context::DioxusServerContext,
-    server_fn::collection::DioxusServerFnRegistry,
 };
 
 /// A extension trait with utilities for integrating Dioxus with your Axum router.
 pub trait DioxusRouterExt<S> {
-    /// Registers server functions with a custom handler function. This allows you to pass custom context to your server functions by generating a [`DioxusServerContext`] from the request.
-    ///
-    /// # Example
-    /// ```rust
-    /// use dioxus_lib::prelude::*;
-    /// use dioxus_fullstack::prelude::*;
-    ///
-    /// #[tokio::main]
-    /// async fn main() {
-    ///    let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
-    ///    axum::Server::bind(&addr)
-    ///        .serve(
-    ///            axum::Router::new()
-    ///                .register_server_fns_with_handler("", |func| {
-    ///                    move |req: Request<Body>| async move {
-    ///                        let (parts, body) = req.into_parts();
-    ///                        let parts: Arc<http::request::Parts> = Arc::new(parts.into());
-    ///                        let server_context = DioxusServerContext::new(parts.clone());
-    ///                        server_fn_handler(server_context, func.clone(), parts, body).await
-    ///                    }
-    ///                })
-    ///                .into_make_service(),
-    ///        )
-    ///        .await
-    ///        .unwrap();
-    /// }
-    /// ```
-    fn register_server_fns_with_handler<H, T>(
-        self,
-        server_fn_route: &'static str,
-        handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
-    ) -> Self
-    where
-        H: Handler<T, S>,
-        T: 'static,
-        S: Clone + Send + Sync + 'static;
-
     /// Registers server functions with the default handler. This handler function will pass an empty [`DioxusServerContext`] to your server functions.
     ///
     /// # Example
@@ -133,7 +97,7 @@ pub trait DioxusRouterExt<S> {
     ///         .unwrap();
     /// }
     /// ```
-    fn register_server_fns(self, server_fn_route: &'static str) -> Self;
+    fn register_server_fns(self) -> Self;
 
     /// Register the web RSX hot reloading endpoint. This will enable hot reloading for your application in debug mode when you call [`dioxus_hot_reload::hot_reload_init`].
     ///
@@ -218,7 +182,6 @@ pub trait DioxusRouterExt<S> {
     /// ```
     fn serve_dioxus_application(
         self,
-        server_fn_route: &'static str,
         cfg: impl Into<ServeConfig>,
         build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
     ) -> Self;
@@ -228,51 +191,20 @@ impl<S> DioxusRouterExt<S> for Router<S>
 where
     S: Send + Sync + Clone + 'static,
 {
-    fn register_server_fns_with_handler<H, T>(
-        self,
-        server_fn_route: &'static str,
-        mut handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
-    ) -> Self
-    where
-        H: Handler<T, S>,
-        T: 'static,
-        S: Clone + Send + Sync + 'static,
-    {
-        let mut router = self;
-        for server_fn_path in DioxusServerFnRegistry::paths_registered() {
-            let func = DioxusServerFnRegistry::get(server_fn_path).unwrap();
-            let full_route = format!("{server_fn_route}/{server_fn_path}");
-            match func.encoding() {
-                Encoding::Url | Encoding::Cbor => {
-                    router = router.route(&full_route, post(handler(func)));
-                }
-                Encoding::GetJSON | Encoding::GetCBOR => {
-                    router = router.route(&full_route, get(handler(func)));
-                }
-            }
+    fn register_server_fns(mut self) -> Self {
+        use http::method::Method;
+
+        for (path, method) in server_fn::axum::server_fn_paths() {
+            let handler = move |req| handle_server_fns_inner(path, ||{}, req);
+            self = match method {
+                Method::GET => self.route(path, get(handler)),
+                Method::POST => self.route(path, post(handler)),
+                Method::PUT => self.route(path, put(handler)),
+                _ => todo!()
+            };
         }
-        router
-    }
 
-    fn register_server_fns(self, server_fn_route: &'static str) -> Self {
-        self.register_server_fns_with_handler(server_fn_route, |func| {
-            move |req: Request<Body>| {
-                let mut service = crate::server_fn_service(Default::default(), func);
-                async move {
-                    let (req, body) = req.into_parts();
-                    let req = Request::from_parts(req, body);
-                    let res = service.run(req);
-                    match res.await {
-                        Ok(res) => Ok::<_, std::convert::Infallible>(res.map(|b| b.into())),
-                        Err(e) => {
-                            let mut res = Response::new(Body::from(e.to_string()));
-                            *res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
-                            Ok(res)
-                        }
-                    }
-                }
-            }
-        })
+        self
     }
 
     fn serve_static_assets(mut self, assets_path: impl Into<std::path::PathBuf>) -> Self {
@@ -317,7 +249,6 @@ where
 
     fn serve_dioxus_application(
         self,
-        server_fn_route: &'static str,
         cfg: impl Into<ServeConfig>,
         build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
     ) -> Self {
@@ -327,7 +258,7 @@ where
         // Add server functions and render index.html
         self.serve_static_assets(cfg.assets_path.clone())
             .connect_hot_reload()
-            .register_server_fns(server_fn_route)
+            .register_server_fns()
             .fallback(get(render_handler).with_state((cfg, Arc::new(build_virtual_dom), ssr_state)))
     }
 
@@ -516,3 +447,104 @@ pub async fn hot_reload_handler(ws: axum::extract::WebSocketUpgrade) -> impl Int
         }
     })
 }
+
+fn get_local_pool() -> tokio_util::task::LocalPoolHandle {
+    use once_cell::sync::OnceCell;
+    static LOCAL_POOL: OnceCell<tokio_util::task::LocalPoolHandle> = OnceCell::new();
+    LOCAL_POOL
+        .get_or_init(|| {
+            tokio_util::task::LocalPoolHandle::new(
+                std::thread::available_parallelism()
+                    .map(Into::into)
+                    .unwrap_or(1),
+            )
+        })
+        .clone()
+}
+
+/// A handler for Dioxus server functions. This will run the server function and return the result.
+async fn handle_server_fns_inner(
+    path: &str,
+    additional_context: impl Fn() + 'static + Clone + Send,
+    req: Request<Body>,
+) -> impl IntoResponse {
+    use server_fn::middleware::Service;
+
+    let (tx, rx) = tokio::sync::oneshot::channel();
+    let path_string = path.to_string();
+
+    get_local_pool().spawn_pinned(move || async move {
+            let (parts, body) = req.into_parts();
+            let req = Request::from_parts(parts.clone(), body);
+        
+
+        let res = if let Some(mut service) =
+            server_fn::axum::get_server_fn_service(&path_string)
+        {
+
+            let server_context = DioxusServerContext::new(Arc::new(RwLock::new(parts)));
+            additional_context();
+
+            // store Accepts and Referrer in case we need them for redirect (below)
+            let accepts_html = req
+                .headers()
+                .get(ACCEPT)
+                .and_then(|v| v.to_str().ok())
+                .map(|v| v.contains("text/html"))
+                .unwrap_or(false);
+            let referrer = req.headers().get(REFERER).cloned();
+
+            // actually run the server fn
+            let mut res = service.run(req).await;
+
+
+            // it it accepts text/html (i.e., is a plain form post) and doesn't already have a
+            // Location set, then redirect to to Referer
+            if accepts_html {
+                if let Some(referrer) = referrer {
+                    let has_location = res.headers().get(LOCATION).is_some();
+                    if !has_location {
+                        *res.status_mut() = StatusCode::FOUND;
+                        res.headers_mut().insert(LOCATION, referrer);
+                    }
+                }
+            }
+
+            // apply the response parts from the server context to the response
+            let mut res_options = server_context.response_parts_mut().unwrap();
+            res.headers_mut().extend(res_options.headers.drain());
+
+            Ok(res)
+        } else {
+            Response::builder().status(StatusCode::BAD_REQUEST).body(
+                {
+                    #[cfg(target_family = "wasm")]
+                    {
+                        Body::from(format!(
+                            "No server function found for path: {path_string}\nYou may need to explicitly register the server function with `register_explicit`, rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.",
+                        ))
+                    }
+                    #[cfg(not(target_family = "wasm"))]
+                    {
+                        Body::from(format!(
+                            "No server function found for path: {path_string}\nYou may need to rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.",
+                        ))
+                    }
+                }
+            )
+        }
+        .expect("could not build Response");
+
+        _ = tx.send(res);
+    });
+
+    rx.await.unwrap_or_else(|e| {
+        (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            ServerFnError::<NoCustomError>::ServerError(e.to_string())
+                .ser()
+                .unwrap_or_default(),
+        )
+            .into_response()
+    })
+}

+ 1 - 1
packages/fullstack/src/config.rs

@@ -129,7 +129,7 @@ impl Config {
             use tower::ServiceBuilder;
 
             let ssr_state = SSRState::new(&cfg);
-            let router = axum::Router::new().register_server_fns(server_fn_route);
+            let router = axum::Router::new().register_server_fns();
             #[cfg(not(any(feature = "desktop", feature = "mobile")))]
             let router = router
                 .serve_static_assets(cfg.assets_path.clone())

+ 0 - 86
packages/fullstack/src/layer.rs

@@ -1,86 +0,0 @@
-use std::pin::Pin;
-use tracing_futures::Instrument;
-
-use http::{Request, Response};
-
-/// A layer that wraps a service. This can be used to add additional information to the request, or response on top of some other service
-pub trait Layer: Send + Sync + 'static {
-    /// Wrap a boxed service with this layer
-    fn layer(&self, inner: BoxedService) -> BoxedService;
-}
-
-impl<L> Layer for L
-where
-    L: tower_layer::Layer<BoxedService> + Sync + Send + 'static,
-    L::Service: Service + Send + 'static,
-{
-    fn layer(&self, inner: BoxedService) -> BoxedService {
-        BoxedService(Box::new(self.layer(inner)))
-    }
-}
-
-type MyBody = axum::body::Body;
-
-/// A service is a function that takes a request and returns an async response
-pub trait Service {
-    /// Run the service and produce a future that resolves to a response
-    fn run(
-        &mut self,
-        req: http::Request<MyBody>,
-    ) -> Pin<
-        Box<
-            dyn std::future::Future<Output = Result<Response<MyBody>, server_fn::ServerFnError>>
-                + Send,
-        >,
-    >;
-}
-
-impl<S> Service for S
-where
-    S: tower::Service<http::Request<MyBody>, Response = Response<MyBody>>,
-    S::Future: Send + 'static,
-    S::Error: Into<server_fn::ServerFnError>,
-{
-    fn run(
-        &mut self,
-        req: http::Request<MyBody>,
-    ) -> Pin<
-        Box<
-            dyn std::future::Future<Output = Result<Response<MyBody>, server_fn::ServerFnError>>
-                + Send,
-        >,
-    > {
-        let fut = self.call(req).instrument(tracing::trace_span!(
-            "service",
-            "{}",
-            std::any::type_name::<S>()
-        ));
-        Box::pin(async move { fut.await.map_err(|err| err.into()) })
-    }
-}
-
-/// A boxed service is a type-erased service that can be used without knowing the underlying type
-pub struct BoxedService(pub Box<dyn Service + Send>);
-
-impl tower::Service<http::Request<MyBody>> for BoxedService {
-    type Response = http::Response<MyBody>;
-    type Error = server_fn::ServerFnError;
-    type Future = Pin<
-        Box<
-            dyn std::future::Future<
-                    Output = Result<http::Response<MyBody>, server_fn::ServerFnError>,
-                > + Send,
-        >,
-    >;
-
-    fn poll_ready(
-        &mut self,
-        _cx: &mut std::task::Context<'_>,
-    ) -> std::task::Poll<Result<(), Self::Error>> {
-        Ok(()).into()
-    }
-
-    fn call(&mut self, req: Request<MyBody>) -> Self::Future {
-        self.0.run(req)
-    }
-}

+ 2 - 17
packages/fullstack/src/lib.rs

@@ -12,21 +12,14 @@ mod html_storage;
 #[cfg(feature = "axum")]
 mod axum_adapter;
 
-#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
-#[cfg(feature = "server")]
-pub use server_fn::service::{server_fn_service, ServerFnHandler};
-
 mod config;
 mod hooks;
 pub mod launch;
-mod server_fn;
 
 #[cfg(all(debug_assertions, feature = "hot-reload", feature = "server"))]
 mod hot_reload;
 pub use config::*;
 
-#[cfg(feature = "server")]
-mod layer;
 
 #[cfg(feature = "server")]
 mod render;
@@ -50,10 +43,6 @@ pub mod prelude {
     #[cfg_attr(docsrs, doc(cfg(not(feature = "server"))))]
     pub use crate::html_storage::deserialize::get_root_props_from_document;
 
-    #[cfg(feature = "server")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "server")))]
-    pub use crate::layer::{Layer, Service};
-
     #[cfg(all(feature = "server", feature = "router"))]
     #[cfg_attr(docsrs, doc(cfg(all(feature = "server", feature = "router"))))]
     pub use crate::render::pre_cache_static_routes_with_props;
@@ -79,17 +68,13 @@ pub mod prelude {
     pub use crate::server_context::{
         extract, server_context, DioxusServerContext, FromServerContext, ProvideServerContext,
     };
-    pub use crate::server_fn::DioxusServerFn;
-
-    #[cfg(feature = "server")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "server")))]
-    pub use crate::server_fn::collection::{ServerFnMiddleware, ServerFnTraitObj, ServerFunction};
-    pub use dioxus_server_macro::*;
 
     #[cfg(feature = "server")]
     #[cfg_attr(docsrs, doc(cfg(feature = "server")))]
     pub use dioxus_ssr::incremental::IncrementalRendererConfig;
+
     pub use server_fn::{self, ServerFn as _, ServerFnError};
+    pub use dioxus_server_macro::*;
 }
 
 // // Warn users about overlapping features

+ 0 - 24
packages/fullstack/src/server_fn/mod.rs

@@ -1,24 +0,0 @@
-#[cfg(feature = "server")]
-pub(crate)mod collection;
-#[cfg(feature = "server")]
-pub mod service;
-
-/// Defines a "server function." A server function can be called from the server or the client,
-/// but the body of its code will only be run on the server, i.e., if a crate feature `ssr` is enabled.
-///
-/// Server functions are created using the `server` macro.
-///
-/// The set of server functions
-/// can be queried on the server for routing purposes by calling [server_fn::ServerFunctionRegistry::get].
-///
-/// Technically, the trait is implemented on a type that describes the server function's arguments, not the function itself.
-pub trait DioxusServerFn: server_fn::ServerFn<()> {
-    /// Registers the server function, allowing the client to query it by URL.
-    #[cfg(feature = "server")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "server")))]
-    fn register_explicit() -> Result<(), server_fn::ServerFnError> {
-        Self::register_in_explicit::<crate::server_fn::collection::DioxusServerFnRegistry>()
-    }
-}
-
-impl<T> DioxusServerFn for T where T: server_fn::ServerFn<()> {}

+ 0 - 154
packages/fullstack/src/server_fn/service.rs

@@ -1,154 +0,0 @@
-//! # Server function Service
-
-//! This module defines a service that can be used to handle server functions.
-
-use http::StatusCode;
-use server_fn::{Encoding, Payload};
-use std::sync::{Arc, RwLock};
-
-use crate::server_fn::collection::MIDDLEWARE;
-use crate::{
-    layer::{BoxedService, Service},
-    prelude::{DioxusServerContext, ProvideServerContext},
-};
-
-type AxumBody = axum::body::Body;
-
-/// Create a server function handler with the given server context and server function.
-pub fn server_fn_service(
-    context: DioxusServerContext,
-    function: server_fn::ServerFnTraitObj<()>,
-) -> crate::layer::BoxedService {
-    let prefix = function.prefix().to_string();
-    let url = function.url().to_string();
-    if let Some(middleware) = MIDDLEWARE.get(&(&prefix, &url)) {
-        let mut service = BoxedService(Box::new(ServerFnHandler::new(context, function)));
-        for middleware in middleware {
-            service = middleware.layer(service);
-        }
-        service
-    } else {
-        BoxedService(Box::new(ServerFnHandler::new(context, function)))
-    }
-}
-
-#[derive(Clone)]
-/// A default handler for server functions. It will deserialize the request body, call the server function, and serialize the response.
-pub struct ServerFnHandler {
-    server_context: DioxusServerContext,
-    function: server_fn::ServerFnTraitObj<()>,
-}
-
-impl ServerFnHandler {
-    /// Create a new server function handler with the given server context and server function.
-    pub fn new(
-        server_context: impl Into<DioxusServerContext>,
-        function: server_fn::ServerFnTraitObj<()>,
-    ) -> Self {
-        let server_context = server_context.into();
-        Self {
-            server_context,
-            function,
-        }
-    }
-}
-
-impl Service for ServerFnHandler {
-    fn run(
-        &mut self,
-        req: http::Request<AxumBody>,
-    ) -> std::pin::Pin<
-        Box<
-            dyn std::future::Future<
-                    Output = Result<http::Response<AxumBody>, server_fn::ServerFnError>,
-                > + Send,
-        >,
-    > {
-        let Self {
-            server_context,
-            function,
-        } = self.clone();
-        Box::pin(async move {
-            let query = req.uri().query().unwrap_or_default().as_bytes().to_vec();
-            let (parts, body) = req.into_parts();
-            let body = axum::body::to_bytes(body, usize::MAX).await?.to_vec();
-            let headers = &parts.headers;
-            let accept_header = headers.get("Accept").cloned();
-            let parts = Arc::new(RwLock::new(parts));
-
-            // Because the future returned by `server_fn_handler` is `Send`, and the future returned by this function must be send, we need to spawn a new runtime
-            let pool = get_local_pool();
-            let result = pool
-                .spawn_pinned({
-                    let function = function.clone();
-                    let mut server_context = server_context.clone();
-                    server_context.parts = parts;
-                    move || async move {
-                        let data = match function.encoding() {
-                            Encoding::Url | Encoding::Cbor => &body,
-                            Encoding::GetJSON | Encoding::GetCBOR => &query,
-                        };
-                        let server_function_future = function.call((), data);
-                        let server_function_future = ProvideServerContext::new(
-                            server_function_future,
-                            server_context.clone(),
-                        );
-                        server_function_future.await
-                    }
-                })
-                .await?;
-            let mut res = http::Response::builder();
-
-            // Set the headers from the server context
-            let parts = server_context.response_parts().unwrap();
-            *res.headers_mut().expect("empty headers should be valid") = parts.headers.clone();
-
-            let serialized = result?;
-            // if this is Accept: application/json then send a serialized JSON response
-            let accept_header = accept_header.as_ref().and_then(|value| value.to_str().ok());
-            if accept_header == Some("application/json")
-                || accept_header
-                    == Some(
-                        "application/\
-                                x-www-form-urlencoded",
-                    )
-                || accept_header == Some("application/cbor")
-            {
-                res = res.status(StatusCode::OK);
-            }
-
-            Ok(match serialized {
-                Payload::Binary(data) => {
-                    res = res.header("Content-Type", "application/cbor");
-                    res.body(data.into())?
-                }
-                Payload::Url(data) => {
-                    res = res.header(
-                        "Content-Type",
-                        "application/\
-                                    x-www-form-urlencoded",
-                    );
-                    res.body(data.into())?
-                }
-                Payload::Json(data) => {
-                    res = res.header("Content-Type", "application/json");
-                    res.body(data.into())?
-                }
-            })
-        })
-    }
-}
-
-fn get_local_pool() -> tokio_util::task::LocalPoolHandle {
-    use once_cell::sync::OnceCell;
-    static LOCAL_POOL: OnceCell<tokio_util::task::LocalPoolHandle> = OnceCell::new();
-    LOCAL_POOL
-        .get_or_init(|| {
-            tokio_util::task::LocalPoolHandle::new(
-                std::thread::available_parallelism()
-                    .map(Into::into)
-                    .unwrap_or(1),
-            )
-        })
-        .clone()
-}

+ 1 - 0
packages/liveview/Cargo.toml

@@ -43,6 +43,7 @@ dioxus = { workspace = true }
 
 [features]
 default = ["hot-reload"]
+axum = ["dep:axum"]
 hot-reload = ["dioxus-hot-reload"]
 
 [[example]]

+ 0 - 1
packages/liveview/src/launch.rs

@@ -1,7 +1,6 @@
 use dioxus_core::*;
 use std::any::Any;
 
-#[cfg(feature = "axum")]
 pub type Config = crate::Config<axum::Router>;
 
 /// Launches the WebView and runs the event loop, with configuration and root props.

+ 1 - 0
packages/liveview/src/lib.rs

@@ -15,6 +15,7 @@ mod config;
 mod eval;
 mod events;
 pub use config::*;
+#[cfg(feature = "axum")]
 pub mod launch;
 
 pub trait WebsocketTx: SinkExt<String, Error = LiveViewError> {}

+ 4 - 2
packages/server-macro/Cargo.toml

@@ -17,11 +17,13 @@ proc-macro2 = "^1.0.63"
 quote = "^1.0.26"
 syn = { version = "2", features = ["full"] }
 convert_case = "^0.6.0"
-server_fn_macro = "^0.5.2"
+server_fn_macro = "^0.6.5"
 
 [lib]
 proc-macro = true
 
 [features]
-default = []
+actix = ["server_fn_macro/actix"]
+axum = ["server_fn_macro/axum"]
+nightly = ["server_fn_macro/nightly"]
 server = ["server_fn_macro/ssr"]

+ 69 - 195
packages/server-macro/src/lib.rs

@@ -2,209 +2,83 @@
 #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
 #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
 
-use convert_case::{Case, Converter};
+//! This crate contains the dioxus implementation of the #[macro@crate::server] macro without additional context from the server.
+//! See the [server_fn_macro] crate for more information.
+
 use proc_macro::TokenStream;
-use proc_macro2::Literal;
-use quote::{ToTokens, __private::TokenStream as TokenStream2};
-use server_fn_macro::*;
-use syn::{
-    parse::{Parse, ParseStream},
-    Ident, ItemFn, Token,
-};
+use server_fn_macro::server_macro_impl;
+use syn::__private::ToTokens;
 
-/// Declares that a function is a [server function](https://dioxuslabs.com/learn/0.4/reference/fullstack/server_functions). This means that
-/// its body will only run on the server, i.e., when the `ssr` feature is enabled.
+/// Declares that a function is a [server function](https://docs.rs/server_fn/).
+/// This means that its body will only run on the server, i.e., when the `ssr`
+/// feature is enabled on this crate.
 ///
-/// If you call a server function from the client (i.e., when the `csr` or `hydrate` features
-/// are enabled), it will instead make a network request to the server.
+/// ## Usage
+/// ```rust,ignore
+/// #[server]
+/// pub async fn blog_posts(
+///     category: String,
+/// ) -> Result<Vec<BlogPost>, ServerFnError> {
+///     let posts = load_posts(&category).await?;
+///     // maybe do some other work
+///     Ok(posts)
+/// }
+/// ```
 ///
-/// You can specify one, two, or three arguments to the server function:
-/// 1. *Optional*: A type name that will be used to identify and register the server function
-///   (e.g., `MyServerFn`).
-/// 2. *Optional*: A URL prefix at which the function will be mounted when it’s registered
-///   (e.g., `"/api"`). Defaults to `"/"`.
-/// 3. *Optional*: either `"Cbor"` (specifying that it should use the binary `cbor` format for
-///   serialization), `"Url"` (specifying that it should be use a URL-encoded form-data string).
-///   Defaults to `"Url"`. If you want to use this server function
-///   using Get instead of Post methods, the encoding must be `"GetCbor"` or `"GetJson"`.
+/// ## Named Arguments
 ///
-/// The server function itself can take any number of arguments, each of which should be serializable
-/// and deserializable with `serde`. Optionally, its first argument can be a [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html),
-/// which will be injected *on the server side.* This can be used to inject the raw HTTP request or other
-/// server-side context into the server function.
+/// You can any combination of the following named arguments:
+/// - `name`: sets the identifier for the server function’s type, which is a struct created
+///    to hold the arguments (defaults to the function identifier in PascalCase)
+/// - `prefix`: a prefix at which the server function handler will be mounted (defaults to `/api`)
+/// - `endpoint`: specifies the exact path at which the server function handler will be mounted,
+///   relative to the prefix (defaults to the function name followed by unique hash)
+/// - `input`: the encoding for the arguments (defaults to `PostUrl`)
+/// - `output`: the encoding for the response (defaults to `Json`)
+/// - `client`: a custom `Client` implementation that will be used for this server fn
+/// - `encoding`: (legacy, may be deprecated in future) specifies the encoding, which may be one
+///   of the following (not case sensitive)
+///     - `"Url"`: `POST` request with URL-encoded arguments and JSON response
+///     - `"GetUrl"`: `GET` request with URL-encoded arguments and JSON response
+///     - `"Cbor"`: `POST` request with CBOR-encoded arguments and response
+///     - `"GetCbor"`: `GET` request with URL-encoded arguments and CBOR response
+/// - `req` and `res` specify the HTTP request and response types to be used on the server (these
+///   should usually only be necessary if you are integrating with a server other than Actix/Axum)
+/// ```rust,ignore
+/// #[server(
+///   name = SomeStructName,
+///   prefix = "/my_api",
+///   endpoint = "my_fn",
+///   input = Cbor,
+///   output = Json
+/// )]
+/// pub async fn my_wacky_server_fn(input: Vec<String>) -> Result<usize, ServerFnError> {
+///   todo!()
+/// }
 ///
-/// ```ignore
-/// # use dioxus_fullstack::prelude::*; use serde::{Serialize, Deserialize};
-/// # #[derive(Serialize, Deserialize)]
-/// # pub struct Post { }
-/// #[server(ReadPosts, "/api")]
-/// pub async fn read_posts(how_many: u8, query: String) -> Result<Vec<Post>, ServerFnError> {
-///   // do some work on the server to access the database
-/// #   unimplemented!()
+/// // expands to
+/// #[derive(Deserialize, Serialize)]
+/// struct SomeStructName {
+///   input: Vec<String>
 /// }
-/// ```
 ///
-/// Note the following:
-/// - **Server functions must be `async`.** Even if the work being done inside the function body
-///   can run synchronously on the server, from the client’s perspective it involves an asynchronous
-///   function call.
-/// - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
-///   inside the function body can’t fail, the processes of serialization/deserialization and the
-///   network call are fallible.
-/// - **Return types must implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html).**
-///   This should be fairly obvious: we have to serialize arguments to send them to the server, and we
-///   need to deserialize the result to return it to the client.
-/// - **Arguments must be implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html)
-///   and [`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html).**
-///   They are serialized as an `application/x-www-form-urlencoded`
-///   form data using [`serde_urlencoded`](https://docs.rs/serde_urlencoded/latest/serde_urlencoded/) or as `application/cbor`
-///   using [`cbor`](https://docs.rs/cbor/latest/cbor/).
-/// - **The [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html) comes from the server.** Optionally, the first argument of a server function
-///   can be a [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html). This scope can be used to inject dependencies like the HTTP request
-///   or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client.
+/// impl ServerFn for SomeStructName {
+///   const PATH: &'static str = "/my_api/my_fn";
+///
+///   // etc.
+/// }
+/// ```
 #[proc_macro_attribute]
 pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
-    // before we pass this off to the server function macro, we apply extractors and middleware
-    let mut function: syn::ItemFn = match syn::parse(s).map_err(|e| e.to_compile_error()) {
-        Ok(f) => f,
-        Err(e) => return e.into(),
-    };
-
-    // extract all #[middleware] attributes
-    let mut middlewares: Vec<Middleware> = vec![];
-    function.attrs.retain(|attr| {
-        if attr.meta.path().is_ident("middleware") {
-            if let Ok(middleware) = attr.parse_args() {
-                middlewares.push(middleware);
-                false
-            } else {
-                true
-            }
-        } else {
-            true
-        }
-    });
-
-    let ItemFn {
-        attrs,
-        vis,
-        sig,
-        block,
-    } = function;
-    let mapped_body = quote::quote! {
-        #(#attrs)*
-        #vis #sig {
-            #block
-        }
-    };
-
-    let server_fn_path: syn::Path = syn::parse_quote!(::dioxus::fullstack::prelude::server_fn);
-    let trait_obj_wrapper: syn::Type =
-        syn::parse_quote!(::dioxus::fullstack::prelude::ServerFnTraitObj);
-    let mut args: ServerFnArgs = match syn::parse(args) {
-        Ok(args) => args,
-        Err(e) => return e.to_compile_error().into(),
-    };
-    if args.struct_name.is_none() {
-        let upper_cammel_case_name = Converter::new()
-            .from_case(Case::Snake)
-            .to_case(Case::UpperCamel)
-            .convert(sig.ident.to_string());
-        args.struct_name = Some(Ident::new(&upper_cammel_case_name, sig.ident.span()));
-    }
-    let struct_name = args.struct_name.as_ref().unwrap();
-    match server_macro_impl(
-        quote::quote!(#args),
-        mapped_body,
-        trait_obj_wrapper,
-        None,
-        Some(server_fn_path.clone()),
-    ) {
-        Err(e) => e.to_compile_error().into(),
-        Ok(tokens) => quote::quote! {
-            #tokens
-            #[cfg(feature = "server")]
-            #server_fn_path::inventory::submit! {
-                ::dioxus::fullstack::prelude::ServerFnMiddleware {
-                    prefix: #struct_name::PREFIX,
-                    url: #struct_name::URL,
-                    middleware: || vec![
-                        #(
-                            std::sync::Arc::new(#middlewares),
-                        ),*
-                    ]
-                }
-            }
-        }
-        .to_token_stream()
-        .into(),
-    }
-}
-
-#[derive(Debug)]
-struct Middleware {
-    expr: syn::Expr,
-}
-
-impl ToTokens for Middleware {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let expr = &self.expr;
-        tokens.extend(quote::quote! {
-            #expr
-        });
-    }
-}
-
-impl Parse for Middleware {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        let arg: syn::Expr = input.parse()?;
-        Ok(Middleware { expr: arg })
-    }
-}
-
-struct ServerFnArgs {
-    struct_name: Option<Ident>,
-    _comma: Option<Token![,]>,
-    prefix: Option<Literal>,
-    _comma2: Option<Token![,]>,
-    encoding: Option<Literal>,
-    _comma3: Option<Token![,]>,
-    fn_path: Option<Literal>,
-}
-
-impl ToTokens for ServerFnArgs {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let struct_name = self.struct_name.as_ref().map(|s| quote::quote! { #s, });
-        let prefix = self.prefix.as_ref().map(|p| quote::quote! { #p, });
-        let encoding = self.encoding.as_ref().map(|e| quote::quote! { #e, });
-        let fn_path = self.fn_path.as_ref().map(|f| quote::quote! { #f, });
-        tokens.extend(quote::quote! {
-            #struct_name
-            #prefix
-            #encoding
-            #fn_path
-        })
-    }
-}
-
-impl Parse for ServerFnArgs {
-    fn parse(input: ParseStream) -> syn::Result<Self> {
-        let struct_name = input.parse()?;
-        let _comma = input.parse()?;
-        let prefix = input.parse()?;
-        let _comma2 = input.parse()?;
-        let encoding = input.parse()?;
-        let _comma3 = input.parse()?;
-        let fn_path = input.parse()?;
-
-        Ok(Self {
-            struct_name,
-            _comma,
-            prefix,
-            _comma2,
-            encoding,
-            _comma3,
-            fn_path,
-        })
-    }
+   match server_macro_impl(
+       args.into(),
+       s.into(),
+       Some(syn::parse_quote!(server_fn)),
+       "/api",
+       None,
+       None,
+   ) {
+       Err(e) => e.to_compile_error().into(),
+       Ok(s) => s.to_token_stream().into(),
+   }
 }