1#![cfg_attr(test, allow(clippy::unwrap_used))]
2#![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
115pub trait CacheIssuer: Send + Sync + 'static {
117 type Key: Hash + Eq + Send + Sync + 'static;
119 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#[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 #[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 #[must_use]
187 pub fn use_scheme(mut self, value: bool) -> Self {
188 self.use_scheme = value;
189 self
190 }
191 #[must_use]
193 pub fn use_authority(mut self, value: bool) -> Self {
194 self.use_authority = value;
195 self
196 }
197 #[must_use]
199 pub fn use_path(mut self, value: bool) -> Self {
200 self.use_path = value;
201 self
202 }
203 #[must_use]
205 pub fn use_query(mut self, value: bool) -> Self {
206 self.use_query = value;
207 self
208 }
209 #[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
249pub trait CacheStore: Send + Sync + 'static {
251 type Error: StdError + Sync + Send + 'static;
253 type Key: Hash + Eq + Send + Clone + 'static;
255 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 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
275pub 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#[derive(Clone, Debug, PartialEq)]
417#[non_exhaustive]
418pub enum CachedBody {
419 None,
421 Once(Bytes),
423 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#[derive(Clone, Debug)]
449#[non_exhaustive]
450pub struct CachedEntry {
451 pub status: Option<StatusCode>,
453 pub headers: HeaderMap,
455 pub body: CachedBody,
459}
460impl CachedEntry {
461 pub fn new(status: Option<StatusCode>, headers: HeaderMap, body: CachedBody) -> Self {
463 Self {
464 status,
465 headers,
466 body,
467 }
468 }
469
470 pub fn status(&self) -> Option<StatusCode> {
472 self.status
473 }
474
475 pub fn headers(&self) -> &HeaderMap {
477 &self.headers
478 }
479
480 pub fn body(&self) -> &CachedBody {
484 &self.body
485 }
486}
487
488#[non_exhaustive]
507pub struct Cache<S, I>
508where
509 S: CacheStore,
510{
511 pub store: S,
513 pub issuer: I,
515 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 #[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 #[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 #[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 #[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 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 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 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 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
661fn 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
687fn 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 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 let res_headers = res.headers_mut();
813 for name in headers.keys() {
814 res_headers.remove(name);
815 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 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 |_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 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 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 #[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 #[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 #[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 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 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 assert!(cached_response(&res, &before, false).is_none());
1222 assert!(cached_response(&res, &before, true).is_none());
1223
1224 assert!(cached_response(&res, &HeaderMap::new(), false).is_some());
1226 }
1227
1228 #[test]
1229 fn handler_response_headers_includes_handler_overrides() {
1230 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 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 #[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 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 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 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 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}