Skip to main content

http_cache_tower/
lib.rs

1//! HTTP caching middleware for Tower services and Axum applications.
2//!
3//! This crate provides Tower layers that implement HTTP caching according to RFC 7234.
4//! It supports both traditional buffered caching and streaming responses for large payloads.
5//!
6//! ## Basic Usage
7//!
8//! ### With Tower Services
9//!
10//! ```rust,no_run
11//! use http_cache_tower::{HttpCacheLayer, RedbManager};
12//! use http_cache::{CacheMode, HttpCache, HttpCacheOptions};
13//! use tower::ServiceBuilder;
14//! use tower::service_fn;
15//! use tower::ServiceExt;
16//! use http::{Request, Response};
17//! use http_body_util::Full;
18//! use bytes::Bytes;
19//! use std::convert::Infallible;
20//!
21//! async fn handler(_req: Request<Full<Bytes>>) -> Result<Response<Full<Bytes>>, Infallible> {
22//!     Ok(Response::new(Full::new(Bytes::from("Hello, World!"))))
23//! }
24//!
25//! #[tokio::main]
26//! async fn main() {
27//!     // Create cache manager with disk storage
28//!     let cache_manager = RedbManager::new("./http-cache.redb").unwrap();
29//!     
30//!     // Create cache layer
31//!     let cache_layer = HttpCacheLayer::new(cache_manager);
32//!     
33//!     // Build service with caching
34//!     let service = ServiceBuilder::new()
35//!         .layer(cache_layer)
36//!         .service_fn(handler);
37//!     
38//!     // Use the service
39//!     let request = Request::builder()
40//!         .uri("http://example.com")
41//!         .body(Full::new(Bytes::new()))
42//!         .unwrap();
43//!     let response = service.oneshot(request).await.unwrap();
44//! }
45//! ```
46//!
47//! ### With Custom Cache Configuration
48//!
49//! ```rust,no_run
50//! use http_cache_tower::{HttpCacheLayer, RedbManager};
51//! use http_cache::{CacheMode, HttpCache, HttpCacheOptions};
52//!
53//! # #[tokio::main]
54//! # async fn main() {
55//! // Create cache manager
56//! let cache_manager = RedbManager::new("./http-cache.redb").unwrap();
57//!
58//! // Configure cache behavior
59//! let cache = HttpCache {
60//!     mode: CacheMode::Default,
61//!     manager: cache_manager,
62//!     options: HttpCacheOptions::default(),
63//! };
64//!
65//! // Create layer with custom cache
66//! let cache_layer = HttpCacheLayer::with_cache(cache);
67//! # }
68//! ```
69//!
70//! ### Streaming Support
71//!
72//! For handling large responses without buffering, use `StreamingManager`:
73//!
74//! ```rust,ignore
75//! use http_cache_tower::HttpCacheStreamingLayer;
76//! use http_cache::StreamingManager;
77//!
78//! # #[tokio::main]
79//! # async fn main() {
80//! // Create streaming cache setup
81//! let streaming_manager = StreamingManager::with_temp_dir(1000).await.unwrap();
82//! let streaming_layer = HttpCacheStreamingLayer::new(streaming_manager);
83//!
84//! // Use with your service
85//! // let service = streaming_layer.layer(your_service);
86//! # }
87//! ```
88//!
89//! ## Cache Modes
90//!
91//! Different cache modes provide different behaviors:
92//!
93//! - `CacheMode::Default`: Follow HTTP caching rules strictly
94//! - `CacheMode::NoStore`: Never cache responses
95//! - `CacheMode::NoCache`: Always revalidate with the origin server
96//! - `CacheMode::ForceCache`: Cache responses even if headers suggest otherwise
97//! - `CacheMode::OnlyIfCached`: Only serve from cache, never hit origin server
98//! - `CacheMode::IgnoreRules`: Cache everything regardless of headers
99//!
100//! ## Cache Invalidation
101//!
102//! The middleware automatically handles cache invalidation for unsafe HTTP methods:
103//!
104//! ```text
105//! These methods will invalidate any cached GET response for the same URI:
106//! - PUT /api/users/123    -> invalidates GET /api/users/123
107//! - POST /api/users/123   -> invalidates GET /api/users/123  
108//! - DELETE /api/users/123 -> invalidates GET /api/users/123
109//! - PATCH /api/users/123  -> invalidates GET /api/users/123
110//! ```
111//!
112//! ## Integration with Other Tower Layers
113//!
114//! The cache layer works with other Tower middleware:
115//!
116//! ```rust,no_run
117//! use tower::ServiceBuilder;
118//! use http_cache_tower::{HttpCacheLayer, RedbManager};
119//! use tower::service_fn;
120//! use tower::ServiceExt;
121//! use http::{Request, Response};
122//! use http_body_util::Full;
123//! use bytes::Bytes;
124//! use std::convert::Infallible;
125//!
126//! async fn handler(_req: Request<Full<Bytes>>) -> Result<Response<Full<Bytes>>, Infallible> {
127//!     Ok(Response::new(Full::new(Bytes::from("Hello, World!"))))
128//! }
129//!
130//! #[tokio::main]
131//! async fn main() {
132//!     let cache_manager = RedbManager::new("./http-cache.redb").unwrap();
133//!     let cache_layer = HttpCacheLayer::new(cache_manager);
134//!
135//!     let service = ServiceBuilder::new()
136//!         // .layer(TraceLayer::new_for_http())  // Logging (requires tower-http)
137//!         // .layer(CompressionLayer::new())     // Compression (requires tower-http)
138//!         .layer(cache_layer)                    // Caching
139//!         .service_fn(handler);
140//!     
141//!     // Use the service
142//!     let request = Request::builder()
143//!         .uri("http://example.com")
144//!         .body(Full::new(Bytes::new()))
145//!         .unwrap();
146//!     let response = service.oneshot(request).await.unwrap();
147//! }
148//! ```
149
150use bytes::Bytes;
151use http::{
152    header::CACHE_CONTROL, request, HeaderValue, Method, Request, Response,
153};
154use http_body::Body;
155use http_body_util::BodyExt;
156
157#[cfg(feature = "manager-cacache")]
158pub use http_cache::CACacheManager;
159
160#[cfg(feature = "manager-redb")]
161pub use http_cache::RedbManager;
162
163#[cfg(feature = "rate-limiting")]
164pub use http_cache::rate_limiting::{
165    CacheAwareRateLimiter, DirectRateLimiter, DomainRateLimiter, Quota,
166};
167#[cfg(feature = "streaming")]
168use http_cache::StreamingError;
169use http_cache::{
170    url_parse, BoxError, CacheManager, CacheMode, CacheOptions, HitOrMiss,
171    HttpCache, HttpCacheOptions, HttpResponse, Middleware, Url, XCACHE,
172    XCACHELOOKUP,
173};
174#[cfg(feature = "streaming")]
175use http_cache::{HttpStreamingCache, StreamingCacheManager};
176use http_cache_semantics::CachePolicy;
177use std::{
178    pin::Pin,
179    sync::Arc,
180    task::{Context, Poll},
181    time::SystemTime,
182};
183use tower::{Layer, Service, ServiceExt};
184
185// Re-export unified error types from http-cache core
186pub use http_cache::HttpCacheError;
187
188#[cfg(feature = "streaming")]
189/// Type alias for tower streaming errors, using the unified streaming error system
190pub type TowerStreamingError = http_cache::ClientStreamingError;
191
192/// Helper functions for error conversions
193trait HttpCacheErrorExt<T> {
194    fn cache_err(self) -> Result<T, HttpCacheError>;
195}
196
197impl<T, E> HttpCacheErrorExt<T> for Result<T, E>
198where
199    E: ToString,
200{
201    fn cache_err(self) -> Result<T, HttpCacheError> {
202        self.map_err(|e| HttpCacheError::cache(e.to_string()))
203    }
204}
205
206/// Helper function to add cache status headers to a response
207fn add_cache_status_headers<B>(
208    mut response: Response<HttpCacheBody<B>>,
209    hit_or_miss: &str,
210    cache_lookup: &str,
211) -> Response<HttpCacheBody<B>> {
212    let headers = response.headers_mut();
213    if let Ok(hv) = HeaderValue::from_str(hit_or_miss) {
214        headers.insert(XCACHE, hv);
215    }
216    if let Ok(hv) = HeaderValue::from_str(cache_lookup) {
217        headers.insert(XCACHELOOKUP, hv);
218    }
219    response
220}
221
222/// Middleware adapter that bridges Tower services to the `http_cache::Middleware`
223/// trait, allowing `HttpCache::run` to drive the full cache flow (mode dispatch,
224/// conditional revalidation, 5xx handling, warning headers, etc.) instead of
225/// reimplementing it inline.
226struct TowerMiddleware<S, ReqBody> {
227    parts: request::Parts,
228    body: Option<ReqBody>,
229    service: Option<S>,
230}
231
232impl<S, ReqBody, ResBody> Middleware for TowerMiddleware<S, ReqBody>
233where
234    S: Service<Request<ReqBody>, Response = Response<ResBody>>
235        + Clone
236        + Send
237        + 'static,
238    S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
239    S::Future: Send + 'static,
240    ReqBody: Body + Send + 'static,
241    ReqBody::Data: Send,
242    ReqBody::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
243    ResBody: Body + Send + 'static,
244    ResBody::Data: Send,
245    ResBody::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
246{
247    fn is_method_get_head(&self) -> bool {
248        self.parts.method == Method::GET || self.parts.method == Method::HEAD
249    }
250
251    fn policy(
252        &self,
253        response: &HttpResponse,
254    ) -> http_cache::Result<CachePolicy> {
255        Ok(CachePolicy::new(&self.parts, &response.parts()?))
256    }
257
258    fn policy_with_options(
259        &self,
260        response: &HttpResponse,
261        options: CacheOptions,
262    ) -> http_cache::Result<CachePolicy> {
263        Ok(CachePolicy::new_options(
264            &self.parts,
265            &response.parts()?,
266            SystemTime::now(),
267            options,
268        ))
269    }
270
271    fn update_headers(
272        &mut self,
273        parts: &request::Parts,
274    ) -> http_cache::Result<()> {
275        for (name, value) in parts.headers.iter() {
276            self.parts.headers.insert(name.clone(), value.clone());
277        }
278        Ok(())
279    }
280
281    fn force_no_cache(&mut self) -> http_cache::Result<()> {
282        self.parts
283            .headers
284            .insert(CACHE_CONTROL, HeaderValue::from_static("no-cache"));
285        Ok(())
286    }
287
288    fn parts(&self) -> http_cache::Result<request::Parts> {
289        Ok(self.parts.clone())
290    }
291
292    fn url(&self) -> http_cache::Result<Url> {
293        url_parse(self.parts.uri.to_string().as_str())
294    }
295
296    fn method(&self) -> http_cache::Result<String> {
297        Ok(self.parts.method.as_ref().to_string())
298    }
299
300    async fn remote_fetch(&mut self) -> http_cache::Result<HttpResponse> {
301        let body = self
302            .body
303            .take()
304            .ok_or_else(|| BoxError::from("request body already consumed"))?;
305        let service = self
306            .service
307            .take()
308            .ok_or_else(|| BoxError::from("inner service already consumed"))?;
309
310        let request = Request::from_parts(self.parts.clone(), body);
311        let response = service.oneshot(request).await.map_err(|e| {
312            let boxed: Box<dyn std::error::Error + Send + Sync> = e.into();
313            boxed
314        })?;
315
316        let (res_parts, res_body) = response.into_parts();
317        let collected = BodyExt::collect(res_body).await.map_err(|e| {
318            let boxed: Box<dyn std::error::Error + Send + Sync> = e.into();
319            boxed
320        })?;
321        let body_bytes = collected.to_bytes().to_vec();
322
323        let url = url_parse(self.parts.uri.to_string().as_str())?;
324        let headers = (&res_parts.headers).into();
325        let status = res_parts.status.as_u16();
326        let version = res_parts.version.try_into()?;
327
328        Ok(HttpResponse {
329            body: body_bytes,
330            headers,
331            status,
332            url,
333            version,
334            metadata: None,
335        })
336    }
337}
338
339/// Convert an [`HttpResponse`] from the cache core into a Tower
340/// `Response<HttpCacheBody<B>>`.
341fn http_response_to_tower_response<B>(
342    http_response: HttpResponse,
343) -> Result<Response<HttpCacheBody<B>>, HttpCacheError> {
344    let mut response = HttpCacheOptions::http_response_to_response(
345        &http_response,
346        HttpCacheBody::Buffered(http_response.body.clone()),
347    )
348    .map_err(HttpCacheError::other)?;
349
350    // Preserve metadata in response extensions
351    if let Some(metadata) = http_response.metadata {
352        response
353            .extensions_mut()
354            .insert(http_cache::HttpCacheMetadata::from(metadata));
355    }
356
357    Ok(response)
358}
359
360#[cfg(feature = "streaming")]
361fn add_cache_status_headers_streaming<B>(
362    mut response: Response<B>,
363    hit_or_miss: &str,
364    cache_lookup: &str,
365) -> Response<B> {
366    let headers = response.headers_mut();
367    if let Ok(hv) = HeaderValue::from_str(hit_or_miss) {
368        headers.insert(XCACHE, hv);
369    }
370    if let Ok(hv) = HeaderValue::from_str(cache_lookup) {
371        headers.insert(XCACHELOOKUP, hv);
372    }
373    response
374}
375
376/// HTTP cache layer for Tower services.
377///
378/// This layer implements HTTP caching according to RFC 7234, automatically caching
379/// GET and HEAD responses based on their cache-control headers and invalidating
380/// cache entries when unsafe methods (PUT, POST, DELETE, PATCH) are used.
381///
382/// # Example
383///
384/// ```rust,no_run
385/// use http_cache_tower::{HttpCacheLayer, RedbManager};
386/// use tower::ServiceBuilder;
387/// use tower::service_fn;
388/// use http::{Request, Response};
389/// use http_body_util::Full;
390/// use bytes::Bytes;
391/// use std::convert::Infallible;
392///
393/// # #[tokio::main]
394/// # async fn main() {
395/// let cache_manager = RedbManager::new("./http-cache.redb").unwrap();
396/// let cache_layer = HttpCacheLayer::new(cache_manager);
397///
398/// // Use with ServiceBuilder
399/// let service = ServiceBuilder::new()
400///     .layer(cache_layer)
401///     .service_fn(|_req: Request<Full<Bytes>>| async {
402///         Ok::<_, Infallible>(Response::new(Full::new(Bytes::from("Hello"))))
403///     });
404/// # }
405/// ```
406#[derive(Clone)]
407pub struct HttpCacheLayer<CM>
408where
409    CM: CacheManager,
410{
411    cache: Arc<HttpCache<CM>>,
412}
413
414impl<CM> HttpCacheLayer<CM>
415where
416    CM: CacheManager,
417{
418    /// Create a new HTTP cache layer with default configuration.
419    ///
420    /// Uses [`CacheMode::Default`] and default [`HttpCacheOptions`].
421    ///
422    /// # Arguments
423    ///
424    /// * `cache_manager` - The cache manager to use for storing responses
425    ///
426    /// # Example
427    ///
428    /// ```rust,no_run
429    /// use http_cache_tower::{HttpCacheLayer, RedbManager};
430    ///
431    /// # #[tokio::main]
432    /// # async fn main() {
433    /// let cache_manager = RedbManager::new("./http-cache.redb").unwrap();
434    /// let layer = HttpCacheLayer::new(cache_manager);
435    /// # }
436    /// ```
437    pub fn new(cache_manager: CM) -> Self {
438        Self {
439            cache: Arc::new(HttpCache {
440                mode: CacheMode::Default,
441                manager: cache_manager,
442                options: HttpCacheOptions::default(),
443            }),
444        }
445    }
446
447    /// Create a new HTTP cache layer with custom options.
448    ///
449    /// Uses [`CacheMode::Default`] but allows customizing the cache behavior
450    /// through [`HttpCacheOptions`].
451    ///
452    /// # Arguments
453    ///
454    /// * `cache_manager` - The cache manager to use for storing responses
455    /// * `options` - Custom cache options
456    ///
457    /// # Example
458    ///
459    /// ```rust,no_run
460    /// use http_cache_tower::{HttpCacheLayer, RedbManager};
461    /// use http_cache::HttpCacheOptions;
462    ///
463    /// # #[tokio::main]
464    /// # async fn main() {
465    /// let cache_manager = RedbManager::new("./http-cache.redb").unwrap();
466    ///
467    /// let options = HttpCacheOptions {
468    ///     cache_key: Some(std::sync::Arc::new(|req: &http::request::Parts| {
469    ///         format!("custom:{}:{}", req.method, req.uri)
470    ///     })),
471    ///     ..Default::default()
472    /// };
473    ///
474    /// let layer = HttpCacheLayer::with_options(cache_manager, options);
475    /// # }
476    /// ```
477    pub fn with_options(cache_manager: CM, options: HttpCacheOptions) -> Self {
478        Self {
479            cache: Arc::new(HttpCache {
480                mode: CacheMode::Default,
481                manager: cache_manager,
482                options,
483            }),
484        }
485    }
486
487    /// Create a new HTTP cache layer with a pre-configured cache.
488    ///
489    /// This method gives you full control over the cache configuration,
490    /// including the cache mode.
491    ///
492    /// # Arguments
493    ///
494    /// * `cache` - A fully configured HttpCache instance
495    ///
496    /// # Example
497    ///
498    /// ```rust,no_run
499    /// use http_cache_tower::{HttpCacheLayer, RedbManager};
500    /// use http_cache::{HttpCache, CacheMode, HttpCacheOptions};
501    ///
502    /// # #[tokio::main]
503    /// # async fn main() {
504    /// let cache_manager = RedbManager::new("./http-cache.redb").unwrap();
505    ///
506    /// let cache = HttpCache {
507    ///     mode: CacheMode::ForceCache,
508    ///     manager: cache_manager,
509    ///     options: HttpCacheOptions::default(),
510    /// };
511    ///
512    /// let layer = HttpCacheLayer::with_cache(cache);
513    /// # }
514    /// ```
515    pub fn with_cache(cache: HttpCache<CM>) -> Self {
516        Self { cache: Arc::new(cache) }
517    }
518}
519
520/// HTTP cache layer with streaming support for Tower services.
521///
522/// This layer provides the same HTTP caching functionality as [`HttpCacheLayer`]
523/// but handles streaming responses. It can work with large
524/// responses without buffering them entirely in memory.
525///
526/// # Example
527///
528/// ```rust,no_run
529/// use http_cache_tower::HttpCacheStreamingLayer;
530/// use http_cache::StreamingManager;
531/// use tower::ServiceBuilder;
532/// use tower::service_fn;
533/// use http::{Request, Response};
534/// use http_body_util::Full;
535/// use bytes::Bytes;
536/// use std::convert::Infallible;
537///
538/// async fn handler(_req: Request<Full<Bytes>>) -> Result<Response<Full<Bytes>>, Infallible> {
539///     Ok(Response::new(Full::new(Bytes::from("Hello"))))
540/// }
541///
542/// # #[tokio::main]
543/// # async fn main() {
544/// let streaming_manager = StreamingManager::with_temp_dir(1000).await.unwrap();
545/// let streaming_layer = HttpCacheStreamingLayer::new(streaming_manager);
546///
547/// // Use with ServiceBuilder
548/// let service = ServiceBuilder::new()
549///     .layer(streaming_layer)
550///     .service_fn(handler);
551/// # }
552/// ```
553#[cfg(feature = "streaming")]
554#[derive(Clone)]
555pub struct HttpCacheStreamingLayer<CM>
556where
557    CM: StreamingCacheManager,
558{
559    cache: Arc<HttpStreamingCache<CM>>,
560}
561
562#[cfg(feature = "streaming")]
563impl<CM> HttpCacheStreamingLayer<CM>
564where
565    CM: StreamingCacheManager,
566{
567    /// Create a new HTTP cache streaming layer with default configuration.
568    ///
569    /// Uses [`CacheMode::Default`] and default [`HttpCacheOptions`].
570    ///
571    /// # Arguments
572    ///
573    /// * `cache_manager` - The streaming cache manager to use
574    ///
575    /// # Example
576    ///
577    /// ```rust,no_run
578    /// use http_cache_tower::HttpCacheStreamingLayer;
579    /// use http_cache::StreamingManager;
580    ///
581    /// # #[tokio::main]
582    /// # async fn main() {
583    /// let streaming_manager = StreamingManager::with_temp_dir(1000).await.unwrap();
584    /// let layer = HttpCacheStreamingLayer::new(streaming_manager);
585    /// # }
586    /// ```
587    pub fn new(cache_manager: CM) -> Self {
588        Self {
589            cache: Arc::new(HttpStreamingCache {
590                mode: CacheMode::Default,
591                manager: cache_manager,
592                options: HttpCacheOptions::default(),
593            }),
594        }
595    }
596
597    /// Create a new HTTP cache streaming layer with custom options.
598    ///
599    /// Uses [`CacheMode::Default`] but allows customizing cache behavior.
600    ///
601    /// # Arguments
602    ///
603    /// * `cache_manager` - The streaming cache manager to use
604    /// * `options` - Custom cache options
605    ///
606    /// # Example
607    ///
608    /// ```rust,no_run
609    /// use http_cache_tower::HttpCacheStreamingLayer;
610    /// use http_cache::{StreamingManager, HttpCacheOptions};
611    ///
612    /// # #[tokio::main]
613    /// # async fn main() {
614    /// let streaming_manager = StreamingManager::with_temp_dir(1000).await.unwrap();
615    ///
616    /// let options = HttpCacheOptions {
617    ///     cache_key: Some(std::sync::Arc::new(|req: &http::request::Parts| {
618    ///         format!("stream:{}:{}", req.method, req.uri)
619    ///     })),
620    ///     ..Default::default()
621    /// };
622    ///
623    /// let layer = HttpCacheStreamingLayer::with_options(streaming_manager, options);
624    /// # }
625    /// ```
626    pub fn with_options(cache_manager: CM, options: HttpCacheOptions) -> Self {
627        Self {
628            cache: Arc::new(HttpStreamingCache {
629                mode: CacheMode::Default,
630                manager: cache_manager,
631                options,
632            }),
633        }
634    }
635
636    /// Create a new HTTP cache streaming layer with a pre-configured cache.
637    ///
638    /// This method gives you full control over the streaming cache configuration.
639    ///
640    /// # Arguments
641    ///
642    /// * `cache` - A fully configured HttpStreamingCache instance
643    ///
644    /// # Example
645    ///
646    /// ```rust,no_run
647    /// use http_cache_tower::HttpCacheStreamingLayer;
648    /// use http_cache::{StreamingManager, HttpStreamingCache, CacheMode, HttpCacheOptions};
649    ///
650    /// # #[tokio::main]
651    /// # async fn main() {
652    /// let streaming_manager = StreamingManager::with_temp_dir(1000).await.unwrap();
653    ///
654    /// let cache = HttpStreamingCache {
655    ///     mode: CacheMode::ForceCache,
656    ///     manager: streaming_manager,
657    ///     options: HttpCacheOptions::default(),
658    /// };
659    ///
660    /// let layer = HttpCacheStreamingLayer::with_cache(cache);
661    /// # }
662    /// ```
663    pub fn with_cache(cache: HttpStreamingCache<CM>) -> Self {
664        Self { cache: Arc::new(cache) }
665    }
666}
667
668impl<S, CM> Layer<S> for HttpCacheLayer<CM>
669where
670    CM: CacheManager,
671{
672    type Service = HttpCacheService<S, CM>;
673
674    fn layer(&self, inner: S) -> Self::Service {
675        HttpCacheService { inner, cache: self.cache.clone() }
676    }
677}
678
679#[cfg(feature = "streaming")]
680impl<S, CM> Layer<S> for HttpCacheStreamingLayer<CM>
681where
682    CM: StreamingCacheManager,
683{
684    type Service = HttpCacheStreamingService<S, CM>;
685
686    fn layer(&self, inner: S) -> Self::Service {
687        HttpCacheStreamingService { inner, cache: self.cache.clone() }
688    }
689}
690
691/// HTTP cache service for Tower/Hyper
692pub struct HttpCacheService<S, CM>
693where
694    CM: CacheManager,
695{
696    inner: S,
697    cache: Arc<HttpCache<CM>>,
698}
699
700impl<S, CM> Clone for HttpCacheService<S, CM>
701where
702    S: Clone,
703    CM: CacheManager,
704{
705    fn clone(&self) -> Self {
706        Self { inner: self.inner.clone(), cache: self.cache.clone() }
707    }
708}
709
710/// HTTP cache streaming service for Tower/Hyper
711#[cfg(feature = "streaming")]
712pub struct HttpCacheStreamingService<S, CM>
713where
714    CM: StreamingCacheManager,
715{
716    inner: S,
717    cache: Arc<HttpStreamingCache<CM>>,
718}
719
720#[cfg(feature = "streaming")]
721impl<S, CM> Clone for HttpCacheStreamingService<S, CM>
722where
723    S: Clone,
724    CM: StreamingCacheManager,
725{
726    fn clone(&self) -> Self {
727        Self { inner: self.inner.clone(), cache: self.cache.clone() }
728    }
729}
730
731impl<S, CM, ReqBody, ResBody> Service<Request<ReqBody>>
732    for HttpCacheService<S, CM>
733where
734    S: Service<Request<ReqBody>, Response = Response<ResBody>>
735        + Clone
736        + Send
737        + 'static,
738    S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
739    S::Future: Send + 'static,
740    ReqBody: Body + Send + 'static,
741    ReqBody::Data: Send,
742    ReqBody::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
743    ResBody: Body + Send + 'static,
744    ResBody::Data: Send,
745    ResBody::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
746    CM: CacheManager,
747{
748    type Response = Response<HttpCacheBody<ResBody>>;
749    type Error = HttpCacheError;
750    type Future = Pin<
751        Box<
752            dyn std::future::Future<
753                    Output = Result<Self::Response, Self::Error>,
754                > + Send,
755        >,
756    >;
757
758    fn poll_ready(
759        &mut self,
760        cx: &mut Context<'_>,
761    ) -> Poll<Result<(), Self::Error>> {
762        self.inner.poll_ready(cx).map_err(|e| HttpCacheError::http(e.into()))
763    }
764
765    fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
766        let cache = self.cache.clone();
767        let (parts, body) = req.into_parts();
768        let inner_service = self.inner.clone();
769
770        Box::pin(async move {
771            let middleware = TowerMiddleware {
772                parts: parts.clone(),
773                body: Some(body),
774                service: Some(inner_service),
775            };
776
777            let can_cache = cache.can_cache_request(&middleware).cache_err()?;
778
779            if can_cache {
780                // Delegate the full cache orchestration (mode dispatch,
781                // conditional revalidation, 304/5xx handling, warning
782                // headers, rate limiting, cache busting) to the core.
783                let res = cache.run(middleware).await.cache_err()?;
784                http_response_to_tower_response(res)
785            } else {
786                // Not cacheable -- forward directly, then invalidate on
787                // success (RFC 7234 Section 4.4).
788                let parts_for_invalidation = middleware.parts().cache_err()?;
789
790                // Reconstruct the request from the middleware's parts.
791                let body = middleware.body.ok_or_else(|| {
792                    HttpCacheError::cache(
793                        "request body already consumed".to_string(),
794                    )
795                })?;
796                let service = middleware.service.ok_or_else(|| {
797                    HttpCacheError::cache(
798                        "inner service already consumed".to_string(),
799                    )
800                })?;
801                let req = Request::from_parts(parts, body);
802
803                let response = service.oneshot(req).await.map_err(|e| {
804                    let boxed: Box<dyn std::error::Error + Send + Sync> =
805                        e.into();
806                    HttpCacheError::http(boxed)
807                })?;
808
809                // Only invalidate for unsafe methods after successful response (RFC 7234 s4.4)
810                if !parts_for_invalidation.method.is_safe()
811                    && (response.status().is_success()
812                        || response.status().is_redirection())
813                {
814                    cache
815                        .run_no_cache_from_parts(&parts_for_invalidation)
816                        .await
817                        .cache_err()?;
818                }
819
820                let mut response = response.map(HttpCacheBody::Original);
821
822                if cache.options.cache_status_headers {
823                    response = add_cache_status_headers(
824                        response,
825                        HitOrMiss::MISS.to_string().as_ref(),
826                        HitOrMiss::MISS.to_string().as_ref(),
827                    );
828                }
829
830                Ok(response)
831            }
832        })
833    }
834}
835
836// Hyper service implementation for HttpCacheService
837impl<S, CM> hyper::service::Service<Request<hyper::body::Incoming>>
838    for HttpCacheService<S, CM>
839where
840    S: Service<
841            Request<hyper::body::Incoming>,
842            Response = Response<http_body_util::Full<Bytes>>,
843        > + Clone
844        + Send
845        + 'static,
846    S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
847    S::Future: Send + 'static,
848    CM: CacheManager,
849{
850    type Response = Response<HttpCacheBody<http_body_util::Full<Bytes>>>;
851    type Error = HttpCacheError;
852    type Future = Pin<
853        Box<
854            dyn std::future::Future<
855                    Output = Result<Self::Response, Self::Error>,
856                > + Send,
857        >,
858    >;
859
860    fn call(&self, req: Request<hyper::body::Incoming>) -> Self::Future {
861        // Delegate to the Tower Service impl, which takes &mut self
862        let mut service_clone = self.clone();
863        Box::pin(
864            async move { tower::Service::call(&mut service_clone, req).await },
865        )
866    }
867}
868
869#[cfg(feature = "streaming")]
870impl<S, CM, ReqBody, ResBody> Service<Request<ReqBody>>
871    for HttpCacheStreamingService<S, CM>
872where
873    S: Service<Request<ReqBody>, Response = Response<ResBody>>
874        + Clone
875        + Send
876        + 'static,
877    S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
878    S::Future: Send + 'static,
879    ReqBody: Body + Send + 'static,
880    ReqBody::Data: Send,
881    ReqBody::Error: Into<StreamingError>,
882    ResBody: Body + Send + 'static,
883    ResBody::Data: Send,
884    ResBody::Error: Into<StreamingError>,
885    CM: StreamingCacheManager,
886    <CM::Body as http_body::Body>::Data: Send,
887    <CM::Body as http_body::Body>::Error:
888        Into<StreamingError> + Send + Sync + 'static,
889{
890    type Response = Response<CM::Body>;
891    type Error = HttpCacheError;
892    type Future = Pin<
893        Box<
894            dyn std::future::Future<
895                    Output = Result<Self::Response, Self::Error>,
896                > + Send,
897        >,
898    >;
899
900    fn poll_ready(
901        &mut self,
902        cx: &mut Context<'_>,
903    ) -> Poll<Result<(), Self::Error>> {
904        self.inner.poll_ready(cx).map_err(|e| HttpCacheError::http(e.into()))
905    }
906
907    fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
908        let cache = self.cache.clone();
909        let (parts, body) = req.into_parts();
910        let inner_service = self.inner.clone();
911
912        Box::pin(async move {
913            // Check whether this request is cacheable.  Non-cacheable
914            // requests (e.g. POST/PUT/DELETE) are forwarded directly and
915            // only trigger cache invalidation on success.
916            let can_cache =
917                cache.can_cache_request(&parts, None).cache_err()?;
918
919            if !can_cache {
920                // Forward the request without cache orchestration.
921                let req = Request::from_parts(parts.clone(), body);
922                let response =
923                    inner_service.oneshot(req).await.map_err(|e| {
924                        let boxed: Box<dyn std::error::Error + Send + Sync> =
925                            e.into();
926                        HttpCacheError::http(boxed)
927                    })?;
928
929                // Only invalidate for unsafe methods after successful response (RFC 7234 s4.4)
930                if !parts.method.is_safe()
931                    && (response.status().is_success()
932                        || response.status().is_redirection())
933                {
934                    cache.run_no_cache(&parts).await.cache_err()?;
935                }
936
937                let mut converted =
938                    cache.manager.convert_body(response).await.cache_err()?;
939
940                if cache.options.cache_status_headers {
941                    converted = add_cache_status_headers_streaming(
942                        converted, "MISS", "MISS",
943                    );
944                }
945
946                return Ok(converted);
947            }
948
949            // Delegate the full cache orchestration (analyse, lookup,
950            // conditional revalidation, 304/200/5xx handling, rate
951            // limiting, warning headers, cache busting) to the core
952            // library.
953            //
954            // The closure is `FnOnce` and called at most once.
955            // We move `body` and `inner_service` directly into the
956            // closure.
957            let result = cache
958                .run(&parts, None, |fetch_req| {
959                    let parts_ref = parts.clone();
960                    async move {
961                        let request_parts = match fetch_req {
962                            http_cache::FetchRequest::Fresh => parts_ref,
963                            http_cache::FetchRequest::FreshNoCache => {
964                                let mut p = parts_ref;
965                                p.headers.insert(
966                                    CACHE_CONTROL,
967                                    HeaderValue::from_static("no-cache"),
968                                );
969                                p
970                            }
971                            http_cache::FetchRequest::Conditional(
972                                cond_parts,
973                            ) => *cond_parts,
974                        };
975
976                        let req = Request::from_parts(request_parts, body);
977
978                        inner_service.oneshot(req).await.map_err(|e| {
979                            let boxed: Box<
980                                dyn std::error::Error + Send + Sync,
981                            > = e.into();
982                            boxed
983                        })
984                    }
985                })
986                .await
987                .cache_err()?;
988
989            Ok(result)
990        })
991    }
992}
993
994/// Body type that wraps cached responses  
995pub enum HttpCacheBody<B> {
996    /// Buffered body from cache
997    Buffered(Vec<u8>),
998    /// Original body (fallback)
999    Original(B),
1000}
1001
1002impl<B> Body for HttpCacheBody<B>
1003where
1004    B: Body + Unpin,
1005    B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
1006    B::Data: Into<bytes::Bytes>,
1007{
1008    type Data = bytes::Bytes;
1009    type Error = Box<dyn std::error::Error + Send + Sync>;
1010
1011    fn poll_frame(
1012        mut self: Pin<&mut Self>,
1013        cx: &mut Context<'_>,
1014    ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
1015        match &mut *self {
1016            HttpCacheBody::Buffered(bytes) => {
1017                if bytes.is_empty() {
1018                    Poll::Ready(None)
1019                } else {
1020                    let data = std::mem::take(bytes);
1021                    Poll::Ready(Some(Ok(http_body::Frame::data(
1022                        bytes::Bytes::from(data),
1023                    ))))
1024                }
1025            }
1026            HttpCacheBody::Original(body) => {
1027                Pin::new(body).poll_frame(cx).map(|opt| {
1028                    opt.map(|res| {
1029                        res.map(|frame| frame.map_data(Into::into))
1030                            .map_err(Into::into)
1031                    })
1032                })
1033            }
1034        }
1035    }
1036
1037    fn is_end_stream(&self) -> bool {
1038        match self {
1039            HttpCacheBody::Buffered(bytes) => bytes.is_empty(),
1040            HttpCacheBody::Original(body) => body.is_end_stream(),
1041        }
1042    }
1043
1044    fn size_hint(&self) -> http_body::SizeHint {
1045        match self {
1046            HttpCacheBody::Buffered(bytes) => {
1047                let len = bytes.len() as u64;
1048                http_body::SizeHint::with_exact(len)
1049            }
1050            HttpCacheBody::Original(body) => body.size_hint(),
1051        }
1052    }
1053}
1054
1055#[cfg(test)]
1056mod test;