rama_http/layer/compression/
predicate.rs1use rama_core::extensions::{Extension, Extensions, ExtensionsRef};
4use rama_http_types::{HeaderMap, StatusCode, StreamingBody, Version, header};
5use rama_utils::str::arcstr::{ArcStr, arcstr};
6
7use crate::headers::encoding::Encoding;
8use crate::layer::decompression::DecompressedFrom;
9
10pub trait Predicate: Clone {
12 fn should_compress<B>(&self, response: &mut rama_http_types::Response<B>) -> bool
17 where
18 B: StreamingBody;
19
20 fn and<Other>(self, other: Other) -> And<Self, Other>
24 where
25 Self: Sized,
26 Other: Predicate,
27 {
28 And {
29 lhs: self,
30 rhs: other,
31 }
32 }
33}
34
35impl<F> Predicate for F
36where
37 F: Fn(StatusCode, Version, &HeaderMap, &Extensions) -> bool + Clone,
38{
39 fn should_compress<B>(&self, response: &mut rama_http_types::Response<B>) -> bool
40 where
41 B: StreamingBody,
42 {
43 let status = response.status();
44 let version = response.version();
45 let headers = response.headers().clone();
46 let extensions = response.extensions();
47 self(status, version, &headers, extensions)
48 }
49}
50
51#[derive(Debug, Clone, Default, Copy)]
53#[non_exhaustive]
54pub struct Always;
55
56impl Always {
57 #[must_use]
58 pub fn new() -> Self {
59 Self
60 }
61}
62
63impl Predicate for Always {
64 fn should_compress<B>(&self, _response: &mut rama_http_types::Response<B>) -> bool
65 where
66 B: StreamingBody,
67 {
68 true
69 }
70}
71
72impl<T> Predicate for Option<T>
73where
74 T: Predicate,
75{
76 fn should_compress<B>(&self, response: &mut rama_http_types::Response<B>) -> bool
77 where
78 B: StreamingBody,
79 {
80 self.as_ref()
81 .map(|inner| inner.should_compress(response))
82 .unwrap_or(true)
83 }
84}
85
86#[derive(Debug, Clone, Default, Copy)]
90pub struct And<Lhs, Rhs> {
91 lhs: Lhs,
92 rhs: Rhs,
93}
94
95impl<Lhs, Rhs> Predicate for And<Lhs, Rhs>
96where
97 Lhs: Predicate,
98 Rhs: Predicate,
99{
100 fn should_compress<B>(&self, response: &mut rama_http_types::Response<B>) -> bool
101 where
102 B: StreamingBody,
103 {
104 self.lhs.should_compress(response) && self.rhs.should_compress(response)
105 }
106}
107
108#[derive(Debug, Clone)]
139pub struct DefaultPredicate(
140 And<And<And<SizeAbove, NotForContentType>, NotForContentType>, NotForContentType>,
141);
142
143impl DefaultPredicate {
144 #[must_use]
146 pub fn new() -> Self {
147 let inner = SizeAbove::new(SizeAbove::DEFAULT_MIN_SIZE)
148 .and(NotForContentType::GRPC)
149 .and(NotForContentType::IMAGES)
150 .and(NotForContentType::SSE);
151 Self(inner)
152 }
153}
154
155impl Default for DefaultPredicate {
156 fn default() -> Self {
157 Self::new()
158 }
159}
160
161impl Predicate for DefaultPredicate {
162 fn should_compress<B>(&self, response: &mut rama_http_types::Response<B>) -> bool
163 where
164 B: StreamingBody,
165 {
166 self.0.should_compress(response)
167 }
168}
169
170#[derive(Debug, Clone)]
171pub struct DefaultStreamPredicate(And<SizeAbove, NotForContentType>);
172
173impl DefaultStreamPredicate {
174 #[must_use]
176 pub fn new() -> Self {
177 let inner = SizeAbove::new(SizeAbove::DEFAULT_MIN_SIZE).and(NotForContentType::IMAGES);
178 Self(inner)
179 }
180}
181
182impl Default for DefaultStreamPredicate {
183 fn default() -> Self {
184 Self::new()
185 }
186}
187
188impl Predicate for DefaultStreamPredicate {
189 fn should_compress<B>(&self, response: &mut rama_http_types::Response<B>) -> bool
190 where
191 B: StreamingBody,
192 {
193 self.0.should_compress(response)
194 }
195}
196
197#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Extension)]
199#[extension(tags(http))]
200pub enum PreferredEncoding {
201 #[default]
202 Gzip,
203 Deflate,
204 Brotli,
205 Zstd,
206}
207
208impl PreferredEncoding {
209 #[must_use]
210 pub const fn as_encoding(self) -> Encoding {
211 match self {
212 Self::Gzip => Encoding::Gzip,
213 Self::Deflate => Encoding::Deflate,
214 Self::Brotli => Encoding::Brotli,
215 Self::Zstd => Encoding::Zstd,
216 }
217 }
218}
219
220#[derive(Debug, Clone, Copy, Default)]
224#[non_exhaustive]
225pub struct MirrorDecompressed;
226
227impl MirrorDecompressed {
228 #[must_use]
229 pub fn new() -> Self {
230 Self
231 }
232}
233
234impl Predicate for MirrorDecompressed {
235 fn should_compress<B>(&self, response: &mut rama_http_types::Response<B>) -> bool
236 where
237 B: StreamingBody,
238 {
239 let preferred = match response.extensions().get_ref::<DecompressedFrom>() {
240 Some(DecompressedFrom::Gzip) => PreferredEncoding::Gzip,
241 Some(DecompressedFrom::Deflate) => PreferredEncoding::Deflate,
242 Some(DecompressedFrom::Brotli) => PreferredEncoding::Brotli,
243 Some(DecompressedFrom::Zstd) => PreferredEncoding::Zstd,
244 None => return false,
245 };
246
247 response.extensions().insert(preferred);
248 true
249 }
250}
251
252#[derive(Clone, Copy, Debug)]
254pub struct SizeAbove(u64);
255
256impl SizeAbove {
257 pub(crate) const DEFAULT_MIN_SIZE: u64 = 32;
258
259 #[must_use]
265 pub const fn new(min_size_bytes: u64) -> Self {
266 Self(min_size_bytes)
267 }
268}
269
270impl Default for SizeAbove {
271 fn default() -> Self {
272 Self(Self::DEFAULT_MIN_SIZE)
273 }
274}
275
276impl Predicate for SizeAbove {
277 fn should_compress<B>(&self, response: &mut rama_http_types::Response<B>) -> bool
278 where
279 B: StreamingBody,
280 {
281 let content_size = response.body().size_hint().exact().or_else(|| {
282 response
283 .headers()
284 .get(header::CONTENT_LENGTH)
285 .and_then(|h| h.to_str().ok())
286 .and_then(|val| val.parse().ok())
287 });
288
289 match content_size {
290 Some(size) => size >= self.0,
291 _ => true,
292 }
293 }
294}
295
296#[derive(Clone, Debug)]
298pub struct NotForContentType {
299 content_type: ArcStr,
300 exception: Option<ArcStr>,
301}
302
303impl NotForContentType {
304 pub const GRPC: Self = Self {
306 content_type: arcstr!("application/grpc"),
307 exception: Some(arcstr!("application/grpc-web")),
308 };
309
310 pub const IMAGES: Self = Self {
312 content_type: arcstr!("image/"),
313 exception: Some(arcstr!("image/svg+xml")),
314 };
315
316 pub const SSE: Self = Self::new(arcstr!("text/event-stream"));
318
319 #[must_use]
321 pub const fn new(content_type: ArcStr) -> Self {
322 Self {
323 content_type,
324 exception: None,
325 }
326 }
327}
328
329impl Predicate for NotForContentType {
330 fn should_compress<B>(&self, response: &mut rama_http_types::Response<B>) -> bool
331 where
332 B: StreamingBody,
333 {
334 let cty = content_type(response);
335 if let Some(except) = &self.exception
336 && cty.starts_with(except.as_str())
337 {
338 return true;
339 }
340
341 !cty.starts_with(self.content_type.as_str())
342 }
343}
344
345fn content_type<B>(response: &rama_http_types::Response<B>) -> &str {
346 response
347 .headers()
348 .get(header::CONTENT_TYPE)
349 .and_then(|h| h.to_str().ok())
350 .unwrap_or_default()
351}