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//! ## Maximum TTL Control
105//!
106//! Set a maximum time-to-live for cached responses, particularly useful with `CacheMode::IgnoreRules`:
107//!
108//! ```rust
109//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
110//! use std::time::Duration;
111//!
112//! let manager = CACacheManager::new("./cache".into(), true);
113//!
114//! // Limit cache duration to 5 minutes regardless of server headers
115//! let options = HttpCacheOptions {
116//!     max_ttl: Some(Duration::from_secs(300)), // 5 minutes
117//!     ..Default::default()
118//! };
119//!
120//! let cache = HttpCache {
121//!     mode: CacheMode::IgnoreRules, // Ignore server cache-control headers
122//!     manager,
123//!     options,
124//! };
125//! ```
126//!
127//! ## Response-Based Cache Mode Override
128//!
129//! Override cache behavior based on the response you receive. This is useful for scenarios like
130//! forcing cache for successful responses even when headers say not to cache, or never caching
131//! error responses like rate limits:
132//!
133//! ```rust
134//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
135//! use std::sync::Arc;
136//!
137//! let manager = CACacheManager::new("./cache".into(), true);
138//!
139//! let options = HttpCacheOptions {
140//!     response_cache_mode_fn: Some(Arc::new(|_request_parts, response| {
141//!         match response.status {
142//!             // Force cache successful responses even if headers say not to cache
143//!             200..=299 => Some(CacheMode::ForceCache),
144//!             // Never cache rate-limited responses  
145//!             429 => Some(CacheMode::NoStore),
146//!             // Use default behavior for everything else
147//!             _ => None,
148//!         }
149//!     })),
150//!     ..Default::default()
151//! };
152//!
153//! let cache = HttpCache {
154//!     mode: CacheMode::Default,
155//!     manager,
156//!     options,
157//! };
158//! ```
159//!
160//! ## Content-Type Based Caching
161//!
162//! You can implement selective caching based on response content types using `response_cache_mode_fn`.
163//! This is useful when you only want to cache certain types of content:
164//!
165//! ```rust
166//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
167//! use std::sync::Arc;
168//!
169//! let manager = CACacheManager::new("./cache".into(), true);
170//!
171//! let options = HttpCacheOptions {
172//!     response_cache_mode_fn: Some(Arc::new(|_request_parts, response| {
173//!         // Check the Content-Type header to decide caching behavior
174//!         if let Some(content_type) = response.headers.get("content-type") {
175//!             match content_type.as_str() {
176//!                 // Cache JSON APIs aggressively
177//!                 ct if ct.starts_with("application/json") => Some(CacheMode::ForceCache),
178//!                 // Cache images with default rules
179//!                 ct if ct.starts_with("image/") => Some(CacheMode::Default),
180//!                 // Cache static assets
181//!                 ct if ct.starts_with("text/css") => Some(CacheMode::ForceCache),
182//!                 ct if ct.starts_with("application/javascript") => Some(CacheMode::ForceCache),
183//!                 // Don't cache HTML pages (dynamic content)
184//!                 ct if ct.starts_with("text/html") => Some(CacheMode::NoStore),
185//!                 // Don't cache unknown content types
186//!                 _ => Some(CacheMode::NoStore),
187//!             }
188//!         } else {
189//!             // No Content-Type header - don't cache
190//!             Some(CacheMode::NoStore)
191//!         }
192//!     })),
193//!     ..Default::default()
194//! };
195//!
196//! let cache = HttpCache {
197//!     mode: CacheMode::Default, // This gets overridden by response_cache_mode_fn
198//!     manager,
199//!     options,
200//! };
201//! ```
202//!
203//! ## Streaming Support
204//!
205//! For handling large responses without full buffering, use the `StreamingManager`:
206//!
207//! ```rust
208//! # #[cfg(feature = "streaming")]
209//! # {
210//! use http_cache::{StreamingBody, HttpStreamingCache, StreamingManager};
211//! use bytes::Bytes;
212//! use std::path::PathBuf;
213//! use http_body::Body;
214//! use http_body_util::Full;
215//!
216//! // Create a file-based streaming cache manager
217//! let manager = StreamingManager::new(PathBuf::from("./streaming-cache"));
218//!
219//! // StreamingBody can handle both buffered and streaming scenarios
220//! let body: StreamingBody<Full<Bytes>> = StreamingBody::buffered(Bytes::from("cached content"));
221//! println!("Body size: {:?}", body.size_hint());
222//! # }
223//! ```
224//!
225//! **Note**: Streaming support requires the `StreamingManager` with the `streaming` feature.
226//! Other cache managers (CACacheManager, MokaManager, QuickManager) do not support streaming
227//! and will buffer response bodies in memory.
228//!
229//! ## Features
230//!
231//! The following features are available. By default `manager-cacache` and `cacache-smol` are enabled.
232//!
233//! - `manager-cacache` (default): enable [cacache](https://github.com/zkat/cacache-rs),
234//! a disk cache, backend manager.
235//! - `cacache-smol` (default): enable [smol](https://github.com/smol-rs/smol) runtime support for cacache.
236//! - `http-headers-compat` (disabled): enable backwards compatibility for deserializing cached
237//! responses from older versions that used single-value headers. Enable this if you need to read
238//! cache entries created by older versions of http-cache.
239//! - `cacache-tokio` (disabled): enable [tokio](https://github.com/tokio-rs/tokio) runtime support for cacache.
240//! - `manager-moka` (disabled): enable [moka](https://github.com/moka-rs/moka),
241//! an in-memory cache, backend manager.
242//! - `streaming` (disabled): enable the `StreamingManager` for streaming cache support.
243//! - `streaming-tokio` (disabled): enable streaming with tokio runtime support.
244//! - `streaming-smol` (disabled): enable streaming with smol runtime support.
245//! - `with-http-types` (disabled): enable [http-types](https://github.com/http-rs/http-types)
246//! type conversion support
247//!
248//! **Note**: Only `StreamingManager` (via the `streaming` feature) provides streaming support.
249//! Other managers will buffer response bodies in memory even when used with `StreamingManager`.
250//!
251//! ## Integration
252//!
253//! This crate is designed to be used as a foundation for HTTP client and server middleware.
254//! See the companion crates for specific integrations:
255//!
256//! - [`http-cache-reqwest`](https://docs.rs/http-cache-reqwest) for reqwest client middleware
257//! - [`http-cache-surf`](https://docs.rs/http-cache-surf) for surf client middleware  
258//! - [`http-cache-tower`](https://docs.rs/http-cache-tower) for tower service middleware
259mod body;
260mod error;
261mod managers;
262
263#[cfg(feature = "streaming")]
264mod runtime;
265
266#[cfg(feature = "rate-limiting")]
267pub mod rate_limiting;
268
269use std::{
270    collections::HashMap,
271    convert::TryFrom,
272    fmt::{self, Debug},
273    str::FromStr,
274    sync::Arc,
275    time::{Duration, SystemTime},
276};
277
278use http::{
279    header::CACHE_CONTROL, request, response, HeaderValue, Response, StatusCode,
280};
281use http_cache_semantics::{AfterResponse, BeforeRequest, CachePolicy};
282use serde::{Deserialize, Deserializer, Serialize, Serializer};
283use url::Url;
284
285pub use body::StreamingBody;
286pub use error::{
287    BadHeader, BadRequest, BadVersion, BoxError, ClientStreamingError,
288    HttpCacheError, HttpCacheResult, Result, StreamingError,
289};
290
291#[cfg(feature = "manager-cacache")]
292pub use managers::cacache::CACacheManager;
293
294#[cfg(feature = "streaming")]
295pub use managers::streaming_cache::StreamingManager;
296
297#[cfg(feature = "manager-moka")]
298pub use managers::moka::MokaManager;
299
300#[cfg(feature = "rate-limiting")]
301pub use rate_limiting::{
302    CacheAwareRateLimiter, DirectRateLimiter, DomainRateLimiter,
303};
304
305#[cfg(feature = "rate-limiting")]
306pub use rate_limiting::Quota;
307
308// Exposing the moka cache for convenience, renaming to avoid naming conflicts
309#[cfg(feature = "manager-moka")]
310#[cfg_attr(docsrs, doc(cfg(feature = "manager-moka")))]
311pub use moka::future::{Cache as MokaCache, CacheBuilder as MokaCacheBuilder};
312
313// Custom headers used to indicate cache status (hit or miss)
314/// `x-cache` header: Value will be HIT if the response was served from cache, MISS if not
315pub const XCACHE: &str = "x-cache";
316/// `x-cache-lookup` header: Value will be HIT if a response existed in cache, MISS if not
317pub const XCACHELOOKUP: &str = "x-cache-lookup";
318/// `warning` header: HTTP warning header as per RFC 7234
319const WARNING: &str = "warning";
320
321/// Represents a basic cache status
322/// Used in the custom headers `x-cache` and `x-cache-lookup`
323#[derive(Debug, Copy, Clone)]
324pub enum HitOrMiss {
325    /// Yes, there was a hit
326    HIT,
327    /// No, there was no hit
328    MISS,
329}
330
331impl fmt::Display for HitOrMiss {
332    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
333        match self {
334            Self::HIT => write!(f, "HIT"),
335            Self::MISS => write!(f, "MISS"),
336        }
337    }
338}
339
340/// Represents an HTTP version
341#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
342#[non_exhaustive]
343pub enum HttpVersion {
344    /// HTTP Version 0.9
345    #[serde(rename = "HTTP/0.9")]
346    Http09,
347    /// HTTP Version 1.0
348    #[serde(rename = "HTTP/1.0")]
349    Http10,
350    /// HTTP Version 1.1
351    #[serde(rename = "HTTP/1.1")]
352    Http11,
353    /// HTTP Version 2.0
354    #[serde(rename = "HTTP/2.0")]
355    H2,
356    /// HTTP Version 3.0
357    #[serde(rename = "HTTP/3.0")]
358    H3,
359}
360
361impl fmt::Display for HttpVersion {
362    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
363        match *self {
364            HttpVersion::Http09 => write!(f, "HTTP/0.9"),
365            HttpVersion::Http10 => write!(f, "HTTP/1.0"),
366            HttpVersion::Http11 => write!(f, "HTTP/1.1"),
367            HttpVersion::H2 => write!(f, "HTTP/2.0"),
368            HttpVersion::H3 => write!(f, "HTTP/3.0"),
369        }
370    }
371}
372
373/// Extract a URL from HTTP request parts for cache key generation
374///
375/// This function reconstructs the full URL from the request parts, handling both
376/// HTTP and HTTPS schemes based on the connection type or explicit headers.
377fn extract_url_from_request_parts(parts: &request::Parts) -> Result<Url> {
378    // First check if the URI is already absolute
379    if let Some(_scheme) = parts.uri.scheme() {
380        // URI is absolute, use it directly
381        return Url::parse(&parts.uri.to_string())
382            .map_err(|_| -> BoxError { BadHeader.into() });
383    }
384
385    // Get the host header
386    let host = parts
387        .headers
388        .get("host")
389        .ok_or(BadHeader)?
390        .to_str()
391        .map_err(|_| BadHeader)?;
392
393    // Determine scheme based on host and headers
394    let scheme = determine_scheme(host, &parts.headers)?;
395
396    // Create base URL using url crate's builder pattern for safety
397    let mut base_url = Url::parse(&format!("{}://{}/", &scheme, host))
398        .map_err(|_| -> BoxError { BadHeader.into() })?;
399
400    // Set the path and query from the URI
401    if let Some(path_and_query) = parts.uri.path_and_query() {
402        base_url.set_path(path_and_query.path());
403        if let Some(query) = path_and_query.query() {
404            base_url.set_query(Some(query));
405        }
406    }
407
408    Ok(base_url)
409}
410
411/// Determine the appropriate scheme for URL construction
412fn determine_scheme(host: &str, headers: &http::HeaderMap) -> Result<String> {
413    // Check for explicit protocol forwarding header first
414    if let Some(forwarded_proto) = headers.get("x-forwarded-proto") {
415        let proto = forwarded_proto.to_str().map_err(|_| BadHeader)?;
416        return match proto {
417            "http" | "https" => Ok(proto.to_string()),
418            _ => Ok("https".to_string()), // Default to secure for unknown protocols
419        };
420    }
421
422    // Check if this looks like a local development host
423    if host.starts_with("localhost") || host.starts_with("127.0.0.1") {
424        Ok("http".to_string())
425    } else {
426        Ok("https".to_string()) // Default to secure for all other hosts
427    }
428}
429
430/// Represents HTTP headers in either legacy or modern format
431#[derive(Debug, Clone)]
432pub enum HttpHeaders {
433    /// Modern header representation - allows multiple values per key
434    Modern(HashMap<String, Vec<String>>),
435    /// Legacy header representation - kept for backward compatibility with deserialization
436    #[cfg(feature = "http-headers-compat")]
437    Legacy(HashMap<String, String>),
438}
439
440// Serialize as enum (needed for bincode to match deserialization)
441impl Serialize for HttpHeaders {
442    fn serialize<S>(
443        &self,
444        serializer: S,
445    ) -> std::result::Result<S::Ok, S::Error>
446    where
447        S: Serializer,
448    {
449        // Serialize the enum properly with variant tags
450        // This matches how RawHttpHeaders deserializes
451        match self {
452            HttpHeaders::Modern(modern) => serializer
453                .serialize_newtype_variant("HttpHeaders", 0, "Modern", modern),
454            #[cfg(feature = "http-headers-compat")]
455            HttpHeaders::Legacy(legacy) => serializer
456                .serialize_newtype_variant("HttpHeaders", 1, "Legacy", legacy),
457        }
458    }
459}
460
461// Deserialize: Need to handle enum variant properly for bincode
462impl<'de> Deserialize<'de> for HttpHeaders {
463    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
464    where
465        D: Deserializer<'de>,
466    {
467        // Bincode serializes enums with a variant index
468        // We need to deserialize it properly as an enum
469        #[cfg(feature = "http-headers-compat")]
470        {
471            #[derive(Deserialize)]
472            enum RawHttpHeaders {
473                Modern(HashMap<String, Vec<String>>),
474                Legacy(HashMap<String, String>),
475            }
476
477            match RawHttpHeaders::deserialize(deserializer)? {
478                RawHttpHeaders::Modern(m) => Ok(HttpHeaders::Modern(m)),
479                RawHttpHeaders::Legacy(l) => Ok(HttpHeaders::Legacy(l)),
480            }
481        }
482
483        #[cfg(not(feature = "http-headers-compat"))]
484        {
485            #[derive(Deserialize)]
486            enum RawHttpHeaders {
487                Modern(HashMap<String, Vec<String>>),
488            }
489
490            match RawHttpHeaders::deserialize(deserializer)? {
491                RawHttpHeaders::Modern(m) => Ok(HttpHeaders::Modern(m)),
492            }
493        }
494    }
495}
496
497impl HttpHeaders {
498    /// Creates a new empty HttpHeaders in modern format
499    pub fn new() -> Self {
500        HttpHeaders::Modern(HashMap::new())
501    }
502
503    /// Inserts a header key-value pair, replacing any existing values for that key
504    pub fn insert(&mut self, key: String, value: String) {
505        match self {
506            #[cfg(feature = "http-headers-compat")]
507            HttpHeaders::Legacy(legacy) => {
508                legacy.insert(key, value);
509            }
510            HttpHeaders::Modern(modern) => {
511                // Replace existing values with a new single-element vec
512                modern.insert(key, vec![value]);
513            }
514        }
515    }
516
517    /// Retrieves the first value for a given header key
518    pub fn get(&self, key: &str) -> Option<&String> {
519        match self {
520            #[cfg(feature = "http-headers-compat")]
521            HttpHeaders::Legacy(legacy) => legacy.get(key),
522            HttpHeaders::Modern(modern) => {
523                modern.get(key).and_then(|vals| vals.first())
524            }
525        }
526    }
527
528    /// Removes a header key and its associated values
529    pub fn remove(&mut self, key: &str) {
530        match self {
531            #[cfg(feature = "http-headers-compat")]
532            HttpHeaders::Legacy(legacy) => {
533                legacy.remove(key);
534            }
535            HttpHeaders::Modern(modern) => {
536                modern.remove(key);
537            }
538        }
539    }
540
541    /// Checks if a header key exists
542    pub fn contains_key(&self, key: &str) -> bool {
543        match self {
544            #[cfg(feature = "http-headers-compat")]
545            HttpHeaders::Legacy(legacy) => legacy.contains_key(key),
546            HttpHeaders::Modern(modern) => modern.contains_key(key),
547        }
548    }
549
550    /// Returns an iterator over the header key-value pairs
551    pub fn iter(&self) -> HttpHeadersIterator<'_> {
552        match self {
553            #[cfg(feature = "http-headers-compat")]
554            HttpHeaders::Legacy(legacy) => {
555                HttpHeadersIterator { inner: legacy.iter().collect(), index: 0 }
556            }
557            HttpHeaders::Modern(modern) => HttpHeadersIterator {
558                inner: modern
559                    .iter()
560                    .flat_map(|(k, vals)| vals.iter().map(move |v| (k, v)))
561                    .collect(),
562                index: 0,
563            },
564        }
565    }
566}
567
568impl From<&http::HeaderMap> for HttpHeaders {
569    fn from(headers: &http::HeaderMap) -> Self {
570        let mut modern_headers = HashMap::new();
571
572        // Collect all unique header names first
573        let header_names: std::collections::HashSet<_> =
574            headers.keys().collect();
575
576        // For each header name, collect ALL values
577        for name in header_names {
578            let values: Vec<String> = headers
579                .get_all(name)
580                .iter()
581                .filter_map(|v| v.to_str().ok())
582                .map(|s| s.to_string())
583                .collect();
584
585            if !values.is_empty() {
586                modern_headers.insert(name.to_string(), values);
587            }
588        }
589
590        HttpHeaders::Modern(modern_headers)
591    }
592}
593
594impl From<HttpHeaders> for HashMap<String, Vec<String>> {
595    fn from(headers: HttpHeaders) -> Self {
596        match headers {
597            #[cfg(feature = "http-headers-compat")]
598            HttpHeaders::Legacy(legacy) => {
599                legacy.into_iter().map(|(k, v)| (k, vec![v])).collect()
600            }
601            HttpHeaders::Modern(modern) => modern,
602        }
603    }
604}
605
606impl Default for HttpHeaders {
607    fn default() -> Self {
608        HttpHeaders::new()
609    }
610}
611
612impl IntoIterator for HttpHeaders {
613    type Item = (String, String);
614    type IntoIter = HttpHeadersIntoIterator;
615
616    fn into_iter(self) -> Self::IntoIter {
617        HttpHeadersIntoIterator {
618            inner: match self {
619                #[cfg(feature = "http-headers-compat")]
620                HttpHeaders::Legacy(legacy) => legacy.into_iter().collect(),
621                HttpHeaders::Modern(modern) => modern
622                    .into_iter()
623                    .flat_map(|(k, vals)| {
624                        vals.into_iter().map(move |v| (k.clone(), v))
625                    })
626                    .collect(),
627            },
628            index: 0,
629        }
630    }
631}
632
633/// Iterator for HttpHeaders
634#[derive(Debug)]
635pub struct HttpHeadersIntoIterator {
636    inner: Vec<(String, String)>,
637    index: usize,
638}
639
640impl Iterator for HttpHeadersIntoIterator {
641    type Item = (String, String);
642
643    fn next(&mut self) -> Option<Self::Item> {
644        if self.index < self.inner.len() {
645            let item = self.inner[self.index].clone();
646            self.index += 1;
647            Some(item)
648        } else {
649            None
650        }
651    }
652}
653
654impl<'a> IntoIterator for &'a HttpHeaders {
655    type Item = (&'a String, &'a String);
656    type IntoIter = HttpHeadersIterator<'a>;
657
658    fn into_iter(self) -> Self::IntoIter {
659        self.iter()
660    }
661}
662
663/// Iterator for HttpHeaders references
664#[derive(Debug)]
665pub struct HttpHeadersIterator<'a> {
666    inner: Vec<(&'a String, &'a String)>,
667    index: usize,
668}
669
670impl<'a> Iterator for HttpHeadersIterator<'a> {
671    type Item = (&'a String, &'a String);
672
673    fn next(&mut self) -> Option<Self::Item> {
674        if self.index < self.inner.len() {
675            let item = self.inner[self.index];
676            self.index += 1;
677            Some(item)
678        } else {
679            None
680        }
681    }
682}
683
684/// A basic generic type that represents an HTTP response
685#[derive(Debug, Clone, Deserialize, Serialize)]
686pub struct HttpResponse {
687    /// HTTP response body
688    pub body: Vec<u8>,
689    /// HTTP response headers
690    pub headers: HttpHeaders,
691    /// HTTP response status code
692    pub status: u16,
693    /// HTTP response url
694    pub url: Url,
695    /// HTTP response version
696    pub version: HttpVersion,
697    /// Metadata
698    pub metadata: Option<Vec<u8>>,
699}
700
701impl HttpResponse {
702    /// Returns `http::response::Parts`
703    pub fn parts(&self) -> Result<response::Parts> {
704        let mut converted =
705            response::Builder::new().status(self.status).body(())?;
706        {
707            let headers = converted.headers_mut();
708            for header in &self.headers {
709                headers.insert(
710                    http::header::HeaderName::from_str(header.0.as_str())?,
711                    HeaderValue::from_str(header.1.as_str())?,
712                );
713            }
714        }
715        Ok(converted.into_parts().0)
716    }
717
718    /// Returns the status code of the warning header if present
719    #[must_use]
720    fn warning_code(&self) -> Option<usize> {
721        self.headers.get(WARNING).and_then(|hdr| {
722            hdr.as_str().chars().take(3).collect::<String>().parse().ok()
723        })
724    }
725
726    /// Adds a warning header to a response
727    fn add_warning(&mut self, url: &Url, code: usize, message: &str) {
728        // warning    = "warning" ":" 1#warning-value
729        // warning-value = warn-code SP warn-agent SP warn-text [SP warn-date]
730        // warn-code  = 3DIGIT
731        // warn-agent = ( host [ ":" port ] ) | pseudonym
732        //                 ; the name or pseudonym of the server adding
733        //                 ; the warning header, for use in debugging
734        // warn-text  = quoted-string
735        // warn-date  = <"> HTTP-date <">
736        // (https://tools.ietf.org/html/rfc2616#section-14.46)
737        let host = url
738            .host()
739            .map(|h| h.to_string())
740            .unwrap_or_else(|| "unknown".to_string());
741        // Escape message to prevent header injection and ensure valid HTTP format
742        let escaped_message =
743            message.replace('"', "'").replace(['\n', '\r'], " ");
744        self.headers.insert(
745            WARNING.to_string(),
746            format!(
747                "{} {} \"{}\" \"{}\"",
748                code,
749                host,
750                escaped_message,
751                httpdate::fmt_http_date(SystemTime::now())
752            ),
753        );
754    }
755
756    /// Removes a warning header from a response
757    fn remove_warning(&mut self) {
758        self.headers.remove(WARNING);
759    }
760
761    /// Update the headers from `http::response::Parts`
762    pub fn update_headers(&mut self, parts: &response::Parts) -> Result<()> {
763        for header in parts.headers.iter() {
764            self.headers.insert(
765                header.0.as_str().to_string(),
766                header.1.to_str()?.to_string(),
767            );
768        }
769        Ok(())
770    }
771
772    /// Checks if the Cache-Control header contains the must-revalidate directive
773    #[must_use]
774    fn must_revalidate(&self) -> bool {
775        self.headers.get(CACHE_CONTROL.as_str()).is_some_and(|val| {
776            val.as_str().to_lowercase().contains("must-revalidate")
777        })
778    }
779
780    /// Adds the custom `x-cache` header to the response
781    pub fn cache_status(&mut self, hit_or_miss: HitOrMiss) {
782        self.headers.insert(XCACHE.to_string(), hit_or_miss.to_string());
783    }
784
785    /// Adds the custom `x-cache-lookup` header to the response
786    pub fn cache_lookup_status(&mut self, hit_or_miss: HitOrMiss) {
787        self.headers.insert(XCACHELOOKUP.to_string(), hit_or_miss.to_string());
788    }
789}
790
791/// A trait providing methods for storing, reading, and removing cache records.
792#[async_trait::async_trait]
793pub trait CacheManager: Send + Sync + 'static {
794    /// Attempts to pull a cached response and related policy from cache.
795    async fn get(
796        &self,
797        cache_key: &str,
798    ) -> Result<Option<(HttpResponse, CachePolicy)>>;
799    /// Attempts to cache a response and related policy.
800    async fn put(
801        &self,
802        cache_key: String,
803        res: HttpResponse,
804        policy: CachePolicy,
805    ) -> Result<HttpResponse>;
806    /// Attempts to remove a record from cache.
807    async fn delete(&self, cache_key: &str) -> Result<()>;
808}
809
810/// A streaming cache manager that supports streaming request/response bodies
811/// without buffering them in memory. This is ideal for large responses.
812#[async_trait::async_trait]
813pub trait StreamingCacheManager: Send + Sync + 'static {
814    /// The body type used by this cache manager
815    type Body: http_body::Body + Send + 'static;
816
817    /// Attempts to pull a cached response and related policy from cache with streaming body.
818    async fn get(
819        &self,
820        cache_key: &str,
821    ) -> Result<Option<(Response<Self::Body>, CachePolicy)>>
822    where
823        <Self::Body as http_body::Body>::Data: Send,
824        <Self::Body as http_body::Body>::Error:
825            Into<StreamingError> + Send + Sync + 'static;
826
827    /// Attempts to cache a response with a streaming body and related policy.
828    async fn put<B>(
829        &self,
830        cache_key: String,
831        response: Response<B>,
832        policy: CachePolicy,
833        request_url: Url,
834        metadata: Option<Vec<u8>>,
835    ) -> Result<Response<Self::Body>>
836    where
837        B: http_body::Body + Send + 'static,
838        B::Data: Send,
839        B::Error: Into<StreamingError>,
840        <Self::Body as http_body::Body>::Data: Send,
841        <Self::Body as http_body::Body>::Error:
842            Into<StreamingError> + Send + Sync + 'static;
843
844    /// Converts a generic body to the manager's body type for non-cacheable responses.
845    /// This is called when a response should not be cached but still needs to be returned
846    /// with the correct body type.
847    async fn convert_body<B>(
848        &self,
849        response: Response<B>,
850    ) -> Result<Response<Self::Body>>
851    where
852        B: http_body::Body + Send + 'static,
853        B::Data: Send,
854        B::Error: Into<StreamingError>,
855        <Self::Body as http_body::Body>::Data: Send,
856        <Self::Body as http_body::Body>::Error:
857            Into<StreamingError> + Send + Sync + 'static;
858
859    /// Attempts to remove a record from cache.
860    async fn delete(&self, cache_key: &str) -> Result<()>;
861
862    /// Convert the manager's body type to a reqwest-compatible bytes stream.
863    /// This enables efficient streaming without collecting the entire body.
864    #[cfg(feature = "streaming")]
865    fn body_to_bytes_stream(
866        body: Self::Body,
867    ) -> impl futures_util::Stream<
868        Item = std::result::Result<
869            bytes::Bytes,
870            Box<dyn std::error::Error + Send + Sync>,
871        >,
872    > + Send
873    where
874        <Self::Body as http_body::Body>::Data: Send,
875        <Self::Body as http_body::Body>::Error: Send + Sync + 'static;
876}
877
878/// Describes the functionality required for interfacing with HTTP client middleware
879#[async_trait::async_trait]
880pub trait Middleware: Send {
881    /// Allows the cache mode to be overridden.
882    ///
883    /// This overrides any cache mode set in the configuration, including cache_mode_fn.
884    fn overridden_cache_mode(&self) -> Option<CacheMode> {
885        None
886    }
887    /// Determines if the request method is either GET or HEAD
888    fn is_method_get_head(&self) -> bool;
889    /// Returns a new cache policy with default options
890    fn policy(&self, response: &HttpResponse) -> Result<CachePolicy>;
891    /// Returns a new cache policy with custom options
892    fn policy_with_options(
893        &self,
894        response: &HttpResponse,
895        options: CacheOptions,
896    ) -> Result<CachePolicy>;
897    /// Attempts to update the request headers with the passed `http::request::Parts`
898    fn update_headers(&mut self, parts: &request::Parts) -> Result<()>;
899    /// Attempts to force the "no-cache" directive on the request
900    fn force_no_cache(&mut self) -> Result<()>;
901    /// Attempts to construct `http::request::Parts` from the request
902    fn parts(&self) -> Result<request::Parts>;
903    /// Attempts to determine the requested url
904    fn url(&self) -> Result<Url>;
905    /// Attempts to determine the request method
906    fn method(&self) -> Result<String>;
907    /// Attempts to fetch an upstream resource and return an [`HttpResponse`]
908    async fn remote_fetch(&mut self) -> Result<HttpResponse>;
909}
910
911/// An interface for HTTP caching that works with composable middleware patterns
912/// like Tower. This trait separates the concerns of request analysis, cache lookup,
913/// and response processing into discrete steps.
914pub trait HttpCacheInterface<B = Vec<u8>>: Send + Sync {
915    /// Analyze a request to determine cache behavior
916    fn analyze_request(
917        &self,
918        parts: &request::Parts,
919        mode_override: Option<CacheMode>,
920    ) -> Result<CacheAnalysis>;
921
922    /// Look up a cached response for the given cache key
923    #[allow(async_fn_in_trait)]
924    async fn lookup_cached_response(
925        &self,
926        key: &str,
927    ) -> Result<Option<(HttpResponse, CachePolicy)>>;
928
929    /// Process a fresh response from upstream and potentially cache it
930    #[allow(async_fn_in_trait)]
931    async fn process_response(
932        &self,
933        analysis: CacheAnalysis,
934        response: Response<B>,
935        metadata: Option<Vec<u8>>,
936    ) -> Result<Response<B>>;
937
938    /// Update request headers for conditional requests (e.g., If-None-Match)
939    fn prepare_conditional_request(
940        &self,
941        parts: &mut request::Parts,
942        cached_response: &HttpResponse,
943        policy: &CachePolicy,
944    ) -> Result<()>;
945
946    /// Handle a 304 Not Modified response by returning the cached response
947    #[allow(async_fn_in_trait)]
948    async fn handle_not_modified(
949        &self,
950        cached_response: HttpResponse,
951        fresh_parts: &response::Parts,
952    ) -> Result<HttpResponse>;
953}
954
955/// Streaming version of the HTTP cache interface that supports streaming request/response bodies
956/// without buffering them in memory. This is ideal for large responses or when memory usage
957/// is a concern.
958pub trait HttpCacheStreamInterface: Send + Sync {
959    /// The body type used by this cache implementation
960    type Body: http_body::Body + Send + 'static;
961
962    /// Analyze a request to determine cache behavior
963    fn analyze_request(
964        &self,
965        parts: &request::Parts,
966        mode_override: Option<CacheMode>,
967    ) -> Result<CacheAnalysis>;
968
969    /// Look up a cached response for the given cache key, returning a streaming body
970    #[allow(async_fn_in_trait)]
971    async fn lookup_cached_response(
972        &self,
973        key: &str,
974    ) -> Result<Option<(Response<Self::Body>, CachePolicy)>>
975    where
976        <Self::Body as http_body::Body>::Data: Send,
977        <Self::Body as http_body::Body>::Error:
978            Into<StreamingError> + Send + Sync + 'static;
979
980    /// Process a fresh response from upstream and potentially cache it with streaming support
981    #[allow(async_fn_in_trait)]
982    async fn process_response<B>(
983        &self,
984        analysis: CacheAnalysis,
985        response: Response<B>,
986        metadata: Option<Vec<u8>>,
987    ) -> Result<Response<Self::Body>>
988    where
989        B: http_body::Body + Send + 'static,
990        B::Data: Send,
991        B::Error: Into<StreamingError>,
992        <Self::Body as http_body::Body>::Data: Send,
993        <Self::Body as http_body::Body>::Error:
994            Into<StreamingError> + Send + Sync + 'static;
995
996    /// Update request headers for conditional requests (e.g., If-None-Match)
997    fn prepare_conditional_request(
998        &self,
999        parts: &mut request::Parts,
1000        cached_response: &Response<Self::Body>,
1001        policy: &CachePolicy,
1002    ) -> Result<()>;
1003
1004    /// Handle a 304 Not Modified response by returning the cached response
1005    #[allow(async_fn_in_trait)]
1006    async fn handle_not_modified(
1007        &self,
1008        cached_response: Response<Self::Body>,
1009        fresh_parts: &response::Parts,
1010    ) -> Result<Response<Self::Body>>
1011    where
1012        <Self::Body as http_body::Body>::Data: Send,
1013        <Self::Body as http_body::Body>::Error:
1014            Into<StreamingError> + Send + Sync + 'static;
1015}
1016
1017/// Analysis result for a request, containing cache key and caching decisions
1018#[derive(Debug, Clone)]
1019pub struct CacheAnalysis {
1020    /// The cache key for this request
1021    pub cache_key: String,
1022    /// Whether this request should be cached
1023    pub should_cache: bool,
1024    /// The effective cache mode for this request
1025    pub cache_mode: CacheMode,
1026    /// Keys to bust from cache before processing
1027    pub cache_bust_keys: Vec<String>,
1028    /// The request parts for policy creation
1029    pub request_parts: request::Parts,
1030    /// Whether this is a GET or HEAD request
1031    pub is_get_head: bool,
1032}
1033
1034/// Cache mode determines how the HTTP cache behaves for requests.
1035///
1036/// These modes are similar to [make-fetch-happen cache options](https://github.com/npm/make-fetch-happen#--optscache)
1037/// and provide fine-grained control over caching behavior.
1038///
1039/// # Examples
1040///
1041/// ```rust
1042/// use http_cache::{CacheMode, HttpCache, CACacheManager, HttpCacheOptions};
1043///
1044/// let manager = CACacheManager::new("./cache".into(), true);
1045///
1046/// // Use different cache modes for different scenarios
1047/// let default_cache = HttpCache {
1048///     mode: CacheMode::Default,        // Standard HTTP caching rules
1049///     manager: manager.clone(),
1050///     options: HttpCacheOptions::default(),
1051/// };
1052///
1053/// let force_cache = HttpCache {
1054///     mode: CacheMode::ForceCache,     // Cache everything, ignore staleness
1055///     manager: manager.clone(),
1056///     options: HttpCacheOptions::default(),
1057/// };
1058///
1059/// let no_cache = HttpCache {
1060///     mode: CacheMode::NoStore,        // Never cache anything
1061///     manager,
1062///     options: HttpCacheOptions::default(),
1063/// };
1064/// ```
1065#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
1066pub enum CacheMode {
1067    /// Standard HTTP caching behavior (recommended for most use cases).
1068    ///
1069    /// This mode:
1070    /// - Checks the cache for fresh responses and uses them
1071    /// - Makes conditional requests for stale responses (revalidation)
1072    /// - Makes normal requests when no cached response exists
1073    /// - Updates the cache with new responses
1074    /// - Falls back to stale responses if revalidation fails
1075    ///
1076    /// This is the most common mode and follows HTTP caching standards closely.
1077    #[default]
1078    Default,
1079
1080    /// Completely bypasses the cache.
1081    ///
1082    /// This mode:
1083    /// - Never reads from the cache
1084    /// - Never writes to the cache
1085    /// - Always makes fresh network requests
1086    ///
1087    /// Use this when you need to ensure every request goes to the origin server.
1088    NoStore,
1089
1090    /// Bypasses cache on request but updates cache with response.
1091    ///
1092    /// This mode:
1093    /// - Ignores any cached responses
1094    /// - Always makes a fresh network request
1095    /// - Updates the cache with the response
1096    ///
1097    /// Equivalent to a "hard refresh" - useful when you know the cache is stale.
1098    Reload,
1099
1100    /// Always revalidates cached responses.
1101    ///
1102    /// This mode:
1103    /// - Makes conditional requests if a cached response exists
1104    /// - Makes normal requests if no cached response exists
1105    /// - Updates the cache with responses
1106    ///
1107    /// Use this when you want to ensure content freshness while still benefiting
1108    /// from conditional requests (304 Not Modified responses).
1109    NoCache,
1110
1111    /// Uses cached responses regardless of staleness.
1112    ///
1113    /// This mode:
1114    /// - Uses any cached response, even if stale
1115    /// - Makes network requests only when no cached response exists
1116    /// - Updates the cache with new responses
1117    ///
1118    /// Useful for offline scenarios or when performance is more important than freshness.
1119    ForceCache,
1120
1121    /// Only serves from cache, never makes network requests.
1122    ///
1123    /// This mode:
1124    /// - Uses any cached response, even if stale
1125    /// - Returns an error if no cached response exists
1126    /// - Never makes network requests
1127    ///
1128    /// Use this for offline-only scenarios or when you want to guarantee
1129    /// no network traffic.
1130    OnlyIfCached,
1131
1132    /// Ignores HTTP caching rules and caches everything.
1133    ///
1134    /// This mode:
1135    /// - Caches all 200 responses regardless of cache-control headers
1136    /// - Uses cached responses regardless of staleness
1137    /// - Makes network requests when no cached response exists
1138    ///
1139    /// Use this when you want aggressive caching and don't want to respect
1140    /// server cache directives.
1141    IgnoreRules,
1142}
1143
1144impl TryFrom<http::Version> for HttpVersion {
1145    type Error = BoxError;
1146
1147    fn try_from(value: http::Version) -> Result<Self> {
1148        Ok(match value {
1149            http::Version::HTTP_09 => Self::Http09,
1150            http::Version::HTTP_10 => Self::Http10,
1151            http::Version::HTTP_11 => Self::Http11,
1152            http::Version::HTTP_2 => Self::H2,
1153            http::Version::HTTP_3 => Self::H3,
1154            _ => return Err(Box::new(BadVersion)),
1155        })
1156    }
1157}
1158
1159impl From<HttpVersion> for http::Version {
1160    fn from(value: HttpVersion) -> Self {
1161        match value {
1162            HttpVersion::Http09 => Self::HTTP_09,
1163            HttpVersion::Http10 => Self::HTTP_10,
1164            HttpVersion::Http11 => Self::HTTP_11,
1165            HttpVersion::H2 => Self::HTTP_2,
1166            HttpVersion::H3 => Self::HTTP_3,
1167        }
1168    }
1169}
1170
1171#[cfg(feature = "http-types")]
1172impl TryFrom<http_types::Version> for HttpVersion {
1173    type Error = BoxError;
1174
1175    fn try_from(value: http_types::Version) -> Result<Self> {
1176        Ok(match value {
1177            http_types::Version::Http0_9 => Self::Http09,
1178            http_types::Version::Http1_0 => Self::Http10,
1179            http_types::Version::Http1_1 => Self::Http11,
1180            http_types::Version::Http2_0 => Self::H2,
1181            http_types::Version::Http3_0 => Self::H3,
1182            _ => return Err(Box::new(BadVersion)),
1183        })
1184    }
1185}
1186
1187#[cfg(feature = "http-types")]
1188impl From<HttpVersion> for http_types::Version {
1189    fn from(value: HttpVersion) -> Self {
1190        match value {
1191            HttpVersion::Http09 => Self::Http0_9,
1192            HttpVersion::Http10 => Self::Http1_0,
1193            HttpVersion::Http11 => Self::Http1_1,
1194            HttpVersion::H2 => Self::Http2_0,
1195            HttpVersion::H3 => Self::Http3_0,
1196        }
1197    }
1198}
1199
1200/// Options struct provided by
1201/// [`http-cache-semantics`](https://github.com/kornelski/rusty-http-cache-semantics).
1202pub use http_cache_semantics::CacheOptions;
1203
1204/// A closure that takes [`http::request::Parts`] and returns a [`String`].
1205/// By default, the cache key is a combination of the request method and uri with a colon in between.
1206pub type CacheKey = Arc<dyn Fn(&request::Parts) -> String + Send + Sync>;
1207
1208/// A closure that takes [`http::request::Parts`] and returns a [`CacheMode`]
1209pub type CacheModeFn = Arc<dyn Fn(&request::Parts) -> CacheMode + Send + Sync>;
1210
1211/// A closure that takes [`http::request::Parts`], [`HttpResponse`] and returns a [`CacheMode`] to override caching behavior based on the response
1212pub type ResponseCacheModeFn = Arc<
1213    dyn Fn(&request::Parts, &HttpResponse) -> Option<CacheMode> + Send + Sync,
1214>;
1215
1216/// 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.
1217/// An empty vector means that no cache busting will be performed.
1218pub type CacheBust = Arc<
1219    dyn Fn(&request::Parts, &Option<CacheKey>, &str) -> Vec<String>
1220        + Send
1221        + Sync,
1222>;
1223
1224/// Type alias for metadata stored alongside cached responses.
1225/// Users are responsible for serialization/deserialization of this data.
1226pub type HttpCacheMetadata = Vec<u8>;
1227
1228/// A closure that takes [`http::request::Parts`] and [`http::response::Parts`] and returns optional metadata to store with the cached response.
1229/// This allows middleware to compute and store additional information alongside cached responses.
1230pub type MetadataProvider = Arc<
1231    dyn Fn(&request::Parts, &response::Parts) -> Option<HttpCacheMetadata>
1232        + Send
1233        + Sync,
1234>;
1235
1236/// A closure that takes a mutable reference to [`HttpResponse`] and modifies it before caching.
1237pub type ModifyResponse = Arc<dyn Fn(&mut HttpResponse) + Send + Sync>;
1238
1239/// Configuration options for customizing HTTP cache behavior on a per-request basis.
1240///
1241/// This struct allows you to override default caching behavior for individual requests
1242/// by providing custom cache options, cache keys, cache modes, and cache busting logic.
1243///
1244/// # Examples
1245///
1246/// ## Basic Custom Cache Key
1247/// ```rust
1248/// use http_cache::{HttpCacheOptions, CacheKey};
1249/// use http::request::Parts;
1250/// use std::sync::Arc;
1251///
1252/// let options = HttpCacheOptions {
1253///     cache_key: Some(Arc::new(|parts: &Parts| {
1254///         format!("custom:{}:{}", parts.method, parts.uri.path())
1255///     })),
1256///     ..Default::default()
1257/// };
1258/// ```
1259///
1260/// ## Custom Cache Mode per Request
1261/// ```rust
1262/// use http_cache::{HttpCacheOptions, CacheMode, CacheModeFn};
1263/// use http::request::Parts;
1264/// use std::sync::Arc;
1265///
1266/// let options = HttpCacheOptions {
1267///     cache_mode_fn: Some(Arc::new(|parts: &Parts| {
1268///         if parts.headers.contains_key("x-no-cache") {
1269///             CacheMode::NoStore
1270///         } else {
1271///             CacheMode::Default
1272///         }
1273///     })),
1274///     ..Default::default()
1275/// };
1276/// ```
1277///
1278/// ## Response-Based Cache Mode Override
1279/// ```rust
1280/// use http_cache::{HttpCacheOptions, ResponseCacheModeFn, CacheMode};
1281/// use http::request::Parts;
1282/// use http_cache::HttpResponse;
1283/// use std::sync::Arc;
1284///
1285/// let options = HttpCacheOptions {
1286///     response_cache_mode_fn: Some(Arc::new(|_parts: &Parts, response: &HttpResponse| {
1287///         // Force cache 2xx responses even if headers say not to cache
1288///         if response.status >= 200 && response.status < 300 {
1289///             Some(CacheMode::ForceCache)
1290///         } else if response.status == 429 { // Rate limited
1291///             Some(CacheMode::NoStore) // Don't cache rate limit responses
1292///         } else {
1293///             None // Use default behavior
1294///         }
1295///     })),
1296///     ..Default::default()
1297/// };
1298/// ```
1299///
1300/// ## Content-Type Based Cache Mode Override
1301/// ```rust
1302/// use http_cache::{HttpCacheOptions, ResponseCacheModeFn, CacheMode};
1303/// use http::request::Parts;
1304/// use http_cache::HttpResponse;
1305/// use std::sync::Arc;
1306///
1307/// let options = HttpCacheOptions {
1308///     response_cache_mode_fn: Some(Arc::new(|_parts: &Parts, response: &HttpResponse| {
1309///         // Cache different content types with different strategies
1310///         if let Some(content_type) = response.headers.get("content-type") {
1311///             match content_type.as_str() {
1312///                 ct if ct.starts_with("application/json") => Some(CacheMode::ForceCache),
1313///                 ct if ct.starts_with("image/") => Some(CacheMode::Default),
1314///                 ct if ct.starts_with("text/html") => Some(CacheMode::NoStore),
1315///                 _ => None, // Use default behavior for other types
1316///             }
1317///         } else {
1318///             Some(CacheMode::NoStore) // No content-type = don't cache
1319///         }
1320///     })),
1321///     ..Default::default()
1322/// };
1323/// ```
1324///
1325/// ## Cache Busting for Related Resources
1326/// ```rust
1327/// use http_cache::{HttpCacheOptions, CacheBust, CacheKey};
1328/// use http::request::Parts;
1329/// use std::sync::Arc;
1330///
1331/// let options = HttpCacheOptions {
1332///     cache_bust: Some(Arc::new(|parts: &Parts, _cache_key: &Option<CacheKey>, _uri: &str| {
1333///         if parts.method == "POST" && parts.uri.path().starts_with("/api/users") {
1334///             vec![
1335///                 "GET:/api/users".to_string(),
1336///                 "GET:/api/users/list".to_string(),
1337///             ]
1338///         } else {
1339///             vec![]
1340///         }
1341///     })),
1342///     ..Default::default()
1343/// };
1344/// ```
1345///
1346/// ## Storing Metadata with Cached Responses
1347/// ```rust
1348/// use http_cache::{HttpCacheOptions, MetadataProvider};
1349/// use http::{request, response};
1350/// use std::sync::Arc;
1351///
1352/// let options = HttpCacheOptions {
1353///     metadata_provider: Some(Arc::new(|request_parts: &request::Parts, response_parts: &response::Parts| {
1354///         // Store computed information with the cached response
1355///         let content_type = response_parts
1356///             .headers
1357///             .get("content-type")
1358///             .and_then(|v| v.to_str().ok())
1359///             .unwrap_or("unknown");
1360///
1361///         // Return serialized metadata (users handle serialization)
1362///         Some(format!("path={};content-type={}", request_parts.uri.path(), content_type).into_bytes())
1363///     })),
1364///     ..Default::default()
1365/// };
1366/// ```
1367#[derive(Clone)]
1368pub struct HttpCacheOptions {
1369    /// Override the default cache options.
1370    pub cache_options: Option<CacheOptions>,
1371    /// Override the default cache key generator.
1372    pub cache_key: Option<CacheKey>,
1373    /// Override the default cache mode.
1374    pub cache_mode_fn: Option<CacheModeFn>,
1375    /// Override cache behavior based on the response received.
1376    /// This function is called after receiving a response and can override
1377    /// the cache mode for that specific response. Returning `None` means
1378    /// use the default cache mode. This allows fine-grained control over
1379    /// caching behavior based on response status, headers, or content.
1380    pub response_cache_mode_fn: Option<ResponseCacheModeFn>,
1381    /// Bust the caches of the returned keys.
1382    pub cache_bust: Option<CacheBust>,
1383    /// Modifies the response before storing it in the cache.
1384    pub modify_response: Option<ModifyResponse>,
1385    /// Determines if the cache status headers should be added to the response.
1386    pub cache_status_headers: bool,
1387    /// Maximum time-to-live for cached responses.
1388    /// When set, this overrides any longer cache durations specified by the server.
1389    /// Particularly useful with `CacheMode::IgnoreRules` to provide expiration control.
1390    pub max_ttl: Option<Duration>,
1391    /// Rate limiter that applies only on cache misses.
1392    /// When enabled, requests that result in cache hits are returned immediately,
1393    /// while cache misses are rate limited before making network requests.
1394    /// This provides the optimal behavior for web scrapers and similar applications.
1395    #[cfg(feature = "rate-limiting")]
1396    pub rate_limiter: Option<Arc<dyn CacheAwareRateLimiter>>,
1397    /// Optional callback to provide metadata to store alongside cached responses.
1398    /// The callback receives request and response parts and can return metadata bytes.
1399    /// This is useful for storing computed information that should be associated with
1400    /// cached responses without recomputation on cache hits.
1401    pub metadata_provider: Option<MetadataProvider>,
1402}
1403
1404impl Default for HttpCacheOptions {
1405    fn default() -> Self {
1406        Self {
1407            cache_options: None,
1408            cache_key: None,
1409            cache_mode_fn: None,
1410            response_cache_mode_fn: None,
1411            cache_bust: None,
1412            modify_response: None,
1413            cache_status_headers: true,
1414            max_ttl: None,
1415            #[cfg(feature = "rate-limiting")]
1416            rate_limiter: None,
1417            metadata_provider: None,
1418        }
1419    }
1420}
1421
1422impl Debug for HttpCacheOptions {
1423    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1424        #[cfg(feature = "rate-limiting")]
1425        {
1426            f.debug_struct("HttpCacheOptions")
1427                .field("cache_options", &self.cache_options)
1428                .field("cache_key", &"Fn(&request::Parts) -> String")
1429                .field("cache_mode_fn", &"Fn(&request::Parts) -> CacheMode")
1430                .field(
1431                    "response_cache_mode_fn",
1432                    &"Fn(&request::Parts, &HttpResponse) -> Option<CacheMode>",
1433                )
1434                .field("cache_bust", &"Fn(&request::Parts) -> Vec<String>")
1435                .field("modify_response", &"Fn(&mut ModifyResponse)")
1436                .field("cache_status_headers", &self.cache_status_headers)
1437                .field("max_ttl", &self.max_ttl)
1438                .field("rate_limiter", &"Option<CacheAwareRateLimiter>")
1439                .field(
1440                    "metadata_provider",
1441                    &"Fn(&request::Parts, &response::Parts) -> Option<Vec<u8>>",
1442                )
1443                .finish()
1444        }
1445
1446        #[cfg(not(feature = "rate-limiting"))]
1447        {
1448            f.debug_struct("HttpCacheOptions")
1449                .field("cache_options", &self.cache_options)
1450                .field("cache_key", &"Fn(&request::Parts) -> String")
1451                .field("cache_mode_fn", &"Fn(&request::Parts) -> CacheMode")
1452                .field(
1453                    "response_cache_mode_fn",
1454                    &"Fn(&request::Parts, &HttpResponse) -> Option<CacheMode>",
1455                )
1456                .field("cache_bust", &"Fn(&request::Parts) -> Vec<String>")
1457                .field("modify_response", &"Fn(&mut ModifyResponse)")
1458                .field("cache_status_headers", &self.cache_status_headers)
1459                .field("max_ttl", &self.max_ttl)
1460                .field(
1461                    "metadata_provider",
1462                    &"Fn(&request::Parts, &response::Parts) -> Option<Vec<u8>>",
1463                )
1464                .finish()
1465        }
1466    }
1467}
1468
1469impl HttpCacheOptions {
1470    fn create_cache_key(
1471        &self,
1472        parts: &request::Parts,
1473        override_method: Option<&str>,
1474    ) -> String {
1475        if let Some(cache_key) = &self.cache_key {
1476            cache_key(parts)
1477        } else {
1478            format!(
1479                "{}:{}",
1480                override_method.unwrap_or_else(|| parts.method.as_str()),
1481                parts.uri
1482            )
1483        }
1484    }
1485
1486    /// Helper function for other crates to generate cache keys for invalidation
1487    /// This ensures consistent cache key generation across all implementations
1488    pub fn create_cache_key_for_invalidation(
1489        &self,
1490        parts: &request::Parts,
1491        method_override: &str,
1492    ) -> String {
1493        self.create_cache_key(parts, Some(method_override))
1494    }
1495
1496    /// Converts HttpResponse to http::Response with the given body type
1497    pub fn http_response_to_response<B>(
1498        http_response: &HttpResponse,
1499        body: B,
1500    ) -> Result<Response<B>> {
1501        let mut response_builder = Response::builder()
1502            .status(http_response.status)
1503            .version(http_response.version.into());
1504
1505        for (name, value) in &http_response.headers {
1506            if let (Ok(header_name), Ok(header_value)) =
1507                (name.parse::<http::HeaderName>(), value.parse::<HeaderValue>())
1508            {
1509                response_builder =
1510                    response_builder.header(header_name, header_value);
1511            }
1512        }
1513
1514        Ok(response_builder.body(body)?)
1515    }
1516
1517    /// Converts response parts to HttpResponse format for cache mode evaluation
1518    fn parts_to_http_response(
1519        &self,
1520        parts: &response::Parts,
1521        request_parts: &request::Parts,
1522        metadata: Option<Vec<u8>>,
1523    ) -> Result<HttpResponse> {
1524        Ok(HttpResponse {
1525            body: vec![], // We don't need the full body for cache mode decision
1526            headers: (&parts.headers).into(),
1527            status: parts.status.as_u16(),
1528            url: extract_url_from_request_parts(request_parts)?,
1529            version: parts.version.try_into()?,
1530            metadata,
1531        })
1532    }
1533
1534    /// Evaluates response-based cache mode override
1535    fn evaluate_response_cache_mode(
1536        &self,
1537        request_parts: &request::Parts,
1538        http_response: &HttpResponse,
1539        original_mode: CacheMode,
1540    ) -> CacheMode {
1541        if let Some(response_cache_mode_fn) = &self.response_cache_mode_fn {
1542            if let Some(override_mode) =
1543                response_cache_mode_fn(request_parts, http_response)
1544            {
1545                return override_mode;
1546            }
1547        }
1548        original_mode
1549    }
1550
1551    /// Generates metadata for a response using the metadata_provider callback if configured
1552    pub fn generate_metadata(
1553        &self,
1554        request_parts: &request::Parts,
1555        response_parts: &response::Parts,
1556    ) -> Option<HttpCacheMetadata> {
1557        self.metadata_provider
1558            .as_ref()
1559            .and_then(|provider| provider(request_parts, response_parts))
1560    }
1561
1562    /// Modifies the response before caching if a modifier function is provided
1563    fn modify_response_before_caching(&self, response: &mut HttpResponse) {
1564        if let Some(modify_response) = &self.modify_response {
1565            modify_response(response);
1566        }
1567    }
1568
1569    /// Creates a cache policy for the given request and response
1570    fn create_cache_policy(
1571        &self,
1572        request_parts: &request::Parts,
1573        response_parts: &response::Parts,
1574    ) -> CachePolicy {
1575        let cache_options = self.cache_options.unwrap_or_default();
1576
1577        // If max_ttl is specified, we need to modify the response headers to enforce it
1578        if let Some(max_ttl) = self.max_ttl {
1579            // Parse existing cache-control header
1580            let cache_control = response_parts
1581                .headers
1582                .get("cache-control")
1583                .and_then(|v| v.to_str().ok())
1584                .unwrap_or("");
1585
1586            // Extract existing max-age if present
1587            let existing_max_age =
1588                cache_control.split(',').find_map(|directive| {
1589                    let directive = directive.trim();
1590                    if directive.starts_with("max-age=") {
1591                        directive.strip_prefix("max-age=")?.parse::<u64>().ok()
1592                    } else {
1593                        None
1594                    }
1595                });
1596
1597            // Convert max_ttl to seconds
1598            let max_ttl_seconds = max_ttl.as_secs();
1599
1600            // Apply max_ttl by setting max-age to the minimum of existing max-age and max_ttl
1601            let effective_max_age = match existing_max_age {
1602                Some(existing) => std::cmp::min(existing, max_ttl_seconds),
1603                None => max_ttl_seconds,
1604            };
1605
1606            // Build new cache-control header
1607            let mut new_directives = Vec::new();
1608
1609            // Add non-max-age directives from existing cache-control
1610            for directive in cache_control.split(',').map(|d| d.trim()) {
1611                if !directive.starts_with("max-age=") && !directive.is_empty() {
1612                    new_directives.push(directive.to_string());
1613                }
1614            }
1615
1616            // Add our effective max-age
1617            new_directives.push(format!("max-age={}", effective_max_age));
1618
1619            let new_cache_control = new_directives.join(", ");
1620
1621            // Create modified response parts - we have to clone since response::Parts has private fields
1622            let mut modified_response_parts = response_parts.clone();
1623            modified_response_parts.headers.insert(
1624                "cache-control",
1625                HeaderValue::from_str(&new_cache_control)
1626                    .unwrap_or_else(|_| HeaderValue::from_static("max-age=0")),
1627            );
1628
1629            CachePolicy::new_options(
1630                request_parts,
1631                &modified_response_parts,
1632                SystemTime::now(),
1633                cache_options,
1634            )
1635        } else {
1636            CachePolicy::new_options(
1637                request_parts,
1638                response_parts,
1639                SystemTime::now(),
1640                cache_options,
1641            )
1642        }
1643    }
1644
1645    /// Determines if a response should be cached based on cache mode and HTTP semantics
1646    fn should_cache_response(
1647        &self,
1648        effective_cache_mode: CacheMode,
1649        http_response: &HttpResponse,
1650        is_get_head: bool,
1651        policy: &CachePolicy,
1652    ) -> bool {
1653        // HTTP status codes that are cacheable by default (RFC 7234)
1654        let is_cacheable_status = matches!(
1655            http_response.status,
1656            200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
1657        );
1658
1659        if is_cacheable_status {
1660            match effective_cache_mode {
1661                CacheMode::ForceCache => is_get_head,
1662                CacheMode::IgnoreRules => true,
1663                CacheMode::NoStore => false,
1664                _ => is_get_head && policy.is_storable(),
1665            }
1666        } else {
1667            false
1668        }
1669    }
1670
1671    /// Common request analysis logic shared between streaming and non-streaming implementations
1672    fn analyze_request_internal(
1673        &self,
1674        parts: &request::Parts,
1675        mode_override: Option<CacheMode>,
1676        default_mode: CacheMode,
1677    ) -> Result<CacheAnalysis> {
1678        let effective_mode = mode_override
1679            .or_else(|| self.cache_mode_fn.as_ref().map(|f| f(parts)))
1680            .unwrap_or(default_mode);
1681
1682        let is_get_head = parts.method == "GET" || parts.method == "HEAD";
1683        let should_cache = effective_mode == CacheMode::IgnoreRules
1684            || (is_get_head && effective_mode != CacheMode::NoStore);
1685
1686        let cache_key = self.create_cache_key(parts, None);
1687
1688        let cache_bust_keys = if let Some(cache_bust) = &self.cache_bust {
1689            cache_bust(parts, &self.cache_key, &cache_key)
1690        } else {
1691            Vec::new()
1692        };
1693
1694        Ok(CacheAnalysis {
1695            cache_key,
1696            should_cache,
1697            cache_mode: effective_mode,
1698            cache_bust_keys,
1699            request_parts: parts.clone(),
1700            is_get_head,
1701        })
1702    }
1703}
1704
1705/// Caches requests according to http spec.
1706#[derive(Debug, Clone)]
1707pub struct HttpCache<T: CacheManager> {
1708    /// Determines the manager behavior.
1709    pub mode: CacheMode,
1710    /// Manager instance that implements the [`CacheManager`] trait.
1711    /// By default, a manager implementation with [`cacache`](https://github.com/zkat/cacache-rs)
1712    /// as the backend has been provided, see [`CACacheManager`].
1713    pub manager: T,
1714    /// Override the default cache options.
1715    pub options: HttpCacheOptions,
1716}
1717
1718/// Streaming version of HTTP cache that supports streaming request/response bodies
1719/// without buffering them in memory.
1720#[derive(Debug, Clone)]
1721pub struct HttpStreamingCache<T: StreamingCacheManager> {
1722    /// Determines the manager behavior.
1723    pub mode: CacheMode,
1724    /// Manager instance that implements the [`StreamingCacheManager`] trait.
1725    pub manager: T,
1726    /// Override the default cache options.
1727    pub options: HttpCacheOptions,
1728}
1729
1730#[allow(dead_code)]
1731impl<T: CacheManager> HttpCache<T> {
1732    /// Determines if the request should be cached
1733    pub fn can_cache_request(
1734        &self,
1735        middleware: &impl Middleware,
1736    ) -> Result<bool> {
1737        let analysis = self.analyze_request(
1738            &middleware.parts()?,
1739            middleware.overridden_cache_mode(),
1740        )?;
1741        Ok(analysis.should_cache)
1742    }
1743
1744    /// Apply rate limiting if enabled in options
1745    #[cfg(feature = "rate-limiting")]
1746    async fn apply_rate_limiting(&self, url: &Url) {
1747        if let Some(rate_limiter) = &self.options.rate_limiter {
1748            let rate_limit_key = url.host_str().unwrap_or("unknown");
1749            rate_limiter.until_key_ready(rate_limit_key).await;
1750        }
1751    }
1752
1753    /// Apply rate limiting if enabled in options (no-op without rate-limiting feature)
1754    #[cfg(not(feature = "rate-limiting"))]
1755    async fn apply_rate_limiting(&self, _url: &Url) {
1756        // No-op when rate limiting feature is not enabled
1757    }
1758
1759    /// Runs the actions to preform when the client middleware is running without the cache
1760    pub async fn run_no_cache(
1761        &self,
1762        middleware: &mut impl Middleware,
1763    ) -> Result<()> {
1764        let parts = middleware.parts()?;
1765
1766        self.manager
1767            .delete(&self.options.create_cache_key(&parts, Some("GET")))
1768            .await
1769            .ok();
1770
1771        let cache_key = self.options.create_cache_key(&parts, None);
1772
1773        if let Some(cache_bust) = &self.options.cache_bust {
1774            for key_to_cache_bust in
1775                cache_bust(&parts, &self.options.cache_key, &cache_key)
1776            {
1777                self.manager.delete(&key_to_cache_bust).await?;
1778            }
1779        }
1780
1781        Ok(())
1782    }
1783
1784    /// Attempts to run the passed middleware along with the cache
1785    pub async fn run(
1786        &self,
1787        mut middleware: impl Middleware,
1788    ) -> Result<HttpResponse> {
1789        // Use the HttpCacheInterface to analyze the request
1790        let analysis = self.analyze_request(
1791            &middleware.parts()?,
1792            middleware.overridden_cache_mode(),
1793        )?;
1794
1795        if !analysis.should_cache {
1796            return self.remote_fetch(&mut middleware).await;
1797        }
1798
1799        // Bust cache keys if needed
1800        for key in &analysis.cache_bust_keys {
1801            self.manager.delete(key).await?;
1802        }
1803
1804        // Look up cached response
1805        if let Some((mut cached_response, policy)) =
1806            self.lookup_cached_response(&analysis.cache_key).await?
1807        {
1808            if self.options.cache_status_headers {
1809                cached_response.cache_lookup_status(HitOrMiss::HIT);
1810            }
1811
1812            // Handle warning headers
1813            if let Some(warning_code) = cached_response.warning_code() {
1814                // https://tools.ietf.org/html/rfc7234#section-4.3.4
1815                //
1816                // If a stored response is selected for update, the cache MUST:
1817                //
1818                // * delete any warning header fields in the stored response with
1819                //   warn-code 1xx (see Section 5.5);
1820                //
1821                // * retain any warning header fields in the stored response with
1822                //   warn-code 2xx;
1823                //
1824                if (100..200).contains(&warning_code) {
1825                    cached_response.remove_warning();
1826                }
1827            }
1828
1829            match analysis.cache_mode {
1830                CacheMode::Default => {
1831                    self.conditional_fetch(middleware, cached_response, policy)
1832                        .await
1833                }
1834                CacheMode::NoCache => {
1835                    middleware.force_no_cache()?;
1836                    let mut res = self.remote_fetch(&mut middleware).await?;
1837                    if self.options.cache_status_headers {
1838                        res.cache_lookup_status(HitOrMiss::HIT);
1839                    }
1840                    Ok(res)
1841                }
1842                CacheMode::ForceCache
1843                | CacheMode::OnlyIfCached
1844                | CacheMode::IgnoreRules => {
1845                    //   112 Disconnected operation
1846                    // SHOULD be included if the cache is intentionally disconnected from
1847                    // the rest of the network for a period of time.
1848                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
1849                    cached_response.add_warning(
1850                        &cached_response.url.clone(),
1851                        112,
1852                        "Disconnected operation",
1853                    );
1854                    if self.options.cache_status_headers {
1855                        cached_response.cache_status(HitOrMiss::HIT);
1856                    }
1857                    Ok(cached_response)
1858                }
1859                _ => self.remote_fetch(&mut middleware).await,
1860            }
1861        } else {
1862            match analysis.cache_mode {
1863                CacheMode::OnlyIfCached => {
1864                    // ENOTCACHED
1865                    let mut res = HttpResponse {
1866                        body: b"GatewayTimeout".to_vec(),
1867                        headers: HttpHeaders::default(),
1868                        status: 504,
1869                        url: middleware.url()?,
1870                        version: HttpVersion::Http11,
1871                        metadata: None,
1872                    };
1873                    if self.options.cache_status_headers {
1874                        res.cache_status(HitOrMiss::MISS);
1875                        res.cache_lookup_status(HitOrMiss::MISS);
1876                    }
1877                    Ok(res)
1878                }
1879                _ => self.remote_fetch(&mut middleware).await,
1880            }
1881        }
1882    }
1883
1884    fn cache_mode(&self, middleware: &impl Middleware) -> Result<CacheMode> {
1885        Ok(if let Some(mode) = middleware.overridden_cache_mode() {
1886            mode
1887        } else if let Some(cache_mode_fn) = &self.options.cache_mode_fn {
1888            cache_mode_fn(&middleware.parts()?)
1889        } else {
1890            self.mode
1891        })
1892    }
1893
1894    async fn remote_fetch(
1895        &self,
1896        middleware: &mut impl Middleware,
1897    ) -> Result<HttpResponse> {
1898        // Apply rate limiting before making the network request
1899        let url = middleware.url()?;
1900        self.apply_rate_limiting(&url).await;
1901
1902        let mut res = middleware.remote_fetch().await?;
1903        if self.options.cache_status_headers {
1904            res.cache_status(HitOrMiss::MISS);
1905            res.cache_lookup_status(HitOrMiss::MISS);
1906        }
1907        let policy = match self.options.cache_options {
1908            Some(options) => middleware.policy_with_options(&res, options)?,
1909            None => middleware.policy(&res)?,
1910        };
1911        let is_get_head = middleware.is_method_get_head();
1912        let mut mode = self.cache_mode(middleware)?;
1913        let parts = middleware.parts()?;
1914
1915        // Allow response-based cache mode override
1916        if let Some(response_cache_mode_fn) =
1917            &self.options.response_cache_mode_fn
1918        {
1919            if let Some(override_mode) = response_cache_mode_fn(&parts, &res) {
1920                mode = override_mode;
1921            }
1922        }
1923
1924        let is_cacheable = self.options.should_cache_response(
1925            mode,
1926            &res,
1927            is_get_head,
1928            &policy,
1929        );
1930
1931        if is_cacheable {
1932            // Generate metadata using the provider callback if configured
1933            let response_parts = res.parts()?;
1934            res.metadata =
1935                self.options.generate_metadata(&parts, &response_parts);
1936
1937            self.options.modify_response_before_caching(&mut res);
1938            Ok(self
1939                .manager
1940                .put(self.options.create_cache_key(&parts, None), res, policy)
1941                .await?)
1942        } else if !is_get_head {
1943            self.manager
1944                .delete(&self.options.create_cache_key(&parts, Some("GET")))
1945                .await
1946                .ok();
1947            Ok(res)
1948        } else {
1949            Ok(res)
1950        }
1951    }
1952
1953    async fn conditional_fetch(
1954        &self,
1955        mut middleware: impl Middleware,
1956        mut cached_res: HttpResponse,
1957        mut policy: CachePolicy,
1958    ) -> Result<HttpResponse> {
1959        let parts = middleware.parts()?;
1960        let before_req = policy.before_request(&parts, SystemTime::now());
1961        match before_req {
1962            BeforeRequest::Fresh(parts) => {
1963                cached_res.update_headers(&parts)?;
1964                if self.options.cache_status_headers {
1965                    cached_res.cache_status(HitOrMiss::HIT);
1966                    cached_res.cache_lookup_status(HitOrMiss::HIT);
1967                }
1968                return Ok(cached_res);
1969            }
1970            BeforeRequest::Stale { request: parts, matches } => {
1971                if matches {
1972                    middleware.update_headers(&parts)?;
1973                }
1974            }
1975        }
1976        let req_url = middleware.url()?;
1977        // Apply rate limiting before revalidation request
1978        self.apply_rate_limiting(&req_url).await;
1979        match middleware.remote_fetch().await {
1980            Ok(mut cond_res) => {
1981                let status = StatusCode::from_u16(cond_res.status)?;
1982                if status.is_server_error() && cached_res.must_revalidate() {
1983                    //   111 Revalidation failed
1984                    //   MUST be included if a cache returns a stale response
1985                    //   because an attempt to revalidate the response failed,
1986                    //   due to an inability to reach the server.
1987                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
1988                    cached_res.add_warning(
1989                        &req_url,
1990                        111,
1991                        "Revalidation failed",
1992                    );
1993                    if self.options.cache_status_headers {
1994                        cached_res.cache_status(HitOrMiss::HIT);
1995                    }
1996                    Ok(cached_res)
1997                } else if cond_res.status == 304 {
1998                    let after_res = policy.after_response(
1999                        &parts,
2000                        &cond_res.parts()?,
2001                        SystemTime::now(),
2002                    );
2003                    match after_res {
2004                        AfterResponse::Modified(new_policy, parts)
2005                        | AfterResponse::NotModified(new_policy, parts) => {
2006                            policy = new_policy;
2007                            cached_res.update_headers(&parts)?;
2008                        }
2009                    }
2010                    if self.options.cache_status_headers {
2011                        cached_res.cache_status(HitOrMiss::HIT);
2012                        cached_res.cache_lookup_status(HitOrMiss::HIT);
2013                    }
2014                    self.options
2015                        .modify_response_before_caching(&mut cached_res);
2016                    let res = self
2017                        .manager
2018                        .put(
2019                            self.options.create_cache_key(&parts, None),
2020                            cached_res,
2021                            policy,
2022                        )
2023                        .await?;
2024                    Ok(res)
2025                } else if cond_res.status == 200 {
2026                    let policy = match self.options.cache_options {
2027                        Some(options) => middleware
2028                            .policy_with_options(&cond_res, options)?,
2029                        None => middleware.policy(&cond_res)?,
2030                    };
2031                    if self.options.cache_status_headers {
2032                        cond_res.cache_status(HitOrMiss::MISS);
2033                        cond_res.cache_lookup_status(HitOrMiss::HIT);
2034                    }
2035                    // Generate metadata using the provider callback if configured
2036                    let response_parts = cond_res.parts()?;
2037                    cond_res.metadata =
2038                        self.options.generate_metadata(&parts, &response_parts);
2039
2040                    self.options.modify_response_before_caching(&mut cond_res);
2041                    let res = self
2042                        .manager
2043                        .put(
2044                            self.options.create_cache_key(&parts, None),
2045                            cond_res,
2046                            policy,
2047                        )
2048                        .await?;
2049                    Ok(res)
2050                } else {
2051                    if self.options.cache_status_headers {
2052                        cached_res.cache_status(HitOrMiss::HIT);
2053                    }
2054                    Ok(cached_res)
2055                }
2056            }
2057            Err(e) => {
2058                if cached_res.must_revalidate() {
2059                    Err(e)
2060                } else {
2061                    //   111 Revalidation failed
2062                    //   MUST be included if a cache returns a stale response
2063                    //   because an attempt to revalidate the response failed,
2064                    //   due to an inability to reach the server.
2065                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
2066                    cached_res.add_warning(
2067                        &req_url,
2068                        111,
2069                        "Revalidation failed",
2070                    );
2071                    if self.options.cache_status_headers {
2072                        cached_res.cache_status(HitOrMiss::HIT);
2073                    }
2074                    Ok(cached_res)
2075                }
2076            }
2077        }
2078    }
2079}
2080
2081impl<T: StreamingCacheManager> HttpCacheStreamInterface
2082    for HttpStreamingCache<T>
2083where
2084    <T::Body as http_body::Body>::Data: Send,
2085    <T::Body as http_body::Body>::Error:
2086        Into<StreamingError> + Send + Sync + 'static,
2087{
2088    type Body = T::Body;
2089
2090    fn analyze_request(
2091        &self,
2092        parts: &request::Parts,
2093        mode_override: Option<CacheMode>,
2094    ) -> Result<CacheAnalysis> {
2095        self.options.analyze_request_internal(parts, mode_override, self.mode)
2096    }
2097
2098    async fn lookup_cached_response(
2099        &self,
2100        key: &str,
2101    ) -> Result<Option<(Response<Self::Body>, CachePolicy)>> {
2102        if let Some((mut response, policy)) = self.manager.get(key).await? {
2103            // Add cache status headers if enabled
2104            if self.options.cache_status_headers {
2105                response.headers_mut().insert(
2106                    XCACHE,
2107                    "HIT".parse().map_err(StreamingError::new)?,
2108                );
2109                response.headers_mut().insert(
2110                    XCACHELOOKUP,
2111                    "HIT".parse().map_err(StreamingError::new)?,
2112                );
2113            }
2114            Ok(Some((response, policy)))
2115        } else {
2116            Ok(None)
2117        }
2118    }
2119
2120    async fn process_response<B>(
2121        &self,
2122        analysis: CacheAnalysis,
2123        response: Response<B>,
2124        metadata: Option<Vec<u8>>,
2125    ) -> Result<Response<Self::Body>>
2126    where
2127        B: http_body::Body + Send + 'static,
2128        B::Data: Send,
2129        B::Error: Into<StreamingError>,
2130        <T::Body as http_body::Body>::Data: Send,
2131        <T::Body as http_body::Body>::Error:
2132            Into<StreamingError> + Send + Sync + 'static,
2133    {
2134        // For non-cacheable requests based on initial analysis, convert them to manager's body type
2135        if !analysis.should_cache {
2136            let mut converted_response =
2137                self.manager.convert_body(response).await?;
2138            // Add cache miss headers
2139            if self.options.cache_status_headers {
2140                converted_response.headers_mut().insert(
2141                    XCACHE,
2142                    "MISS".parse().map_err(StreamingError::new)?,
2143                );
2144                converted_response.headers_mut().insert(
2145                    XCACHELOOKUP,
2146                    "MISS".parse().map_err(StreamingError::new)?,
2147                );
2148            }
2149            return Ok(converted_response);
2150        }
2151
2152        // Bust cache keys if needed
2153        for key in &analysis.cache_bust_keys {
2154            self.manager.delete(key).await?;
2155        }
2156
2157        // Convert response to HttpResponse format for response-based cache mode evaluation
2158        let (parts, body) = response.into_parts();
2159        // Use provided metadata or generate from provider
2160        let effective_metadata = metadata.or_else(|| {
2161            self.options.generate_metadata(&analysis.request_parts, &parts)
2162        });
2163        let http_response = self.options.parts_to_http_response(
2164            &parts,
2165            &analysis.request_parts,
2166            effective_metadata.clone(),
2167        )?;
2168
2169        // Check for response-based cache mode override
2170        let effective_cache_mode = self.options.evaluate_response_cache_mode(
2171            &analysis.request_parts,
2172            &http_response,
2173            analysis.cache_mode,
2174        );
2175
2176        // Reconstruct response for further processing
2177        let response = Response::from_parts(parts, body);
2178
2179        // If response-based override says NoStore, don't cache
2180        if effective_cache_mode == CacheMode::NoStore {
2181            let mut converted_response =
2182                self.manager.convert_body(response).await?;
2183            // Add cache miss headers
2184            if self.options.cache_status_headers {
2185                converted_response.headers_mut().insert(
2186                    XCACHE,
2187                    "MISS".parse().map_err(StreamingError::new)?,
2188                );
2189                converted_response.headers_mut().insert(
2190                    XCACHELOOKUP,
2191                    "MISS".parse().map_err(StreamingError::new)?,
2192                );
2193            }
2194            return Ok(converted_response);
2195        }
2196
2197        // Create policy for the response
2198        let (parts, body) = response.into_parts();
2199        let policy =
2200            self.options.create_cache_policy(&analysis.request_parts, &parts);
2201
2202        // Reconstruct response for caching
2203        let response = Response::from_parts(parts, body);
2204
2205        let should_cache_response = self.options.should_cache_response(
2206            effective_cache_mode,
2207            &http_response,
2208            analysis.is_get_head,
2209            &policy,
2210        );
2211
2212        if should_cache_response {
2213            // Extract URL from request parts for caching
2214            let request_url =
2215                extract_url_from_request_parts(&analysis.request_parts)?;
2216
2217            // Cache the response using the streaming manager
2218            let mut cached_response = self
2219                .manager
2220                .put(
2221                    analysis.cache_key,
2222                    response,
2223                    policy,
2224                    request_url,
2225                    effective_metadata,
2226                )
2227                .await?;
2228
2229            // Add cache miss headers (response is being stored for first time)
2230            if self.options.cache_status_headers {
2231                cached_response.headers_mut().insert(
2232                    XCACHE,
2233                    "MISS".parse().map_err(StreamingError::new)?,
2234                );
2235                cached_response.headers_mut().insert(
2236                    XCACHELOOKUP,
2237                    "MISS".parse().map_err(StreamingError::new)?,
2238                );
2239            }
2240            Ok(cached_response)
2241        } else {
2242            // Don't cache, just convert to manager's body type
2243            let mut converted_response =
2244                self.manager.convert_body(response).await?;
2245            // Add cache miss headers
2246            if self.options.cache_status_headers {
2247                converted_response.headers_mut().insert(
2248                    XCACHE,
2249                    "MISS".parse().map_err(StreamingError::new)?,
2250                );
2251                converted_response.headers_mut().insert(
2252                    XCACHELOOKUP,
2253                    "MISS".parse().map_err(StreamingError::new)?,
2254                );
2255            }
2256            Ok(converted_response)
2257        }
2258    }
2259
2260    fn prepare_conditional_request(
2261        &self,
2262        parts: &mut request::Parts,
2263        _cached_response: &Response<Self::Body>,
2264        policy: &CachePolicy,
2265    ) -> Result<()> {
2266        let before_req = policy.before_request(parts, SystemTime::now());
2267        if let BeforeRequest::Stale { request, .. } = before_req {
2268            parts.headers.extend(request.headers);
2269        }
2270        Ok(())
2271    }
2272
2273    async fn handle_not_modified(
2274        &self,
2275        cached_response: Response<Self::Body>,
2276        fresh_parts: &response::Parts,
2277    ) -> Result<Response<Self::Body>> {
2278        let (mut parts, body) = cached_response.into_parts();
2279
2280        // Update headers from the 304 response
2281        parts.headers.extend(fresh_parts.headers.clone());
2282
2283        Ok(Response::from_parts(parts, body))
2284    }
2285}
2286
2287impl<T: CacheManager> HttpCacheInterface for HttpCache<T> {
2288    fn analyze_request(
2289        &self,
2290        parts: &request::Parts,
2291        mode_override: Option<CacheMode>,
2292    ) -> Result<CacheAnalysis> {
2293        self.options.analyze_request_internal(parts, mode_override, self.mode)
2294    }
2295
2296    async fn lookup_cached_response(
2297        &self,
2298        key: &str,
2299    ) -> Result<Option<(HttpResponse, CachePolicy)>> {
2300        self.manager.get(key).await
2301    }
2302
2303    async fn process_response(
2304        &self,
2305        analysis: CacheAnalysis,
2306        response: Response<Vec<u8>>,
2307        metadata: Option<Vec<u8>>,
2308    ) -> Result<Response<Vec<u8>>> {
2309        if !analysis.should_cache {
2310            return Ok(response);
2311        }
2312
2313        // Bust cache keys if needed
2314        for key in &analysis.cache_bust_keys {
2315            self.manager.delete(key).await?;
2316        }
2317
2318        // Convert response to HttpResponse format
2319        let (parts, body) = response.into_parts();
2320        // Use provided metadata or generate from provider
2321        let effective_metadata = metadata.or_else(|| {
2322            self.options.generate_metadata(&analysis.request_parts, &parts)
2323        });
2324        let mut http_response = self.options.parts_to_http_response(
2325            &parts,
2326            &analysis.request_parts,
2327            effective_metadata,
2328        )?;
2329        http_response.body = body.clone(); // Include the body for buffered cache managers
2330
2331        // Check for response-based cache mode override
2332        let effective_cache_mode = self.options.evaluate_response_cache_mode(
2333            &analysis.request_parts,
2334            &http_response,
2335            analysis.cache_mode,
2336        );
2337
2338        // If response-based override says NoStore, don't cache
2339        if effective_cache_mode == CacheMode::NoStore {
2340            let response = Response::from_parts(parts, body);
2341            return Ok(response);
2342        }
2343
2344        // Create policy and determine if we should cache based on response-based mode
2345        let policy = self.options.create_cache_policy(
2346            &analysis.request_parts,
2347            &http_response.parts()?,
2348        );
2349
2350        let should_cache_response = self.options.should_cache_response(
2351            effective_cache_mode,
2352            &http_response,
2353            analysis.is_get_head,
2354            &policy,
2355        );
2356
2357        if should_cache_response {
2358            let cached_response = self
2359                .manager
2360                .put(analysis.cache_key, http_response, policy)
2361                .await?;
2362
2363            // Convert back to standard Response
2364            let response_parts = cached_response.parts()?;
2365            let mut response = Response::builder()
2366                .status(response_parts.status)
2367                .version(response_parts.version)
2368                .body(cached_response.body)?;
2369
2370            // Copy headers from the response parts
2371            *response.headers_mut() = response_parts.headers;
2372
2373            Ok(response)
2374        } else {
2375            // Don't cache, return original response
2376            let response = Response::from_parts(parts, body);
2377            Ok(response)
2378        }
2379    }
2380
2381    fn prepare_conditional_request(
2382        &self,
2383        parts: &mut request::Parts,
2384        _cached_response: &HttpResponse,
2385        policy: &CachePolicy,
2386    ) -> Result<()> {
2387        let before_req = policy.before_request(parts, SystemTime::now());
2388        if let BeforeRequest::Stale { request, .. } = before_req {
2389            parts.headers.extend(request.headers);
2390        }
2391        Ok(())
2392    }
2393
2394    async fn handle_not_modified(
2395        &self,
2396        mut cached_response: HttpResponse,
2397        fresh_parts: &response::Parts,
2398    ) -> Result<HttpResponse> {
2399        cached_response.update_headers(fresh_parts)?;
2400        if self.options.cache_status_headers {
2401            cached_response.cache_status(HitOrMiss::HIT);
2402            cached_response.cache_lookup_status(HitOrMiss::HIT);
2403        }
2404        Ok(cached_response)
2405    }
2406}
2407
2408#[allow(dead_code)]
2409#[cfg(test)]
2410mod test;