rust_web_server/compression/
mod.rs1#[cfg(test)]
2mod tests;
3
4use flate2::Compression;
5use flate2::write::GzEncoder;
6use std::io::Write;
7use crate::header::Header;
8use crate::request::Request;
9use crate::response::Response;
10
11const COMPRESSIBLE: &[&str] = &[
13 "text/html",
14 "text/css",
15 "text/javascript",
16 "text/plain",
17 "text/xml",
18 "application/json",
19 "application/javascript",
20 "application/xml",
21 "application/xhtml+xml",
22 "image/svg+xml",
23];
24
25pub const MIN_COMPRESS_SIZE: usize = 512;
29
30pub fn apply_gzip(request: &Request, response: &mut Response) {
37 if response.content_range_list.is_empty() {
38 return;
39 }
40 if response.status_code == 206 || response.content_range_list.len() > 1 {
44 return;
45 }
46 if response.headers.iter().any(|h| h.name.eq_ignore_ascii_case(Header::_CONTENT_ENCODING)) {
47 return;
48 }
49 let original_size: usize = response.content_range_list.iter().map(|cr| cr.body.len()).sum();
50 if original_size < MIN_COMPRESS_SIZE {
51 return;
52 }
53
54 let accepts_gzip = request
55 .get_header(Header::_ACCEPT_ENCODING.to_string())
56 .map(|h| h.value.to_lowercase().contains("gzip"))
57 .unwrap_or(false);
58
59 if !accepts_gzip {
60 return;
61 }
62
63 let content_type = response
64 .content_range_list
65 .first()
66 .map(|cr| cr.content_type.to_lowercase())
67 .unwrap_or_default();
68
69 let is_compressible = COMPRESSIBLE.iter().any(|mime| content_type.starts_with(mime));
70 if !is_compressible {
71 return;
72 }
73
74 let mut compressed_bodies = Vec::with_capacity(response.content_range_list.len());
75 for cr in &response.content_range_list {
76 let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
77 if encoder.write_all(&cr.body).is_err() {
78 return;
79 }
80 match encoder.finish() {
81 Ok(compressed) => compressed_bodies.push(compressed),
82 Err(_) => return,
83 }
84 }
85 let compressed_size: usize = compressed_bodies.iter().map(Vec::len).sum();
86 if compressed_size >= original_size {
87 return;
88 }
89 for (cr, compressed) in response.content_range_list.iter_mut().zip(compressed_bodies) {
90 let new_len = compressed.len() as u64;
91 cr.body = compressed;
92 cr.range.end = new_len;
93 cr.size = new_len.to_string();
94 }
95
96 response.headers.push(Header {
97 name: Header::_CONTENT_ENCODING.to_string(),
98 value: "gzip".to_string(),
99 });
100
101 let vary_pos = response.headers.iter().position(|h| h.name == Header::_VARY);
103 if let Some(i) = vary_pos {
104 let current = response.headers[i].value.clone();
105 if !current.to_lowercase().contains("accept-encoding") {
106 response.headers[i].value = format!("{}, Accept-Encoding", current);
107 }
108 } else {
109 response.headers.push(Header {
110 name: Header::_VARY.to_string(),
111 value: "Accept-Encoding".to_string(),
112 });
113 }
114}