trillium_caching_headers/
etag.rs

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