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}