rust_serv/handler/
compress.rs1use crate::error::{Error, Result};
2use hyper::HeaderMap;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum CompressionType {
7 Gzip,
8 Brotli,
9 None,
10}
11
12pub fn parse_accept_encoding(headers: &HeaderMap) -> CompressionType {
14 let accept_encoding = match headers.get("Accept-Encoding")
15 .and_then(|v| v.to_str().ok()) {
16 Some(h) => h,
17 None => return CompressionType::None,
18 };
19
20 if accept_encoding.contains("br") {
22 return CompressionType::Brotli;
23 }
24
25 if accept_encoding.contains("gzip") {
27 return CompressionType::Gzip;
28 }
29
30 CompressionType::None
31}
32
33pub fn should_skip_compression(content_type: &str) -> bool {
35 content_type.starts_with("image/")
37 || content_type.starts_with("video/")
38 || content_type.starts_with("audio/")
39 || content_type == "application/gzip"
40 || content_type == "application/x-gzip"
41 || content_type == "application/x-brotli"
42 || content_type == "application/zip"
43 || content_type == "application/x-rar-compressed"
44 || content_type == "application/x-7z-compressed"
45 || content_type == "application/x-tar"
46 || content_type == "application/x-tar-gz"
47}
48
49pub fn compress_gzip(data: &[u8]) -> Result<Vec<u8>> {
51 use flate2::write::GzEncoder;
52 use flate2::Compression;
53 use std::io::Write;
54
55 let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
56 encoder.write_all(data)?;
57 encoder.finish()
58 .map_err(|e| Error::Internal(format!("Gzip compression failed: {}", e)))
59}
60
61pub fn compress_brotli(data: &[u8]) -> Result<Vec<u8>> {
63 use std::io::Write;
64
65 let mut compressed = Vec::new();
66
67 {
69 let quality = 11u32;
70 let lgwin = 22u32;
71 let mut encoder = brotli::CompressorWriter::new(&mut compressed, 4096, quality, lgwin);
72 encoder.write_all(data)
73 .map_err(|e| Error::Internal(format!("Brotli compression failed: {}", e)))?;
74 encoder.flush()
75 .map_err(|e| Error::Internal(format!("Brotli flush failed: {}", e)))?;
76 }
78
79 Ok(compressed)
80}
81
82pub fn compress(data: &[u8], compression_type: CompressionType) -> Result<Vec<u8>> {
84 if data.len() < 512 {
86 return Ok(data.to_vec());
87 }
88
89 match compression_type {
90 CompressionType::Gzip => compress_gzip(data),
91 CompressionType::Brotli => compress_brotli(data),
92 CompressionType::None => Ok(data.to_vec()),
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use hyper::HeaderMap;
100
101 #[test]
102 fn test_parse_accept_encoding_gzip() {
103 let mut headers = HeaderMap::new();
104 headers.insert("Accept-Encoding", "gzip, deflate".parse().unwrap());
105 assert_eq!(parse_accept_encoding(&headers), CompressionType::Gzip);
106 }
107
108 #[test]
109 fn test_parse_accept_encoding_brotli() {
110 let mut headers = HeaderMap::new();
111 headers.insert("Accept-Encoding", "br, gzip".parse().unwrap());
112 assert_eq!(parse_accept_encoding(&headers), CompressionType::Brotli);
113 }
114
115 #[test]
116 fn test_parse_accept_encoding_none() {
117 let headers = HeaderMap::new();
118 assert_eq!(parse_accept_encoding(&headers), CompressionType::None);
119 }
120
121 #[test]
122 fn test_parse_accept_encoding_identity() {
123 let mut headers = HeaderMap::new();
124 headers.insert("Accept-Encoding", "identity".parse().unwrap());
125 assert_eq!(parse_accept_encoding(&headers), CompressionType::None);
126 }
127
128 #[test]
129 fn test_should_skip_compression_images() {
130 assert!(should_skip_compression("image/jpeg"));
131 assert!(should_skip_compression("image/png"));
132 }
133
134 #[test]
135 fn test_should_skip_compression_videos() {
136 assert!(should_skip_compression("video/mp4"));
137 assert!(should_skip_compression("video/webm"));
138 }
139
140 #[test]
141 fn test_should_skip_compression_audio() {
142 assert!(should_skip_compression("audio/mpeg"));
143 assert!(should_skip_compression("audio/ogg"));
144 }
145
146 #[test]
147 fn test_should_skip_compression_compressed() {
148 assert!(should_skip_compression("application/gzip"));
149 assert!(should_skip_compression("application/zip"));
150 }
151
152 #[test]
153 fn test_should_compress_text() {
154 assert!(!should_skip_compression("text/html"));
155 assert!(!should_skip_compression("application/json"));
156 assert!(!should_skip_compression("text/css"));
157 assert!(!should_skip_compression("text/javascript"));
158 }
159
160 #[test]
161 fn test_compress_gzip() {
162 let data = b"Hello, World! Hello, World! Hello, World!";
163 let compressed = compress_gzip(data).unwrap();
164
165 assert!(compressed.len() < data.len());
167 }
168
169 #[test]
170 fn test_compress_gzip_large() {
171 let data = b"Hello, World! ".repeat(100);
172 let compressed = compress_gzip(&data).unwrap();
173
174 assert!(compressed.len() < data.len() / 2);
176 }
177
178 #[test]
179 fn test_compress_brotli() {
180 let data = b"Hello, World! Hello, World! Hello, World!";
181 let compressed = compress_brotli(data).unwrap();
182
183 assert!(compressed.len() < data.len());
185 }
186
187 #[test]
188 fn test_compress_none() {
189 let data = b"Hello, World!";
190 let compressed = compress(data, CompressionType::None).unwrap();
191
192 assert_eq!(compressed, data.to_vec());
194 }
195
196 #[test]
197 fn test_compress_small_data() {
198 let data = b"Hi!";
199 let compressed = compress(data, CompressionType::Gzip).unwrap();
200
201 assert_eq!(compressed, data.to_vec());
203 }
204
205 #[test]
206 fn test_compress_repetitive_data() {
207 let data = b"ABC ".repeat(1000);
208 let compressed = compress_gzip(&data).unwrap();
209
210 assert!(compressed.len() < data.len() / 10);
212 }
213
214 #[test]
215 fn test_compress_with_none_type() {
216 let data = b"Hello, World! This is a test.";
217 let result = compress(data, CompressionType::None);
218 assert!(result.is_ok());
219 let compressed = result.unwrap();
221 assert_eq!(compressed, data.to_vec());
222 }
223}