Procházet zdrojové kódy

refactor incremental rendering

Evan Almloff před 1 rokem
rodič
revize
d885846589

+ 128 - 0
packages/ssr/src/fs_cache.rs

@@ -0,0 +1,128 @@
+#![allow(non_snake_case)]
+
+
+
+use std::{
+    ops::{Deref, DerefMut},
+    path::{PathBuf},
+    time::{Duration},
+};
+
+
+/// Information about the freshness of a rendered response
+#[derive(Debug, Clone, Copy)]
+pub struct RenderFreshness {
+    /// The age of the rendered response
+    age: u64,
+    /// The maximum age of the rendered response
+    max_age: Option<u64>,
+}
+
+impl RenderFreshness {
+    /// Create new freshness information
+    pub fn new(age: u64, max_age: u64) -> Self {
+        Self {
+            age,
+            max_age: Some(max_age),
+        }
+    }
+
+    /// Create new freshness information with only the age
+    pub fn new_age(age: u64) -> Self {
+        Self { age, max_age: None }
+    }
+
+    /// Create new freshness information at the current time
+    pub fn now(max_age: Option<Duration>) -> Self {
+        Self {
+            age: 0,
+            max_age: max_age.map(|d| d.as_secs()),
+        }
+    }
+
+    /// Get the age of the rendered response in seconds
+    pub fn age(&self) -> u64 {
+        self.age
+    }
+
+    /// Get the maximum age of the rendered response in seconds
+    pub fn max_age(&self) -> Option<u64> {
+        self.max_age
+    }
+
+    /// Write the freshness to the response headers.
+    pub fn write(&self, headers: &mut http::HeaderMap<http::HeaderValue>) {
+        let age = self.age();
+        headers.insert(http::header::AGE, age.into());
+        if let Some(max_age) = self.max_age() {
+            headers.insert(
+                http::header::CACHE_CONTROL,
+                http::HeaderValue::from_str(&format!("max-age={}", max_age)).unwrap(),
+            );
+        }
+    }
+}
+
+struct WriteBuffer {
+    buffer: Vec<u8>,
+}
+
+impl std::fmt::Write for WriteBuffer {
+    fn write_str(&mut self, s: &str) -> std::fmt::Result {
+        self.buffer.extend_from_slice(s.as_bytes());
+        Ok(())
+    }
+}
+
+impl Deref for WriteBuffer {
+    type Target = Vec<u8>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.buffer
+    }
+}
+
+impl DerefMut for WriteBuffer {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.buffer
+    }
+}
+
+pub(crate) struct ValidCachedPath {
+    pub(crate) full_path: PathBuf,
+    pub(crate) timestamp: std::time::SystemTime,
+}
+
+impl ValidCachedPath {
+    pub fn try_from_path(value: PathBuf) -> Option<Self> {
+        if value.extension() != Some(std::ffi::OsStr::new("html")) {
+            return None;
+        }
+        let timestamp = decode_timestamp(value.file_stem()?.to_str()?)?;
+        let full_path = value;
+        Some(Self {
+            full_path,
+            timestamp,
+        })
+    }
+
+    pub fn freshness(&self, max_age: Option<std::time::Duration>) -> Option<RenderFreshness> {
+        let age = self.timestamp.elapsed().ok()?.as_secs();
+        let max_age = max_age.map(|max_age| max_age.as_secs());
+        Some(RenderFreshness::new(age, max_age?))
+    }
+}
+
+fn decode_timestamp(timestamp: &str) -> Option<std::time::SystemTime> {
+    let timestamp = u64::from_str_radix(timestamp, 16).ok()?;
+    Some(std::time::UNIX_EPOCH + std::time::Duration::from_secs(timestamp))
+}
+
+pub fn timestamp() -> String {
+    let datetime = std::time::SystemTime::now();
+    let timestamp = datetime
+        .duration_since(std::time::UNIX_EPOCH)
+        .unwrap()
+        .as_secs();
+    format!("{:x}", timestamp)
+}

+ 9 - 222
packages/ssr/src/incremental.rs

@@ -2,150 +2,30 @@
 
 #![allow(non_snake_case)]
 
+use crate::fs_cache::ValidCachedPath;
 use dioxus_core::{Element, Scope, VirtualDom};
 use rustc_hash::FxHasher;
 use std::{
     hash::BuildHasherDefault,
     io::Write,
-    num::NonZeroUsize,
     ops::{Deref, DerefMut},
-    path::{Path, PathBuf},
-    sync::Arc,
+    path::{PathBuf},
     time::{Duration, SystemTime},
 };
 use tokio::io::{AsyncWrite, AsyncWriteExt, BufReader};
 
