Skip to main content

tower_http_cache_plus/cache/
response.rs

1use super::{body::*, configuration::*, hooks::*, weight::*};
2
3use {
4    core::any::*,
5    duration_str::*,
6    http::{header::*, response::*, *},
7    http_body::*,
8    kutil::{
9        http::*,
10        std::{error::*, immutable::*},
11        transcoding::*,
12    },
13    std::{io, mem::*, result::Result, sync::*, time::*},
14};
15
16/// Common reference type for [CachedResponse].
17pub type CachedResponseRef = Arc<CachedResponse>;
18
19//
20// CachedResponse
21//
22
23/// Cached HTTP response.
24///
25/// Caching the body is handled by [CachedBody].
26#[derive(Clone, Debug)]
27pub struct CachedResponse {
28    /// Response parts.
29    pub parts: Parts,
30
31    /// Response body.
32    pub body: CachedBody,
33
34    /// Optional duration.
35    pub duration: Option<Duration>,
36}
37
38impl CachedResponse {
39    /// Constructor.
40    ///
41    /// Reads the response body and stores it as [ImmutableBytes].
42    ///
43    /// If `known_body_size` is not [None] then that's the size we expect. Otherwise
44    /// we'll try to read to `max_body_size` and will expect at least `min_body_size`.
45    ///
46    /// In either case we will return an error if the body wasn't completely read (we won't cache
47    /// incomplete bodies!), together with [ResponsePieces], which can be used by the caller to
48    /// reconstruct the original response.
49    ///
50    /// `preferred_encoding` is the encoding in which we *want* to store the body. If the response's
51    /// encoding is different from what we want then it will be reencoded, unless the `XX-Encode`
52    /// header is "false", in which case it's as if `preferred_encoding` were
53    /// [Identity](Encoding::Identity).
54    ///
55    /// If an [Identity](Encoding::Identity) is created during this reencoding then it will also be
56    /// stored if `keep_identity_encoding` is true.
57    ///
58    /// If the response doesn't already have a `Last-Modified` header, we will set it to the
59    /// current time.
60    pub async fn new_for<BodyT>(
61        uri: &Uri,
62        response: Response<BodyT>,
63        declared_body_size: Option<usize>,
64        mut preferred_encoding: Encoding,
65        skip_encoding: bool,
66        caching_configuration: &CachingConfiguration,
67        encoding_configuration: &EncodingConfiguration,
68    ) -> Result<Self, ErrorWithResponsePieces<ReadBodyError, BodyT>>
69    where
70        BodyT: Body + Unpin,
71        BodyT::Error: Into<CapturedError>,
72    {
73        let (mut parts, body) = response.into_parts();
74
75        let bytes = match body
76            .read_into_bytes_or_pieces(
77                declared_body_size,
78                caching_configuration.min_body_size,
79                caching_configuration.max_body_size,
80            )
81            .await
82        {
83            Ok((bytes, _trailers)) => bytes,
84            Err(error) => {
85                return Err(ErrorWithResponsePieces::new_from_body(error, parts));
86            }
87        };
88
89        if preferred_encoding != Encoding::Identity {
90            if !parts
91                .headers
92                .xx_encode(encoding_configuration.encodable_by_default)
93            {
94                tracing::debug!(
95                    "not encoding to {} ({}=false)",
96                    preferred_encoding,
97                    XX_ENCODE
98                );
99                preferred_encoding = Encoding::Identity;
100            } else if bytes.len() < encoding_configuration.min_body_size {
101                tracing::debug!("not encoding to {} (too small)", preferred_encoding);
102                preferred_encoding = Encoding::Identity;
103            }
104        }
105
106        let body = CachedBody::new_with(
107            bytes,
108            parts.headers.content_encoding().into(),
109            preferred_encoding,
110            encoding_configuration,
111        )
112        .await
113        // This is not *exactly* a ReadBodyError, but rather an encoding error for the read body
114        .map_err(|error| ErrorWithResponsePieces::from(ReadBodyError::from(error)))?;
115
116        // Extract `XX-Cache-Duration` or call hook
117        let duration = match parts.headers.xx_cache_duration() {
118            Some(duration) => Some(duration),
119            None => caching_configuration
120                .cache_duration
121                .as_ref()
122                .and_then(|duration| duration(CacheDurationHookContext::new(uri, &parts.headers))),
123        };
124
125        if let Some(duration) = duration {
126            tracing::debug!("duration: {}", duration.human_format());
127        }
128
129        // Make sure we have a `Last-Modified`
130        if !parts.headers.contains_key(LAST_MODIFIED) {
131            parts.headers.set_into_header_value(LAST_MODIFIED, now());
132        }
133
134        parts.headers.remove(XX_CACHE);
135        parts.headers.remove(XX_CACHE_DURATION);
136        parts.headers.remove(CONTENT_ENCODING);
137        parts.headers.remove(CONTENT_LENGTH);
138        parts.headers.remove(CONTENT_DIGEST);
139
140        // Note that we are keeping the `XX-Encode` header in the cache
141        // (but will remove it in `to_response`)
142
143        if skip_encoding {
144            parts.headers.set_bool_value(XX_ENCODE, true);
145        }
146
147        // TODO: can we support ranges? if so, we should not remove this header
148        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Ranges
149        parts.headers.remove(ACCEPT_RANGES);
150
151        Ok(Self {
152            parts,
153            body,
154            duration,
155        })
156    }
157
158    /// Clone with new body.
159    pub fn clone_with_body(&self, body: CachedBody) -> Self {
160        Self {
161            parts: self.parts.clone(),
162            body,
163            duration: self.duration.clone(),
164        }
165    }
166
167    /// Headers.
168    pub fn headers(&self) -> &HeaderMap {
169        &self.parts.headers
170    }
171
172    /// Create a [Response].
173    ///
174    /// If we don't have the specified encoding then we will reencode from another encoding,
175    /// storing the result so that we won't have to encode it again.
176    ///
177    /// If an [Identity](Encoding::Identity) is created during this reencoding then it will also be
178    /// stored if `keep_identity_encoding` is true.
179    ///
180    /// If the stored `XX-Encode` header is "false" then will ignore the specified encoding and
181    /// return an [Identity](Encoding::Identity) response.
182    ///
183    /// Returns a modified clone if reencoding caused a new encoding to be stored. Note that
184    /// cloning should be cheap due to our use of [ImmutableBytes] in the body.
185    pub async fn to_response<BodyT>(
186        &self,
187        mut encoding: &Encoding,
188        configuration: &EncodingConfiguration,
189    ) -> io::Result<(Response<BodyT>, Option<Self>)>
190    where
191        BodyT: Body + From<ImmutableBytes>,
192    {
193        if (*encoding != Encoding::Identity)
194            && !self.headers().xx_encode(configuration.encodable_by_default)
195        {
196            tracing::debug!("not encoding to {} ({}=false)", encoding, XX_ENCODE);
197            encoding = &Encoding::Identity;
198        }
199
200        let (bytes, modified) = self.body.get(encoding, configuration).await?;
201
202        let mut parts = self.parts.clone();
203
204        parts.headers.remove(XX_ENCODE);
205
206        if *encoding != Encoding::Identity {
207            // No need to specify Identity as it's the default
208            parts
209                .headers
210                .set_into_header_value(CONTENT_ENCODING, encoding.clone());
211        }
212
213        parts.headers.set_value(CONTENT_LENGTH, bytes.len());
214
215        Ok((
216            Response::from_parts(parts, bytes.into()),
217            modified.map(|body| self.clone_with_body(body)),
218        ))
219    }
220}
221
222impl CacheWeight for CachedResponse {
223    fn cache_weight(&self) -> usize {
224        const SELF_SIZE: usize = size_of::<CachedResponse>();
225        const HEADER_MAP_ENTRY_SIZE: usize = size_of::<HeaderName>() + size_of::<HeaderValue>();
226        const EXTENSION_ENTRY_SIZE: usize = size_of::<TypeId>();
227
228        let mut size = SELF_SIZE;
229
230        let parts = &self.parts;
231        for (name, value) in &parts.headers {
232            size += HEADER_MAP_ENTRY_SIZE + name.as_str().len() + value.len()
233        }
234        size += parts.extensions.len() * EXTENSION_ENTRY_SIZE;
235
236        size += self.body.cache_weight();
237
238        size
239    }
240}