Skip to main content

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//! # #[cfg(feature = "manager-cacache")]
35//! # fn main() {
36//! use http_cache::{CACacheManager, HttpCache, CacheMode, HttpCacheOptions};
37//!
38//! // Create a cache manager with disk storage
39//! let manager = CACacheManager::new("./cache".into(), true);
40//!
41//! // Create an HTTP cache with default behavior
42//! let cache = HttpCache {
43//!     mode: CacheMode::Default,
44//!     manager,
45//!     options: HttpCacheOptions::default(),
46//! };
47//! # }
48//! # #[cfg(not(feature = "manager-cacache"))]
49//! # fn main() {}
50//! ```
51//!
52//! ## Cache Modes
53//!
54//! Different cache modes provide different behaviors:
55//!
56//! ```rust
57//! # #[cfg(feature = "manager-cacache")]
58//! # fn main() {
59//! use http_cache::{CacheMode, HttpCache, CACacheManager, HttpCacheOptions};
60//!
61//! let manager = CACacheManager::new("./cache".into(), true);
62//!
63//! // Default mode: follows HTTP caching rules
64//! let default_cache = HttpCache {
65//!     mode: CacheMode::Default,
66//!     manager: manager.clone(),
67//!     options: HttpCacheOptions::default(),
68//! };
69//!
70//! // NoStore mode: never caches responses
71//! let no_store_cache = HttpCache {
72//!     mode: CacheMode::NoStore,
73//!     manager: manager.clone(),
74//!     options: HttpCacheOptions::default(),
75//! };
76//!
77//! // ForceCache mode: caches responses even if headers suggest otherwise
78//! let force_cache = HttpCache {
79//!     mode: CacheMode::ForceCache,
80//!     manager,
81//!     options: HttpCacheOptions::default(),
82//! };
83//! # }
84//! # #[cfg(not(feature = "manager-cacache"))]
85//! # fn main() {}
86//! ```
87//!
88//! ## Custom Cache Keys
89//!
90//! You can customize how cache keys are generated:
91//!
92//! ```rust
93//! # #[cfg(feature = "manager-cacache")]
94//! # fn main() {
95//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
96//! use std::sync::Arc;
97//! use http::request::Parts;
98//!
99//! let manager = CACacheManager::new("./cache".into(), true);
100//!
101//! let options = HttpCacheOptions {
102//!     cache_key: Some(Arc::new(|req: &Parts| {
103//!         // Custom cache key that includes query parameters
104//!         format!("{}:{}", req.method, req.uri)
105//!     })),
106//!     ..Default::default()
107//! };
108//!
109//! let cache = HttpCache {
110//!     mode: CacheMode::Default,
111//!     manager,
112//!     options,
113//! };
114//! # }
115//! # #[cfg(not(feature = "manager-cacache"))]
116//! # fn main() {}
117//! ```
118//!
119//! ## Maximum TTL Control
120//!
121//! Set a maximum time-to-live for cached responses, particularly useful with `CacheMode::IgnoreRules`:
122//!
123//! ```rust
124//! # #[cfg(feature = "manager-cacache")]
125//! # fn main() {
126//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
127//! use std::time::Duration;
128//!
129//! let manager = CACacheManager::new("./cache".into(), true);
130//!
131//! // Limit cache duration to 5 minutes regardless of server headers
132//! let options = HttpCacheOptions {
133//!     max_ttl: Some(Duration::from_secs(300)), // 5 minutes
134//!     ..Default::default()
135//! };
136//!
137//! let cache = HttpCache {
138//!     mode: CacheMode::IgnoreRules, // Ignore server cache-control headers
139//!     manager,
140//!     options,
141//! };
142//! # }
143//! # #[cfg(not(feature = "manager-cacache"))]
144//! # fn main() {}
145//! ```
146//!
147//! ## Response-Based Cache Mode Override
148//!
149//! Override cache behavior based on the response you receive. This is useful for scenarios like
150//! forcing cache for successful responses even when headers say not to cache, or never caching
151//! error responses like rate limits:
152//!
153//! ```rust
154//! # #[cfg(feature = "manager-cacache")]
155//! # fn main() {
156//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
157//! use std::sync::Arc;
158//!
159//! let manager = CACacheManager::new("./cache".into(), true);
160//!
161//! let options = HttpCacheOptions {
162//!     response_cache_mode_fn: Some(Arc::new(|_request_parts, response| {
163//!         match response.status {
164//!             // Force cache successful responses even if headers say not to cache
165//!             200..=299 => Some(CacheMode::ForceCache),
166//!             // Never cache rate-limited responses
167//!             429 => Some(CacheMode::NoStore),
168//!             // Use default behavior for everything else
169//!             _ => None,
170//!         }
171//!     })),
172//!     ..Default::default()
173//! };
174//!
175//! let cache = HttpCache {
176//!     mode: CacheMode::Default,
177//!     manager,
178//!     options,
179//! };
180//! # }
181//! # #[cfg(not(feature = "manager-cacache"))]
182//! # fn main() {}
183//! ```
184//!
185//! ## Content-Type Based Caching
186//!
187//! You can implement selective caching based on response content types using `response_cache_mode_fn`.
188//! This is useful when you only want to cache certain types of content:
189//!
190//! ```rust
191//! # #[cfg(feature = "manager-cacache")]
192//! # fn main() {
193//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
194//! use std::sync::Arc;
195//!
196//! let manager = CACacheManager::new("./cache".into(), true);
197//!
198//! let options = HttpCacheOptions {
199//!     response_cache_mode_fn: Some(Arc::new(|_request_parts, response| {
200//!         // Check the Content-Type header to decide caching behavior
201//!         if let Some(content_type) = response.headers.get("content-type") {
202//!             match content_type.as_str() {
203//!                 // Cache JSON APIs aggressively
204//!                 ct if ct.starts_with("application/json") => Some(CacheMode::ForceCache),
205//!                 // Cache images with default rules
206//!                 ct if ct.starts_with("image/") => Some(CacheMode::Default),
207//!                 // Cache static assets
208//!                 ct if ct.starts_with("text/css") => Some(CacheMode::ForceCache),
209//!                 ct if ct.starts_with("application/javascript") => Some(CacheMode::ForceCache),
210//!                 // Don't cache HTML pages (dynamic content)
211//!                 ct if ct.starts_with("text/html") => Some(CacheMode::NoStore),
212//!                 // Don't cache unknown content types
213//!                 _ => Some(CacheMode::NoStore),
214//!             }
215//!         } else {
216//!             // No Content-Type header - don't cache
217//!             Some(CacheMode::NoStore)
218//!         }
219//!     })),
220//!     ..Default::default()
221//! };
222//!
223//! let cache = HttpCache {
224//!     mode: CacheMode::Default, // This gets overridden by response_cache_mode_fn
225//!     manager,
226//!     options,
227//! };
228//! # }
229//! # #[cfg(not(feature = "manager-cacache"))]
230//! # fn main() {}
231//! ```
232//!
233//! ## Streaming Support
234//!
235//! For handling large responses without full buffering, use the `StreamingManager`:
236//!
237//! ```rust
238//! # #[cfg(feature = "streaming")]
239//! # {
240//! use http_cache::StreamingBody;
241//! use bytes::Bytes;
242//! use http_body::Body;
243//! use http_body_util::Full;
244//!
245//! // StreamingManager uses redb (metadata) + tokio::fs (bodies) with a moka
246//! // in-memory hot cache for disk-backed streaming.
247//! // Create with: StreamingManager::with_temp_dir(1000).await.unwrap()
248//!
249//! // StreamingBody can handle both buffered and streaming scenarios
250//! let body: StreamingBody<Full<Bytes>> = StreamingBody::buffered(Bytes::from("cached content"));
251//! println!("Body size: {:?}", body.size_hint());
252//! # }
253//! ```
254//!
255//! **Note**: Streaming support requires the `StreamingManager` with the `streaming` feature.
256//! Other cache managers (CACacheManager, MokaManager, QuickManager) do not support streaming
257//! and will buffer response bodies in memory.
258//!
259//! ## Features
260//!
261//! The following features are available. By default `manager-cacache` is enabled.
262//!
263//! - `manager-cacache` (default): enable [cacache](https://github.com/zkat/cacache-rs),
264//! a disk cache, backend manager. Uses tokio runtime.
265//! - `manager-moka` (disabled): enable [moka](https://github.com/moka-rs/moka),
266//! an in-memory cache, backend manager.
267//! - `manager-foyer` (disabled): enable [foyer](https://github.com/foyer-rs/foyer),
268//! a hybrid in-memory + disk cache, backend manager. Uses tokio runtime.
269//! - `http-headers-compat` (disabled): enable backwards compatibility for deserializing cached
270//! responses from older versions that used single-value headers. Enable this if you need to read
271//! cache entries created by older versions of http-cache.
272//! - `streaming` (disabled): enable the `StreamingManager` for streaming cache
273//!   support. Uses redb for metadata, raw `tokio::fs` files for bodies, and
274//!   moka as an in-memory hot cache of metadata.
275//! - `with-http-types` (disabled): enable [http-types](https://github.com/http-rs/http-types)
276//! type conversion support
277//!
278//! ### URL Implementation Features
279//!
280//! Exactly one URL implementation must be enabled. These features are **mutually exclusive**:
281//!
282//! - `url-standard` (default): uses the [url](https://github.com/servo/rust-url) crate.
283//!   Note: This brings in the `idna` crate which has a Unicode license.
284//! - `url-ada` (disabled): uses [ada-url](https://github.com/ada-url/rust) for WHATWG-compliant
285//!   URL parsing without the Unicode/IDNA license dependency.
286//!
287//! If you need to avoid the Unicode license, use `url-ada`:
288//!
289//! ```toml
290//! [dependencies]
291//! http-cache = { version = "1.0", default-features = false, features = ["manager-cacache", "url-ada"] }
292//! ```
293//!
294//! ### Legacy bincode features (deprecated)
295//!
296//! These features are deprecated due to [RUSTSEC-2025-0141](https://rustsec.org/advisories/RUSTSEC-2025-0141)
297//! and will be removed in the next major version:
298//!
299//! - `manager-cacache-bincode`: cacache with bincode serialization
300//! - `manager-moka-bincode`: moka with bincode serialization
301//!
302//! **Note**: Only `StreamingManager` (via the `streaming` feature) provides streaming support.
303//! Other managers will buffer response bodies in memory even when used with `StreamingManager`.
304//!
305//! ## Integration
306//!
307//! This crate is designed to be used as a foundation for HTTP client and server middleware.
308//! See the companion crates for specific integrations:
309//!
310//! - [`http-cache-reqwest`](https://docs.rs/http-cache-reqwest) for reqwest client middleware
311//! - [`http-cache-surf`](https://docs.rs/http-cache-surf) for surf client middleware  
312//! - [`http-cache-tower`](https://docs.rs/http-cache-tower) for tower service middleware
313
314// URL feature validation - exactly one URL implementation must be enabled
315#[cfg(all(feature = "url-standard", feature = "url-ada"))]
316compile_error!("features `url-standard` and `url-ada` are mutually exclusive");
317
318#[cfg(not(any(feature = "url-standard", feature = "url-ada")))]
319compile_error!("either feature `url-standard` or `url-ada` must be enabled");
320
321mod body;
322mod error;
323mod managers;
324
325#[cfg(feature = "rate-limiting")]
326pub mod rate_limiting;
327
328use std::{
329    collections::HashMap,
330    convert::TryFrom,
331    fmt::{self, Debug},
332    future::Future,
333    str::FromStr,
334    sync::Arc,
335    time::{Duration, SystemTime},
336};
337
338use http::{
339    header::CACHE_CONTROL, request, response, HeaderValue, Response, StatusCode,
340};
341use http_cache_semantics::{AfterResponse, BeforeRequest, CachePolicy};
342use serde::{Deserialize, Deserializer, Serialize, Serializer};
343
344// URL type alias - allows users to choose between `url` (default) and `ada-url` crates
345// When using `url-ada` feature, this becomes `ada_url::Url`
346#[cfg(feature = "url-ada")]
347pub use ada_url::Url;
348#[cfg(not(feature = "url-ada"))]
349pub use url::Url;
350
351// ============================================================================
352// URL Helper Functions
353// ============================================================================
354// These functions abstract away API differences between `url` and `ada-url` crates.
355// Internal code should use these helpers instead of calling URL methods directly.
356
357/// Parse a URL string into a `Url` type.
358///
359/// This helper abstracts the parsing API difference between `url` and `ada-url`:
360/// - `url` crate: `Url::parse(s)` returns `Result<Url, ParseError>`
361/// - `ada-url` crate: `Url::parse(s, None)` returns `Result<Url, ParseUrlError>`
362#[inline]
363pub fn url_parse(s: &str) -> Result<Url> {
364    #[cfg(feature = "url-ada")]
365    {
366        Url::parse(s, None).map_err(|e| -> BoxError { e.to_string().into() })
367    }
368    #[cfg(not(feature = "url-ada"))]
369    {
370        Url::parse(s).map_err(|e| -> BoxError { Box::new(e) })
371    }
372}
373
374/// Set the path component of a URL.
375///
376/// API differences:
377/// - `url` crate: `url.set_path(path)`
378/// - `ada-url` crate: `url.set_pathname(Some(path))`
379#[inline]
380pub fn url_set_path(url: &mut Url, path: &str) {
381    #[cfg(feature = "url-ada")]
382    {
383        let _ = url.set_pathname(Some(path));
384    }
385    #[cfg(not(feature = "url-ada"))]
386    {
387        url.set_path(path);
388    }
389}
390
391/// Set the query component of a URL.
392///
393/// API differences:
394/// - `url` crate: `url.set_query(Some(query))` or `url.set_query(None)`
395/// - `ada-url` crate: `url.set_search(Some(query))` or `url.set_search(None)`
396#[inline]
397pub fn url_set_query(url: &mut Url, query: Option<&str>) {
398    #[cfg(feature = "url-ada")]
399    {
400        url.set_search(query);
401    }
402    #[cfg(not(feature = "url-ada"))]
403    {
404        url.set_query(query);
405    }
406}
407
408/// Get the hostname of a URL as a string.
409///
410/// API differences:
411/// - `url` crate: `url.host_str()` returns `Option<&str>`
412/// - `ada-url` crate: `url.hostname()` returns `&str` (empty string if no host)
413#[inline]
414#[must_use]
415pub fn url_hostname(url: &Url) -> Option<&str> {
416    #[cfg(feature = "url-ada")]
417    {
418        let hostname = url.hostname();
419        if hostname.is_empty() {
420            None
421        } else {
422            Some(hostname)
423        }
424    }
425    #[cfg(not(feature = "url-ada"))]
426    {
427        url.host_str()
428    }
429}
430
431/// Get the host of a URL as a string for display purposes (e.g., warning headers).
432///
433/// This returns the host portion as a string, or "unknown" if not available.
434/// Used in places like HTTP Warning headers where we need a displayable host value.
435#[inline]
436#[must_use]
437pub fn url_host_str(url: &Url) -> String {
438    #[cfg(feature = "url-ada")]
439    {
440        let hostname = url.hostname();
441        if hostname.is_empty() {
442            "unknown".to_string()
443        } else {
444            hostname.to_string()
445        }
446    }
447    #[cfg(not(feature = "url-ada"))]
448    {
449        url.host()
450            .map(|h| h.to_string())
451            .unwrap_or_else(|| "unknown".to_string())
452    }
453}
454
455pub use body::StreamingBody;
456pub use error::{
457    BadHeader, BadRequest, BadVersion, BoxError, ClientStreamingError,
458    HttpCacheError, HttpCacheResult, Result, StreamingError,
459};
460
461#[cfg(any(
462    feature = "manager-cacache",
463    feature = "manager-cacache-bincode"
464))]
465pub use managers::cacache::CACacheManager;
466
467#[cfg(feature = "streaming")]
468pub use managers::streaming_cache::StreamingManager;
469
470#[cfg(any(feature = "manager-moka", feature = "manager-moka-bincode"))]
471pub use managers::moka::MokaManager;
472
473#[cfg(feature = "manager-foyer")]
474pub use managers::foyer::FoyerManager;
475
476#[cfg(feature = "manager-redb")]
477pub use managers::redb::RedbManager;
478
479#[cfg(feature = "rate-limiting")]
480pub use rate_limiting::{
481    CacheAwareRateLimiter, DirectRateLimiter, DomainRateLimiter,
482};
483
484#[cfg(feature = "rate-limiting")]
485pub use rate_limiting::Quota;
486
487// Exposing the moka cache for convenience, renaming to avoid naming conflicts
488#[cfg(any(feature = "manager-moka", feature = "manager-moka-bincode"))]
489#[cfg_attr(docsrs, doc(cfg(feature = "manager-moka")))]
490pub use moka::future::{Cache as MokaCache, CacheBuilder as MokaCacheBuilder};
491
492// Custom headers used to indicate cache status (hit or miss)
493/// `x-cache` header: Value will be HIT if the response was served from cache, MISS if not
494pub const XCACHE: &str = "x-cache";
495/// `x-cache-lookup` header: Value will be HIT if a response existed in cache, MISS if not
496pub const XCACHELOOKUP: &str = "x-cache-lookup";
497/// `warning` header: HTTP warning header as per RFC 7234
498const WARNING: &str = "warning";
499
500/// Represents a basic cache status
501/// Used in the custom headers `x-cache` and `x-cache-lookup`
502#[derive(Debug, Copy, Clone)]
503pub enum HitOrMiss {
504    /// Yes, there was a hit
505    HIT,
506    /// No, there was no hit
507    MISS,
508}
509
510impl fmt::Display for HitOrMiss {
511    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
512        match self {
513            Self::HIT => write!(f, "HIT"),
514            Self::MISS => write!(f, "MISS"),
515        }
516    }
517}
518
519/// Represents an HTTP version
520#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
521#[non_exhaustive]
522pub enum HttpVersion {
523    /// HTTP Version 0.9
524    #[serde(rename = "HTTP/0.9")]
525    Http09,
526    /// HTTP Version 1.0
527    #[serde(rename = "HTTP/1.0")]
528    Http10,
529    /// HTTP Version 1.1
530    #[serde(rename = "HTTP/1.1")]
531    Http11,
532    /// HTTP Version 2.0
533    #[serde(rename = "HTTP/2.0")]
534    H2,
535    /// HTTP Version 3.0
536    #[serde(rename = "HTTP/3.0")]
537    H3,
538}
539
540impl fmt::Display for HttpVersion {
541    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
542        match *self {
543            HttpVersion::Http09 => write!(f, "HTTP/0.9"),
544            HttpVersion::Http10 => write!(f, "HTTP/1.0"),
545            HttpVersion::Http11 => write!(f, "HTTP/1.1"),
546            HttpVersion::H2 => write!(f, "HTTP/2.0"),
547            HttpVersion::H3 => write!(f, "HTTP/3.0"),
548        }
549    }
550}
551
552/// Extract a URL from HTTP request parts for cache key generation
553///
554/// This function reconstructs the full URL from the request parts, handling both
555/// HTTP and HTTPS schemes based on the connection type or explicit headers.
556fn extract_url_from_request_parts(parts: &request::Parts) -> Result<Url> {
557    // First check if the URI is already absolute
558    if let Some(_scheme) = parts.uri.scheme() {
559        // URI is absolute, use it directly
560        return url_parse(&parts.uri.to_string())
561            .map_err(|_| -> BoxError { BadHeader.into() });
562    }
563
564    // Get the host header
565    let host = parts
566        .headers
567        .get("host")
568        .ok_or(BadHeader)?
569        .to_str()
570        .map_err(|_| BadHeader)?;
571
572    // Determine scheme based on host and headers
573    let scheme = determine_scheme(host, &parts.headers)?;
574
575    // Create base URL using the URL helper for cross-crate compatibility
576    let mut base_url = url_parse(&format!("{}://{}/", scheme, host))
577        .map_err(|_| -> BoxError { BadHeader.into() })?;
578
579    // Set the path and query from the URI using helpers
580    if let Some(path_and_query) = parts.uri.path_and_query() {
581        url_set_path(&mut base_url, path_and_query.path());
582        if let Some(query) = path_and_query.query() {
583            url_set_query(&mut base_url, Some(query));
584        }
585    }
586
587    Ok(base_url)
588}
589
590/// Determine the appropriate scheme for URL construction
591fn determine_scheme(host: &str, headers: &http::HeaderMap) -> Result<String> {
592    // Check for explicit protocol forwarding header first
593    if let Some(forwarded_proto) = headers.get("x-forwarded-proto") {
594        let proto = forwarded_proto.to_str().map_err(|_| BadHeader)?;
595        return match proto {
596            "http" | "https" => Ok(proto.to_string()),
597            _ => Ok("https".to_string()), // Default to secure for unknown protocols
598        };
599    }
600
601    // Check if this looks like a local development host
602    if host.starts_with("localhost") || host.starts_with("127.0.0.1") {
603        Ok("http".to_string())
604    } else {
605        Ok("https".to_string()) // Default to secure for all other hosts
606    }
607}
608
609/// Represents HTTP headers in either legacy or modern format.
610///
611/// Values are `String`s, so header values that are not valid UTF-8 cannot
612/// be represented and are skipped during conversion from `http::HeaderMap`.
613#[derive(Debug, Clone)]
614pub enum HttpHeaders {
615    /// Modern header representation - allows multiple values per key
616    Modern(HashMap<String, Vec<String>>),
617    /// Legacy header representation - kept for backward compatibility with deserialization
618    #[cfg(feature = "http-headers-compat")]
619    Legacy(HashMap<String, String>),
620}
621
622// Serialize directly as the inner HashMap (no enum variant wrapper)
623// This ensures compatibility: serialized data is just the raw HashMap
624impl Serialize for HttpHeaders {
625    fn serialize<S>(
626        &self,
627        serializer: S,
628    ) -> std::result::Result<S::Ok, S::Error>
629    where
630        S: Serializer,
631    {
632        #[cfg(feature = "http-headers-compat")]
633        {
634            // Always serialize as Legacy format when compat is enabled
635            match self {
636                HttpHeaders::Modern(modern) => {
637                    // Convert Modern to Legacy format by joining values
638                    let legacy: HashMap<String, String> = modern
639                        .iter()
640                        .map(|(k, v)| (k.clone(), v.join(", ")))
641                        .collect();
642                    legacy.serialize(serializer)
643                }
644                HttpHeaders::Legacy(legacy) => legacy.serialize(serializer),
645            }
646        }
647
648        #[cfg(not(feature = "http-headers-compat"))]
649        {
650            match self {
651                HttpHeaders::Modern(modern) => modern.serialize(serializer),
652            }
653        }
654    }
655}
656
657// Deserialize directly as HashMap based on feature flag
658// With http-headers-compat: reads HashMap<String, String> (legacy alpha.2 format)
659// Without http-headers-compat: reads HashMap<String, Vec<String>> (modern format)
660impl<'de> Deserialize<'de> for HttpHeaders {
661    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
662    where
663        D: Deserializer<'de>,
664    {
665        #[cfg(feature = "http-headers-compat")]
666        {
667            let legacy = HashMap::<String, String>::deserialize(deserializer)?;
668            Ok(HttpHeaders::Legacy(legacy))
669        }
670
671        #[cfg(not(feature = "http-headers-compat"))]
672        {
673            let modern =
674                HashMap::<String, Vec<String>>::deserialize(deserializer)?;
675            Ok(HttpHeaders::Modern(modern))
676        }
677    }
678}
679
680impl HttpHeaders {
681    /// Creates a new empty HttpHeaders in modern format
682    pub fn new() -> Self {
683        HttpHeaders::Modern(HashMap::new())
684    }
685
686    /// Inserts a header key-value pair, replacing any existing values for that key
687    /// Keys are normalized to lowercase per RFC 7230
688    pub fn insert(&mut self, key: String, value: String) {
689        let normalized_key = key.to_ascii_lowercase();
690        match self {
691            #[cfg(feature = "http-headers-compat")]
692            HttpHeaders::Legacy(legacy) => {
693                legacy.insert(normalized_key, value);
694            }
695            HttpHeaders::Modern(modern) => {
696                // Replace existing values with a new single-element vec
697                modern.insert(normalized_key, vec![value]);
698            }
699        }
700    }
701
702    /// Appends a header value, preserving existing values for the same key
703    /// Keys are normalized to lowercase per RFC 7230
704    pub fn append(&mut self, key: String, value: String) {
705        let normalized_key = key.to_ascii_lowercase();
706        match self {
707            #[cfg(feature = "http-headers-compat")]
708            HttpHeaders::Legacy(legacy) => {
709                // Legacy format doesn't support multi-value, fall back to insert
710                legacy.insert(normalized_key, value);
711            }
712            HttpHeaders::Modern(modern) => {
713                modern
714                    .entry(normalized_key)
715                    .or_insert_with(Vec::new)
716                    .push(value);
717            }
718        }
719    }
720
721    /// Retrieves the first value for a given header key
722    /// Keys are normalized to lowercase per RFC 7230
723    pub fn get(&self, key: &str) -> Option<&String> {
724        let normalized_key = key.to_ascii_lowercase();
725        match self {
726            #[cfg(feature = "http-headers-compat")]
727            HttpHeaders::Legacy(legacy) => legacy.get(&normalized_key),
728            HttpHeaders::Modern(modern) => {
729                modern.get(&normalized_key).and_then(|vals| vals.first())
730            }
731        }
732    }
733
734    /// Removes a header key and its associated values
735    /// Keys are normalized to lowercase per RFC 7230
736    pub fn remove(&mut self, key: &str) {
737        let normalized_key = key.to_ascii_lowercase();
738        match self {
739            #[cfg(feature = "http-headers-compat")]
740            HttpHeaders::Legacy(legacy) => {
741                legacy.remove(&normalized_key);
742            }
743            HttpHeaders::Modern(modern) => {
744                modern.remove(&normalized_key);
745            }
746        }
747    }
748
749    /// Checks if a header key exists
750    /// Keys are normalized to lowercase per RFC 7230
751    pub fn contains_key(&self, key: &str) -> bool {
752        let normalized_key = key.to_ascii_lowercase();
753        match self {
754            #[cfg(feature = "http-headers-compat")]
755            HttpHeaders::Legacy(legacy) => legacy.contains_key(&normalized_key),
756            HttpHeaders::Modern(modern) => modern.contains_key(&normalized_key),
757        }
758    }
759
760    /// Returns an iterator over the header key-value pairs
761    pub fn iter(&self) -> HttpHeadersIterator<'_> {
762        match self {
763            #[cfg(feature = "http-headers-compat")]
764            HttpHeaders::Legacy(legacy) => {
765                HttpHeadersIterator { inner: legacy.iter().collect(), index: 0 }
766            }
767            HttpHeaders::Modern(modern) => HttpHeadersIterator {
768                inner: modern
769                    .iter()
770                    .flat_map(|(k, vals)| vals.iter().map(move |v| (k, v)))
771                    .collect(),
772                index: 0,
773            },
774        }
775    }
776}
777
778impl From<&http::HeaderMap> for HttpHeaders {
779    fn from(headers: &http::HeaderMap) -> Self {
780        let mut modern_headers = HashMap::new();
781
782        // headers.keys() already yields each unique name exactly once
783        for name in headers.keys() {
784            let values: Vec<String> = headers
785                .get_all(name)
786                .iter()
787                .filter_map(|v| v.to_str().ok())
788                .map(|s| s.to_string())
789                .collect();
790
791            if !values.is_empty() {
792                modern_headers.insert(name.to_string(), values);
793            }
794        }
795
796        HttpHeaders::Modern(modern_headers)
797    }
798}
799
800impl From<HttpHeaders> for HashMap<String, Vec<String>> {
801    fn from(headers: HttpHeaders) -> Self {
802        match headers {
803            #[cfg(feature = "http-headers-compat")]
804            HttpHeaders::Legacy(legacy) => {
805                legacy.into_iter().map(|(k, v)| (k, vec![v])).collect()
806            }
807            HttpHeaders::Modern(modern) => modern,
808        }
809    }
810}
811
812impl Default for HttpHeaders {
813    fn default() -> Self {
814        HttpHeaders::new()
815    }
816}
817
818impl IntoIterator for HttpHeaders {
819    type Item = (String, String);
820    type IntoIter = HttpHeadersIntoIterator;
821
822    fn into_iter(self) -> Self::IntoIter {
823        HttpHeadersIntoIterator {
824            inner: match self {
825                #[cfg(feature = "http-headers-compat")]
826                HttpHeaders::Legacy(legacy) => legacy.into_iter().collect(),
827                HttpHeaders::Modern(modern) => modern
828                    .into_iter()
829                    .flat_map(|(k, vals)| {
830                        vals.into_iter().map(move |v| (k.clone(), v))
831                    })
832                    .collect(),
833            },
834            index: 0,
835        }
836    }
837}
838
839/// Iterator for HttpHeaders
840#[derive(Debug)]
841pub struct HttpHeadersIntoIterator {
842    inner: Vec<(String, String)>,
843    index: usize,
844}
845
846impl Iterator for HttpHeadersIntoIterator {
847    type Item = (String, String);
848
849    fn next(&mut self) -> Option<Self::Item> {
850        if self.index < self.inner.len() {
851            let item = self.inner[self.index].clone();
852            self.index += 1;
853            Some(item)
854        } else {
855            None
856        }
857    }
858}
859
860impl<'a> IntoIterator for &'a HttpHeaders {
861    type Item = (&'a String, &'a String);
862    type IntoIter = HttpHeadersIterator<'a>;
863
864    fn into_iter(self) -> Self::IntoIter {
865        self.iter()
866    }
867}
868
869/// Iterator for HttpHeaders references
870#[derive(Debug)]
871pub struct HttpHeadersIterator<'a> {
872    inner: Vec<(&'a String, &'a String)>,
873    index: usize,
874}
875
876impl<'a> Iterator for HttpHeadersIterator<'a> {
877    type Item = (&'a String, &'a String);
878
879    fn next(&mut self) -> Option<Self::Item> {
880        if self.index < self.inner.len() {
881            let item = self.inner[self.index];
882            self.index += 1;
883            Some(item)
884        } else {
885            None
886        }
887    }
888}
889
890/// A basic generic type that represents an HTTP response
891#[derive(Debug, Clone, Deserialize, Serialize)]
892pub struct HttpResponse {
893    /// HTTP response body
894    pub body: Vec<u8>,
895    /// HTTP response headers
896    pub headers: HttpHeaders,
897    /// HTTP response status code
898    pub status: u16,
899    /// HTTP response url
900    pub url: Url,
901    /// HTTP response version
902    pub version: HttpVersion,
903    /// Metadata
904    #[serde(default)]
905    pub metadata: Option<Vec<u8>>,
906}
907
908impl HttpResponse {
909    /// Returns `http::response::Parts`
910    pub fn parts(&self) -> Result<response::Parts> {
911        let mut converted =
912            response::Builder::new().status(self.status).body(())?;
913        {
914            let headers = converted.headers_mut();
915            for header in &self.headers {
916                headers.append(
917                    http::header::HeaderName::from_str(header.0.as_str())?,
918                    HeaderValue::from_str(header.1.as_str())?,
919                );
920            }
921        }
922        Ok(converted.into_parts().0)
923    }
924
925    /// Returns the status code of the warning header if present
926    #[must_use]
927    fn warning_code(&self) -> Option<usize> {
928        self.headers.get(WARNING).and_then(|hdr| {
929            hdr.as_str().chars().take(3).collect::<String>().parse().ok()
930        })
931    }
932
933    /// Adds a warning header to a response
934    fn add_warning(&mut self, url: &Url, code: usize, message: &str) {
935        // warning    = "warning" ":" 1#warning-value
936        // warning-value = warn-code SP warn-agent SP warn-text [SP warn-date]
937        // warn-code  = 3DIGIT
938        // warn-agent = ( host [ ":" port ] ) | pseudonym
939        //                 ; the name or pseudonym of the server adding
940        //                 ; the warning header, for use in debugging
941        // warn-text  = quoted-string
942        // warn-date  = <"> HTTP-date <">
943        // (https://tools.ietf.org/html/rfc2616#section-14.46)
944        let host = url_host_str(url);
945        // Escape message to prevent header injection and ensure valid HTTP format
946        let escaped_message =
947            message.replace('"', "'").replace(['\n', '\r'], " ");
948        self.headers.insert(
949            WARNING.to_string(),
950            format!(
951                "{} {} \"{}\" \"{}\"",
952                code,
953                host,
954                escaped_message,
955                httpdate::fmt_http_date(SystemTime::now())
956            ),
957        );
958    }
959
960    /// Removes a warning header from a response
961    fn remove_warning(&mut self) {
962        self.headers.remove(WARNING);
963    }
964
965    /// Update the headers from `http::response::Parts`
966    pub fn update_headers(&mut self, parts: &response::Parts) -> Result<()> {
967        // Clear-then-append per key. The From conversion keeps a key only
968        // when at least one of its values converts to a string, so headers
969        // whose values cannot be represented survive untouched.
970        let incoming: HashMap<String, Vec<String>> =
971            HttpHeaders::from(&parts.headers).into();
972        for (name, values) in incoming {
973            self.headers.remove(name.as_str());
974            for value in values {
975                self.headers.append(name.clone(), value);
976            }
977        }
978        Ok(())
979    }
980
981    /// Checks if the Cache-Control header contains the must-revalidate directive
982    #[must_use]
983    fn must_revalidate(&self) -> bool {
984        self.headers.get(CACHE_CONTROL.as_str()).is_some_and(|val| {
985            val.as_str().to_lowercase().contains("must-revalidate")
986        })
987    }
988
989    /// Adds the custom `x-cache` header to the response
990    pub fn cache_status(&mut self, hit_or_miss: HitOrMiss) {
991        self.headers.insert(XCACHE.to_string(), hit_or_miss.to_string());
992    }
993
994    /// Adds the custom `x-cache-lookup` header to the response
995    pub fn cache_lookup_status(&mut self, hit_or_miss: HitOrMiss) {
996        self.headers.insert(XCACHELOOKUP.to_string(), hit_or_miss.to_string());
997    }
998}
999
1000/// A trait providing methods for storing, reading, and removing cache records.
1001pub trait CacheManager: Send + Sync + 'static {
1002    /// Attempts to pull a cached response and related policy from cache.
1003    fn get(
1004        &self,
1005        cache_key: &str,
1006    ) -> impl Future<Output = Result<Option<(HttpResponse, CachePolicy)>>> + Send;
1007    /// Attempts to cache a response and related policy.
1008    fn put(
1009        &self,
1010        cache_key: String,
1011        res: HttpResponse,
1012        policy: CachePolicy,
1013    ) -> impl Future<Output = Result<HttpResponse>> + Send;
1014    /// Attempts to remove a record from cache.
1015    fn delete(
1016        &self,
1017        cache_key: &str,
1018    ) -> impl Future<Output = Result<()>> + Send;
1019}
1020
1021/// A streaming cache manager that supports streaming request/response bodies
1022/// without buffering them in memory. This is ideal for large responses.
1023pub trait StreamingCacheManager: Send + Sync + 'static {
1024    /// The body type used by this cache manager
1025    type Body: http_body::Body + Send + 'static;
1026
1027    /// Attempts to pull a cached response and related policy from cache with streaming body.
1028    fn get(
1029        &self,
1030        cache_key: &str,
1031    ) -> impl Future<Output = Result<Option<(Response<Self::Body>, CachePolicy)>>>
1032           + Send
1033    where
1034        <Self::Body as http_body::Body>::Data: Send,
1035        <Self::Body as http_body::Body>::Error:
1036            Into<StreamingError> + Send + Sync + 'static;
1037
1038    /// Attempts to cache a response with a streaming body and related policy.
1039    ///
1040    /// After `put()` resolves, the cache entry is durable and visible
1041    /// at-or-after full consumption of the returned body. The current
1042    /// implementation commits before returning; callers must not rely on
1043    /// the stronger property.
1044    fn put<B>(
1045        &self,
1046        cache_key: String,
1047        response: Response<B>,
1048        policy: CachePolicy,
1049        request_url: Url,
1050        metadata: Option<Vec<u8>>,
1051    ) -> impl Future<Output = Result<Response<Self::Body>>> + Send
1052    where
1053        B: http_body::Body + Send + 'static,
1054        B::Data: Send,
1055        B::Error: Into<StreamingError>,
1056        <Self::Body as http_body::Body>::Data: Send,
1057        <Self::Body as http_body::Body>::Error:
1058            Into<StreamingError> + Send + Sync + 'static;
1059
1060    /// Update the stored headers, cache policy, and user metadata for an
1061    /// existing entry WITHOUT touching the body file. Used by 304
1062    /// revalidation, where the body is known-unchanged.
1063    ///
1064    /// If `token` is `Some`, the update is applied only when the stored
1065    /// entry still matches that identity; `Ok(false)` means the entry no
1066    /// longer exists or was concurrently replaced — callers should serve
1067    /// the response and skip re-caching.
1068    fn update_metadata(
1069        &self,
1070        cache_key: &str,
1071        headers: &http::HeaderMap,
1072        policy: CachePolicy,
1073        user_metadata: Option<Vec<u8>>,
1074        token: Option<&CacheEntryToken>,
1075    ) -> impl Future<Output = Result<bool>> + Send;
1076
1077    /// Converts a generic body to the manager's body type for non-cacheable responses.
1078    /// This is called when a response should not be cached but still needs to be returned
1079    /// with the correct body type.
1080    fn convert_body<B>(
1081        &self,
1082        response: Response<B>,
1083    ) -> impl Future<Output = Result<Response<Self::Body>>> + Send
1084    where
1085        B: http_body::Body + Send + 'static,
1086        B::Data: Send,
1087        B::Error: Into<StreamingError>,
1088        <Self::Body as http_body::Body>::Data: Send,
1089        <Self::Body as http_body::Body>::Error:
1090            Into<StreamingError> + Send + Sync + 'static;
1091
1092    /// Attempts to remove a record from cache.
1093    fn delete(
1094        &self,
1095        cache_key: &str,
1096    ) -> impl Future<Output = Result<()>> + Send;
1097
1098    /// Creates an empty body of the manager's body type.
1099    /// Used for returning 504 Gateway Timeout responses on OnlyIfCached cache misses.
1100    fn empty_body(&self) -> Self::Body;
1101
1102    /// Convert the manager's body type to a reqwest-compatible bytes stream.
1103    /// This enables efficient streaming without collecting the entire body.
1104    #[cfg(feature = "streaming")]
1105    fn body_to_bytes_stream(
1106        body: Self::Body,
1107    ) -> impl futures_util::Stream<
1108        Item = std::result::Result<
1109            bytes::Bytes,
1110            Box<dyn std::error::Error + Send + Sync>,
1111        >,
1112    > + Send
1113    where
1114        <Self::Body as http_body::Body>::Data: Send,
1115        <Self::Body as http_body::Body>::Error: Send + Sync + 'static;
1116}
1117
1118/// Describes the functionality required for interfacing with HTTP client middleware
1119pub trait Middleware: Send {
1120    /// Allows the cache mode to be overridden.
1121    ///
1122    /// This overrides any cache mode set in the configuration, including cache_mode_fn.
1123    fn overridden_cache_mode(&self) -> Option<CacheMode> {
1124        None
1125    }
1126    /// Determines if the request method is either GET or HEAD
1127    fn is_method_get_head(&self) -> bool;
1128    /// Returns a new cache policy with default options
1129    fn policy(&self, response: &HttpResponse) -> Result<CachePolicy>;
1130    /// Returns a new cache policy with custom options
1131    fn policy_with_options(
1132        &self,
1133        response: &HttpResponse,
1134        options: CacheOptions,
1135    ) -> Result<CachePolicy>;
1136    /// Attempts to update the request headers with the passed `http::request::Parts`
1137    fn update_headers(&mut self, parts: &request::Parts) -> Result<()>;
1138    /// Attempts to force the "no-cache" directive on the request
1139    fn force_no_cache(&mut self) -> Result<()>;
1140    /// Attempts to construct `http::request::Parts` from the request
1141    fn parts(&self) -> Result<request::Parts>;
1142    /// Attempts to determine the requested url
1143    fn url(&self) -> Result<Url>;
1144    /// Attempts to determine the request method
1145    fn method(&self) -> Result<String>;
1146    /// Attempts to fetch an upstream resource and return an [`HttpResponse`]
1147    fn remote_fetch(
1148        &mut self,
1149    ) -> impl Future<Output = Result<HttpResponse>> + Send;
1150}
1151
1152/// An interface for HTTP caching that works with composable middleware patterns
1153/// like Tower. This trait separates the concerns of request analysis, cache lookup,
1154/// and response processing into discrete steps.
1155pub trait HttpCacheInterface<B = Vec<u8>>: Send + Sync {
1156    /// Analyze a request to determine cache behavior
1157    fn analyze_request(
1158        &self,
1159        parts: &request::Parts,
1160        mode_override: Option<CacheMode>,
1161    ) -> Result<CacheAnalysis>;
1162
1163    /// Look up a cached response for the given cache key
1164    #[allow(async_fn_in_trait)]
1165    async fn lookup_cached_response(
1166        &self,
1167        key: &str,
1168    ) -> Result<Option<(HttpResponse, CachePolicy)>>;
1169
1170    /// Process a fresh response from upstream and potentially cache it
1171    #[allow(async_fn_in_trait)]
1172    async fn process_response(
1173        &self,
1174        analysis: CacheAnalysis,
1175        response: Response<B>,
1176        metadata: Option<Vec<u8>>,
1177    ) -> Result<Response<B>>;
1178
1179    /// Update request headers for conditional requests (e.g., If-None-Match)
1180    fn prepare_conditional_request(
1181        &self,
1182        parts: &mut request::Parts,
1183        cached_response: &HttpResponse,
1184        policy: &CachePolicy,
1185    ) -> Result<()>;
1186
1187    /// Handle a 304 Not Modified response by returning the cached response
1188    #[allow(async_fn_in_trait)]
1189    async fn handle_not_modified(
1190        &self,
1191        cached_response: HttpResponse,
1192        fresh_parts: &response::Parts,
1193    ) -> Result<HttpResponse>;
1194}
1195
1196/// Streaming version of the HTTP cache interface that supports streaming request/response bodies
1197/// without buffering them in memory. This is ideal for large responses or when memory usage
1198/// is a concern.
1199pub trait HttpCacheStreamInterface: Send + Sync {
1200    /// The body type used by this cache implementation
1201    type Body: http_body::Body + Send + 'static;
1202
1203    /// Analyze a request to determine cache behavior
1204    fn analyze_request(
1205        &self,
1206        parts: &request::Parts,
1207        mode_override: Option<CacheMode>,
1208    ) -> Result<CacheAnalysis>;
1209
1210    /// Look up a cached response for the given cache key, returning a streaming body
1211    #[allow(async_fn_in_trait)]
1212    async fn lookup_cached_response(
1213        &self,
1214        key: &str,
1215    ) -> Result<Option<(Response<Self::Body>, CachePolicy)>>
1216    where
1217        <Self::Body as http_body::Body>::Data: Send,
1218        <Self::Body as http_body::Body>::Error:
1219            Into<StreamingError> + Send + Sync + 'static;
1220
1221    /// Process a fresh response from upstream and potentially cache it with streaming support
1222    #[allow(async_fn_in_trait)]
1223    async fn process_response<B>(
1224        &self,
1225        analysis: CacheAnalysis,
1226        response: Response<B>,
1227        metadata: Option<Vec<u8>>,
1228    ) -> Result<Response<Self::Body>>
1229    where
1230        B: http_body::Body + Send + 'static,
1231        B::Data: Send,
1232        B::Error: Into<StreamingError>,
1233        <Self::Body as http_body::Body>::Data: Send,
1234        <Self::Body as http_body::Body>::Error:
1235            Into<StreamingError> + Send + Sync + 'static;
1236
1237    /// Update request headers for conditional requests (e.g., If-None-Match)
1238    fn prepare_conditional_request(
1239        &self,
1240        parts: &mut request::Parts,
1241        cached_response: &Response<Self::Body>,
1242        policy: &CachePolicy,
1243    ) -> Result<()>;
1244
1245    /// Handle a 304 Not Modified response by returning the cached response
1246    #[allow(async_fn_in_trait)]
1247    async fn handle_not_modified(
1248        &self,
1249        cached_response: Response<Self::Body>,
1250        fresh_parts: &response::Parts,
1251    ) -> Result<Response<Self::Body>>
1252    where
1253        <Self::Body as http_body::Body>::Data: Send,
1254        <Self::Body as http_body::Body>::Error:
1255            Into<StreamingError> + Send + Sync + 'static;
1256}
1257
1258/// Analysis result for a request, containing cache key and caching decisions
1259#[derive(Debug, Clone)]
1260pub struct CacheAnalysis {
1261    /// The cache key for this request
1262    pub cache_key: String,
1263    /// Whether this request should be cached
1264    pub should_cache: bool,
1265    /// The effective cache mode for this request
1266    pub cache_mode: CacheMode,
1267    /// Keys to bust from cache before processing
1268    pub cache_bust_keys: Vec<String>,
1269    /// The request parts for policy creation
1270    pub request_parts: request::Parts,
1271    /// Whether this is a GET or HEAD request
1272    pub is_get_head: bool,
1273}
1274
1275/// Describes the type of fetch to perform when the streaming cache
1276/// orchestrator needs to make a network request.
1277///
1278/// The streaming `run` method accepts a callback `FnOnce(FetchRequest) -> Fut`
1279/// rather than an `impl Middleware`, so this enum tells the caller whether to
1280/// issue a fresh request or a conditional (revalidation) request.
1281#[derive(Debug)]
1282pub enum FetchRequest {
1283    /// A fresh fetch (cache miss or forced).
1284    Fresh,
1285    /// A fresh fetch where the caller should add `cache-control: no-cache`
1286    /// to the outgoing request.  Used for [`CacheMode::NoCache`] to signal
1287    /// upstream caches that they must revalidate.
1288    FreshNoCache,
1289    /// A conditional fetch for revalidation.  The contained
1290    /// [`request::Parts`] carry the conditional headers (e.g.
1291    /// `If-None-Match`, `If-Modified-Since`) that should be merged into
1292    /// the outgoing request before sending it.  Callers should replace
1293    /// existing headers of the same name (insert, not append).
1294    Conditional(Box<request::Parts>),
1295}
1296
1297/// Cache mode determines how the HTTP cache behaves for requests.
1298///
1299/// These modes are similar to [make-fetch-happen cache options](https://github.com/npm/make-fetch-happen#--optscache)
1300/// and provide fine-grained control over caching behavior.
1301///
1302/// # Examples
1303///
1304/// ```rust
1305/// # #[cfg(feature = "manager-cacache")]
1306/// # fn main() {
1307/// use http_cache::{CacheMode, HttpCache, CACacheManager, HttpCacheOptions};
1308///
1309/// let manager = CACacheManager::new("./cache".into(), true);
1310///
1311/// // Use different cache modes for different scenarios
1312/// let default_cache = HttpCache {
1313///     mode: CacheMode::Default,        // Standard HTTP caching rules
1314///     manager: manager.clone(),
1315///     options: HttpCacheOptions::default(),
1316/// };
1317///
1318/// let force_cache = HttpCache {
1319///     mode: CacheMode::ForceCache,     // Cache everything, ignore staleness
1320///     manager: manager.clone(),
1321///     options: HttpCacheOptions::default(),
1322/// };
1323///
1324/// let no_cache = HttpCache {
1325///     mode: CacheMode::NoStore,        // Never cache anything
1326///     manager,
1327///     options: HttpCacheOptions::default(),
1328/// };
1329/// # }
1330/// # #[cfg(not(feature = "manager-cacache"))]
1331/// # fn main() {}
1332/// ```
1333#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
1334pub enum CacheMode {
1335    /// Standard HTTP caching behavior (recommended for most use cases).
1336    ///
1337    /// This mode:
1338    /// - Checks the cache for fresh responses and uses them
1339    /// - Makes conditional requests for stale responses (revalidation)
1340    /// - Makes normal requests when no cached response exists
1341    /// - Updates the cache with new responses
1342    /// - Falls back to stale responses if revalidation fails
1343    ///
1344    /// This is the most common mode and follows HTTP caching standards closely.
1345    #[default]
1346    Default,
1347
1348    /// Completely bypasses the cache.
1349    ///
1350    /// This mode:
1351    /// - Never reads from the cache
1352    /// - Never writes to the cache
1353    /// - Always makes fresh network requests
1354    ///
1355    /// Use this when you need to ensure every request goes to the origin server.
1356    NoStore,
1357
1358    /// Bypasses cache on request but updates cache with response.
1359    ///
1360    /// This mode:
1361    /// - Ignores any cached responses
1362    /// - Always makes a fresh network request
1363    /// - Updates the cache with the response
1364    ///
1365    /// Equivalent to a "hard refresh" - useful when you know the cache is stale.
1366    Reload,
1367
1368    /// Always revalidates cached responses.
1369    ///
1370    /// This mode:
1371    /// - Makes conditional requests if a cached response exists
1372    /// - Makes normal requests if no cached response exists
1373    /// - Updates the cache with responses
1374    ///
1375    /// Use this when you want to ensure content freshness while still benefiting
1376    /// from conditional requests (304 Not Modified responses).
1377    NoCache,
1378
1379    /// Uses cached responses regardless of staleness.
1380    ///
1381    /// This mode:
1382    /// - Uses any cached response, even if stale
1383    /// - Makes network requests only when no cached response exists
1384    /// - Updates the cache with new responses
1385    ///
1386    /// Useful for offline scenarios or when performance is more important than freshness.
1387    ForceCache,
1388
1389    /// Only serves from cache, never makes network requests.
1390    ///
1391    /// This mode:
1392    /// - Uses any cached response, even if stale
1393    /// - Returns an error if no cached response exists
1394    /// - Never makes network requests
1395    ///
1396    /// Use this for offline-only scenarios or when you want to guarantee
1397    /// no network traffic.
1398    OnlyIfCached,
1399
1400    /// Ignores HTTP caching rules and caches everything.
1401    ///
1402    /// This mode:
1403    /// - Caches all 200 responses regardless of cache-control headers
1404    /// - Uses cached responses regardless of staleness
1405    /// - Makes network requests when no cached response exists
1406    ///
1407    /// Use this when you want aggressive caching and don't want to respect
1408    /// server cache directives.
1409    IgnoreRules,
1410}
1411
1412impl TryFrom<http::Version> for HttpVersion {
1413    type Error = BoxError;
1414
1415    fn try_from(value: http::Version) -> Result<Self> {
1416        Ok(match value {
1417            http::Version::HTTP_09 => Self::Http09,
1418            http::Version::HTTP_10 => Self::Http10,
1419            http::Version::HTTP_11 => Self::Http11,
1420            http::Version::HTTP_2 => Self::H2,
1421            http::Version::HTTP_3 => Self::H3,
1422            _ => return Err(Box::new(BadVersion)),
1423        })
1424    }
1425}
1426
1427impl From<HttpVersion> for http::Version {
1428    fn from(value: HttpVersion) -> Self {
1429        match value {
1430            HttpVersion::Http09 => Self::HTTP_09,
1431            HttpVersion::Http10 => Self::HTTP_10,
1432            HttpVersion::Http11 => Self::HTTP_11,
1433            HttpVersion::H2 => Self::HTTP_2,
1434            HttpVersion::H3 => Self::HTTP_3,
1435        }
1436    }
1437}
1438
1439#[cfg(feature = "http-types")]
1440impl TryFrom<http_types::Version> for HttpVersion {
1441    type Error = BoxError;
1442
1443    fn try_from(value: http_types::Version) -> Result<Self> {
1444        Ok(match value {
1445            http_types::Version::Http0_9 => Self::Http09,
1446            http_types::Version::Http1_0 => Self::Http10,
1447            http_types::Version::Http1_1 => Self::Http11,
1448            http_types::Version::Http2_0 => Self::H2,
1449            http_types::Version::Http3_0 => Self::H3,
1450            _ => return Err(Box::new(BadVersion)),
1451        })
1452    }
1453}
1454
1455#[cfg(feature = "http-types")]
1456impl From<HttpVersion> for http_types::Version {
1457    fn from(value: HttpVersion) -> Self {
1458        match value {
1459            HttpVersion::Http09 => Self::Http0_9,
1460            HttpVersion::Http10 => Self::Http1_0,
1461            HttpVersion::Http11 => Self::Http1_1,
1462            HttpVersion::H2 => Self::Http2_0,
1463            HttpVersion::H3 => Self::Http3_0,
1464        }
1465    }
1466}
1467
1468/// Options struct provided by
1469/// [`http-cache-semantics`](https://github.com/kornelski/rusty-http-cache-semantics).
1470pub use http_cache_semantics::CacheOptions;
1471
1472/// A closure that takes [`http::request::Parts`] and returns a [`String`].
1473/// By default, the cache key is a combination of the request method and uri with a colon in between.
1474pub type CacheKey = Arc<dyn Fn(&request::Parts) -> String + Send + Sync>;
1475
1476/// A closure that takes [`http::request::Parts`] and returns a [`CacheMode`]
1477pub type CacheModeFn = Arc<dyn Fn(&request::Parts) -> CacheMode + Send + Sync>;
1478
1479/// A closure that takes [`http::request::Parts`], [`HttpResponse`] and returns a [`CacheMode`] to override caching behavior based on the response
1480pub type ResponseCacheModeFn = Arc<
1481    dyn Fn(&request::Parts, &HttpResponse) -> Option<CacheMode> + Send + Sync,
1482>;
1483
1484/// 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.
1485/// An empty vector means that no cache busting will be performed.
1486pub type CacheBust = Arc<
1487    dyn Fn(&request::Parts, &Option<CacheKey>, &str) -> Vec<String>
1488        + Send
1489        + Sync,
1490>;
1491
1492/// Type alias for metadata stored alongside cached responses.
1493/// Users are responsible for serialization/deserialization of this data.
1494pub type HttpCacheMetadata = Vec<u8>;
1495
1496/// A closure that takes [`http::request::Parts`] and [`http::response::Parts`] and returns optional metadata to store with the cached response.
1497/// This allows middleware to compute and store additional information alongside cached responses.
1498pub type MetadataProvider = Arc<
1499    dyn Fn(&request::Parts, &response::Parts) -> Option<HttpCacheMetadata>
1500        + Send
1501        + Sync,
1502>;
1503
1504/// A closure that takes a mutable reference to [`HttpResponse`] and modifies it before caching.
1505pub type ModifyResponse = Arc<dyn Fn(&mut HttpResponse) + Send + Sync>;
1506
1507/// Configuration options for customizing HTTP cache behavior on a per-request basis.
1508///
1509/// This struct allows you to override default caching behavior for individual requests
1510/// by providing custom cache options, cache keys, cache modes, and cache busting logic.
1511///
1512/// # Examples
1513///
1514/// ## Basic Custom Cache Key
1515/// ```rust
1516/// use http_cache::{HttpCacheOptions, CacheKey};
1517/// use http::request::Parts;
1518/// use std::sync::Arc;
1519///
1520/// let options = HttpCacheOptions {
1521///     cache_key: Some(Arc::new(|parts: &Parts| {
1522///         format!("custom:{}:{}", parts.method, parts.uri.path())
1523///     })),
1524///     ..Default::default()
1525/// };
1526/// ```
1527///
1528/// ## Custom Cache Mode per Request
1529/// ```rust
1530/// use http_cache::{HttpCacheOptions, CacheMode, CacheModeFn};
1531/// use http::request::Parts;
1532/// use std::sync::Arc;
1533///
1534/// let options = HttpCacheOptions {
1535///     cache_mode_fn: Some(Arc::new(|parts: &Parts| {
1536///         if parts.headers.contains_key("x-no-cache") {
1537///             CacheMode::NoStore
1538///         } else {
1539///             CacheMode::Default
1540///         }
1541///     })),
1542///     ..Default::default()
1543/// };
1544/// ```
1545///
1546/// ## Response-Based Cache Mode Override
1547/// ```rust
1548/// use http_cache::{HttpCacheOptions, ResponseCacheModeFn, CacheMode};
1549/// use http::request::Parts;
1550/// use http_cache::HttpResponse;
1551/// use std::sync::Arc;
1552///
1553/// let options = HttpCacheOptions {
1554///     response_cache_mode_fn: Some(Arc::new(|_parts: &Parts, response: &HttpResponse| {
1555///         // Force cache 2xx responses even if headers say not to cache
1556///         if response.status >= 200 && response.status < 300 {
1557///             Some(CacheMode::ForceCache)
1558///         } else if response.status == 429 { // Rate limited
1559///             Some(CacheMode::NoStore) // Don't cache rate limit responses
1560///         } else {
1561///             None // Use default behavior
1562///         }
1563///     })),
1564///     ..Default::default()
1565/// };
1566/// ```
1567///
1568/// ## Content-Type Based Cache Mode Override
1569/// ```rust
1570/// use http_cache::{HttpCacheOptions, ResponseCacheModeFn, CacheMode};
1571/// use http::request::Parts;
1572/// use http_cache::HttpResponse;
1573/// use std::sync::Arc;
1574///
1575/// let options = HttpCacheOptions {
1576///     response_cache_mode_fn: Some(Arc::new(|_parts: &Parts, response: &HttpResponse| {
1577///         // Cache different content types with different strategies
1578///         if let Some(content_type) = response.headers.get("content-type") {
1579///             match content_type.as_str() {
1580///                 ct if ct.starts_with("application/json") => Some(CacheMode::ForceCache),
1581///                 ct if ct.starts_with("image/") => Some(CacheMode::Default),
1582///                 ct if ct.starts_with("text/html") => Some(CacheMode::NoStore),
1583///                 _ => None, // Use default behavior for other types
1584///             }
1585///         } else {
1586///             Some(CacheMode::NoStore) // No content-type = don't cache
1587///         }
1588///     })),
1589///     ..Default::default()
1590/// };
1591/// ```
1592///
1593/// ## Cache Busting for Related Resources
1594/// ```rust
1595/// use http_cache::{HttpCacheOptions, CacheBust, CacheKey};
1596/// use http::request::Parts;
1597/// use std::sync::Arc;
1598///
1599/// let options = HttpCacheOptions {
1600///     cache_bust: Some(Arc::new(|parts: &Parts, _cache_key: &Option<CacheKey>, _uri: &str| {
1601///         if parts.method == "POST" && parts.uri.path().starts_with("/api/users") {
1602///             vec![
1603///                 "GET:/api/users".to_string(),
1604///                 "GET:/api/users/list".to_string(),
1605///             ]
1606///         } else {
1607///             vec![]
1608///         }
1609///     })),
1610///     ..Default::default()
1611/// };
1612/// ```
1613///
1614/// ## Storing Metadata with Cached Responses
1615/// ```rust
1616/// use http_cache::{HttpCacheOptions, MetadataProvider};
1617/// use http::{request, response};
1618/// use std::sync::Arc;
1619///
1620/// let options = HttpCacheOptions {
1621///     metadata_provider: Some(Arc::new(|request_parts: &request::Parts, response_parts: &response::Parts| {
1622///         // Store computed information with the cached response
1623///         let content_type = response_parts
1624///             .headers
1625///             .get("content-type")
1626///             .and_then(|v| v.to_str().ok())
1627///             .unwrap_or("unknown");
1628///
1629///         // Return serialized metadata (users handle serialization)
1630///         Some(format!("path={};content-type={}", request_parts.uri.path(), content_type).into_bytes())
1631///     })),
1632///     ..Default::default()
1633/// };
1634/// ```
1635#[derive(Clone)]
1636pub struct HttpCacheOptions {
1637    /// Override the default cache options.
1638    pub cache_options: Option<CacheOptions>,
1639    /// Override the default cache key generator.
1640    ///
1641    /// **Note:** Custom closures receive only `&request::Parts` and do not receive
1642    /// the `override_method` parameter used for cache invalidation. This means
1643    /// RFC 7234 section 4.4 cross-method invalidation (e.g., a POST invalidating
1644    /// cached GET/HEAD entries) will not work correctly unless your closure
1645    /// differentiates keys by HTTP method.
1646    pub cache_key: Option<CacheKey>,
1647    /// Override the default cache mode.
1648    pub cache_mode_fn: Option<CacheModeFn>,
1649    /// Override cache behavior based on the response received.
1650    /// This function is called after receiving a response and can override
1651    /// the cache mode for that specific response. Returning `None` means
1652    /// use the default cache mode. This allows fine-grained control over
1653    /// caching behavior based on response status, headers, or content.
1654    pub response_cache_mode_fn: Option<ResponseCacheModeFn>,
1655    /// Bust the caches of the returned keys.
1656    pub cache_bust: Option<CacheBust>,
1657    /// Modifies the response before storing it in the cache.
1658    pub modify_response: Option<ModifyResponse>,
1659    /// Determines if the cache status headers should be added to the response.
1660    pub cache_status_headers: bool,
1661    /// Maximum time-to-live for cached responses.
1662    /// When set, this overrides any longer cache durations specified by the server.
1663    /// Particularly useful with `CacheMode::IgnoreRules` to provide expiration control.
1664    pub max_ttl: Option<Duration>,
1665    /// Rate limiter that applies only on cache misses.
1666    /// When enabled, requests that result in cache hits are returned immediately,
1667    /// while cache misses are rate limited before making network requests.
1668    /// This provides the optimal behavior for web scrapers and similar applications.
1669    #[cfg(feature = "rate-limiting")]
1670    pub rate_limiter: Option<Arc<dyn CacheAwareRateLimiter>>,
1671    /// Optional callback to provide metadata to store alongside cached responses.
1672    /// The callback receives request and response parts and can return metadata bytes.
1673    /// This is useful for storing computed information that should be associated with
1674    /// cached responses without recomputation on cache hits.
1675    pub metadata_provider: Option<MetadataProvider>,
1676}
1677
1678impl Default for HttpCacheOptions {
1679    fn default() -> Self {
1680        Self {
1681            cache_options: None,
1682            cache_key: None,
1683            cache_mode_fn: None,
1684            response_cache_mode_fn: None,
1685            cache_bust: None,
1686            modify_response: None,
1687            cache_status_headers: true,
1688            max_ttl: None,
1689            #[cfg(feature = "rate-limiting")]
1690            rate_limiter: None,
1691            metadata_provider: None,
1692        }
1693    }
1694}
1695
1696impl Debug for HttpCacheOptions {
1697    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1698        #[cfg(feature = "rate-limiting")]
1699        {
1700            f.debug_struct("HttpCacheOptions")
1701                .field("cache_options", &self.cache_options)
1702                .field("cache_key", &"Fn(&request::Parts) -> String")
1703                .field("cache_mode_fn", &"Fn(&request::Parts) -> CacheMode")
1704                .field(
1705                    "response_cache_mode_fn",
1706                    &"Fn(&request::Parts, &HttpResponse) -> Option<CacheMode>",
1707                )
1708                .field("cache_bust", &"Fn(&request::Parts) -> Vec<String>")
1709                .field("modify_response", &"Fn(&mut ModifyResponse)")
1710                .field("cache_status_headers", &self.cache_status_headers)
1711                .field("max_ttl", &self.max_ttl)
1712                .field("rate_limiter", &"Option<CacheAwareRateLimiter>")
1713                .field(
1714                    "metadata_provider",
1715                    &"Fn(&request::Parts, &response::Parts) -> Option<Vec<u8>>",
1716                )
1717                .finish()
1718        }
1719
1720        #[cfg(not(feature = "rate-limiting"))]
1721        {
1722            f.debug_struct("HttpCacheOptions")
1723                .field("cache_options", &self.cache_options)
1724                .field("cache_key", &"Fn(&request::Parts) -> String")
1725                .field("cache_mode_fn", &"Fn(&request::Parts) -> CacheMode")
1726                .field(
1727                    "response_cache_mode_fn",
1728                    &"Fn(&request::Parts, &HttpResponse) -> Option<CacheMode>",
1729                )
1730                .field("cache_bust", &"Fn(&request::Parts) -> Vec<String>")
1731                .field("modify_response", &"Fn(&mut ModifyResponse)")
1732                .field("cache_status_headers", &self.cache_status_headers)
1733                .field("max_ttl", &self.max_ttl)
1734                .field(
1735                    "metadata_provider",
1736                    &"Fn(&request::Parts, &response::Parts) -> Option<Vec<u8>>",
1737                )
1738                .finish()
1739        }
1740    }
1741}
1742
1743impl HttpCacheOptions {
1744    fn create_cache_key(
1745        &self,
1746        parts: &request::Parts,
1747        override_method: Option<&str>,
1748    ) -> String {
1749        if let Some(cache_key) = &self.cache_key {
1750            cache_key(parts)
1751        } else {
1752            format!(
1753                "{}:{}",
1754                override_method.unwrap_or_else(|| parts.method.as_str()),
1755                parts.uri
1756            )
1757        }
1758    }
1759
1760    /// Helper function for other crates to generate cache keys for invalidation
1761    /// This ensures consistent cache key generation across all implementations
1762    pub fn create_cache_key_for_invalidation(
1763        &self,
1764        parts: &request::Parts,
1765        method_override: &str,
1766    ) -> String {
1767        self.create_cache_key(parts, Some(method_override))
1768    }
1769
1770    /// Converts HttpResponse to http::Response with the given body type
1771    pub fn http_response_to_response<B>(
1772        http_response: &HttpResponse,
1773        body: B,
1774    ) -> Result<Response<B>> {
1775        let mut response_builder = Response::builder()
1776            .status(http_response.status)
1777            .version(http_response.version.into());
1778
1779        for (name, value) in &http_response.headers {
1780            if let (Ok(header_name), Ok(header_value)) =
1781                (name.parse::<http::HeaderName>(), value.parse::<HeaderValue>())
1782            {
1783                response_builder =
1784                    response_builder.header(header_name, header_value);
1785            }
1786        }
1787
1788        Ok(response_builder.body(body)?)
1789    }
1790
1791    /// Converts response parts to HttpResponse format for cache mode evaluation
1792    fn parts_to_http_response(
1793        &self,
1794        parts: &response::Parts,
1795        request_parts: &request::Parts,
1796        metadata: Option<Vec<u8>>,
1797    ) -> Result<HttpResponse> {
1798        Ok(HttpResponse {
1799            body: vec![], // We don't need the full body for cache mode decision
1800            headers: (&parts.headers).into(),
1801            status: parts.status.as_u16(),
1802            url: extract_url_from_request_parts(request_parts)?,
1803            version: parts.version.try_into()?,
1804            metadata,
1805        })
1806    }
1807
1808    /// Evaluates response-based cache mode override
1809    fn evaluate_response_cache_mode(
1810        &self,
1811        request_parts: &request::Parts,
1812        http_response: &HttpResponse,
1813        original_mode: CacheMode,
1814    ) -> CacheMode {
1815        if let Some(response_cache_mode_fn) = &self.response_cache_mode_fn {
1816            if let Some(override_mode) =
1817                response_cache_mode_fn(request_parts, http_response)
1818            {
1819                return override_mode;
1820            }
1821        }
1822        original_mode
1823    }
1824
1825    /// Generates metadata for a response using the metadata_provider callback if configured
1826    pub fn generate_metadata(
1827        &self,
1828        request_parts: &request::Parts,
1829        response_parts: &response::Parts,
1830    ) -> Option<HttpCacheMetadata> {
1831        self.metadata_provider
1832            .as_ref()
1833            .and_then(|provider| provider(request_parts, response_parts))
1834    }
1835
1836    /// Modifies the response before caching if a modifier function is provided
1837    pub fn modify_response_before_caching(&self, response: &mut HttpResponse) {
1838        if let Some(modify_response) = &self.modify_response {
1839            modify_response(response);
1840        }
1841    }
1842
1843    /// Creates a cache policy for the given request and response
1844    fn create_cache_policy(
1845        &self,
1846        request_parts: &request::Parts,
1847        response_parts: &response::Parts,
1848    ) -> CachePolicy {
1849        let cache_options = self.cache_options.unwrap_or_default();
1850
1851        // If max_ttl is specified, we need to modify the response headers to enforce it
1852        if let Some(max_ttl) = self.max_ttl {
1853            // Parse existing cache-control header
1854            let cache_control = response_parts
1855                .headers
1856                .get("cache-control")
1857                .and_then(|v| v.to_str().ok())
1858                .unwrap_or("");
1859
1860            // Extract existing max-age if present
1861            let existing_max_age =
1862                cache_control.split(',').find_map(|directive| {
1863                    let directive = directive.trim();
1864                    if directive.starts_with("max-age=") {
1865                        directive.strip_prefix("max-age=")?.parse::<u64>().ok()
1866                    } else {
1867                        None
1868                    }
1869                });
1870
1871            // Convert max_ttl to seconds
1872            let max_ttl_seconds = max_ttl.as_secs();
1873
1874            // Apply max_ttl by setting max-age to the minimum of existing max-age and max_ttl
1875            let effective_max_age = match existing_max_age {
1876                Some(existing) => std::cmp::min(existing, max_ttl_seconds),
1877                None => max_ttl_seconds,
1878            };
1879
1880            // Build new cache-control header
1881            let mut new_directives = Vec::new();
1882
1883            // Add non-max-age directives from existing cache-control
1884            for directive in cache_control.split(',').map(|d| d.trim()) {
1885                if !directive.starts_with("max-age=") && !directive.is_empty() {
1886                    new_directives.push(directive.to_string());
1887                }
1888            }
1889
1890            // Add our effective max-age
1891            new_directives.push(format!("max-age={}", effective_max_age));
1892
1893            let new_cache_control = new_directives.join(", ");
1894
1895            // Create modified response parts - we have to clone since response::Parts has private fields
1896            let mut modified_response_parts = response_parts.clone();
1897            modified_response_parts.headers.insert(
1898                "cache-control",
1899                HeaderValue::from_str(&new_cache_control)
1900                    .unwrap_or_else(|_| HeaderValue::from_static("max-age=0")),
1901            );
1902
1903            CachePolicy::new_options(
1904                request_parts,
1905                &modified_response_parts,
1906                SystemTime::now(),
1907                cache_options,
1908            )
1909        } else {
1910            CachePolicy::new_options(
1911                request_parts,
1912                response_parts,
1913                SystemTime::now(),
1914                cache_options,
1915            )
1916        }
1917    }
1918
1919    /// Determines if a response should be cached based on cache mode and HTTP semantics
1920    fn should_cache_response(
1921        &self,
1922        effective_cache_mode: CacheMode,
1923        http_response: &HttpResponse,
1924        is_get_head: bool,
1925        policy: &CachePolicy,
1926    ) -> bool {
1927        // HTTP status codes that are cacheable by default (RFC 7234)
1928        let is_cacheable_status = matches!(
1929            http_response.status,
1930            200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
1931        );
1932
1933        if is_cacheable_status {
1934            match effective_cache_mode {
1935                CacheMode::ForceCache => is_get_head,
1936                CacheMode::IgnoreRules => true,
1937                CacheMode::NoStore => false,
1938                _ => is_get_head && policy.is_storable(),
1939            }
1940        } else {
1941            false
1942        }
1943    }
1944
1945    /// Common request analysis logic shared between streaming and non-streaming implementations
1946    fn analyze_request_internal(
1947        &self,
1948        parts: &request::Parts,
1949        mode_override: Option<CacheMode>,
1950        default_mode: CacheMode,
1951    ) -> Result<CacheAnalysis> {
1952        let effective_mode = mode_override
1953            .or_else(|| self.cache_mode_fn.as_ref().map(|f| f(parts)))
1954            .unwrap_or(default_mode);
1955
1956        let is_get_head = parts.method == "GET" || parts.method == "HEAD";
1957        let should_cache = effective_mode == CacheMode::IgnoreRules
1958            || (is_get_head && effective_mode != CacheMode::NoStore);
1959
1960        let cache_key = self.create_cache_key(parts, None);
1961
1962        let cache_bust_keys = if let Some(cache_bust) = &self.cache_bust {
1963            cache_bust(parts, &self.cache_key, &cache_key)
1964        } else {
1965            Vec::new()
1966        };
1967
1968        Ok(CacheAnalysis {
1969            cache_key,
1970            should_cache,
1971            cache_mode: effective_mode,
1972            cache_bust_keys,
1973            request_parts: parts.clone(),
1974            is_get_head,
1975        })
1976    }
1977}
1978
1979/// Caches requests according to http spec.
1980#[derive(Debug, Clone)]
1981pub struct HttpCache<T: CacheManager> {
1982    /// Determines the manager behavior.
1983    pub mode: CacheMode,
1984    /// Manager instance that implements the [`CacheManager`] trait.
1985    /// By default, a manager implementation with [`cacache`](https://github.com/zkat/cacache-rs)
1986    /// as the backend has been provided, see [`CACacheManager`].
1987    pub manager: T,
1988    /// Override the default cache options.
1989    pub options: HttpCacheOptions,
1990}
1991
1992/// Wrapper for user metadata stored in response extensions during cache reads.
1993/// Used to preserve metadata through 304 re-cache operations so that
1994/// `StreamingCacheManager::put` receives the original metadata instead of
1995/// regenerating it (which may produce different or empty results).
1996#[derive(Debug, Clone)]
1997pub(crate) struct CachedUserMetadata(pub Option<Vec<u8>>);
1998
1999/// Request method of the request that produced a response, attached to the
2000/// response's extensions by the streaming orchestrator before it calls
2001/// [`StreamingCacheManager::put`]. Managers use it to special-case HEAD:
2002/// a HEAD response's `Content-Length` describes the entity, not the
2003/// (empty) stored body (RFC 9110 §8.6), so size/completeness checks that
2004/// compare received bytes against `Content-Length` must be skipped.
2005#[derive(Clone, Debug)]
2006pub struct CachedRequestMethod(pub http::Method);
2007
2008/// Opaque identity of the specific stored entry revision a response was
2009/// served from, attached to responses returned by
2010/// [`StreamingCacheManager::get`]. Passing it back to `update_metadata`
2011/// lets the manager refuse to apply a metadata update to an entry that was
2012/// concurrently replaced (which would otherwise staple one revision's
2013/// headers onto another revision's body).
2014#[derive(Clone, Debug, PartialEq, Eq)]
2015pub struct CacheEntryToken(pub Vec<u8>);
2016
2017/// Streaming version of HTTP cache that supports streaming request/response bodies
2018/// without buffering them in memory.
2019#[derive(Debug, Clone)]
2020pub struct HttpStreamingCache<T: StreamingCacheManager> {
2021    /// Determines the manager behavior.
2022    pub mode: CacheMode,
2023    /// Manager instance that implements the [`StreamingCacheManager`] trait.
2024    pub manager: T,
2025    /// Override the default cache options.
2026    pub options: HttpCacheOptions,
2027}
2028
2029// ============================================================================
2030// Helper functions for working with warning headers on http::Response
2031// ============================================================================
2032
2033/// Extracts the warning code from an `http::Response`'s warning header, if
2034/// present.  Returns the 3-digit warn-code as a `usize`.
2035fn response_warning_code<B>(response: &Response<B>) -> Option<usize> {
2036    response
2037        .headers()
2038        .get(WARNING)
2039        .and_then(|hdr| hdr.to_str().ok())
2040        .and_then(|s| s.chars().take(3).collect::<String>().parse().ok())
2041}
2042
2043/// Adds an RFC 2616 §14.46 warning header to an `http::Response`.
2044fn response_add_warning<B>(
2045    response: &mut Response<B>,
2046    url: &Url,
2047    code: usize,
2048    message: &str,
2049) {
2050    let host = url_host_str(url);
2051    let escaped_message = message.replace('"', "'").replace(['\n', '\r'], " ");
2052    let value = format!(
2053        "{} {} \"{}\" \"{}\"",
2054        code,
2055        host,
2056        escaped_message,
2057        httpdate::fmt_http_date(SystemTime::now()),
2058    );
2059    if let Ok(hv) = HeaderValue::from_str(&value) {
2060        response.headers_mut().insert(WARNING, hv);
2061    }
2062}
2063
2064/// Removes the warning header from an `http::Response`.
2065fn response_remove_warning<B>(response: &mut Response<B>) {
2066    response.headers_mut().remove(WARNING);
2067}
2068
2069/// Returns `true` if the `cache-control` header of the response contains the
2070/// `must-revalidate` directive.
2071fn response_must_revalidate<B>(response: &Response<B>) -> bool {
2072    response
2073        .headers()
2074        .get(CACHE_CONTROL)
2075        .and_then(|v| v.to_str().ok())
2076        .is_some_and(|val| val.to_lowercase().contains("must-revalidate"))
2077}
2078
2079/// Adds the custom `x-cache` status header to an `http::Response`.
2080fn response_cache_status<B>(
2081    response: &mut Response<B>,
2082    hit_or_miss: HitOrMiss,
2083) {
2084    if let Ok(hv) = HeaderValue::from_str(&hit_or_miss.to_string()) {
2085        response.headers_mut().insert(XCACHE, hv);
2086    }
2087}
2088
2089/// Adds the custom `x-cache-lookup` status header to an `http::Response`.
2090fn response_cache_lookup_status<B>(
2091    response: &mut Response<B>,
2092    hit_or_miss: HitOrMiss,
2093) {
2094    if let Ok(hv) = HeaderValue::from_str(&hit_or_miss.to_string()) {
2095        response.headers_mut().insert(XCACHELOOKUP, hv);
2096    }
2097}
2098
2099/// Applies [`HttpCacheOptions::modify_response_before_caching`] to a
2100/// streaming `Response<B>`.  The callback expects `&mut HttpResponse`, so we
2101/// build a temporary shim with the response's headers/status (empty body),
2102/// call the callback, and copy any header or status changes back.
2103///
2104/// Body and metadata modifications made by the callback are not reflected
2105/// because the streaming body is not buffered.
2106fn apply_modify_response_shim<B>(
2107    options: &HttpCacheOptions,
2108    response: &mut Response<B>,
2109    url: &Url,
2110) {
2111    let modify = match &options.modify_response {
2112        Some(f) => f,
2113        None => return,
2114    };
2115    let mut shim = HttpResponse {
2116        body: Vec::new(),
2117        headers: HttpHeaders::from(response.headers()),
2118        status: response.status().as_u16(),
2119        url: url.clone(),
2120        version: response.version().try_into().unwrap_or(HttpVersion::Http11),
2121        metadata: None,
2122    };
2123    modify(&mut shim);
2124    // Apply header changes back
2125    response.headers_mut().clear();
2126    for (name, value) in shim.headers.iter() {
2127        if let (Ok(hn), Ok(hv)) = (
2128            http::header::HeaderName::from_bytes(name.as_bytes()),
2129            HeaderValue::from_str(value),
2130        ) {
2131            response.headers_mut().append(hn, hv);
2132        }
2133    }
2134    // Apply status change back
2135    if let Ok(new_status) = StatusCode::from_u16(shim.status) {
2136        *response.status_mut() = new_status;
2137    }
2138}
2139
2140/// Replaces `dst` entries for every header name present in `src`, preserving
2141/// multi-valued headers (clear-then-append; naive `insert`/`extend` drops or
2142/// doubles values like `Set-Cookie`).
2143fn merge_headers(dst: &mut http::HeaderMap, src: &http::HeaderMap) {
2144    for name in src.keys() {
2145        dst.remove(name);
2146    }
2147    for (name, value) in src.iter() {
2148        dst.append(name.clone(), value.clone());
2149    }
2150}
2151
2152/// RFC 7234 s4.4: the GET and HEAD cache keys to invalidate after a
2153/// successful (2xx/3xx) response to a non-GET/HEAD request, or `None` when
2154/// the status does not warrant invalidation.
2155fn get_head_invalidation_keys(
2156    options: &HttpCacheOptions,
2157    parts: &request::Parts,
2158    status: StatusCode,
2159) -> Option<(String, String)> {
2160    (status.is_success() || status.is_redirection()).then(|| {
2161        (
2162            options.create_cache_key(parts, Some("GET")),
2163            options.create_cache_key(parts, Some("HEAD")),
2164        )
2165    })
2166}
2167
2168// ============================================================================
2169// HttpStreamingCache orchestrator methods
2170// ============================================================================
2171
2172impl<T: StreamingCacheManager> HttpStreamingCache<T>
2173where
2174    <T::Body as http_body::Body>::Data: Send,
2175    <T::Body as http_body::Body>::Error:
2176        Into<StreamingError> + Send + Sync + 'static,
2177{
2178    /// Determines if the request described by `parts` should be cached,
2179    /// taking into account any `mode_override`.
2180    pub fn can_cache_request(
2181        &self,
2182        parts: &request::Parts,
2183        mode_override: Option<CacheMode>,
2184    ) -> Result<bool> {
2185        let analysis = <Self as HttpCacheStreamInterface>::analyze_request(
2186            self,
2187            parts,
2188            mode_override,
2189        )?;
2190        Ok(analysis.should_cache)
2191    }
2192
2193    /// Apply rate limiting if enabled in options.
2194    #[cfg(feature = "rate-limiting")]
2195    async fn apply_rate_limiting(&self, url: &Url) {
2196        if let Some(rate_limiter) = &self.options.rate_limiter {
2197            let rate_limit_key = url_hostname(url).unwrap_or("unknown");
2198            rate_limiter.until_key_ready(rate_limit_key).await;
2199        }
2200    }
2201
2202    /// Apply rate limiting if enabled in options (no-op without
2203    /// rate-limiting feature).
2204    #[cfg(not(feature = "rate-limiting"))]
2205    async fn apply_rate_limiting(&self, _url: &Url) {
2206        // No-op when rate limiting feature is not enabled
2207    }
2208
2209    /// Performs cache-busting housekeeping for requests that should not be
2210    /// cached.  Mirrors [`HttpCache::run_no_cache`].
2211    pub async fn run_no_cache(&self, parts: &request::Parts) -> Result<()> {
2212        self.manager
2213            .delete(&self.options.create_cache_key(parts, Some("GET")))
2214            .await
2215            .ok();
2216        self.manager
2217            .delete(&self.options.create_cache_key(parts, Some("HEAD")))
2218            .await
2219            .ok();
2220
2221        let cache_key = self.options.create_cache_key(parts, None);
2222
2223        if let Some(cache_bust) = &self.options.cache_bust {
2224            for key_to_cache_bust in
2225                cache_bust(parts, &self.options.cache_key, &cache_key)
2226            {
2227                self.manager.delete(&key_to_cache_bust).await?;
2228            }
2229        }
2230
2231        Ok(())
2232    }
2233
2234    /// See [`get_head_invalidation_keys`].
2235    async fn invalidate_get_head(
2236        &self,
2237        parts: &request::Parts,
2238        status: StatusCode,
2239    ) {
2240        if let Some((get_key, head_key)) =
2241            get_head_invalidation_keys(&self.options, parts, status)
2242        {
2243            self.manager.delete(&get_key).await.ok();
2244            self.manager.delete(&head_key).await.ok();
2245        }
2246    }
2247
2248    /// The main streaming cache orchestrator.
2249    ///
2250    /// This mirrors the logic of [`HttpCache::run`] but operates on
2251    /// streaming `Response<B>` bodies and delegates upstream fetching to a
2252    /// caller-supplied callback instead of an `impl Middleware`.
2253    ///
2254    /// # Arguments
2255    ///
2256    /// * `parts` - The request parts to evaluate.
2257    /// * `mode_override` - Optional per-request cache mode override.
2258    /// * `fetch` - A callback that performs the actual HTTP request.  It
2259    ///   receives a [`FetchRequest`] indicating whether to issue a fresh or
2260    ///   conditional request, and must return the upstream `Response<B>`.
2261    ///   Called at most once per request.
2262    pub async fn run<B, F, Fut>(
2263        &self,
2264        parts: &request::Parts,
2265        mode_override: Option<CacheMode>,
2266        fetch: F,
2267    ) -> Result<Response<T::Body>>
2268    where
2269        B: http_body::Body + Send + 'static,
2270        B::Data: Send,
2271        B::Error: Into<StreamingError>,
2272        F: FnOnce(FetchRequest) -> Fut,
2273        Fut: Future<Output = Result<Response<B>>>,
2274    {
2275        // 1. Analyze the request
2276        let analysis = <Self as HttpCacheStreamInterface>::analyze_request(
2277            self,
2278            parts,
2279            mode_override,
2280        )?;
2281
2282        // 2. If the request should not be cached, fetch and process as a
2283        //    remote miss.
2284        if !analysis.should_cache {
2285            let url = extract_url_from_request_parts(parts)?;
2286            self.apply_rate_limiting(&url).await;
2287            let response = fetch(FetchRequest::Fresh).await?;
2288            return self.remote_fetch_and_cache(analysis, response).await;
2289        }
2290
2291        // 3. Bust cache keys if needed
2292        for key in &analysis.cache_bust_keys {
2293            self.manager.delete(key).await?;
2294        }
2295
2296        // 4. Look up cached response
2297        if let Some((mut cached_response, policy)) =
2298            <Self as HttpCacheStreamInterface>::lookup_cached_response(
2299                self,
2300                &analysis.cache_key,
2301            )
2302            .await?
2303        {
2304            if self.options.cache_status_headers {
2305                response_cache_lookup_status(
2306                    &mut cached_response,
2307                    HitOrMiss::HIT,
2308                );
2309            }
2310
2311            // Handle warning headers per RFC 7234 §4.3.4
2312            if let Some(warning_code) = response_warning_code(&cached_response)
2313            {
2314                if (100..200).contains(&warning_code) {
2315                    response_remove_warning(&mut cached_response);
2316                }
2317            }
2318
2319            // 5. Branch on cache mode
2320            match analysis.cache_mode {
2321                CacheMode::Default => {
2322                    self.conditional_fetch(
2323                        &analysis,
2324                        fetch,
2325                        cached_response,
2326                        policy,
2327                    )
2328                    .await
2329                }
2330                CacheMode::NoCache => {
2331                    // Force a fresh fetch with no-cache directive, but
2332                    // note that we had a cache lookup hit.
2333                    let url = extract_url_from_request_parts(parts)?;
2334                    self.apply_rate_limiting(&url).await;
2335                    let response = fetch(FetchRequest::FreshNoCache).await?;
2336                    let mut res =
2337                        self.remote_fetch_and_cache(analysis, response).await?;
2338                    if self.options.cache_status_headers {
2339                        response_cache_lookup_status(&mut res, HitOrMiss::HIT);
2340                    }
2341                    Ok(res)
2342                }
2343                CacheMode::ForceCache
2344                | CacheMode::OnlyIfCached
2345                | CacheMode::IgnoreRules => {
2346                    //   112 Disconnected operation
2347                    // SHOULD be included if the cache is intentionally
2348                    // disconnected from the rest of the network for a
2349                    // period of time.
2350                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
2351                    let url = extract_url_from_request_parts(parts)?;
2352                    response_add_warning(
2353                        &mut cached_response,
2354                        &url,
2355                        112,
2356                        "Disconnected operation",
2357                    );
2358                    if self.options.cache_status_headers {
2359                        response_cache_status(
2360                            &mut cached_response,
2361                            HitOrMiss::HIT,
2362                        );
2363                    }
2364                    Ok(cached_response)
2365                }
2366                CacheMode::Reload => {
2367                    let url = extract_url_from_request_parts(parts)?;
2368                    self.apply_rate_limiting(&url).await;
2369                    let response = fetch(FetchRequest::Fresh).await?;
2370                    let mut res =
2371                        self.remote_fetch_and_cache(analysis, response).await?;
2372                    if self.options.cache_status_headers {
2373                        response_cache_lookup_status(&mut res, HitOrMiss::HIT);
2374                    }
2375                    Ok(res)
2376                }
2377                _ => {
2378                    let url = extract_url_from_request_parts(parts)?;
2379                    self.apply_rate_limiting(&url).await;
2380                    let response = fetch(FetchRequest::Fresh).await?;
2381                    self.remote_fetch_and_cache(analysis, response).await
2382                }
2383            }
2384        } else {
2385            // 6. No cached response found
2386            match analysis.cache_mode {
2387                CacheMode::OnlyIfCached => {
2388                    // ENOTCACHED — return 504 Gateway Timeout
2389                    let mut res = Response::builder()
2390                        .status(StatusCode::GATEWAY_TIMEOUT)
2391                        .body(self.manager.empty_body())
2392                        .map_err(|e| -> BoxError { e.into() })?;
2393                    if self.options.cache_status_headers {
2394                        response_cache_status(&mut res, HitOrMiss::MISS);
2395                        response_cache_lookup_status(&mut res, HitOrMiss::MISS);
2396                    }
2397                    Ok(res)
2398                }
2399                _ => {
2400                    let url = extract_url_from_request_parts(parts)?;
2401                    self.apply_rate_limiting(&url).await;
2402                    let response = fetch(FetchRequest::Fresh).await?;
2403                    self.remote_fetch_and_cache(analysis, response).await
2404                }
2405            }
2406        }
2407    }
2408
2409    /// Processes a fresh upstream response and potentially caches it.
2410    ///
2411    /// Mirrors [`HttpCache::remote_fetch`] but receives the response
2412    /// directly rather than calling middleware.  Rate limiting is performed
2413    /// by the caller before invoking `fetch`.
2414    async fn remote_fetch_and_cache<B>(
2415        &self,
2416        analysis: CacheAnalysis,
2417        response: Response<B>,
2418    ) -> Result<Response<T::Body>>
2419    where
2420        B: http_body::Body + Send + 'static,
2421        B::Data: Send,
2422        B::Error: Into<StreamingError>,
2423    {
2424        // Delegate to process_response which handles:
2425        //   - response-based cache mode override evaluation
2426        //   - policy creation
2427        //   - should_cache_response check
2428        //   - cache busting for non-GET/HEAD
2429        //   - storing via manager.put or converting via manager.convert_body
2430        //   - adding cache status headers (MISS/MISS)
2431        //   - applying modify_response_before_caching shim before put
2432        let res = <Self as HttpCacheStreamInterface>::process_response(
2433            self,
2434            analysis.clone(),
2435            response,
2436            None,
2437        )
2438        .await?;
2439
2440        Ok(res)
2441    }
2442
2443    /// Performs a conditional fetch (revalidation) against the origin,
2444    /// returning either the still-valid cached response or the fresh
2445    /// upstream response.
2446    ///
2447    /// Mirrors [`HttpCache::conditional_fetch`].
2448    async fn conditional_fetch<B, F, Fut>(
2449        &self,
2450        analysis: &CacheAnalysis,
2451        fetch: F,
2452        mut cached_res: Response<T::Body>,
2453        mut policy: CachePolicy,
2454    ) -> Result<Response<T::Body>>
2455    where
2456        B: http_body::Body + Send + 'static,
2457        B::Data: Send,
2458        B::Error: Into<StreamingError>,
2459        F: FnOnce(FetchRequest) -> Fut,
2460        Fut: Future<Output = Result<Response<B>>>,
2461    {
2462        let parts = &analysis.request_parts;
2463        let before_req = policy.before_request(parts, SystemTime::now());
2464        match before_req {
2465            BeforeRequest::Fresh(fresh_parts) => {
2466                merge_headers(cached_res.headers_mut(), &fresh_parts.headers);
2467                if self.options.cache_status_headers {
2468                    response_cache_status(&mut cached_res, HitOrMiss::HIT);
2469                    response_cache_lookup_status(
2470                        &mut cached_res,
2471                        HitOrMiss::HIT,
2472                    );
2473                }
2474                Ok(cached_res)
2475            }
2476            BeforeRequest::Stale { request: stale_parts, matches } => {
2477                let req_url = extract_url_from_request_parts(parts)?;
2478                // Apply rate limiting before revalidation request
2479                self.apply_rate_limiting(&req_url).await;
2480
2481                // Only send conditional headers when matches is true
2482                // (matching reference behavior at HttpCache::conditional_fetch)
2483                let fetch_result = if matches {
2484                    fetch(FetchRequest::Conditional(Box::new(stale_parts)))
2485                        .await
2486                } else {
2487                    fetch(FetchRequest::Fresh).await
2488                };
2489
2490                match fetch_result {
2491                    Ok(cond_res) => {
2492                        let status = cond_res.status();
2493
2494                        if status.is_server_error()
2495                            && response_must_revalidate(&cached_res)
2496                        {
2497                            //   111 Revalidation failed
2498                            //   MUST be included if a cache returns a
2499                            //   stale response because an attempt to
2500                            //   revalidate the response failed, due to an
2501                            //   inability to reach the server.
2502                            // (https://tools.ietf.org/html/rfc2616#section-14.46)
2503                            response_add_warning(
2504                                &mut cached_res,
2505                                &req_url,
2506                                111,
2507                                "Revalidation failed",
2508                            );
2509                            if self.options.cache_status_headers {
2510                                response_cache_status(
2511                                    &mut cached_res,
2512                                    HitOrMiss::HIT,
2513                                );
2514                            }
2515                            Ok(cached_res)
2516                        } else if status == StatusCode::NOT_MODIFIED {
2517                            // 304 Not Modified — update cached response
2518                            // headers using policy.after_response
2519                            let (cond_parts, _cond_body) =
2520                                cond_res.into_parts();
2521                            let after_res = policy.after_response(
2522                                parts,
2523                                &cond_parts,
2524                                SystemTime::now(),
2525                            );
2526                            match after_res {
2527                                AfterResponse::Modified(
2528                                    new_policy,
2529                                    new_parts,
2530                                )
2531                                | AfterResponse::NotModified(
2532                                    new_policy,
2533                                    new_parts,
2534                                ) => {
2535                                    policy = new_policy;
2536                                    merge_headers(
2537                                        cached_res.headers_mut(),
2538                                        &new_parts.headers,
2539                                    );
2540                                }
2541                            }
2542                            if self.options.cache_status_headers {
2543                                response_cache_status(
2544                                    &mut cached_res,
2545                                    HitOrMiss::HIT,
2546                                );
2547                                response_cache_lookup_status(
2548                                    &mut cached_res,
2549                                    HitOrMiss::HIT,
2550                                );
2551                            }
2552
2553                            apply_modify_response_shim(
2554                                &self.options,
2555                                &mut cached_res,
2556                                &req_url,
2557                            );
2558
2559                            // Preserve the cached response's original user
2560                            // metadata instead of regenerating it.
2561                            let metadata = cached_res
2562                                .extensions()
2563                                .get::<CachedUserMetadata>()
2564                                .and_then(|m| m.0.clone());
2565
2566                            // Metadata-only refresh: the body is
2567                            // known-unchanged (that's what 304 means), so
2568                            // never re-read or rewrite the body file. Any
2569                            // failure here must not break the response —
2570                            // cached_res is already valid to serve.
2571                            let cache_key =
2572                                self.options.create_cache_key(parts, None);
2573                            let token = cached_res
2574                                .extensions()
2575                                .get::<CacheEntryToken>()
2576                                .cloned();
2577                            match self
2578                                .manager
2579                                .update_metadata(
2580                                    &cache_key,
2581                                    cached_res.headers(),
2582                                    policy,
2583                                    metadata,
2584                                    token.as_ref(),
2585                                )
2586                                .await
2587                            {
2588                                Ok(true) => {}
2589                                Ok(false) => log::debug!(
2590                                    "streaming 304: entry vanished or was \
2591                                     replaced during revalidation; serving \
2592                                     without re-cache"
2593                                ),
2594                                Err(e) => log::debug!(
2595                                    "streaming 304: metadata update failed; \
2596                                     serving without re-cache: {e}"
2597                                ),
2598                            }
2599                            Ok(cached_res)
2600                        } else if status == StatusCode::OK {
2601                            // 200 OK — fresh response, create new policy
2602                            // and cache
2603                            let (cond_parts, cond_body) = cond_res.into_parts();
2604                            let new_policy = self
2605                                .options
2606                                .create_cache_policy(parts, &cond_parts);
2607                            let metadata = self
2608                                .options
2609                                .generate_metadata(parts, &cond_parts);
2610                            let cond_res =
2611                                Response::from_parts(cond_parts, cond_body);
2612
2613                            let request_url =
2614                                extract_url_from_request_parts(parts)?;
2615
2616                            // Apply modify_response BEFORE cacheability checks
2617                            // (matches non-streaming reference order)
2618                            let mut cond_res = cond_res;
2619                            apply_modify_response_shim(
2620                                &self.options,
2621                                &mut cond_res,
2622                                &request_url,
2623                            );
2624
2625                            // Build HttpResponse shim from modified response
2626                            let http_response_shim = HttpResponse {
2627                                body: vec![],
2628                                headers: cond_res.headers().into(),
2629                                status: cond_res.status().as_u16(),
2630                                url: request_url.clone(),
2631                                version: cond_res
2632                                    .version()
2633                                    .try_into()
2634                                    .unwrap_or(HttpVersion::Http11),
2635                                metadata: metadata.clone(),
2636                            };
2637                            // Apply response-based cache mode override
2638                            let effective_mode =
2639                                self.options.evaluate_response_cache_mode(
2640                                    parts,
2641                                    &http_response_shim,
2642                                    analysis.cache_mode,
2643                                );
2644                            let is_cacheable =
2645                                self.options.should_cache_response(
2646                                    effective_mode,
2647                                    &http_response_shim,
2648                                    analysis.is_get_head,
2649                                    &new_policy,
2650                                );
2651
2652                            // Set cache status headers
2653                            if self.options.cache_status_headers {
2654                                response_cache_status(
2655                                    &mut cond_res,
2656                                    HitOrMiss::MISS,
2657                                );
2658                                response_cache_lookup_status(
2659                                    &mut cond_res,
2660                                    HitOrMiss::HIT,
2661                                );
2662                            }
2663
2664                            if is_cacheable {
2665                                cond_res.extensions_mut().insert(
2666                                    CachedRequestMethod(parts.method.clone()),
2667                                );
2668                                let res = self
2669                                    .manager
2670                                    .put(
2671                                        self.options
2672                                            .create_cache_key(parts, None),
2673                                        cond_res,
2674                                        new_policy,
2675                                        request_url,
2676                                        metadata,
2677                                    )
2678                                    .await?;
2679                                Ok(res)
2680                            } else {
2681                                let res =
2682                                    self.manager.convert_body(cond_res).await?;
2683                                Ok(res)
2684                            }
2685                        } else {
2686                            // Any other status — return fresh response
2687                            let mut res =
2688                                self.manager.convert_body(cond_res).await?;
2689                            if self.options.cache_status_headers {
2690                                response_cache_status(
2691                                    &mut res,
2692                                    HitOrMiss::MISS,
2693                                );
2694                                response_cache_lookup_status(
2695                                    &mut res,
2696                                    HitOrMiss::HIT,
2697                                );
2698                            }
2699                            Ok(res)
2700                        }
2701                    }
2702                    Err(e) => {
2703                        if response_must_revalidate(&cached_res) {
2704                            Err(e)
2705                        } else {
2706                            //   111 Revalidation failed
2707                            //   MUST be included if a cache returns a
2708                            //   stale response because an attempt to
2709                            //   revalidate the response failed, due to an
2710                            //   inability to reach the server.
2711                            // (https://tools.ietf.org/html/rfc2616#section-14.46)
2712                            response_add_warning(
2713                                &mut cached_res,
2714                                &req_url,
2715                                111,
2716                                "Revalidation failed",
2717                            );
2718                            if self.options.cache_status_headers {
2719                                response_cache_status(
2720                                    &mut cached_res,
2721                                    HitOrMiss::HIT,
2722                                );
2723                            }
2724                            Ok(cached_res)
2725                        }
2726                    }
2727                }
2728            }
2729        }
2730    }
2731}
2732
2733impl<T: CacheManager> HttpCache<T> {
2734    /// Determines if the request should be cached
2735    pub fn can_cache_request(
2736        &self,
2737        middleware: &impl Middleware,
2738    ) -> Result<bool> {
2739        let analysis = self.analyze_request(
2740            &middleware.parts()?,
2741            middleware.overridden_cache_mode(),
2742        )?;
2743        Ok(analysis.should_cache)
2744    }
2745
2746    /// Apply rate limiting if enabled in options
2747    #[cfg(feature = "rate-limiting")]
2748    async fn apply_rate_limiting(&self, url: &Url) {
2749        if let Some(rate_limiter) = &self.options.rate_limiter {
2750            let rate_limit_key = url_hostname(url).unwrap_or("unknown");
2751            rate_limiter.until_key_ready(rate_limit_key).await;
2752        }
2753    }
2754
2755    /// Apply rate limiting if enabled in options (no-op without rate-limiting feature)
2756    #[cfg(not(feature = "rate-limiting"))]
2757    async fn apply_rate_limiting(&self, _url: &Url) {
2758        // No-op when rate limiting feature is not enabled
2759    }
2760
2761    /// Cache-busting for non-cacheable requests, taking pre-extracted parts.
2762    pub async fn run_no_cache_from_parts(
2763        &self,
2764        parts: &request::Parts,
2765    ) -> Result<()> {
2766        self.manager
2767            .delete(&self.options.create_cache_key(parts, Some("GET")))
2768            .await
2769            .ok();
2770        self.manager
2771            .delete(&self.options.create_cache_key(parts, Some("HEAD")))
2772            .await
2773            .ok();
2774
2775        let cache_key = self.options.create_cache_key(parts, None);
2776
2777        if let Some(cache_bust) = &self.options.cache_bust {
2778            for key_to_cache_bust in
2779                cache_bust(parts, &self.options.cache_key, &cache_key)
2780            {
2781                self.manager.delete(&key_to_cache_bust).await?;
2782            }
2783        }
2784
2785        Ok(())
2786    }
2787
2788    /// Runs the actions to perform when the client middleware is running without the cache
2789    pub async fn run_no_cache(
2790        &self,
2791        middleware: &mut impl Middleware,
2792    ) -> Result<()> {
2793        let parts = middleware.parts()?;
2794        self.run_no_cache_from_parts(&parts).await
2795    }
2796
2797    /// See [`get_head_invalidation_keys`].
2798    async fn invalidate_get_head(
2799        &self,
2800        parts: &request::Parts,
2801        status: StatusCode,
2802    ) {
2803        if let Some((get_key, head_key)) =
2804            get_head_invalidation_keys(&self.options, parts, status)
2805        {
2806            self.manager.delete(&get_key).await.ok();
2807            self.manager.delete(&head_key).await.ok();
2808        }
2809    }
2810
2811    /// Attempts to run the passed middleware along with the cache
2812    pub async fn run(
2813        &self,
2814        mut middleware: impl Middleware,
2815    ) -> Result<HttpResponse> {
2816        // Use the HttpCacheInterface to analyze the request
2817        let analysis = self.analyze_request(
2818            &middleware.parts()?,
2819            middleware.overridden_cache_mode(),
2820        )?;
2821
2822        if !analysis.should_cache {
2823            return self.remote_fetch(&mut middleware).await;
2824        }
2825
2826        // Bust cache keys if needed
2827        for key in &analysis.cache_bust_keys {
2828            self.manager.delete(key).await?;
2829        }
2830
2831        // Look up cached response
2832        if let Some((mut cached_response, policy)) =
2833            self.lookup_cached_response(&analysis.cache_key).await?
2834        {
2835            if self.options.cache_status_headers {
2836                cached_response.cache_lookup_status(HitOrMiss::HIT);
2837            }
2838
2839            // Handle warning headers
2840            if let Some(warning_code) = cached_response.warning_code() {
2841                // https://tools.ietf.org/html/rfc7234#section-4.3.4
2842                //
2843                // If a stored response is selected for update, the cache MUST:
2844                //
2845                // * delete any warning header fields in the stored response with
2846                //   warn-code 1xx (see Section 5.5);
2847                //
2848                // * retain any warning header fields in the stored response with
2849                //   warn-code 2xx;
2850                //
2851                if (100..200).contains(&warning_code) {
2852                    cached_response.remove_warning();
2853                }
2854            }
2855
2856            match analysis.cache_mode {
2857                CacheMode::Default => {
2858                    self.conditional_fetch(middleware, cached_response, policy)
2859                        .await
2860                }
2861                CacheMode::NoCache => {
2862                    middleware.force_no_cache()?;
2863                    let mut res = self.remote_fetch(&mut middleware).await?;
2864                    if self.options.cache_status_headers {
2865                        res.cache_lookup_status(HitOrMiss::HIT);
2866                    }
2867                    Ok(res)
2868                }
2869                CacheMode::ForceCache
2870                | CacheMode::OnlyIfCached
2871                | CacheMode::IgnoreRules => {
2872                    //   112 Disconnected operation
2873                    // SHOULD be included if the cache is intentionally disconnected from
2874                    // the rest of the network for a period of time.
2875                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
2876                    cached_response.add_warning(
2877                        &cached_response.url.clone(),
2878                        112,
2879                        "Disconnected operation",
2880                    );
2881                    if self.options.cache_status_headers {
2882                        cached_response.cache_status(HitOrMiss::HIT);
2883                    }
2884                    Ok(cached_response)
2885                }
2886                CacheMode::Reload => {
2887                    let mut res = self.remote_fetch(&mut middleware).await?;
2888                    if self.options.cache_status_headers {
2889                        res.cache_lookup_status(HitOrMiss::HIT);
2890                    }
2891                    Ok(res)
2892                }
2893                _ => self.remote_fetch(&mut middleware).await,
2894            }
2895        } else {
2896            match analysis.cache_mode {
2897                CacheMode::OnlyIfCached => {
2898                    // ENOTCACHED
2899                    let mut res = HttpResponse {
2900                        body: Vec::new(),
2901                        headers: HttpHeaders::default(),
2902                        status: 504,
2903                        url: middleware.url()?,
2904                        version: HttpVersion::Http11,
2905                        metadata: None,
2906                    };
2907                    if self.options.cache_status_headers {
2908                        res.cache_status(HitOrMiss::MISS);
2909                        res.cache_lookup_status(HitOrMiss::MISS);
2910                    }
2911                    Ok(res)
2912                }
2913                _ => self.remote_fetch(&mut middleware).await,
2914            }
2915        }
2916    }
2917
2918    fn cache_mode(&self, middleware: &impl Middleware) -> Result<CacheMode> {
2919        Ok(if let Some(mode) = middleware.overridden_cache_mode() {
2920            mode
2921        } else if let Some(cache_mode_fn) = &self.options.cache_mode_fn {
2922            cache_mode_fn(&middleware.parts()?)
2923        } else {
2924            self.mode
2925        })
2926    }
2927
2928    async fn remote_fetch(
2929        &self,
2930        middleware: &mut impl Middleware,
2931    ) -> Result<HttpResponse> {
2932        // Apply rate limiting before making the network request
2933        let url = middleware.url()?;
2934        self.apply_rate_limiting(&url).await;
2935
2936        let mut res = middleware.remote_fetch().await?;
2937        if self.options.cache_status_headers {
2938            res.cache_status(HitOrMiss::MISS);
2939            res.cache_lookup_status(HitOrMiss::MISS);
2940        }
2941        let policy = match self.options.cache_options {
2942            Some(options) => middleware.policy_with_options(&res, options)?,
2943            None => middleware.policy(&res)?,
2944        };
2945        let is_get_head = middleware.is_method_get_head();
2946        let mut mode = self.cache_mode(middleware)?;
2947        let parts = middleware.parts()?;
2948
2949        // Allow response-based cache mode override
2950        if let Some(response_cache_mode_fn) =
2951            &self.options.response_cache_mode_fn
2952        {
2953            if let Some(override_mode) = response_cache_mode_fn(&parts, &res) {
2954                mode = override_mode;
2955            }
2956        }
2957
2958        let is_cacheable = self.options.should_cache_response(
2959            mode,
2960            &res,
2961            is_get_head,
2962            &policy,
2963        );
2964
2965        if is_cacheable {
2966            // Generate metadata using the provider callback if configured
2967            let response_parts = res.parts()?;
2968            res.metadata =
2969                self.options.generate_metadata(&parts, &response_parts);
2970
2971            self.options.modify_response_before_caching(&mut res);
2972            let res = self
2973                .manager
2974                .put(self.options.create_cache_key(&parts, None), res, policy)
2975                .await?;
2976            if !is_get_head {
2977                self.invalidate_get_head(
2978                    &parts,
2979                    StatusCode::from_u16(res.status)?,
2980                )
2981                .await;
2982            }
2983            Ok(res)
2984        } else if !is_get_head {
2985            self.invalidate_get_head(&parts, StatusCode::from_u16(res.status)?)
2986                .await;
2987            Ok(res)
2988        } else {
2989            Ok(res)
2990        }
2991    }
2992
2993    async fn conditional_fetch(
2994        &self,
2995        mut middleware: impl Middleware,
2996        mut cached_res: HttpResponse,
2997        mut policy: CachePolicy,
2998    ) -> Result<HttpResponse> {
2999        let parts = middleware.parts()?;
3000        let before_req = policy.before_request(&parts, SystemTime::now());
3001        match before_req {
3002            BeforeRequest::Fresh(parts) => {
3003                cached_res.update_headers(&parts)?;
3004                if self.options.cache_status_headers {
3005                    cached_res.cache_status(HitOrMiss::HIT);
3006                    cached_res.cache_lookup_status(HitOrMiss::HIT);
3007                }
3008                return Ok(cached_res);
3009            }
3010            BeforeRequest::Stale { request: parts, matches } => {
3011                if matches {
3012                    middleware.update_headers(&parts)?;
3013                }
3014            }
3015        }
3016        let req_url = middleware.url()?;
3017        // Apply rate limiting before revalidation request
3018        self.apply_rate_limiting(&req_url).await;
3019        match middleware.remote_fetch().await {
3020            Ok(mut cond_res) => {
3021                let status = StatusCode::from_u16(cond_res.status)?;
3022                if status.is_server_error() && cached_res.must_revalidate() {
3023                    //   111 Revalidation failed
3024                    //   MUST be included if a cache returns a stale response
3025                    //   because an attempt to revalidate the response failed,
3026                    //   due to an inability to reach the server.
3027                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
3028                    cached_res.add_warning(
3029                        &req_url,
3030                        111,
3031                        "Revalidation failed",
3032                    );
3033                    if self.options.cache_status_headers {
3034                        cached_res.cache_status(HitOrMiss::HIT);
3035                    }
3036                    Ok(cached_res)
3037                } else if cond_res.status == 304 {
3038                    let after_res = policy.after_response(
3039                        &parts,
3040                        &cond_res.parts()?,
3041                        SystemTime::now(),
3042                    );
3043                    match after_res {
3044                        AfterResponse::Modified(new_policy, parts)
3045                        | AfterResponse::NotModified(new_policy, parts) => {
3046                            policy = new_policy;
3047                            cached_res.update_headers(&parts)?;
3048                        }
3049                    }
3050                    if self.options.cache_status_headers {
3051                        cached_res.cache_status(HitOrMiss::HIT);
3052                        cached_res.cache_lookup_status(HitOrMiss::HIT);
3053                    }
3054                    self.options
3055                        .modify_response_before_caching(&mut cached_res);
3056                    let res = self
3057                        .manager
3058                        .put(
3059                            self.options.create_cache_key(&parts, None),
3060                            cached_res,
3061                            policy,
3062                        )
3063                        .await?;
3064                    Ok(res)
3065                } else if cond_res.status == 200 {
3066                    let policy = match self.options.cache_options {
3067                        Some(options) => middleware
3068                            .policy_with_options(&cond_res, options)?,
3069                        None => middleware.policy(&cond_res)?,
3070                    };
3071                    if self.options.cache_status_headers {
3072                        cond_res.cache_status(HitOrMiss::MISS);
3073                        cond_res.cache_lookup_status(HitOrMiss::HIT);
3074                    }
3075                    // Generate metadata using the provider callback if configured
3076                    let response_parts = cond_res.parts()?;
3077                    cond_res.metadata =
3078                        self.options.generate_metadata(&parts, &response_parts);
3079
3080                    self.options.modify_response_before_caching(&mut cond_res);
3081
3082                    let mode = self.cache_mode(&middleware)?;
3083                    // Apply response-based cache mode override if configured
3084                    let mode = self
3085                        .options
3086                        .evaluate_response_cache_mode(&parts, &cond_res, mode);
3087                    let is_get_head = middleware.is_method_get_head();
3088                    let is_cacheable = self.options.should_cache_response(
3089                        mode,
3090                        &cond_res,
3091                        is_get_head,
3092                        &policy,
3093                    );
3094
3095                    if is_cacheable {
3096                        let res = self
3097                            .manager
3098                            .put(
3099                                self.options.create_cache_key(&parts, None),
3100                                cond_res,
3101                                policy,
3102                            )
3103                            .await?;
3104                        Ok(res)
3105                    } else {
3106                        Ok(cond_res)
3107                    }
3108                } else {
3109                    // Return fresh response for any status other than 304 or 200
3110                    if self.options.cache_status_headers {
3111                        cond_res.cache_status(HitOrMiss::MISS);
3112                        cond_res.cache_lookup_status(HitOrMiss::HIT);
3113                    }
3114                    Ok(cond_res)
3115                }
3116            }
3117            Err(e) => {
3118                if cached_res.must_revalidate() {
3119                    Err(e)
3120                } else {
3121                    //   111 Revalidation failed
3122                    //   MUST be included if a cache returns a stale response
3123                    //   because an attempt to revalidate the response failed,
3124                    //   due to an inability to reach the server.
3125                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
3126                    cached_res.add_warning(
3127                        &req_url,
3128                        111,
3129                        "Revalidation failed",
3130                    );
3131                    if self.options.cache_status_headers {
3132                        cached_res.cache_status(HitOrMiss::HIT);
3133                    }
3134                    Ok(cached_res)
3135                }
3136            }
3137        }
3138    }
3139}
3140
3141impl<T: StreamingCacheManager> HttpCacheStreamInterface
3142    for HttpStreamingCache<T>
3143where
3144    <T::Body as http_body::Body>::Data: Send,
3145    <T::Body as http_body::Body>::Error:
3146        Into<StreamingError> + Send + Sync + 'static,
3147{
3148    type Body = T::Body;
3149
3150    fn analyze_request(
3151        &self,
3152        parts: &request::Parts,
3153        mode_override: Option<CacheMode>,
3154    ) -> Result<CacheAnalysis> {
3155        self.options.analyze_request_internal(parts, mode_override, self.mode)
3156    }
3157
3158    async fn lookup_cached_response(
3159        &self,
3160        key: &str,
3161    ) -> Result<Option<(Response<Self::Body>, CachePolicy)>> {
3162        self.manager.get(key).await
3163    }
3164
3165    async fn process_response<B>(
3166        &self,
3167        analysis: CacheAnalysis,
3168        response: Response<B>,
3169        metadata: Option<Vec<u8>>,
3170    ) -> Result<Response<Self::Body>>
3171    where
3172        B: http_body::Body + Send + 'static,
3173        B::Data: Send,
3174        B::Error: Into<StreamingError>,
3175        <T::Body as http_body::Body>::Data: Send,
3176        <T::Body as http_body::Body>::Error:
3177            Into<StreamingError> + Send + Sync + 'static,
3178    {
3179        // For non-cacheable requests based on initial analysis, convert them to manager's body type
3180        if !analysis.should_cache {
3181            if !analysis.is_get_head {
3182                self.invalidate_get_head(
3183                    &analysis.request_parts,
3184                    response.status(),
3185                )
3186                .await;
3187            }
3188            let mut converted_response =
3189                self.manager.convert_body(response).await?;
3190            // Add cache miss headers
3191            if self.options.cache_status_headers {
3192                converted_response.headers_mut().insert(
3193                    XCACHE,
3194                    "MISS".parse().map_err(StreamingError::new)?,
3195                );
3196                converted_response.headers_mut().insert(
3197                    XCACHELOOKUP,
3198                    "MISS".parse().map_err(StreamingError::new)?,
3199                );
3200            }
3201            return Ok(converted_response);
3202        }
3203
3204        // Bust cache keys if needed
3205        for key in &analysis.cache_bust_keys {
3206            self.manager.delete(key).await?;
3207        }
3208
3209        // Convert response to HttpResponse format for response-based cache mode evaluation
3210        let (parts, body) = response.into_parts();
3211        // Use provided metadata or generate from provider
3212        let effective_metadata = metadata.or_else(|| {
3213            self.options.generate_metadata(&analysis.request_parts, &parts)
3214        });
3215        let http_response = self.options.parts_to_http_response(
3216            &parts,
3217            &analysis.request_parts,
3218            effective_metadata.clone(),
3219        )?;
3220
3221        // Check for response-based cache mode override
3222        let effective_cache_mode = self.options.evaluate_response_cache_mode(
3223            &analysis.request_parts,
3224            &http_response,
3225            analysis.cache_mode,
3226        );
3227
3228        // Reconstruct response for further processing
3229        let response = Response::from_parts(parts, body);
3230
3231        // If response-based override says NoStore, don't cache
3232        if effective_cache_mode == CacheMode::NoStore {
3233            if !analysis.is_get_head {
3234                self.invalidate_get_head(
3235                    &analysis.request_parts,
3236                    StatusCode::from_u16(http_response.status)?,
3237                )
3238                .await;
3239            }
3240            let mut converted_response =
3241                self.manager.convert_body(response).await?;
3242            // Add cache miss headers
3243            if self.options.cache_status_headers {
3244                converted_response.headers_mut().insert(
3245                    XCACHE,
3246                    "MISS".parse().map_err(StreamingError::new)?,
3247                );
3248                converted_response.headers_mut().insert(
3249                    XCACHELOOKUP,
3250                    "MISS".parse().map_err(StreamingError::new)?,
3251                );
3252            }
3253            return Ok(converted_response);
3254        }
3255
3256        // Create policy for the response
3257        let (parts, body) = response.into_parts();
3258        let policy =
3259            self.options.create_cache_policy(&analysis.request_parts, &parts);
3260
3261        // Reconstruct response for caching
3262        let response = Response::from_parts(parts, body);
3263
3264        let should_cache_response = self.options.should_cache_response(
3265            effective_cache_mode,
3266            &http_response,
3267            analysis.is_get_head,
3268            &policy,
3269        );
3270
3271        if should_cache_response {
3272            // Extract URL from request parts for caching
3273            let request_url =
3274                extract_url_from_request_parts(&analysis.request_parts)?;
3275
3276            // Apply modify_response_before_caching shim before storing
3277            let mut response = response;
3278            apply_modify_response_shim(
3279                &self.options,
3280                &mut response,
3281                &request_url,
3282            );
3283            response.extensions_mut().insert(CachedRequestMethod(
3284                analysis.request_parts.method.clone(),
3285            ));
3286
3287            // Cache the response using the streaming manager
3288            let mut cached_response = self
3289                .manager
3290                .put(
3291                    analysis.cache_key,
3292                    response,
3293                    policy,
3294                    request_url,
3295                    effective_metadata,
3296                )
3297                .await?;
3298
3299            if !analysis.is_get_head {
3300                self.invalidate_get_head(
3301                    &analysis.request_parts,
3302                    StatusCode::from_u16(http_response.status)?,
3303                )
3304                .await;
3305            }
3306
3307            // Add cache miss headers (response is being stored for first time)
3308            if self.options.cache_status_headers {
3309                cached_response.headers_mut().insert(
3310                    XCACHE,
3311                    "MISS".parse().map_err(StreamingError::new)?,
3312                );
3313                cached_response.headers_mut().insert(
3314                    XCACHELOOKUP,
3315                    "MISS".parse().map_err(StreamingError::new)?,
3316                );
3317            }
3318            Ok(cached_response)
3319        } else {
3320            if !analysis.is_get_head {
3321                self.invalidate_get_head(
3322                    &analysis.request_parts,
3323                    StatusCode::from_u16(http_response.status)?,
3324                )
3325                .await;
3326            }
3327            // Don't cache, just convert to manager's body type
3328            let mut converted_response =
3329                self.manager.convert_body(response).await?;
3330            // Add cache miss headers
3331            if self.options.cache_status_headers {
3332                converted_response.headers_mut().insert(
3333                    XCACHE,
3334                    "MISS".parse().map_err(StreamingError::new)?,
3335                );
3336                converted_response.headers_mut().insert(
3337                    XCACHELOOKUP,
3338                    "MISS".parse().map_err(StreamingError::new)?,
3339                );
3340            }
3341            Ok(converted_response)
3342        }
3343    }
3344
3345    fn prepare_conditional_request(
3346        &self,
3347        parts: &mut request::Parts,
3348        _cached_response: &Response<Self::Body>,
3349        policy: &CachePolicy,
3350    ) -> Result<()> {
3351        let before_req = policy.before_request(parts, SystemTime::now());
3352        if let BeforeRequest::Stale { request, .. } = before_req {
3353            parts.headers.extend(request.headers);
3354        }
3355        Ok(())
3356    }
3357
3358    async fn handle_not_modified(
3359        &self,
3360        cached_response: Response<Self::Body>,
3361        fresh_parts: &response::Parts,
3362    ) -> Result<Response<Self::Body>> {
3363        let (mut parts, body) = cached_response.into_parts();
3364
3365        merge_headers(&mut parts.headers, &fresh_parts.headers);
3366
3367        let mut response = Response::from_parts(parts, body);
3368        if self.options.cache_status_headers {
3369            response_cache_status(&mut response, HitOrMiss::HIT);
3370            response_cache_lookup_status(&mut response, HitOrMiss::HIT);
3371        }
3372        Ok(response)
3373    }
3374}
3375
3376impl<T: CacheManager> HttpCacheInterface for HttpCache<T> {
3377    fn analyze_request(
3378        &self,
3379        parts: &request::Parts,
3380        mode_override: Option<CacheMode>,
3381    ) -> Result<CacheAnalysis> {
3382        self.options.analyze_request_internal(parts, mode_override, self.mode)
3383    }
3384
3385    async fn lookup_cached_response(
3386        &self,
3387        key: &str,
3388    ) -> Result<Option<(HttpResponse, CachePolicy)>> {
3389        self.manager.get(key).await
3390    }
3391
3392    async fn process_response(
3393        &self,
3394        analysis: CacheAnalysis,
3395        response: Response<Vec<u8>>,
3396        metadata: Option<Vec<u8>>,
3397    ) -> Result<Response<Vec<u8>>> {
3398        if !analysis.should_cache {
3399            if !analysis.is_get_head {
3400                self.invalidate_get_head(
3401                    &analysis.request_parts,
3402                    response.status(),
3403                )
3404                .await;
3405            }
3406            return Ok(response);
3407        }
3408
3409        // Bust cache keys if needed
3410        for key in &analysis.cache_bust_keys {
3411            self.manager.delete(key).await?;
3412        }
3413
3414        // Convert response to HttpResponse format
3415        let (parts, body) = response.into_parts();
3416        // Use provided metadata or generate from provider
3417        let effective_metadata = metadata.or_else(|| {
3418            self.options.generate_metadata(&analysis.request_parts, &parts)
3419        });
3420        let mut http_response = self.options.parts_to_http_response(
3421            &parts,
3422            &analysis.request_parts,
3423            effective_metadata,
3424        )?;
3425        http_response.body = body.clone(); // Include the body for buffered cache managers
3426
3427        // Check for response-based cache mode override
3428        let effective_cache_mode = self.options.evaluate_response_cache_mode(
3429            &analysis.request_parts,
3430            &http_response,
3431            analysis.cache_mode,
3432        );
3433
3434        // If response-based override says NoStore, don't cache
3435        if effective_cache_mode == CacheMode::NoStore {
3436            if !analysis.is_get_head {
3437                self.invalidate_get_head(
3438                    &analysis.request_parts,
3439                    StatusCode::from_u16(http_response.status)?,
3440                )
3441                .await;
3442            }
3443            let response = Response::from_parts(parts, body);
3444            return Ok(response);
3445        }
3446
3447        // Create policy and determine if we should cache based on response-based mode
3448        let policy = self.options.create_cache_policy(
3449            &analysis.request_parts,
3450            &http_response.parts()?,
3451        );
3452
3453        let should_cache_response = self.options.should_cache_response(
3454            effective_cache_mode,
3455            &http_response,
3456            analysis.is_get_head,
3457            &policy,
3458        );
3459
3460        if should_cache_response {
3461            self.options.modify_response_before_caching(&mut http_response);
3462            let cached_response = self
3463                .manager
3464                .put(analysis.cache_key, http_response, policy)
3465                .await?;
3466
3467            if !analysis.is_get_head {
3468                self.invalidate_get_head(
3469                    &analysis.request_parts,
3470                    StatusCode::from_u16(cached_response.status)?,
3471                )
3472                .await;
3473            }
3474
3475            // Convert back to standard Response
3476            let response_parts = cached_response.parts()?;
3477            let mut response = Response::builder()
3478                .status(response_parts.status)
3479                .version(response_parts.version)
3480                .body(cached_response.body)?;
3481
3482            // Copy headers from the response parts
3483            *response.headers_mut() = response_parts.headers;
3484
3485            Ok(response)
3486        } else {
3487            if !analysis.is_get_head {
3488                self.invalidate_get_head(
3489                    &analysis.request_parts,
3490                    StatusCode::from_u16(http_response.status)?,
3491                )
3492                .await;
3493            }
3494            // Don't cache, return original response
3495            let response = Response::from_parts(parts, body);
3496            Ok(response)
3497        }
3498    }
3499
3500    fn prepare_conditional_request(
3501        &self,
3502        parts: &mut request::Parts,
3503        _cached_response: &HttpResponse,
3504        policy: &CachePolicy,
3505    ) -> Result<()> {
3506        let before_req = policy.before_request(parts, SystemTime::now());
3507        if let BeforeRequest::Stale { request, .. } = before_req {
3508            parts.headers.extend(request.headers);
3509        }
3510        Ok(())
3511    }
3512
3513    async fn handle_not_modified(
3514        &self,
3515        mut cached_response: HttpResponse,
3516        fresh_parts: &response::Parts,
3517    ) -> Result<HttpResponse> {
3518        cached_response.update_headers(fresh_parts)?;
3519        if self.options.cache_status_headers {
3520            cached_response.cache_status(HitOrMiss::HIT);
3521            cached_response.cache_lookup_status(HitOrMiss::HIT);
3522        }
3523        Ok(cached_response)
3524    }
3525}
3526
3527#[cfg(test)]
3528mod test;