diff --git a/Cargo.lock b/Cargo.lock index 11adb533..39070ec5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -263,6 +278,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.19.1" @@ -362,6 +398,7 @@ version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" dependencies = [ + "brotli", "compression-core", "flate2", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 653f690b..ffddc409 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ tokio-stream = { version = "0.1.18", features = ["fs"] } bytes = "1.11.1" tempfile = "3.25.0" tower = "0.5.3" -tower-http = { version = "0.6.8", features = ["fs", "compression-gzip", "trace", "cors", "add-extension", "request-id", "set-header"] } +tower-http = { version = "0.6.8", features = ["fs", "compression-gzip", "compression-br", "trace", "cors", "add-extension", "request-id", "set-header"] } flate2 = "1.1.9" zip = "=6.0.0" tracing = "0.1.44" @@ -75,4 +75,3 @@ debug = true lto = "fat" codegen-units = 1 opt-level = 3 - diff --git a/src/interfaces/api/routes.rs b/src/interfaces/api/routes.rs index 0d13c143..fdb42bdd 100644 --- a/src/interfaces/api/routes.rs +++ b/src/interfaces/api/routes.rs @@ -358,7 +358,8 @@ pub fn create_api_routes(app_state: &AppState) -> Router { let admin_router = admin_handler::admin_routes().with_state(app_state.clone()); router = router.nest("/admin", admin_router); + // Compression for API responses (JSON) — excludes file downloads router - .layer(CompressionLayer::new()) + .layer(CompressionLayer::new().br(true).gzip(true)) .layer(TraceLayer::new_for_http()) } diff --git a/src/interfaces/web/mod.rs b/src/interfaces/web/mod.rs index ad480340..014a2af2 100644 --- a/src/interfaces/web/mod.rs +++ b/src/interfaces/web/mod.rs @@ -13,7 +13,7 @@ pub fn create_web_routes() -> Router { let static_path = config.static_path.clone(); // Static assets (JS, CSS, JSON, SVG, ICO) served via ServeDir - // with gzip compression and aggressive caching (7 days). + // with brotli + gzip compression and aggressive caching (7 days). // HTML pages are served via explicit routes (include_str!) and // do NOT pass through these layers. let static_service = ServeDir::new(static_path); @@ -26,7 +26,7 @@ pub fn create_web_routes() -> Router { .route("/shared", get(serve_shared_page)) // Serve static files with compression + cache headers .fallback_service(static_service) - .layer(CompressionLayer::new()) + .layer(CompressionLayer::new().br(true).gzip(true)) .layer(SetResponseHeaderLayer::if_not_present( CACHE_CONTROL, HeaderValue::from_static("public, max-age=604800, stale-while-revalidate=86400"),