Skip to content

Commit 19f34a7

Browse files
committed
Squashed commit of the following:
commit 452e454 Author: Denis Cornehl <denis@cornehl.org> Date: Tue Nov 18 20:13:36 2025 +0100 kk commit a4f6d6a Author: Denis Cornehl <denis@cornehl.org> Date: Tue Nov 18 20:08:27 2025 +0100 kk commit 555c532 Author: Denis Cornehl <denis@cornehl.org> Date: Tue Nov 18 20:05:57 2025 +0100 stream source? commit 6faf536 Author: Denis Cornehl <denis@cornehl.org> Date: Tue Nov 18 19:52:04 2025 +0100 clean commit 4f55c51 Author: Denis Cornehl <denis@cornehl.org> Date: Tue Nov 18 19:17:36 2025 +0100 kkjk commit d8a699b Author: Denis Cornehl <denis@cornehl.org> Date: Tue Nov 18 07:31:24 2025 +0100 kk commit 192786a Author: Denis Cornehl <denis@cornehl.org> Date: Fri Nov 14 14:08:43 2025 +0100 WIP
1 parent b4e619d commit 19f34a7

File tree

12 files changed

+409
-91
lines changed

12 files changed

+409
-91
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ fn-error-context = "0.2.0"
102102
# Templating
103103
askama = "0.14.0"
104104
walkdir = "2"
105+
phf = "0.13.1"
105106

