adds feature server_fn_axum that compiles for wasm target (#3840)
The current `axum` feature and `server` feature are incompatible with wasm32-unknown-unknown target due to: 1. depends on axum features not compatible with wasm 2. SSR requires reading the filesystem for index.html With this patch I add the `server_fn_axum` feature which disables SSR and enables targeting wasm32-unknown-unknown with server functions enabled. Note that for this to work, `server_fn` depends on `inventory` and as stated in this docs depending on the context you might need to expose and call [`__wasm_call_ctors`](https://docs.rs/inventory/latest/inventory/#webassembly-and-constructors) (I needed that specifically for cloudflare workers) change axum_server_fn feature for axum_wasm and include SSR tools there This change additionaly make axum_server_fn feature additive by exposing a different `DioxusRouterWasmExt` with only the wasm-compatible parts instead of exposing the `DioxusRouter` trait under the same name. Docs are updated to reflect this change. rename axum_wasm to axum_core and update docs fix typo re-enable default-features for axum in dioxus-cli re-enable axum default-features for liveview re-enable default-features for axum for playwright-tests
This commit is contained in:
10 files changed
+446
-368
No files matched your search
Generated
+1
-12
@@ -1322,7 +1322,6 @@ checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum-core 0.4.5",
|
"axum-core 0.4.5",
|
||||||
"axum-macros",
|
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@@ -1415,17 +1414,6 @@ dependencies = [
|
|||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "axum-macros"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.98",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-server"
|
name = "axum-server"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@@ -3870,6 +3858,7 @@ name = "dioxus-examples"
|
|||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
|
"axum 0.7.9",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"ciborium",
|
"ciborium",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
|
|||||||
+2
-1
@@ -191,7 +191,7 @@ tauri-utils = { version = "=2.1.1" }
|
|||||||
tauri-bundler = { version = "=2.2.3" }
|
tauri-bundler = { version = "=2.2.3" }
|
||||||
lru = "0.12.2"
|
lru = "0.12.2"
|
||||||
async-trait = "0.1.77"
|
async-trait = "0.1.77"
|
||||||
axum = "0.7.0"
|
axum = { version = "0.7.0", default-features = false }
|
||||||
axum-server = { version = "0.7.1", default-features = false }
|
axum-server = { version = "0.7.1", default-features = false }
|
||||||
tower = "0.4.13"
|
tower = "0.4.13"
|
||||||
http = "1.0.0"
|
http = "1.0.0"
|
||||||
@@ -348,6 +348,7 @@ rand = { version = "0.8.4", features = ["small_rng"] }
|
|||||||
form_urlencoded = "1.2.0"
|
form_urlencoded = "1.2.0"
|
||||||
async-std = "1.12.0"
|
async-std = "1.12.0"
|
||||||
web-time = "1.1.0"
|
web-time = "1.1.0"
|
||||||
|
axum = { workspace = true, default-features = true }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||||
getrandom = { version = "0.2.12", features = ["js"] }
|
getrandom = { version = "0.2.12", features = ["js"] }
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ krates = { version = "0.17.0" }
|
|||||||
cargo-config2 = { workspace = true, optional = true }
|
cargo-config2 = { workspace = true, optional = true }
|
||||||
regex = "1.10.6"
|
regex = "1.10.6"
|
||||||
|
|
||||||
axum = { workspace = true, features = ["ws"] }
|
axum = { workspace = true, default-features = true, features = ["ws"] }
|
||||||
axum-server = { workspace = true, features = ["tls-rustls"] }
|
axum-server = { workspace = true, features = ["tls-rustls"] }
|
||||||
axum-extra = { workspace = true, features = ["typed-header"] }
|
axum-extra = { workspace = true, features = ["typed-header"] }
|
||||||
tower-http = { workspace = true, features = ["full"] }
|
tower-http = { workspace = true, features = ["full"] }
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ server_fn = { version = "0.7.3", features = ["json", "url", "browser"], default-
|
|||||||
dioxus_server_macro = { workspace = true }
|
dioxus_server_macro = { workspace = true }
|
||||||
|
|
||||||
# axum
|
# axum
|
||||||
axum = { workspace = true, features = ["ws", "macros"], optional = true }
|
axum = { workspace = true, optional = true }
|
||||||
tower-http = { workspace = true, optional = true, features = ["fs"] }
|
tower-http = { workspace = true, optional = true, features = ["fs"] }
|
||||||
|
|
||||||
dioxus-lib = { workspace = true }
|
dioxus-lib = { workspace = true }
|
||||||
@@ -88,7 +88,18 @@ desktop = ["dep:dioxus-desktop", "server_fn/reqwest", "dioxus_server_macro/reqwe
|
|||||||
mobile = ["dep:dioxus-mobile", "server_fn/reqwest", "dioxus_server_macro/reqwest"]
|
mobile = ["dep:dioxus-mobile", "server_fn/reqwest", "dioxus_server_macro/reqwest"]
|
||||||
default-tls = ["server_fn/default-tls"]
|
default-tls = ["server_fn/default-tls"]
|
||||||
rustls = ["server_fn/rustls", "dep:rustls", "dep:hyper-rustls"]
|
rustls = ["server_fn/rustls", "dep:rustls", "dep:hyper-rustls"]
|
||||||
axum = ["dep:axum", "dep:tower-http", "server", "server_fn/axum", "dioxus_server_macro/axum", "default-tls"]
|
axum_core = [
|
||||||
|
"dep:axum",
|
||||||
|
"server_fn/axum-no-default",
|
||||||
|
"dioxus_server_macro/axum",
|
||||||
|
"default-tls",
|
||||||
|
"server",
|
||||||
|
]
|
||||||
|
axum = [
|
||||||
|
"dep:tower-http",
|
||||||
|
"server_fn/axum",
|
||||||
|
"axum_core",
|
||||||
|
]
|
||||||
server = [
|
server = [
|
||||||
"server_fn/ssr",
|
"server_fn/ssr",
|
||||||
"dioxus_server_macro/server",
|
"dioxus_server_macro/server",
|
||||||
|
|||||||
@@ -0,0 +1,393 @@
|
|||||||
|
//! Dioxus core utilities for the [Axum](https://docs.rs/axum/latest/axum/index.html) server framework.
|
||||||
|
//!
|
||||||
|
//! # Example
|
||||||
|
//! ```rust, no_run
|
||||||
|
//! #![allow(non_snake_case)]
|
||||||
|
//! use dioxus::prelude::*;
|
||||||
|
//!
|
||||||
|
//! fn main() {
|
||||||
|
//! #[cfg(feature = "web")]
|
||||||
|
//! // Hydrate the application on the client
|
||||||
|
//! dioxus::launch(app);
|
||||||
|
//! #[cfg(feature = "server")]
|
||||||
|
//! {
|
||||||
|
//! tokio::runtime::Runtime::new()
|
||||||
|
//! .unwrap()
|
||||||
|
//! .block_on(async move {
|
||||||
|
//! // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address
|
||||||
|
//! // and we use the generated address the CLI gives us
|
||||||
|
//! let address = dioxus::cli_config::fullstack_address_or_localhost();
|
||||||
|
//! let listener = tokio::net::TcpListener::bind(address)
|
||||||
|
//! .await
|
||||||
|
//! .unwrap();
|
||||||
|
//! axum::serve(
|
||||||
|
//! listener,
|
||||||
|
//! axum::Router::new()
|
||||||
|
//! // Server side render the application, serve static assets, and register server functions
|
||||||
|
//! .register_server_functions()
|
||||||
|
//! .fallback(get(render_handler)
|
||||||
|
//! // Note: ServeConfig::new won't work on WASM
|
||||||
|
//! .with_state(RenderHandler::new(ServeConfig::new().unwrap(), app))
|
||||||
|
//! )
|
||||||
|
//! .into_make_service(),
|
||||||
|
//! )
|
||||||
|
//! .await
|
||||||
|
//! .unwrap();
|
||||||
|
//! });
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn app() -> Element {
|
||||||
|
//! let mut text = use_signal(|| "...".to_string());
|
||||||
|
//!
|
||||||
|
//! rsx! {
|
||||||
|
//! button {
|
||||||
|
//! onclick: move |_| async move {
|
||||||
|
//! if let Ok(data) = get_server_data().await {
|
||||||
|
//! text.set(data);
|
||||||
|
//! }
|
||||||
|
//! },
|
||||||
|
//! "Run a server function"
|
||||||
|
//! }
|
||||||
|
//! "Server said: {text}"
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[server(GetServerData)]
|
||||||
|
//! async fn get_server_data() -> Result<String, ServerFnError> {
|
||||||
|
//! Ok("Hello from the server!".to_string())
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! # WASM support
|
||||||
|
//!
|
||||||
|
//! These utilities compile to the WASM family of targets, while the more complete ones found in [server] don't
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::render::SSRError;
|
||||||
|
use crate::ContextProviders;
|
||||||
|
|
||||||
|
use axum::body;
|
||||||
|
use axum::extract::State;
|
||||||
|
use axum::routing::*;
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
http::{Request, Response, StatusCode},
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
use dioxus_lib::prelude::{Element, VirtualDom};
|
||||||
|
use http::header::*;
|
||||||
|
|
||||||
|
/// A extension trait with server function utilities for integrating Dioxus with your Axum router.
|
||||||
|
pub trait DioxusRouterFnExt<S> {
|
||||||
|
/// Registers server functions with the default handler. This handler function will pass an empty [`DioxusServerContext`] to your server functions.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust, no_run
|
||||||
|
/// # use dioxus_lib::prelude::*;
|
||||||
|
/// # use dioxus_fullstack::prelude::*;
|
||||||
|
/// #[tokio::main]
|
||||||
|
/// async fn main() {
|
||||||
|
/// let addr = dioxus::cli_config::fullstack_address_or_localhost();
|
||||||
|
/// let router = axum::Router::new()
|
||||||
|
/// // Register server functions routes with the default handler
|
||||||
|
/// .register_server_functions()
|
||||||
|
/// .into_make_service();
|
||||||
|
/// let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||||
|
/// axum::serve(listener, router).await.unwrap();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn register_server_functions(self) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.register_server_functions_with_context(Default::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers server functions with some additional context to insert into the [`DioxusServerContext`] for that handler.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust, no_run
|
||||||
|
/// # use dioxus_lib::prelude::*;
|
||||||
|
/// # use dioxus_fullstack::prelude::*;
|
||||||
|
/// # use std::sync::Arc;
|
||||||
|
/// #[tokio::main]
|
||||||
|
/// async fn main() {
|
||||||
|
/// let addr = dioxus::cli_config::fullstack_address_or_localhost();
|
||||||
|
/// let router = axum::Router::new()
|
||||||
|
/// // Register server functions routes with the default handler
|
||||||
|
/// .register_server_functions_with_context(Arc::new(vec![Box::new(|| Box::new(1234567890u32))]))
|
||||||
|
/// .into_make_service();
|
||||||
|
/// let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||||
|
/// axum::serve(listener, router).await.unwrap();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
fn register_server_functions_with_context(self, context_providers: ContextProviders) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> DioxusRouterFnExt<S> for Router<S>
|
||||||
|
where
|
||||||
|
S: Send + Sync + Clone + 'static,
|
||||||
|
{
|
||||||
|
fn register_server_functions_with_context(
|
||||||
|
mut self,
|
||||||
|
context_providers: ContextProviders,
|
||||||
|
) -> Self {
|
||||||
|
use http::method::Method;
|
||||||
|
|
||||||
|
for (path, method) in server_fn::axum::server_fn_paths() {
|
||||||
|
tracing::trace!("Registering server function: {} {}", method, path);
|
||||||
|
let context_providers = context_providers.clone();
|
||||||
|
let handler = move |req| handle_server_fns_inner(path, context_providers, req);
|
||||||
|
self = match method {
|
||||||
|
Method::GET => self.route(path, get(handler)),
|
||||||
|
Method::POST => self.route(path, post(handler)),
|
||||||
|
Method::PUT => self.route(path, put(handler)),
|
||||||
|
_ => unimplemented!("Unsupported server function method: {}", method),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A handler for Dioxus server functions. This will run the server function and return the result.
|
||||||
|
async fn handle_server_fns_inner(
|
||||||
|
path: &str,
|
||||||
|
additional_context: ContextProviders,
|
||||||
|
req: Request<Body>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
use server_fn::middleware::Service;
|
||||||
|
|
||||||
|
let path_string = path.to_string();
|
||||||
|
|
||||||
|
let (parts, body) = req.into_parts();
|
||||||
|
let req = Request::from_parts(parts.clone(), body);
|
||||||
|
let method = req.method().clone();
|
||||||
|
|
||||||
|
if let Some(mut service) =
|
||||||
|
server_fn::axum::get_server_fn_service(&path_string, method)
|
||||||
|
{
|
||||||
|
// Create the server context with info from the request
|
||||||
|
let server_context = DioxusServerContext::new(parts);
|
||||||
|
// Provide additional context from the render state
|
||||||
|
add_server_context(&server_context, &additional_context);
|
||||||
|
|
||||||
|
// store Accepts and Referrer in case we need them for redirect (below)
|
||||||
|
let accepts_html = req
|
||||||
|
.headers()
|
||||||
|
.get(ACCEPT)
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.map(|v| v.contains("text/html"))
|
||||||
|
.unwrap_or(false);
|
||||||
|
let referrer = req.headers().get(REFERER).cloned();
|
||||||
|
|
||||||
|
// actually run the server fn (which may use the server context)
|
||||||
|
let fut = with_server_context(server_context.clone(), || service.run(req));
|
||||||
|
let mut res = ProvideServerContext::new(fut, server_context.clone()).await;
|
||||||
|
|
||||||
|
// it it accepts text/html (i.e., is a plain form post) and doesn't already have a
|
||||||
|
// Location set, then redirect to Referer
|
||||||
|
if accepts_html {
|
||||||
|
if let Some(referrer) = referrer {
|
||||||
|
let has_location = res.headers().get(LOCATION).is_some();
|
||||||
|
if !has_location {
|
||||||
|
*res.status_mut() = StatusCode::FOUND;
|
||||||
|
res.headers_mut().insert(LOCATION, referrer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply the response parts from the server context to the response
|
||||||
|
let mut res_options = server_context.response_parts_mut();
|
||||||
|
res.headers_mut().extend(res_options.headers.drain());
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
} else {
|
||||||
|
Response::builder().status(StatusCode::BAD_REQUEST).body(
|
||||||
|
{
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
{
|
||||||
|
Body::from(format!(
|
||||||
|
"No server function found for path: {path_string}\nYou may need to explicitly register the server function with `register_explicit`, rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
|
{
|
||||||
|
Body::from(format!(
|
||||||
|
"No server function found for path: {path_string}\nYou may need to rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.expect("could not build Response")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_server_context(
|
||||||
|
server_context: &DioxusServerContext,
|
||||||
|
context_providers: &ContextProviders,
|
||||||
|
) {
|
||||||
|
for index in 0..context_providers.len() {
|
||||||
|
let context_providers = context_providers.clone();
|
||||||
|
server_context.insert_boxed_factory(Box::new(move || context_providers[index]()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_request_parts_to_response<B>(
|
||||||
|
headers: hyper::header::HeaderMap,
|
||||||
|
response: &mut axum::response::Response<B>,
|
||||||
|
) {
|
||||||
|
let mut_headers = response.headers_mut();
|
||||||
|
for (key, value) in headers.iter() {
|
||||||
|
mut_headers.insert(key, value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// State used by [`render_handler`] to render a dioxus component with axum
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RenderHandleState {
|
||||||
|
config: ServeConfig,
|
||||||
|
build_virtual_dom: Arc<dyn Fn() -> VirtualDom + Send + Sync>,
|
||||||
|
ssr_state: once_cell::sync::OnceCell<SSRState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderHandleState {
|
||||||
|
/// Create a new [`RenderHandleState`]
|
||||||
|
pub fn new(config: ServeConfig, root: fn() -> Element) -> Self {
|
||||||
|
Self {
|
||||||
|
config,
|
||||||
|
build_virtual_dom: Arc::new(move || VirtualDom::new(root)),
|
||||||
|
ssr_state: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [`RenderHandleState`] with a custom [`VirtualDom`] factory. This method can be used to pass context into the root component of your application.
|
||||||
|
pub fn new_with_virtual_dom_factory(
|
||||||
|
config: ServeConfig,
|
||||||
|
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
config,
|
||||||
|
build_virtual_dom: Arc::new(build_virtual_dom),
|
||||||
|
ssr_state: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the [`ServeConfig`] for this [`RenderHandleState`]
|
||||||
|
pub fn with_config(mut self, config: ServeConfig) -> Self {
|
||||||
|
self.config = config;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the [`SSRState`] for this [`RenderHandleState`]. Sharing a [`SSRState`] between multiple [`RenderHandleState`]s is more efficient than creating a new [`SSRState`] for each [`RenderHandleState`].
|
||||||
|
pub fn with_ssr_state(mut self, ssr_state: SSRState) -> Self {
|
||||||
|
self.ssr_state = once_cell::sync::OnceCell::new();
|
||||||
|
if self.ssr_state.set(ssr_state).is_err() {
|
||||||
|
panic!("SSRState already set");
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ssr_state(&self) -> &SSRState {
|
||||||
|
self.ssr_state.get_or_init(|| SSRState::new(&self.config))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SSR renderer handler for Axum with added context injection.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// #![allow(non_snake_case)]
|
||||||
|
/// use std::sync::{Arc, Mutex};
|
||||||
|
///
|
||||||
|
/// use axum::routing::get;
|
||||||
|
/// use dioxus::prelude::*;
|
||||||
|
///
|
||||||
|
/// fn app() -> Element {
|
||||||
|
/// rsx! {
|
||||||
|
/// "hello!"
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[tokio::main]
|
||||||
|
/// async fn main() {
|
||||||
|
/// let addr = dioxus::cli_config::fullstack_address_or_localhost();
|
||||||
|
/// let router = axum::Router::new()
|
||||||
|
/// // Register server functions, etc.
|
||||||
|
/// // Note you can use `register_server_functions_with_context`
|
||||||
|
/// // to inject the context into server functions running outside
|
||||||
|
/// // of an SSR render context.
|
||||||
|
/// .fallback(get(render_handler)
|
||||||
|
/// .with_state(RenderHandleState::new(ServeConfig::new().unwrap(), app))
|
||||||
|
/// )
|
||||||
|
/// .into_make_service();
|
||||||
|
/// let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||||
|
/// axum::serve(listener, router).await.unwrap();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub async fn render_handler(
|
||||||
|
State(state): State<RenderHandleState>,
|
||||||
|
request: Request<Body>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let cfg = &state.config;
|
||||||
|
let ssr_state = state.ssr_state();
|
||||||
|
let build_virtual_dom = {
|
||||||
|
let build_virtual_dom = state.build_virtual_dom.clone();
|
||||||
|
let context_providers = state.config.context_providers.clone();
|
||||||
|
move || {
|
||||||
|
let mut vdom = build_virtual_dom();
|
||||||
|
for state in context_providers.as_slice() {
|
||||||
|
vdom.insert_any_root_context(state());
|
||||||
|
}
|
||||||
|
vdom
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (parts, _) = request.into_parts();
|
||||||
|
let url = parts
|
||||||
|
.uri
|
||||||
|
.path_and_query()
|
||||||
|
.ok_or(StatusCode::BAD_REQUEST)?
|
||||||
|
.to_string();
|
||||||
|
let parts: Arc<parking_lot::RwLock<http::request::Parts>> =
|
||||||
|
Arc::new(parking_lot::RwLock::new(parts));
|
||||||
|
// Create the server context with info from the request
|
||||||
|
let server_context = DioxusServerContext::from_shared_parts(parts.clone());
|
||||||
|
// Provide additional context from the render state
|
||||||
|
add_server_context(&server_context, &state.config.context_providers);
|
||||||
|
|
||||||
|
match ssr_state
|
||||||
|
.render(url, cfg, build_virtual_dom, &server_context)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok((freshness, rx)) => {
|
||||||
|
let mut response = axum::response::Html::from(Body::from_stream(rx)).into_response();
|
||||||
|
freshness.write(response.headers_mut());
|
||||||
|
let headers = server_context.response_parts().headers.clone();
|
||||||
|
apply_request_parts_to_response(headers, &mut response);
|
||||||
|
Result::<http::Response<axum::body::Body>, StatusCode>::Ok(response)
|
||||||
|
}
|
||||||
|
Err(SSRError::Incremental(e)) => {
|
||||||
|
tracing::error!("Failed to render page: {}", e);
|
||||||
|
Ok(report_err(e).into_response())
|
||||||
|
}
|
||||||
|
Err(SSRError::Routing(e)) => {
|
||||||
|
tracing::trace!("Page not found: {}", e);
|
||||||
|
Ok(Response::builder()
|
||||||
|
.status(StatusCode::NOT_FOUND)
|
||||||
|
.body(Body::from("Page not found"))
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn report_err<E: std::fmt::Display>(e: E) -> Response<axum::body::Body> {
|
||||||
|
Response::builder()
|
||||||
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
.body(body::Body::new(format!("Error: {}", e)))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
@@ -4,14 +4,24 @@
|
|||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use once_cell;
|
pub use once_cell;
|
||||||
|
|
||||||
mod html_storage;
|
mod html_storage;
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) type ContextProviders =
|
||||||
|
Arc<Vec<Box<dyn Fn() -> Box<dyn std::any::Any> + Send + Sync + 'static>>>;
|
||||||
|
|
||||||
#[cfg(feature = "axum")]
|
#[cfg(feature = "axum")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
|
#[cfg(feature = "axum_core")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "axum_core")))]
|
||||||
|
pub mod axum_core;
|
||||||
|
|
||||||
mod hooks;
|
mod hooks;
|
||||||
|
|
||||||
pub mod document;
|
pub mod document;
|
||||||
@@ -22,6 +32,7 @@ mod streaming;
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
mod serve_config;
|
mod serve_config;
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub use serve_config::*;
|
pub use serve_config::*;
|
||||||
|
|
||||||
@@ -37,6 +48,9 @@ pub mod prelude {
|
|||||||
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
|
||||||
pub use crate::server::*;
|
pub use crate::server::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "axum_core")]
|
||||||
|
pub use crate::axum_core::*;
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||||
pub use crate::render::{FullstackHTMLTemplate, SSRState};
|
pub use crate::render::{FullstackHTMLTemplate, SSRState};
|
||||||
@@ -45,8 +59,8 @@ pub mod prelude {
|
|||||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||||
pub use crate::serve_config::{ServeConfig, ServeConfigBuilder};
|
pub use crate::serve_config::{ServeConfig, ServeConfigBuilder};
|
||||||
|
|
||||||
#[cfg(all(feature = "server", feature = "axum"))]
|
#[cfg(feature = "axum")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "server", feature = "axum"))))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
|
||||||
pub use crate::server_context::Axum;
|
pub use crate::server_context::Axum;
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ use std::io::Read;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub(crate) type ContextProviders =
|
use crate::ContextProviders;
|
||||||
Arc<Vec<Box<dyn Fn() -> Box<dyn std::any::Any> + Send + Sync + 'static>>>;
|
|
||||||
|
|
||||||
/// A ServeConfig is used to configure how to serve a Dioxus application. It contains information about how to serve static assets, and what content to render with [`dioxus-ssr`].
|
/// A ServeConfig is used to configure how to serve a Dioxus application. It contains information about how to serve static assets, and what content to render with [`dioxus-ssr`].
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
@@ -337,20 +336,24 @@ impl ServeConfigBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Build the ServeConfig. This may fail if the index.html file is not found.
|
/// Build the ServeConfig. This may fail if the index.html file is not found.
|
||||||
|
///
|
||||||
|
/// ## WASM compatibility
|
||||||
|
/// In the context of WASM the file system normally can't be read so this always requires
|
||||||
|
/// the `index_html` field to be set.
|
||||||
pub fn build(self) -> Result<ServeConfig, UnableToLoadIndex> {
|
pub fn build(self) -> Result<ServeConfig, UnableToLoadIndex> {
|
||||||
// The CLI always bundles static assets into the exe/public directory
|
|
||||||
let public_path = public_path();
|
|
||||||
|
|
||||||
let index_path = self
|
|
||||||
.index_path
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.unwrap_or_else(|| public_path.join("index.html"));
|
|
||||||
|
|
||||||
let root_id = self.root_id.unwrap_or("main");
|
let root_id = self.root_id.unwrap_or("main");
|
||||||
|
|
||||||
let index_html = match self.index_html {
|
let index_html = match self.index_html {
|
||||||
Some(index) => index,
|
Some(index) => index,
|
||||||
None => load_index_path(index_path)?,
|
None => {
|
||||||
|
// The CLI always bundles static assets into the exe/public directory
|
||||||
|
let public_path = public_path();
|
||||||
|
|
||||||
|
let index_path = self
|
||||||
|
.index_path
|
||||||
|
.unwrap_or_else(|| public_path.join("index.html"));
|
||||||
|
load_index_path(index_path)?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let index = load_index_html(index_html, root_id);
|
let index = load_index_html(index_html, root_id);
|
||||||
@@ -491,6 +494,7 @@ impl LaunchConfig for ServeConfig {}
|
|||||||
|
|
||||||
impl ServeConfig {
|
impl ServeConfig {
|
||||||
/// Create a new ServeConfig
|
/// Create a new ServeConfig
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
pub fn new() -> Result<Self, UnableToLoadIndex> {
|
pub fn new() -> Result<Self, UnableToLoadIndex> {
|
||||||
ServeConfigBuilder::new().build()
|
ServeConfigBuilder::new().build()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,66 +58,12 @@
|
|||||||
pub mod launch;
|
pub mod launch;
|
||||||
|
|
||||||
use axum::routing::*;
|
use axum::routing::*;
|
||||||
use axum::{
|
use dioxus_lib::prelude::Element;
|
||||||
body::{self, Body},
|
|
||||||
extract::State,
|
|
||||||
http::{Request, Response, StatusCode},
|
|
||||||
response::IntoResponse,
|
|
||||||
};
|
|
||||||
use dioxus_lib::prelude::{Element, VirtualDom};
|
|
||||||
use http::header::*;
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::render::SSRError;
|
|
||||||
use crate::{prelude::*, ContextProviders};
|
|
||||||
|
|
||||||
/// A extension trait with utilities for integrating Dioxus with your Axum router.
|
/// A extension trait with utilities for integrating Dioxus with your Axum router.
|
||||||
pub trait DioxusRouterExt<S> {
|
pub trait DioxusRouterExt<S>: DioxusRouterFnExt<S> {
|
||||||
/// Registers server functions with the default handler. This handler function will pass an empty [`DioxusServerContext`] to your server functions.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```rust, no_run
|
|
||||||
/// # use dioxus_lib::prelude::*;
|
|
||||||
/// # use dioxus_fullstack::prelude::*;
|
|
||||||
/// #[tokio::main]
|
|
||||||
/// async fn main() {
|
|
||||||
/// let addr = dioxus::cli_config::fullstack_address_or_localhost();
|
|
||||||
/// let router = axum::Router::new()
|
|
||||||
/// // Register server functions routes with the default handler
|
|
||||||
/// .register_server_functions()
|
|
||||||
/// .into_make_service();
|
|
||||||
/// let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
|
||||||
/// axum::serve(listener, router).await.unwrap();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
fn register_server_functions(self) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
self.register_server_functions_with_context(Default::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Registers server functions with some additional context to insert into the [`DioxusServerContext`] for that handler.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```rust, no_run
|
|
||||||
/// # use dioxus_lib::prelude::*;
|
|
||||||
/// # use dioxus_fullstack::prelude::*;
|
|
||||||
/// # use std::sync::Arc;
|
|
||||||
/// #[tokio::main]
|
|
||||||
/// async fn main() {
|
|
||||||
/// let addr = dioxus::cli_config::fullstack_address_or_localhost();
|
|
||||||
/// let router = axum::Router::new()
|
|
||||||
/// // Register server functions routes with the default handler
|
|
||||||
/// .register_server_functions_with_context(Arc::new(vec![Box::new(|| Box::new(1234567890u32))]))
|
|
||||||
/// .into_make_service();
|
|
||||||
/// let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
|
||||||
/// axum::serve(listener, router).await.unwrap();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
fn register_server_functions_with_context(self, context_providers: ContextProviders) -> Self;
|
|
||||||
|
|
||||||
/// Serves the static WASM for your Dioxus application (except the generated index.html).
|
/// Serves the static WASM for your Dioxus application (except the generated index.html).
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@@ -176,27 +122,6 @@ impl<S> DioxusRouterExt<S> for Router<S>
|
|||||||
where
|
where
|
||||||
S: Send + Sync + Clone + 'static,
|
S: Send + Sync + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn register_server_functions_with_context(
|
|
||||||
mut self,
|
|
||||||
context_providers: ContextProviders,
|
|
||||||
) -> Self {
|
|
||||||
use http::method::Method;
|
|
||||||
|
|
||||||
for (path, method) in server_fn::axum::server_fn_paths() {
|
|
||||||
tracing::trace!("Registering server function: {} {}", method, path);
|
|
||||||
let context_providers = context_providers.clone();
|
|
||||||
let handler = move |req| handle_server_fns_inner(path, context_providers, req);
|
|
||||||
self = match method {
|
|
||||||
Method::GET => self.route(path, get(handler)),
|
|
||||||
Method::POST => self.route(path, post(handler)),
|
|
||||||
Method::PUT => self.route(path, put(handler)),
|
|
||||||
_ => unimplemented!("Unsupported server function method: {}", method),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serve_static_assets(mut self) -> Self {
|
fn serve_static_assets(mut self) -> Self {
|
||||||
use tower_http::services::{ServeDir, ServeFile};
|
use tower_http::services::{ServeDir, ServeFile};
|
||||||
|
|
||||||
@@ -272,262 +197,3 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_request_parts_to_response<B>(
|
|
||||||
headers: hyper::header::HeaderMap,
|
|
||||||
response: &mut axum::response::Response<B>,
|
|
||||||
) {
|
|
||||||
let mut_headers = response.headers_mut();
|
|
||||||
for (key, value) in headers.iter() {
|
|
||||||
mut_headers.insert(key, value.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_server_context(server_context: &DioxusServerContext, context_providers: &ContextProviders) {
|
|
||||||
for index in 0..context_providers.len() {
|
|
||||||
let context_providers = context_providers.clone();
|
|
||||||
server_context.insert_boxed_factory(Box::new(move || context_providers[index]()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// State used by [`render_handler`] to render a dioxus component with axum
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct RenderHandleState {
|
|
||||||
config: ServeConfig,
|
|
||||||
build_virtual_dom: Arc<dyn Fn() -> VirtualDom + Send + Sync>,
|
|
||||||
ssr_state: once_cell::sync::OnceCell<SSRState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderHandleState {
|
|
||||||
/// Create a new [`RenderHandleState`]
|
|
||||||
pub fn new(config: ServeConfig, root: fn() -> Element) -> Self {
|
|
||||||
Self {
|
|
||||||
config,
|
|
||||||
build_virtual_dom: Arc::new(move || VirtualDom::new(root)),
|
|
||||||
ssr_state: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new [`RenderHandleState`] with a custom [`VirtualDom`] factory. This method can be used to pass context into the root component of your application.
|
|
||||||
pub fn new_with_virtual_dom_factory(
|
|
||||||
config: ServeConfig,
|
|
||||||
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
config,
|
|
||||||
build_virtual_dom: Arc::new(build_virtual_dom),
|
|
||||||
ssr_state: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the [`ServeConfig`] for this [`RenderHandleState`]
|
|
||||||
pub fn with_config(mut self, config: ServeConfig) -> Self {
|
|
||||||
self.config = config;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the [`SSRState`] for this [`RenderHandleState`]. Sharing a [`SSRState`] between multiple [`RenderHandleState`]s is more efficient than creating a new [`SSRState`] for each [`RenderHandleState`].
|
|
||||||
pub fn with_ssr_state(mut self, ssr_state: SSRState) -> Self {
|
|
||||||
self.ssr_state = once_cell::sync::OnceCell::new();
|
|
||||||
if self.ssr_state.set(ssr_state).is_err() {
|
|
||||||
panic!("SSRState already set");
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ssr_state(&self) -> &SSRState {
|
|
||||||
self.ssr_state.get_or_init(|| SSRState::new(&self.config))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// SSR renderer handler for Axum with added context injection.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// #![allow(non_snake_case)]
|
|
||||||
/// use std::sync::{Arc, Mutex};
|
|
||||||
///
|
|
||||||
/// use axum::routing::get;
|
|
||||||
/// use dioxus::prelude::*;
|
|
||||||
///
|
|
||||||
/// fn app() -> Element {
|
|
||||||
/// rsx! {
|
|
||||||
/// "hello!"
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// #[tokio::main]
|
|
||||||
/// async fn main() {
|
|
||||||
/// let addr = dioxus::cli_config::fullstack_address_or_localhost();
|
|
||||||
/// let router = axum::Router::new()
|
|
||||||
/// // Register server functions, etc.
|
|
||||||
/// // Note you can use `register_server_functions_with_context`
|
|
||||||
/// // to inject the context into server functions running outside
|
|
||||||
/// // of an SSR render context.
|
|
||||||
/// .fallback(get(render_handler)
|
|
||||||
/// .with_state(RenderHandleState::new(ServeConfig::new().unwrap(), app))
|
|
||||||
/// )
|
|
||||||
/// .into_make_service();
|
|
||||||
/// let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
|
||||||
/// axum::serve(listener, router).await.unwrap();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub async fn render_handler(
|
|
||||||
State(state): State<RenderHandleState>,
|
|
||||||
request: Request<Body>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
let cfg = &state.config;
|
|
||||||
let ssr_state = state.ssr_state();
|
|
||||||
let build_virtual_dom = {
|
|
||||||
let build_virtual_dom = state.build_virtual_dom.clone();
|
|
||||||
let context_providers = state.config.context_providers.clone();
|
|
||||||
move || {
|
|
||||||
let mut vdom = build_virtual_dom();
|
|
||||||
for state in context_providers.as_slice() {
|
|
||||||
vdom.insert_any_root_context(state());
|
|
||||||
}
|
|
||||||
vdom
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let (parts, _) = request.into_parts();
|
|
||||||
let url = parts
|
|
||||||
.uri
|
|
||||||
.path_and_query()
|
|
||||||
.ok_or(StatusCode::BAD_REQUEST)?
|
|
||||||
.to_string();
|
|
||||||
let parts: Arc<parking_lot::RwLock<http::request::Parts>> =
|
|
||||||
Arc::new(parking_lot::RwLock::new(parts));
|
|
||||||
// Create the server context with info from the request
|
|
||||||
let server_context = DioxusServerContext::from_shared_parts(parts.clone());
|
|
||||||
// Provide additional context from the render state
|
|
||||||
add_server_context(&server_context, &state.config.context_providers);
|
|
||||||
|
|
||||||
match ssr_state
|
|
||||||
.render(url, cfg, build_virtual_dom, &server_context)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok((freshness, rx)) => {
|
|
||||||
let mut response = axum::response::Html::from(Body::from_stream(rx)).into_response();
|
|
||||||
freshness.write(response.headers_mut());
|
|
||||||
let headers = server_context.response_parts().headers.clone();
|
|
||||||
apply_request_parts_to_response(headers, &mut response);
|
|
||||||
Result::<http::Response<axum::body::Body>, StatusCode>::Ok(response)
|
|
||||||
}
|
|
||||||
Err(SSRError::Incremental(e)) => {
|
|
||||||
tracing::error!("Failed to render page: {}", e);
|
|
||||||
Ok(report_err(e).into_response())
|
|
||||||
}
|
|
||||||
Err(SSRError::Routing(e)) => {
|
|
||||||
tracing::trace!("Page not found: {}", e);
|
|
||||||
Ok(Response::builder()
|
|
||||||
.status(StatusCode::NOT_FOUND)
|
|
||||||
.body(Body::from("Page not found"))
|
|
||||||
.unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn report_err<E: std::fmt::Display>(e: E) -> Response<axum::body::Body> {
|
|
||||||
Response::builder()
|
|
||||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
.body(body::Body::new(format!("Error: {}", e)))
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A handler for Dioxus server functions. This will run the server function and return the result.
|
|
||||||
async fn handle_server_fns_inner(
|
|
||||||
path: &str,
|
|
||||||
additional_context: ContextProviders,
|
|
||||||
req: Request<Body>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
use server_fn::middleware::Service;
|
|
||||||
|
|
||||||
let path_string = path.to_string();
|
|
||||||
|
|
||||||
let future = move || async move {
|
|
||||||
let (parts, body) = req.into_parts();
|
|
||||||
let req = Request::from_parts(parts.clone(), body);
|
|
||||||
let method = req.method().clone();
|
|
||||||
|
|
||||||
if let Some(mut service) =
|
|
||||||
server_fn::axum::get_server_fn_service(&path_string, method)
|
|
||||||
{
|
|
||||||
// Create the server context with info from the request
|
|
||||||
let server_context = DioxusServerContext::new(parts);
|
|
||||||
// Provide additional context from the render state
|
|
||||||
add_server_context(&server_context, &additional_context);
|
|
||||||
|
|
||||||
// store Accepts and Referrer in case we need them for redirect (below)
|
|
||||||
let accepts_html = req
|
|
||||||
.headers()
|
|
||||||
.get(ACCEPT)
|
|
||||||
.and_then(|v| v.to_str().ok())
|
|
||||||
.map(|v| v.contains("text/html"))
|
|
||||||
.unwrap_or(false);
|
|
||||||
let referrer = req.headers().get(REFERER).cloned();
|
|
||||||
|
|
||||||
// actually run the server fn (which may use the server context)
|
|
||||||
let fut = with_server_context(server_context.clone(), || service.run(req));
|
|
||||||
let mut res = ProvideServerContext::new(fut, server_context.clone()).await;
|
|
||||||
|
|
||||||
// it it accepts text/html (i.e., is a plain form post) and doesn't already have a
|
|
||||||
// Location set, then redirect to Referer
|
|
||||||
if accepts_html {
|
|
||||||
if let Some(referrer) = referrer {
|
|
||||||
let has_location = res.headers().get(LOCATION).is_some();
|
|
||||||
if !has_location {
|
|
||||||
*res.status_mut() = StatusCode::FOUND;
|
|
||||||
res.headers_mut().insert(LOCATION, referrer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply the response parts from the server context to the response
|
|
||||||
let mut res_options = server_context.response_parts_mut();
|
|
||||||
res.headers_mut().extend(res_options.headers.drain());
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
} else {
|
|
||||||
Response::builder().status(StatusCode::BAD_REQUEST).body(
|
|
||||||
{
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
{
|
|
||||||
Body::from(format!(
|
|
||||||
"No server function found for path: {path_string}\nYou may need to explicitly register the server function with `register_explicit`, rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
{
|
|
||||||
Body::from(format!(
|
|
||||||
"No server function found for path: {path_string}\nYou may need to rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.expect("could not build Response")
|
|
||||||
};
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
{
|
|
||||||
use futures_util::future::FutureExt;
|
|
||||||
|
|
||||||
let result = tokio::task::spawn_local(future);
|
|
||||||
let result = result.then(|f| async move { f.unwrap() });
|
|
||||||
result.await.unwrap_or_else(|e| {
|
|
||||||
use server_fn::error::NoCustomError;
|
|
||||||
use server_fn::error::ServerFnErrorSerde;
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
ServerFnError::<NoCustomError>::ServerError(e.to_string())
|
|
||||||
.ser()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)
|
|
||||||
.into_response()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
{
|
|
||||||
future().await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -33,7 +33,7 @@ dioxus-cli-config = { workspace = true }
|
|||||||
generational-box = { workspace = true }
|
generational-box = { workspace = true }
|
||||||
|
|
||||||
# axum
|
# axum
|
||||||
axum = { workspace = true, optional = true, features = ["ws"] }
|
axum = { workspace = true, optional = true, default-features = true, features = ["ws"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
|
|||||||
@@ -10,4 +10,4 @@ publish = false
|
|||||||
dioxus = { workspace = true }
|
dioxus = { workspace = true }
|
||||||
dioxus-liveview = { workspace = true, features = ["axum"] }
|
dioxus-liveview = { workspace = true, features = ["axum"] }
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
axum = { workspace = true, features = ["ws"] }
|
axum = { workspace = true, default-features = true, features = ["ws"] }
|
||||||
Reference in new issue
Block a user