From de72d85391a44ecb0452ab21931f924b56fef124 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 14 Jul 2023 17:23:12 -0700 Subject: [PATCH] create server_cached function --- .../examples/axum-hello-world/src/main.rs | 24 +++++++++- .../deserialize.rs} | 27 ++++++++++- .../src/{props_html => html_storage}/mod.rs | 48 +++++++++++++++++-- .../serialize.rs} | 22 +++++++-- packages/fullstack/src/lib.rs | 8 +++- packages/fullstack/src/render.rs | 30 +++++++++++- packages/fullstack/src/server_context.rs | 19 ++++++++ packages/fullstack/src/use_server/mod.rs | 18 +++++++ 8 files changed, 182 insertions(+), 14 deletions(-) rename packages/fullstack/src/{props_html/deserialize_props.rs => html_storage/deserialize.rs} (55%) rename packages/fullstack/src/{props_html => html_storage}/mod.rs (50%) rename packages/fullstack/src/{props_html/serialize_props.rs => html_storage/serialize.rs} (50%) create mode 100644 packages/fullstack/src/use_server/mod.rs diff --git a/packages/fullstack/examples/axum-hello-world/src/main.rs b/packages/fullstack/examples/axum-hello-world/src/main.rs index 739a24b1c..9d8d232dc 100644 --- a/packages/fullstack/examples/axum-hello-world/src/main.rs +++ b/packages/fullstack/examples/axum-hello-world/src/main.rs @@ -16,10 +16,32 @@ struct AppProps { } fn app(cx: Scope) -> Element { + let state1 = server_cached(|| { + #[cfg(not(feature = "ssr"))] + panic!(); + 12345 + }); + assert_eq!(state1, 12345); + let state2 = server_cached(|| { + #[cfg(not(feature = "ssr"))] + panic!(); + 123456 + }); + assert_eq!(state2, 123456); + let state3 = server_cached(|| { + #[cfg(not(feature = "ssr"))] + panic!(); + 1234567 + }); + assert_eq!(state3, 1234567); + let mut count = use_state(cx, || cx.props.count); let text = use_state(cx, || "...".to_string()); cx.render(rsx! { + div { + "Server state: {state1}, {state2}, {state3}" + } h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count -= 1, "Down low!" } @@ -42,7 +64,7 @@ fn app(cx: Scope) -> Element { #[server(PostServerData)] async fn post_server_data(data: String) -> Result<(), ServerFnError> { - let axum::extract::Host(host): axum::extract::Host = extract()?; + let axum::extract::Host(host): axum::extract::Host = extract().await?; println!("Server received: {}", data); println!("{:?}", host); diff --git a/packages/fullstack/src/props_html/deserialize_props.rs b/packages/fullstack/src/html_storage/deserialize.rs similarity index 55% rename from packages/fullstack/src/props_html/deserialize_props.rs rename to packages/fullstack/src/html_storage/deserialize.rs index f5c805865..3a0fc51df 100644 --- a/packages/fullstack/src/props_html/deserialize_props.rs +++ b/packages/fullstack/src/html_storage/deserialize.rs @@ -3,6 +3,8 @@ use serde::de::DeserializeOwned; use base64::engine::general_purpose::STANDARD; use base64::Engine; +use super::HTMLDataCursor; + #[allow(unused)] pub(crate) fn serde_from_bytes(string: &[u8]) -> Option { let decompressed = STANDARD.decode(string).ok()?; @@ -10,6 +12,29 @@ pub(crate) fn serde_from_bytes(string: &[u8]) -> Option postcard::from_bytes(&decompressed).ok() } +static SERVER_DATA: once_cell::sync::Lazy> = + once_cell::sync::Lazy::new(|| { + #[cfg(target_arch = "wasm32")] + { + let attribute = web_sys::window()? + .document()? + .get_element_by_id("dioxus-storage-data")? + .get_attribute("data-serialized")?; + + let data: super::HTMLData = serde_from_bytes(attribute.as_bytes())?; + + Some(data.cursor()) + } + #[cfg(not(target_arch = "wasm32"))] + { + None + } + }); + +pub(crate) fn take_server_data() -> Option { + SERVER_DATA.as_ref()?.take() +} + #[cfg(not(feature = "ssr"))] /// Get the props from the document. This is only available in the browser. /// @@ -23,7 +48,7 @@ pub fn get_root_props_from_document() -> Option { { let attribute = web_sys::window()? .document()? - .get_element_by_id("dioxus-storage")? + .get_element_by_id("dioxus-storage-props")? .get_attribute("data-serialized")?; serde_from_bytes(attribute.as_bytes()) diff --git a/packages/fullstack/src/props_html/mod.rs b/packages/fullstack/src/html_storage/mod.rs similarity index 50% rename from packages/fullstack/src/props_html/mod.rs rename to packages/fullstack/src/html_storage/mod.rs index 143c40231..dd40ce587 100644 --- a/packages/fullstack/src/props_html/mod.rs +++ b/packages/fullstack/src/html_storage/mod.rs @@ -1,6 +1,46 @@ -pub(crate) mod deserialize_props; +use std::sync::atomic::AtomicUsize; -pub(crate) mod serialize_props; +use serde::{de::DeserializeOwned, Serialize}; + +pub(crate) mod deserialize; + +pub(crate) mod serialize; + +#[derive(serde::Serialize, serde::Deserialize, Default)] +pub(crate) struct HTMLData { + pub data: Vec>, +} + +impl HTMLData { + pub(crate) fn push(&mut self, value: &T) { + let serialized = postcard::to_allocvec(value).unwrap(); + self.data.push(serialized); + } + + pub(crate) fn cursor(self) -> HTMLDataCursor { + HTMLDataCursor { + data: self.data, + index: AtomicUsize::new(0), + } + } +} + +pub(crate) struct HTMLDataCursor { + data: Vec>, + index: AtomicUsize, +} + +impl HTMLDataCursor { + pub fn take(&self) -> Option { + let current = self.index.load(std::sync::atomic::Ordering::SeqCst); + if current >= self.data.len() { + return None; + } + let mut cursor = &self.data[current]; + self.index.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + Some(postcard::from_bytes(&mut cursor).unwrap()) + } +} #[test] fn serialized_and_deserializes() { @@ -37,7 +77,7 @@ fn serialized_and_deserializes() { }; y ]; - serialize_props::serde_to_writable(&data, &mut as_string).unwrap(); + serialize::serde_to_writable(&data, &mut as_string).unwrap(); println!("{:?}", as_string); println!( @@ -47,7 +87,7 @@ fn serialized_and_deserializes() { println!("serialized size: {}", to_allocvec(&data).unwrap().len()); println!("compressed size: {}", as_string.len()); - let decoded: Vec = deserialize_props::serde_from_bytes(&as_string).unwrap(); + let decoded: Vec = deserialize::serde_from_bytes(&as_string).unwrap(); assert_eq!(data, decoded); } } diff --git a/packages/fullstack/src/props_html/serialize_props.rs b/packages/fullstack/src/html_storage/serialize.rs similarity index 50% rename from packages/fullstack/src/props_html/serialize_props.rs rename to packages/fullstack/src/html_storage/serialize.rs index 0c6ae3c49..7f0a7efc7 100644 --- a/packages/fullstack/src/props_html/serialize_props.rs +++ b/packages/fullstack/src/html_storage/serialize.rs @@ -15,12 +15,26 @@ pub(crate) fn serde_to_writable( #[cfg(feature = "ssr")] /// Encode data into a element. This is inteded to be used in the server to send data to the client. -pub(crate) fn encode_in_element( - data: T, +pub(crate) fn encode_props_in_element( + data: &T, write_to: &mut impl std::io::Write, ) -> std::io::Result<()> { - write_to - .write_all(r#""#.as_bytes()) +} + +#[cfg(feature = "ssr")] +/// Encode data into a element. This is inteded to be used in the server to send data to the client. +pub(crate) fn encode_in_element( + data: &super::HTMLData, + write_to: &mut impl std::io::Write, +) -> std::io::Result<()> { + write_to.write_all( + r#""#.as_bytes()) } diff --git a/packages/fullstack/src/lib.rs b/packages/fullstack/src/lib.rs index 4728c1846..b9318d0d6 100644 --- a/packages/fullstack/src/lib.rs +++ b/packages/fullstack/src/lib.rs @@ -5,7 +5,7 @@ pub use once_cell; -mod props_html; +mod html_storage; #[cfg(feature = "router")] pub mod router; @@ -26,6 +26,7 @@ mod serve_config; #[cfg(feature = "ssr")] mod server_context; mod server_fn; +mod use_server; /// A prelude of commonly used items in dioxus-fullstack. pub mod prelude { @@ -36,7 +37,7 @@ pub mod prelude { #[cfg(feature = "warp")] pub use crate::adapters::warp_adapter::*; #[cfg(not(feature = "ssr"))] - pub use crate::props_html::deserialize_props::get_root_props_from_document; + pub use crate::html_storage::deserialize::get_root_props_from_document; #[cfg(all(feature = "ssr", feature = "router"))] pub use crate::render::pre_cache_static_routes_with_props; #[cfg(feature = "ssr")] @@ -52,9 +53,12 @@ pub mod prelude { pub use crate::server_fn::DioxusServerFn; #[cfg(feature = "ssr")] pub use crate::server_fn::{ServerFnMiddleware, ServerFnTraitObj, ServerFunction}; + use crate::use_server; pub use crate::{launch, launch_router}; pub use dioxus_server_macro::*; #[cfg(feature = "ssr")] pub use dioxus_ssr::incremental::IncrementalRendererConfig; pub use server_fn::{self, ServerFn as _, ServerFnError}; + + pub use use_server::server_cached; } diff --git a/packages/fullstack/src/render.rs b/packages/fullstack/src/render.rs index fbd4bd7f3..9660617d6 100644 --- a/packages/fullstack/src/render.rs +++ b/packages/fullstack/src/render.rs @@ -27,7 +27,10 @@ impl SsrRendererPool { to: &mut WriteBuffer, server_context: &DioxusServerContext, ) -> Result { - let wrapper = FullstackRenderer { cfg }; + let wrapper = FullstackRenderer { + cfg, + server_context: server_context.clone(), + }; match self { Self::Renderer(pool) => { let server_context = Box::new(server_context.clone()); @@ -126,6 +129,7 @@ impl SSRState { struct FullstackRenderer<'a, P: Clone + Send + Sync + 'static> { cfg: &'a ServeConfig

, + server_context: DioxusServerContext, } impl<'a, P: Clone + Serialize + Send + Sync + 'static> dioxus_ssr::incremental::WrapBody @@ -147,7 +151,29 @@ impl<'a, P: Clone + Serialize + Send + Sync + 'static> dioxus_ssr::incremental:: to: &mut R, ) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> { // serialize the props - crate::props_html::serialize_props::encode_in_element(&self.cfg.props, to)?; + crate::html_storage::serialize::encode_props_in_element(&self.cfg.props, to)?; + // serialize the server state + crate::html_storage::serialize::encode_in_element( + &*self.server_context.html_data().map_err(|err| { + dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new({ + #[derive(Debug)] + struct HTMLDataReadError; + + impl std::fmt::Display for HTMLDataReadError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Failed to read the server data to serialize it into the HTML", + ) + } + } + + impl std::error::Error for HTMLDataReadError {} + + HTMLDataReadError + })) + })?, + to, + )?; #[cfg(all(debug_assertions, feature = "hot-reload"))] { diff --git a/packages/fullstack/src/server_context.rs b/packages/fullstack/src/server_context.rs index fcd0ef66b..bf84bd524 100644 --- a/packages/fullstack/src/server_context.rs +++ b/packages/fullstack/src/server_context.rs @@ -1,3 +1,4 @@ +use crate::html_storage::HTMLData; pub use server_fn_impl::*; use std::sync::Arc; use std::sync::RwLock; @@ -13,6 +14,7 @@ pub struct DioxusServerContext { >, response_parts: std::sync::Arc>, pub(crate) parts: Arc>, + html_data: Arc>, } #[allow(clippy::derivable_impls)] @@ -24,6 +26,7 @@ impl Default for DioxusServerContext { http::response::Response::new(()).into_parts().0, )), parts: std::sync::Arc::new(RwLock::new(http::request::Request::new(()).into_parts().0)), + html_data: Arc::new(RwLock::new(HTMLData::default())), } } } @@ -45,6 +48,7 @@ mod server_fn_impl { response_parts: std::sync::Arc::new(RwLock::new( http::response::Response::new(()).into_parts().0, )), + html_data: Arc::new(RwLock::new(HTMLData::default())), } } @@ -100,6 +104,21 @@ mod server_fn_impl { ) -> Result { T::from_request(self).await } + + /// Insert some data into the html data store + pub(crate) async fn push_html_data( + &self, + value: &T, + ) -> Result<(), PoisonError>> { + self.html_data.write().map(|mut map| { + map.push(value); + }) + } + + /// Get the html data store + pub(crate) fn html_data(&self) -> LockResult> { + self.html_data.read() + } } } diff --git a/packages/fullstack/src/use_server/mod.rs b/packages/fullstack/src/use_server/mod.rs new file mode 100644 index 000000000..dc47b5bca --- /dev/null +++ b/packages/fullstack/src/use_server/mod.rs @@ -0,0 +1,18 @@ +use serde::{de::DeserializeOwned, Serialize}; + +/// TODO: Document this +pub fn server_cached(server_fn: impl Fn() -> O) -> O { + #[cfg(feature = "ssr")] + { + let data = + crate::html_storage::deserialize::take_server_data().unwrap_or_else(|| server_fn()); + let sc = crate::prelude::server_context(); + sc.push_html_data(&data); + data + } + #[cfg(not(feature = "ssr"))] + { + let data = server_fn(); + data + } +}