Skip to main content

rustio_admin/middleware/
compression.rs

1//! Transparent gzip compression. Kicks in for text responses over
2//! ~1KB when the client sends `Accept-Encoding: gzip`.
3//!
4//! We don't compress binary types or small bodies — the overhead isn't
5//! worth it. The threshold and content-type list are deliberately
6//! small and not configurable; if you need more, add a custom
7//! middleware.
8
9use std::io::Write;
10
11use bytes::Bytes;
12use flate2::write::GzEncoder;
13use flate2::Compression;
14
15use crate::error::Result;
16use crate::http::{Request, Response};
17use crate::router::Next;
18
19const MIN_SIZE: usize = 1024;
20
21// public:
22pub async fn gzip(req: Request, next: Next) -> Result<Response> {
23    let accepts_gzip = req
24        .header("accept-encoding")
25        .map(|v| v.contains("gzip"))
26        .unwrap_or(false);
27    let mut resp = next.run(req).await?;
28
29    if !accepts_gzip || resp.body.len() < MIN_SIZE {
30        return Ok(resp);
31    }
32
33    let content_type = resp
34        .headers
35        .iter()
36        .find(|(n, _)| n.eq_ignore_ascii_case("content-type"))
37        .map(|(_, v)| v.to_ascii_lowercase())
38        .unwrap_or_default();
39
40    if !should_compress(&content_type) {
41        return Ok(resp);
42    }
43
44    let mut encoder = GzEncoder::new(Vec::new(), Compression::fast());
45    if encoder.write_all(&resp.body).is_err() {
46        return Ok(resp);
47    }
48    let compressed = match encoder.finish() {
49        Ok(b) => b,
50        Err(_) => return Ok(resp),
51    };
52
53    resp.body = Bytes::from(compressed);
54    resp.headers
55        .push(("content-encoding".into(), "gzip".into()));
56    // Remove any stale content-length; the server sets the new one.
57    resp.headers
58        .retain(|(n, _)| !n.eq_ignore_ascii_case("content-length"));
59    Ok(resp)
60}
61
62fn should_compress(content_type: &str) -> bool {
63    content_type.starts_with("text/")
64        || content_type.starts_with("application/json")
65        || content_type.starts_with("application/javascript")
66        || content_type.starts_with("application/xml")
67        || content_type.contains("svg+xml")
68}