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