bump server functions
This commit is contained in:
1 parent
6879507652
commit
e745212157
13 files changed
+297
-616
No files matched your search
Generated
+93
-41
@@ -622,6 +622,7 @@ dependencies = [
|
|||||||
"matchit",
|
"matchit",
|
||||||
"memchr",
|
"memchr",
|
||||||
"mime",
|
"mime",
|
||||||
|
"multer",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
@@ -2692,7 +2693,7 @@ dependencies = [
|
|||||||
"dioxus-router-macro",
|
"dioxus-router-macro",
|
||||||
"dioxus-ssr",
|
"dioxus-ssr",
|
||||||
"gloo",
|
"gloo",
|
||||||
"gloo-utils",
|
"gloo-utils 0.1.7",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -4086,7 +4087,7 @@ dependencies = [
|
|||||||
"gloo-render",
|
"gloo-render",
|
||||||
"gloo-storage",
|
"gloo-storage",
|
||||||
"gloo-timers",
|
"gloo-timers",
|
||||||
"gloo-utils",
|
"gloo-utils 0.1.7",
|
||||||
"gloo-worker",
|
"gloo-worker",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4096,7 +4097,7 @@ version = "0.2.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f"
|
checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gloo-utils",
|
"gloo-utils 0.1.7",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -4142,7 +4143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f"
|
checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gloo-events",
|
"gloo-events",
|
||||||
"gloo-utils",
|
"gloo-utils 0.1.7",
|
||||||
"serde",
|
"serde",
|
||||||
"serde-wasm-bindgen",
|
"serde-wasm-bindgen",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
@@ -4151,26 +4152,6 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gloo-net"
|
|
||||||
version = "0.2.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10"
|
|
||||||
dependencies = [
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
"gloo-utils",
|
|
||||||
"js-sys",
|
|
||||||
"pin-project",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gloo-net"
|
name = "gloo-net"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -4180,7 +4161,28 @@ dependencies = [
|
|||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"gloo-utils",
|
"gloo-utils 0.1.7",
|
||||||
|
"http 0.2.11",
|
||||||
|
"js-sys",
|
||||||
|
"pin-project",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gloo-net"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"gloo-utils 0.2.0",
|
||||||
"http 0.2.11",
|
"http 0.2.11",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
@@ -4208,7 +4210,7 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480"
|
checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gloo-utils",
|
"gloo-utils 0.1.7",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -4240,6 +4242,19 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gloo-utils"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gloo-worker"
|
name = "gloo-worker"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -4249,7 +4264,7 @@ dependencies = [
|
|||||||
"anymap2",
|
"anymap2",
|
||||||
"bincode",
|
"bincode",
|
||||||
"gloo-console",
|
"gloo-console",
|
||||||
"gloo-utils",
|
"gloo-utils 0.1.7",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -6008,6 +6023,24 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multer"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a15d522be0a9c3e46fd2632e272d178f56387bdb5c9fbb3a36c649062e9b5219"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"encoding_rs",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.0.0",
|
||||||
|
"httparse",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"mime",
|
||||||
|
"spin 0.9.8",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "names"
|
name = "names"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -7608,6 +7641,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
@@ -8097,6 +8131,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "send_wrapper"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "separator"
|
name = "separator"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -8260,49 +8303,58 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "server_fn"
|
name = "server_fn"
|
||||||
version = "0.5.7"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c265de965fe48e09ad8899d0ab1ffebdfa1a9914e4de5ff107b07bd94cf7541"
|
checksum = "97fab54d9dd2d7e9eba4efccac41d2ec3e7c6e9973d14c0486d662a32662320c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ciborium",
|
"axum",
|
||||||
|
"bytes",
|
||||||
"const_format",
|
"const_format",
|
||||||
"gloo-net 0.2.6",
|
"dashmap",
|
||||||
|
"futures",
|
||||||
|
"gloo-net 0.5.0",
|
||||||
|
"http 1.0.0",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper 1.1.0",
|
||||||
"inventory",
|
"inventory",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"lazy_static",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"send_wrapper",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_qs",
|
"serde_qs",
|
||||||
"server_fn_macro_default",
|
"server_fn_macro_default",
|
||||||
"syn 2.0.49",
|
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"wasm-streams",
|
||||||
|
"web-sys",
|
||||||
"xxhash-rust",
|
"xxhash-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "server_fn_macro"
|
name = "server_fn_macro"
|
||||||
version = "0.5.7"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f77000541a62ceeec01eef3ee0f86c155c33dac5fae750ad04a40852c6d5469a"
|
checksum = "3be6011b586a0665546b7ced372b0be690d9e005d3f8524795da2843274d7720"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const_format",
|
"const_format",
|
||||||
"proc-macro-error",
|
"convert_case 0.6.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde",
|
|
||||||
"syn 2.0.49",
|
"syn 2.0.49",
|
||||||
"xxhash-rust",
|
"xxhash-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "server_fn_macro_default"
|
name = "server_fn_macro_default"
|
||||||
version = "0.5.7"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a3353f22e2bcc451074d4feaa37317d9d17dff11d4311928384734ea17ab9ca"
|
checksum = "752ed78ec49132d154b922cf5ab6485680cab039a75740c48ea2db621ad481da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"server_fn_macro",
|
"server_fn_macro",
|
||||||
"syn 2.0.49",
|
"syn 2.0.49",
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ resolver = "2"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# server functions
|
# server functions
|
||||||
server_fn = { version = "0.5.2", default-features = false }
|
server_fn = { version = "0.6.5", features = ["json", "url", "browser"], default-features = false }
|
||||||
dioxus_server_macro = { workspace = true }
|
dioxus_server_macro = { workspace = true, version = "0.6.5", default-features = false }
|
||||||
|
|
||||||
|
|
||||||
# axum
|
# axum
|
||||||
axum = { workspace = true, features = ["ws", "macros"], default-features = false, optional = true }
|
axum = { workspace = true, features = ["ws", "macros"], default-features = false, optional = true }
|
||||||
@@ -71,7 +70,7 @@ desktop = ["dioxus-desktop"]
|
|||||||
mobile = ["dioxus-mobile"]
|
mobile = ["dioxus-mobile"]
|
||||||
default-tls = ["server_fn/default-tls"]
|
default-tls = ["server_fn/default-tls"]
|
||||||
rustls = ["server_fn/rustls"]
|
rustls = ["server_fn/rustls"]
|
||||||
axum = ["dep:axum", "tower-http", "server"]
|
axum = ["dep:axum", "tower-http", "server", "server_fn/axum", "dioxus_server_macro/axum"]
|
||||||
server = [
|
server = [
|
||||||
"server_fn/ssr",
|
"server_fn/ssr",
|
||||||
"dioxus_server_macro/server",
|
"dioxus_server_macro/server",
|
||||||
|
|||||||
@@ -57,61 +57,25 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
body::{self, Body},
|
body::{self, Body},
|
||||||
extract::State,
|
extract::State,
|
||||||
handler::Handler,
|
|
||||||
http::{Request, Response, StatusCode},
|
http::{Request, Response, StatusCode},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use dioxus_lib::prelude::VirtualDom;
|
use dioxus_lib::prelude::VirtualDom;
|
||||||
use server_fn::{Encoding, ServerFunctionRegistry};
|
use server_fn::error::NoCustomError;
|
||||||
|
use server_fn::error::ServerFnErrorSerde;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
use axum::routing::*;
|
||||||
|
use http::header::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*, render::SSRState, serve_config::ServeConfig, server_context::DioxusServerContext,
|
prelude::*, render::SSRState, serve_config::ServeConfig, server_context::DioxusServerContext,
|
||||||
server_fn::collection::DioxusServerFnRegistry,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// 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> {
|
||||||
/// Registers server functions with a custom handler function. This allows you to pass custom context to your server functions by generating a [`DioxusServerContext`] from the request.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```rust
|
|
||||||
/// use dioxus_lib::prelude::*;
|
|
||||||
/// use dioxus_fullstack::prelude::*;
|
|
||||||
///
|
|
||||||
/// #[tokio::main]
|
|
||||||
/// async fn main() {
|
|
||||||
/// let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
|
|
||||||
/// axum::Server::bind(&addr)
|
|
||||||
/// .serve(
|
|
||||||
/// axum::Router::new()
|
|
||||||
/// .register_server_fns_with_handler("", |func| {
|
|
||||||
/// move |req: Request<Body>| async move {
|
|
||||||
/// let (parts, body) = req.into_parts();
|
|
||||||
/// 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
|
|
||||||
/// }
|
|
||||||
/// })
|
|
||||||
/// .into_make_service(),
|
|
||||||
/// )
|
|
||||||
/// .await
|
|
||||||
/// .unwrap();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
fn register_server_fns_with_handler<H, T>(
|
|
||||||
self,
|
|
||||||
server_fn_route: &'static str,
|
|
||||||
handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
H: Handler<T, S>,
|
|
||||||
T: 'static,
|
|
||||||
S: Clone + Send + Sync + 'static;
|
|
||||||
|
|
||||||
/// Registers server functions with the default handler. This handler function will pass an empty [`DioxusServerContext`] to your server functions.
|
/// Registers server functions with the default handler. This handler function will pass an empty [`DioxusServerContext`] to your server functions.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@@ -133,7 +97,7 @@ pub trait DioxusRouterExt<S> {
|
|||||||
/// .unwrap();
|
/// .unwrap();
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
fn register_server_fns(self, server_fn_route: &'static str) -> Self;
|
fn register_server_fns(self) -> Self;
|
||||||
|
|
||||||
/// Register the web RSX hot reloading endpoint. This will enable hot reloading for your application in debug mode when you call [`dioxus_hot_reload::hot_reload_init`].
|
/// Register the web RSX hot reloading endpoint. This will enable hot reloading for your application in debug mode when you call [`dioxus_hot_reload::hot_reload_init`].
|
||||||
///
|
///
|
||||||
@@ -218,7 +182,6 @@ pub trait DioxusRouterExt<S> {
|
|||||||
/// ```
|
/// ```
|
||||||
fn serve_dioxus_application(
|
fn serve_dioxus_application(
|
||||||
self,
|
self,
|
||||||
server_fn_route: &'static str,
|
|
||||||
cfg: impl Into<ServeConfig>,
|
cfg: impl Into<ServeConfig>,
|
||||||
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
||||||
) -> Self;
|
) -> Self;
|
||||||
@@ -228,51 +191,20 @@ impl<S> DioxusRouterExt<S> for Router<S>
|
|||||||
where
|
where
|
||||||
S: Send + Sync + Clone + 'static,
|
S: Send + Sync + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn register_server_fns_with_handler<H, T>(
|
fn register_server_fns(mut self) -> Self {
|
||||||
self,
|
use http::method::Method;
|
||||||
server_fn_route: &'static str,
|
|
||||||
mut handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
H: Handler<T, S>,
|
|
||||||
T: 'static,
|
|
||||||
S: Clone + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
let mut router = self;
|
|
||||||
for server_fn_path in DioxusServerFnRegistry::paths_registered() {
|
|
||||||
let func = DioxusServerFnRegistry::get(server_fn_path).unwrap();
|
|
||||||
let full_route = format!("{server_fn_route}/{server_fn_path}");
|
|
||||||
match func.encoding() {
|
|
||||||
Encoding::Url | Encoding::Cbor => {
|
|
||||||
router = router.route(&full_route, post(handler(func)));
|
|
||||||
}
|
|
||||||
Encoding::GetJSON | Encoding::GetCBOR => {
|
|
||||||
router = router.route(&full_route, get(handler(func)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
router
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_server_fns(self, server_fn_route: &'static str) -> Self {
|
for (path, method) in server_fn::axum::server_fn_paths() {
|
||||||
self.register_server_fns_with_handler(server_fn_route, |func| {
|
let handler = move |req| handle_server_fns_inner(path, ||{}, req);
|
||||||
move |req: Request<Body>| {
|
self = match method {
|
||||||
let mut service = crate::server_fn_service(Default::default(), func);
|
Method::GET => self.route(path, get(handler)),
|
||||||
async move {
|
Method::POST => self.route(path, post(handler)),
|
||||||
let (req, body) = req.into_parts();
|
Method::PUT => self.route(path, put(handler)),
|
||||||
let req = Request::from_parts(req, body);
|
_ => todo!()
|
||||||
let res = service.run(req);
|
};
|
||||||
match res.await {
|
}
|
||||||
Ok(res) => Ok::<_, std::convert::Infallible>(res.map(|b| b.into())),
|
|
||||||
Err(e) => {
|
self
|
||||||
let mut res = Response::new(Body::from(e.to_string()));
|
|
||||||
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serve_static_assets(mut self, assets_path: impl Into<std::path::PathBuf>) -> Self {
|
fn serve_static_assets(mut self, assets_path: impl Into<std::path::PathBuf>) -> Self {
|
||||||
@@ -317,7 +249,6 @@ where
|
|||||||
|
|
||||||
fn serve_dioxus_application(
|
fn serve_dioxus_application(
|
||||||
self,
|
self,
|
||||||
server_fn_route: &'static str,
|
|
||||||
cfg: impl Into<ServeConfig>,
|
cfg: impl Into<ServeConfig>,
|
||||||
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -327,7 +258,7 @@ where
|
|||||||
// Add server functions and render index.html
|
// Add server functions and render index.html
|
||||||
self.serve_static_assets(cfg.assets_path.clone())
|
self.serve_static_assets(cfg.assets_path.clone())
|
||||||
.connect_hot_reload()
|
.connect_hot_reload()
|
||||||
.register_server_fns(server_fn_route)
|
.register_server_fns()
|
||||||
.fallback(get(render_handler).with_state((cfg, Arc::new(build_virtual_dom), ssr_state)))
|
.fallback(get(render_handler).with_state((cfg, Arc::new(build_virtual_dom), ssr_state)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,3 +447,104 @@ pub async fn hot_reload_handler(ws: axum::extract::WebSocketUpgrade) -> impl Int
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_local_pool() -> tokio_util::task::LocalPoolHandle {
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
static LOCAL_POOL: OnceCell<tokio_util::task::LocalPoolHandle> = OnceCell::new();
|
||||||
|
LOCAL_POOL
|
||||||
|
.get_or_init(|| {
|
||||||
|
tokio_util::task::LocalPoolHandle::new(
|
||||||
|
std::thread::available_parallelism()
|
||||||
|
.map(Into::into)
|
||||||
|
.unwrap_or(1),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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: impl Fn() + 'static + Clone + Send,
|
||||||
|
req: Request<Body>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
use server_fn::middleware::Service;
|
||||||
|
|
||||||
|
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||||
|
let path_string = path.to_string();
|
||||||
|
|
||||||
|
get_local_pool().spawn_pinned(move || async move {
|
||||||
|
let (parts, body) = req.into_parts();
|
||||||
|
let req = Request::from_parts(parts.clone(), body);
|
||||||
|
|
||||||
|
|
||||||
|
let res = if let Some(mut service) =
|
||||||
|
server_fn::axum::get_server_fn_service(&path_string)
|
||||||
|
{
|
||||||
|
|
||||||
|
let server_context = DioxusServerContext::new(Arc::new(RwLock::new(parts)));
|
||||||
|
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
|
||||||
|
let mut res = service.run(req).await;
|
||||||
|
|
||||||
|
|
||||||
|
// it it accepts text/html (i.e., is a plain form post) and doesn't already have a
|
||||||
|
// Location set, then redirect to 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().unwrap();
|
||||||
|
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");
|
||||||
|
|
||||||
|
_ = tx.send(res);
|
||||||
|
});
|
||||||
|
|
||||||
|
rx.await.unwrap_or_else(|e| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ServerFnError::<NoCustomError>::ServerError(e.to_string())
|
||||||
|
.ser()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -129,7 +129,7 @@ impl Config {
|
|||||||
use tower::ServiceBuilder;
|
use tower::ServiceBuilder;
|
||||||
|
|
||||||
let ssr_state = SSRState::new(&cfg);
|
let ssr_state = SSRState::new(&cfg);
|
||||||
let router = axum::Router::new().register_server_fns(server_fn_route);
|
let router = axum::Router::new().register_server_fns();
|
||||||
#[cfg(not(any(feature = "desktop", feature = "mobile")))]
|
#[cfg(not(any(feature = "desktop", feature = "mobile")))]
|
||||||
let router = router
|
let router = router
|
||||||
.serve_static_assets(cfg.assets_path.clone())
|
.serve_static_assets(cfg.assets_path.clone())
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
use std::pin::Pin;
|
|
||||||
use tracing_futures::Instrument;
|
|
||||||
|
|
||||||
use http::{Request, Response};
|
|
||||||
|
|
||||||
/// A layer that wraps a service. This can be used to add additional information to the request, or response on top of some other service
|
|
||||||
pub trait Layer: Send + Sync + 'static {
|
|
||||||
/// Wrap a boxed service with this layer
|
|
||||||
fn layer(&self, inner: BoxedService) -> BoxedService;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<L> Layer for L
|
|
||||||
where
|
|
||||||
L: tower_layer::Layer<BoxedService> + Sync + Send + 'static,
|
|
||||||
L::Service: Service + Send + 'static,
|
|
||||||
{
|
|
||||||
fn layer(&self, inner: BoxedService) -> BoxedService {
|
|
||||||
BoxedService(Box::new(self.layer(inner)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MyBody = axum::body::Body;
|
|
||||||
|
|
||||||
/// A service is a function that takes a request and returns an async response
|
|
||||||
pub trait Service {
|
|
||||||
/// Run the service and produce a future that resolves to a response
|
|
||||||
fn run(
|
|
||||||
&mut self,
|
|
||||||
req: http::Request<MyBody>,
|
|
||||||
) -> Pin<
|
|
||||||
Box<
|
|
||||||
dyn std::future::Future<Output = Result<Response<MyBody>, server_fn::ServerFnError>>
|
|
||||||
+ Send,
|
|
||||||
>,
|
|
||||||
>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> Service for S
|
|
||||||
where
|
|
||||||
S: tower::Service<http::Request<MyBody>, Response = Response<MyBody>>,
|
|
||||||
S::Future: Send + 'static,
|
|
||||||
S::Error: Into<server_fn::ServerFnError>,
|
|
||||||
{
|
|
||||||
fn run(
|
|
||||||
&mut self,
|
|
||||||
req: http::Request<MyBody>,
|
|
||||||
) -> Pin<
|
|
||||||
Box<
|
|
||||||
dyn std::future::Future<Output = Result<Response<MyBody>, server_fn::ServerFnError>>
|
|
||||||
+ Send,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
let fut = self.call(req).instrument(tracing::trace_span!(
|
|
||||||
"service",
|
|
||||||
"{}",
|
|
||||||
std::any::type_name::<S>()
|
|
||||||
));
|
|
||||||
Box::pin(async move { fut.await.map_err(|err| err.into()) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A boxed service is a type-erased service that can be used without knowing the underlying type
|
|
||||||
pub struct BoxedService(pub Box<dyn Service + Send>);
|
|
||||||
|
|
||||||
impl tower::Service<http::Request<MyBody>> for BoxedService {
|
|
||||||
type Response = http::Response<MyBody>;
|
|
||||||
type Error = server_fn::ServerFnError;
|
|
||||||
type Future = Pin<
|
|
||||||
Box<
|
|
||||||
dyn std::future::Future<
|
|
||||||
Output = Result<http::Response<MyBody>, server_fn::ServerFnError>,
|
|
||||||
> + Send,
|
|
||||||
>,
|
|
||||||
>;
|
|
||||||
|
|
||||||
fn poll_ready(
|
|
||||||
&mut self,
|
|
||||||
_cx: &mut std::task::Context<'_>,
|
|
||||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
|
||||||
Ok(()).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, req: Request<MyBody>) -> Self::Future {
|
|
||||||
self.0.run(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,21 +12,14 @@ mod html_storage;
|
|||||||
#[cfg(feature = "axum")]
|
#[cfg(feature = "axum")]
|
||||||
mod axum_adapter;
|
mod axum_adapter;
|
||||||
|
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
pub use server_fn::service::{server_fn_service, ServerFnHandler};
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod hooks;
|
mod hooks;
|
||||||
pub mod launch;
|
pub mod launch;
|
||||||
mod server_fn;
|
|
||||||
|
|
||||||
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "server"))]
|
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "server"))]
|
||||||
mod hot_reload;
|
mod hot_reload;
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
mod layer;
|
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
mod render;
|
mod render;
|
||||||
@@ -50,10 +43,6 @@ pub mod prelude {
|
|||||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "server"))))]
|
#[cfg_attr(docsrs, doc(cfg(not(feature = "server"))))]
|
||||||
pub use crate::html_storage::deserialize::get_root_props_from_document;
|
pub use crate::html_storage::deserialize::get_root_props_from_document;
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
|
||||||
pub use crate::layer::{Layer, Service};
|
|
||||||
|
|
||||||
#[cfg(all(feature = "server", feature = "router"))]
|
#[cfg(all(feature = "server", feature = "router"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "server", feature = "router"))))]
|
#[cfg_attr(docsrs, doc(cfg(all(feature = "server", feature = "router"))))]
|
||||||
pub use crate::render::pre_cache_static_routes_with_props;
|
pub use crate::render::pre_cache_static_routes_with_props;
|
||||||
@@ -79,17 +68,13 @@ pub mod prelude {
|
|||||||
pub use crate::server_context::{
|
pub use crate::server_context::{
|
||||||
extract, server_context, DioxusServerContext, FromServerContext, ProvideServerContext,
|
extract, server_context, DioxusServerContext, FromServerContext, ProvideServerContext,
|
||||||
};
|
};
|
||||||
pub use crate::server_fn::DioxusServerFn;
|
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
|
||||||
pub use crate::server_fn::collection::{ServerFnMiddleware, ServerFnTraitObj, ServerFunction};
|
|
||||||
pub use dioxus_server_macro::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||||
pub use dioxus_ssr::incremental::IncrementalRendererConfig;
|
pub use dioxus_ssr::incremental::IncrementalRendererConfig;
|
||||||
|
|
||||||
pub use server_fn::{self, ServerFn as _, ServerFnError};
|
pub use server_fn::{self, ServerFn as _, ServerFnError};
|
||||||
|
pub use dioxus_server_macro::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Warn users about overlapping features
|
// // Warn users about overlapping features
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
#[cfg(feature = "server")]
|
|
||||||
pub(crate)mod collection;
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
pub mod service;
|
|
||||||
|
|
||||||
/// Defines a "server function." A server function can be called from the server or the client,
|
|
||||||
/// but the body of its code will only be run on the server, i.e., if a crate feature `ssr` is enabled.
|
|
||||||
///
|
|
||||||
/// Server functions are created using the `server` macro.
|
|
||||||
///
|
|
||||||
/// The set of server functions
|
|
||||||
/// 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<()> {
|
|
||||||
/// Registers the server function, allowing the client to query it by URL.
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
|
||||||
fn register_explicit() -> Result<(), server_fn::ServerFnError> {
|
|
||||||
Self::register_in_explicit::<crate::server_fn::collection::DioxusServerFnRegistry>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> DioxusServerFn for T where T: server_fn::ServerFn<()> {}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
//! # Server function Service
|
|
||||||
|
|
||||||
//! This module defines a service that can be used to handle server functions.
|
|
||||||
|
|
||||||
use http::StatusCode;
|
|
||||||
use server_fn::{Encoding, Payload};
|
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
|
|
||||||
use crate::server_fn::collection::MIDDLEWARE;
|
|
||||||
use crate::{
|
|
||||||
layer::{BoxedService, Service},
|
|
||||||
prelude::{DioxusServerContext, ProvideServerContext},
|
|
||||||
};
|
|
||||||
|
|
||||||
type AxumBody = axum::body::Body;
|
|
||||||
|
|
||||||
/// Create a server function handler with the given server context and server function.
|
|
||||||
pub fn server_fn_service(
|
|
||||||
context: DioxusServerContext,
|
|
||||||
function: server_fn::ServerFnTraitObj<()>,
|
|
||||||
) -> crate::layer::BoxedService {
|
|
||||||
let prefix = function.prefix().to_string();
|
|
||||||
let url = function.url().to_string();
|
|
||||||
if let Some(middleware) = MIDDLEWARE.get(&(&prefix, &url)) {
|
|
||||||
let mut service = BoxedService(Box::new(ServerFnHandler::new(context, function)));
|
|
||||||
for middleware in middleware {
|
|
||||||
service = middleware.layer(service);
|
|
||||||
}
|
|
||||||
service
|
|
||||||
} else {
|
|
||||||
BoxedService(Box::new(ServerFnHandler::new(context, function)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
/// 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<()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<()>,
|
|
||||||
) -> Self {
|
|
||||||
let server_context = server_context.into();
|
|
||||||
Self {
|
|
||||||
server_context,
|
|
||||||
function,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Service for ServerFnHandler {
|
|
||||||
fn run(
|
|
||||||
&mut self,
|
|
||||||
req: http::Request<AxumBody>,
|
|
||||||
) -> std::pin::Pin<
|
|
||||||
Box<
|
|
||||||
dyn std::future::Future<
|
|
||||||
Output = Result<http::Response<AxumBody>, server_fn::ServerFnError>,
|
|
||||||
> + Send,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
let Self {
|
|
||||||
server_context,
|
|
||||||
function,
|
|
||||||
} = self.clone();
|
|
||||||
Box::pin(async move {
|
|
||||||
let query = req.uri().query().unwrap_or_default().as_bytes().to_vec();
|
|
||||||
let (parts, body) = req.into_parts();
|
|
||||||
let body = axum::body::to_bytes(body, usize::MAX).await?.to_vec();
|
|
||||||
let headers = &parts.headers;
|
|
||||||
let accept_header = headers.get("Accept").cloned();
|
|
||||||
let parts = Arc::new(RwLock::new(parts));
|
|
||||||
|
|
||||||
// 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 pool = get_local_pool();
|
|
||||||
let result = pool
|
|
||||||
.spawn_pinned({
|
|
||||||
let function = function.clone();
|
|
||||||
let mut server_context = server_context.clone();
|
|
||||||
server_context.parts = parts;
|
|
||||||
move || async move {
|
|
||||||
let data = match function.encoding() {
|
|
||||||
Encoding::Url | Encoding::Cbor => &body,
|
|
||||||
Encoding::GetJSON | Encoding::GetCBOR => &query,
|
|
||||||
};
|
|
||||||
let server_function_future = function.call((), data);
|
|
||||||
let server_function_future = ProvideServerContext::new(
|
|
||||||
server_function_future,
|
|
||||||
server_context.clone(),
|
|
||||||
);
|
|
||||||
server_function_future.await
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
let mut res = http::Response::builder();
|
|
||||||
|
|
||||||
// Set the headers from the server context
|
|
||||||
let parts = server_context.response_parts().unwrap();
|
|
||||||
*res.headers_mut().expect("empty headers should be valid") = parts.headers.clone();
|
|
||||||
|
|
||||||
let serialized = result?;
|
|
||||||
// if this is Accept: application/json then send a serialized JSON response
|
|
||||||
let accept_header = accept_header.as_ref().and_then(|value| value.to_str().ok());
|
|
||||||
if accept_header == Some("application/json")
|
|
||||||
|| accept_header
|
|
||||||
== Some(
|
|
||||||
"application/\
|
|
||||||
x-www-form-urlencoded",
|
|
||||||
)
|
|
||||||
|| accept_header == Some("application/cbor")
|
|
||||||
{
|
|
||||||
res = res.status(StatusCode::OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(match serialized {
|
|
||||||
Payload::Binary(data) => {
|
|
||||||
res = res.header("Content-Type", "application/cbor");
|
|
||||||
res.body(data.into())?
|
|
||||||
}
|
|
||||||
Payload::Url(data) => {
|
|
||||||
res = res.header(
|
|
||||||
"Content-Type",
|
|
||||||
"application/\
|
|
||||||
x-www-form-urlencoded",
|
|
||||||
);
|
|
||||||
res.body(data.into())?
|
|
||||||
}
|
|
||||||
Payload::Json(data) => {
|
|
||||||
res = res.header("Content-Type", "application/json");
|
|
||||||
res.body(data.into())?
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_local_pool() -> tokio_util::task::LocalPoolHandle {
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
static LOCAL_POOL: OnceCell<tokio_util::task::LocalPoolHandle> = OnceCell::new();
|
|
||||||
LOCAL_POOL
|
|
||||||
.get_or_init(|| {
|
|
||||||
tokio_util::task::LocalPoolHandle::new(
|
|
||||||
std::thread::available_parallelism()
|
|
||||||
.map(Into::into)
|
|
||||||
.unwrap_or(1),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.clone()
|
|
||||||
}
|
|
||||||
@@ -43,6 +43,7 @@ dioxus = { workspace = true }
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["hot-reload"]
|
default = ["hot-reload"]
|
||||||
|
axum = ["dep:axum"]
|
||||||
hot-reload = ["dioxus-hot-reload"]
|
hot-reload = ["dioxus-hot-reload"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
#[cfg(feature = "axum")]
|
|
||||||
pub type Config = crate::Config<axum::Router>;
|
pub type Config = crate::Config<axum::Router>;
|
||||||
|
|
||||||
/// Launches the WebView and runs the event loop, with configuration and root props.
|
/// Launches the WebView and runs the event loop, with configuration and root props.
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ mod config;
|
|||||||
mod eval;
|
mod eval;
|
||||||
mod events;
|
mod events;
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
pub mod launch;
|
pub mod launch;
|
||||||
|
|
||||||
pub trait WebsocketTx: SinkExt<String, Error = LiveViewError> {}
|
pub trait WebsocketTx: SinkExt<String, Error = LiveViewError> {}
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ proc-macro2 = "^1.0.63"
|
|||||||
quote = "^1.0.26"
|
quote = "^1.0.26"
|
||||||
syn = { version = "2", features = ["full"] }
|
syn = { version = "2", features = ["full"] }
|
||||||
convert_case = "^0.6.0"
|
convert_case = "^0.6.0"
|
||||||
server_fn_macro = "^0.5.2"
|
server_fn_macro = "^0.6.5"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
actix = ["server_fn_macro/actix"]
|
||||||
|
axum = ["server_fn_macro/axum"]
|
||||||
|
nightly = ["server_fn_macro/nightly"]
|
||||||
server = ["server_fn_macro/ssr"]
|
server = ["server_fn_macro/ssr"]
|
||||||
@@ -2,209 +2,83 @@
|
|||||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||||
|
|
||||||
use convert_case::{Case, Converter};
|
//! This crate contains the dioxus implementation of the #[macro@crate::server] macro without additional context from the server.
|
||||||
use proc_macro::TokenStream;
|
//! See the [server_fn_macro] crate for more information.
|
||||||
use proc_macro2::Literal;
|
|
||||||
use quote::{ToTokens, __private::TokenStream as TokenStream2};
|
|
||||||
use server_fn_macro::*;
|
|
||||||
use syn::{
|
|
||||||
parse::{Parse, ParseStream},
|
|
||||||
Ident, ItemFn, Token,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Declares that a function is a [server function](https://dioxuslabs.com/learn/0.4/reference/fullstack/server_functions). This means that
|
use proc_macro::TokenStream;
|
||||||
/// its body will only run on the server, i.e., when the `ssr` feature is enabled.
|
use server_fn_macro::server_macro_impl;
|
||||||
|
use syn::__private::ToTokens;
|
||||||
|
|
||||||
|
/// Declares that a function is a [server function](https://docs.rs/server_fn/).
|
||||||
|
/// This means that its body will only run on the server, i.e., when the `ssr`
|
||||||
|
/// feature is enabled on this crate.
|
||||||
///
|
///
|
||||||
/// If you call a server function from the client (i.e., when the `csr` or `hydrate` features
|
/// ## Usage
|
||||||
/// are enabled), it will instead make a network request to the server.
|
/// ```rust,ignore
|
||||||
///
|
/// #[server]
|
||||||
/// You can specify one, two, or three arguments to the server function:
|
/// pub async fn blog_posts(
|
||||||
/// 1. *Optional*: A type name that will be used to identify and register the server function
|
/// category: String,
|
||||||
/// (e.g., `MyServerFn`).
|
/// ) -> Result<Vec<BlogPost>, ServerFnError> {
|
||||||
/// 2. *Optional*: A URL prefix at which the function will be mounted when it’s registered
|
/// let posts = load_posts(&category).await?;
|
||||||
/// (e.g., `"/api"`). Defaults to `"/"`.
|
/// // maybe do some other work
|
||||||
/// 3. *Optional*: either `"Cbor"` (specifying that it should use the binary `cbor` format for
|
/// Ok(posts)
|
||||||
/// serialization), `"Url"` (specifying that it should be use a URL-encoded form-data string).
|
|
||||||
/// Defaults to `"Url"`. If you want to use this server function
|
|
||||||
/// using Get instead of Post methods, the encoding must be `"GetCbor"` or `"GetJson"`.
|
|
||||||
///
|
|
||||||
/// The server function itself can take any number of arguments, each of which should be serializable
|
|
||||||
/// and deserializable with `serde`. Optionally, its first argument can be a [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html),
|
|
||||||
/// which will be injected *on the server side.* This can be used to inject the raw HTTP request or other
|
|
||||||
/// server-side context into the server function.
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// # use dioxus_fullstack::prelude::*; use serde::{Serialize, Deserialize};
|
|
||||||
/// # #[derive(Serialize, Deserialize)]
|
|
||||||
/// # pub struct Post { }
|
|
||||||
/// #[server(ReadPosts, "/api")]
|
|
||||||
/// pub async fn read_posts(how_many: u8, query: String) -> Result<Vec<Post>, ServerFnError> {
|
|
||||||
/// // do some work on the server to access the database
|
|
||||||
/// # unimplemented!()
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Note the following:
|
/// ## Named Arguments
|
||||||
/// - **Server functions must be `async`.** Even if the work being done inside the function body
|
///
|
||||||
/// can run synchronously on the server, from the client’s perspective it involves an asynchronous
|
/// You can any combination of the following named arguments:
|
||||||
/// function call.
|
/// - `name`: sets the identifier for the server function’s type, which is a struct created
|
||||||
/// - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
|
/// to hold the arguments (defaults to the function identifier in PascalCase)
|
||||||
/// inside the function body can’t fail, the processes of serialization/deserialization and the
|
/// - `prefix`: a prefix at which the server function handler will be mounted (defaults to `/api`)
|
||||||
/// network call are fallible.
|
/// - `endpoint`: specifies the exact path at which the server function handler will be mounted,
|
||||||
/// - **Return types must implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html).**
|
/// relative to the prefix (defaults to the function name followed by unique hash)
|
||||||
/// This should be fairly obvious: we have to serialize arguments to send them to the server, and we
|
/// - `input`: the encoding for the arguments (defaults to `PostUrl`)
|
||||||
/// need to deserialize the result to return it to the client.
|
/// - `output`: the encoding for the response (defaults to `Json`)
|
||||||
/// - **Arguments must be implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html)
|
/// - `client`: a custom `Client` implementation that will be used for this server fn
|
||||||
/// and [`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html).**
|
/// - `encoding`: (legacy, may be deprecated in future) specifies the encoding, which may be one
|
||||||
/// They are serialized as an `application/x-www-form-urlencoded`
|
/// of the following (not case sensitive)
|
||||||
/// form data using [`serde_urlencoded`](https://docs.rs/serde_urlencoded/latest/serde_urlencoded/) or as `application/cbor`
|
/// - `"Url"`: `POST` request with URL-encoded arguments and JSON response
|
||||||
/// using [`cbor`](https://docs.rs/cbor/latest/cbor/).
|
/// - `"GetUrl"`: `GET` request with URL-encoded arguments and JSON response
|
||||||
/// - **The [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html) comes from the server.** Optionally, the first argument of a server function
|
/// - `"Cbor"`: `POST` request with CBOR-encoded arguments and response
|
||||||
/// can be a [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html). This scope can be used to inject dependencies like the HTTP request
|
/// - `"GetCbor"`: `GET` request with URL-encoded arguments and CBOR response
|
||||||
/// or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client.
|
/// - `req` and `res` specify the HTTP request and response types to be used on the server (these
|
||||||
|
/// should usually only be necessary if you are integrating with a server other than Actix/Axum)
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// #[server(
|
||||||
|
/// name = SomeStructName,
|
||||||
|
/// prefix = "/my_api",
|
||||||
|
/// endpoint = "my_fn",
|
||||||
|
/// input = Cbor,
|
||||||
|
/// output = Json
|
||||||
|
/// )]
|
||||||
|
/// pub async fn my_wacky_server_fn(input: Vec<String>) -> Result<usize, ServerFnError> {
|
||||||
|
/// todo!()
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // expands to
|
||||||
|
/// #[derive(Deserialize, Serialize)]
|
||||||
|
/// struct SomeStructName {
|
||||||
|
/// input: Vec<String>
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl ServerFn for SomeStructName {
|
||||||
|
/// const PATH: &'static str = "/my_api/my_fn";
|
||||||
|
///
|
||||||
|
/// // etc.
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[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 {
|
||||||
// before we pass this off to the server function macro, we apply extractors and middleware
|
match server_macro_impl(
|
||||||
let mut function: syn::ItemFn = match syn::parse(s).map_err(|e| e.to_compile_error()) {
|
args.into(),
|
||||||
Ok(f) => f,
|
s.into(),
|
||||||
Err(e) => return e.into(),
|
Some(syn::parse_quote!(server_fn)),
|
||||||
};
|
"/api",
|
||||||
|
None,
|
||||||
// extract all #[middleware] attributes
|
None,
|
||||||
let mut middlewares: Vec<Middleware> = vec![];
|
) {
|
||||||
function.attrs.retain(|attr| {
|
Err(e) => e.to_compile_error().into(),
|
||||||
if attr.meta.path().is_ident("middleware") {
|
Ok(s) => s.to_token_stream().into(),
|
||||||
if let Ok(middleware) = attr.parse_args() {
|
}
|
||||||
middlewares.push(middleware);
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let ItemFn {
|
|
||||||
attrs,
|
|
||||||
vis,
|
|
||||||
sig,
|
|
||||||
block,
|
|
||||||
} = function;
|
|
||||||
let mapped_body = quote::quote! {
|
|
||||||
#(#attrs)*
|
|
||||||
#vis #sig {
|
|
||||||
#block
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let server_fn_path: syn::Path = syn::parse_quote!(::dioxus::fullstack::prelude::server_fn);
|
|
||||||
let trait_obj_wrapper: syn::Type =
|
|
||||||
syn::parse_quote!(::dioxus::fullstack::prelude::ServerFnTraitObj);
|
|
||||||
let mut args: ServerFnArgs = match syn::parse(args) {
|
|
||||||
Ok(args) => args,
|
|
||||||
Err(e) => return e.to_compile_error().into(),
|
|
||||||
};
|
|
||||||
if args.struct_name.is_none() {
|
|
||||||
let upper_cammel_case_name = Converter::new()
|
|
||||||
.from_case(Case::Snake)
|
|
||||||
.to_case(Case::UpperCamel)
|
|
||||||
.convert(sig.ident.to_string());
|
|
||||||
args.struct_name = Some(Ident::new(&upper_cammel_case_name, sig.ident.span()));
|
|
||||||
}
|
|
||||||
let struct_name = args.struct_name.as_ref().unwrap();
|
|
||||||
match server_macro_impl(
|
|
||||||
quote::quote!(#args),
|
|
||||||
mapped_body,
|
|
||||||
trait_obj_wrapper,
|
|
||||||
None,
|
|
||||||
Some(server_fn_path.clone()),
|
|
||||||
) {
|
|
||||||
Err(e) => e.to_compile_error().into(),
|
|
||||||
Ok(tokens) => quote::quote! {
|
|
||||||
#tokens
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
#server_fn_path::inventory::submit! {
|
|
||||||
::dioxus::fullstack::prelude::ServerFnMiddleware {
|
|
||||||
prefix: #struct_name::PREFIX,
|
|
||||||
url: #struct_name::URL,
|
|
||||||
middleware: || vec![
|
|
||||||
#(
|
|
||||||
std::sync::Arc::new(#middlewares),
|
|
||||||
),*
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.to_token_stream()
|
|
||||||
.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Middleware {
|
|
||||||
expr: syn::Expr,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToTokens for Middleware {
|
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
|
||||||
let expr = &self.expr;
|
|
||||||
tokens.extend(quote::quote! {
|
|
||||||
#expr
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Middleware {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let arg: syn::Expr = input.parse()?;
|
|
||||||
Ok(Middleware { expr: arg })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ServerFnArgs {
|
|
||||||
struct_name: Option<Ident>,
|
|
||||||
_comma: Option<Token![,]>,
|
|
||||||
prefix: Option<Literal>,
|
|
||||||
_comma2: Option<Token![,]>,
|
|
||||||
encoding: Option<Literal>,
|
|
||||||
_comma3: Option<Token![,]>,
|
|
||||||
fn_path: Option<Literal>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToTokens for ServerFnArgs {
|
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
|
||||||
let struct_name = self.struct_name.as_ref().map(|s| quote::quote! { #s, });
|
|
||||||
let prefix = self.prefix.as_ref().map(|p| quote::quote! { #p, });
|
|
||||||
let encoding = self.encoding.as_ref().map(|e| quote::quote! { #e, });
|
|
||||||
let fn_path = self.fn_path.as_ref().map(|f| quote::quote! { #f, });
|
|
||||||
tokens.extend(quote::quote! {
|
|
||||||
#struct_name
|
|
||||||
#prefix
|
|
||||||
#encoding
|
|
||||||
#fn_path
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for ServerFnArgs {
|
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
||||||
let struct_name = input.parse()?;
|
|
||||||
let _comma = input.parse()?;
|
|
||||||
let prefix = input.parse()?;
|
|
||||||
let _comma2 = input.parse()?;
|
|
||||||
let encoding = input.parse()?;
|
|
||||||
let _comma3 = input.parse()?;
|
|
||||||
let fn_path = input.parse()?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
struct_name,
|
|
||||||
_comma,
|
|
||||||
prefix,
|
|
||||||
_comma2,
|
|
||||||
encoding,
|
|
||||||
_comma3,
|
|
||||||
fn_path,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user