-/// Something that can render a HTML page from a body.
-pub trait WrapBody {
-    /// Render the HTML before the body
-    fn render_before_body<R: Write>(&self, to: &mut R) -> Result<(), IncrementalRendererError>;
-    /// Render the HTML after the body
-    fn render_after_body<R: Write>(&self, to: &mut R) -> Result<(), IncrementalRendererError>;
-}
-
-/// The default page renderer
-pub struct DefaultRenderer {
-    /// The HTML before the body.
-    pub before_body: String,
-    /// The HTML after the body.
-    pub after_body: String,
-}
-
-impl Default for DefaultRenderer {
-    fn default() -> Self {
-        let before = r#"<!DOCTYPE html>
-        <html lang="en">
-        <head>
-            <meta charset="UTF-8">
-            <meta name="viewport" content="width=device-width, initial-scale=1.0">
-            <title>Dioxus Application</title>
-        </head>
-        <body>"#;
-        let after = r#"</body>
-        </html>"#;
-        Self {
-            before_body: before.to_string(),
-            after_body: after.to_string(),
-        }
-    }
-}
-
-impl WrapBody for DefaultRenderer {
-    fn render_before_body<R: Write>(&self, to: &mut R) -> Result<(), IncrementalRendererError> {
-        to.write_all(self.before_body.as_bytes())?;
-        Ok(())
-    }
-
-    fn render_after_body<R: Write>(&self, to: &mut R) -> Result<(), IncrementalRendererError> {
-        to.write_all(self.after_body.as_bytes())?;
-        Ok(())
-    }
-}
-
-type PathMapFn = Arc<dyn Fn(&str) -> PathBuf + Send + Sync>;
-
-/// A configuration for the incremental renderer.
-#[derive(Clone)]
-pub struct IncrementalRendererConfig {
-    static_dir: PathBuf,
-    memory_cache_limit: usize,
-    invalidate_after: Option<Duration>,
-    map_path: Option<PathMapFn>,
-}
-
-impl Default for IncrementalRendererConfig {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl IncrementalRendererConfig {
-    /// Create a new incremental renderer configuration.
-    pub fn new() -> Self {
-        Self {
-            static_dir: PathBuf::from("./static"),
-            memory_cache_limit: 10000,
-            invalidate_after: None,
-            map_path: None,
-        }
-    }
-
-    /// Set a mapping from the route to the file path. This will override the default mapping configured with `static_dir`.
-    /// The function should return the path to the folder to store the index.html file in.
-    pub fn map_path<F: Fn(&str) -> PathBuf + Send + Sync + 'static>(mut self, map_path: F) -> Self {
-        self.map_path = Some(Arc::new(map_path));
-        self
-    }
-
-    /// Set the static directory.
-    pub fn static_dir<P: AsRef<Path>>(mut self, static_dir: P) -> Self {
-        self.static_dir = static_dir.as_ref().to_path_buf();
-        self
-    }
-
-    /// Set the memory cache limit.
-    pub const fn memory_cache_limit(mut self, memory_cache_limit: usize) -> Self {
-        self.memory_cache_limit = memory_cache_limit;
-        self
-    }
-
-    /// Set the invalidation time.
-    pub fn invalidate_after(mut self, invalidate_after: Duration) -> Self {
-        self.invalidate_after = Some(invalidate_after);
-        self
-    }
-
-    /// Build the incremental renderer.
-    pub fn build(self) -> IncrementalRenderer {
-        let static_dir = self.static_dir.clone();
-        IncrementalRenderer {
-            static_dir: self.static_dir.clone(),
-            memory_cache: NonZeroUsize::new(self.memory_cache_limit)
-                .map(|limit| lru::LruCache::with_hasher(limit, Default::default())),
-            invalidate_after: self.invalidate_after,
-            ssr_renderer: crate::Renderer::new(),
-            map_path: self.map_path.unwrap_or_else(move || {
-                Arc::new(move |route: &str| {
-                    let mut path = static_dir.clone();
-                    for segment in route.split('/') {
-                        path.push(segment);
-                    }
-                    path
-                })
-            }),
-        }
-    }
-}
+pub use crate::fs_cache::*;
+pub use crate::incremental_cfg::*;
 
 /// An incremental renderer.
 pub struct IncrementalRenderer {
-    static_dir: PathBuf,
+    pub(crate) static_dir: PathBuf,
     #[allow(clippy::type_complexity)]
-    memory_cache:
+    pub(crate) memory_cache:
         Option<lru::LruCache<String, (SystemTime, Vec<u8>), BuildHasherDefault<FxHasher>>>,
-    invalidate_after: Option<Duration>,
-    ssr_renderer: crate::Renderer,
-    map_path: PathMapFn,
+    pub(crate) invalidate_after: Option<Duration>,
+    pub(crate) ssr_renderer: crate::Renderer,
+    pub(crate) map_path: PathMapFn,
 }
 
 impl IncrementalRenderer {
@@ -366,60 +246,6 @@ impl IncrementalRenderer {
     }
 }
 
