mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 22:54:12 +00:00
add extractors to server-fn
This commit is contained in:
parent
523be82965
commit
4f6e6a7c0d
12 changed files with 260 additions and 139 deletions
|
@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "ssr", "fullstack"]
|
|||
|
||||
[dependencies]
|
||||
# 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" }
|
||||
|
||||
# warp
|
||||
|
@ -53,6 +53,9 @@ postcard = { version = "1.0.4", features = ["use-std"] }
|
|||
yazi = "0.1.5"
|
||||
base64 = "0.21.0"
|
||||
|
||||
pin-project = "1.1.2"
|
||||
async-trait = "0.1.71"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
dioxus-hot-reload = { workspace = true }
|
||||
|
||||
|
|
|
@ -26,12 +26,11 @@ fn app(cx: Scope<AppProps>) -> Element {
|
|||
button {
|
||||
onclick: move |_| {
|
||||
to_owned![text];
|
||||
let sc = cx.sc();
|
||||
async move {
|
||||
if let Ok(data) = get_server_data().await {
|
||||
println!("Client received: {}", data);
|
||||
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)]
|
||||
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.
|
||||
let cx = server_context();
|
||||
cx.response_headers_mut()
|
||||
.insert("Set-Cookie", "foo=bar".parse().unwrap());
|
||||
println!("Server received: {}", data);
|
||||
|
|
|
@ -7,7 +7,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
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"] }
|
||||
|
||||
[lib]
|
||||
|
|
|
@ -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.
|
||||
#[proc_macro_attribute]
|
||||
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(
|
||||
args.into(),
|
||||
s.into(),
|
||||
syn::parse_quote!(::dioxus_fullstack::prelude::ServerFnTraitObj),
|
||||
Some(context),
|
||||
None,
|
||||
Some(syn::parse_quote!(::dioxus_fullstack::prelude::server_fn)),
|
||||
) {
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
|
|
|
@ -64,8 +64,8 @@ use axum::{
|
|||
Router,
|
||||
};
|
||||
use server_fn::{Encoding, Payload, ServerFunctionRegistry};
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
use crate::{
|
||||
|
@ -91,7 +91,7 @@ pub trait DioxusRouterExt<S> {
|
|||
/// .register_server_fns_with_handler("", |func| {
|
||||
/// move |req: Request<Body>| async move {
|
||||
/// 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());
|
||||
/// 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>(
|
||||
self,
|
||||
server_fn_route: &'static str,
|
||||
handler: impl FnMut(server_fn::ServerFnTraitObj<DioxusServerContext>) -> H,
|
||||
handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
|
||||
) -> Self
|
||||
where
|
||||
H: Handler<T, S>,
|
||||
|
@ -230,7 +230,7 @@ where
|
|||
fn register_server_fns_with_handler<H, T>(
|
||||
self,
|
||||
server_fn_route: &'static str,
|
||||
mut handler: impl FnMut(server_fn::ServerFnTraitObj<DioxusServerContext>) -> H,
|
||||
mut handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
|
||||
) -> Self
|
||||
where
|
||||
H: Handler<T, S, Body>,
|
||||
|
@ -257,7 +257,7 @@ where
|
|||
self.register_server_fns_with_handler(server_fn_route, |func| {
|
||||
move |req: Request<Body>| async move {
|
||||
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());
|
||||
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>,
|
||||
) -> impl IntoResponse {
|
||||
let (parts, _) = request.into_parts();
|
||||
let parts: Arc<RequestParts> = Arc::new(parts.into());
|
||||
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());
|
||||
|
||||
match ssr_state
|
||||
.render(url, &cfg, |vdom| {
|
||||
vdom.base_scope().provide_context(server_context.clone());
|
||||
})
|
||||
.await
|
||||
{
|
||||
match ssr_state.render(url, &cfg, &server_context).await {
|
||||
Ok(rendered) => {
|
||||
let crate::render::RenderResponse { html, freshness } = rendered;
|
||||
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.
|
||||
pub async fn server_fn_handler(
|
||||
server_context: DioxusServerContext,
|
||||
function: server_fn::ServerFnTraitObj<DioxusServerContext>,
|
||||
parts: Arc<RequestParts>,
|
||||
function: server_fn::ServerFnTraitObj<()>,
|
||||
parts: Arc<RwLock<http::request::Parts>>,
|
||||
body: Body,
|
||||
) -> impl IntoResponse {
|
||||
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
|
||||
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({
|
||||
move || {
|
||||
tokio::runtime::Runtime::new()
|
||||
|
@ -414,9 +415,13 @@ pub async fn server_fn_handler(
|
|||
Encoding::Url | Encoding::Cbor => &body,
|
||||
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) => {
|
||||
// if this is Accept: application/json then send a serialized JSON response
|
||||
let parts = parts.read().unwrap();
|
||||
let accept_header = parts
|
||||
.headers
|
||||
.get("Accept")
|
||||
|
@ -463,7 +468,7 @@ pub async fn server_fn_handler(
|
|||
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()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(body::boxed(format!("Error: {}", e)))
|
||||
|
|
|
@ -75,7 +75,7 @@ pub trait DioxusRouterExt {
|
|||
/// use dioxus_fullstack::prelude::*;
|
||||
///
|
||||
/// struct ServerFunctionHandler {
|
||||
/// server_fn: server_fn::ServerFnTraitObj<DioxusServerContext>,
|
||||
/// server_fn: server_fn::ServerFnTraitObj<()>,
|
||||
/// }
|
||||
///
|
||||
/// #[handler]
|
||||
|
@ -108,7 +108,7 @@ pub trait DioxusRouterExt {
|
|||
fn register_server_fns_with_handler<H>(
|
||||
self,
|
||||
server_fn_route: &'static str,
|
||||
handler: impl Fn(server_fn::ServerFnTraitObj<DioxusServerContext>) -> H,
|
||||
handler: impl Fn(server_fn::ServerFnTraitObj<()>) -> H,
|
||||
) -> Self
|
||||
where
|
||||
H: Handler + 'static;
|
||||
|
@ -202,7 +202,7 @@ impl DioxusRouterExt for Router {
|
|||
fn register_server_fns_with_handler<H>(
|
||||
self,
|
||||
server_fn_route: &'static str,
|
||||
mut handler: impl FnMut(server_fn::ServerFnTraitObj<DioxusServerContext>) -> H,
|
||||
mut handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
|
||||
) -> Self
|
||||
where
|
||||
H: Handler + 'static,
|
||||
|
@ -333,9 +333,7 @@ impl<P: Clone + serde::Serialize + Send + Sync + 'static> Handler for SSRHandler
|
|||
let server_context = DioxusServerContext::new(parts);
|
||||
|
||||
match renderer_pool
|
||||
.render(route, &self.cfg, |vdom| {
|
||||
vdom.base_scope().provide_context(server_context.clone());
|
||||
})
|
||||
.render(route, &self.cfg, &server_context)
|
||||
.await
|
||||
{
|
||||
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.
|
||||
pub struct ServerFnHandler {
|
||||
server_context: DioxusServerContext,
|
||||
function: server_fn::ServerFnTraitObj<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<DioxusServerContext>,
|
||||
function: server_fn::ServerFnTraitObj<()>,
|
||||
) -> Self {
|
||||
let server_context = server_context.into();
|
||||
Self {
|
||||
|
@ -413,7 +411,12 @@ impl ServerFnHandler {
|
|||
Encoding::Url | Encoding::Cbor => &body,
|
||||
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();
|
||||
})
|
||||
|
|
|
@ -91,7 +91,7 @@ pub fn register_server_fns_with_handler<H, F, R>(
|
|||
mut handler: H,
|
||||
) -> BoxedFilter<(R,)>
|
||||
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::Extract: Send,
|
||||
R: Reply + 'static,
|
||||
|
@ -192,12 +192,7 @@ pub fn render_ssr<P: Clone + serde::Serialize + Send + Sync + 'static>(
|
|||
async move {
|
||||
let server_context = DioxusServerContext::new(parts);
|
||||
|
||||
match renderer
|
||||
.render(route, &cfg, |vdom| {
|
||||
vdom.base_scope().provide_context(server_context.clone());
|
||||
})
|
||||
.await
|
||||
{
|
||||
match renderer.render(route, &cfg, &server_context).await {
|
||||
Ok(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.
|
||||
pub async fn server_fn_handler(
|
||||
server_context: impl Into<DioxusServerContext>,
|
||||
function: server_fn::ServerFnTraitObj<DioxusServerContext>,
|
||||
server_context: impl Into<(DioxusServerContext)>,
|
||||
function: server_fn::ServerFnTraitObj<()>,
|
||||
parts: RequestParts,
|
||||
body: Bytes,
|
||||
) -> Result<Box<dyn warp::Reply>, warp::Rejection> {
|
||||
|
@ -299,7 +294,10 @@ pub async fn server_fn_handler(
|
|||
Encoding::Url | Encoding::Cbor => &body,
|
||||
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) => {
|
||||
// if this is Accept: application/json then send a serialized JSON response
|
||||
let accept_header = parts
|
||||
|
|
|
@ -18,6 +18,7 @@ pub mod launch;
|
|||
mod render;
|
||||
#[cfg(feature = "ssr")]
|
||||
mod serve_config;
|
||||
#[cfg(feature = "ssr")]
|
||||
mod server_context;
|
||||
mod server_fn;
|
||||
|
||||
|
@ -38,8 +39,11 @@ pub mod prelude {
|
|||
#[cfg(feature = "ssr")]
|
||||
pub use crate::serve_config::{ServeConfig, ServeConfigBuilder};
|
||||
#[cfg(feature = "ssr")]
|
||||
pub use crate::server_context::RequestParts;
|
||||
pub use crate::server_context::{DioxusServerContext, HasServerContext};
|
||||
pub use crate::server_context::Axum;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub use crate::server_context::{
|
||||
server_context, DioxusServerContext, FromServerContext, ProvideServerContext,
|
||||
};
|
||||
pub use crate::server_fn::DioxusServerFn;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub use crate::server_fn::{ServerFnTraitObj, ServerFunction};
|
||||
|
|
|
@ -9,7 +9,7 @@ use dioxus_ssr::{
|
|||
};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{prelude::*, server_context::with_server_context};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
enum SsrRendererPool {
|
||||
|
@ -25,15 +25,17 @@ impl SsrRendererPool {
|
|||
component: Component<P>,
|
||||
props: P,
|
||||
to: &mut String,
|
||||
modify_vdom: impl FnOnce(&mut VirtualDom),
|
||||
server_context: &DioxusServerContext,
|
||||
) -> Result<RenderFreshness, dioxus_ssr::incremental::IncrementalRendererError> {
|
||||
let wrapper = FullstackRenderer { cfg };
|
||||
match self {
|
||||
Self::Renderer(pool) => {
|
||||
let server_context = Box::new(server_context.clone());
|
||||
let mut vdom = VirtualDom::new_with_props(component, props);
|
||||
modify_vdom(&mut vdom);
|
||||
|
||||
let _ = vdom.rebuild();
|
||||
with_server_context(server_context, || {
|
||||
let _ = vdom.rebuild();
|
||||
});
|
||||
|
||||
let mut renderer = pool.pull(pre_renderer);
|
||||
|
||||
|
@ -48,7 +50,19 @@ impl SsrRendererPool {
|
|||
let mut renderer =
|
||||
pool.pull(|| incremental_pre_renderer(cfg.incremental.as_ref().unwrap()));
|
||||
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?)
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +100,7 @@ impl SSRState {
|
|||
&'a self,
|
||||
route: String,
|
||||
cfg: &'a ServeConfig<P>,
|
||||
modify_vdom: impl FnOnce(&mut VirtualDom) + Send + 'a,
|
||||
server_context: &'a DioxusServerContext,
|
||||
) -> impl std::future::Future<
|
||||
Output = Result<RenderResponse, dioxus_ssr::incremental::IncrementalRendererError>,
|
||||
> + Send
|
||||
|
@ -97,7 +111,7 @@ impl SSRState {
|
|||
|
||||
let freshness = self
|
||||
.renderers
|
||||
.render_to(cfg, route, *app, props.clone(), &mut html, modify_vdom)
|
||||
.render_to(cfg, route, *app, props.clone(), &mut html, server_context)
|
||||
.await?;
|
||||
|
||||
Ok(RenderResponse { html, freshness })
|
||||
|
|
|
@ -1,28 +1,6 @@
|
|||
use dioxus::prelude::ScopeState;
|
||||
|
||||
/// A trait for an object that contains a server context
|
||||
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 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use server_fn_impl::*;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
/// 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.
|
||||
|
@ -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.
|
||||
#[derive(Clone)]
|
||||
pub struct DioxusServerContext {
|
||||
#[cfg(feature = "ssr")]
|
||||
shared_context: std::sync::Arc<
|
||||
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>>,
|
||||
#[cfg(feature = "ssr")]
|
||||
pub(crate) parts: std::sync::Arc<RequestParts>,
|
||||
pub(crate) parts: Arc<RwLock<http::request::Parts>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for DioxusServerContext {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#[cfg(feature = "ssr")]
|
||||
shared_context: std::sync::Arc::new(std::sync::RwLock::new(anymap::Map::new())),
|
||||
#[cfg(feature = "ssr")]
|
||||
headers: Default::default(),
|
||||
#[cfg(feature = "ssr")]
|
||||
parts: Default::default(),
|
||||
parts: std::sync::Arc::new(RwLock::new(http::request::Request::new(()).into_parts().0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub use server_fn_impl::*;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
mod server_fn_impl {
|
||||
use super::*;
|
||||
use std::sync::LockResult;
|
||||
|
@ -68,7 +36,7 @@ mod server_fn_impl {
|
|||
|
||||
impl DioxusServerContext {
|
||||
/// 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 {
|
||||
parts: parts.into(),
|
||||
shared_context: Arc::new(RwLock::new(SendSyncAnyMap::new())),
|
||||
|
@ -126,35 +94,170 @@ mod server_fn_impl {
|
|||
/// Get the request that triggered:
|
||||
/// - The initial SSR render if called from a ScopeState or ServerFn
|
||||
/// - The server function to be called if called from a server function after the initial render
|
||||
pub fn request_parts(&self) -> &RequestParts {
|
||||
&self.parts
|
||||
pub fn request_parts(
|
||||
&self,
|
||||
) -> std::sync::LockResult<RwLockReadGuard<'_, http::request::Parts>> {
|
||||
self.parts.read()
|
||||
}
|
||||
}
|
||||
|
||||
/// Associated parts of an HTTP Request
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RequestParts {
|
||||
/// The request's method
|
||||
pub method: http::Method,
|
||||
/// The request's URI
|
||||
pub uri: http::Uri,
|
||||
/// 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,
|
||||
}
|
||||
/// Get the request that triggered:
|
||||
/// - The initial SSR render if called from a ScopeState or ServerFn
|
||||
/// - The server function to be called if called from a server function after the initial render
|
||||
pub fn request_parts_mut(
|
||||
&self,
|
||||
) -> std::sync::LockResult<RwLockWriteGuard<'_, http::request::Parts>> {
|
||||
self.parts.write()
|
||||
}
|
||||
|
||||
impl From<http::request::Parts> for RequestParts {
|
||||
fn from(parts: http::request::Parts) -> Self {
|
||||
Self {
|
||||
method: parts.method,
|
||||
uri: parts.uri,
|
||||
version: parts.version,
|
||||
headers: parts.headers,
|
||||
extensions: parts.extensions,
|
||||
}
|
||||
/// Extract some part from the request
|
||||
pub async fn extract<R: std::error::Error, T: FromServerContext<Rejection = R>>(
|
||||
&self,
|
||||
) -> Result<T, R> {
|
||||
T::from_request(self).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
use crate::server_context::DioxusServerContext;
|
||||
|
||||
#[cfg(any(feature = "ssr", doc))]
|
||||
#[derive(Clone)]
|
||||
/// A trait object for a function that be called on serializable arguments and returns a serializable result.
|
||||
pub struct ServerFnTraitObj(server_fn::ServerFnTraitObj<DioxusServerContext>);
|
||||
pub struct ServerFnTraitObj(server_fn::ServerFnTraitObj<()>);
|
||||
|
||||
#[cfg(any(feature = "ssr", doc))]
|
||||
impl std::ops::Deref for ServerFnTraitObj {
|
||||
type Target = server_fn::ServerFnTraitObj<DioxusServerContext>;
|
||||
type Target = server_fn::ServerFnTraitObj<()>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
|
@ -33,9 +31,7 @@ impl ServerFnTraitObj {
|
|||
}
|
||||
|
||||
/// Create a new `ServerFnTraitObj` from a `server_fn::ServerFnTraitObj`.
|
||||
pub const fn from_generic_server_fn(
|
||||
server_fn: server_fn::ServerFnTraitObj<DioxusServerContext>,
|
||||
) -> Self {
|
||||
pub const fn from_generic_server_fn(server_fn: server_fn::ServerFnTraitObj<()>) -> Self {
|
||||
Self(server_fn)
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +41,7 @@ server_fn::inventory::collect!(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::SerializedFnTraitObj<DioxusServerContext>;
|
||||
pub type ServerFunction = server_fn::SerializedFnTraitObj<()>;
|
||||
|
||||
#[cfg(any(feature = "ssr", doc))]
|
||||
#[allow(clippy::type_complexity)]
|
||||
|
@ -64,7 +60,7 @@ static REGISTERED_SERVER_FUNCTIONS: once_cell::sync::Lazy<
|
|||
pub struct DioxusServerFnRegistry;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
impl server_fn::ServerFunctionRegistry<DioxusServerContext> for DioxusServerFnRegistry {
|
||||
impl server_fn::ServerFunctionRegistry<()> for DioxusServerFnRegistry {
|
||||
type Error = ServerRegistrationFnError;
|
||||
|
||||
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.
|
||||
fn get(url: &str) -> Option<server_fn::ServerFnTraitObj<DioxusServerContext>> {
|
||||
fn get(url: &str) -> Option<server_fn::ServerFnTraitObj<()>> {
|
||||
REGISTERED_SERVER_FUNCTIONS
|
||||
.read()
|
||||
.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.
|
||||
fn get_trait_obj(url: &str) -> Option<server_fn::ServerFnTraitObj<DioxusServerContext>> {
|
||||
fn get_trait_obj(url: &str) -> Option<server_fn::ServerFnTraitObj<()>> {
|
||||
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].
|
||||
///
|
||||
/// 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.
|
||||
#[cfg(any(feature = "ssr", doc))]
|
||||
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<()> {}
|
||||
|
|
|
@ -170,7 +170,7 @@ impl IncrementalRenderer {
|
|||
comp: fn(Scope<P>) -> Element,
|
||||
props: P,
|
||||
output: &'a mut (impl AsyncWrite + Unpin + Send),
|
||||
modify_vdom: impl FnOnce(&mut VirtualDom),
|
||||
rebuild_with: impl FnOnce(&mut VirtualDom),
|
||||
renderer: &'a R,
|
||||
) -> impl std::future::Future<Output = Result<RenderFreshness, IncrementalRendererError>> + 'a + Send
|
||||
{
|
||||
|
@ -179,8 +179,7 @@ impl IncrementalRenderer {
|
|||
let result2;
|
||||
{
|
||||
let mut vdom = VirtualDom::new_with_props(comp, props);
|
||||
modify_vdom(&mut vdom);
|
||||
let _ = vdom.rebuild();
|
||||
rebuild_with(&mut vdom);
|
||||
|
||||
result_1 = renderer.render_before_body(&mut *html_buffer);
|
||||
result2 = self.ssr_renderer.render_to(&mut html_buffer, &vdom);
|
||||
|
@ -276,7 +275,7 @@ impl IncrementalRenderer {
|
|||
component: fn(Scope<P>) -> Element,
|
||||
props: P,
|
||||
output: &mut (impl AsyncWrite + Unpin + std::marker::Send),
|
||||
modify_vdom: impl FnOnce(&mut VirtualDom),
|
||||
rebuild_with: impl FnOnce(&mut VirtualDom),
|
||||
renderer: &R,
|
||||
) -> Result<RenderFreshness, IncrementalRendererError> {
|
||||
// check if this route is cached
|
||||
|
@ -285,7 +284,7 @@ impl IncrementalRenderer {
|
|||
} else {
|
||||
// if not, create it
|
||||
let freshness = self
|
||||
.render_and_cache(route, component, props, output, modify_vdom, renderer)
|
||||
.render_and_cache(route, component, props, output, rebuild_with, renderer)
|
||||
.await?;
|
||||
log::trace!("cache miss");
|
||||
Ok(freshness)
|
||||
|
@ -299,7 +298,7 @@ impl IncrementalRenderer {
|
|||
component: fn(Scope<P>) -> Element,
|
||||
props: P,
|
||||
output: &mut String,
|
||||
modify_vdom: impl FnOnce(&mut VirtualDom),
|
||||
rebuild_with: impl FnOnce(&mut VirtualDom),
|
||||
renderer: &R,
|
||||
) -> Result<RenderFreshness, IncrementalRendererError> {
|
||||
unsafe {
|
||||
|
@ -309,7 +308,7 @@ impl IncrementalRenderer {
|
|||
component,
|
||||
props,
|
||||
output.as_mut_vec(),
|
||||
modify_vdom,
|
||||
rebuild_with,
|
||||
renderer,
|
||||
)
|
||||
.await
|
||||
|
|
Loading…
Reference in a new issue