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;