-/// Information about the freshness of a rendered response
-#[derive(Debug, Clone, Copy)]
-pub struct RenderFreshness {
-    /// The age of the rendered response
-    age: u64,
-    /// The maximum age of the rendered response
-    max_age: Option<u64>,
-}
-
-impl RenderFreshness {
-    /// Create new freshness information
-    pub fn new(age: u64, max_age: u64) -> Self {
-        Self {
-            age,
-            max_age: Some(max_age),
-        }
-    }
-
-    /// Create new freshness information with only the age
-    pub fn new_age(age: u64) -> Self {
-        Self { age, max_age: None }
-    }
-
-    /// Create new freshness information at the current time
-    pub fn now(max_age: Option<Duration>) -> Self {
-        Self {
-            age: 0,
-            max_age: max_age.map(|d| d.as_secs()),
-        }
-    }
-
-    /// Get the age of the rendered response in seconds
-    pub fn age(&self) -> u64 {
-        self.age
-    }
-
-    /// Get the maximum age of the rendered response in seconds
-    pub fn max_age(&self) -> Option<u64> {
-        self.max_age
-    }
-
-    /// Write the freshness to the response headers.
-    pub fn write(&self, headers: &mut http::HeaderMap<http::HeaderValue>) {
-        let age = self.age();
-        headers.insert(http::header::AGE, age.into());
-        if let Some(max_age) = self.max_age() {
-            headers.insert(
-                http::header::CACHE_CONTROL,
-                http::HeaderValue::from_str(&format!("max-age={}", max_age)).unwrap(),
-            );
-        }
-    }
-}
-
 struct WriteBuffer {
     buffer: Vec<u8>,
 }
@@ -445,45 +271,6 @@ impl DerefMut for WriteBuffer {
     }
 }
 
