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