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