-struct ValidCachedPath {
-    full_path: PathBuf,
-    timestamp: std::time::SystemTime,
-}
-
-impl ValidCachedPath {
-    fn try_from_path(value: PathBuf) -> Option<Self> {
-        if value.extension() != Some(std::ffi::OsStr::new("html")) {
-            return None;
-        }
-        let timestamp = decode_timestamp(value.file_stem()?.to_str()?)?;
-        let full_path = value;
-        Some(Self {
-            full_path,
-            timestamp,
-        })
-    }
-
-    fn freshness(&self, max_age: Option<std::time::Duration>) -> Option<RenderFreshness> {
-        let age = self.timestamp.elapsed().ok()?.as_secs();
-        let max_age = max_age.map(|max_age| max_age.as_secs());
-        Some(RenderFreshness::new(age, max_age?))
-    }
-}
-
-fn decode_timestamp(timestamp: &str) -> Option<std::time::SystemTime> {
-    let timestamp = u64::from_str_radix(timestamp, 16).ok()?;
-    Some(std::time::UNIX_EPOCH + std::time::Duration::from_secs(timestamp))
-}
-
-fn timestamp() -> String {
-    let datetime = std::time::SystemTime::now();
-    let timestamp = datetime
-        .duration_since(std::time::UNIX_EPOCH)
-        .unwrap()
-        .as_secs();
-    format!("{:x}", timestamp)
-}
-
 /// An error that can occur while rendering a route or retrieving a cached route.
 #[derive(Debug, thiserror::Error)]
 pub enum IncrementalRendererError {

+ 134 - 0
packages/ssr/src/incremental_cfg.rs

@@ -0,0 +1,134 @@
+#![allow(non_snake_case)]
+
+use crate::incremental::IncrementalRenderer;
+use crate::incremental::IncrementalRendererError;
+
+use std::{
+    io::Write,
+    num::NonZeroUsize,
+    path::{Path, PathBuf},
+    sync::Arc,
+    time::Duration,
+};
+
+/// Something that can render a HTML page from a body.
+pub trait WrapBody {
+    /// Render the HTML before the body
+    fn render_before_body<R: Write>(&self, to: &mut R) -> Result<(), IncrementalRendererError>;
+    /// Render the HTML after the body
+    fn render_after_body<R: Write>(&self, to: &mut R) -> Result<(), IncrementalRendererError>;
+}
+
+/// The default page renderer
+pub struct DefaultRenderer {
+    /// The HTML before the body.
+    pub before_body: String,
+    /// The HTML after the body.
+    pub after_body: String,
+}
+
+impl Default for DefaultRenderer {
+    fn default() -> Self {
+        let before = r#"<!DOCTYPE html>
+        <html lang="en">
+        <head>
+            <meta charset="UTF-8">
+            <meta name="viewport" content="width=device-width, initial-scale=1.0">
+            <title>Dioxus Application</title>
+        </head>
+        <body>"#;
+        let after = r#"</body>
+        </html>"#;
+        Self {
+            before_body: before.to_string(),
+            after_body: after.to_string(),
+        }
+    }
+}
+
+impl WrapBody for DefaultRenderer {
+    fn render_before_body<R: Write>(&self, to: &mut R) -> Result<(), IncrementalRendererError> {
+        to.write_all(self.before_body.as_bytes())?;
+        Ok(())
+    }
+
+    fn render_after_body<R: Write>(&self, to: &mut R) -> Result<(), IncrementalRendererError> {
+        to.write_all(self.after_body.as_bytes())?;
+        Ok(())
+    }
+}
+
+pub(crate) type PathMapFn = Arc<dyn Fn(&str) -> PathBuf + Send + Sync>;
+
+/// A configuration for the incremental renderer.
+#[derive(Clone)]
+pub struct IncrementalRendererConfig {
+    static_dir: PathBuf,
+    memory_cache_limit: usize,
+    invalidate_after: Option<Duration>,
+    map_path: Option<PathMapFn>,
+}
+
+impl Default for IncrementalRendererConfig {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl IncrementalRendererConfig {
+    /// Create a new incremental renderer configuration.
+    pub fn new() -> Self {
+        Self {
+            static_dir: PathBuf::from("./static"),
+            memory_cache_limit: 10000,
+            invalidate_after: None,
+            map_path: None,
+        }
+    }
+
+    /// Set a mapping from the route to the file path. This will override the default mapping configured with `static_dir`.
+    /// The function should return the path to the folder to store the index.html file in.
+    pub fn map_path<F: Fn(&str) -> PathBuf + Send + Sync + 'static>(mut self, map_path: F) -> Self {
+        self.map_path = Some(Arc::new(map_path));
+        self
+    }
+
+    /// Set the static directory.
+    pub fn static_dir<P: AsRef<Path>>(mut self, static_dir: P) -> Self {
+        self.static_dir = static_dir.as_ref().to_path_buf();
+        self
+    }
+
+    /// Set the memory cache limit.
+    pub const fn memory_cache_limit(mut self, memory_cache_limit: usize) -> Self {
+        self.memory_cache_limit = memory_cache_limit;
+        self
+    }
+
+    /// Set the invalidation time.
+    pub fn invalidate_after(mut self, invalidate_after: Duration) -> Self {
+        self.invalidate_after = Some(invalidate_after);
+        self
+    }
+
+    /// Build the incremental renderer.
+    pub fn build(self) -> IncrementalRenderer {
+        let static_dir = self.static_dir.clone();
+        IncrementalRenderer {
+            static_dir: self.static_dir.clone(),
+            memory_cache: NonZeroUsize::new(self.memory_cache_limit)
+                .map(|limit| lru::LruCache::with_hasher(limit, Default::default())),
+            invalidate_after: self.invalidate_after,
+            ssr_renderer: crate::Renderer::new(),
+            map_path: self.map_path.unwrap_or_else(move || {
+                Arc::new(move |route: &str| {
+                    let mut path = static_dir.clone();
+                    for segment in route.split('/') {
+                        path.push(segment);
+                    }
+                    path
+                })
+            }),
+        }
+    }
+}

+ 3 - 0
packages/ssr/src/lib.rs

@@ -2,9 +2,12 @@
 
 mod cache;
 pub mod config;
+mod fs_cache;
 pub mod incremental;
+mod incremental_cfg;
 pub mod renderer;
 pub mod template;
+
 use dioxus_core::{Element, LazyNodes, Scope, VirtualDom};
 use std::cell::Cell;