Skip to main content

http_cache/
lib.rs

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