Skip to main content

trillium_caching_headers/
etag.rs

1use crate::CachingHeadersExt;
2use etag::EntityTag;
3use trillium::{Conn, Handler, Status};
4
5/// # Etag and If-None-Match header handler
6///
7/// Trillium handler that provides an outbound [`etag
8/// header`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag)
9/// after other handlers have been run, and if the request includes an
10/// [`if-none-match`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match)
11/// header, compares these values and sends a
12/// [`304 not modified`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304) status,
13/// omitting the response body.
14///
15/// ## Streamed bodies
16///
17/// Note that this handler does not currently provide an etag trailer for
18/// streamed bodies, but may do so in the future.
19///
20/// ## Strong vs weak comparison
21///
22/// Etags can be compared using a strong method or a weak
23/// method. By default, this handler allows weak comparison. To change
24/// this setting, construct your handler with `Etag::new().strong()`.
25/// See [`etag::EntityTag`](https://docs.rs/etag/3.0.0/etag/struct.EntityTag.html#comparison)
26/// for further documentation.
27#[derive(Default, Clone, Copy, Debug)]
28pub struct Etag {
29    strong: bool,
30}
31
32impl Etag {
33    /// constructs a new Etag handler
34    pub fn new() -> Self {
35        Self::default()
36    }
37
38    /// Configures this handler to use strong content-based etag
39    /// comparison only. See
40    /// [`etag::EntityTag`](https://docs.rs/etag/3.0.0/etag/struct.EntityTag.html#comparison)
41    /// for further documentation on the differences between strong
42    /// and weak etag comparison.
43    pub fn strong(mut self) -> Self {
44        self.strong = true;
45        self
46    }
47}
48
49impl Handler for Etag {
50    async fn run(&self, conn: Conn) -> Conn {
51        conn
52    }
53
54    async fn before_send(&self, mut conn: Conn) -> Conn {
55        let if_none_match = conn.if_none_match();
56
57        let etag = conn.etag().or_else(|| {
58            let etag = conn
59                .response_body()
60                .and_then(|body| body.static_bytes())
61                .map(EntityTag::from_data);
62
63            if let Some(ref entity_tag) = etag {
64                conn.set_etag(entity_tag);
65            }
66
67            etag
68        });
69
70        if let (Some(ref etag), Some(ref if_none_match)) = (etag, if_none_match) {
71            let eq = if self.strong {
72                etag.strong_eq(if_none_match)
73            } else {
74                etag.weak_eq(if_none_match)
75            };
76
77            if eq {
78                return conn.with_status(Status::NotModified);
79            }
80        }
81
82        conn
83    }
84}