add extractors to server-fn

This commit is contained in:
Evan Almloff 2023-07-06 17:54:05 -07:00
parent 523be82965
commit 4f6e6a7c0d
12 changed files with 260 additions and 139 deletions

View file

@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "ssr", "fullstack"]
[dependencies] [dependencies]
# server functions # server functions
server_fn = { git = "https://github.com/leptos-rs/leptos", rev = "15a4e54435eb5a539afb75891292bcccd2cc8e85", default-features = false, features = ["stable"] } server_fn = { git = "https://github.com/leptos-rs/leptos", rev = "6b90e1babd425c9a324181c86e3fd1b942c9b10f", default-features = false }
dioxus_server_macro = { path = "server-macro" } dioxus_server_macro = { path = "server-macro" }
# warp # warp
@ -53,6 +53,9 @@ postcard = { version = "1.0.4", features = ["use-std"] }
yazi = "0.1.5" yazi = "0.1.5"
base64 = "0.21.0" base64 = "0.21.0"
pin-project = "1.1.2"
async-trait = "0.1.71"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
dioxus-hot-reload = { workspace = true } dioxus-hot-reload = { workspace = true }

View file

@ -26,12 +26,11 @@ fn app(cx: Scope<AppProps>) -> Element {
button { button {
onclick: move |_| { onclick: move |_| {
to_owned![text]; to_owned![text];
let sc = cx.sc();
async move { async move {
if let Ok(data) = get_server_data().await { if let Ok(data) = get_server_data().await {
println!("Client received: {}", data); println!("Client received: {}", data);
text.set(data.clone()); text.set(data.clone());
post_server_data(sc, data).await.unwrap(); post_server_data(data).await.unwrap();
} }
} }
}, },
@ -42,8 +41,9 @@ fn app(cx: Scope<AppProps>) -> Element {
} }
#[server(PostServerData)] #[server(PostServerData)]
async fn post_server_data(cx: DioxusServerContext, data: String) -> Result<(), ServerFnError> { async fn post_server_data(data: String) -> Result<(), ServerFnError> {
// The server context contains information about the current request and allows you to modify the response. // The server context contains information about the current request and allows you to modify the response.
let cx = server_context();
cx.response_headers_mut() cx.response_headers_mut()
.insert("Set-Cookie", "foo=bar".parse().unwrap()); .insert("Set-Cookie", "foo=bar".parse().unwrap());
println!("Server received: {}", data); println!("Server received: {}", data);

View file

@ -7,7 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
quote = "1.0.26" quote = "1.0.26"
server_fn_macro = { git = "https://github.com/leptos-rs/leptos", rev = "15a4e54435eb5a539afb75891292bcccd2cc8e85", features = ["stable"] } server_fn_macro = { git = "https://github.com/leptos-rs/leptos", rev = "6b90e1babd425c9a324181c86e3fd1b942c9b10f" }
syn = { version = "2", features = ["full"] } syn = { version = "2", features = ["full"] }
[lib] [lib]

View file

@ -54,15 +54,11 @@ use server_fn_macro::*;
/// or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client. /// or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client.
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream { pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
let context = ServerContext {
ty: syn::parse_quote!(DioxusServerContext),
path: syn::parse_quote!(::dioxus_fullstack::prelude::DioxusServerContext),
};
match server_macro_impl( match server_macro_impl(
args.into(), args.into(),
s.into(), s.into(),
syn::parse_quote!(::dioxus_fullstack::prelude::ServerFnTraitObj), syn::parse_quote!(::dioxus_fullstack::prelude::ServerFnTraitObj),
Some(context), None,
Some(syn::parse_quote!(::dioxus_fullstack::prelude::server_fn)), Some(syn::parse_quote!(::dioxus_fullstack::prelude::server_fn)),
) { ) {
Err(e) => e.to_compile_error().into(), Err(e) => e.to_compile_error().into(),

View file

@ -64,8 +64,8 @@ use axum::{
Router, Router,
}; };
use server_fn::{Encoding, Payload, ServerFunctionRegistry}; use server_fn::{Encoding, Payload, ServerFunctionRegistry};
use std::error::Error;
use std::sync::Arc; use std::sync::Arc;
use std::sync::RwLock;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
use crate::{ use crate::{
@ -91,7 +91,7 @@ pub trait DioxusRouterExt<S> {
/// .register_server_fns_with_handler("", |func| { /// .register_server_fns_with_handler("", |func| {
/// move |req: Request<Body>| async move { /// move |req: Request<Body>| async move {
/// let (parts, body) = req.into_parts(); /// let (parts, body) = req.into_parts();
/// let parts: Arc<RequestParts> = Arc::new(parts.into()); /// let parts: Arc<http::request::Parts> = Arc::new(parts.into());
/// let server_context = DioxusServerContext::new(parts.clone()); /// let server_context = DioxusServerContext::new(parts.clone());
/// server_fn_handler(server_context, func.clone(), parts, body).await /// server_fn_handler(server_context, func.clone(), parts, body).await
/// } /// }
@ -105,7 +105,7 @@ pub trait DioxusRouterExt<S> {
fn register_server_fns_with_handler<H, T>( fn register_server_fns_with_handler<H, T>(
self, self,
server_fn_route: &'static str, server_fn_route: &'static str,
handler: impl FnMut(server_fn::ServerFnTraitObj<DioxusServerContext>) -> H, handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
) -> Self ) -> Self
where where
H: Handler<T, S>, H: Handler<T, S>,
@ -230,7 +230,7 @@ where
fn register_server_fns_with_handler<H, T>( fn register_server_fns_with_handler<H, T>(
self, self,
server_fn_route: &'static str, server_fn_route: &'static str,
mut handler: impl FnMut(server_fn::ServerFnTraitObj<DioxusServerContext>) -> H, mut handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
) -> Self ) -> Self
where where
H: Handler<T, S, Body>, H: Handler<T, S, Body>,
@ -257,7 +257,7 @@ where
self.register_server_fns_with_handler(server_fn_route, |func| { self.register_server_fns_with_handler(server_fn_route, |func| {
move |req: Request<Body>| async move { move |req: Request<Body>| async move {
let (parts, body) = req.into_parts(); let (parts, body) = req.into_parts();
let parts: Arc<RequestParts> = Arc::new(parts.into()); let parts: Arc<RwLock<http::request::Parts>> = Arc::new(parts.into());
let server_context = DioxusServerContext::new(parts.clone()); let server_context = DioxusServerContext::new(parts.clone());
server_fn_handler(server_context, func.clone(), parts, body).await server_fn_handler(server_context, func.clone(), parts, body).await
} }
@ -364,16 +364,11 @@ async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
request: Request<Body>, request: Request<Body>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let (parts, _) = request.into_parts(); let (parts, _) = request.into_parts();
let parts: Arc<RequestParts> = Arc::new(parts.into());
let url = parts.uri.path_and_query().unwrap().to_string(); let url = parts.uri.path_and_query().unwrap().to_string();
let parts: Arc<RwLock<http::request::Parts>> = Arc::new(RwLock::new(parts.into()));
let server_context = DioxusServerContext::new(parts.clone()); let server_context = DioxusServerContext::new(parts.clone());
match ssr_state match ssr_state.render(url, &cfg, &server_context).await {
.render(url, &cfg, |vdom| {
vdom.base_scope().provide_context(server_context.clone());
})
.await
{
Ok(rendered) => { Ok(rendered) => {
let crate::render::RenderResponse { html, freshness } = rendered; let crate::render::RenderResponse { html, freshness } = rendered;
let mut response = axum::response::Html::from(html).into_response(); let mut response = axum::response::Html::from(html).into_response();
@ -392,8 +387,8 @@ async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
/// A default handler for server functions. It will deserialize the request, call the server function, and serialize the response. /// A default handler for server functions. It will deserialize the request, call the server function, and serialize the response.
pub async fn server_fn_handler( pub async fn server_fn_handler(
server_context: DioxusServerContext, server_context: DioxusServerContext,
function: server_fn::ServerFnTraitObj<DioxusServerContext>, function: server_fn::ServerFnTraitObj<()>,
parts: Arc<RequestParts>, parts: Arc<RwLock<http::request::Parts>>,
body: Body, body: Body,
) -> impl IntoResponse { ) -> impl IntoResponse {
let body = hyper::body::to_bytes(body).await; let body = hyper::body::to_bytes(body).await;
@ -403,7 +398,13 @@ pub async fn server_fn_handler(
// 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 // 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 (resp_tx, resp_rx) = tokio::sync::oneshot::channel(); let (resp_tx, resp_rx) = tokio::sync::oneshot::channel();
let query_string = parts.uri.query().unwrap_or_default().to_string(); let query_string = parts
.read()
.unwrap()
.uri
.query()
.unwrap_or_default()
.to_string();
spawn_blocking({ spawn_blocking({
move || { move || {
tokio::runtime::Runtime::new() tokio::runtime::Runtime::new()
@ -414,9 +415,13 @@ pub async fn server_fn_handler(
Encoding::Url | Encoding::Cbor => &body, Encoding::Url | Encoding::Cbor => &body,
Encoding::GetJSON | Encoding::GetCBOR => query, Encoding::GetJSON | Encoding::GetCBOR => query,
}; };
let resp = match function.call(server_context.clone(), data).await { let server_function_future = function.call((), data);
let server_function_future =
ProvideServerContext::new(server_function_future, server_context.clone());
let resp = match server_function_future.await {
Ok(serialized) => { Ok(serialized) => {
// if this is Accept: application/json then send a serialized JSON response // if this is Accept: application/json then send a serialized JSON response
let parts = parts.read().unwrap();
let accept_header = parts let accept_header = parts
.headers .headers
.get("Accept") .get("Accept")
@ -463,7 +468,7 @@ pub async fn server_fn_handler(
resp_rx.await.unwrap() resp_rx.await.unwrap()
} }
fn report_err<E: Error>(e: E) -> Response<BoxBody> { fn report_err<E: std::fmt::Display>(e: E) -> Response<BoxBody> {
Response::builder() Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR) .status(StatusCode::INTERNAL_SERVER_ERROR)
.body(body::boxed(format!("Error: {}", e))) .body(body::boxed(format!("Error: {}", e)))

View file

@ -75,7 +75,7 @@ pub trait DioxusRouterExt {
/// use dioxus_fullstack::prelude::*; /// use dioxus_fullstack::prelude::*;
/// ///
/// struct ServerFunctionHandler { /// struct ServerFunctionHandler {
/// server_fn: server_fn::ServerFnTraitObj<DioxusServerContext>, /// server_fn: server_fn::ServerFnTraitObj<()>,
/// } /// }
/// ///
/// #[handler] /// #[handler]
@ -108,7 +108,7 @@ pub trait DioxusRouterExt {
fn register_server_fns_with_handler<H>( fn register_server_fns_with_handler<H>(
self, self,
server_fn_route: &'static str, server_fn_route: &'static str,
handler: impl Fn(server_fn::ServerFnTraitObj<DioxusServerContext>) -> H, handler: impl Fn(server_fn::ServerFnTraitObj<()>) -> H,
) -> Self ) -> Self
where where
H: Handler + 'static; H: Handler + 'static;
@ -202,7 +202,7 @@ impl DioxusRouterExt for Router {
fn register_server_fns_with_handler<H>( fn register_server_fns_with_handler<H>(
self, self,
server_fn_route: &'static str, server_fn_route: &'static str,
mut handler: impl FnMut(server_fn::ServerFnTraitObj<DioxusServerContext>) -> H, mut handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
) -> Self ) -> Self
where where
H: Handler + 'static, H: Handler + 'static,
@ -333,9 +333,7 @@ impl<P: Clone + serde::Serialize + Send + Sync + 'static> Handler for SSRHandler
let server_context = DioxusServerContext::new(parts); let server_context = DioxusServerContext::new(parts);
match renderer_pool match renderer_pool
.render(route, &self.cfg, |vdom| { .render(route, &self.cfg, &server_context)
vdom.base_scope().provide_context(server_context.clone());
})
.await .await
{ {
Ok(rendered) => { Ok(rendered) => {
@ -357,14 +355,14 @@ impl<P: Clone + serde::Serialize + Send + Sync + 'static> Handler for SSRHandler
/// A default handler for server functions. It will deserialize the request body, call the server function, and serialize the response. /// A default handler for server functions. It will deserialize the request body, call the server function, and serialize the response.
pub struct ServerFnHandler { pub struct ServerFnHandler {
server_context: DioxusServerContext, server_context: DioxusServerContext,
function: server_fn::ServerFnTraitObj<DioxusServerContext>, function: server_fn::ServerFnTraitObj<()>,
} }
impl ServerFnHandler { impl ServerFnHandler {
/// Create a new server function handler with the given server context and server function. /// Create a new server function handler with the given server context and server function.
pub fn new( pub fn new(
server_context: impl Into<DioxusServerContext>, server_context: impl Into<DioxusServerContext>,
function: server_fn::ServerFnTraitObj<DioxusServerContext>, function: server_fn::ServerFnTraitObj<()>,
) -> Self { ) -> Self {
let server_context = server_context.into(); let server_context = server_context.into();
Self { Self {
@ -413,7 +411,12 @@ impl ServerFnHandler {
Encoding::Url | Encoding::Cbor => &body, Encoding::Url | Encoding::Cbor => &body,
Encoding::GetJSON | Encoding::GetCBOR => &query, Encoding::GetJSON | Encoding::GetCBOR => &query,
}; };
let resp = function.call(server_context, data).await; let server_function_future = function.call((), data);
let server_function_future = ProvideServerContext::new(
server_function_future,
server_context.clone(),
);
let resp = server_function_future.await;
resp_tx.send(resp).unwrap(); resp_tx.send(resp).unwrap();
}) })

View file

@ -91,7 +91,7 @@ pub fn register_server_fns_with_handler<H, F, R>(
mut handler: H, mut handler: H,
) -> BoxedFilter<(R,)> ) -> BoxedFilter<(R,)>
where where
H: FnMut(String, server_fn::ServerFnTraitObj<DioxusServerContext>) -> F, H: FnMut(String, server_fn::ServerFnTraitObj<()>) -> F,
F: Filter<Extract = (R,), Error = warp::Rejection> + Send + Sync + 'static, F: Filter<Extract = (R,), Error = warp::Rejection> + Send + Sync + 'static,
F::Extract: Send, F::Extract: Send,
R: Reply + 'static, R: Reply + 'static,
@ -192,12 +192,7 @@ pub fn render_ssr<P: Clone + serde::Serialize + Send + Sync + 'static>(
async move { async move {
let server_context = DioxusServerContext::new(parts); let server_context = DioxusServerContext::new(parts);
match renderer match renderer.render(route, &cfg, &server_context).await {
.render(route, &cfg, |vdom| {
vdom.base_scope().provide_context(server_context.clone());
})
.await
{
Ok(rendered) => { Ok(rendered) => {
let crate::render::RenderResponse { html, freshness } = rendered; let crate::render::RenderResponse { html, freshness } = rendered;
@ -270,8 +265,8 @@ 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. /// 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( pub async fn server_fn_handler(
server_context: impl Into<DioxusServerContext>, server_context: impl Into<(DioxusServerContext)>,
function: server_fn::ServerFnTraitObj<DioxusServerContext>, function: server_fn::ServerFnTraitObj<()>,
parts: RequestParts, parts: RequestParts,
body: Bytes, body: Bytes,
) -> Result<Box<dyn warp::Reply>, warp::Rejection> { ) -> Result<Box<dyn warp::Reply>, warp::Rejection> {
@ -299,7 +294,10 @@ pub async fn server_fn_handler(
Encoding::Url | Encoding::Cbor => &body, Encoding::Url | Encoding::Cbor => &body,
Encoding::GetJSON | Encoding::GetCBOR => &query, Encoding::GetJSON | Encoding::GetCBOR => &query,
}; };
let resp = match function.call(server_context.clone(), data).await { let server_function_future = function.call((), data);
let server_function_future =
ProvideServerContext::new(server_function_future, server_context.clone());
let resp = match server_function_future.await {
Ok(serialized) => { Ok(serialized) => {
// if this is Accept: application/json then send a serialized JSON response // if this is Accept: application/json then send a serialized JSON response
let accept_header = parts let accept_header = parts

View file

@ -18,6 +18,7 @@ pub mod launch;
mod render; mod render;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
mod serve_config; mod serve_config;
#[cfg(feature = "ssr")]
mod server_context; mod server_context;
mod server_fn; mod server_fn;
@ -38,8 +39,11 @@ pub mod prelude {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
pub use crate::serve_config::{ServeConfig, ServeConfigBuilder}; pub use crate::serve_config::{ServeConfig, ServeConfigBuilder};
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
pub use crate::server_context::RequestParts; pub use crate::server_context::Axum;
pub use crate::server_context::{DioxusServerContext, HasServerContext}; #[cfg(feature = "ssr")]
pub use crate::server_context::{
server_context, DioxusServerContext, FromServerContext, ProvideServerContext,
};
pub use crate::server_fn::DioxusServerFn; pub use crate::server_fn::DioxusServerFn;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
pub use crate::server_fn::{ServerFnTraitObj, ServerFunction}; pub use crate::server_fn::{ServerFnTraitObj, ServerFunction};

View file

@ -9,7 +9,7 @@ use dioxus_ssr::{
}; };
use serde::Serialize; use serde::Serialize;
use crate::prelude::*; use crate::{prelude::*, server_context::with_server_context};
use dioxus::prelude::*; use dioxus::prelude::*;
enum SsrRendererPool { enum SsrRendererPool {
@ -25,15 +25,17 @@ impl SsrRendererPool {
component: Component<P>, component: Component<P>,
props: P, props: P,
to: &mut String, to: &mut String,
modify_vdom: impl FnOnce(&mut VirtualDom), server_context: &DioxusServerContext,
) -> Result<RenderFreshness, dioxus_ssr::incremental::IncrementalRendererError> { ) -> Result<RenderFreshness, dioxus_ssr::incremental::IncrementalRendererError> {
let wrapper = FullstackRenderer { cfg }; let wrapper = FullstackRenderer { cfg };
match self { match self {
Self::Renderer(pool) => { Self::Renderer(pool) => {
let server_context = Box::new(server_context.clone());
let mut vdom = VirtualDom::new_with_props(component, props); let mut vdom = VirtualDom::new_with_props(component, props);
modify_vdom(&mut vdom);
with_server_context(server_context, || {
let _ = vdom.rebuild(); let _ = vdom.rebuild();
});
let mut renderer = pool.pull(pre_renderer); let mut renderer = pool.pull(pre_renderer);
@ -48,7 +50,19 @@ impl SsrRendererPool {
let mut renderer = let mut renderer =
pool.pull(|| incremental_pre_renderer(cfg.incremental.as_ref().unwrap())); pool.pull(|| incremental_pre_renderer(cfg.incremental.as_ref().unwrap()));
Ok(renderer Ok(renderer
.render_to_string(route, component, props, to, modify_vdom, &wrapper) .render_to_string(
route,
component,
props,
to,
|vdom| {
let server_context = Box::new(server_context.clone());
with_server_context(server_context, || {
let _ = vdom.rebuild();
});
},
&wrapper,
)
.await?) .await?)
} }
} }
@ -86,7 +100,7 @@ impl SSRState {
&'a self, &'a self,
route: String, route: String,
cfg: &'a ServeConfig<P>, cfg: &'a ServeConfig<P>,
modify_vdom: impl FnOnce(&mut VirtualDom) + Send + 'a, server_context: &'a DioxusServerContext,
) -> impl std::future::Future< ) -> impl std::future::Future<
Output = Result<RenderResponse, dioxus_ssr::incremental::IncrementalRendererError>, Output = Result<RenderResponse, dioxus_ssr::incremental::IncrementalRendererError>,
> + Send > + Send
@ -97,7 +111,7 @@ impl SSRState {
let freshness = self let freshness = self
.renderers .renderers
.render_to(cfg, route, *app, props.clone(), &mut html, modify_vdom) .render_to(cfg, route, *app, props.clone(), &mut html, server_context)
.await?; .await?;
Ok(RenderResponse { html, freshness }) Ok(RenderResponse { html, freshness })

View file

@ -1,28 +1,6 @@
use dioxus::prelude::ScopeState; pub use server_fn_impl::*;
use std::sync::Arc;
/// A trait for an object that contains a server context use std::sync::RwLock;
pub trait HasServerContext {
/// Get the server context from the state
fn server_context(&self) -> DioxusServerContext;
/// A shortcut for `self.server_context()`
fn sc(&self) -> DioxusServerContext {
self.server_context()
}
}
impl HasServerContext for &ScopeState {
fn server_context(&self) -> DioxusServerContext {
#[cfg(feature = "ssr")]
{
self.consume_context().expect("No server context found")
}
#[cfg(not(feature = "ssr"))]
{
DioxusServerContext {}
}
}
}
/// A shared context for server functions that contains infomation about the request and middleware state. /// A shared context for server functions that contains infomation 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. /// 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.
@ -30,34 +8,24 @@ impl HasServerContext for &ScopeState {
/// 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. Instead use the `HasServerContext` trait to get the server context from the scope.
#[derive(Clone)] #[derive(Clone)]
pub struct DioxusServerContext { pub struct DioxusServerContext {
#[cfg(feature = "ssr")]
shared_context: std::sync::Arc< shared_context: std::sync::Arc<
std::sync::RwLock<anymap::Map<dyn anymap::any::Any + Send + Sync + 'static>>, std::sync::RwLock<anymap::Map<dyn anymap::any::Any + Send + Sync + 'static>>,
>, >,
#[cfg(feature = "ssr")]
headers: std::sync::Arc<std::sync::RwLock<hyper::header::HeaderMap>>, headers: std::sync::Arc<std::sync::RwLock<hyper::header::HeaderMap>>,
#[cfg(feature = "ssr")] pub(crate) parts: Arc<RwLock<http::request::Parts>>,
pub(crate) parts: std::sync::Arc<RequestParts>,
} }
#[allow(clippy::derivable_impls)] #[allow(clippy::derivable_impls)]
impl Default for DioxusServerContext { impl Default for DioxusServerContext {
fn default() -> Self { fn default() -> Self {
Self { Self {
#[cfg(feature = "ssr")]
shared_context: std::sync::Arc::new(std::sync::RwLock::new(anymap::Map::new())), shared_context: std::sync::Arc::new(std::sync::RwLock::new(anymap::Map::new())),
#[cfg(feature = "ssr")]
headers: Default::default(), headers: Default::default(),
#[cfg(feature = "ssr")] parts: std::sync::Arc::new(RwLock::new(http::request::Request::new(()).into_parts().0)),
parts: Default::default(),
} }
} }
} }
#[cfg(feature = "ssr")]
pub use server_fn_impl::*;
#[cfg(feature = "ssr")]
mod server_fn_impl { mod server_fn_impl {
use super::*; use super::*;
use std::sync::LockResult; use std::sync::LockResult;
@ -68,7 +36,7 @@ mod server_fn_impl {
impl DioxusServerContext { impl DioxusServerContext {
/// Create a new server context from a request /// Create a new server context from a request
pub fn new(parts: impl Into<Arc<RequestParts>>) -> Self { pub fn new(parts: impl Into<Arc<RwLock<http::request::Parts>>>) -> Self {
Self { Self {
parts: parts.into(), parts: parts.into(),
shared_context: Arc::new(RwLock::new(SendSyncAnyMap::new())), shared_context: Arc::new(RwLock::new(SendSyncAnyMap::new())),
@ -126,35 +94,170 @@ mod server_fn_impl {
/// Get the request that triggered: /// Get the request that triggered:
/// - The initial SSR render if called from a ScopeState or ServerFn /// - 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 /// - The server function to be called if called from a server function after the initial render
pub fn request_parts(&self) -> &RequestParts { pub fn request_parts(
&self.parts &self,
} ) -> std::sync::LockResult<RwLockReadGuard<'_, http::request::Parts>> {
self.parts.read()
} }
/// Associated parts of an HTTP Request /// Get the request that triggered:
#[derive(Debug, Default)] /// - The initial SSR render if called from a ScopeState or ServerFn
pub struct RequestParts { /// - The server function to be called if called from a server function after the initial render
/// The request's method pub fn request_parts_mut(
pub method: http::Method, &self,
/// The request's URI ) -> std::sync::LockResult<RwLockWriteGuard<'_, http::request::Parts>> {
pub uri: http::Uri, self.parts.write()
/// The request's version
pub version: http::Version,
/// The request's headers
pub headers: http::HeaderMap<http::HeaderValue>,
/// The request's extensions
pub extensions: http::Extensions,
} }
impl From<http::request::Parts> for RequestParts { /// Extract some part from the request
fn from(parts: http::request::Parts) -> Self { pub async fn extract<R: std::error::Error, T: FromServerContext<Rejection = R>>(
Self { &self,
method: parts.method, ) -> Result<T, R> {
uri: parts.uri, T::from_request(self).await
version: parts.version,
headers: parts.headers,
extensions: parts.extensions,
}
} }
} }
} }
std::thread_local! {
static SERVER_CONTEXT: std::cell::RefCell<Box<DioxusServerContext>> = std::cell::RefCell::new(Box::new(DioxusServerContext::default() ));
}
/// Get information about the current server request.
///
/// This function will only provide the current server context if it is called from a server function.
pub fn server_context() -> DioxusServerContext {
SERVER_CONTEXT.with(|ctx| *ctx.borrow_mut().clone())
}
pub(crate) fn with_server_context<O>(
context: Box<DioxusServerContext>,
f: impl FnOnce() -> O,
) -> (O, Box<DioxusServerContext>) {
// before polling the future, we need to set the context
let prev_context = SERVER_CONTEXT.with(|ctx| ctx.replace(context));
// poll the future, which may call server_context()
let result = f();
// after polling the future, we need to restore the context
(result, SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context)))
}
/// A future that provides the server context to the inner future
#[pin_project::pin_project]
pub struct ProvideServerContext<F: std::future::Future> {
context: Option<Box<DioxusServerContext>>,
#[pin]
f: F,
}
impl<F: std::future::Future> ProvideServerContext<F> {
/// Create a new future that provides the server context to the inner future
pub fn new(f: F, context: DioxusServerContext) -> Self {
Self {
context: Some(Box::new(context)),
f,
}
}
}
impl<F: std::future::Future> std::future::Future for ProvideServerContext<F> {
type Output = F::Output;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
let this = self.project();
let context = this.context.take().unwrap();
let (result, context) = with_server_context(context, || this.f.poll(cx));
*this.context = Some(context);
result
}
}
/// A trait for extracting types from the server context
#[async_trait::async_trait(?Send)]
pub trait FromServerContext: Sized {
/// The error type returned when extraction fails. This type must implement `IntoResponse`.
type Rejection: std::error::Error;
/// Extract this type from the server context.
async fn from_request(req: &DioxusServerContext) -> Result<Self, Self::Rejection>;
}
/// A type was not found in the server context
pub struct NotFoundInServerContext<T: 'static>(std::marker::PhantomData<T>);
impl<T: 'static> std::fmt::Debug for NotFoundInServerContext<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let type_name = std::any::type_name::<T>();
write!(f, "`{type_name}` not found in server context")
}
}
impl<T: 'static> std::fmt::Display for NotFoundInServerContext<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let type_name = std::any::type_name::<T>();
write!(f, "`{type_name}` not found in server context")
}
}
impl<T: 'static> std::error::Error for NotFoundInServerContext<T> {}
pub struct FromContext<T: std::marker::Send + std::marker::Sync + Clone + 'static>(pub(crate) T);
#[async_trait::async_trait(?Send)]
impl<T: Send + Sync + Clone + 'static> FromServerContext for FromContext<T> {
type Rejection = NotFoundInServerContext<T>;
async fn from_request(req: &DioxusServerContext) -> Result<Self, Self::Rejection> {
Ok(Self(req.clone().get::<T>().ok_or_else(|| {
NotFoundInServerContext::<T>(std::marker::PhantomData::<T>)
})?))
}
}
#[cfg(feature = "axum")]
/// An adapter for axum extractors for the server context
pub struct Axum<
I: axum::extract::FromRequestParts<(), Rejection = R>,
R: axum::response::IntoResponse + std::error::Error,
>(pub(crate) I, std::marker::PhantomData<R>);
impl<
I: axum::extract::FromRequestParts<(), Rejection = R>,
R: axum::response::IntoResponse + std::error::Error,
> std::ops::Deref for Axum<I, R>
{
type Target = I;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<
I: axum::extract::FromRequestParts<(), Rejection = R>,
R: axum::response::IntoResponse + std::error::Error,
> std::ops::DerefMut for Axum<I, R>
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(feature = "axum")]
#[async_trait::async_trait(?Send)]
impl<
I: axum::extract::FromRequestParts<(), Rejection = R>,
R: axum::response::IntoResponse + std::error::Error,
> FromServerContext for Axum<I, R>
{
type Rejection = R;
async fn from_request(req: &DioxusServerContext) -> Result<Self, Self::Rejection> {
Ok(Self(
I::from_request_parts(&mut *req.request_parts_mut().unwrap(), &()).await?,
std::marker::PhantomData,
))
}
}

View file

@ -1,13 +1,11 @@
use crate::server_context::DioxusServerContext;
#[cfg(any(feature = "ssr", doc))] #[cfg(any(feature = "ssr", doc))]
#[derive(Clone)] #[derive(Clone)]
/// A trait object for a function that be called on serializable arguments and returns a serializable result. /// A trait object for a function that be called on serializable arguments and returns a serializable result.
pub struct ServerFnTraitObj(server_fn::ServerFnTraitObj<DioxusServerContext>); pub struct ServerFnTraitObj(server_fn::ServerFnTraitObj<()>);
#[cfg(any(feature = "ssr", doc))] #[cfg(any(feature = "ssr", doc))]
impl std::ops::Deref for ServerFnTraitObj { impl std::ops::Deref for ServerFnTraitObj {
type Target = server_fn::ServerFnTraitObj<DioxusServerContext>; type Target = server_fn::ServerFnTraitObj<()>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
@ -33,9 +31,7 @@ impl ServerFnTraitObj {
} }
/// Create a new `ServerFnTraitObj` from a `server_fn::ServerFnTraitObj`. /// Create a new `ServerFnTraitObj` from a `server_fn::ServerFnTraitObj`.
pub const fn from_generic_server_fn( pub const fn from_generic_server_fn(server_fn: server_fn::ServerFnTraitObj<()>) -> Self {
server_fn: server_fn::ServerFnTraitObj<DioxusServerContext>,
) -> Self {
Self(server_fn) Self(server_fn)
} }
} }
@ -45,7 +41,7 @@ server_fn::inventory::collect!(ServerFnTraitObj);
#[cfg(any(feature = "ssr", doc))] #[cfg(any(feature = "ssr", doc))]
/// A server function that can be called on serializable arguments and returns a serializable result. /// A server function that can be called on serializable arguments and returns a serializable result.
pub type ServerFunction = server_fn::SerializedFnTraitObj<DioxusServerContext>; pub type ServerFunction = server_fn::SerializedFnTraitObj<()>;
#[cfg(any(feature = "ssr", doc))] #[cfg(any(feature = "ssr", doc))]
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
@ -64,7 +60,7 @@ static REGISTERED_SERVER_FUNCTIONS: once_cell::sync::Lazy<
pub struct DioxusServerFnRegistry; pub struct DioxusServerFnRegistry;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
impl server_fn::ServerFunctionRegistry<DioxusServerContext> for DioxusServerFnRegistry { impl server_fn::ServerFunctionRegistry<()> for DioxusServerFnRegistry {
type Error = ServerRegistrationFnError; type Error = ServerRegistrationFnError;
fn register_explicit( fn register_explicit(
@ -105,7 +101,7 @@ 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. /// 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<DioxusServerContext>> { fn get(url: &str) -> Option<server_fn::ServerFnTraitObj<()>> {
REGISTERED_SERVER_FUNCTIONS REGISTERED_SERVER_FUNCTIONS
.read() .read()
.ok() .ok()
@ -113,7 +109,7 @@ 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. /// 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<DioxusServerContext>> { fn get_trait_obj(url: &str) -> Option<server_fn::ServerFnTraitObj<()>> {
Self::get(url) Self::get(url)
} }
@ -155,7 +151,7 @@ pub enum ServerRegistrationFnError {
/// can be queried on the server for routing purposes by calling [server_fn::ServerFunctionRegistry::get]. /// 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. /// 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<DioxusServerContext> { pub trait DioxusServerFn: server_fn::ServerFn<()> {
/// Registers the server function, allowing the client to query it by URL. /// Registers the server function, allowing the client to query it by URL.
#[cfg(any(feature = "ssr", doc))] #[cfg(any(feature = "ssr", doc))]
fn register_explicit() -> Result<(), server_fn::ServerFnError> { fn register_explicit() -> Result<(), server_fn::ServerFnError> {
@ -163,4 +159,4 @@ pub trait DioxusServerFn: server_fn::ServerFn<DioxusServerContext> {
} }
} }
impl<T> DioxusServerFn for T where T: server_fn::ServerFn<DioxusServerContext> {} impl<T> DioxusServerFn for T where T: server_fn::ServerFn<()> {}

View file

@ -170,7 +170,7 @@ impl IncrementalRenderer {
comp: fn(Scope<P>) -> Element, comp: fn(Scope<P>) -> Element,
props: P, props: P,
output: &'a mut (impl AsyncWrite + Unpin + Send), output: &'a mut (impl AsyncWrite + Unpin + Send),
modify_vdom: impl FnOnce(&mut VirtualDom), rebuild_with: impl FnOnce(&mut VirtualDom),
renderer: &'a R, renderer: &'a R,
) -> impl std::future::Future<Output = Result<RenderFreshness, IncrementalRendererError>> + 'a + Send ) -> impl std::future::Future<Output = Result<RenderFreshness, IncrementalRendererError>> + 'a + Send
{ {
@ -179,8 +179,7 @@ impl IncrementalRenderer {
let result2; let result2;
{ {
let mut vdom = VirtualDom::new_with_props(comp, props); let mut vdom = VirtualDom::new_with_props(comp, props);
modify_vdom(&mut vdom); rebuild_with(&mut vdom);
let _ = vdom.rebuild();
result_1 = renderer.render_before_body(&mut *html_buffer); result_1 = renderer.render_before_body(&mut *html_buffer);
result2 = self.ssr_renderer.render_to(&mut html_buffer, &vdom); result2 = self.ssr_renderer.render_to(&mut html_buffer, &vdom);
@ -276,7 +275,7 @@ impl IncrementalRenderer {
component: fn(Scope<P>) -> Element, component: fn(Scope<P>) -> Element,
props: P, props: P,
output: &mut (impl AsyncWrite + Unpin + std::marker::Send), output: &mut (impl AsyncWrite + Unpin + std::marker::Send),
modify_vdom: impl FnOnce(&mut VirtualDom), rebuild_with: impl FnOnce(&mut VirtualDom),
renderer: &R, renderer: &R,
) -> Result<RenderFreshness, IncrementalRendererError> { ) -> Result<RenderFreshness, IncrementalRendererError> {
// check if this route is cached // check if this route is cached
@ -285,7 +284,7 @@ impl IncrementalRenderer {
} else { } else {
// if not, create it // if not, create it
let freshness = self let freshness = self
.render_and_cache(route, component, props, output, modify_vdom, renderer) .render_and_cache(route, component, props, output, rebuild_with, renderer)
.await?; .await?;
log::trace!("cache miss"); log::trace!("cache miss");
Ok(freshness) Ok(freshness)
@ -299,7 +298,7 @@ impl IncrementalRenderer {
component: fn(Scope<P>) -> Element, component: fn(Scope<P>) -> Element,
props: P, props: P,
output: &mut String, output: &mut String,
modify_vdom: impl FnOnce(&mut VirtualDom), rebuild_with: impl FnOnce(&mut VirtualDom),
renderer: &R, renderer: &R,
) -> Result<RenderFreshness, IncrementalRendererError> { ) -> Result<RenderFreshness, IncrementalRendererError> {
unsafe { unsafe {
@ -309,7 +308,7 @@ impl IncrementalRenderer {
component, component,
props, props,
output.as_mut_vec(), output.as_mut_vec(),
modify_vdom, rebuild_with,
renderer, renderer,
) )
.await .await