create server_cached function

This commit is contained in:
Evan Almloff 2023-07-14 17:23:12 -07:00
parent d885846589
commit de72d85391
8 changed files with 182 additions and 14 deletions

View file

@ -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);

View file

@ -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())

View file

@ -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);
}
}

View file

@ -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())
}

View file

@ -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;
}

View file

@ -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"))]
{

View file

@ -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()
}
}
}

View file

@ -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
}
}