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