atagen

malice

public
Star0
Watch0

Commit 54e1929170a7

tarball

atagen <boss@atagen.co>2026-10-26 03:48 UTC

6 files changed+133-25
server: never answer 304 for the shell and serve precompressed assets

ServeDir stamped every nix store file with an epoch Last-Modified, so conditional requests for /, /index.html and /sw.js always got 304 and Helium stayed on a service worker from 10-23 that predates the avatar cache. Hashed assets are now immutable for a year and brotli or gzip copies are emitted at build time, which shrinks the worker wasm transfer from 18.6MB to 3.6MB.
Modified file: Cargo.lock+48 additions−11 deletions
@@ -51,6 +51,21 @@ dependencies = [
5151 ]
5252
5353 [[package]]
54+name = "alloc-no-stdlib"
55+version = "2.0.4"
56+source = "registry+https://github.com/rust-lang/crates.io-index"
57+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
58+
59+[[package]]
60+name = "alloc-stdlib"
61+version = "0.2.4"
62+source = "registry+https://github.com/rust-lang/crates.io-index"
63+checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195"
64+dependencies = [
65+ "alloc-no-stdlib",
66+]
67+
68+[[package]]
5469 name = "allocator-api2"
5570 version = "0.2.21"
5671 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -439,6 +454,27 @@ dependencies = [
439454 ]
440455
441456 [[package]]
457+name = "brotli"
458+version = "8.0.4"
459+source = "registry+https://github.com/rust-lang/crates.io-index"
460+checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3"
461+dependencies = [
462+ "alloc-no-stdlib",
463+ "alloc-stdlib",
464+ "brotli-decompressor",
465+]
466+
467+[[package]]
468+name = "brotli-decompressor"
469+version = "5.0.3"
470+source = "registry+https://github.com/rust-lang/crates.io-index"
471+checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583"
472+dependencies = [
473+ "alloc-no-stdlib",
474+ "alloc-stdlib",
475+]
476+
477+[[package]]
442478 name = "bs58"
443479 version = "0.5.1"
444480 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -619,6 +655,7 @@ version = "0.4.43"
619655 source = "registry+https://github.com/rust-lang/crates.io-index"
620656 checksum = "bef16c47ba2797aa6a909cc37d39911f3a6743811fe7408ac0b0cc0276b656e9"
621657 dependencies = [
658+ "brotli",
622659 "compression-core",
623660 "flate2",
624661 "memchr",
@@ -2212,7 +2249,7 @@ checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e"
22122249 [[package]]
22132250 name = "macltok"
22142251 version = "0.1.0"
2215-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/macltok?rev=038cc6faeb1429501e5cd06596b831bbd2044b92#038cc6faeb1429501e5cd06596b831bbd2044b92"
2252+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/macltok?rev=417da9e719bcfbb8b60e4ff4f1a09f3d6b0d0160#417da9e719bcfbb8b60e4ff4f1a09f3d6b0d0160"
22162253 dependencies = [
22172254 "futures-util",
22182255 "getrandom 0.2.17",
@@ -2447,7 +2484,7 @@ dependencies = [
24472484 [[package]]
24482485 name = "matrix-sdk"
24492486 version = "0.18.0"
2450-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"
2487+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"
24512488 dependencies = [
24522489 "anymap2",
24532490 "aquamarine",
@@ -2510,7 +2547,7 @@ dependencies = [
25102547 [[package]]
25112548 name = "matrix-sdk-base"
25122549 version = "0.18.0"
2513-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"
2550+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"
25142551 dependencies = [
25152552 "as_variant",
25162553 "assert_matches",
@@ -2540,7 +2577,7 @@ dependencies = [
25402577 [[package]]
25412578 name = "matrix-sdk-common"
25422579 version = "0.18.0"
2543-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"
2580+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"
25442581 dependencies = [
25452582 "eyeball-im",
25462583 "futures-core",
@@ -2563,7 +2600,7 @@ dependencies = [
25632600 [[package]]
25642601 name = "matrix-sdk-crypto"
25652602 version = "0.18.0"
2566-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"
2603+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"
25672604 dependencies = [
25682605 "aes",
25692606 "aquamarine",
@@ -2604,7 +2641,7 @@ dependencies = [
26042641 [[package]]
26052642 name = "matrix-sdk-indexeddb"
26062643 version = "0.18.0"
2607-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"
2644+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"
26082645 dependencies = [
26092646 "async-trait",
26102647 "base64 0.22.1",
@@ -2635,7 +2672,7 @@ dependencies = [
26352672 [[package]]
26362673 name = "matrix-sdk-sqlite"
26372674 version = "0.18.0"
2638-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"
2675+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"
26392676 dependencies = [
26402677 "as_variant",
26412678 "async-trait",
@@ -2662,7 +2699,7 @@ dependencies = [
26622699 [[package]]
26632700 name = "matrix-sdk-store-encryption"
26642701 version = "0.18.0"
2665-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"
2702+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"
26662703 dependencies = [
26672704 "base64 0.22.1",
26682705 "blake3",
@@ -2683,7 +2720,7 @@ dependencies = [
26832720 [[package]]
26842721 name = "matrix-sdk-test"
26852722 version = "0.18.0"
2686-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"
2723+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"
26872724 dependencies = [
26882725 "as_variant",
26892726 "ctor",
@@ -2706,7 +2743,7 @@ dependencies = [
27062743 [[package]]
27072744 name = "matrix-sdk-test-macros"
27082745 version = "0.18.0"
2709-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"
2746+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"
27102747 dependencies = [
27112748 "quote",
27122749 "syn 2.0.119",
@@ -2715,7 +2752,7 @@ dependencies = [
27152752 [[package]]
27162753 name = "matrix-sdk-ui"
27172754 version = "0.18.0"
2718-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"
2755+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"
27192756 dependencies = [
27202757 "as_variant",
27212758 "async-rx",
Modified file: crates/malice-server/Cargo.toml+2 additions−2 deletions
@@ -24,14 +24,14 @@ serde = { workspace = true }
2424 serde_json = { workspace = true }
2525 sha2 = "0.10"
2626 tokio = { workspace = true, features = ["full"] }
27-tower-http = { version = "0.6", features = ["fs", "trace"] }
27+tower-http = { version = "0.6", features = ["compression-br", "compression-gzip", "fs", "trace"] }
2828 tracing = { workspace = true }
2929 tracing-subscriber = { version = "0.3", features = ["env-filter"] }
3030
3131 [dev-dependencies]
3232 http-body-util = "0.1"
3333 tower = { version = "0.5", features = ["util"] }
34-matrix-sdk = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd", default-features = false, features = ["testing"] }
34+matrix-sdk = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "ec026bc9ea2393fd0cce6212b950d91ab6d36097", default-features = false, features = ["testing"] }
3535 reqwest = { version = "0.13", default-features = false, features = ["json", "query", "rustls"] }
3636 tempfile = "3"
3737 wiremock = "0.6"
Modified file: crates/malice-server/src/routes.rs+18 additions−4 deletions
@@ -17,6 +17,10 @@ use malice_core::{
1717 };
1818 use serde::{Deserialize, Serialize};
1919 use tower_http::{
20+ compression::{
21+ CompressionLayer,
22+ predicate::{DefaultPredicate, NotForContentType, Predicate},
23+ },
2024 services::{ServeDir, ServeFile},
2125 trace::TraceLayer,
2226 };
@@ -1145,15 +1149,25 @@ pub fn router(state: AppState, web_dir: Option<String>) -> Router {
11451149 .route("/encryption/recover", post(recover_encryption))
11461150 .route("/encryption/start", post(start_verification))
11471151 .route("/encryption/verify", post(verify_encryption));
1152+ let compression = CompressionLayer::new().compress_when(
1153+ DefaultPredicate::new()
1154+ .and(NotForContentType::const_new("video/"))
1155+ .and(NotForContentType::const_new("audio/"))
1156+ .and(NotForContentType::const_new("application/octet-stream")),
1157+ );
11481158 let api = Router::new()
1149- .nest("/api", api)
1159+ .nest("/api", api.layer(compression))
11501160 .layer(middleware::from_fn(csrf_guard));
11511161 let router = api.with_state(state).layer(TraceLayer::new_for_http());
11521162 match web_dir {
11531163 Some(dir) => {
1154- let files =
1155- ServeDir::new(&dir).not_found_service(ServeFile::new(format!("{dir}/index.html")));
1156- router.fallback_service(files)
1164+ let files = ServeDir::new(&dir)
1165+ .precompressed_br()
1166+ .precompressed_gzip()
1167+ .not_found_service(ServeFile::new(format!("{dir}/index.html")));
1168+ router
1169+ .fallback_service(files)
1170+ .layer(middleware::from_fn(malice_static::static_headers))
11571171 }
11581172 None => router.fallback(static_shell),
11591173 }
Modified file: crates/malice-static/src/lib.rs+26 additions−6 deletions
@@ -105,15 +105,33 @@ fn is_no_cache_path(path: &str) -> bool {
105105 matches!(path, "/" | "/index.html" | "/sw.js")
106106 }
107107
108+fn is_immutable_path(path: &str) -> bool {
109+ path.starts_with("/assets/") || path.starts_with("/twemoji/")
110+}
111+
108112 /// index.html and sw.js must never be cached, or a stale service worker or
109-/// app shell survives a rebuild; the rest of the bundle is hashed and can be
110-/// cached freely.
111-async fn static_headers(request: axum::extract::Request, next: Next) -> Response {
113+/// app shell survives a rebuild. Content-hashed assets are cached forever.
114+pub async fn static_headers(request: axum::extract::Request, next: Next) -> Response {
112115 let no_cache = is_no_cache_path(request.uri().path());
116+ let immutable = is_immutable_path(request.uri().path());
117+ let mut request = request;
118+ if no_cache {
119+ let headers = request.headers_mut();
120+ headers.remove(header::IF_MODIFIED_SINCE);
121+ headers.remove(header::IF_NONE_MATCH);
122+ }
113123 let mut response = next.run(request).await;
124+ response.headers_mut().remove(header::LAST_MODIFIED);
125+ if immutable && response.status().is_success() {
126+ response.headers_mut().insert(
127+ header::CACHE_CONTROL,
128+ HeaderValue::from_static("public, max-age=31536000, immutable"),
129+ );
130+ }
114131 if no_cache {
115132 let headers = response.headers_mut();
116- headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-cache"));
133+ headers.remove(header::ETAG);
134+ headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
117135 headers.insert(
118136 HeaderName::from_static("service-worker-allowed"),
119137 HeaderValue::from_static("/"),
@@ -131,8 +149,10 @@ pub fn static_router(web_dir: Option<String>) -> Router {
131149 .layer(TraceLayer::new_for_http());
132150 match web_dir {
133151 Some(dir) => {
134- let files =
135- ServeDir::new(&dir).not_found_service(ServeFile::new(format!("{dir}/index.html")));
152+ let files = ServeDir::new(&dir)
153+ .precompressed_br()
154+ .precompressed_gzip()
155+ .not_found_service(ServeFile::new(format!("{dir}/index.html")));
136156 router
137157 .fallback_service(files)
138158 .layer(middleware::from_fn(static_headers))
Modified file: crates/malice-static/tests/static_only.rs+36 additions−2 deletions
@@ -77,6 +77,9 @@ async fn static_only_serves_the_bundle_and_exits_when_idle() {
7777 .expect("write index.html");
7878 std::fs::write(web_dir.path().join("sw.js"), "// service worker").expect("write sw.js");
7979 std::fs::write(web_dir.path().join("style.css"), "body {}").expect("write style.css");
80+ std::fs::create_dir(web_dir.path().join("assets")).expect("create assets");
81+ std::fs::write(web_dir.path().join("assets/app-abc.wasm"), "wasm").expect("write wasm");
82+ std::fs::write(web_dir.path().join("assets/app-abc.wasm.br"), "brotli").expect("write br");
8083
8184 let client = Client::new();
8285 let port = unused_port();
@@ -87,15 +90,28 @@ async fn static_only_serves_the_bundle_and_exits_when_idle() {
8790 assert_eq!(index.status(), StatusCode::OK);
8891 assert_eq!(
8992 index.headers().get(header::CACHE_CONTROL).unwrap(),
90- "no-cache"
93+ "no-store"
9194 );
9295 assert_eq!(index.headers().get("service-worker-allowed").unwrap(), "/");
9396 assert_eq!(index.text().await.unwrap(), "<html>malice</html>");
9497
9598 let sw = client.get(format!("{base}/sw.js")).send().await.unwrap();
9699 assert_eq!(sw.status(), StatusCode::OK);
97- assert_eq!(sw.headers().get(header::CACHE_CONTROL).unwrap(), "no-cache");
100+ assert_eq!(sw.headers().get(header::CACHE_CONTROL).unwrap(), "no-store");
98101 assert_eq!(sw.headers().get("service-worker-allowed").unwrap(), "/");
102+ assert!(sw.headers().get(header::LAST_MODIFIED).is_none());
103+ assert!(sw.headers().get(header::ETAG).is_none());
104+
105+ for path in ["/", "/sw.js"] {
106+ let conditional = client
107+ .get(format!("{base}{path}"))
108+ .header(header::IF_MODIFIED_SINCE, "Thu, 01 Jan 1970 00:00:01 GMT")
109+ .send()
110+ .await
111+ .unwrap();
112+ assert_eq!(conditional.status(), StatusCode::OK, "{path}");
113+ assert!(!conditional.text().await.unwrap().is_empty(), "{path}");
114+ }
99115
100116 let asset = client
101117 .get(format!("{base}/style.css"))
@@ -105,6 +121,24 @@ async fn static_only_serves_the_bundle_and_exits_when_idle() {
105121 assert_eq!(asset.status(), StatusCode::OK);
106122 assert!(asset.headers().get(header::CACHE_CONTROL).is_none());
107123
124+ let wasm = client
125+ .get(format!("{base}/assets/app-abc.wasm"))
126+ .header(header::ACCEPT_ENCODING, "br, gzip")
127+ .send()
128+ .await
129+ .unwrap();
130+ assert_eq!(wasm.status(), StatusCode::OK);
131+ assert_eq!(
132+ wasm.headers().get(header::CACHE_CONTROL).unwrap(),
133+ "public, max-age=31536000, immutable"
134+ );
135+ assert_eq!(wasm.headers().get(header::CONTENT_ENCODING).unwrap(), "br");
136+ assert_eq!(
137+ wasm.headers().get(header::CONTENT_TYPE).unwrap(),
138+ "application/wasm"
139+ );
140+ assert_eq!(wasm.bytes().await.unwrap().as_ref(), b"brotli");
141+
108142 assert_eq!(
109143 client
110144 .get(format!("{base}/api/rooms"))
Modified file: flake.nix+3 additions
@@ -94,6 +94,7 @@
9494 nativeBuildInputs = [
9595 pkgs.nodejs_22
9696 pkgs.importNpmLock.hooks.linkNodeModulesHook
97+ pkgs.brotli
9798 ];
9899 buildPhase = ''
99100 runHook preBuild
@@ -105,6 +106,8 @@
105106 runHook preInstall
106107 mkdir -p $out/share/malice
107108 cp -R ${outDir}/. $out/share/malice/
109+ find $out/share/malice -type f \( -name '*.wasm' -o -name '*.js' -o -name '*.css' -o -name '*.html' -o -name '*.svg' -o -name '*.json' -o -name '*.webmanifest' \) -size +1k \
110+ -exec brotli --best --keep {} \; -exec gzip -9 --keep {} \;
108111 runHook postInstall
109112 '';
110113 };