Commit 54e1929170a7
tarball6 files changed+133-25
@@ -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",@@ -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"@@ -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
+ router1169
+ .fallback_service(files)1170
+ .layer(middleware::from_fn(malice_static::static_headers))11571171
}11581172
None => router.fallback(static_shell),11591173
}@@ -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 or109
-/// app shell survives a rebuild; the rest of the bundle is hashed and can be110
-/// 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
router137157
.fallback_service(files)138158
.layer(middleware::from_fn(static_headers))@@ -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 = client107
+ .get(format!("{base}{path}"))108
+ .header(header::IF_MODIFIED_SINCE, "Thu, 01 Jan 1970 00:00:01 GMT")109
+ .send()110
+ .await111
+ .unwrap();112
+ assert_eq!(conditional.status(), StatusCode::OK, "{path}");113
+ assert!(!conditional.text().await.unwrap().is_empty(), "{path}");114
+ }99115
100116
let asset = client101117
.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 = client125
+ .get(format!("{base}/assets/app-abc.wasm"))126
+ .header(header::ACCEPT_ENCODING, "br, gzip")127
+ .send()128
+ .await129
+ .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
client110144
.get(format!("{base}/api/rooms"))@@ -94,6 +94,7 @@
9494
nativeBuildInputs = [9595
pkgs.nodejs_229696
pkgs.importNpmLock.hooks.linkNodeModulesHook97
+ pkgs.brotli9798
];9899
buildPhase = ''99100
runHook preBuild@@ -105,6 +106,8 @@
105106
runHook preInstall106107
mkdir -p $out/share/malice107108
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 postInstall109112
'';110113
};