sentinel_proxy/static_files/
compression.rs1use anyhow::Result;
7use bytes::Bytes;
8use flate2::write::GzEncoder;
9use flate2::Compression;
10use http::{header, Request};
11use std::io::Write;
12
13#[derive(Debug, Clone, Copy, PartialEq)]
19pub enum ContentEncoding {
20 Identity,
21 Gzip,
22 Brotli,
23}
24
25impl ContentEncoding {
26 pub fn as_str(&self) -> &'static str {
28 match self {
29 ContentEncoding::Identity => "identity",
30 ContentEncoding::Gzip => "gzip",
31 ContentEncoding::Brotli => "br",
32 }
33 }
34}
35
36pub fn should_compress(content_type: &str) -> bool {
42 content_type.starts_with("text/")
43 || content_type.contains("javascript")
44 || content_type.contains("json")
45 || content_type.contains("xml")
46 || content_type.contains("svg")
47 || content_type == "application/wasm"
48}
49
50pub fn negotiate_encoding<B>(req: &Request<B>) -> ContentEncoding {
52 if let Some(accept_encoding) = req.headers().get(header::ACCEPT_ENCODING) {
53 if let Ok(accept_str) = accept_encoding.to_str() {
54 if accept_str.contains("br") {
56 return ContentEncoding::Brotli;
57 }
58 if accept_str.contains("gzip") {
60 return ContentEncoding::Gzip;
61 }
62 }
63 }
64 ContentEncoding::Identity
65}
66
67pub fn compress_content(content: &Bytes, encoding: ContentEncoding) -> Result<Bytes> {
69 match encoding {
70 ContentEncoding::Gzip => {
71 let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
72 encoder.write_all(content)?;
73 let compressed = encoder.finish()?;
74 Ok(Bytes::from(compressed))
75 }
76 ContentEncoding::Brotli => {
77 let mut compressed = Vec::new();
78 {
79 let mut encoder = brotli::CompressorWriter::new(&mut compressed, 4096, 4, 22);
80 encoder.write_all(content)?;
81 }
82 Ok(Bytes::from(compressed))
83 }
84 ContentEncoding::Identity => Ok(content.clone()),
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_should_compress() {
94 assert!(should_compress("text/html"));
95 assert!(should_compress("text/css"));
96 assert!(should_compress("application/javascript"));
97 assert!(should_compress("application/json"));
98 assert!(should_compress("image/svg+xml"));
99 assert!(should_compress("application/wasm"));
100
101 assert!(!should_compress("image/png"));
102 assert!(!should_compress("image/jpeg"));
103 assert!(!should_compress("application/octet-stream"));
104 }
105
106 #[test]
107 fn test_content_encoding_as_str() {
108 assert_eq!(ContentEncoding::Identity.as_str(), "identity");
109 assert_eq!(ContentEncoding::Gzip.as_str(), "gzip");
110 assert_eq!(ContentEncoding::Brotli.as_str(), "br");
111 }
112
113 #[test]
114 fn test_compress_content_gzip() {
115 let content = Bytes::from("Hello, World!");
116 let compressed = compress_content(&content, ContentEncoding::Gzip).unwrap();
117
118 assert!(!compressed.is_empty());
120 }
121
122 #[test]
123 fn test_compress_content_identity() {
124 let content = Bytes::from("Hello, World!");
125 let result = compress_content(&content, ContentEncoding::Identity).unwrap();
126 assert_eq!(result, content);
127 }
128}