make regestering server functions optional

This commit is contained in:
Evan Almloff 2023-06-19 16:08:46 -05:00
parent 8a16eb4cb3
commit 158852e5a0
17 changed files with 106 additions and 79 deletions

View File

@ -16,9 +16,6 @@ fn main() {
use axum::extract::State;
use axum::routing::get;
// Register the server function before starting the server
DoubleServer::register().unwrap();
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {

View File

@ -25,9 +25,6 @@ fn main() {
use axum::routing::get;
use std::sync::Arc;
// Register the server function before starting the server
DoubleServer::register().unwrap();
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {

View File

@ -16,9 +16,6 @@ fn main() {
use axum::extract::State;
use axum::routing::get;
// Register the server function before starting the server
DoubleServer::register().unwrap();
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {

View File

@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "ssr", "fullstack"]
[dependencies]
# server functions
server_fn = { git = "https://github.com/leptos-rs/leptos", rev = "671b1e4a8fff7a2e05bb621ef08e87be2b18ccae", default-features = false, features = ["stable"] }
server_fn = { git = "https://github.com/leptos-rs/leptos", rev = "15a4e54435eb5a539afb75891292bcccd2cc8e85", default-features = false, features = ["stable"] }
dioxus_server_macro = { path = "server-macro" }
# warp

View File

@ -46,7 +46,6 @@ fn main() {
);
#[cfg(feature = "ssr")]
{
GetMeaning::register().unwrap();
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {

View File

@ -8,9 +8,6 @@ use dioxus_fullstack::prelude::*;
#[tokio::main]
async fn main() {
PostServerData::register().unwrap();
GetServerData::register().unwrap();
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
axum::Server::bind(&addr)
.serve(

View File

@ -32,8 +32,6 @@ fn main() {
true
}));
PostServerData::register().unwrap();
GetServerData::register().unwrap();
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {

View File

@ -34,8 +34,6 @@ fn main() {
}));
use axum::extract::State;
PostServerData::register().unwrap();
GetServerData::register().unwrap();
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {

View File

@ -33,8 +33,6 @@ fn main() {
}));
use salvo::prelude::*;
PostServerData::register().unwrap();
GetServerData::register().unwrap();
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {

View File

@ -32,8 +32,6 @@ fn main() {
true
}));
PostServerData::register().unwrap();
GetServerData::register().unwrap();
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {

View File

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

View File

@ -35,7 +35,6 @@ use server_fn_macro::*;
/// ```
///
/// Note the following:
/// - You must **register** the server function by calling `T::register()` somewhere in your main function.
/// - **Server functions must be `async`.** Even if the work being done inside the function body
/// can run synchronously on the server, from the clients perspective it involves an asynchronous
/// function call.
@ -62,6 +61,7 @@ pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
match server_macro_impl(
args.into(),
s.into(),
syn::parse_quote!(::dioxus_fullstack::prelude::ServerFnTraitObj),
Some(context),
Some(syn::parse_quote!(::dioxus_fullstack::prelude::server_fn)),
) {

View File

@ -12,7 +12,6 @@
//! dioxus_web::launch_cfg(app, dioxus_web::Config::new().hydrate(true));
//! #[cfg(feature = "ssr")]
//! {
//! GetServerData::register().unwrap();
//! tokio::runtime::Runtime::new()
//! .unwrap()
//! .block_on(async move {
@ -107,7 +106,7 @@ pub trait DioxusRouterExt<S> {
fn register_server_fns_with_handler<H, T>(
self,
server_fn_route: &'static str,
handler: impl Fn(ServerFunction) -> H,
handler: impl FnMut(server_fn::ServerFnTraitObj<DioxusServerContext>) -> H,
) -> Self
where
H: Handler<T, S>,
@ -232,7 +231,7 @@ where
fn register_server_fns_with_handler<H, T>(
self,
server_fn_route: &'static str,
mut handler: impl FnMut(ServerFunction) -> H,
mut handler: impl FnMut(server_fn::ServerFnTraitObj<DioxusServerContext>) -> H,
) -> Self
where
H: Handler<T, S, Body>,
@ -243,7 +242,7 @@ where
for server_fn_path in DioxusServerFnRegistry::paths_registered() {
let func = DioxusServerFnRegistry::get(server_fn_path).unwrap();
let full_route = format!("{server_fn_route}/{server_fn_path}");
match func.encoding {
match func.encoding() {
Encoding::Url | Encoding::Cbor => {
router = router.route(&full_route, post(handler(func)));
}
@ -314,7 +313,7 @@ where
let cfg = cfg.into();
// Add server functions and render index.html
self.serve_static_assets(&cfg.assets_path)
self.serve_static_assets(cfg.assets_path)
.route(
"/",
get(render_handler).with_state((cfg, SSRState::default())),
@ -371,7 +370,7 @@ async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
/// A default handler for server functions. It will deserialize the request, call the server function, and serialize the response.
pub async fn server_fn_handler(
server_context: DioxusServerContext,
function: ServerFunction,
function: server_fn::ServerFnTraitObj<DioxusServerContext>,
parts: Arc<RequestParts>,
body: Body,
) -> impl IntoResponse {
@ -389,11 +388,11 @@ pub async fn server_fn_handler(
.expect("couldn't spawn runtime")
.block_on(async {
let query = &query_string.into();
let data = match &function.encoding {
let data = match &function.encoding() {
Encoding::Url | Encoding::Cbor => &body,
Encoding::GetJSON | Encoding::GetCBOR => query,
};
let resp = match (function.trait_obj)(server_context.clone(), &data).await {
let resp = match function.call(server_context.clone(), data).await {
Ok(serialized) => {
// if this is Accept: application/json then send a serialized JSON response
let accept_header = parts

View File

@ -12,7 +12,6 @@
//! #[cfg(feature = "ssr")]
//! {
//! use salvo::prelude::*;
//! GetServerData::register().unwrap();
//! tokio::runtime::Runtime::new()
//! .unwrap()
//! .block_on(async move {
@ -110,7 +109,7 @@ pub trait DioxusRouterExt {
fn register_server_fns_with_handler<H>(
self,
server_fn_route: &'static str,
handler: impl Fn(ServerFunction) -> H,
handler: impl Fn(server_fn::ServerFnTraitObj<DioxusServerContext>) -> H,
) -> Self
where
H: Handler + 'static;
@ -204,7 +203,7 @@ impl DioxusRouterExt for Router {
fn register_server_fns_with_handler<H>(
self,
server_fn_route: &'static str,
mut handler: impl FnMut(ServerFunction) -> H,
mut handler: impl FnMut(server_fn::ServerFnTraitObj<DioxusServerContext>) -> H,
) -> Self
where
H: Handler + 'static,
@ -213,7 +212,7 @@ impl DioxusRouterExt for Router {
for server_fn_path in DioxusServerFnRegistry::paths_registered() {
let func = DioxusServerFnRegistry::get(server_fn_path).unwrap();
let full_route = format!("{server_fn_route}/{server_fn_path}");
match func.encoding {
match func.encoding() {
Encoding::Url | Encoding::Cbor => {
router = router.push(Router::with_path(&full_route).post(handler(func)));
}
@ -280,7 +279,7 @@ impl DioxusRouterExt for Router {
) -> Self {
let cfg = cfg.into();
self.serve_static_assets(&cfg.assets_path)
self.serve_static_assets(cfg.assets_path)
.connect_hot_reload()
.register_server_fns(server_fn_path)
.push(Router::with_path("/").get(SSRHandler { cfg }))
@ -288,8 +287,8 @@ impl DioxusRouterExt for Router {
fn connect_hot_reload(self) -> Self {
let mut _dioxus_router = Router::with_path("_dioxus");
_dioxus_router = _dioxus_router
.push(Router::with_path("hot_reload").handle(HotReloadHandler::default()));
_dioxus_router =
_dioxus_router.push(Router::with_path("hot_reload").handle(HotReloadHandler));
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
{
_dioxus_router = _dioxus_router.push(Router::with_path("disconnect").handle(ignore_ws));
@ -346,12 +345,15 @@ impl<P: Clone + serde::Serialize + Send + Sync + 'static> Handler for SSRHandler
/// A default handler for server functions. It will deserialize the request body, call the server function, and serialize the response.
pub struct ServerFnHandler {
server_context: DioxusServerContext,
function: ServerFunction,
function: server_fn::ServerFnTraitObj<DioxusServerContext>,
}
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: ServerFunction) -> Self {
pub fn new(
server_context: impl Into<DioxusServerContext>,
function: server_fn::ServerFnTraitObj<DioxusServerContext>,
) -> Self {
let server_context = server_context.into();
Self {
server_context,
@ -395,11 +397,11 @@ impl ServerFnHandler {
tokio::runtime::Runtime::new()
.expect("couldn't spawn runtime")
.block_on(async move {
let data = match &function.encoding {
let data = match function.encoding() {
Encoding::Url | Encoding::Cbor => &body,
Encoding::GetJSON | Encoding::GetCBOR => &query,
};
let resp = (function.trait_obj)(server_context, data).await;
let resp = function.call(server_context, data).await;
resp_tx.send(resp).unwrap();
})

View File

@ -11,7 +11,6 @@
//! dioxus_web::launch_cfg(app, dioxus_web::Config::new().hydrate(true));
//! #[cfg(feature = "ssr")]
//! {
//! GetServerData::register().unwrap();
//! tokio::runtime::Runtime::new()
//! .unwrap()
//! .block_on(async move {
@ -93,7 +92,7 @@ pub fn register_server_fns_with_handler<H, F, R>(
mut handler: H,
) -> BoxedFilter<(R,)>
where
H: FnMut(String, ServerFunction) -> F,
H: FnMut(String, server_fn::ServerFnTraitObj<DioxusServerContext>) -> F,
F: Filter<Extract = (R,), Error = warp::Rejection> + Send + Sync + 'static,
F::Extract: Send,
R: Reply + 'static,
@ -104,7 +103,7 @@ where
let full_route = format!("{server_fn_route}/{server_fn_path}")
.trim_start_matches('/')
.to_string();
let route = handler(full_route, func.clone()).boxed();
let route = handler(full_route, func).boxed();
if let Some(boxed_filter) = filter.take() {
filter = Some(boxed_filter.or(route).unify().boxed());
} else {
@ -128,7 +127,7 @@ where
/// ```
pub fn register_server_fns(server_fn_route: &'static str) -> BoxedFilter<(impl Reply,)> {
register_server_fns_with_handler(server_fn_route, |full_route, func| {
path(full_route.clone())
path(full_route)
.and(warp::post().or(warp::get()).unify())
.and(request_parts())
.and(warp::body::bytes())
@ -250,7 +249,7 @@ impl warp::reject::Reject for RecieveFailed {}
/// A default handler for server functions. It will deserialize the request body, call the server function, and serialize the response.
pub async fn server_fn_handler(
server_context: impl Into<DioxusServerContext>,
function: ServerFunction,
function: server_fn::ServerFnTraitObj<DioxusServerContext>,
parts: RequestParts,
body: Bytes,
) -> Result<Box<dyn warp::Reply>, warp::Rejection> {
@ -274,11 +273,11 @@ pub async fn server_fn_handler(
.as_bytes()
.to_vec()
.into();
let data = match &function.encoding {
let data = match function.encoding() {
Encoding::Url | Encoding::Cbor => &body,
Encoding::GetJSON | Encoding::GetCBOR => &query,
};
let resp = match (function.trait_obj)(server_context.clone(), data).await {
let resp = match function.call(server_context.clone(), data).await {
Ok(serialized) => {
// if this is Accept: application/json then send a serialized JSON response
let accept_header = parts
@ -373,7 +372,7 @@ pub fn connect_hot_reload() -> impl Filter<Extract = (impl Reply,), Error = warp
use warp::ws::Message;
let hot_reload = warp::path!("_dioxus" / "hot_reload")
.and(warp::any().then(|| crate::hot_reload::spawn_hot_reload()))
.and(warp::any().then(crate::hot_reload::spawn_hot_reload))
.and(warp::ws())
.map(move |state: &'static HotReloadState, ws: warp::ws::Ws| {
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
@ -425,7 +424,7 @@ pub fn connect_hot_reload() -> impl Filter<Extract = (impl Reply,), Error = warp
struct DisconnectOnDrop(Option<warp::ws::WebSocket>);
impl Drop for DisconnectOnDrop {
fn drop(&mut self) {
let _ = self.0.take().unwrap().close();
std::mem::drop(self.0.take().unwrap().close());
}
}

View File

@ -34,7 +34,7 @@ pub mod prelude {
#[cfg(feature = "ssr")]
pub use crate::server_context::RequestParts;
pub use crate::server_context::{DioxusServerContext, HasServerContext};
pub use crate::server_fn::ServerFn;
pub use crate::server_fn::DioxusServerFn;
#[cfg(feature = "ssr")]
pub use crate::server_fn::{ServerFnTraitObj, ServerFunction};
pub use dioxus_server_macro::*;

View File

@ -1,30 +1,76 @@
use crate::server_context::DioxusServerContext;
#[cfg(any(feature = "ssr", doc))]
#[derive(Clone)]
/// A trait object for a function that be called on serializable arguments and returns a serializable result.
pub type ServerFnTraitObj = server_fn::ServerFnTraitObj<DioxusServerContext>;
pub struct ServerFnTraitObj(server_fn::ServerFnTraitObj<DioxusServerContext>);
#[cfg(any(feature = "ssr", doc))]
impl std::ops::Deref for ServerFnTraitObj {
type Target = server_fn::ServerFnTraitObj<DioxusServerContext>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(any(feature = "ssr", doc))]
impl std::ops::DerefMut for ServerFnTraitObj {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(any(feature = "ssr", doc))]
impl ServerFnTraitObj {
fn new(
prefix: &'static str,
url: &'static str,
encoding: server_fn::Encoding,
run: ServerFunction,
) -> Self {
Self(server_fn::ServerFnTraitObj::new(prefix, url, encoding, run))
}
/// Create a new `ServerFnTraitObj` from a `server_fn::ServerFnTraitObj`.
pub const fn from_generic_server_fn(
server_fn: server_fn::ServerFnTraitObj<DioxusServerContext>,
) -> Self {
Self(server_fn)
}
}
#[cfg(any(feature = "ssr", doc))]
server_fn::inventory::collect!(ServerFnTraitObj);
#[cfg(any(feature = "ssr", doc))]
/// A server function that can be called on serializable arguments and returns a serializable result.
pub type ServerFunction = server_fn::ServerFunction<DioxusServerContext>;
pub type ServerFunction = server_fn::SerializedFnTraitObj<DioxusServerContext>;
#[cfg(any(feature = "ssr", doc))]
#[allow(clippy::type_complexity)]
static REGISTERED_SERVER_FUNCTIONS: once_cell::sync::Lazy<
std::sync::Arc<std::sync::RwLock<std::collections::HashMap<&'static str, ServerFunction>>>,
> = once_cell::sync::Lazy::new(Default::default);
std::sync::Arc<std::sync::RwLock<std::collections::HashMap<&'static str, ServerFnTraitObj>>>,
> = once_cell::sync::Lazy::new(|| {
let mut map = std::collections::HashMap::new();
for server_fn in server_fn::inventory::iter::<ServerFnTraitObj> {
map.insert(server_fn.0.url(), server_fn.clone());
}
std::sync::Arc::new(std::sync::RwLock::new(map))
});
#[cfg(any(feature = "ssr", doc))]
/// The registry of all Dioxus server functions.
pub struct DioxusServerFnRegistry;
#[cfg(any(feature = "ssr"))]
#[cfg(feature = "ssr")]
impl server_fn::ServerFunctionRegistry<DioxusServerContext> for DioxusServerFnRegistry {
type Error = ServerRegistrationFnError;
fn register(
fn register_explicit(
prefix: &'static str,
url: &'static str,
server_function: std::sync::Arc<ServerFnTraitObj>,
server_function: ServerFunction,
encoding: server_fn::Encoding,
) -> Result<(), Self::Error> {
// store it in the hashmap
@ -33,10 +79,7 @@ impl server_fn::ServerFunctionRegistry<DioxusServerContext> for DioxusServerFnRe
.map_err(|e| ServerRegistrationFnError::Poisoned(e.to_string()))?;
let prev = write.insert(
url,
ServerFunction {
trait_obj: server_function,
encoding,
},
ServerFnTraitObj::new(prefix, url, encoding, server_function),
);
// if there was already a server function with this key,
@ -53,27 +96,32 @@ impl server_fn::ServerFunctionRegistry<DioxusServerContext> for DioxusServerFnRe
}
}
/// Returns the server function registered at the given URL, or `None` if no function is registered at that URL.
fn get(url: &str) -> Option<ServerFunction> {
REGISTERED_SERVER_FUNCTIONS
.read()
.ok()
.and_then(|fns| fns.get(url).cloned())
fn register(
url: &'static str,
server_function: ServerFunction,
encoding: server_fn::Encoding,
) -> Result<(), Self::Error> {
Self::register_explicit("", url, server_function, encoding)
}
/// Returns the server function registered at the given URL, or `None` if no function is registered at that URL.
fn get_trait_obj(url: &str) -> Option<std::sync::Arc<ServerFnTraitObj>> {
fn get(url: &str) -> Option<server_fn::ServerFnTraitObj<DioxusServerContext>> {
REGISTERED_SERVER_FUNCTIONS
.read()
.ok()
.and_then(|fns| fns.get(url).map(|f| f.trait_obj.clone()))
.and_then(|fns| fns.get(url).map(|inner| inner.0.clone()))
}
/// Returns the server function registered at the given URL, or `None` if no function is registered at that URL.
fn get_trait_obj(url: &str) -> Option<server_fn::ServerFnTraitObj<DioxusServerContext>> {
Self::get(url)
}
fn get_encoding(url: &str) -> Option<server_fn::Encoding> {
REGISTERED_SERVER_FUNCTIONS
.read()
.ok()
.and_then(|fns| fns.get(url).map(|f| f.encoding.clone()))
.and_then(|fns| fns.get(url).map(|f| f.encoding()))
}
/// Returns a list of all registered server functions.
@ -103,16 +151,16 @@ pub enum ServerRegistrationFnError {
///
/// Server functions are created using the `server` macro.
///
/// The function should be registered by calling `ServerFn::register()`. The set of server functions
/// 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 ServerFn: server_fn::ServerFn<DioxusServerContext> {
pub trait DioxusServerFn: server_fn::ServerFn<DioxusServerContext> {
/// Registers the server function, allowing the client to query it by URL.
#[cfg(any(feature = "ssr", doc))]
fn register() -> Result<(), server_fn::ServerFnError> {
Self::register_in::<DioxusServerFnRegistry>()
fn register_explicit() -> Result<(), server_fn::ServerFnError> {
Self::register_in_explicit::<DioxusServerFnRegistry>()
}
}
impl<T> ServerFn for T where T: server_fn::ServerFn<DioxusServerContext> {}
impl<T> DioxusServerFn for T where T: server_fn::ServerFn<DioxusServerContext> {}