Skip to content

Commit 6cc212a

Browse files
committed
in memory fe serving cache
1 parent 6522cab commit 6cc212a

File tree

7 files changed

+112
-27
lines changed

7 files changed

+112
-27
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ axum = { version = "0.6.20", features = ["headers", "ws"] }
2525
derive_more = { version = "0.99.17", default-features = false, features = ["display"] }
2626
serde_json = "1.0.113"
2727
strum = { version = "0.26.1", features = ["derive"] }
28+
derived-deref = "2.1.0"
2829
reqwest = { version = "0.11.14", default-features = false, features = ["cookies"] }
2930
envtestkit = { version = "1.1.2", default-features = false, features = ["lock"] }

backend/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,13 @@ serde_json.workspace = true
3030
axum.workspace = true
3131
derive_more.workspace = true
3232
strum.workspace = true
33-
33+
derived-deref.workspace = true
3434

3535
thiserror = "1.0.39"
3636
futures-util = "0.3.28"
3737
anyhow = "1.0.71"
3838
axum-macros = "0.3.8"
3939
url = "2.4.1"
40-
derived-deref = "2.1.0"
4140
bidirectional-map = "0.1.4"
4241
rand = "0.8.5"
4342

fe_server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ serde.workspace = true
1919
serde_json.workspace = true
2020
axum.workspace = true
2121
derive_more.workspace = true
22+
derived-deref.workspace = true
2223

2324
bytes = "1.5.0"
2425
httpdate = "1.0.3"

fe_server/src/serve_files.rs

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,69 @@ pub struct ServedFile<'a> {
55
pub size: &'a str,
66
}
77

