Skip to main content

sark_core/http/
compress.rs

1//! Built-in gzip response compression (libdeflater). `Gzip::with_thread_local`
2//! borrows a per-thread encoder (level 3) so a handler can compress a body when
3//! `Accept-Encoding` includes `gzip` without per-request allocation; `encode`
4//! returns the gzip bytes. This is sark's standard compression path — response
5//! bodies are never hand-rolled.
6//!
7//! ```no_run
8//! use sark_core::http::compress::Gzip;
9//! let body: &[u8] = b"hello";
10//! let zipped = Gzip::with_thread_local(|gz| gz.encode(body).to_vec());
11//! ```
12
13use std::cell::RefCell;
14
15use libdeflater::{CompressionLvl, Compressor};
16
17pub struct Gzip {
18    encoder: Compressor,
19    buf: Vec<u8>,
20}
21
22impl Gzip {
23    fn new(level: i32) -> Self {
24        let lvl = CompressionLvl::new(level).unwrap_or(CompressionLvl::fastest());
25        Self {
26            encoder: Compressor::new(lvl),
27            buf: Vec::with_capacity(64 * 1024),
28        }
29    }
30
31    pub fn encode(&mut self, src: &[u8]) -> &[u8] {
32        let cap = self.encoder.gzip_compress_bound(src.len());
33        if self.buf.capacity() < cap {
34            self.buf.reserve(cap - self.buf.capacity());
35        }
36        self.buf.resize(cap, 0);
37        let n = self
38            .encoder
39            .gzip_compress(src, &mut self.buf)
40            .expect("gzip_compress_bound undersized");
41        self.buf.truncate(n);
42        &self.buf
43    }
44
45    pub fn with_thread_local<R>(f: impl FnOnce(&mut Gzip) -> R) -> R {
46        thread_local! {
47            static SLOT: RefCell<Gzip> = RefCell::new(Gzip::new(3));
48        }
49        SLOT.with(|cell| f(&mut cell.borrow_mut()))
50    }
51}