update server functions commit

This commit is contained in:
Evan Almloff committed 2023-04-27 20:25:16 -05:00
1 parent 7193453fe0
commit 0726a7b08f
8 files changed
+138 -58

No files matched your search

+1 -1
View File
@@ -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
+1 -1
View File
@@ -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]
+3 -2
View File
@@ -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 `<form>` 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),
+25 -14
View File
@@ -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<S> {
fn register_server_fns_with_handler<H, T>(
self,
server_fn_route: &'static str,
handler: impl Fn(Arc<ServerFnTraitObj>) -> H,
handler: impl Fn(ServerFunction) -> H,
) -> Self
where
H: Handler<T, S>,
@@ -200,7 +198,7 @@ where
fn register_server_fns_with_handler<H, T>(
self,
server_fn_route: &'static str,
mut handler: impl FnMut(Arc<ServerFnTraitObj>) -> H,
mut handler: impl FnMut(ServerFunction) -> H,
) -> Self
where
H: Handler<T, S, Body>,
@@ -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<Body>| async move {
server_fn_handler((), func.clone(), headers, body).await
move |headers: HeaderMap, RawQuery(query): RawQuery, body: Request<Body>| async move {
server_fn_handler((), func.clone(), headers, query, body).await
}
})
}
@@ -299,8 +304,9 @@ async fn render_handler<P: Clone + Send + Sync + 'static>(
/// 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<DioxusServerContext>,
function: Arc<ServerFnTraitObj>,
function: ServerFunction,
headers: HeaderMap,
query: Option<String>,
req: Request<Body>,
) -> 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 =
+28 -16
View File
@@ -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<ServerFnTraitObj>,
/// server_fn: ServerFunction,
/// }
///
/// #[handler]
@@ -112,7 +109,7 @@ pub trait DioxusRouterExt {
fn register_server_fns_with_handler<H>(
self,
server_fn_route: &'static str,
handler: impl Fn(Arc<ServerFnTraitObj>) -> 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<H>(
self,
server_fn_route: &'static str,
mut handler: impl FnMut(Arc<ServerFnTraitObj>) -> 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<P: Clone + Send + Sync + 'static> Handler for SSRHandler<P> {
/// 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<ServerFnTraitObj>,
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<DioxusServerContext>,
function: Arc<ServerFnTraitObj>,
) -> Self {
pub fn new(server_context: impl Into<DioxusServerContext>, 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();
})
+50 -16
View File
@@ -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<H, F, R>(
mut handler: H,
) -> BoxedFilter<(R,)>
where
H: FnMut(String, Arc<ServerFnTraitObj>) -> F,
H: FnMut(String, ServerFunction) -> F,
F: Filter<Extract = (R,), Error = warp::Rejection> + 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<DioxusServerContext>,
function: Arc<ServerFnTraitObj>,
function: ServerFunction,
headers: HeaderMap,
query: Option<String>,
body: Bytes,
) -> Result<Box<dyn warp::Reply>, 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 =
+1 -1
View File
@@ -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};
}
+29 -7
View File
@@ -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<DioxusServerContext>;
#[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<DioxusServerContext>;
#[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<ServerFnTraitObj>>,
>,
>,
std::sync::Arc<std::sync::RwLock<std::collections::HashMap<&'static str, ServerFunction>>>,
> = once_cell::sync::Lazy::new(Default::default);
#[cfg(any(feature = "ssr", doc))]
@@ -25,12 +25,19 @@ impl server_fn::ServerFunctionRegistry<DioxusServerContext> for DioxusServerFnRe
fn register(
url: &'static str,
server_function: std::sync::Arc<ServerFnTraitObj>,
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<DioxusServerContext> 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<std::sync::Arc<ServerFnTraitObj>> {
fn get(url: &str) -> Option<ServerFunction> {
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<std::sync::Arc<ServerFnTraitObj>> {
REGISTERED_SERVER_FUNCTIONS
.read()
.ok()
.and_then(|fns| fns.get(url).map(|f| f.trait_obj.clone()))
}
fn get_encoding(url: &str) -> Option<server_fn::Encoding> {
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