106107
# Date and Time utilities
107108
chrono = { version = "0.4.11", default-features = false, features = ["clock", "serde"] }
@@ -110,6 +111,7 @@ chrono = { version = "0.4.11", default-features = false, features = ["clock", "s
110111
thread_local = "1.1.3"
111112
constant_time_eq = "0.4.2"
112113
fastly-api = "12.0.0"
114+
md5 = "0.8.0"
113115

114116
[dev-dependencies]
115117
criterion = "0.7.0"
@@ -132,8 +134,10 @@ debug = "line-tables-only"
132134

133135
[build-dependencies]
134136
time = "0.3"
137+
md5 = "0.8.0"
135138
gix = { version = "0.74.0", default-features = false }
136139
string_cache_codegen = "0.6.1"
140+
phf_codegen = "0.13"
137141
walkdir = "2"
138142
anyhow = { version = "1.0.42", features = ["backtrace"] }
139143
grass = { version = "0.13.1", default-features = false }

NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# NOTES
2+
3+
* optionalFromRequest for TypedHeader<IfNoneMatch> never retursn "None",
4+
and always returns "Some()"

build.rs

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::{Context as _, Error, Result};
2-
use std::{env, path::Path};
2+
use std::{env, fs::File, io::Write as _, path::Path};
33

44
mod tracked {
55
use std::{
@@ -68,13 +68,27 @@ mod tracked {
6868
}
6969
}
7070

71+
type ETagMap<'a> = phf_codegen::Map<'a, String>;
72+
7173
fn main() -> Result<()> {
7274
let out_dir = env::var("OUT_DIR").context("missing OUT_DIR")?;
7375
let out_dir = Path::new(&out_dir);
7476
read_git_version()?;
75-
compile_sass(out_dir)?;
77+
78+
let mut etag_map: ETagMap = ETagMap::new();
79+
80+
compile_sass(out_dir, &mut etag_map)?;
7681
write_known_targets(out_dir)?;
7782
compile_syntax(out_dir).context("could not compile syntax files")?;
83+
calculate_static_etags(&mut etag_map)?;
84+
85+
let mut etag_file = File::create(out_dir.join("static_etag_map.rs"))?;
86+
writeln!(
87+
&mut etag_file,
88+
"pub static STATIC_ETAG_MAP: ::phf::Map<&'static str, &'static str> = {};",
89+
etag_map.build()
90+
)?;
91+
etag_file.sync_all()?;
7892

7993
// trigger recompilation when a new migration is added
8094
println!("cargo:rerun-if-changed=migrations");
@@ -118,6 +132,16 @@ fn get_git_hash() -> Result<Option<String>> {
118132
}
119133
}
120134

135+
fn etag_from_path(path: impl AsRef<Path>) -> Result<String> {
136+
Ok(etag_from_content(std::fs::read(&path)?))
137+
}
138+
139+
fn etag_from_content(content: impl AsRef<[u8]>) -> String {
140+
let digest = md5::compute(content);
141+
let md5_hex = format!("{:x}", digest);
142+
format!(r#""\"{md5_hex}\"""#)
143+
}
144+
121145
fn compile_sass_file(src: &Path, dest: &Path) -> Result<()> {
122146
let css = grass::from_path(
123147
src.to_str()
@@ -133,7 +157,7 @@ fn compile_sass_file(src: &Path, dest: &Path) -> Result<()> {
133157
Ok(())
134158
}
135159

136-
fn compile_sass(out_dir: &Path) -> Result<()> {
160+
fn compile_sass(out_dir: &Path, etag_map: &mut ETagMap) -> Result<()> {
137161
const STYLE_DIR: &str = "templates/style";
138162

139163
for entry in walkdir::WalkDir::new(STYLE_DIR) {
@@ -146,12 +170,13 @@ fn compile_sass(out_dir: &Path) -> Result<()> {
146170
.to_str()
147171
.context("file name must be a utf-8 string")?;
148172
if !file_name.starts_with('_') {
149-
let dest = out_dir
150-
.join(entry.path().strip_prefix(STYLE_DIR)?)
151-
.with_extension("css");
173+
let dest = out_dir.join(file_name).with_extension("css");
152174
compile_sass_file(entry.path(), &dest).with_context(|| {
153175
format!("compiling {} to {}", entry.path().display(), dest.display())
154176
})?;
177+
178+
let dest_str = dest.file_name().unwrap().to_str().unwrap().to_owned();
179+
etag_map.entry(dest_str, etag_from_path(&dest)?);
155180
}
156181
}
157182
}
@@ -160,7 +185,32 @@ fn compile_sass(out_dir: &Path) -> Result<()> {
160185
let pure = tracked::read_to_string("vendor/pure-css/css/pure-min.css")?;
161186
let grids = tracked::read_to_string("vendor/pure-css/css/grids-responsive-min.css")?;
162187
let vendored = pure + &grids;
163-
std::fs::write(out_dir.join("vendored").with_extension("css"), vendored)?;
188+
std::fs::write(out_dir.join("vendored").with_extension("css"), &vendored)?;
189+
190+
etag_map.entry(
191+
"vendored.css".to_owned(),
192+
etag_from_content(vendored.as_bytes()),
193+
);
194+
195+
Ok(())
196+
}
197+
198+
fn calculate_static_etags(etag_map: &mut ETagMap) -> Result<()> {
199+
const STATIC_DIRS: &[&str] = &["static", "vendor"];
200+
201+
for static_dir in STATIC_DIRS {
202+
for entry in walkdir::WalkDir::new(static_dir) {
203+
let entry = entry?;
204+
let path = entry.path();
205+
if !path.is_file() {
206+
continue;
207+
}
208+
209+
let partial_path = path.strip_prefix(static_dir).unwrap();
210+
let partial_path_str = partial_path.to_string_lossy().to_string();
211+
etag_map.entry(partial_path_str, etag_from_path(path)?);
212+
}
213+
}
164214

165215
Ok(())
166216
}

src/storage/database.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::{Blob, FileRange, StorageMetrics, StreamingBlob};
2-
use crate::{InstanceMetrics, db::Pool, error::Result};
2+
use crate::{InstanceMetrics, db::Pool, error::Result, web::headers::compute_etag};
33
use chrono::{DateTime, Utc};
44
use futures_util::stream::{Stream, TryStreamExt};
55
use sqlx::Acquire;
@@ -123,13 +123,16 @@ impl DatabaseBackend {
123123
});
124124
let content = result.content.unwrap_or_default();
125125
let content_len = content.len();
126+
127+
let etag = compute_etag(&content);
126128
Ok(StreamingBlob {
127129
path: result.path,
128130
mime: result
129131
.mime
130132
.parse()
131133
.unwrap_or(mime::APPLICATION_OCTET_STREAM),
132134
date_updated: result.date_updated,
135+
etag: Some(etag),
133136
content: Box::new(io::Cursor::new(content)),
134137
content_length: content_len,
135138
compression,

0 commit comments

Comments
 (0)