1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
use etag::EntityTag;
use trillium::{async_trait, Conn, Handler, Status};

use crate::CachingHeadersExt;

/**
# Etag and If-None-Match header handler

Trillium handler that provides an outbound [`etag
header`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag)
after other handlers have been run, and if the request includes an
[`if-none-match`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match)
header, compares these values and sends a
[`304 not modified`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304) status,
omitting the response body.

## Streamed bodies

Note that this handler does not currently provide an etag trailer for
streamed bodies, but may do so in the future.

## Strong vs weak comparison

Etags can be compared using a strong method or a weak
method. By default, this handler allows weak comparison. To change
this setting, construct your handler with `Etag::new().strong()`.
See [`etag::EntityTag`](https://docs.rs/etag/3.0.0/etag/struct.EntityTag.html#comparison)
for further documentation.
*/
#[derive(Default, Clone, Copy, Debug)]
pub struct Etag {
    strong: bool,
}

impl Etag {
    /// constructs a new Etag handler
    pub fn new() -> Self {
        Self::default()
    }

    /// Configures this handler to use strong content-based etag
    /// comparison only. See
    /// [`etag::EntityTag`](https://docs.rs/etag/3.0.0/etag/struct.EntityTag.html#comparison)
    /// for further documentation on the differences between strong
    /// and weak etag comparison.
    pub fn strong(mut self) -> Self {
        self.strong = true;
        self
    }
}

#[async_trait]
impl Handler for Etag {
    async fn run(&self, conn: Conn) -> Conn {
        conn
    }

    async fn before_send(&self, mut conn: Conn) -> Conn {
        let if_none_match = conn.if_none_match();

        let etag = conn.etag().or_else(|| {
            let etag = conn
                .inner()
                .response_body()
                .and_then(|body| body.static_bytes())
                .map(EntityTag::from_data);

            if let Some(ref entity_tag) = etag {
                conn.set_etag(entity_tag);
            }

            etag
        });

        if let (Some(ref etag), Some(ref if_none_match)) = (etag, if_none_match) {
            let eq = if self.strong {
                etag.strong_eq(if_none_match)
            } else {
                etag.weak_eq(if_none_match)
            };

            if eq {
                return conn.with_status(Status::NotModified);
            }
        }

        conn
    }
}