actix_web_rust_embed_responder/
compress.rs1use std::{
2 collections::HashMap,
3 io::{BufReader, Write},
4 sync::RwLock,
5};
6
7use brotli::enc::BrotliEncoderParams;
8use flate2::Compression;
9use lazy_static::lazy_static;
10use regex::Regex;
11
12#[derive(Default)]
14pub enum Compress {
15 Never,
17 #[default]
26 IfPrecompressed,
27 IfWellKnown,
33 Always,
40}
41
42pub(crate) fn is_well_known_compressible_mime_type(mime_type: &str) -> bool {
44 lazy_static! {
45 static ref RE: Regex =
46 Regex::new(r#"^text/.*|application/(javascript|json5?|(ld|jsonml)[+]json|xml)$"#)
47 .unwrap();
48 }
49 RE.is_match(mime_type)
50}
51
52#[allow(unused_must_use)]
56pub(crate) fn compress_data_gzip(hash: &str, data: &[u8]) -> Vec<u8> {
62 lazy_static! {
63 static ref CACHED_GZIP_DATA: RwLock<HashMap<String, Vec<u8>>> = RwLock::new(HashMap::new());
64 }
65
66 if let Some(data_gzip) = CACHED_GZIP_DATA
67 .read()
68 .ok()
69 .and_then(|cached| cached.get(hash).map(ToOwned::to_owned))
70 {
71 return data_gzip;
72 }
73
74 let mut compressed: Vec<u8> = Vec::new();
75 flate2::write::GzEncoder::new(&mut compressed, Compression::default())
76 .write_all(data)
77 .unwrap();
78 CACHED_GZIP_DATA
79 .write()
80 .map(|mut cached| cached.insert(hash.to_string(), compressed.clone()));
81 compressed
82}
83
84#[allow(unused_must_use)]
88pub(crate) fn compress_data_br(hash: &str, data: &[u8]) -> Vec<u8> {
94 lazy_static! {
95 static ref CACHED_BR_DATA: RwLock<HashMap<String, Vec<u8>>> = RwLock::new(HashMap::new());
96 }
97
98 if let Some(data_gzip) = CACHED_BR_DATA
99 .read()
100 .ok()
101 .and_then(|cached| cached.get(hash).map(ToOwned::to_owned))
102 {
103 return data_gzip;
104 }
105
106 let mut data_read = BufReader::new(data);
107 let mut compressed: Vec<u8> = Vec::new();
108 brotli::BrotliCompress(
109 &mut data_read,
110 &mut compressed,
111 &BrotliEncoderParams::default(),
112 )
113 .expect("Failed to compress br data");
114 CACHED_BR_DATA
115 .write()
116 .map(|mut cached| cached.insert(hash.to_string(), compressed.clone()));
117 compressed
118}
119
120#[allow(unused_must_use)]
124#[cfg(feature = "compression-zstd")]
130pub(crate) fn compress_data_zstd(hash: &str, data: &[u8]) -> Vec<u8> {
131 lazy_static! {
132 static ref CACHED_ZSTD_DATA: RwLock<HashMap<String, Vec<u8>>> = RwLock::new(HashMap::new());
133 }
134
135 if let Some(data_zstd) = CACHED_ZSTD_DATA
136 .read()
137 .ok()
138 .and_then(|cached| cached.get(hash).map(ToOwned::to_owned))
139 {
140 return data_zstd;
141 }
142
143 let compressed = zstd::encode_all(data, 0).expect("Failed to compress zstd data");
144 CACHED_ZSTD_DATA
145 .write()
146 .map(|mut cached| cached.insert(hash.to_string(), compressed.clone()));
147 compressed
148}
149
150#[allow(unused_imports)]
151mod test {
152 use crate::compress::is_well_known_compressible_mime_type;
153 use crate::{compress_data_br, compress_data_gzip};
154 use std::io::Write;
155 use std::time::Instant;
156
157 #[test]
158 fn html_file_is_compressible() {
159 assert!(is_well_known_compressible_mime_type("text/html"))
160 }
161
162 #[test]
163 fn css_file_is_compressible() {
164 assert!(is_well_known_compressible_mime_type("text/css"))
165 }
166
167 #[test]
168 fn javascript_file_is_compressible() {
169 assert!(is_well_known_compressible_mime_type(
170 "application/javascript"
171 ))
172 }
173
174 #[test]
175 fn json_file_is_compressible() {
176 assert!(is_well_known_compressible_mime_type("application/json"))
177 }
178
179 #[test]
180 fn xml_file_is_compressible() {
181 assert!(is_well_known_compressible_mime_type("application/xml"))
182 }
183
184 #[test]
185 fn jpg_file_not_compressible() {
186 assert!(!is_well_known_compressible_mime_type("image/jpeg"))
187 }
188
189 #[test]
190 fn zip_file_not_compressible() {
191 assert!(!is_well_known_compressible_mime_type("application/zip"))
192 }
193
194 #[test]
195 fn gzip_roundtrip() {
196 let source = b"x123";
197 let compressed = compress_data_gzip("foo", source);
198 let mut decompressed = Vec::new();
199 flate2::write::GzDecoder::new(&mut decompressed)
200 .write_all(&compressed)
201 .unwrap();
202 assert_eq!(source, &decompressed[..]);
203 }
204
205 #[test]
206 fn br_roundtrip() {
207 let source = b"x123";
208 let compressed = compress_data_br("bar", source);
209 let mut decompressed = Vec::new();
210 brotli::BrotliDecompress(&mut &compressed[..], &mut decompressed).unwrap();
211 assert_eq!(source, &decompressed[..]);
212 }
213
214 #[test]
215 fn compression_is_cached() {
216 let source = b"Et quos non sed magnam reiciendis praesentium quod libero. Architecto optio tempora iure aspernatur rerum voluptatem quas. Eos ut atque quas perspiciatis dolorem quidem. Cum et quo et. Voluptatum ut est id eligendi illum inventore. Est non rerum vel rem. Molestiae similique alias nihil harum qui. Consectetur et dolores autem. Magnam et saepe ad reprehenderit. Repellendus vel excepturi eaque esse error. Deserunt est impedit totam nostrum sunt. Eligendi magnam distinctio odit iste molestias est id. Deserunt odit similique magnam repudiandae aut saepe. Dolores laboriosam consectetur quos dolores ea. Non quod veniam quisquam molestias aut deserunt tempora. Mollitia consequuntur facilis doloremque provident eligendi similique possimus. Deleniti facere quam fugiat porro. Tenetur cupiditate eum consequatur beatae dolorum. Veniam voluptatem qui eum quasi corrupti. Quis necessitatibus maxime eum numquam ipsam ducimus expedita maiores. Aliquid voluptas non aut. Tempore dicta ut aperiam ipsum ut et esse explicabo.";
217
218 let first_start = Instant::now();
219 compress_data_gzip("lorem", source);
220 let first = first_start.elapsed();
221 let second_start = Instant::now();
222 compress_data_gzip("lorem", source);
223 let second = second_start.elapsed();
224
225 assert!(first > second);
227 }
228
229 #[test]
230 fn br_compression_is_cached() {
231 let source = b"Et quos non sed magnam reiciendis praesentium quod libero. Architecto optio tempora iure aspernatur rerum voluptatem quas. Eos ut atque quas perspiciatis dolorem quidem. Cum et quo et. Voluptatum ut est id eligendi illum inventore. Est non rerum vel rem. Molestiae similique alias nihil harum qui. Consectetur et dolores autem. Magnam et saepe ad reprehenderit. Repellendus vel excepturi eaque esse error. Deserunt est impedit totam nostrum sunt. Eligendi magnam distinctio odit iste molestias est id. Deserunt odit similique magnam repudiandae aut saepe. Dolores laboriosam consectetur quos dolores ea. Non quod veniam quisquam molestias aut deserunt tempora. Mollitia consequuntur facilis doloremque provident eligendi similique possimus. Deleniti facere quam fugiat porro. Tenetur cupiditate eum consequatur beatae dolorum. Veniam voluptatem qui eum quasi corrupti. Quis necessitatibus maxime eum numquam ipsam ducimus expedita maiores. Aliquid voluptas non aut. Tempore dicta ut aperiam ipsum ut et esse explicabo.";
232
233 let first_start = Instant::now();
234 compress_data_br("lorem-br", source);
235 let first = first_start.elapsed();
236 let second_start = Instant::now();
237 compress_data_br("lorem-br", source);
238 let second = second_start.elapsed();
239
240 assert!(first > second);
242 }
243
244 #[test]
245 #[cfg(feature = "compression-zstd")]
246 fn zstd_roundtrip() {
247 let source = b"x123";
248 let compressed = crate::compress_data_zstd("foo", source);
249 let decompressed = zstd::decode_all(&compressed[..]).unwrap();
250 assert_eq!(source, &decompressed[..]);
251 }
252
253 #[test]
254 #[cfg(feature = "compression-zstd")]
255 fn zstd_compression_is_cached() {
256 let source = b"Et quos non sed magnam reiciendis praesentium quod libero. Architecto optio tempora iure aspernatur rerum voluptatem quas. Eos ut atque quas perspiciatis dolorem quidem. Cum et quo et. Voluptatum ut est id eligendi illum inventore. Est non rerum vel rem. Molestiae similique alias nihil harum qui. Consectetur et dolores autem. Magnam et saepe ad reprehenderit. Repellendus vel excepturi eaque esse error. Deserunt est impedit totam nostrum sunt. Eligendi magnam distinctio odit iste molestias est id. Deserunt odit similique magnam repudiandae aut saepe. Dolores laboriosam consectetur quos dolores ea. Non quod veniam quisquam molestias aut deserunt tempora. Mollitia consequuntur facilis doloremque provident eligendi similique possimus. Deleniti facere quam fugiat porro. Tenetur cupiditate eum consequatur beatae dolorum. Veniam voluptatem qui eum quasi corrupti. Quis necessitatibus maxime eum numquam ipsam ducimus expedita maiores. Aliquid voluptas non aut. Tempore dicta ut aperiam ipsum ut et esse explicabo.";
257
258 let first_start = Instant::now();
259 crate::compress_data_zstd("lorem-zstd", source);
260 let first = first_start.elapsed();
261 let second_start = Instant::now();
262 crate::compress_data_zstd("lorem-zstd", source);
263 let second = second_start.elapsed();
264
265 assert!(first > second);
267 }
268}