Browse Source

create server_cached function

Evan Almloff 1 năm trước cách đây
mục cha
commit
de72d85391

+ 23 - 1
packages/fullstack/examples/axum-hello-world/src/main.rs

@@ -16,10 +16,32 @@ struct AppProps {
 }
 
 fn app(cx: Scope<AppProps>) -> 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<AppProps>) -> 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);
 

+ 26 - 1
packages/fullstack/src/props_html/deserialize_props.rs → 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<T: DeserializeOwned>(string: &[u8]) -> Option<T> {
     let decompressed = STANDARD.decode(string).ok()?;
@@ -10,6 +12,29 @@ pub(crate) fn serde_from_bytes<T: DeserializeOwned>(string: &[u8]) -> Option<T>
     postcard::from_bytes(&decompressed).ok()
 }
 
+static SERVER_DATA: once_cell::sync::Lazy<Option<HTMLDataCursor>> =
+    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<T: DeserializeOwned>() -> Option<T> {
+    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<T: DeserializeOwned>() -> Option<T> {
     {
         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())

+ 44 - 4
packages/fullstack/src/props_html/mod.rs → 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<Vec<u8>>,
+}
+
+impl HTMLData {
+    pub(crate) fn push<T: Serialize>(&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<Vec<u8>>,
+    index: AtomicUsize,
+}
+
+impl HTMLDataCursor {
+    pub fn take<T: DeserializeOwned>(&self) -> Option<T> {
+        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<Data> = deserialize_props::serde_from_bytes(&as_string).unwrap();
+            let decoded: Vec<Data> = deserialize::serde_from_bytes(&as_string).unwrap();
             assert_eq!(data, decoded);
         }
     }

+ 18 - 4
packages/fullstack/src/props_html/serialize_props.rs → packages/fullstack/src/html_storage/serialize.rs

@@ -15,12 +15,26 @@ pub(crate) fn serde_to_writable<T: Serialize>(
 
 #[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<T: Serialize>(
-    data: T,
+pub(crate) fn encode_props_in_element<T: Serialize>(
+    data: &T,
     write_to: &mut impl std::io::Write,
 ) -> std::io::Result<()> {
-    write_to
-        .write_all(r#"<meta hidden="true" id="dioxus-storage" data-serialized=""#.as_bytes())?;
+    write_to.write_all(
+        r#"<meta hidden="true" id="dioxus-storage-props" data-serialized=""#.as_bytes(),
+    )?;
+    serde_to_writable(data, write_to)?;
+    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#"<meta hidden="true" id="dioxus-storage-data" data-serialized=""#.as_bytes(),
+    )?;
     serde_to_writable(&data, write_to)?;
     write_to.write_all(r#"" />"#.as_bytes())
 }

+ 6 - 2
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;
 }

+ 28 - 2
packages/fullstack/src/render.rs

@@ -27,7 +27,10 @@ impl SsrRendererPool {
         to: &mut WriteBuffer,
         server_context: &DioxusServerContext,
     ) -> Result<RenderFreshness, dioxus_ssr::incremental::IncrementalRendererError> {
-        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<P>,
+    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"))]
         {

+ 19 - 0
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<std::sync::RwLock<http::response::Parts>>,
     pub(crate) parts: Arc<RwLock<http::request::Parts>>,
+    html_data: Arc<RwLock<HTMLData>>,
 }
 
 #[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, R> {
             T::from_request(self).await
         }
+
+        /// Insert some data into the html data store
+        pub(crate) async fn push_html_data<T: serde::Serialize>(
+            &self,
+            value: &T,
+        ) -> Result<(), PoisonError<RwLockWriteGuard<'_, HTMLData>>> {
+            self.html_data.write().map(|mut map| {
+                map.push(value);
+            })
+        }
+
+        /// Get the html data store
+        pub(crate) fn html_data(&self) -> LockResult<RwLockReadGuard<'_, HTMLData>> {
+            self.html_data.read()
+        }
     }
 }
 

+ 18 - 0
packages/fullstack/src/use_server/mod.rs

@@ -0,0 +1,18 @@
+use serde::{de::DeserializeOwned, Serialize};
+
+/// TODO: Document this
+pub fn server_cached<O: 'static + Serialize + DeserializeOwned>(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
+    }
+}