8-
pub fn file_response(
9-
contents: impl Into<axum::body::Full<bytes::Bytes>>,
10-
path: impl AsRef<std::path::Path>,
11-
modified: std::time::SystemTime,
12-
) -> axum::response::Response {
8+
pub fn file_response(file: &File) -> axum::response::Response {
139
use axum::response::IntoResponse;
14-
let last_modified = httpdate::fmt_http_date(modified);
15-
let mime_type = mime_guess::from_path(path).first_or_text_plain();
10+
let last_modified = httpdate::fmt_http_date(file.modified);
11+
let mime_type = mime_guess::from_path(&file.path.as_ref()).first_or_text_plain();
12+
// tracing::warn!("mime type {mime_type} derived from {:?}", &file.path);
13+
1614
axum::http::Response::builder()
1715
.status(axum::http::StatusCode::OK)
1816
.header(
1917
axum::http::header::CONTENT_TYPE,
2018
axum::http::HeaderValue::from_str(mime_type.as_ref()).unwrap(),
2119
)
2220
.header(axum::http::header::LAST_MODIFIED, last_modified)
23-
.body(axum::body::boxed(contents.into()))
21+
.body(axum::body::boxed(axum::body::Full::<bytes::Bytes>::from(
22+
file.contents.clone(),
23+
)))
2424
.unwrap_or_else(|_| axum::http::StatusCode::INTERNAL_SERVER_ERROR.into_response())
2525
}
26+
27+
use std::collections::HashMap;
28+
use std::sync::Arc;
29+
use tokio::sync::RwLock;
30+
31+
#[derive(Debug)]
32+
pub struct File {
33+
pub contents: Vec<u8>,
34+
pub request_path: String,
35+
pub path: Box<std::path::PathBuf>,
36+
pub modified: std::time::SystemTime,
37+
}
38+
39+
#[derive(Default, Debug, Clone, derived_deref::Deref)]
40+
pub struct Cache {
41+
#[target]
42+
request_path_to_file: Arc<RwLock<HashMap<String, Arc<File>>>>,
43+
disk_path_to_file: Arc<RwLock<HashMap<Box<std::path::PathBuf>, Arc<File>>>>,
44+
}
45+
46+
impl Cache {
47+
pub async fn get_request_path(&self, path: &str) -> Option<Arc<File>> {
48+
self.request_path_to_file
49+
.read()
50+
.await
51+
.get(path)
52+
.map(Clone::clone)
53+
}
54+
55+
pub async fn get_disk_path(&self, path: &std::path::PathBuf) -> Option<Arc<File>> {
56+
self.disk_path_to_file
57+
.read()
58+
.await
59+
.get(path)
60+
.map(Clone::clone)
61+
}
62+
63+
pub async fn insert(&self, path: String, file: Arc<File>) {
64+
self.request_path_to_file
65+
.write()
66+
.await
67+
.insert(path, file.clone());
68+
self.disk_path_to_file
69+
.write()
70+
.await
71+
.insert(file.path.clone(), file);
72+
}
73+
}

fe_server/src/server.rs

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -62,30 +62,36 @@ mod routing {
6262
use super::*;
6363
use axum::routing::get;
6464
use axum::routing::Router;
65+
use tower_http::add_extension::AddExtensionLayer;
6566

6667
mod routes {
6768
pub mod fallback {
6869
use std::io::Read;
6970

70-
use axum::response::IntoResponse;
71+
use axum::{body::HttpBody, response::IntoResponse, Extension};
7172

7273
use crate::serve_files::*;
7374

74-
pub async fn fallback(uri: axum::http::Uri) -> axum::response::Response {
75-
let relative_file_path = {
76-
let relative_file_path = uri.to_string();
77-
relative_file_path
78-
.trim_start_matches('/')
79-
.trim()
80-
.to_string()
75+
pub async fn fallback(
76+
uri: axum::http::Uri,
77+
Extension(cache): Extension<crate::serve_files::Cache>,
78+
) -> axum::response::Response {
79+
let request_path = {
80+
let request_path = uri.to_string();
81+
request_path.trim_start_matches('/').trim().to_string()
8182
};
8283

8384
use crate::conf;
8485

8586
let conf = conf::EnvConf::current();
8687

88+
if let Some(file) = cache.get_request_path(&request_path).await {
89+
tracing::info!("cache hit for request path: {request_path:?}");
90+
return file_response(&file);
91+
}
92+
8793
let dir = std::path::Path::new(&conf.dir);
88-
let file_path = dir.join(relative_file_path);
94+
let file_path = dir.join(request_path.clone());
8995

9096
let (file_path) = if file_path.is_file() {
9197
file_path
@@ -104,15 +110,43 @@ mod routing {
104110
}
105111
};
106112

107-
let mut file = std::fs::File::open(&file_path).expect("opens when exists");
108-
109-
// tracing::info!("sending file {:?}", file_path);
113+
let file = cache.get_disk_path(&file_path).await;
110114

111-
let modified = file.metadata().unwrap().modified().unwrap();
112-
let mut contents = vec![];
113-
file.read_to_end(&mut contents);
115+
#[allow(unused)]
116+
let display_cache_keys = async {
117+
tracing::warn!("cache keys: {:?}", cache.read().await.keys());
118+
};
114119

115-
file_response(contents, file_path, modified)
120+
match file {
121+
None => {
122+
tracing::warn!("cache miss on file path: {file_path:?}");
123+
let process_file = |mut file: std::fs::File| {
124+
let modified = file.metadata().unwrap().modified().unwrap();
125+
let mut contents = vec![];
126+
file.read_to_end(&mut contents);
127+
File {
128+
contents,
129+
path: Box::new(file_path.clone()),
130+
request_path: request_path.clone(),
131+
modified,
132+
}
133+
};
134+
135+
let mut file = std::fs::File::open(&file_path).expect("opens when exists");
136+
let file = process_file(file);
137+
let response = file_response(&file);
138+
cache.insert(request_path, std::sync::Arc::new(file)).await;
139+
// display_cache_keys.await;
140+
response
141+
}
142+
Some(cached) => {
143+
tracing::info!("cache hit on file path: {file_path:?}");
144+
// do not go to disk, reuse cached value
145+
cache.insert(request_path, cached.clone()).await;
146+
// display_cache_keys.await;
147+
file_response(&cached)
148+
}
149+
}
116150
}
117151
}
118152

@@ -128,6 +162,7 @@ mod routing {
128162
Router::new()
129163
.nest("/api", api_router)
130164
.fallback(routes::fallback::fallback)
165+
.layer(AddExtensionLayer::new(crate::serve_files::Cache::default()))
131166
.layer(crate::trace::request_trace_layer())
132167
}
133168
}

frontend/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ static_routes = { path = "../common/static_routes" }
1111

1212
serde.workspace = true
1313
serde_json.workspace = true
14+
derived-deref.workspace = true
1415

1516
# Must match version in flake.nix
1617
wasm-bindgen = "=0.2.89"
@@ -32,7 +33,6 @@ js-sys = "0.3.64"
3233
wasm-bindgen-futures = "0.4.37"
3334
derivative = "2.2.0"
3435
tokio = { version = "1.35.1", default-features = false, features = ["sync"] }
35-
derived-deref = "2.1.0"
3636

3737
[dependencies.web-sys]
3838
version = "0.3.61"

0 commit comments

Comments
 (0)