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//! This crate provides the core HTTP caching functionality that can be used to build
20//! caching middleware for various HTTP clients and server frameworks. It implements
21//! RFC 7234 HTTP caching semantics, supporting features like:
22//!
23//! - Automatic cache invalidation for unsafe HTTP methods (PUT, POST, DELETE, PATCH)
24//! - Respect for HTTP cache-control headers
25//! - Conditional requests (ETag, Last-Modified)
26//! - Multiple cache storage backends
27//! - Streaming response support
28//!
29//! ## Basic Usage
30//!
31//! The core types for building HTTP caches:
32//!
33//! ```rust
34//! use http_cache::{CACacheManager, HttpCache, CacheMode, HttpCacheOptions};
35//!
36//! // Create a cache manager with disk storage
37//! let manager = CACacheManager::new("./cache".into(), true);
38//!
39//! // Create an HTTP cache with default behavior
40//! let cache = HttpCache {
41//!     mode: CacheMode::Default,
42//!     manager,
43//!     options: HttpCacheOptions::default(),
44//! };
45//! ```
46//!
47//! ## Cache Modes
48//!
49//! Different cache modes provide different behaviors:
50//!
51//! ```rust
52//! use http_cache::{CacheMode, HttpCache, CACacheManager, HttpCacheOptions};
53//!
54//! let manager = CACacheManager::new("./cache".into(), true);
55//!
56//! // Default mode: follows HTTP caching rules
57//! let default_cache = HttpCache {
58//!     mode: CacheMode::Default,
59//!     manager: manager.clone(),
60//!     options: HttpCacheOptions::default(),
61//! };
62//!
63//! // NoStore mode: never caches responses
64//! let no_store_cache = HttpCache {
65//!     mode: CacheMode::NoStore,
66//!     manager: manager.clone(),
67//!     options: HttpCacheOptions::default(),
68//! };
69//!
70//! // ForceCache mode: caches responses even if headers suggest otherwise
71//! let force_cache = HttpCache {
72//!     mode: CacheMode::ForceCache,
73//!     manager,
74//!     options: HttpCacheOptions::default(),
75//! };
76//! ```
77//!
78//! ## Custom Cache Keys
79//!
80//! You can customize how cache keys are generated:
81//!
82//! ```rust
83//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
84//! use std::sync::Arc;
85//! use http::request::Parts;
86//!
87//! let manager = CACacheManager::new("./cache".into(), true);
88//!
89//! let options = HttpCacheOptions {
90//!     cache_key: Some(Arc::new(|req: &Parts| {
91//!         // Custom cache key that includes query parameters
92//!         format!("{}:{}", req.method, req.uri)
93//!     })),
94//!     ..Default::default()
95//! };
96//!
97//! let cache = HttpCache {
98//!     mode: CacheMode::Default,
99//!     manager,
100//!     options,
101//! };
102//! ```
103//!
104//! ## Response-Based Cache Mode Override
105//!
106//! Override cache behavior based on the response you receive. This is useful for scenarios like
107//! forcing cache for successful responses even when headers say not to cache, or never caching
108//! error responses like rate limits:
109//!
110//! ```rust
111//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
112//! use std::sync::Arc;
113//!
114//! let manager = CACacheManager::new("./cache".into(), true);
115//!
116//! let options = HttpCacheOptions {
117//!     response_cache_mode_fn: Some(Arc::new(|_request_parts, response| {
118//!         match response.status {
119//!             // Force cache successful responses even if headers say not to cache
120//!             200..=299 => Some(CacheMode::ForceCache),
121//!             // Never cache rate-limited responses  
122//!             429 => Some(CacheMode::NoStore),
123//!             // Use default behavior for everything else
124//!             _ => None,
125//!         }
126//!     })),
127//!     ..Default::default()
128//! };
129//!
130//! let cache = HttpCache {
131//!     mode: CacheMode::Default,
132//!     manager,
133//!     options,
134//! };
135//! ```
136//!
137//! ## Streaming Support
138//!
139//! For handling large responses without full buffering, use the `StreamingManager`:
140//!
141//! ```rust
142//! # #[cfg(feature = "streaming")]
143//! # {
144//! use http_cache::{StreamingBody, HttpStreamingCache, StreamingManager};
145//! use bytes::Bytes;
146//! use std::path::PathBuf;
147//! use http_body::Body;
148//! use http_body_util::Full;
149//!
150//! // Create a file-based streaming cache manager
151//! let manager = StreamingManager::new(PathBuf::from("./streaming-cache"));
152//!
153//! // StreamingBody can handle both buffered and streaming scenarios
154//! let body: StreamingBody<Full<Bytes>> = StreamingBody::buffered(Bytes::from("cached content"));
155//! println!("Body size: {:?}", body.size_hint());
156//! # }
157//! ```
158//!
159//! **Note**: Streaming support requires the `StreamingManager` with the `streaming` feature.
160//! Other cache managers (CACacheManager, MokaManager, QuickManager) do not support streaming
161//! and will buffer response bodies in memory.
162//!
163//! ## Features
164//!
165//! The following features are available. By default `manager-cacache` and `cacache-smol` are enabled.
166//!
167//! - `manager-cacache` (default): enable [cacache](https://github.com/zkat/cacache-rs),
168//! a disk cache, backend manager.
169//! - `cacache-smol` (default): enable [smol](https://github.com/smol-rs/smol) runtime support for cacache.
170//! - `cacache-tokio` (disabled): enable [tokio](https://github.com/tokio-rs/tokio) runtime support for cacache.
171//! - `manager-moka` (disabled): enable [moka](https://github.com/moka-rs/moka),
172//! an in-memory cache, backend manager.
173//! - `streaming` (disabled): enable the `StreamingManager` for streaming cache support.
174//! - `streaming-tokio` (disabled): enable streaming with tokio runtime support.
175//! - `streaming-smol` (disabled): enable streaming with smol runtime support.
176//! - `with-http-types` (disabled): enable [http-types](https://github.com/http-rs/http-types)
177//! type conversion support
178//!
179//! **Note**: Only `StreamingManager` (via the `streaming` feature) provides streaming support.
180//! Other managers will buffer response bodies in memory even when used with `StreamingManager`.
181//!
182//! ## Integration
183//!
184//! This crate is designed to be used as a foundation for HTTP client and server middleware.
185//! See the companion crates for specific integrations:
186//!
187//! - [`http-cache-reqwest`](https://docs.rs/http-cache-reqwest) for reqwest client middleware
188//! - [`http-cache-surf`](https://docs.rs/http-cache-surf) for surf client middleware  
189//! - [`http-cache-tower`](https://docs.rs/http-cache-tower) for tower service middleware
190mod body;
191mod error;
192mod managers;
193
194#[cfg(feature = "streaming")]
195mod runtime;
196
197use std::{
198    collections::HashMap,
199    convert::TryFrom,
200    fmt::{self, Debug},
201    str::FromStr,
202    sync::Arc,
203    time::SystemTime,
204};
205
206use http::{header::CACHE_CONTROL, request, response, Response, StatusCode};
207use http_cache_semantics::{AfterResponse, BeforeRequest, CachePolicy};
208use serde::{Deserialize, Serialize};
209use url::Url;
210
211pub use body::StreamingBody;
212pub use error::{BadHeader, BadVersion, BoxError, Result, StreamingError};
213
214#[cfg(feature = "manager-cacache")]
215pub use managers::cacache::CACacheManager;
216
217#[cfg(feature = "streaming")]
218pub use managers::streaming_cache::StreamingManager;
219
220#[cfg(feature = "manager-moka")]
221pub use managers::moka::MokaManager;
222
223// Exposing the moka cache for convenience, renaming to avoid naming conflicts
224#[cfg(feature = "manager-moka")]
225#[cfg_attr(docsrs, doc(cfg(feature = "manager-moka")))]
226pub use moka::future::{Cache as MokaCache, CacheBuilder as MokaCacheBuilder};
227
228// Custom headers used to indicate cache status (hit or miss)
229/// `x-cache` header: Value will be HIT if the response was served from cache, MISS if not
230pub const XCACHE: &str = "x-cache";
231/// `x-cache-lookup` header: Value will be HIT if a response existed in cache, MISS if not
232pub const XCACHELOOKUP: &str = "x-cache-lookup";
233/// `warning` header: HTTP warning header as per RFC 7234
234const WARNING: &str = "warning";
235
236/// Represents a basic cache status
237/// Used in the custom headers `x-cache` and `x-cache-lookup`
238#[derive(Debug, Copy, Clone)]
239pub enum HitOrMiss {
240    /// Yes, there was a hit
241    HIT,
242    /// No, there was no hit
243    MISS,
244}
245
246impl fmt::Display for HitOrMiss {
247    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
248        match self {
249            Self::HIT => write!(f, "HIT"),
250            Self::MISS => write!(f, "MISS"),
251        }
252    }
253}
254
255/// Represents an HTTP version
256#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
257#[non_exhaustive]
258pub enum HttpVersion {
259    /// HTTP Version 0.9
260    #[serde(rename = "HTTP/0.9")]
261    Http09,
262    /// HTTP Version 1.0
263    #[serde(rename = "HTTP/1.0")]
264    Http10,
265    /// HTTP Version 1.1
266    #[serde(rename = "HTTP/1.1")]
267    Http11,
268    /// HTTP Version 2.0
269    #[serde(rename = "HTTP/2.0")]
270    H2,
271    /// HTTP Version 3.0
272    #[serde(rename = "HTTP/3.0")]
273    H3,
274}
275
276impl fmt::Display for HttpVersion {
277    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
278        match *self {
279            HttpVersion::Http09 => write!(f, "HTTP/0.9"),
280            HttpVersion::Http10 => write!(f, "HTTP/1.0"),
281            HttpVersion::Http11 => write!(f, "HTTP/1.1"),
282            HttpVersion::H2 => write!(f, "HTTP/2.0"),
283            HttpVersion::H3 => write!(f, "HTTP/3.0"),
284        }
285    }
286}
287
288/// Extract a URL from HTTP request parts for cache key generation
289///
290/// This function reconstructs the full URL from the request parts, handling both
291/// HTTP and HTTPS schemes based on the connection type or explicit headers.
292fn extract_url_from_request_parts(parts: &request::Parts) -> Result<Url> {
293    // First check if the URI is already absolute
294    if let Some(_scheme) = parts.uri.scheme() {
295        // URI is absolute, use it directly
296        return Url::parse(&parts.uri.to_string())
297            .map_err(|_| BadHeader.into());
298    }
299
300    // Get the scheme - default to https for security, but check for explicit http
301    let scheme = if let Some(host) = parts.headers.get("host") {
302        let host_str = host.to_str().map_err(|_| BadHeader)?;
303        // Check if this looks like a local development host
304        if host_str.starts_with("localhost")
305            || host_str.starts_with("127.0.0.1")
306        {
307            "http"
308        } else if let Some(forwarded_proto) =
309            parts.headers.get("x-forwarded-proto")
310        {
311            forwarded_proto.to_str().map_err(|_| BadHeader)?
312        } else {
313            "https" // Default to secure
314        }
315    } else {
316        "https" // Default to secure if no host header
317    };
318
319    // Get the host
320    let host = parts
321        .headers
322        .get("host")
323        .ok_or(BadHeader)?
324        .to_str()
325        .map_err(|_| BadHeader)?;
326
327    // Construct the full URL
328    let url_string = format!(
329        "{}://{}{}",
330        scheme,
331        host,
332        parts.uri.path_and_query().map(|pq| pq.as_str()).unwrap_or("/")
333    );
334
335    Url::parse(&url_string).map_err(|_| BadHeader.into())
336}
337
338/// A basic generic type that represents an HTTP response
339#[derive(Debug, Clone, Deserialize, Serialize)]
340pub struct HttpResponse {
341    /// HTTP response body
342    pub body: Vec<u8>,
343    /// HTTP response headers
344    pub headers: HashMap<String, String>,
345    /// HTTP response status code
346    pub status: u16,
347    /// HTTP response url
348    pub url: Url,
349    /// HTTP response version
350    pub version: HttpVersion,
351}
352
353impl HttpResponse {
354    /// Returns `http::response::Parts`
355    pub fn parts(&self) -> Result<response::Parts> {
356        let mut converted =
357            response::Builder::new().status(self.status).body(())?;
358        {
359            let headers = converted.headers_mut();
360            for header in &self.headers {
361                headers.insert(
362                    http::header::HeaderName::from_str(header.0.as_str())?,
363                    http::HeaderValue::from_str(header.1.as_str())?,
364                );
365            }
366        }
367        Ok(converted.into_parts().0)
368    }
369
370    /// Returns the status code of the warning header if present
371    #[must_use]
372    fn warning_code(&self) -> Option<usize> {
373        self.headers.get(WARNING).and_then(|hdr| {
374            hdr.as_str().chars().take(3).collect::<String>().parse().ok()
375        })
376    }
377
378    /// Adds a warning header to a response
379    fn add_warning(&mut self, url: &Url, code: usize, message: &str) {
380        // warning    = "warning" ":" 1#warning-value
381        // warning-value = warn-code SP warn-agent SP warn-text [SP warn-date]
382        // warn-code  = 3DIGIT
383        // warn-agent = ( host [ ":" port ] ) | pseudonym
384        //                 ; the name or pseudonym of the server adding
385        //                 ; the warning header, for use in debugging
386        // warn-text  = quoted-string
387        // warn-date  = <"> HTTP-date <">
388        // (https://tools.ietf.org/html/rfc2616#section-14.46)
389        self.headers.insert(
390            WARNING.to_string(),
391            format!(
392                "{} {} {:?} \"{}\"",
393                code,
394                url.host().expect("Invalid URL"),
395                message,
396                httpdate::fmt_http_date(SystemTime::now())
397            ),
398        );
399    }
400
401    /// Removes a warning header from a response
402    fn remove_warning(&mut self) {
403        self.headers.remove(WARNING);
404    }
405
406    /// Update the headers from `http::response::Parts`
407    pub fn update_headers(&mut self, parts: &response::Parts) -> Result<()> {
408        for header in parts.headers.iter() {
409            self.headers.insert(
410                header.0.as_str().to_string(),
411                header.1.to_str()?.to_string(),
412            );
413        }
414        Ok(())
415    }
416
417    /// Checks if the Cache-Control header contains the must-revalidate directive
418    #[must_use]
419    fn must_revalidate(&self) -> bool {
420        self.headers.get(CACHE_CONTROL.as_str()).is_some_and(|val| {
421            val.as_str().to_lowercase().contains("must-revalidate")
422        })
423    }
424
425    /// Adds the custom `x-cache` header to the response
426    pub fn cache_status(&mut self, hit_or_miss: HitOrMiss) {
427        self.headers.insert(XCACHE.to_string(), hit_or_miss.to_string());
428    }
429
430    /// Adds the custom `x-cache-lookup` header to the response
431    pub fn cache_lookup_status(&mut self, hit_or_miss: HitOrMiss) {
432        self.headers.insert(XCACHELOOKUP.to_string(), hit_or_miss.to_string());
433    }
434}
435
436/// A trait providing methods for storing, reading, and removing cache records.
437#[async_trait::async_trait]
438pub trait CacheManager: Send + Sync + 'static {
439    /// Attempts to pull a cached response and related policy from cache.
440    async fn get(
441        &self,
442        cache_key: &str,
443    ) -> Result<Option<(HttpResponse, CachePolicy)>>;
444    /// Attempts to cache a response and related policy.
445    async fn put(
446        &self,
447        cache_key: String,
448        res: HttpResponse,
449        policy: CachePolicy,
450    ) -> Result<HttpResponse>;
451    /// Attempts to remove a record from cache.
452    async fn delete(&self, cache_key: &str) -> Result<()>;
453}
454
455/// A streaming cache manager that supports streaming request/response bodies
456/// without buffering them in memory. This is ideal for large responses.
457#[async_trait::async_trait]
458pub trait StreamingCacheManager: Send + Sync + 'static {
459    /// The body type used by this cache manager
460    type Body: http_body::Body + Send + 'static;
461
462    /// Attempts to pull a cached response and related policy from cache with streaming body.
463    async fn get(
464        &self,
465        cache_key: &str,
466    ) -> Result<Option<(Response<Self::Body>, CachePolicy)>>
467    where
468        <Self::Body as http_body::Body>::Data: Send,
469        <Self::Body as http_body::Body>::Error:
470            Into<StreamingError> + Send + Sync + 'static;
471
472    /// Attempts to cache a response with a streaming body and related policy.
473    async fn put<B>(
474        &self,
475        cache_key: String,
476        response: Response<B>,
477        policy: CachePolicy,
478        request_url: Url,
479    ) -> Result<Response<Self::Body>>
480    where
481        B: http_body::Body + Send + 'static,
482        B::Data: Send,
483        B::Error: Into<StreamingError>,
484        <Self::Body as http_body::Body>::Data: Send,
485        <Self::Body as http_body::Body>::Error:
486            Into<StreamingError> + Send + Sync + 'static;
487
488    /// Converts a generic body to the manager's body type for non-cacheable responses.
489    /// This is called when a response should not be cached but still needs to be returned
490    /// with the correct body type.
491    async fn convert_body<B>(
492        &self,
493        response: Response<B>,
494    ) -> Result<Response<Self::Body>>
495    where
496        B: http_body::Body + Send + 'static,
497        B::Data: Send,
498        B::Error: Into<StreamingError>,
499        <Self::Body as http_body::Body>::Data: Send,
500        <Self::Body as http_body::Body>::Error:
501            Into<StreamingError> + Send + Sync + 'static;
502
503    /// Attempts to remove a record from cache.
504    async fn delete(&self, cache_key: &str) -> Result<()>;
505
506    /// Convert the manager's body type to a reqwest-compatible bytes stream.
507    /// This enables efficient streaming without collecting the entire body.
508    #[cfg(feature = "streaming")]
509    fn body_to_bytes_stream(
510        body: Self::Body,
511    ) -> impl futures_util::Stream<
512        Item = std::result::Result<
513            bytes::Bytes,
514            Box<dyn std::error::Error + Send + Sync>,
515        >,
516    > + Send
517    where
518        <Self::Body as http_body::Body>::Data: Send,
519        <Self::Body as http_body::Body>::Error: Send + Sync + 'static;
520}
521
522/// Describes the functionality required for interfacing with HTTP client middleware
523#[async_trait::async_trait]
524pub trait Middleware: Send {
525    /// Allows the cache mode to be overridden.
526    ///
527    /// This overrides any cache mode set in the configuration, including cache_mode_fn.
528    fn overridden_cache_mode(&self) -> Option<CacheMode> {
529        None
530    }
531    /// Determines if the request method is either GET or HEAD
532    fn is_method_get_head(&self) -> bool;
533    /// Returns a new cache policy with default options
534    fn policy(&self, response: &HttpResponse) -> Result<CachePolicy>;
535    /// Returns a new cache policy with custom options
536    fn policy_with_options(
537        &self,
538        response: &HttpResponse,
539        options: CacheOptions,
540    ) -> Result<CachePolicy>;
541    /// Attempts to update the request headers with the passed `http::request::Parts`
542    fn update_headers(&mut self, parts: &request::Parts) -> Result<()>;
543    /// Attempts to force the "no-cache" directive on the request
544    fn force_no_cache(&mut self) -> Result<()>;
545    /// Attempts to construct `http::request::Parts` from the request
546    fn parts(&self) -> Result<request::Parts>;
547    /// Attempts to determine the requested url
548    fn url(&self) -> Result<Url>;
549    /// Attempts to determine the request method
550    fn method(&self) -> Result<String>;
551    /// Attempts to fetch an upstream resource and return an [`HttpResponse`]
552    async fn remote_fetch(&mut self) -> Result<HttpResponse>;
553}
554
555/// An interface for HTTP caching that works with composable middleware patterns
556/// like Tower. This trait separates the concerns of request analysis, cache lookup,
557/// and response processing into discrete steps.
558pub trait HttpCacheInterface<B = Vec<u8>>: Send + Sync {
559    /// Analyze a request to determine cache behavior
560    fn analyze_request(
561        &self,
562        parts: &request::Parts,
563        mode_override: Option<CacheMode>,
564    ) -> Result<CacheAnalysis>;
565
566    /// Look up a cached response for the given cache key
567    #[allow(async_fn_in_trait)]
568    async fn lookup_cached_response(
569        &self,
570        key: &str,
571    ) -> Result<Option<(HttpResponse, CachePolicy)>>;
572
573    /// Process a fresh response from upstream and potentially cache it
574    #[allow(async_fn_in_trait)]
575    async fn process_response(
576        &self,
577        analysis: CacheAnalysis,
578        response: Response<B>,
579    ) -> Result<Response<B>>;
580
581    /// Update request headers for conditional requests (e.g., If-None-Match)
582    fn prepare_conditional_request(
583        &self,
584        parts: &mut request::Parts,
585        cached_response: &HttpResponse,
586        policy: &CachePolicy,
587    ) -> Result<()>;
588
589    /// Handle a 304 Not Modified response by returning the cached response
590    #[allow(async_fn_in_trait)]
591    async fn handle_not_modified(
592        &self,
593        cached_response: HttpResponse,
594        fresh_parts: &response::Parts,
595    ) -> Result<HttpResponse>;
596}
597
598/// Streaming version of the HTTP cache interface that supports streaming request/response bodies
599/// without buffering them in memory. This is ideal for large responses or when memory usage
600/// is a concern.
601pub trait HttpCacheStreamInterface: Send + Sync {
602    /// The body type used by this cache implementation
603    type Body: http_body::Body + Send + 'static;
604
605    /// Analyze a request to determine cache behavior
606    fn analyze_request(
607        &self,
608        parts: &request::Parts,
609        mode_override: Option<CacheMode>,
610    ) -> Result<CacheAnalysis>;
611
612    /// Look up a cached response for the given cache key, returning a streaming body
613    #[allow(async_fn_in_trait)]
614    async fn lookup_cached_response(
615        &self,
616        key: &str,
617    ) -> Result<Option<(Response<Self::Body>, CachePolicy)>>
618    where
619        <Self::Body as http_body::Body>::Data: Send,
620        <Self::Body as http_body::Body>::Error:
621            Into<StreamingError> + Send + Sync + 'static;
622
623    /// Process a fresh response from upstream and potentially cache it with streaming support
624    #[allow(async_fn_in_trait)]
625    async fn process_response<B>(
626        &self,
627        analysis: CacheAnalysis,
628        response: Response<B>,
629    ) -> Result<Response<Self::Body>>
630    where
631        B: http_body::Body + Send + 'static,
632        B::Data: Send,
633        B::Error: Into<StreamingError>,
634        <Self::Body as http_body::Body>::Data: Send,
635        <Self::Body as http_body::Body>::Error:
636            Into<StreamingError> + Send + Sync + 'static;
637
638    /// Update request headers for conditional requests (e.g., If-None-Match)
639    fn prepare_conditional_request(
640        &self,
641        parts: &mut request::Parts,
642        cached_response: &Response<Self::Body>,
643        policy: &CachePolicy,
644    ) -> Result<()>;
645
646    /// Handle a 304 Not Modified response by returning the cached response
647    #[allow(async_fn_in_trait)]
648    async fn handle_not_modified(
649        &self,
650        cached_response: Response<Self::Body>,
651        fresh_parts: &response::Parts,
652    ) -> Result<Response<Self::Body>>
653    where
654        <Self::Body as http_body::Body>::Data: Send,
655        <Self::Body as http_body::Body>::Error:
656            Into<StreamingError> + Send + Sync + 'static;
657}
658
659/// Analysis result for a request, containing cache key and caching decisions
660#[derive(Debug, Clone)]
661pub struct CacheAnalysis {
662    /// The cache key for this request
663    pub cache_key: String,
664    /// Whether this request should be cached
665    pub should_cache: bool,
666    /// The effective cache mode for this request
667    pub cache_mode: CacheMode,
668    /// Keys to bust from cache before processing
669    pub cache_bust_keys: Vec<String>,
670    /// The request parts for policy creation
671    pub request_parts: request::Parts,
672    /// Whether this is a GET or HEAD request
673    pub is_get_head: bool,
674}
675
676/// Cache mode determines how the HTTP cache behaves for requests.
677///
678/// These modes are similar to [make-fetch-happen cache options](https://github.com/npm/make-fetch-happen#--optscache)
679/// and provide fine-grained control over caching behavior.
680///
681/// # Examples
682///
683/// ```rust
684/// use http_cache::{CacheMode, HttpCache, CACacheManager, HttpCacheOptions};
685///
686/// let manager = CACacheManager::new("./cache".into(), true);
687///
688/// // Use different cache modes for different scenarios
689/// let default_cache = HttpCache {
690///     mode: CacheMode::Default,        // Standard HTTP caching rules
691///     manager: manager.clone(),
692///     options: HttpCacheOptions::default(),
693/// };
694///
695/// let force_cache = HttpCache {
696///     mode: CacheMode::ForceCache,     // Cache everything, ignore staleness
697///     manager: manager.clone(),
698///     options: HttpCacheOptions::default(),
699/// };
700///
701/// let no_cache = HttpCache {
702///     mode: CacheMode::NoStore,        // Never cache anything
703///     manager,
704///     options: HttpCacheOptions::default(),
705/// };
706/// ```
707#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
708pub enum CacheMode {
709    /// Standard HTTP caching behavior (recommended for most use cases).
710    ///
711    /// This mode:
712    /// - Checks the cache for fresh responses and uses them
713    /// - Makes conditional requests for stale responses (revalidation)
714    /// - Makes normal requests when no cached response exists
715    /// - Updates the cache with new responses
716    /// - Falls back to stale responses if revalidation fails
717    ///
718    /// This is the most common mode and follows HTTP caching standards closely.
719    #[default]
720    Default,
721
722    /// Completely bypasses the cache.
723    ///
724    /// This mode:
725    /// - Never reads from the cache
726    /// - Never writes to the cache
727    /// - Always makes fresh network requests
728    ///
729    /// Use this when you need to ensure every request goes to the origin server.
730    NoStore,
731
732    /// Bypasses cache on request but updates cache with response.
733    ///
734    /// This mode:
735    /// - Ignores any cached responses
736    /// - Always makes a fresh network request
737    /// - Updates the cache with the response
738    ///
739    /// Equivalent to a "hard refresh" - useful when you know the cache is stale.
740    Reload,
741
742    /// Always revalidates cached responses.
743    ///
744    /// This mode:
745    /// - Makes conditional requests if a cached response exists
746    /// - Makes normal requests if no cached response exists
747    /// - Updates the cache with responses
748    ///
749    /// Use this when you want to ensure content freshness while still benefiting
750    /// from conditional requests (304 Not Modified responses).
751    NoCache,
752
753    /// Uses cached responses regardless of staleness.
754    ///
755    /// This mode:
756    /// - Uses any cached response, even if stale
757    /// - Makes network requests only when no cached response exists
758    /// - Updates the cache with new responses
759    ///
760    /// Useful for offline scenarios or when performance is more important than freshness.
761    ForceCache,
762
763    /// Only serves from cache, never makes network requests.
764    ///
765    /// This mode:
766    /// - Uses any cached response, even if stale
767    /// - Returns an error if no cached response exists
768    /// - Never makes network requests
769    ///
770    /// Use this for offline-only scenarios or when you want to guarantee
771    /// no network traffic.
772    OnlyIfCached,
773
774    /// Ignores HTTP caching rules and caches everything.
775    ///
776    /// This mode:
777    /// - Caches all 200 responses regardless of cache-control headers
778    /// - Uses cached responses regardless of staleness
779    /// - Makes network requests when no cached response exists
780    ///
781    /// Use this when you want aggressive caching and don't want to respect
782    /// server cache directives.
783    IgnoreRules,
784}
785
786impl TryFrom<http::Version> for HttpVersion {
787    type Error = BoxError;
788
789    fn try_from(value: http::Version) -> Result<Self> {
790        Ok(match value {
791            http::Version::HTTP_09 => Self::Http09,
792            http::Version::HTTP_10 => Self::Http10,
793            http::Version::HTTP_11 => Self::Http11,
794            http::Version::HTTP_2 => Self::H2,
795            http::Version::HTTP_3 => Self::H3,
796            _ => return Err(Box::new(BadVersion)),
797        })
798    }
799}
800
801impl From<HttpVersion> for http::Version {
802    fn from(value: HttpVersion) -> Self {
803        match value {
804            HttpVersion::Http09 => Self::HTTP_09,
805            HttpVersion::Http10 => Self::HTTP_10,
806            HttpVersion::Http11 => Self::HTTP_11,
807            HttpVersion::H2 => Self::HTTP_2,
808            HttpVersion::H3 => Self::HTTP_3,
809        }
810    }
811}
812
813#[cfg(feature = "http-types")]
814impl TryFrom<http_types::Version> for HttpVersion {
815    type Error = BoxError;
816
817    fn try_from(value: http_types::Version) -> Result<Self> {
818        Ok(match value {
819            http_types::Version::Http0_9 => Self::Http09,
820            http_types::Version::Http1_0 => Self::Http10,
821            http_types::Version::Http1_1 => Self::Http11,
822            http_types::Version::Http2_0 => Self::H2,
823            http_types::Version::Http3_0 => Self::H3,
824            _ => return Err(Box::new(BadVersion)),
825        })
826    }
827}
828
829#[cfg(feature = "http-types")]
830impl From<HttpVersion> for http_types::Version {
831    fn from(value: HttpVersion) -> Self {
832        match value {
833            HttpVersion::Http09 => Self::Http0_9,
834            HttpVersion::Http10 => Self::Http1_0,
835            HttpVersion::Http11 => Self::Http1_1,
836            HttpVersion::H2 => Self::Http2_0,
837            HttpVersion::H3 => Self::Http3_0,
838        }
839    }
840}
841
842/// Options struct provided by
843/// [`http-cache-semantics`](https://github.com/kornelski/rusty-http-cache-semantics).
844pub use http_cache_semantics::CacheOptions;
845
846/// A closure that takes [`http::request::Parts`] and returns a [`String`].
847/// By default, the cache key is a combination of the request method and uri with a colon in between.
848pub type CacheKey = Arc<dyn Fn(&request::Parts) -> String + Send + Sync>;
849
850/// A closure that takes [`http::request::Parts`] and returns a [`CacheMode`]
851pub type CacheModeFn = Arc<dyn Fn(&request::Parts) -> CacheMode + Send + Sync>;
852
853/// A closure that takes [`http::request::Parts`], [`HttpResponse`] and returns a [`CacheMode`] to override caching behavior based on the response
854pub type ResponseCacheModeFn = Arc<
855    dyn Fn(&request::Parts, &HttpResponse) -> Option<CacheMode> + Send + Sync,
856>;
857
858/// 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.
859/// An empty vector means that no cache busting will be performed.
860pub type CacheBust = Arc<
861    dyn Fn(&request::Parts, &Option<CacheKey>, &str) -> Vec<String>
862        + Send
863        + Sync,
864>;
865
866/// Configuration options for customizing HTTP cache behavior on a per-request basis.
867///
868/// This struct allows you to override default caching behavior for individual requests
869/// by providing custom cache options, cache keys, cache modes, and cache busting logic.
870///
871/// # Examples
872///
873/// ## Basic Custom Cache Key
874/// ```rust
875/// use http_cache::{HttpCacheOptions, CacheKey};
876/// use http::request::Parts;
877/// use std::sync::Arc;
878///
879/// let options = HttpCacheOptions {
880///     cache_key: Some(Arc::new(|parts: &Parts| {
881///         format!("custom:{}:{}", parts.method, parts.uri.path())
882///     })),
883///     ..Default::default()
884/// };
885/// ```
886///
887/// ## Custom Cache Mode per Request
888/// ```rust
889/// use http_cache::{HttpCacheOptions, CacheMode, CacheModeFn};
890/// use http::request::Parts;
891/// use std::sync::Arc;
892///
893/// let options = HttpCacheOptions {
894///     cache_mode_fn: Some(Arc::new(|parts: &Parts| {
895///         if parts.headers.contains_key("x-no-cache") {
896///             CacheMode::NoStore
897///         } else {
898///             CacheMode::Default
899///         }
900///     })),
901///     ..Default::default()
902/// };
903/// ```
904///
905/// ## Response-Based Cache Mode Override
906/// ```rust
907/// use http_cache::{HttpCacheOptions, ResponseCacheModeFn, CacheMode};
908/// use http::request::Parts;
909/// use http_cache::HttpResponse;
910/// use std::sync::Arc;
911///
912/// let options = HttpCacheOptions {
913///     response_cache_mode_fn: Some(Arc::new(|_parts: &Parts, response: &HttpResponse| {
914///         // Force cache 2xx responses even if headers say not to cache
915///         if response.status >= 200 && response.status < 300 {
916///             Some(CacheMode::ForceCache)
917///         } else if response.status == 429 { // Rate limited
918///             Some(CacheMode::NoStore) // Don't cache rate limit responses
919///         } else {
920///             None // Use default behavior
921///         }
922///     })),
923///     ..Default::default()
924/// };
925/// ```
926///
927/// ## Cache Busting for Related Resources
928/// ```rust
929/// use http_cache::{HttpCacheOptions, CacheBust, CacheKey};
930/// use http::request::Parts;
931/// use std::sync::Arc;
932///
933/// let options = HttpCacheOptions {
934///     cache_bust: Some(Arc::new(|parts: &Parts, _cache_key: &Option<CacheKey>, _uri: &str| {
935///         if parts.method == "POST" && parts.uri.path().starts_with("/api/users") {
936///             vec![
937///                 "GET:/api/users".to_string(),
938///                 "GET:/api/users/list".to_string(),
939///             ]
940///         } else {
941///             vec![]
942///         }
943///     })),
944///     ..Default::default()
945/// };
946/// ```
947#[derive(Clone)]
948pub struct HttpCacheOptions {
949    /// Override the default cache options.
950    pub cache_options: Option<CacheOptions>,
951    /// Override the default cache key generator.
952    pub cache_key: Option<CacheKey>,
953    /// Override the default cache mode.
954    pub cache_mode_fn: Option<CacheModeFn>,
955    /// Override cache behavior based on the response received.
956    /// This function is called after receiving a response and can override
957    /// the cache mode for that specific response. Returning `None` means
958    /// use the default cache mode. This allows fine-grained control over
959    /// caching behavior based on response status, headers, or content.
960    pub response_cache_mode_fn: Option<ResponseCacheModeFn>,
961    /// Bust the caches of the returned keys.
962    pub cache_bust: Option<CacheBust>,
963    /// Determines if the cache status headers should be added to the response.
964    pub cache_status_headers: bool,
965}
966
967impl Default for HttpCacheOptions {
968    fn default() -> Self {
969        Self {
970            cache_options: None,
971            cache_key: None,
972            cache_mode_fn: None,
973            response_cache_mode_fn: None,
974            cache_bust: None,
975            cache_status_headers: true,
976        }
977    }
978}
979
980impl Debug for HttpCacheOptions {
981    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
982        f.debug_struct("HttpCacheOptions")
983            .field("cache_options", &self.cache_options)
984            .field("cache_key", &"Fn(&request::Parts) -> String")
985            .field("cache_mode_fn", &"Fn(&request::Parts) -> CacheMode")
986            .field(
987                "response_cache_mode_fn",
988                &"Fn(&request::Parts, &HttpResponse) -> Option<CacheMode>",
989            )
990            .field("cache_bust", &"Fn(&request::Parts) -> Vec<String>")
991            .field("cache_status_headers", &self.cache_status_headers)
992            .finish()
993    }
994}
995
996impl HttpCacheOptions {
997    fn create_cache_key(
998        &self,
999        parts: &request::Parts,
1000        override_method: Option<&str>,
1001    ) -> String {
1002        if let Some(cache_key) = &self.cache_key {
1003            cache_key(parts)
1004        } else {
1005            format!(
1006                "{}:{}",
1007                override_method.unwrap_or_else(|| parts.method.as_str()),
1008                parts.uri
1009            )
1010        }
1011    }
1012
1013    /// Helper function for other crates to generate cache keys for invalidation
1014    /// This ensures consistent cache key generation across all implementations
1015    pub fn create_cache_key_for_invalidation(
1016        &self,
1017        parts: &request::Parts,
1018        method_override: &str,
1019    ) -> String {
1020        self.create_cache_key(parts, Some(method_override))
1021    }
1022
1023    /// Converts http::HeaderMap to HashMap<String, String> for HttpResponse
1024    fn headers_to_hashmap(
1025        headers: &http::HeaderMap,
1026    ) -> HashMap<String, String> {
1027        headers
1028            .iter()
1029            .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
1030            .collect()
1031    }
1032
1033    /// Converts HttpResponse to http::Response with the given body type
1034    pub fn http_response_to_response<B>(
1035        http_response: &HttpResponse,
1036        body: B,
1037    ) -> Result<Response<B>> {
1038        let mut response_builder = Response::builder()
1039            .status(http_response.status)
1040            .version(http_response.version.into());
1041
1042        for (name, value) in &http_response.headers {
1043            if let (Ok(header_name), Ok(header_value)) = (
1044                name.parse::<http::HeaderName>(),
1045                value.parse::<http::HeaderValue>(),
1046            ) {
1047                response_builder =
1048                    response_builder.header(header_name, header_value);
1049            }
1050        }
1051
1052        Ok(response_builder.body(body)?)
1053    }
1054
1055    /// Converts response parts to HttpResponse format for cache mode evaluation
1056    fn parts_to_http_response(
1057        &self,
1058        parts: &response::Parts,
1059        request_parts: &request::Parts,
1060    ) -> Result<HttpResponse> {
1061        Ok(HttpResponse {
1062            body: vec![], // We don't need the full body for cache mode decision
1063            headers: Self::headers_to_hashmap(&parts.headers),
1064            status: parts.status.as_u16(),
1065            url: extract_url_from_request_parts(request_parts)?,
1066            version: parts.version.try_into()?,
1067        })
1068    }
1069
1070    /// Evaluates response-based cache mode override
1071    fn evaluate_response_cache_mode(
1072        &self,
1073        request_parts: &request::Parts,
1074        http_response: &HttpResponse,
1075        original_mode: CacheMode,
1076    ) -> CacheMode {
1077        if let Some(response_cache_mode_fn) = &self.response_cache_mode_fn {
1078            if let Some(override_mode) =
1079                response_cache_mode_fn(request_parts, http_response)
1080            {
1081                return override_mode;
1082            }
1083        }
1084        original_mode
1085    }
1086
1087    /// Creates a cache policy for the given request and response
1088    fn create_cache_policy(
1089        &self,
1090        request_parts: &request::Parts,
1091        response_parts: &response::Parts,
1092    ) -> CachePolicy {
1093        match self.cache_options {
1094            Some(options) => CachePolicy::new_options(
1095                request_parts,
1096                response_parts,
1097                SystemTime::now(),
1098                options,
1099            ),
1100            None => CachePolicy::new(request_parts, response_parts),
1101        }
1102    }
1103
1104    /// Determines if a response should be cached based on cache mode and HTTP semantics
1105    fn should_cache_response(
1106        &self,
1107        effective_cache_mode: CacheMode,
1108        http_response: &HttpResponse,
1109        is_get_head: bool,
1110        policy: &CachePolicy,
1111    ) -> bool {
1112        // HTTP status codes that are cacheable by default (RFC 7234)
1113        let is_cacheable_status = matches!(
1114            http_response.status,
1115            200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
1116        );
1117
1118        if is_cacheable_status {
1119            match effective_cache_mode {
1120                CacheMode::ForceCache => is_get_head,
1121                CacheMode::IgnoreRules => true,
1122                CacheMode::NoStore => false,
1123                _ => is_get_head && policy.is_storable(),
1124            }
1125        } else {
1126            false
1127        }
1128    }
1129
1130    /// Common request analysis logic shared between streaming and non-streaming implementations
1131    fn analyze_request_internal(
1132        &self,
1133        parts: &request::Parts,
1134        mode_override: Option<CacheMode>,
1135        default_mode: CacheMode,
1136    ) -> Result<CacheAnalysis> {
1137        let effective_mode = mode_override
1138            .or_else(|| self.cache_mode_fn.as_ref().map(|f| f(parts)))
1139            .unwrap_or(default_mode);
1140
1141        let is_get_head = parts.method == "GET" || parts.method == "HEAD";
1142        let should_cache = effective_mode == CacheMode::IgnoreRules
1143            || (is_get_head && effective_mode != CacheMode::NoStore);
1144
1145        let cache_key = self.create_cache_key(parts, None);
1146
1147        let cache_bust_keys = if let Some(cache_bust) = &self.cache_bust {
1148            cache_bust(parts, &self.cache_key, &cache_key)
1149        } else {
1150            Vec::new()
1151        };
1152
1153        Ok(CacheAnalysis {
1154            cache_key,
1155            should_cache,
1156            cache_mode: effective_mode,
1157            cache_bust_keys,
1158            request_parts: parts.clone(),
1159            is_get_head,
1160        })
1161    }
1162}
1163
1164/// Caches requests according to http spec.
1165#[derive(Debug, Clone)]
1166pub struct HttpCache<T: CacheManager> {
1167    /// Determines the manager behavior.
1168    pub mode: CacheMode,
1169    /// Manager instance that implements the [`CacheManager`] trait.
1170    /// By default, a manager implementation with [`cacache`](https://github.com/zkat/cacache-rs)
1171    /// as the backend has been provided, see [`CACacheManager`].
1172    pub manager: T,
1173    /// Override the default cache options.
1174    pub options: HttpCacheOptions,
1175}
1176
1177/// Streaming version of HTTP cache that supports streaming request/response bodies
1178/// without buffering them in memory.
1179#[derive(Debug, Clone)]
1180pub struct HttpStreamingCache<T: StreamingCacheManager> {
1181    /// Determines the manager behavior.
1182    pub mode: CacheMode,
1183    /// Manager instance that implements the [`StreamingCacheManager`] trait.
1184    pub manager: T,
1185    /// Override the default cache options.
1186    pub options: HttpCacheOptions,
1187}
1188
1189#[allow(dead_code)]
1190impl<T: CacheManager> HttpCache<T> {
1191    /// Determines if the request should be cached
1192    pub fn can_cache_request(
1193        &self,
1194        middleware: &impl Middleware,
1195    ) -> Result<bool> {
1196        let analysis = self.analyze_request(
1197            &middleware.parts()?,
1198            middleware.overridden_cache_mode(),
1199        )?;
1200        Ok(analysis.should_cache)
1201    }
1202
1203    /// Runs the actions to preform when the client middleware is running without the cache
1204    pub async fn run_no_cache(
1205        &self,
1206        middleware: &mut impl Middleware,
1207    ) -> Result<()> {
1208        let parts = middleware.parts()?;
1209
1210        self.manager
1211            .delete(&self.options.create_cache_key(&parts, Some("GET")))
1212            .await
1213            .ok();
1214
1215        let cache_key = self.options.create_cache_key(&parts, None);
1216
1217        if let Some(cache_bust) = &self.options.cache_bust {
1218            for key_to_cache_bust in
1219                cache_bust(&parts, &self.options.cache_key, &cache_key)
1220            {
1221                self.manager.delete(&key_to_cache_bust).await?;
1222            }
1223        }
1224
1225        Ok(())
1226    }
1227
1228    /// Attempts to run the passed middleware along with the cache
1229    pub async fn run(
1230        &self,
1231        mut middleware: impl Middleware,
1232    ) -> Result<HttpResponse> {
1233        // Use the HttpCacheInterface to analyze the request
1234        let analysis = self.analyze_request(
1235            &middleware.parts()?,
1236            middleware.overridden_cache_mode(),
1237        )?;
1238
1239        if !analysis.should_cache {
1240            return self.remote_fetch(&mut middleware).await;
1241        }
1242
1243        // Bust cache keys if needed
1244        for key in &analysis.cache_bust_keys {
1245            self.manager.delete(key).await?;
1246        }
1247
1248        // Look up cached response
1249        if let Some((mut cached_response, policy)) =
1250            self.lookup_cached_response(&analysis.cache_key).await?
1251        {
1252            if self.options.cache_status_headers {
1253                cached_response.cache_lookup_status(HitOrMiss::HIT);
1254            }
1255
1256            // Handle warning headers
1257            if let Some(warning_code) = cached_response.warning_code() {
1258                // https://tools.ietf.org/html/rfc7234#section-4.3.4
1259                //
1260                // If a stored response is selected for update, the cache MUST:
1261                //
1262                // * delete any warning header fields in the stored response with
1263                //   warn-code 1xx (see Section 5.5);
1264                //
1265                // * retain any warning header fields in the stored response with
1266                //   warn-code 2xx;
1267                //
1268                if (100..200).contains(&warning_code) {
1269                    cached_response.remove_warning();
1270                }
1271            }
1272
1273            match analysis.cache_mode {
1274                CacheMode::Default => {
1275                    self.conditional_fetch(middleware, cached_response, policy)
1276                        .await
1277                }
1278                CacheMode::NoCache => {
1279                    middleware.force_no_cache()?;
1280                    let mut res = self.remote_fetch(&mut middleware).await?;
1281                    if self.options.cache_status_headers {
1282                        res.cache_lookup_status(HitOrMiss::HIT);
1283                    }
1284                    Ok(res)
1285                }
1286                CacheMode::ForceCache
1287                | CacheMode::OnlyIfCached
1288                | CacheMode::IgnoreRules => {
1289                    //   112 Disconnected operation
1290                    // SHOULD be included if the cache is intentionally disconnected from
1291                    // the rest of the network for a period of time.
1292                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
1293                    cached_response.add_warning(
1294                        &cached_response.url.clone(),
1295                        112,
1296                        "Disconnected operation",
1297                    );
1298                    if self.options.cache_status_headers {
1299                        cached_response.cache_status(HitOrMiss::HIT);
1300                    }
1301                    Ok(cached_response)
1302                }
1303                _ => self.remote_fetch(&mut middleware).await,
1304            }
1305        } else {
1306            match analysis.cache_mode {
1307                CacheMode::OnlyIfCached => {
1308                    // ENOTCACHED
1309                    let mut res = HttpResponse {
1310                        body: b"GatewayTimeout".to_vec(),
1311                        headers: HashMap::default(),
1312                        status: 504,
1313                        url: middleware.url()?,
1314                        version: HttpVersion::Http11,
1315                    };
1316                    if self.options.cache_status_headers {
1317                        res.cache_status(HitOrMiss::MISS);
1318                        res.cache_lookup_status(HitOrMiss::MISS);
1319                    }
1320                    Ok(res)
1321                }
1322                _ => self.remote_fetch(&mut middleware).await,
1323            }
1324        }
1325    }
1326
1327    fn cache_mode(&self, middleware: &impl Middleware) -> Result<CacheMode> {
1328        Ok(if let Some(mode) = middleware.overridden_cache_mode() {
1329            mode
1330        } else if let Some(cache_mode_fn) = &self.options.cache_mode_fn {
1331            cache_mode_fn(&middleware.parts()?)
1332        } else {
1333            self.mode
1334        })
1335    }
1336
1337    async fn remote_fetch(
1338        &self,
1339        middleware: &mut impl Middleware,
1340    ) -> Result<HttpResponse> {
1341        let mut res = middleware.remote_fetch().await?;
1342        if self.options.cache_status_headers {
1343            res.cache_status(HitOrMiss::MISS);
1344            res.cache_lookup_status(HitOrMiss::MISS);
1345        }
1346        let policy = match self.options.cache_options {
1347            Some(options) => middleware.policy_with_options(&res, options)?,
1348            None => middleware.policy(&res)?,
1349        };
1350        let is_get_head = middleware.is_method_get_head();
1351        let mut mode = self.cache_mode(middleware)?;
1352        let parts = middleware.parts()?;
1353
1354        // Allow response-based cache mode override
1355        if let Some(response_cache_mode_fn) =
1356            &self.options.response_cache_mode_fn
1357        {
1358            if let Some(override_mode) = response_cache_mode_fn(&parts, &res) {
1359                mode = override_mode;
1360            }
1361        }
1362
1363        let is_cacheable = self.options.should_cache_response(
1364            mode,
1365            &res,
1366            is_get_head,
1367            &policy,
1368        );
1369
1370        if is_cacheable {
1371            Ok(self
1372                .manager
1373                .put(self.options.create_cache_key(&parts, None), res, policy)
1374                .await?)
1375        } else if !is_get_head {
1376            self.manager
1377                .delete(&self.options.create_cache_key(&parts, Some("GET")))
1378                .await
1379                .ok();
1380            Ok(res)
1381        } else {
1382            Ok(res)
1383        }
1384    }
1385
1386    async fn conditional_fetch(
1387        &self,
1388        mut middleware: impl Middleware,
1389        mut cached_res: HttpResponse,
1390        mut policy: CachePolicy,
1391    ) -> Result<HttpResponse> {
1392        let parts = middleware.parts()?;
1393        let before_req = policy.before_request(&parts, SystemTime::now());
1394        match before_req {
1395            BeforeRequest::Fresh(parts) => {
1396                cached_res.update_headers(&parts)?;
1397                if self.options.cache_status_headers {
1398                    cached_res.cache_status(HitOrMiss::HIT);
1399                    cached_res.cache_lookup_status(HitOrMiss::HIT);
1400                }
1401                return Ok(cached_res);
1402            }
1403            BeforeRequest::Stale { request: parts, matches } => {
1404                if matches {
1405                    middleware.update_headers(&parts)?;
1406                }
1407            }
1408        }
1409        let req_url = middleware.url()?;
1410        match middleware.remote_fetch().await {
1411            Ok(mut cond_res) => {
1412                let status = StatusCode::from_u16(cond_res.status)?;
1413                if status.is_server_error() && cached_res.must_revalidate() {
1414                    //   111 Revalidation failed
1415                    //   MUST be included if a cache returns a stale response
1416                    //   because an attempt to revalidate the response failed,
1417                    //   due to an inability to reach the server.
1418                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
1419                    cached_res.add_warning(
1420                        &req_url,
1421                        111,
1422                        "Revalidation failed",
1423                    );
1424                    if self.options.cache_status_headers {
1425                        cached_res.cache_status(HitOrMiss::HIT);
1426                    }
1427                    Ok(cached_res)
1428                } else if cond_res.status == 304 {
1429                    let after_res = policy.after_response(
1430                        &parts,
1431                        &cond_res.parts()?,
1432                        SystemTime::now(),
1433                    );
1434                    match after_res {
1435                        AfterResponse::Modified(new_policy, parts)
1436                        | AfterResponse::NotModified(new_policy, parts) => {
1437                            policy = new_policy;
1438                            cached_res.update_headers(&parts)?;
1439                        }
1440                    }
1441                    if self.options.cache_status_headers {
1442                        cached_res.cache_status(HitOrMiss::HIT);
1443                        cached_res.cache_lookup_status(HitOrMiss::HIT);
1444                    }
1445                    let res = self
1446                        .manager
1447                        .put(
1448                            self.options.create_cache_key(&parts, None),
1449                            cached_res,
1450                            policy,
1451                        )
1452                        .await?;
1453                    Ok(res)
1454                } else if cond_res.status == 200 {
1455                    let policy = match self.options.cache_options {
1456                        Some(options) => middleware
1457                            .policy_with_options(&cond_res, options)?,
1458                        None => middleware.policy(&cond_res)?,
1459                    };
1460                    if self.options.cache_status_headers {
1461                        cond_res.cache_status(HitOrMiss::MISS);
1462                        cond_res.cache_lookup_status(HitOrMiss::HIT);
1463                    }
1464                    let res = self
1465                        .manager
1466                        .put(
1467                            self.options.create_cache_key(&parts, None),
1468                            cond_res,
1469                            policy,
1470                        )
1471                        .await?;
1472                    Ok(res)
1473                } else {
1474                    if self.options.cache_status_headers {
1475                        cached_res.cache_status(HitOrMiss::HIT);
1476                    }
1477                    Ok(cached_res)
1478                }
1479            }
1480            Err(e) => {
1481                if cached_res.must_revalidate() {
1482                    Err(e)
1483                } else {
1484                    //   111 Revalidation failed
1485                    //   MUST be included if a cache returns a stale response
1486                    //   because an attempt to revalidate the response failed,
1487                    //   due to an inability to reach the server.
1488                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
1489                    cached_res.add_warning(
1490                        &req_url,
1491                        111,
1492                        "Revalidation failed",
1493                    );
1494                    if self.options.cache_status_headers {
1495                        cached_res.cache_status(HitOrMiss::HIT);
1496                    }
1497                    Ok(cached_res)
1498                }
1499            }
1500        }
1501    }
1502}
1503
1504impl<T: StreamingCacheManager> HttpCacheStreamInterface
1505    for HttpStreamingCache<T>
1506where
1507    <T::Body as http_body::Body>::Data: Send,
1508    <T::Body as http_body::Body>::Error:
1509        Into<StreamingError> + Send + Sync + 'static,
1510{
1511    type Body = T::Body;
1512
1513    fn analyze_request(
1514        &self,
1515        parts: &request::Parts,
1516        mode_override: Option<CacheMode>,
1517    ) -> Result<CacheAnalysis> {
1518        self.options.analyze_request_internal(parts, mode_override, self.mode)
1519    }
1520
1521    async fn lookup_cached_response(
1522        &self,
1523        key: &str,
1524    ) -> Result<Option<(Response<Self::Body>, CachePolicy)>> {
1525        self.manager.get(key).await
1526    }
1527
1528    async fn process_response<B>(
1529        &self,
1530        analysis: CacheAnalysis,
1531        response: Response<B>,
1532    ) -> Result<Response<Self::Body>>
1533    where
1534        B: http_body::Body + Send + 'static,
1535        B::Data: Send,
1536        B::Error: Into<StreamingError>,
1537        <T::Body as http_body::Body>::Data: Send,
1538        <T::Body as http_body::Body>::Error:
1539            Into<StreamingError> + Send + Sync + 'static,
1540    {
1541        // For non-cacheable requests based on initial analysis, convert them to manager's body type
1542        if !analysis.should_cache {
1543            return self.manager.convert_body(response).await;
1544        }
1545
1546        // Bust cache keys if needed
1547        for key in &analysis.cache_bust_keys {
1548            self.manager.delete(key).await?;
1549        }
1550
1551        // Convert response to HttpResponse format for response-based cache mode evaluation
1552        let (parts, body) = response.into_parts();
1553        let http_response = self
1554            .options
1555            .parts_to_http_response(&parts, &analysis.request_parts)?;
1556
1557        // Check for response-based cache mode override
1558        let effective_cache_mode = self.options.evaluate_response_cache_mode(
1559            &analysis.request_parts,
1560            &http_response,
1561            analysis.cache_mode,
1562        );
1563
1564        // Reconstruct response for further processing
1565        let response = Response::from_parts(parts, body);
1566
1567        // If response-based override says NoStore, don't cache
1568        if effective_cache_mode == CacheMode::NoStore {
1569            return self.manager.convert_body(response).await;
1570        }
1571
1572        // Create policy for the response
1573        let (parts, body) = response.into_parts();
1574        let policy =
1575            self.options.create_cache_policy(&analysis.request_parts, &parts);
1576
1577        // Reconstruct response for caching
1578        let response = Response::from_parts(parts, body);
1579
1580        let should_cache_response = self.options.should_cache_response(
1581            effective_cache_mode,
1582            &http_response,
1583            analysis.is_get_head,
1584            &policy,
1585        );
1586
1587        if should_cache_response {
1588            // Extract URL from request parts for caching
1589            let request_url =
1590                extract_url_from_request_parts(&analysis.request_parts)?;
1591
1592            // Cache the response using the streaming manager
1593            self.manager
1594                .put(analysis.cache_key, response, policy, request_url)
1595                .await
1596        } else {
1597            // Don't cache, just convert to manager's body type
1598            self.manager.convert_body(response).await
1599        }
1600    }
1601
1602    fn prepare_conditional_request(
1603        &self,
1604        parts: &mut request::Parts,
1605        _cached_response: &Response<Self::Body>,
1606        policy: &CachePolicy,
1607    ) -> Result<()> {
1608        let before_req = policy.before_request(parts, SystemTime::now());
1609        if let BeforeRequest::Stale { request, .. } = before_req {
1610            parts.headers.extend(request.headers);
1611        }
1612        Ok(())
1613    }
1614
1615    async fn handle_not_modified(
1616        &self,
1617        cached_response: Response<Self::Body>,
1618        fresh_parts: &response::Parts,
1619    ) -> Result<Response<Self::Body>> {
1620        let (mut parts, body) = cached_response.into_parts();
1621
1622        // Update headers from the 304 response
1623        parts.headers.extend(fresh_parts.headers.clone());
1624
1625        Ok(Response::from_parts(parts, body))
1626    }
1627}
1628
1629impl<T: CacheManager> HttpCacheInterface for HttpCache<T> {
1630    fn analyze_request(
1631        &self,
1632        parts: &request::Parts,
1633        mode_override: Option<CacheMode>,
1634    ) -> Result<CacheAnalysis> {
1635        self.options.analyze_request_internal(parts, mode_override, self.mode)
1636    }
1637
1638    async fn lookup_cached_response(
1639        &self,
1640        key: &str,
1641    ) -> Result<Option<(HttpResponse, CachePolicy)>> {
1642        self.manager.get(key).await
1643    }
1644
1645    async fn process_response(
1646        &self,
1647        analysis: CacheAnalysis,
1648        response: Response<Vec<u8>>,
1649    ) -> Result<Response<Vec<u8>>> {
1650        if !analysis.should_cache {
1651            return Ok(response);
1652        }
1653
1654        // Bust cache keys if needed
1655        for key in &analysis.cache_bust_keys {
1656            self.manager.delete(key).await?;
1657        }
1658
1659        // Convert response to HttpResponse format
1660        let (parts, body) = response.into_parts();
1661        let mut http_response = self
1662            .options
1663            .parts_to_http_response(&parts, &analysis.request_parts)?;
1664        http_response.body = body.clone(); // Include the body for buffered cache managers
1665
1666        // Check for response-based cache mode override
1667        let effective_cache_mode = self.options.evaluate_response_cache_mode(
1668            &analysis.request_parts,
1669            &http_response,
1670            analysis.cache_mode,
1671        );
1672
1673        // If response-based override says NoStore, don't cache
1674        if effective_cache_mode == CacheMode::NoStore {
1675            let response = Response::from_parts(parts, body);
1676            return Ok(response);
1677        }
1678
1679        // Create policy and determine if we should cache based on response-based mode
1680        let policy = self.options.create_cache_policy(
1681            &analysis.request_parts,
1682            &http_response.parts()?,
1683        );
1684
1685        let should_cache_response = self.options.should_cache_response(
1686            effective_cache_mode,
1687            &http_response,
1688            analysis.is_get_head,
1689            &policy,
1690        );
1691
1692        if should_cache_response {
1693            let cached_response = self
1694                .manager
1695                .put(analysis.cache_key, http_response, policy)
1696                .await?;
1697
1698            // Convert back to standard Response
1699            let response_parts = cached_response.parts()?;
1700            let mut response = Response::builder()
1701                .status(response_parts.status)
1702                .version(response_parts.version)
1703                .body(cached_response.body)?;
1704
1705            // Copy headers from the response parts
1706            *response.headers_mut() = response_parts.headers;
1707
1708            Ok(response)
1709        } else {
1710            // Don't cache, return original response
1711            let response = Response::from_parts(parts, body);
1712            Ok(response)
1713        }
1714    }
1715
1716    fn prepare_conditional_request(
1717        &self,
1718        parts: &mut request::Parts,
1719        _cached_response: &HttpResponse,
1720        policy: &CachePolicy,
1721    ) -> Result<()> {
1722        let before_req = policy.before_request(parts, SystemTime::now());
1723        if let BeforeRequest::Stale { request, .. } = before_req {
1724            parts.headers.extend(request.headers);
1725        }
1726        Ok(())
1727    }
1728
1729    async fn handle_not_modified(
1730        &self,
1731        mut cached_response: HttpResponse,
1732        fresh_parts: &response::Parts,
1733    ) -> Result<HttpResponse> {
1734        cached_response.update_headers(fresh_parts)?;
1735        if self.options.cache_status_headers {
1736            cached_response.cache_status(HitOrMiss::HIT);
1737            cached_response.cache_lookup_status(HitOrMiss::HIT);
1738        }
1739        Ok(cached_response)
1740    }
1741}
1742
1743#[allow(dead_code)]
1744#[cfg(test)]
1745mod test;