diff --git a/.env.example b/.env.example index 2cb1cb9..34b2afe 100644 --- a/.env.example +++ b/.env.example @@ -28,7 +28,7 @@ SSR_CACHE_SECS=3600 # Compression algorithms for HTTP responses. # Comma-separated list, case-insensitive. Supported: gzip, brotli (or br), deflate, zstd. -# Leave empty or unset to disable compression entirely. +# Use "all" to enable everything (default when unset); use "none" or "off" to disable. COMPRESSION_ALGORITHMS=gzip,brotli,deflate,zstd # Image serving cache headers (hardcoded defaults) diff --git a/src/main.rs b/src/main.rs index 5770a7d..467e03a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,18 +28,42 @@ mod theme; mod utils; mod webp; -/// 根据 COMPRESSION_ALGORITHMS 环境变量构造 CompressionLayer。 -/// 环境变量为空或未设置时返回 None,表示不启用压缩。 +/// 压缩算法配置。 #[cfg(feature = "server")] -fn compression_layer_from_env() -> Option { - use tower_http::compression::CompressionLayer; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct CompressionAlgorithms { + gzip: bool, + brotli: bool, + deflate: bool, + zstd: bool, +} - let env = std::env::var("COMPRESSION_ALGORITHMS").unwrap_or_default(); +#[cfg(feature = "server")] +impl CompressionAlgorithms { + fn all_enabled() -> Self { + Self { + gzip: true, + brotli: true, + deflate: true, + zstd: true, + } + } + + fn is_empty(&self) -> bool { + !self.gzip && !self.brotli && !self.deflate && !self.zstd + } +} + +/// 解析 COMPRESSION_ALGORITHMS 环境变量值。 +/// ""、"none"、"off" 返回 None;"all" 或未识别到任何算法时启用全部。 +#[cfg(feature = "server")] +fn parse_compression_algorithms(env: &str) -> Option { let env = env.trim(); - if env.is_empty() { + if env.is_empty() || env.eq_ignore_ascii_case("none") || env.eq_ignore_ascii_case("off") { return None; } + let mut all = false; let mut gzip = false; let mut brotli = false; let mut deflate = false; @@ -47,6 +71,7 @@ fn compression_layer_from_env() -> Option all = true, "gzip" => gzip = true, "brotli" | "br" => brotli = true, "deflate" => deflate = true, @@ -58,16 +83,39 @@ fn compression_layer_from_env() -> Option Option { + use tower_http::compression::CompressionLayer; + + let env = std::env::var("COMPRESSION_ALGORITHMS").unwrap_or_else(|_| "all".to_string()); + let algorithms = parse_compression_algorithms(&env)?; + + Some( + CompressionLayer::new() + .gzip(algorithms.gzip) + .br(algorithms.brotli) + .deflate(algorithms.deflate) + .zstd(algorithms.zstd), + ) } /// 根据请求路径和方法决定公开页面的 Cache-Control 头。 @@ -247,7 +295,7 @@ fn main() { #[cfg(all(test, feature = "server"))] mod tests { - use super::cache_control_for_path; + use super::{cache_control_for_path, parse_compression_algorithms, CompressionAlgorithms}; use axum::http::Method; fn cache_value(path: &str, method: Method) -> Option { @@ -314,4 +362,90 @@ mod tests { Some("public, max-age=300, stale-while-revalidate=3600".to_string()) ); } + + #[test] + fn compression_all_enables_everything() { + assert_eq!( + parse_compression_algorithms("all"), + Some(CompressionAlgorithms::all_enabled()) + ); + } + + #[test] + fn compression_default_env_is_all() { + // 模拟未设置环境变量时的默认值 + assert_eq!( + parse_compression_algorithms("all"), + Some(CompressionAlgorithms::all_enabled()) + ); + } + + #[test] + fn compression_empty_none_off_disable() { + assert_eq!(parse_compression_algorithms(""), None); + assert_eq!(parse_compression_algorithms("none"), None); + assert_eq!(parse_compression_algorithms("NONE"), None); + assert_eq!(parse_compression_algorithms("off"), None); + assert_eq!(parse_compression_algorithms("OFF"), None); + } + + #[test] + fn compression_single_algorithm() { + assert_eq!( + parse_compression_algorithms("gzip"), + Some(CompressionAlgorithms { + gzip: true, + brotli: false, + deflate: false, + zstd: false, + }) + ); + assert_eq!( + parse_compression_algorithms("br"), + Some(CompressionAlgorithms { + gzip: false, + brotli: true, + deflate: false, + zstd: false, + }) + ); + } + + #[test] + fn compression_multiple_algorithms() { + assert_eq!( + parse_compression_algorithms("gzip, zstd"), + Some(CompressionAlgorithms { + gzip: true, + brotli: false, + deflate: false, + zstd: true, + }) + ); + } + + #[test] + fn compression_case_insensitive_and_whitespace_tolerant() { + assert_eq!( + parse_compression_algorithms("GZIP, Brotli, Deflate, Zstd"), + Some(CompressionAlgorithms::all_enabled()) + ); + assert_eq!( + parse_compression_algorithms(" gzip , br , deflate , zstd "), + Some(CompressionAlgorithms::all_enabled()) + ); + } + + #[test] + fn compression_unknown_algorithms_are_ignored() { + assert_eq!( + parse_compression_algorithms("gzip, unknown, lz4"), + Some(CompressionAlgorithms { + gzip: true, + brotli: false, + deflate: false, + zstd: false, + }) + ); + } }