http_cache/
lib.rs

1#![forbid(unsafe_code, future_incompatible)]
2#![deny(
3    missing_docs,
4    missing_debug_implementations,
5    missing_copy_implementations,
6    nonstandard_style,
7    unused_qualifications,
8    unused_import_braces,
9    unused_extern_crates,
10    trivial_casts,
11    trivial_numeric_casts
12)]
13#![allow(clippy::doc_lazy_continuation)]
14#![cfg_attr(docsrs, feature(doc_cfg))]
15//! A caching middleware that follows HTTP caching rules, thanks to
16//! [`http-cache-semantics`](https://github.com/kornelski/rusty-http-cache-semantics).
17//! By default, it uses [`cacache`](https://github.com/zkat/cacache-rs) as the backend cache manager.
18//!
19//! ## Features
20//!
21//! The following features are available. By default `manager-cacache` and `cacache-async-std` are enabled.
22//!
23//! - `manager-cacache` (default): enable [cacache](https://github.com/zkat/cacache-rs),
24//! a high-performance disk cache, backend manager.
25//! - `cacache-async-std` (default): enable [async-std](https://github.com/async-rs/async-std) runtime support for cacache.
26//! - `cacache-tokio` (disabled): enable [tokio](https://github.com/tokio-rs/tokio) runtime support for cacache.
27//! - `manager-moka` (disabled): enable [moka](https://github.com/moka-rs/moka),
28//! a high-performance in-memory cache, backend manager.
29//! - `with-http-types` (disabled): enable [http-types](https://github.com/http-rs/http-types)
30//! type conversion support
31mod error;
32mod managers;
33
34use std::{
35    collections::HashMap,
36    convert::TryFrom,
37    fmt::{self, Debug},
38    str::FromStr,
39    sync::Arc,
40    time::SystemTime,
41};
42
43use http::{header::CACHE_CONTROL, request, response, StatusCode};
44use http_cache_semantics::{AfterResponse, BeforeRequest, CachePolicy};
45use serde::{Deserialize, Serialize};
46use url::Url;
47
48pub use error::{BadHeader, BadVersion, BoxError, Result};
49
50#[cfg(feature = "manager-cacache")]
51pub use managers::cacache::CACacheManager;
52
53#[cfg(feature = "manager-moka")]
54pub use managers::moka::MokaManager;
55
56// Exposing the moka cache for convenience, renaming to avoid naming conflicts
57#[cfg(feature = "manager-moka")]
58#[cfg_attr(docsrs, doc(cfg(feature = "manager-moka")))]
59pub use moka::future::{Cache as MokaCache, CacheBuilder as MokaCacheBuilder};
60
61// Custom headers used to indicate cache status (hit or miss)
62/// `x-cache` header: Value will be HIT if the response was served from cache, MISS if not
63pub const XCACHE: &str = "x-cache";
64/// `x-cache-lookup` header: Value will be HIT if a response existed in cache, MISS if not
65pub const XCACHELOOKUP: &str = "x-cache-lookup";
66
67/// Represents a basic cache status
68/// Used in the custom headers `x-cache` and `x-cache-lookup`
69#[derive(Debug, Copy, Clone)]
70pub enum HitOrMiss {
71    /// Yes, there was a hit
72    HIT,
73    /// No, there was no hit
74    MISS,
75}
76
77impl fmt::Display for HitOrMiss {
78    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79        match self {
80            Self::HIT => write!(f, "HIT"),
81            Self::MISS => write!(f, "MISS"),
82        }
83    }
84}
85
86/// Represents an HTTP version
87#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
88#[non_exhaustive]
89pub enum HttpVersion {
90    /// HTTP Version 0.9
91    #[serde(rename = "HTTP/0.9")]
92    Http09,
93    /// HTTP Version 1.0
94    #[serde(rename = "HTTP/1.0")]
95    Http10,
96    /// HTTP Version 1.1
97    #[serde(rename = "HTTP/1.1")]
98    Http11,
99    /// HTTP Version 2.0
100    #[serde(rename = "HTTP/2.0")]
101    H2,
102    /// HTTP Version 3.0
103    #[serde(rename = "HTTP/3.0")]
104    H3,
105}
106
107impl fmt::Display for HttpVersion {
108    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109        match *self {
110            HttpVersion::Http09 => write!(f, "HTTP/0.9"),
111            HttpVersion::Http10 => write!(f, "HTTP/1.0"),
112            HttpVersion::Http11 => write!(f, "HTTP/1.1"),
113            HttpVersion::H2 => write!(f, "HTTP/2.0"),
114            HttpVersion::H3 => write!(f, "HTTP/3.0"),
115        }
116    }
117}
118
119/// A basic generic type that represents an HTTP response
120#[derive(Debug, Clone, Deserialize, Serialize)]
121pub struct HttpResponse {
122    /// HTTP response body
123    pub body: Vec<u8>,
124    /// HTTP response headers
125    pub headers: HashMap<String, String>,
126    /// HTTP response status code
127    pub status: u16,
128    /// HTTP response url
129    pub url: Url,
130    /// HTTP response version
131    pub version: HttpVersion,
132}
133
134impl HttpResponse {
135    /// Returns `http::response::Parts`
136    pub fn parts(&self) -> Result<response::Parts> {
137        let mut converted =
138            response::Builder::new().status(self.status).body(())?;
139        {
140            let headers = converted.headers_mut();
141            for header in &self.headers {
142                headers.insert(
143                    http::header::HeaderName::from_str(header.0.as_str())?,
144                    http::HeaderValue::from_str(header.1.as_str())?,
145                );
146            }
147        }
148        Ok(converted.into_parts().0)
149    }
150
151    /// Returns the status code of the warning header if present
152    #[must_use]
153    pub fn warning_code(&self) -> Option<usize> {
154        self.headers.get("warning").and_then(|hdr| {
155            hdr.as_str().chars().take(3).collect::<String>().parse().ok()
156        })
157    }
158
159    /// Adds a warning header to a response
160    pub fn add_warning(&mut self, url: &Url, code: usize, message: &str) {
161        // warning    = "warning" ":" 1#warning-value
162        // warning-value = warn-code SP warn-agent SP warn-text [SP warn-date]
163        // warn-code  = 3DIGIT
164        // warn-agent = ( host [ ":" port ] ) | pseudonym
165        //                 ; the name or pseudonym of the server adding
166        //                 ; the warning header, for use in debugging
167        // warn-text  = quoted-string
168        // warn-date  = <"> HTTP-date <">
169        // (https://tools.ietf.org/html/rfc2616#section-14.46)
170        self.headers.insert(
171            "warning".to_string(),
172            format!(
173                "{} {} {:?} \"{}\"",
174                code,
175                url.host().expect("Invalid URL"),
176                message,
177                httpdate::fmt_http_date(SystemTime::now())
178            ),
179        );
180    }
181
182    /// Removes a warning header from a response
183    pub fn remove_warning(&mut self) {
184        self.headers.remove("warning");
185    }
186
187    /// Update the headers from `http::response::Parts`
188    pub fn update_headers(&mut self, parts: &response::Parts) -> Result<()> {
189        for header in parts.headers.iter() {
190            self.headers.insert(
191                header.0.as_str().to_string(),
192                header.1.to_str()?.to_string(),
193            );
194        }
195        Ok(())
196    }
197
198    /// Checks if the Cache-Control header contains the must-revalidate directive
199    #[must_use]
200    pub fn must_revalidate(&self) -> bool {
201        self.headers.get(CACHE_CONTROL.as_str()).is_some_and(|val| {
202            val.as_str().to_lowercase().contains("must-revalidate")
203        })
204    }
205
206    /// Adds the custom `x-cache` header to the response
207    pub fn cache_status(&mut self, hit_or_miss: HitOrMiss) {
208        self.headers.insert(XCACHE.to_string(), hit_or_miss.to_string());
209    }
210
211    /// Adds the custom `x-cache-lookup` header to the response
212    pub fn cache_lookup_status(&mut self, hit_or_miss: HitOrMiss) {
213        self.headers.insert(XCACHELOOKUP.to_string(), hit_or_miss.to_string());
214    }
215}
216
217/// A trait providing methods for storing, reading, and removing cache records.
218#[async_trait::async_trait]
219pub trait CacheManager: Send + Sync + 'static {
220    /// Attempts to pull a cached response and related policy from cache.
221    async fn get(
222        &self,
223        cache_key: &str,
224    ) -> Result<Option<(HttpResponse, CachePolicy)>>;
225    /// Attempts to cache a response and related policy.
226    async fn put(
227        &self,
228        cache_key: String,
229        res: HttpResponse,
230        policy: CachePolicy,
231    ) -> Result<HttpResponse>;
232    /// Attempts to remove a record from cache.
233    async fn delete(&self, cache_key: &str) -> Result<()>;
234}
235
236/// Describes the functionality required for interfacing with HTTP client middleware
237#[async_trait::async_trait]
238pub trait Middleware: Send {
239    /// Allows the cache mode to be overridden.
240    ///
241    /// This overrides any cache mode set in the configuration, including cache_mode_fn.
242    fn overridden_cache_mode(&self) -> Option<CacheMode> {
243        None
244    }
245    /// Determines if the request method is either GET or HEAD
246    fn is_method_get_head(&self) -> bool;
247    /// Returns a new cache policy with default options
248    fn policy(&self, response: &HttpResponse) -> Result<CachePolicy>;
249    /// Returns a new cache policy with custom options
250    fn policy_with_options(
251        &self,
252        response: &HttpResponse,
253        options: CacheOptions,
254    ) -> Result<CachePolicy>;
255    /// Attempts to update the request headers with the passed `http::request::Parts`
256    fn update_headers(&mut self, parts: &request::Parts) -> Result<()>;
257    /// Attempts to force the "no-cache" directive on the request
258    fn force_no_cache(&mut self) -> Result<()>;
259    /// Attempts to construct `http::request::Parts` from the request
260    fn parts(&self) -> Result<request::Parts>;
261    /// Attempts to determine the requested url
262    fn url(&self) -> Result<Url>;
263    /// Attempts to determine the request method
264    fn method(&self) -> Result<String>;
265    /// Attempts to fetch an upstream resource and return an [`HttpResponse`]
266    async fn remote_fetch(&mut self) -> Result<HttpResponse>;
267}
268
269/// Similar to [make-fetch-happen cache options](https://github.com/npm/make-fetch-happen#--optscache).
270/// Passed in when the [`HttpCache`] struct is being built.
271#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
272pub enum CacheMode {
273    /// Will inspect the HTTP cache on the way to the network.
274    /// If there is a fresh response it will be used.
275    /// If there is a stale response a conditional request will be created,
276    /// and a normal request otherwise.
277    /// It then updates the HTTP cache with the response.
278    /// If the revalidation request fails (for example, on a 500 or if you're offline),
279    /// the stale response will be returned.
280    #[default]
281    Default,
282    /// Behaves as if there is no HTTP cache at all.
283    NoStore,
284    /// Behaves as if there is no HTTP cache on the way to the network.
285    /// Ergo, it creates a normal request and updates the HTTP cache with the response.
286    Reload,
287    /// Creates a conditional request if there is a response in the HTTP cache
288    /// and a normal request otherwise. It then updates the HTTP cache with the response.
289    NoCache,
290    /// Uses any response in the HTTP cache matching the request,
291    /// not paying attention to staleness. If there was no response,
292    /// it creates a normal request and updates the HTTP cache with the response.
293    ForceCache,
294    /// Uses any response in the HTTP cache matching the request,
295    /// not paying attention to staleness. If there was no response,
296    /// it returns a network error.
297    OnlyIfCached,
298    /// Overrides the check that determines if a response can be cached to always return true on 200.
299    /// Uses any response in the HTTP cache matching the request,
300    /// not paying attention to staleness. If there was no response,
301    /// it creates a normal request and updates the HTTP cache with the response.
302    IgnoreRules,
303}
304
305impl TryFrom<http::Version> for HttpVersion {
306    type Error = BoxError;
307
308    fn try_from(value: http::Version) -> Result<Self> {
309        Ok(match value {
310            http::Version::HTTP_09 => Self::Http09,
311            http::Version::HTTP_10 => Self::Http10,
312            http::Version::HTTP_11 => Self::Http11,
313            http::Version::HTTP_2 => Self::H2,
314            http::Version::HTTP_3 => Self::H3,
315            _ => return Err(Box::new(BadVersion)),
316        })
317    }
318}
319
320impl From<HttpVersion> for http::Version {
321    fn from(value: HttpVersion) -> Self {
322        match value {
323            HttpVersion::Http09 => Self::HTTP_09,
324            HttpVersion::Http10 => Self::HTTP_10,
325            HttpVersion::Http11 => Self::HTTP_11,
326            HttpVersion::H2 => Self::HTTP_2,
327            HttpVersion::H3 => Self::HTTP_3,
328        }
329    }
330}
331
332#[cfg(feature = "http-types")]
333impl TryFrom<http_types::Version> for HttpVersion {
334    type Error = BoxError;
335
336    fn try_from(value: http_types::Version) -> Result<Self> {
337        Ok(match value {
338            http_types::Version::Http0_9 => Self::Http09,
339            http_types::Version::Http1_0 => Self::Http10,
340            http_types::Version::Http1_1 => Self::Http11,
341            http_types::Version::Http2_0 => Self::H2,
342            http_types::Version::Http3_0 => Self::H3,
343            _ => return Err(Box::new(BadVersion)),
344        })
345    }
346}
347
348#[cfg(feature = "http-types")]
349impl From<HttpVersion> for http_types::Version {
350    fn from(value: HttpVersion) -> Self {
351        match value {
352            HttpVersion::Http09 => Self::Http0_9,
353            HttpVersion::Http10 => Self::Http1_0,
354            HttpVersion::Http11 => Self::Http1_1,
355            HttpVersion::H2 => Self::Http2_0,
356            HttpVersion::H3 => Self::Http3_0,
357        }
358    }
359}
360
361/// Options struct provided by
362/// [`http-cache-semantics`](https://github.com/kornelski/rusty-http-cache-semantics).
363pub use http_cache_semantics::CacheOptions;
364
365/// A closure that takes [`http::request::Parts`] and returns a [`String`].
366/// By default, the cache key is a combination of the request method and uri with a colon in between.
367pub type CacheKey = Arc<dyn Fn(&request::Parts) -> String + Send + Sync>;
368
369/// A closure that takes [`http::request::Parts`] and returns a [`CacheMode`]
370pub type CacheModeFn = Arc<dyn Fn(&request::Parts) -> CacheMode + Send + Sync>;
371
372/// A closure that takes [`http::request::Parts`], [`Option<CacheKey>`], the default cache key ([`&str``]) and returns [`Vec<String>`] of keys to bust the cache for.
373/// An empty vector means that no cache busting will be performed.
374pub type CacheBust = Arc<
375    dyn Fn(&request::Parts, &Option<CacheKey>, &str) -> Vec<String>
376        + Send
377        + Sync,
378>;
379
380/// Can be used to override the default [`CacheOptions`] and cache key.
381/// The cache key is a closure that takes [`http::request::Parts`] and returns a [`String`].
382#[derive(Clone)]
383pub struct HttpCacheOptions {
384    /// Override the default cache options.
385    pub cache_options: Option<CacheOptions>,
386    /// Override the default cache key generator.
387    pub cache_key: Option<CacheKey>,
388    /// Override the default cache mode.
389    pub cache_mode_fn: Option<CacheModeFn>,
390    /// Bust the caches of the returned keys.
391    pub cache_bust: Option<CacheBust>,
392    /// Determines if the cache status headers should be added to the response.
393    pub cache_status_headers: bool,
394}
395
396impl Default for HttpCacheOptions {
397    fn default() -> Self {
398        Self {
399            cache_options: None,
400            cache_key: None,
401            cache_mode_fn: None,
402            cache_bust: None,
403            cache_status_headers: true,
404        }
405    }
406}
407
408impl Debug for HttpCacheOptions {
409    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410        f.debug_struct("HttpCacheOptions")
411            .field("cache_options", &self.cache_options)
412            .field("cache_key", &"Fn(&request::Parts) -> String")
413            .field("cache_mode_fn", &"Fn(&request::Parts) -> CacheMode")
414            .field("cache_bust", &"Fn(&request::Parts) -> Vec<String>")
415            .field("cache_status_headers", &self.cache_status_headers)
416            .finish()
417    }
418}
419
420impl HttpCacheOptions {
421    fn create_cache_key(
422        &self,
423        parts: &request::Parts,
424        override_method: Option<&str>,
425    ) -> String {
426        if let Some(cache_key) = &self.cache_key {
427            cache_key(parts)
428        } else {
429            format!(
430                "{}:{}",
431                override_method.unwrap_or_else(|| parts.method.as_str()),
432                parts.uri
433            )
434        }
435    }
436}
437
438/// Caches requests according to http spec.
439#[derive(Debug, Clone)]
440pub struct HttpCache<T: CacheManager> {
441    /// Determines the manager behavior.
442    pub mode: CacheMode,
443    /// Manager instance that implements the [`CacheManager`] trait.
444    /// By default, a manager implementation with [`cacache`](https://github.com/zkat/cacache-rs)
445    /// as the backend has been provided, see [`CACacheManager`].
446    pub manager: T,
447    /// Override the default cache options.
448    pub options: HttpCacheOptions,
449}
450
451#[allow(dead_code)]
452impl<T: CacheManager> HttpCache<T> {
453    /// Determines if the request should be cached
454    pub fn can_cache_request(
455        &self,
456        middleware: &impl Middleware,
457    ) -> Result<bool> {
458        let mode = self.cache_mode(middleware)?;
459
460        Ok(mode == CacheMode::IgnoreRules
461            || middleware.is_method_get_head() && mode != CacheMode::NoStore)
462    }
463
464    /// Runs the actions to preform when the client middleware is running without the cache
465    pub async fn run_no_cache(
466        &self,
467        middleware: &mut impl Middleware,
468    ) -> Result<()> {
469        self.manager
470            .delete(
471                &self
472                    .options
473                    .create_cache_key(&middleware.parts()?, Some("GET")),
474            )
475            .await
476            .ok();
477
478        let cache_key =
479            self.options.create_cache_key(&middleware.parts()?, None);
480
481        if let Some(cache_bust) = &self.options.cache_bust {
482            for key_to_cache_bust in cache_bust(
483                &middleware.parts()?,
484                &self.options.cache_key,
485                &cache_key,
486            ) {
487                self.manager.delete(&key_to_cache_bust).await?;
488            }
489        }
490
491        Ok(())
492    }
493
494    /// Attempts to run the passed middleware along with the cache
495    pub async fn run(
496        &self,
497        mut middleware: impl Middleware,
498    ) -> Result<HttpResponse> {
499        let is_cacheable = self.can_cache_request(&middleware)?;
500        if !is_cacheable {
501            return self.remote_fetch(&mut middleware).await;
502        }
503
504        let cache_key =
505            self.options.create_cache_key(&middleware.parts()?, None);
506
507        if let Some(cache_bust) = &self.options.cache_bust {
508            for key_to_cache_bust in cache_bust(
509                &middleware.parts()?,
510                &self.options.cache_key,
511                &cache_key,
512            ) {
513                self.manager.delete(&key_to_cache_bust).await?;
514            }
515        }
516
517        if let Some(store) = self.manager.get(&cache_key).await? {
518            let (mut res, policy) = store;
519            if self.options.cache_status_headers {
520                res.cache_lookup_status(HitOrMiss::HIT);
521            }
522            if let Some(warning_code) = res.warning_code() {
523                // https://tools.ietf.org/html/rfc7234#section-4.3.4
524                //
525                // If a stored response is selected for update, the cache MUST:
526                //
527                // * delete any warning header fields in the stored response with
528                //   warn-code 1xx (see Section 5.5);
529                //
530                // * retain any warning header fields in the stored response with
531                //   warn-code 2xx;
532                //
533                if (100..200).contains(&warning_code) {
534                    res.remove_warning();
535                }
536            }
537
538            match self.cache_mode(&middleware)? {
539                CacheMode::Default => {
540                    self.conditional_fetch(middleware, res, policy).await
541                }
542                CacheMode::NoCache => {
543                    middleware.force_no_cache()?;
544                    let mut res = self.remote_fetch(&mut middleware).await?;
545                    if self.options.cache_status_headers {
546                        res.cache_lookup_status(HitOrMiss::HIT);
547                    }
548                    Ok(res)
549                }
550                CacheMode::ForceCache
551                | CacheMode::OnlyIfCached
552                | CacheMode::IgnoreRules => {
553                    //   112 Disconnected operation
554                    // SHOULD be included if the cache is intentionally disconnected from
555                    // the rest of the network for a period of time.
556                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
557                    res.add_warning(
558                        &res.url.clone(),
559                        112,
560                        "Disconnected operation",
561                    );
562                    if self.options.cache_status_headers {
563                        res.cache_status(HitOrMiss::HIT);
564                    }
565                    Ok(res)
566                }
567                _ => self.remote_fetch(&mut middleware).await,
568            }
569        } else {
570            match self.cache_mode(&middleware)? {
571                CacheMode::OnlyIfCached => {
572                    // ENOTCACHED
573                    let mut res = HttpResponse {
574                        body: b"GatewayTimeout".to_vec(),
575                        headers: HashMap::default(),
576                        status: 504,
577                        url: middleware.url()?,
578                        version: HttpVersion::Http11,
579                    };
580                    if self.options.cache_status_headers {
581                        res.cache_status(HitOrMiss::MISS);
582                        res.cache_lookup_status(HitOrMiss::MISS);
583                    }
584                    Ok(res)
585                }
586                _ => self.remote_fetch(&mut middleware).await,
587            }
588        }
589    }
590
591    fn cache_mode(&self, middleware: &impl Middleware) -> Result<CacheMode> {
592        Ok(if let Some(mode) = middleware.overridden_cache_mode() {
593            mode
594        } else if let Some(cache_mode_fn) = &self.options.cache_mode_fn {
595            cache_mode_fn(&middleware.parts()?)
596        } else {
597            self.mode
598        })
599    }
600
601    async fn remote_fetch(
602        &self,
603        middleware: &mut impl Middleware,
604    ) -> Result<HttpResponse> {
605        let mut res = middleware.remote_fetch().await?;
606        if self.options.cache_status_headers {
607            res.cache_status(HitOrMiss::MISS);
608            res.cache_lookup_status(HitOrMiss::MISS);
609        }
610        let policy = match self.options.cache_options {
611            Some(options) => middleware.policy_with_options(&res, options)?,
612            None => middleware.policy(&res)?,
613        };
614        let is_get_head = middleware.is_method_get_head();
615        let mode = self.cache_mode(middleware)?;
616        let mut is_cacheable = is_get_head
617            && mode != CacheMode::NoStore
618            && res.status == 200
619            && policy.is_storable();
620        if mode == CacheMode::IgnoreRules && res.status == 200 {
621            is_cacheable = true;
622        }
623        if is_cacheable {
624            Ok(self
625                .manager
626                .put(
627                    self.options.create_cache_key(&middleware.parts()?, None),
628                    res,
629                    policy,
630                )
631                .await?)
632        } else if !is_get_head {
633            self.manager
634                .delete(
635                    &self
636                        .options
637                        .create_cache_key(&middleware.parts()?, Some("GET")),
638                )
639                .await
640                .ok();
641            Ok(res)
642        } else {
643            Ok(res)
644        }
645    }
646
647    async fn conditional_fetch(
648        &self,
649        mut middleware: impl Middleware,
650        mut cached_res: HttpResponse,
651        mut policy: CachePolicy,
652    ) -> Result<HttpResponse> {
653        let before_req =
654            policy.before_request(&middleware.parts()?, SystemTime::now());
655        match before_req {
656            BeforeRequest::Fresh(parts) => {
657                cached_res.update_headers(&parts)?;
658                if self.options.cache_status_headers {
659                    cached_res.cache_status(HitOrMiss::HIT);
660                    cached_res.cache_lookup_status(HitOrMiss::HIT);
661                }
662                return Ok(cached_res);
663            }
664            BeforeRequest::Stale { request: parts, matches } => {
665                if matches {
666                    middleware.update_headers(&parts)?;
667                }
668            }
669        }
670        let req_url = middleware.url()?;
671        match middleware.remote_fetch().await {
672            Ok(mut cond_res) => {
673                let status = StatusCode::from_u16(cond_res.status)?;
674                if status.is_server_error() && cached_res.must_revalidate() {
675                    //   111 Revalidation failed
676                    //   MUST be included if a cache returns a stale response
677                    //   because an attempt to revalidate the response failed,
678                    //   due to an inability to reach the server.
679                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
680                    cached_res.add_warning(
681                        &req_url,
682                        111,
683                        "Revalidation failed",
684                    );
685                    if self.options.cache_status_headers {
686                        cached_res.cache_status(HitOrMiss::HIT);
687                    }
688                    Ok(cached_res)
689                } else if cond_res.status == 304 {
690                    let after_res = policy.after_response(
691                        &middleware.parts()?,
692                        &cond_res.parts()?,
693                        SystemTime::now(),
694                    );
695                    match after_res {
696                        AfterResponse::Modified(new_policy, parts)
697                        | AfterResponse::NotModified(new_policy, parts) => {
698                            policy = new_policy;
699                            cached_res.update_headers(&parts)?;
700                        }
701                    }
702                    if self.options.cache_status_headers {
703                        cached_res.cache_status(HitOrMiss::HIT);
704                        cached_res.cache_lookup_status(HitOrMiss::HIT);
705                    }
706                    let res = self
707                        .manager
708                        .put(
709                            self.options
710                                .create_cache_key(&middleware.parts()?, None),
711                            cached_res,
712                            policy,
713                        )
714                        .await?;
715                    Ok(res)
716                } else if cond_res.status == 200 {
717                    let policy = match self.options.cache_options {
718                        Some(options) => middleware
719                            .policy_with_options(&cond_res, options)?,
720                        None => middleware.policy(&cond_res)?,
721                    };
722                    if self.options.cache_status_headers {
723                        cond_res.cache_status(HitOrMiss::MISS);
724                        cond_res.cache_lookup_status(HitOrMiss::HIT);
725                    }
726                    let res = self
727                        .manager
728                        .put(
729                            self.options
730                                .create_cache_key(&middleware.parts()?, None),
731                            cond_res,
732                            policy,
733                        )
734                        .await?;
735                    Ok(res)
736                } else {
737                    if self.options.cache_status_headers {
738                        cached_res.cache_status(HitOrMiss::HIT);
739                    }
740                    Ok(cached_res)
741                }
742            }
743            Err(e) => {
744                if cached_res.must_revalidate() {
745                    Err(e)
746                } else {
747                    //   111 Revalidation failed
748                    //   MUST be included if a cache returns a stale response
749                    //   because an attempt to revalidate the response failed,
750                    //   due to an inability to reach the server.
751                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
752                    cached_res.add_warning(
753                        &req_url,
754                        111,
755                        "Revalidation failed",
756                    );
757                    if self.options.cache_status_headers {
758                        cached_res.cache_status(HitOrMiss::HIT);
759                    }
760                    Ok(cached_res)
761                }
762            }
763        }
764    }
765}
766
767#[cfg(test)]
768mod test;