tower_etag_cache/
passthrough_predicate.rs

1use http::{
2    header::{CONTENT_LENGTH, ETAG},
3    Method,
4};
5
6/// Controls when requests and responses should ignore the caching layer
7pub trait PassthroughPredicate: Clone {
8    /// Returns true if the given request should ignore the 2 EtagCache services
9    /// and only be processed by the inner service
10    fn should_passthrough_req<T>(&mut self, req: &http::Request<T>) -> bool;
11
12    /// Returns true if the given inner service response should ignore the
13    /// second EtagCache service and not have its ETag calculated and cached
14    fn should_passthrough_resp<T>(&mut self, resp: &http::Response<T>) -> bool;
15}
16
17/// A [`PassthroughPredicate`] with sensible defaults for controlling ETag cache behaviour
18#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash)]
19pub struct DefaultPredicate;
20
21impl PassthroughPredicate for DefaultPredicate {
22    /// Only run GET and HEAD methods through cache services
23    fn should_passthrough_req<T>(&mut self, req: &http::Request<T>) -> bool {
24        !matches!(*req.method(), Method::GET | Method::HEAD)
25    }
26
27    /// Only cache:
28    /// - 2XX responses excluding 204 No Content
29    /// - responses that dont already have ETag header set
30    /// - responses that either dont have a valid Content-Length header or have a non-zero Content-Length
31    fn should_passthrough_resp<T>(&mut self, resp: &http::Response<T>) -> bool {
32        match resp.status().as_u16() {
33            200..=203 | 205..=299 => (),
34            _ => return true,
35        }
36        if resp.headers().contains_key(ETAG) {
37            return true;
38        }
39        let content_length_hv = match resp.headers().get(CONTENT_LENGTH) {
40            Some(s) => s,
41            None => return false,
42        };
43        let content_length_str = match content_length_hv.to_str() {
44            Ok(s) => s,
45            Err(_) => return false,
46        };
47        let content_length: usize = match content_length_str.parse() {
48            Ok(u) => u,
49            Err(_) => return false,
50        };
51        content_length == 0
52    }
53}