Skip to main content

salvo_cache/
lib.rs

1#![cfg_attr(test, allow(clippy::unwrap_used))]
2//! Response caching middleware for the Salvo web framework.
3//!
4//! This middleware intercepts HTTP responses and caches them for subsequent
5//! requests, reducing server load and improving response times for cacheable
6//! content.
7//!
8//! # What Gets Cached
9//!
10//! The cache stores the complete response including:
11//! - HTTP status code
12//! - Response headers
13//! - Response body (except for streaming responses)
14//!
15//! # Key Components
16//!
17//! - [`CacheIssuer`]: Determines the cache key for each request
18//! - [`CacheStore`]: Backend storage for cached responses
19//! - [`Cache`]: The middleware handler
20//!
21//! # Default Implementations
22//!
23//! - [`RequestIssuer`]: Generates cache keys from the request URI and method
24//! - [`MokaStore`]: High-performance concurrent cache backed by [`moka`]
25//!
26//! # Example
27//!
28//! ```ignore
29//! use std::time::Duration;
30//! use salvo_cache::{Cache, MokaStore, RequestIssuer};
31//! use salvo_core::prelude::*;
32//!
33//! let cache = Cache::new(
34//!     MokaStore::builder()
35//!         .time_to_live(Duration::from_secs(300))  // Cache for 5 minutes
36//!         .build(),
37//!     RequestIssuer::default(),
38//! );
39//!
40//! let router = Router::new()
41//!     .hoop(cache)
42//!     .get(my_expensive_handler);
43//! ```
44//!
45//! # Custom Cache Keys
46//!
47//! Implement [`CacheIssuer`] to customize cache key generation:
48//!
49//! ```ignore
50//! use salvo_cache::CacheIssuer;
51//!
52//! struct UserBasedIssuer;
53//! impl CacheIssuer for UserBasedIssuer {
54//!     type Key = String;
55//!
56//!     async fn issue(&self, req: &mut Request, depot: &Depot) -> Option<Self::Key> {
57//!         // Cache per user + path
58//!         let user_id = depot.get::<String>("user_id").ok()?;
59//!         Some(format!("{}:{}", user_id, req.uri().path()))
60//!     }
61//! }
62//! ```
63//!
64//! # Skipping Cache
65//!
66//! By default, only GET requests are cached. Use the `skipper` method to customize:
67//!
68//! ```ignore
69//! let cache = Cache::new(store, issuer)
70//!     .skipper(|req, _depot| req.uri().path().starts_with("/api/"));
71//! ```
72//!
73//! # Concurrent Misses
74//!
75//! Concurrent misses for the same cache key are coalesced. One request populates
76//! the cache, and other in-flight requests reuse the generated cache entry when
77//! the response is cacheable. At most [`DEFAULT_MAX_IN_FLIGHT`] distinct cache
78//! keys are coalesced at once by default; additional misses bypass coalescing and
79//! execute normally until an in-flight slot is released.
80//!
81//! # Limitations
82//!
83//! - Streaming responses ([`ResBody::Stream`]) cannot be cached
84//! - Error responses are not cached
85//!
86//! Read more: <https://salvo.rs>
87#![doc(html_favicon_url = "https://salvo.rs/favicon-32x32.png")]
88#![doc(html_logo_url = "https://salvo.rs/images/logo.svg")]
89#![cfg_attr(docsrs, feature(doc_cfg))]
90
91use std::borrow::Borrow;
92use std::collections::{HashMap, VecDeque};
93use std::error::Error as StdError;
94use std::fmt::{self, Debug, Formatter};
95use std::hash::Hash;
96use std::sync::{Arc, Mutex, MutexGuard};
97
98use bytes::Bytes;
99use salvo_core::handler::Skipper;
100use salvo_core::http::header::{AUTHORIZATION, CACHE_CONTROL, COOKIE, SET_COOKIE, VARY};
101use salvo_core::http::{HeaderMap, ResBody, StatusCode};
102use salvo_core::{Depot, Error, FlowCtrl, Handler, Request, Response, async_trait, cfg_feature};
103use tokio::sync::Notify;
104
105mod skipper;
106pub use skipper::MethodSkipper;
107
108cfg_feature! {
109    #![feature = "moka-store"]
110
111    pub mod moka_store;
112    pub use moka_store::{MokaStore};
113}
114
115/// Issues a cache key for a request, deciding whether the request should be cached.
116pub trait CacheIssuer: Send + Sync + 'static {
117    /// The key type used to identify a cached entry.
118    type Key: Hash + Eq + Send + Sync + 'static;
119    /// Issue a key for the request. If it returns `None`, the request will not be cached.
120    fn issue(
121        &self,
122        req: &mut Request,
123        depot: &Depot,
124    ) -> impl Future<Output = Option<Self::Key>> + Send;
125}
126impl<F, K> CacheIssuer for F
127where
128    F: Fn(&mut Request, &Depot) -> Option<K> + Send + Sync + 'static,
129    K: Hash + Eq + Send + Sync + 'static,
130{
131    type Key = K;
132    async fn issue(&self, req: &mut Request, depot: &Depot) -> Option<Self::Key> {
133        self(req, depot)
134    }
135}
136
137/// Identify cacheable requests by their URI.
138///
139/// # Caveats
140///
141/// The generated key is derived only from the request's scheme, authority,
142/// path, query, and (optionally) method. It does **not** include content
143/// negotiation headers such as `Accept-Encoding` or `Accept`. If the cached
144/// responses vary by those headers — for example when a compression middleware
145/// also runs — a client may receive a representation encoded for a different
146/// request (e.g. a `gzip` body without `Accept-Encoding: gzip`).
147///
148/// The cache stores the response produced by the handlers *inside* it (`hoop`s
149/// run outer-to-inner and the entry is captured on the way back out), so the
150/// negotiating middleware must run **outside** the cache — added to the router
151/// *before* the cache hoop — so it re-negotiates on every request, including
152/// cache hits. Alternatively, use a custom [`CacheIssuer`] that folds the
153/// relevant headers into the key.
154///
155/// Note that a `Vary` response header is **not** a fix here: the store never
156/// evaluates `Vary` at lookup time, so a `Vary` response is never cached (in
157/// either `cache_private` mode) — it would otherwise be replayed under the same
158/// key regardless of the request's negotiation headers. Fold the relevant headers
159/// into a custom [`CacheIssuer`] key instead.
160#[derive(Clone, Debug)]
161pub struct RequestIssuer {
162    use_scheme: bool,
163    use_authority: bool,
164    use_path: bool,
165    use_query: bool,
166    use_method: bool,
167}
168impl Default for RequestIssuer {
169    fn default() -> Self {
170        Self::new()
171    }
172}
173impl RequestIssuer {
174    /// Create a new `RequestIssuer`.
175    #[must_use]
176    pub fn new() -> Self {
177        Self {
178            use_scheme: true,
179            use_authority: true,
180            use_path: true,
181            use_query: true,
182            use_method: true,
183        }
184    }
185    /// Whether to use the request's URI scheme when generating the key.
186    #[must_use]
187    pub fn use_scheme(mut self, value: bool) -> Self {
188        self.use_scheme = value;
189        self
190    }
191    /// Whether to use the request's URI authority when generating the key.
192    #[must_use]
193    pub fn use_authority(mut self, value: bool) -> Self {
194        self.use_authority = value;
195        self
196    }
197    /// Whether to use the request's URI path when generating the key.
198    #[must_use]
199    pub fn use_path(mut self, value: bool) -> Self {
200        self.use_path = value;
201        self
202    }
203    /// Whether to use the request's URI query when generating the key.
204    #[must_use]
205    pub fn use_query(mut self, value: bool) -> Self {
206        self.use_query = value;
207        self
208    }
209    /// Whether to use the request method when generating the key.
210    #[must_use]
211    pub fn use_method(mut self, value: bool) -> Self {
212        self.use_method = value;
213        self
214    }
215}
216
217impl CacheIssuer for RequestIssuer {
218    type Key = String;
219    async fn issue(&self, req: &mut Request, _depot: &Depot) -> Option<Self::Key> {
220        let mut key = String::with_capacity(req.uri().path().len() + 16);
221        if self.use_scheme
222            && let Some(scheme) = req.uri().scheme_str()
223        {
224            key.push_str(scheme);
225            key.push_str("://");
226        }
227        if self.use_authority
228            && let Some(authority) = req.uri().authority()
229        {
230            key.push_str(authority.as_str());
231        }
232        if self.use_path {
233            key.push_str(req.uri().path());
234        }
235        if self.use_query
236            && let Some(query) = req.uri().query()
237        {
238            key.push('?');
239            key.push_str(query);
240        }
241        if self.use_method {
242            key.push('|');
243            key.push_str(req.method().as_str());
244        }
245        Some(key)
246    }
247}
248
249/// Store cache.
250pub trait CacheStore: Send + Sync + 'static {
251    /// Error type for CacheStore.
252    type Error: StdError + Sync + Send + 'static;
253    /// Key
254    type Key: Hash + Eq + Send + Clone + 'static;
255    /// Get the cache item from the store.
256    fn load_entry<Q>(&self, key: &Q) -> impl Future<Output = Option<CachedEntry>> + Send
257    where
258        Self::Key: Borrow<Q>,
259        Q: Hash + Eq + Sync;
260    /// Save the cache item to the store.
261    fn save_entry(
262        &self,
263        key: Self::Key,
264        data: CachedEntry,
265    ) -> impl Future<Output = Result<(), Self::Error>> + Send;
266}
267
268fn mutex_lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
269    match mutex.lock() {
270        Ok(guard) => guard,
271        Err(poisoned) => poisoned.into_inner(),
272    }
273}
274
275/// Default maximum number of distinct cache keys tracked for concurrent miss coalescing.
276pub const DEFAULT_MAX_IN_FLIGHT: usize = 1024;
277
278struct InFlight<K> {
279    entries: Mutex<HashMap<K, Arc<Flight>>>,
280    max_entries: usize,
281}
282
283impl<K> InFlight<K> {
284    fn new(max_entries: usize) -> Self {
285        Self {
286            entries: Mutex::new(HashMap::new()),
287            max_entries,
288        }
289    }
290}
291
292impl<K> Default for InFlight<K> {
293    fn default() -> Self {
294        Self::new(DEFAULT_MAX_IN_FLIGHT)
295    }
296}
297
298impl<K> InFlight<K>
299where
300    K: Hash + Eq + Clone,
301{
302    fn enter(self: &Arc<Self>, key: K) -> FlightPermit<K> {
303        let mut entries = mutex_lock(&self.entries);
304        if let Some(flight) = entries.get(&key) {
305            return FlightPermit::Follower(flight.clone());
306        }
307        if entries.len() >= self.max_entries {
308            return FlightPermit::Bypass;
309        }
310
311        let flight = Arc::new(Flight::default());
312        entries.insert(key.clone(), flight.clone());
313        FlightPermit::Leader(FlightGuard {
314            key: Some(key),
315            flight,
316            in_flight: self.clone(),
317        })
318    }
319}
320
321impl<K> InFlight<K>
322where
323    K: Hash + Eq,
324{
325    fn remove(&self, key: &K, flight: &Arc<Flight>) {
326        let mut entries = mutex_lock(&self.entries);
327        if entries
328            .get(key)
329            .is_some_and(|current| Arc::ptr_eq(current, flight))
330        {
331            entries.remove(key);
332        }
333    }
334}
335
336enum FlightPermit<K>
337where
338    K: Hash + Eq,
339{
340    Leader(FlightGuard<K>),
341    Follower(Arc<Flight>),
342    Bypass,
343}
344
345struct FlightGuard<K>
346where
347    K: Hash + Eq,
348{
349    key: Option<K>,
350    flight: Arc<Flight>,
351    in_flight: Arc<InFlight<K>>,
352}
353
354impl<K> FlightGuard<K>
355where
356    K: Hash + Eq,
357{
358    fn finish(mut self, entry: Option<CachedEntry>) {
359        self.complete(entry);
360    }
361
362    fn complete(&mut self, entry: Option<CachedEntry>) {
363        if let Some(key) = self.key.take() {
364            self.flight.finish(entry);
365            self.in_flight.remove(&key, &self.flight);
366        }
367    }
368}
369
370impl<K> Drop for FlightGuard<K>
371where
372    K: Hash + Eq,
373{
374    fn drop(&mut self) {
375        self.complete(None);
376    }
377}
378
379#[derive(Default)]
380struct Flight {
381    state: Mutex<FlightState>,
382    notify: Notify,
383}
384
385#[derive(Default)]
386struct FlightState {
387    done: bool,
388    entry: Option<CachedEntry>,
389}
390
391impl Flight {
392    async fn wait(&self) {
393        loop {
394            let notified = self.notify.notified();
395            if mutex_lock(&self.state).done {
396                return;
397            }
398            notified.await;
399        }
400    }
401
402    fn finish(&self, entry: Option<CachedEntry>) {
403        *mutex_lock(&self.state) = FlightState { done: true, entry };
404        self.notify.notify_waiters();
405    }
406
407    fn entry(&self) -> Option<CachedEntry> {
408        mutex_lock(&self.state).entry.clone()
409    }
410}
411
412/// `CachedBody` is used to save the response body to `CacheStore`.
413///
414/// [`ResBody`] has a Stream type, which is not `Send + Sync`, so we need to convert it to
415/// `CachedBody`. If the response's body is [`ResBody::Stream`], it will not be cached.
416#[derive(Clone, Debug, PartialEq)]
417#[non_exhaustive]
418pub enum CachedBody {
419    /// No body.
420    None,
421    /// Single bytes body.
422    Once(Bytes),
423    /// Chunks body.
424    Chunks(VecDeque<Bytes>),
425}
426impl TryFrom<&ResBody> for CachedBody {
427    type Error = Error;
428    fn try_from(body: &ResBody) -> Result<Self, Self::Error> {
429        match body {
430            ResBody::None => Ok(Self::None),
431            ResBody::Once(bytes) => Ok(Self::Once(bytes.to_owned())),
432            ResBody::Chunks(chunks) => Ok(Self::Chunks(chunks.to_owned())),
433            _ => Err(Error::other("unsupported body type")),
434        }
435    }
436}
437impl From<CachedBody> for ResBody {
438    fn from(body: CachedBody) -> Self {
439        match body {
440            CachedBody::None => Self::None,
441            CachedBody::Once(bytes) => Self::Once(bytes),
442            CachedBody::Chunks(chunks) => Self::Chunks(chunks),
443        }
444    }
445}
446
447/// Cached entry which will be stored in the cache store.
448#[derive(Clone, Debug)]
449#[non_exhaustive]
450pub struct CachedEntry {
451    /// Response status.
452    pub status: Option<StatusCode>,
453    /// Response headers.
454    pub headers: HeaderMap,
455    /// Response body.
456    ///
457    /// *Notice: If the response's body is streaming, it will be ignored and not cached.
458    pub body: CachedBody,
459}
460impl CachedEntry {
461    /// Create a new `CachedEntry`.
462    pub fn new(status: Option<StatusCode>, headers: HeaderMap, body: CachedBody) -> Self {
463        Self {
464            status,
465            headers,
466            body,
467        }
468    }
469
470    /// Get the response status.
471    pub fn status(&self) -> Option<StatusCode> {
472        self.status
473    }
474
475    /// Get the response headers.
476    pub fn headers(&self) -> &HeaderMap {
477        &self.headers
478    }
479
480    /// Get the response body.
481    ///
482    /// *Notice: If the response's body is streaming, it will be ignored and not cached.
483    pub fn body(&self) -> &CachedBody {
484        &self.body
485    }
486}
487
488/// Cache middleware.
489///
490/// # Example
491///
492/// ```
493/// use std::time::Duration;
494///
495/// use salvo_cache::{Cache, MokaStore, RequestIssuer};
496/// use salvo_core::Router;
497///
498/// let cache = Cache::new(
499///     MokaStore::builder()
500///         .time_to_live(Duration::from_secs(60))
501///         .build(),
502///     RequestIssuer::default(),
503/// );
504/// let router = Router::new().hoop(cache);
505/// ```
506#[non_exhaustive]
507pub struct Cache<S, I>
508where
509    S: CacheStore,
510{
511    /// Cache store.
512    pub store: S,
513    /// Cache issuer.
514    pub issuer: I,
515    /// Skipper.
516    pub skipper: Box<dyn Skipper>,
517    cache_private: bool,
518    in_flight: Arc<InFlight<S::Key>>,
519}
520impl<S, I> Debug for Cache<S, I>
521where
522    S: CacheStore + Debug,
523    I: Debug,
524{
525    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
526        f.debug_struct("Cache")
527            .field("store", &self.store)
528            .field("issuer", &self.issuer)
529            .finish()
530    }
531}
532
533impl<S, I> Cache<S, I>
534where
535    S: CacheStore,
536{
537    /// Create a new `Cache`.
538    #[inline]
539    #[must_use]
540    pub fn new(store: S, issuer: I) -> Self {
541        let skipper = MethodSkipper::new().skip_all().skip_get(false);
542        Self {
543            store,
544            issuer,
545            skipper: Box::new(skipper),
546            cache_private: false,
547            in_flight: Arc::new(InFlight::default()),
548        }
549    }
550    /// Sets skipper and returns a new `Cache`.
551    #[inline]
552    #[must_use]
553    pub fn skipper(mut self, skipper: impl Skipper) -> Self {
554        self.skipper = Box::new(skipper);
555        self
556    }
557
558    /// Allow caching requests or responses that contain private-user cache signals.
559    ///
560    /// By default, requests with `Authorization` or `Cookie`, and responses with `Set-Cookie`,
561    /// `Vary`, or `Cache-Control: private`, are not cached. Enabling this lifts those
562    /// privacy restrictions. `Cache-Control: no-store`/`no-cache` responses are never
563    /// cached regardless of this setting.
564    #[inline]
565    #[must_use]
566    pub fn cache_private(mut self, cache_private: bool) -> Self {
567        self.cache_private = cache_private;
568        self
569    }
570
571    /// Sets the maximum number of distinct cache keys tracked for concurrent miss coalescing.
572    ///
573    /// When this limit is reached, misses for new keys bypass coalescing and execute normally.
574    /// Existing in-flight keys can still be followed. Set to `0` to disable miss coalescing.
575    #[inline]
576    #[must_use]
577    pub fn max_in_flight(mut self, max_in_flight: usize) -> Self {
578        self.in_flight = Arc::new(InFlight::new(max_in_flight));
579        self
580    }
581}
582
583async fn call_next_and_cache<S>(
584    store: &S,
585    key: S::Key,
586    cache_private: bool,
587    req: &mut Request,
588    depot: &mut Depot,
589    res: &mut Response,
590    ctrl: &mut FlowCtrl,
591) -> Option<CachedEntry>
592where
593    S: CacheStore,
594{
595    // Snapshot the headers set by outer middleware *before* the inner handler
596    // runs, so only the headers the handler itself adds or changes are cached.
597    // This keeps per-request outer headers (e.g. `x-request-id`) out of the
598    // entry while still capturing headers the handler overwrites (e.g. CORS
599    // headers under `CallNext::After`).
600    let headers_before = res.headers().clone();
601    ctrl.call_next(req, depot, res).await;
602    let cached_data = cached_response(res, &headers_before, cache_private)?;
603    if let Err(e) = store.save_entry(key, cached_data.clone()).await {
604        tracing::error!(error = ?e, "cache failed");
605    }
606    Some(cached_data)
607}
608
609fn cached_response(
610    res: &Response,
611    headers_before: &HeaderMap,
612    cache_private: bool,
613) -> Option<CachedEntry> {
614    if res.body.is_stream() || res.body.is_error() {
615        return None;
616    }
617    // Only cache successful responses. Use the *effective* status: a missing
618    // status code is sent as `200 OK` when a body is present but as `404` when
619    // the body is `None` (mirrors `Response::into_hyper`). Caching error or
620    // redirect statuses (e.g. a transient `500`, an auth-dependent `401`/`403`,
621    // or an empty unmatched `404`) would replay them to every client for the
622    // whole TTL.
623    let effective_status = res.status_code.unwrap_or(if res.body.is_none() {
624        StatusCode::NOT_FOUND
625    } else {
626        StatusCode::OK
627    });
628    if !effective_status.is_success() {
629        return None;
630    }
631    // `no-store`/`no-cache` forbid storing or reusing without revalidation in
632    // *any* cache, so they apply even in private-cache mode.
633    if response_disallows_caching(res.headers()) {
634        return None;
635    }
636    if !cache_private && response_has_private_cache_headers(res.headers()) {
637        return None;
638    }
639    // The entry can only record headers still present on the response, so a
640    // header the handler *removed* (one set by an outer middleware before the
641    // cache, now gone) cannot be represented. Replaying such an entry on a hit
642    // would leave the stale outer header in place, making hits differ from the
643    // cached miss. Skip caching instead of serving an inconsistent response.
644    if headers_before
645        .keys()
646        .any(|name| !res.headers().contains_key(name))
647    {
648        return None;
649    }
650    let headers = handler_response_headers(headers_before, res.headers());
651    let body = match TryInto::<CachedBody>::try_into(&res.body) {
652        Ok(body) => body,
653        Err(e) => {
654            tracing::error!(error = ?e, "cache failed");
655            return None;
656        }
657    };
658    Some(CachedEntry::new(res.status_code, headers, body))
659}
660
661/// Headers the inner handler contributed, i.e. names whose values changed
662/// between `before` (set by outer middleware) and `after` (the final response).
663/// Headers left untouched by the handler are excluded so the cache does not
664/// replay a stale per-request value over the fresh one on later hits.
665fn handler_response_headers(before: &HeaderMap, after: &HeaderMap) -> HeaderMap {
666    let mut headers = HeaderMap::new();
667    for name in after.keys() {
668        let after_values = after.get_all(name).iter().collect::<Vec<_>>();
669        let before_values = before.get_all(name).iter().collect::<Vec<_>>();
670        if after_values != before_values {
671            for value in after_values {
672                headers.append(name.clone(), value.clone());
673            }
674        }
675    }
676    headers
677}
678
679fn request_has_private_cache_headers(req: &Request) -> bool {
680    req.headers().contains_key(AUTHORIZATION) || req.headers().contains_key(COOKIE)
681}
682
683fn response_has_private_cache_headers(headers: &HeaderMap) -> bool {
684    headers.contains_key(SET_COOKIE) || cache_control_contains(headers, "private")
685}
686
687/// Directives/headers that forbid caching regardless of whether this is a shared
688/// or private cache. `no-store` bans storing the response anywhere; `no-cache`
689/// requires revalidation before reuse, which this middleware does not perform, so
690/// both are treated as non-cacheable rather than served blindly.
691///
692/// `Vary` is also handled here: this middleware never folds the varied request
693/// headers into the cache key, so it cannot tell two representations apart. Caching
694/// a `Vary` response (even in private mode) would replay one client's representation
695/// (e.g. a `gzip` body, or a specific `Accept-Language`) to clients that negotiated
696/// differently, so such responses are not cached at all.
697fn response_disallows_caching(headers: &HeaderMap) -> bool {
698    cache_control_contains(headers, "no-store")
699        || cache_control_contains(headers, "no-cache")
700        || headers.contains_key(VARY)
701}
702
703fn cache_control_contains(headers: &HeaderMap, directive: &str) -> bool {
704    headers.get_all(CACHE_CONTROL).iter().any(|value| {
705        value.to_str().ok().is_some_and(|value| {
706            value.split(',').any(|part| {
707                let part = part.trim();
708                part.eq_ignore_ascii_case(directive)
709                    || part
710                        .split_once('=')
711                        .is_some_and(|(name, _)| name.trim().eq_ignore_ascii_case(directive))
712            })
713        })
714    })
715}
716
717#[async_trait]
718impl<S, I> Handler for Cache<S, I>
719where
720    S: CacheStore<Key = I::Key>,
721    I: CacheIssuer,
722    I::Key: Clone,
723{
724    async fn handle(
725        &self,
726        req: &mut Request,
727        depot: &mut Depot,
728        res: &mut Response,
729        ctrl: &mut FlowCtrl,
730    ) {
731        if self.skipper.skipped(req, depot)
732            || (!self.cache_private && request_has_private_cache_headers(req))
733        {
734            ctrl.call_next(req, depot, res).await;
735            return;
736        }
737        let Some(key) = self.issuer.issue(req, depot).await else {
738            // No cache key means "do not cache this request"; still run the rest
739            // of the chain so the handler executes instead of returning an empty
740            // response.
741            ctrl.call_next(req, depot, res).await;
742            return;
743        };
744        let Some(cache) = self.store.load_entry(&key).await else {
745            match self.in_flight.enter(key.clone()) {
746                FlightPermit::Leader(guard) => {
747                    let cached_data = call_next_and_cache(
748                        &self.store,
749                        key,
750                        self.cache_private,
751                        req,
752                        depot,
753                        res,
754                        ctrl,
755                    )
756                    .await;
757                    guard.finish(cached_data);
758                }
759                FlightPermit::Follower(flight) => {
760                    flight.wait().await;
761                    if let Some(cache) = flight.entry() {
762                        respond_from_cache(res, cache);
763                        ctrl.skip_rest();
764                    } else {
765                        call_next_and_cache(
766                            &self.store,
767                            key,
768                            self.cache_private,
769                            req,
770                            depot,
771                            res,
772                            ctrl,
773                        )
774                        .await;
775                    }
776                }
777                FlightPermit::Bypass => {
778                    call_next_and_cache(
779                        &self.store,
780                        key,
781                        self.cache_private,
782                        req,
783                        depot,
784                        res,
785                        ctrl,
786                    )
787                    .await;
788                }
789            }
790            return;
791        };
792        respond_from_cache(res, cache);
793        ctrl.skip_rest();
794    }
795}
796
797fn respond_from_cache(res: &mut Response, cache: CachedEntry) {
798    let CachedEntry {
799        status,
800        headers,
801        body,
802    } = cache;
803    if let Some(status) = status {
804        res.status_code(status);
805    }
806    // Merge the cached headers into the response rather than replacing the whole
807    // map, so headers set by outer middleware (request id, CORS, security
808    // headers) on *this* request are preserved. The entry only holds the headers
809    // the cached handler itself produced (see `handler_response_headers`), so a
810    // cached header fully replaces any same-named value — restoring handler
811    // overrides — while untouched outer headers are left as their fresh value.
812    let res_headers = res.headers_mut();
813    for name in headers.keys() {
814        res_headers.remove(name);
815        // Re-insert every cached value, keeping multi-valued headers such as
816        // `Set-Cookie` intact.
817        for value in headers.get_all(name) {
818            res_headers.append(name.clone(), value.clone());
819        }
820    }
821    *res.body_mut() = body.into();
822}
823
824#[cfg(test)]
825mod tests {
826    use std::collections::VecDeque;
827    use std::sync::Arc;
828    use std::sync::atomic::{AtomicUsize, Ordering};
829
830    use bytes::Bytes;
831    use salvo_core::http::HeaderMap;
832    use salvo_core::prelude::*;
833    use salvo_core::test::{ResponseExt, TestClient};
834    use time::OffsetDateTime;
835
836    use super::*;
837
838    #[handler]
839    async fn cached() -> String {
840        format!(
841            "Hello World, my birth time is {}",
842            OffsetDateTime::now_utc()
843        )
844    }
845
846    #[derive(Debug)]
847    struct SlowCached {
848        calls: Arc<AtomicUsize>,
849    }
850
851    #[async_trait]
852    impl Handler for SlowCached {
853        async fn handle(
854            &self,
855            _req: &mut Request,
856            _depot: &mut Depot,
857            res: &mut Response,
858            _ctrl: &mut FlowCtrl,
859        ) {
860            let call = self.calls.fetch_add(1, Ordering::SeqCst) + 1;
861            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
862            res.render(format!("backend call {call}"));
863        }
864    }
865
866    #[tokio::test]
867    async fn test_cache() {
868        let cache = Cache::new(
869            MokaStore::builder()
870                .time_to_live(std::time::Duration::from_secs(5))
871                .build(),
872            RequestIssuer::default(),
873        );
874        let router = Router::new().hoop(cache).goal(cached);
875        let service = Service::new(router);
876
877        let mut res = TestClient::get("http://127.0.0.1:5801")
878            .send(&service)
879            .await;
880        assert_eq!(res.status_code.unwrap(), StatusCode::OK);
881
882        let content0 = res.take_string().await.unwrap();
883
884        let mut res = TestClient::get("http://127.0.0.1:5801")
885            .send(&service)
886            .await;
887        assert_eq!(res.status_code.unwrap(), StatusCode::OK);
888
889        let content1 = res.take_string().await.unwrap();
890        assert_eq!(content0, content1);
891
892        tokio::time::sleep(tokio::time::Duration::from_secs(6)).await;
893        let mut res = TestClient::post("http://127.0.0.1:5801")
894            .send(&service)
895            .await;
896        let content2 = res.take_string().await.unwrap();
897
898        assert_ne!(content0, content2);
899    }
900
901    #[handler]
902    async fn server_error(res: &mut Response) {
903        res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
904        res.render(format!("error at {}", OffsetDateTime::now_utc()));
905    }
906
907    #[tokio::test]
908    async fn test_cache_skips_non_success_status() {
909        let cache = Cache::new(
910            MokaStore::builder()
911                .time_to_live(std::time::Duration::from_secs(5))
912                .build(),
913            RequestIssuer::default(),
914        );
915        let router = Router::new().hoop(cache).goal(server_error);
916        let service = Service::new(router);
917
918        let mut res = TestClient::get("http://127.0.0.1:5802")
919            .send(&service)
920            .await;
921        assert_eq!(res.status_code.unwrap(), StatusCode::INTERNAL_SERVER_ERROR);
922        let content0 = res.take_string().await.unwrap();
923
924        // A non-success response must not be cached, so the backend runs again
925        // and produces a fresh (different) body.
926        let mut res = TestClient::get("http://127.0.0.1:5802")
927            .send(&service)
928            .await;
929        let content1 = res.take_string().await.unwrap();
930        assert_ne!(content0, content1);
931    }
932
933    #[tokio::test]
934    async fn test_cache_issuer_none_runs_handler() {
935        let cache = Cache::new(
936            MokaStore::builder()
937                .time_to_live(std::time::Duration::from_secs(5))
938                .build(),
939            // Issuer that never produces a key: requests must still be handled.
940            |_req: &mut Request, _depot: &Depot| Option::<String>::None,
941        );
942        let router = Router::new().hoop(cache).goal(cached);
943        let service = Service::new(router);
944
945        let mut res = TestClient::get("http://127.0.0.1:5803")
946            .send(&service)
947            .await;
948        assert_eq!(res.status_code.unwrap(), StatusCode::OK);
949        let body = res.take_string().await.unwrap();
950        assert!(body.contains("Hello World"));
951    }
952
953    #[test]
954    fn test_cached_response_skips_empty_unmatched_404() {
955        // No status code + empty body is sent as `404 NOT_FOUND`
956        // (see `Response::into_hyper`), so its effective status is non-success
957        // and it must not be cached.
958        let res = Response::new();
959        assert!(res.status_code.is_none() && res.body.is_none());
960        assert!(cached_response(&res, &HeaderMap::new(), false).is_none());
961    }
962
963    #[test]
964    fn test_cached_response_caches_default_success() {
965        // No status code but a body present is sent as `200 OK`, so it is cached.
966        let mut res = Response::new();
967        res.render("ok");
968        assert!(res.status_code.is_none());
969        assert!(cached_response(&res, &HeaderMap::new(), false).is_some());
970    }
971
972    // Tests for RequestIssuer
973    #[test]
974    fn test_request_issuer_new() {
975        let issuer = RequestIssuer::new();
976        assert!(issuer.use_scheme);
977        assert!(issuer.use_authority);
978        assert!(issuer.use_path);
979        assert!(issuer.use_query);
980        assert!(issuer.use_method);
981    }
982
983    #[test]
984    fn test_request_issuer_default() {
985        let issuer = RequestIssuer::default();
986        assert!(issuer.use_scheme);
987        assert!(issuer.use_authority);
988        assert!(issuer.use_path);
989        assert!(issuer.use_query);
990        assert!(issuer.use_method);
991    }
992
993    #[test]
994    fn test_request_issuer_use_scheme() {
995        let issuer = RequestIssuer::new().use_scheme(false);
996        assert!(!issuer.use_scheme);
997        assert!(issuer.use_authority);
998    }
999
1000    #[test]
1001    fn test_request_issuer_use_authority() {
1002        let issuer = RequestIssuer::new().use_authority(false);
1003        assert!(issuer.use_scheme);
1004        assert!(!issuer.use_authority);
1005    }
1006
1007    #[test]
1008    fn test_request_issuer_use_path() {
1009        let issuer = RequestIssuer::new().use_path(false);
1010        assert!(!issuer.use_path);
1011    }
1012
1013    #[test]
1014    fn test_request_issuer_use_query() {
1015        let issuer = RequestIssuer::new().use_query(false);
1016        assert!(!issuer.use_query);
1017    }
1018
1019    #[test]
1020    fn test_request_issuer_use_method() {
1021        let issuer = RequestIssuer::new().use_method(false);
1022        assert!(!issuer.use_method);
1023    }
1024
1025    #[test]
1026    fn test_request_issuer_chain() {
1027        let issuer = RequestIssuer::new()
1028            .use_scheme(false)
1029            .use_authority(false)
1030            .use_path(true)
1031            .use_query(false)
1032            .use_method(true);
1033        assert!(!issuer.use_scheme);
1034        assert!(!issuer.use_authority);
1035        assert!(issuer.use_path);
1036        assert!(!issuer.use_query);
1037        assert!(issuer.use_method);
1038    }
1039
1040    #[test]
1041    fn test_request_issuer_debug() {
1042        let issuer = RequestIssuer::new();
1043        let debug_str = format!("{issuer:?}");
1044        assert!(debug_str.contains("RequestIssuer"));
1045        assert!(debug_str.contains("use_scheme"));
1046    }
1047
1048    #[test]
1049    fn test_request_issuer_clone() {
1050        let issuer = RequestIssuer::new().use_scheme(false);
1051        let cloned = issuer.clone();
1052        assert_eq!(issuer.use_scheme, cloned.use_scheme);
1053        assert_eq!(issuer.use_authority, cloned.use_authority);
1054    }
1055
1056    // Tests for CachedBody
1057    #[test]
1058    fn test_cached_body_none() {
1059        let body = CachedBody::None;
1060        assert_eq!(body, CachedBody::None);
1061    }
1062
1063    #[test]
1064    fn test_cached_body_once() {
1065        let bytes = Bytes::from("test data");
1066        let body = CachedBody::Once(bytes.clone());
1067        assert_eq!(body, CachedBody::Once(bytes));
1068    }
1069
1070    #[test]
1071    fn test_cached_body_chunks() {
1072        let mut chunks = VecDeque::new();
1073        chunks.push_back(Bytes::from("chunk1"));
1074        chunks.push_back(Bytes::from("chunk2"));
1075        let body = CachedBody::Chunks(chunks.clone());
1076        assert_eq!(body, CachedBody::Chunks(chunks));
1077    }
1078
1079    #[test]
1080    fn test_cached_body_try_from_res_body_none() {
1081        let res_body = ResBody::None;
1082        let result: Result<CachedBody, _> = (&res_body).try_into();
1083        assert_eq!(result.unwrap(), CachedBody::None);
1084    }
1085
1086    #[test]
1087    fn test_cached_body_try_from_res_body_once() {
1088        let bytes = Bytes::from("test");
1089        let res_body = ResBody::Once(bytes.clone());
1090        let result: Result<CachedBody, _> = (&res_body).try_into();
1091        assert_eq!(result.unwrap(), CachedBody::Once(bytes));
1092    }
1093
1094    #[test]
1095    fn test_cached_body_try_from_res_body_chunks() {
1096        let mut chunks = VecDeque::new();
1097        chunks.push_back(Bytes::from("chunk1"));
1098        chunks.push_back(Bytes::from("chunk2"));
1099        let res_body = ResBody::Chunks(chunks.clone());
1100        let result: Result<CachedBody, _> = (&res_body).try_into();
1101        assert_eq!(result.unwrap(), CachedBody::Chunks(chunks));
1102    }
1103
1104    #[test]
1105    fn test_cached_body_into_res_body_none() {
1106        let cb = CachedBody::None;
1107        let res_body: ResBody = cb.into();
1108        assert!(matches!(res_body, ResBody::None));
1109    }
1110
1111    #[test]
1112    fn test_cached_body_into_res_body_once() {
1113        let bytes = Bytes::from("test");
1114        let cb = CachedBody::Once(bytes.clone());
1115        let res_body: ResBody = cb.into();
1116        assert!(matches!(res_body, ResBody::Once(b) if b == bytes));
1117    }
1118
1119    #[test]
1120    fn test_cached_body_into_res_body_chunks() {
1121        let mut chunks = VecDeque::new();
1122        chunks.push_back(Bytes::from("chunk1"));
1123        let cb = CachedBody::Chunks(chunks);
1124        let res_body: ResBody = cb.into();
1125        assert!(matches!(res_body, ResBody::Chunks(_)));
1126    }
1127
1128    #[test]
1129    fn test_cached_body_debug() {
1130        let body = CachedBody::None;
1131        let debug_str = format!("{body:?}");
1132        assert!(debug_str.contains("None"));
1133
1134        let body = CachedBody::Once(Bytes::from("test"));
1135        let debug_str = format!("{body:?}");
1136        assert!(debug_str.contains("Once"));
1137    }
1138
1139    #[test]
1140    fn test_cached_body_clone() {
1141        let body = CachedBody::Once(Bytes::from("test"));
1142        let cloned = body.clone();
1143        assert_eq!(body, cloned);
1144    }
1145
1146    // Tests for CachedEntry
1147    #[test]
1148    fn test_cached_entry_new() {
1149        let entry = CachedEntry::new(Some(StatusCode::OK), HeaderMap::new(), CachedBody::None);
1150        assert_eq!(entry.status, Some(StatusCode::OK));
1151        assert!(entry.headers.is_empty());
1152        assert_eq!(entry.body, CachedBody::None);
1153    }
1154
1155    #[test]
1156    fn test_cached_entry_status() {
1157        let entry = CachedEntry::new(
1158            Some(StatusCode::NOT_FOUND),
1159            HeaderMap::new(),
1160            CachedBody::None,
1161        );
1162        assert_eq!(entry.status(), Some(StatusCode::NOT_FOUND));
1163    }
1164
1165    #[test]
1166    fn test_cached_entry_status_none() {
1167        let entry = CachedEntry::new(None, HeaderMap::new(), CachedBody::None);
1168        assert_eq!(entry.status(), None);
1169    }
1170
1171    #[test]
1172    fn test_cached_entry_headers() {
1173        let mut headers = HeaderMap::new();
1174        headers.insert("Content-Type", "application/json".parse().unwrap());
1175        let entry = CachedEntry::new(Some(StatusCode::OK), headers.clone(), CachedBody::None);
1176        assert_eq!(entry.headers().len(), 1);
1177        assert!(entry.headers().contains_key("Content-Type"));
1178    }
1179
1180    #[test]
1181    fn test_cached_entry_body() {
1182        let body = CachedBody::Once(Bytes::from("test body"));
1183        let entry = CachedEntry::new(Some(StatusCode::OK), HeaderMap::new(), body.clone());
1184        assert_eq!(entry.body(), &body);
1185    }
1186
1187    #[test]
1188    fn test_cached_entry_debug() {
1189        let entry = CachedEntry::new(Some(StatusCode::OK), HeaderMap::new(), CachedBody::None);
1190        let debug_str = format!("{entry:?}");
1191        assert!(debug_str.contains("CachedEntry"));
1192        assert!(debug_str.contains("status"));
1193    }
1194
1195    #[test]
1196    fn handler_response_headers_excludes_untouched_outer_headers() {
1197        // An outer middleware set `x-request-id` before the handler ran; the
1198        // handler only added `content-type`. The per-request `x-request-id` must
1199        // be left out of the entry so later hits keep their own fresh value.
1200        let mut before = HeaderMap::new();
1201        before.insert("x-request-id", "req-1".parse().unwrap());
1202        let mut after = before.clone();
1203        after.insert("content-type", "text/plain".parse().unwrap());
1204
1205        let stored = handler_response_headers(&before, &after);
1206        assert!(!stored.contains_key("x-request-id"));
1207        assert_eq!(stored.get("content-type").unwrap(), "text/plain");
1208    }
1209
1210    #[test]
1211    fn cached_response_skips_when_handler_removes_outer_header() {
1212        // An outer middleware set `x-default` before the cache; the handler
1213        // removed it. The entry cannot represent that deletion, so the response
1214        // must not be cached (otherwise hits would keep the stale `x-default`).
1215        let mut before = HeaderMap::new();
1216        before.insert("x-default", "from-outer".parse().unwrap());
1217
1218        let mut res = Response::new();
1219        res.body(ResBody::Once(Bytes::from_static(b"cached")));
1220        // `res` does not carry `x-default`, i.e. the handler dropped it.
1221        assert!(cached_response(&res, &before, false).is_none());
1222        assert!(cached_response(&res, &before, true).is_none());
1223
1224        // Sanity: with no pre-cache header to drop, the same response caches.
1225        assert!(cached_response(&res, &HeaderMap::new(), false).is_some());
1226    }
1227
1228    #[test]
1229    fn handler_response_headers_includes_handler_overrides() {
1230        // CORS `CallNext::After` writes a default before the handler runs; the
1231        // handler overwrites it. The overridden value must be cached so hits
1232        // reproduce it instead of the pre-cache default.
1233        let mut before = HeaderMap::new();
1234        before.insert(
1235            "access-control-allow-origin",
1236            "https://default.example".parse().unwrap(),
1237        );
1238        let mut after = HeaderMap::new();
1239        after.insert(
1240            "access-control-allow-origin",
1241            "https://handler.example".parse().unwrap(),
1242        );
1243
1244        let stored = handler_response_headers(&before, &after);
1245        assert_eq!(
1246            stored.get("access-control-allow-origin").unwrap(),
1247            "https://handler.example"
1248        );
1249    }
1250
1251    #[test]
1252    fn respond_from_cache_overrides_handler_headers_but_keeps_outer() {
1253        // The entry only carries handler-produced headers. On a hit they must
1254        // override same-named pre-cache defaults, while outer headers absent
1255        // from the entry (a fresh per-request `x-request-id`) are preserved.
1256        let mut entry_headers = HeaderMap::new();
1257        entry_headers.insert(
1258            "access-control-allow-origin",
1259            "https://handler.example".parse().unwrap(),
1260        );
1261        entry_headers.insert("content-type", "text/plain".parse().unwrap());
1262        let entry = CachedEntry::new(
1263            Some(StatusCode::OK),
1264            entry_headers,
1265            CachedBody::Once(Bytes::from_static(b"cached")),
1266        );
1267
1268        let mut res = Response::new();
1269        res.headers_mut()
1270            .insert("x-request-id", "fresh-for-this-request".parse().unwrap());
1271        res.headers_mut().insert(
1272            "access-control-allow-origin",
1273            "https://default.example".parse().unwrap(),
1274        );
1275
1276        respond_from_cache(&mut res, entry);
1277
1278        assert_eq!(
1279            res.headers().get("x-request-id").unwrap(),
1280            "fresh-for-this-request"
1281        );
1282        assert_eq!(
1283            res.headers().get("access-control-allow-origin").unwrap(),
1284            "https://handler.example"
1285        );
1286        assert_eq!(res.headers().get("content-type").unwrap(), "text/plain");
1287    }
1288
1289    #[test]
1290    fn respond_from_cache_restores_multi_valued_headers() {
1291        let mut cached_headers = HeaderMap::new();
1292        cached_headers.append(SET_COOKIE, "a=1".parse().unwrap());
1293        cached_headers.append(SET_COOKIE, "b=2".parse().unwrap());
1294        let entry = CachedEntry::new(Some(StatusCode::OK), cached_headers, CachedBody::None);
1295
1296        let mut res = Response::new();
1297        respond_from_cache(&mut res, entry);
1298
1299        let cookies: Vec<_> = res
1300            .headers()
1301            .get_all(SET_COOKIE)
1302            .iter()
1303            .map(|v| v.to_str().unwrap().to_owned())
1304            .collect();
1305        assert_eq!(cookies, vec!["a=1", "b=2"]);
1306    }
1307
1308    #[test]
1309    fn test_cached_entry_clone() {
1310        let entry = CachedEntry::new(
1311            Some(StatusCode::OK),
1312            HeaderMap::new(),
1313            CachedBody::Once(Bytes::from("test")),
1314        );
1315        let cloned = entry.clone();
1316        assert_eq!(entry.status, cloned.status);
1317        assert_eq!(entry.body, cloned.body);
1318    }
1319
1320    // Tests for Cache
1321    #[test]
1322    fn test_cache_new() {
1323        let cache = Cache::new(MokaStore::<String>::new(100), RequestIssuer::default());
1324        assert!(format!("{cache:?}").contains("Cache"));
1325    }
1326
1327    #[test]
1328    fn in_flight_limit_bypasses_new_keys_when_full() {
1329        let in_flight = Arc::new(InFlight::new(1));
1330        let FlightPermit::Leader(first) = in_flight.enter("a") else {
1331            panic!("first key should lead an in-flight request");
1332        };
1333
1334        assert!(matches!(in_flight.enter("a"), FlightPermit::Follower(_)));
1335        assert!(matches!(in_flight.enter("b"), FlightPermit::Bypass));
1336
1337        drop(first);
1338        assert!(matches!(in_flight.enter("b"), FlightPermit::Leader(_)));
1339    }
1340
1341    #[test]
1342    fn cache_max_in_flight_can_disable_coalescing() {
1343        let cache =
1344            Cache::new(MokaStore::<String>::new(100), RequestIssuer::default()).max_in_flight(0);
1345
1346        assert!(matches!(
1347            cache.in_flight.enter("uncached".to_owned()),
1348            FlightPermit::Bypass
1349        ));
1350    }
1351
1352    #[test]
1353    fn cached_response_skips_private_cache_headers_by_default() {
1354        // Privacy heuristics only restrict a *shared* cache; a private cache
1355        // (`cache_private(true)`) may still store these.
1356        for (name, value) in [(SET_COOKIE, "sid=abc"), (CACHE_CONTROL, "private")] {
1357            let mut res = Response::new();
1358            res.body(ResBody::Once(Bytes::from_static(b"cached")));
1359            res.headers_mut().insert(name, value.parse().unwrap());
1360            assert!(cached_response(&res, &HeaderMap::new(), false).is_none());
1361            assert!(cached_response(&res, &HeaderMap::new(), true).is_some());
1362        }
1363    }
1364
1365    #[test]
1366    fn cached_response_never_stores_vary() {
1367        // The store never folds the varied request headers into the key, so a
1368        // `Vary` response cannot be told apart from another representation and is
1369        // not cached in either mode (otherwise one client's representation would be
1370        // replayed to clients that negotiated differently).
1371        let mut res = Response::new();
1372        res.body(ResBody::Once(Bytes::from_static(b"cached")));
1373        res.headers_mut()
1374            .insert(VARY, "accept-language".parse().unwrap());
1375        assert!(cached_response(&res, &HeaderMap::new(), false).is_none());
1376        assert!(cached_response(&res, &HeaderMap::new(), true).is_none());
1377    }
1378
1379    #[test]
1380    fn cached_response_never_stores_no_store_or_no_cache() {
1381        // `no-store`/`no-cache` forbid caching in any cache, so they must be
1382        // honored even in private-cache mode.
1383        for value in ["max-age=60, no-store", "no-cache", "public, no-cache"] {
1384            let mut res = Response::new();
1385            res.body(ResBody::Once(Bytes::from_static(b"cached")));
1386            res.headers_mut()
1387                .insert(CACHE_CONTROL, value.parse().unwrap());
1388            assert!(cached_response(&res, &HeaderMap::new(), false).is_none());
1389            assert!(cached_response(&res, &HeaderMap::new(), true).is_none());
1390        }
1391    }
1392
1393    #[tokio::test]
1394    async fn authorization_requests_are_not_cached_by_default() {
1395        let calls = Arc::new(AtomicUsize::new(0));
1396        let cache = Cache::new(
1397            MokaStore::builder()
1398                .time_to_live(std::time::Duration::from_secs(60))
1399                .build(),
1400            RequestIssuer::default(),
1401        );
1402        let router = Arc::new(Router::new().hoop(cache).goal(SlowCached {
1403            calls: calls.clone(),
1404        }));
1405
1406        for _ in 0..2 {
1407            let mut res = TestClient::get("http://127.0.0.1:5801")
1408                .add_header(AUTHORIZATION, "Bearer token", true)
1409                .send(router.clone())
1410                .await;
1411            assert_eq!(res.status_code, Some(StatusCode::OK));
1412            let _ = res.take_string().await.unwrap();
1413        }
1414
1415        assert_eq!(calls.load(Ordering::SeqCst), 2);
1416    }
1417
1418    #[test]
1419    fn test_cache_debug() {
1420        let cache = Cache::new(MokaStore::<String>::new(100), RequestIssuer::default());
1421        let debug_str = format!("{cache:?}");
1422        assert!(debug_str.contains("Cache"));
1423        assert!(debug_str.contains("store"));
1424        assert!(debug_str.contains("issuer"));
1425    }
1426
1427    #[tokio::test]
1428    async fn test_cache_same_path_same_content() {
1429        let cache = Cache::new(
1430            MokaStore::builder()
1431                .time_to_live(std::time::Duration::from_secs(60))
1432                .build(),
1433            RequestIssuer::default(),
1434        );
1435        let router = Router::new().hoop(cache).goal(cached);
1436        let service = Service::new(router);
1437
1438        let mut res1 = TestClient::get("http://127.0.0.1:5801/same-path")
1439            .send(&service)
1440            .await;
1441        let content1 = res1.take_string().await.unwrap();
1442
1443        let mut res2 = TestClient::get("http://127.0.0.1:5801/same-path")
1444            .send(&service)
1445            .await;
1446        let content2 = res2.take_string().await.unwrap();
1447
1448        // Same path should return cached content
1449        assert_eq!(content1, content2);
1450    }
1451
1452    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1453    async fn test_cache_coalesces_concurrent_misses() {
1454        let calls = Arc::new(AtomicUsize::new(0));
1455        let cache = Cache::new(
1456            MokaStore::builder()
1457                .time_to_live(std::time::Duration::from_secs(60))
1458                .build(),
1459            RequestIssuer::default(),
1460        );
1461        let router = Arc::new(Router::new().hoop(cache).goal(SlowCached {
1462            calls: calls.clone(),
1463        }));
1464        let barrier = Arc::new(tokio::sync::Barrier::new(16));
1465
1466        let mut tasks = Vec::new();
1467        for _ in 0..16 {
1468            let router = router.clone();
1469            let barrier = barrier.clone();
1470            tasks.push(tokio::spawn(async move {
1471                barrier.wait().await;
1472                let mut res = TestClient::get("http://127.0.0.1:5801").send(router).await;
1473                res.take_string().await.unwrap()
1474            }));
1475        }
1476
1477        let mut bodies = Vec::new();
1478        for task in tasks {
1479            bodies.push(task.await.unwrap());
1480        }
1481
1482        assert_eq!(calls.load(Ordering::SeqCst), 1);
1483        assert!(bodies.iter().all(|body| body == &bodies[0]));
1484    }
1485}