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}