diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 18633e97a..d182aa415 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "ssr", "fullstack"] [dependencies] # server functions -server_fn = { git = "https://github.com/leptos-rs/leptos", rev = "a9e6590b5e7f1c0b01da7db7b86719cb18a4aaa1", features = ["stable"] } +server_fn = { git = "https://github.com/leptos-rs/leptos", rev = "671b1e4a8fff7a2e05bb621ef08e87be2b18ccae", features = ["stable"] } dioxus_server_macro = { path = "server-macro" } # warp diff --git a/packages/server/server-macro/Cargo.toml b/packages/server/server-macro/Cargo.toml index 45b0ec3c6..286481bdd 100644 --- a/packages/server/server-macro/Cargo.toml +++ b/packages/server/server-macro/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] quote = "1.0.26" -server_fn_macro = { git = "https://github.com/leptos-rs/leptos", rev = "a9e6590b5e7f1c0b01da7db7b86719cb18a4aaa1", features = ["stable"] } +server_fn_macro = { git = "https://github.com/leptos-rs/leptos", rev = "671b1e4a8fff7a2e05bb621ef08e87be2b18ccae", features = ["stable"] } syn = { version = "1", features = ["full"] } [lib] diff --git a/packages/server/server-macro/src/lib.rs b/packages/server/server-macro/src/lib.rs index 14fc0ac77..b3f890f6a 100644 --- a/packages/server/server-macro/src/lib.rs +++ b/packages/server/server-macro/src/lib.rs @@ -14,9 +14,10 @@ use server_fn_macro::*; /// 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) or `"Url"` (specifying that it should be use a URL-encoded form-data string). +/// 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 to power a `
` that will -/// work without WebAssembly, the encoding must be `"Url"`. +/// work without WebAssembly, the encoding must be `"Url"`. If you want to use this server function +/// using Get instead of Post methods, the encoding must be `"GetCbor"` or `"GetJson"`. /// /// 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-server/latest/dixous_server/prelude/struct.DioxusServerContext.html), diff --git a/packages/server/src/adapters/axum_adapter.rs b/packages/server/src/adapters/axum_adapter.rs index f4bdd8a96..2d16e9491 100644 --- a/packages/server/src/adapters/axum_adapter.rs +++ b/packages/server/src/adapters/axum_adapter.rs @@ -55,10 +55,9 @@ //! } //! ``` -use std::{error::Error, sync::Arc}; - use axum::{ body::{self, Body, BoxBody, Full}, + extract::RawQuery, extract::{State, WebSocketUpgrade}, handler::Handler, http::{HeaderMap, Request, Response, StatusCode}, @@ -66,14 +65,13 @@ use axum::{ routing::{get, post}, Router, }; -use server_fn::{Payload, ServerFunctionRegistry}; +use server_fn::{Encoding, Payload, ServerFunctionRegistry}; +use std::error::Error; use tokio::task::spawn_blocking; use crate::{ - render::SSRState, - serve_config::ServeConfig, - server_context::DioxusServerContext, - server_fn::{DioxusServerFnRegistry, ServerFnTraitObj}, + prelude::*, render::SSRState, serve_config::ServeConfig, server_context::DioxusServerContext, + server_fn::DioxusServerFnRegistry, }; /// A extension trait with utilities for integrating Dioxus with your Axum router. @@ -106,7 +104,7 @@ pub trait DioxusRouterExt { fn register_server_fns_with_handler( self, server_fn_route: &'static str, - handler: impl Fn(Arc) -> H, + handler: impl Fn(ServerFunction) -> H, ) -> Self where H: Handler, @@ -200,7 +198,7 @@ where fn register_server_fns_with_handler( self, server_fn_route: &'static str, - mut handler: impl FnMut(Arc) -> H, + mut handler: impl FnMut(ServerFunction) -> H, ) -> Self where H: Handler, @@ -211,15 +209,22 @@ where 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}"); - router = router.route(&full_route, post(handler(func))); + 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))); + } + } } router } fn register_server_fns(self, server_fn_route: &'static str) -> Self { self.register_server_fns_with_handler(server_fn_route, |func| { - move |headers: HeaderMap, body: Request| async move { - server_fn_handler((), func.clone(), headers, body).await + move |headers: HeaderMap, RawQuery(query): RawQuery, body: Request| async move { + server_fn_handler((), func.clone(), headers, query, body).await } }) } @@ -299,8 +304,9 @@ async fn render_handler( /// A default handler for server functions. It will deserialize the request body, call the server function, and serialize the response. pub async fn server_fn_handler( server_context: impl Into, - function: Arc, + function: ServerFunction, headers: HeaderMap, + query: Option, req: Request, ) -> impl IntoResponse { let server_context = server_context.into(); @@ -317,7 +323,12 @@ pub async fn server_fn_handler( tokio::runtime::Runtime::new() .expect("couldn't spawn runtime") .block_on(async { - let resp = match function(server_context, &body).await { + let query = &query.unwrap_or_default().into(); + let data = match &function.encoding { + Encoding::Url | Encoding::Cbor => &body, + Encoding::GetJSON | Encoding::GetCBOR => query, + }; + let resp = match (function.trait_obj)(server_context, &data).await { Ok(serialized) => { // if this is Accept: application/json then send a serialized JSON response let accept_header = diff --git a/packages/server/src/adapters/salvo_adapter.rs b/packages/server/src/adapters/salvo_adapter.rs index ffe72bef5..402a0b0a0 100644 --- a/packages/server/src/adapters/salvo_adapter.rs +++ b/packages/server/src/adapters/salvo_adapter.rs @@ -50,7 +50,7 @@ //! } //! ``` -use std::{error::Error, sync::Arc}; +use std::error::Error; use hyper::{http::HeaderValue, StatusCode}; use salvo::{ @@ -58,14 +58,11 @@ use salvo::{ serve_static::{StaticDir, StaticFile}, Depot, FlowCtrl, Handler, Request, Response, Router, }; -use server_fn::{Payload, ServerFunctionRegistry}; +use server_fn::{Encoding, Payload, ServerFunctionRegistry}; use tokio::task::spawn_blocking; use crate::{ - prelude::DioxusServerContext, - render::SSRState, - serve_config::ServeConfig, - server_fn::{DioxusServerFnRegistry, ServerFnTraitObj}, + prelude::*, render::SSRState, serve_config::ServeConfig, server_fn::DioxusServerFnRegistry, }; /// A extension trait with utilities for integrating Dioxus with your Salvo router. @@ -79,7 +76,7 @@ pub trait DioxusRouterExt { /// use dioxus_server::prelude::*; /// /// struct ServerFunctionHandler { - /// server_fn: Arc, + /// server_fn: ServerFunction, /// } /// /// #[handler] @@ -112,7 +109,7 @@ pub trait DioxusRouterExt { fn register_server_fns_with_handler( self, server_fn_route: &'static str, - handler: impl Fn(Arc) -> H, + handler: impl Fn(ServerFunction) -> H, ) -> Self where H: Handler + 'static; @@ -187,7 +184,7 @@ impl DioxusRouterExt for Router { fn register_server_fns_with_handler( self, server_fn_route: &'static str, - mut handler: impl FnMut(Arc) -> H, + mut handler: impl FnMut(ServerFunction) -> H, ) -> Self where H: Handler + 'static, @@ -196,7 +193,14 @@ impl DioxusRouterExt for Router { 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}"); - router = router.push(Router::with_path(&full_route).post(handler(func))); + match func.encoding { + Encoding::Url | Encoding::Cbor => { + router = router.push(Router::with_path(&full_route).post(handler(func))); + } + Encoding::GetJSON | Encoding::GetCBOR => { + router = router.push(Router::with_path(&full_route).get(handler(func))); + } + } } router } @@ -288,15 +292,12 @@ impl Handler for SSRHandler

{ /// 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: Arc, + function: ServerFunction, } impl ServerFnHandler { /// Create a new server function handler with the given server context and server function. - pub fn new( - server_context: impl Into, - function: Arc, - ) -> Self { + pub fn new(server_context: impl Into, function: ServerFunction) -> Self { let server_context = server_context.into(); Self { server_context, @@ -324,12 +325,23 @@ impl ServerFnHandler { let (resp_tx, resp_rx) = tokio::sync::oneshot::channel(); let function = function.clone(); let server_context = server_context.clone(); + let query = req + .uri() + .query() + .unwrap_or_default() + .as_bytes() + .to_vec() + .into(); spawn_blocking({ move || { tokio::runtime::Runtime::new() .expect("couldn't spawn runtime") .block_on(async move { - let resp = function(server_context, &body).await; + let data = match &function.encoding { + Encoding::Url | Encoding::Cbor => &body, + Encoding::GetJSON | Encoding::GetCBOR => &query, + }; + let resp = (function.trait_obj)(server_context, data).await; resp_tx.send(resp).unwrap(); }) diff --git a/packages/server/src/adapters/warp_adapter.rs b/packages/server/src/adapters/warp_adapter.rs index 5041e294d..db88ac2b4 100644 --- a/packages/server/src/adapters/warp_adapter.rs +++ b/packages/server/src/adapters/warp_adapter.rs @@ -47,9 +47,12 @@ //! //! ``` -use std::{error::Error, sync::Arc}; +use crate::{ + prelude::*, render::SSRState, serve_config::ServeConfig, server_fn::DioxusServerFnRegistry, +}; -use server_fn::{Payload, ServerFunctionRegistry}; +use server_fn::{Encoding, Payload, ServerFunctionRegistry}; +use std::error::Error; use tokio::task::spawn_blocking; use warp::{ filters::BoxedFilter, @@ -58,13 +61,6 @@ use warp::{ path, Filter, Reply, }; -use crate::{ - prelude::DioxusServerContext, - render::SSRState, - serve_config::ServeConfig, - server_fn::{DioxusServerFnRegistry, ServerFnTraitObj}, -}; - /// 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 @@ -94,7 +90,7 @@ pub fn register_server_fns_with_handler( mut handler: H, ) -> BoxedFilter<(R,)> where - H: FnMut(String, Arc) -> F, + H: FnMut(String, ServerFunction) -> F, F: Filter + Send + Sync + 'static, F::Extract: Send, R: Reply + 'static, @@ -129,16 +125,48 @@ where /// ``` pub fn register_server_fns(server_fn_route: &'static str) -> BoxedFilter<(impl Reply,)> { register_server_fns_with_handler(server_fn_route, |full_route, func| { - path(full_route) - .and(warp::post()) + let func2 = func.clone(); + let func3 = func.clone(); + path(full_route.clone()) + .and(warp::filters::method::get()) .and(warp::header::headers_cloned()) + .and(warp::filters::query::raw()) .and(warp::body::bytes()) - .and_then(move |headers: HeaderMap, body| { + .and_then(move |headers, query, body| { let func = func.clone(); async move { - server_fn_handler(DioxusServerContext::default(), func, headers, body).await + server_fn_handler( + DioxusServerContext::default(), + func, + headers, + Some(query), + body, + ) + .await } }) + .or(path(full_route.clone()) + .and(warp::filters::method::get()) + .and(warp::header::headers_cloned()) + .and(warp::body::bytes()) + .and_then(move |headers, body| { + let func = func2.clone(); + async move { + server_fn_handler(DioxusServerContext::default(), func, headers, None, body) + .await + } + })) + .or(path(full_route) + .and(warp::filters::method::post()) + .and(warp::header::headers_cloned()) + .and(warp::body::bytes()) + .and_then(move |headers, body| { + let func = func3.clone(); + async move { + server_fn_handler(DioxusServerContext::default(), func, headers, None, body) + .await + } + })) }) } @@ -198,8 +226,9 @@ impl warp::reject::Reject for RecieveFailed {} /// A default handler for server functions. It will deserialize the request body, call the server function, and serialize the response. pub async fn server_fn_handler( server_context: impl Into, - function: Arc, + function: ServerFunction, headers: HeaderMap, + query: Option, body: Bytes, ) -> Result, warp::Rejection> { let server_context = server_context.into(); @@ -210,7 +239,12 @@ pub async fn server_fn_handler( tokio::runtime::Runtime::new() .expect("couldn't spawn runtime") .block_on(async { - let resp = match function(server_context, &body).await { + let query = &query.unwrap_or_default().into(); + let data = match &function.encoding { + Encoding::Url | Encoding::Cbor => &body, + Encoding::GetJSON | Encoding::GetCBOR => query, + }; + let resp = match (function.trait_obj)(server_context, &data).await { Ok(serialized) => { // if this is Accept: application/json then send a serialized JSON response let accept_header = diff --git a/packages/server/src/lib.rs b/packages/server/src/lib.rs index df2735cf4..525b636b3 100644 --- a/packages/server/src/lib.rs +++ b/packages/server/src/lib.rs @@ -30,7 +30,7 @@ pub mod prelude { pub use crate::server_context::DioxusServerContext; pub use crate::server_fn::ServerFn; #[cfg(feature = "ssr")] - pub use crate::server_fn::ServerFnTraitObj; + pub use crate::server_fn::{ServerFnTraitObj, ServerFunction}; pub use dioxus_server_macro::*; pub use server_fn::{self, ServerFn as _, ServerFnError}; } diff --git a/packages/server/src/server_fn.rs b/packages/server/src/server_fn.rs index 643f25c9f..da7311263 100644 --- a/packages/server/src/server_fn.rs +++ b/packages/server/src/server_fn.rs @@ -4,14 +4,14 @@ use crate::server_context::DioxusServerContext; /// A trait object for a function that be called on serializable arguments and returns a serializable result. pub type ServerFnTraitObj = server_fn::ServerFnTraitObj; +#[cfg(any(feature = "ssr", doc))] +/// A server function that can be called on serializable arguments and returns a serializable result. +pub type ServerFunction = server_fn::ServerFunction; + #[cfg(any(feature = "ssr", doc))] #[allow(clippy::type_complexity)] static REGISTERED_SERVER_FUNCTIONS: once_cell::sync::Lazy< - std::sync::Arc< - std::sync::RwLock< - std::collections::HashMap<&'static str, std::sync::Arc>, - >, - >, + std::sync::Arc>>, > = once_cell::sync::Lazy::new(Default::default); #[cfg(any(feature = "ssr", doc))] @@ -25,12 +25,19 @@ impl server_fn::ServerFunctionRegistry for DioxusServerFnRe fn register( url: &'static str, server_function: std::sync::Arc, + 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, server_function); + let prev = write.insert( + url, + ServerFunction { + trait_obj: server_function, + encoding, + }, + ); // if there was already a server function with this key, // return Err @@ -47,13 +54,28 @@ impl server_fn::ServerFunctionRegistry for DioxusServerFnRe } /// Returns the server function registered at the given URL, or `None` if no function is registered at that URL. - fn get(url: &str) -> Option> { + fn get(url: &str) -> Option { REGISTERED_SERVER_FUNCTIONS .read() .ok() .and_then(|fns| fns.get(url).cloned()) } + /// 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> { + REGISTERED_SERVER_FUNCTIONS + .read() + .ok() + .and_then(|fns| fns.get(url).map(|f| f.trait_obj.clone())) + } + + fn get_encoding(url: &str) -> Option { + REGISTERED_SERVER_FUNCTIONS + .read() + .ok() + .and_then(|fns| fns.get(url).map(|f| f.encoding.clone())) + } + /// Returns a list of all registered server functions. fn paths_registered() -> Vec<&'static str> { REGISTERED_SERVER_FUNCTIONS