1#![forbid(unsafe_code, future_incompatible)]
2#![deny(
3 missing_docs,
4 missing_debug_implementations,
5 missing_copy_implementations,
6 nonstandard_style,
7 unused_qualifications,
8 unused_import_braces,
9 unused_extern_crates,
10 trivial_casts,
11 trivial_numeric_casts
12)]
13#![allow(clippy::doc_lazy_continuation)]
14#![cfg_attr(docsrs, feature(doc_cfg))]
15mod error;
32mod managers;
33
34use std::{
35 collections::HashMap,
36 convert::TryFrom,
37 fmt::{self, Debug},
38 str::FromStr,
39 sync::Arc,
40 time::SystemTime,
41};
42
43use http::{header::CACHE_CONTROL, request, response, StatusCode};
44use http_cache_semantics::{AfterResponse, BeforeRequest, CachePolicy};
45use serde::{Deserialize, Serialize};
46use url::Url;
47
48pub use error::{BadHeader, BadVersion, BoxError, Result};
49
50#[cfg(feature = "manager-cacache")]
51pub use managers::cacache::CACacheManager;
52
53#[cfg(feature = "manager-moka")]
54pub use managers::moka::MokaManager;
55
56#[cfg(feature = "manager-moka")]
58#[cfg_attr(docsrs, doc(cfg(feature = "manager-moka")))]
59pub use moka::future::{Cache as MokaCache, CacheBuilder as MokaCacheBuilder};
60
61pub const XCACHE: &str = "x-cache";
64pub const XCACHELOOKUP: &str = "x-cache-lookup";
66
67#[derive(Debug, Copy, Clone)]
70pub enum HitOrMiss {
71 HIT,
73 MISS,
75}
76
77impl fmt::Display for HitOrMiss {
78 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79 match self {
80 Self::HIT => write!(f, "HIT"),
81 Self::MISS => write!(f, "MISS"),
82 }
83 }
84}
85
86#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
88#[non_exhaustive]
89pub enum HttpVersion {
90 #[serde(rename = "HTTP/0.9")]
92 Http09,
93 #[serde(rename = "HTTP/1.0")]
95 Http10,
96 #[serde(rename = "HTTP/1.1")]
98 Http11,
99 #[serde(rename = "HTTP/2.0")]
101 H2,
102 #[serde(rename = "HTTP/3.0")]
104 H3,
105}
106
107impl fmt::Display for HttpVersion {
108 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109 match *self {
110 HttpVersion::Http09 => write!(f, "HTTP/0.9"),
111 HttpVersion::Http10 => write!(f, "HTTP/1.0"),
112 HttpVersion::Http11 => write!(f, "HTTP/1.1"),
113 HttpVersion::H2 => write!(f, "HTTP/2.0"),
114 HttpVersion::H3 => write!(f, "HTTP/3.0"),
115 }
116 }
117}
118
119#[derive(Debug, Clone, Deserialize, Serialize)]
121pub struct HttpResponse {
122 pub body: Vec<u8>,
124 pub headers: HashMap<String, String>,
126 pub status: u16,
128 pub url: Url,
130 pub version: HttpVersion,
132}
133
134impl HttpResponse {
135 pub fn parts(&self) -> Result<response::Parts> {
137 let mut converted =
138 response::Builder::new().status(self.status).body(())?;
139 {
140 let headers = converted.headers_mut();
141 for header in &self.headers {
142 headers.insert(
143 http::header::HeaderName::from_str(header.0.as_str())?,
144 http::HeaderValue::from_str(header.1.as_str())?,
145 );
146 }
147 }
148 Ok(converted.into_parts().0)
149 }
150
151 #[must_use]
153 pub fn warning_code(&self) -> Option<usize> {
154 self.headers.get("warning").and_then(|hdr| {
155 hdr.as_str().chars().take(3).collect::<String>().parse().ok()
156 })
157 }
158
159 pub fn add_warning(&mut self, url: &Url, code: usize, message: &str) {
161 self.headers.insert(
171 "warning".to_string(),
172 format!(
173 "{} {} {:?} \"{}\"",
174 code,
175 url.host().expect("Invalid URL"),
176 message,
177 httpdate::fmt_http_date(SystemTime::now())
178 ),
179 );
180 }
181
182 pub fn remove_warning(&mut self) {
184 self.headers.remove("warning");
185 }
186
187 pub fn update_headers(&mut self, parts: &response::Parts) -> Result<()> {
189 for header in parts.headers.iter() {
190 self.headers.insert(
191 header.0.as_str().to_string(),
192 header.1.to_str()?.to_string(),
193 );
194 }
195 Ok(())
196 }
197
198 #[must_use]
200 pub fn must_revalidate(&self) -> bool {
201 self.headers.get(CACHE_CONTROL.as_str()).is_some_and(|val| {
202 val.as_str().to_lowercase().contains("must-revalidate")
203 })
204 }
205
206 pub fn cache_status(&mut self, hit_or_miss: HitOrMiss) {
208 self.headers.insert(XCACHE.to_string(), hit_or_miss.to_string());
209 }
210
211 pub fn cache_lookup_status(&mut self, hit_or_miss: HitOrMiss) {
213 self.headers.insert(XCACHELOOKUP.to_string(), hit_or_miss.to_string());
214 }
215}
216
217#[async_trait::async_trait]
219pub trait CacheManager: Send + Sync + 'static {
220 async fn get(
222 &self,
223 cache_key: &str,
224 ) -> Result<Option<(HttpResponse, CachePolicy)>>;
225 async fn put(
227 &self,
228 cache_key: String,
229 res: HttpResponse,
230 policy: CachePolicy,
231 ) -> Result<HttpResponse>;
232 async fn delete(&self, cache_key: &str) -> Result<()>;
234}
235
236#[async_trait::async_trait]
238pub trait Middleware: Send {
239 fn overridden_cache_mode(&self) -> Option<CacheMode> {
243 None
244 }
245 fn is_method_get_head(&self) -> bool;
247 fn policy(&self, response: &HttpResponse) -> Result<CachePolicy>;
249 fn policy_with_options(
251 &self,
252 response: &HttpResponse,
253 options: CacheOptions,
254 ) -> Result<CachePolicy>;
255 fn update_headers(&mut self, parts: &request::Parts) -> Result<()>;
257 fn force_no_cache(&mut self) -> Result<()>;
259 fn parts(&self) -> Result<request::Parts>;
261 fn url(&self) -> Result<Url>;
263 fn method(&self) -> Result<String>;
265 async fn remote_fetch(&mut self) -> Result<HttpResponse>;
267}
268
269#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
272pub enum CacheMode {
273 #[default]
281 Default,
282 NoStore,
284 Reload,
287 NoCache,
290 ForceCache,
294 OnlyIfCached,
298 IgnoreRules,
303}
304
305impl TryFrom<http::Version> for HttpVersion {
306 type Error = BoxError;
307
308 fn try_from(value: http::Version) -> Result<Self> {
309 Ok(match value {
310 http::Version::HTTP_09 => Self::Http09,
311 http::Version::HTTP_10 => Self::Http10,
312 http::Version::HTTP_11 => Self::Http11,
313 http::Version::HTTP_2 => Self::H2,
314 http::Version::HTTP_3 => Self::H3,
315 _ => return Err(Box::new(BadVersion)),
316 })
317 }
318}
319
320impl From<HttpVersion> for http::Version {
321 fn from(value: HttpVersion) -> Self {
322 match value {
323 HttpVersion::Http09 => Self::HTTP_09,
324 HttpVersion::Http10 => Self::HTTP_10,
325 HttpVersion::Http11 => Self::HTTP_11,
326 HttpVersion::H2 => Self::HTTP_2,
327 HttpVersion::H3 => Self::HTTP_3,
328 }
329 }
330}
331
332#[cfg(feature = "http-types")]
333impl TryFrom<http_types::Version> for HttpVersion {
334 type Error = BoxError;
335
336 fn try_from(value: http_types::Version) -> Result<Self> {
337 Ok(match value {
338 http_types::Version::Http0_9 => Self::Http09,
339 http_types::Version::Http1_0 => Self::Http10,
340 http_types::Version::Http1_1 => Self::Http11,
341 http_types::Version::Http2_0 => Self::H2,
342 http_types::Version::Http3_0 => Self::H3,
343 _ => return Err(Box::new(BadVersion)),
344 })
345 }
346}
347
348#[cfg(feature = "http-types")]
349impl From<HttpVersion> for http_types::Version {
350 fn from(value: HttpVersion) -> Self {
351 match value {
352 HttpVersion::Http09 => Self::Http0_9,
353 HttpVersion::Http10 => Self::Http1_0,
354 HttpVersion::Http11 => Self::Http1_1,
355 HttpVersion::H2 => Self::Http2_0,
356 HttpVersion::H3 => Self::Http3_0,
357 }
358 }
359}
360
361pub use http_cache_semantics::CacheOptions;
364
365pub type CacheKey = Arc<dyn Fn(&request::Parts) -> String + Send + Sync>;
368
369pub type CacheModeFn = Arc<dyn Fn(&request::Parts) -> CacheMode + Send + Sync>;
371
372pub type CacheBust = Arc<
375 dyn Fn(&request::Parts, &Option<CacheKey>, &str) -> Vec<String>
376 + Send
377 + Sync,
378>;
379
380#[derive(Clone)]
383pub struct HttpCacheOptions {
384 pub cache_options: Option<CacheOptions>,
386 pub cache_key: Option<CacheKey>,
388 pub cache_mode_fn: Option<CacheModeFn>,
390 pub cache_bust: Option<CacheBust>,
392 pub cache_status_headers: bool,
394}
395
396impl Default for HttpCacheOptions {
397 fn default() -> Self {
398 Self {
399 cache_options: None,
400 cache_key: None,
401 cache_mode_fn: None,
402 cache_bust: None,
403 cache_status_headers: true,
404 }
405 }
406}
407
408impl Debug for HttpCacheOptions {
409 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410 f.debug_struct("HttpCacheOptions")
411 .field("cache_options", &self.cache_options)
412 .field("cache_key", &"Fn(&request::Parts) -> String")
413 .field("cache_mode_fn", &"Fn(&request::Parts) -> CacheMode")
414 .field("cache_bust", &"Fn(&request::Parts) -> Vec<String>")
415 .field("cache_status_headers", &self.cache_status_headers)
416 .finish()
417 }
418}
419
420impl HttpCacheOptions {
421 fn create_cache_key(
422 &self,
423 parts: &request::Parts,
424 override_method: Option<&str>,
425 ) -> String {
426 if let Some(cache_key) = &self.cache_key {
427 cache_key(parts)
428 } else {
429 format!(
430 "{}:{}",
431 override_method.unwrap_or_else(|| parts.method.as_str()),
432 parts.uri
433 )
434 }
435 }
436}
437
438#[derive(Debug, Clone)]
440pub struct HttpCache<T: CacheManager> {
441 pub mode: CacheMode,
443 pub manager: T,
447 pub options: HttpCacheOptions,
449}
450
451#[allow(dead_code)]
452impl<T: CacheManager> HttpCache<T> {
453 pub fn can_cache_request(
455 &self,
456 middleware: &impl Middleware,
457 ) -> Result<bool> {
458 let mode = self.cache_mode(middleware)?;
459
460 Ok(mode == CacheMode::IgnoreRules
461 || middleware.is_method_get_head() && mode != CacheMode::NoStore)
462 }
463
464 pub async fn run_no_cache(
466 &self,
467 middleware: &mut impl Middleware,
468 ) -> Result<()> {
469 self.manager
470 .delete(
471 &self
472 .options
473 .create_cache_key(&middleware.parts()?, Some("GET")),
474 )
475 .await
476 .ok();
477
478 let cache_key =
479 self.options.create_cache_key(&middleware.parts()?, None);
480
481 if let Some(cache_bust) = &self.options.cache_bust {
482 for key_to_cache_bust in cache_bust(
483 &middleware.parts()?,
484 &self.options.cache_key,
485 &cache_key,
486 ) {
487 self.manager.delete(&key_to_cache_bust).await?;
488 }
489 }
490
491 Ok(())
492 }
493
494 pub async fn run(
496 &self,
497 mut middleware: impl Middleware,
498 ) -> Result<HttpResponse> {
499 let is_cacheable = self.can_cache_request(&middleware)?;
500 if !is_cacheable {
501 return self.remote_fetch(&mut middleware).await;
502 }
503
504 let cache_key =
505 self.options.create_cache_key(&middleware.parts()?, None);
506
507 if let Some(cache_bust) = &self.options.cache_bust {
508 for key_to_cache_bust in cache_bust(
509 &middleware.parts()?,
510 &self.options.cache_key,
511 &cache_key,
512 ) {
513 self.manager.delete(&key_to_cache_bust).await?;
514 }
515 }
516
517 if let Some(store) = self.manager.get(&cache_key).await? {
518 let (mut res, policy) = store;
519 if self.options.cache_status_headers {
520 res.cache_lookup_status(HitOrMiss::HIT);
521 }
522 if let Some(warning_code) = res.warning_code() {
523 if (100..200).contains(&warning_code) {
534 res.remove_warning();
535 }
536 }
537
538 match self.cache_mode(&middleware)? {
539 CacheMode::Default => {
540 self.conditional_fetch(middleware, res, policy).await
541 }
542 CacheMode::NoCache => {
543 middleware.force_no_cache()?;
544 let mut res = self.remote_fetch(&mut middleware).await?;
545 if self.options.cache_status_headers {
546 res.cache_lookup_status(HitOrMiss::HIT);
547 }
548 Ok(res)
549 }
550 CacheMode::ForceCache
551 | CacheMode::OnlyIfCached
552 | CacheMode::IgnoreRules => {
553 res.add_warning(
558 &res.url.clone(),
559 112,
560 "Disconnected operation",
561 );
562 if self.options.cache_status_headers {
563 res.cache_status(HitOrMiss::HIT);
564 }
565 Ok(res)
566 }
567 _ => self.remote_fetch(&mut middleware).await,
568 }
569 } else {
570 match self.cache_mode(&middleware)? {
571 CacheMode::OnlyIfCached => {
572 let mut res = HttpResponse {
574 body: b"GatewayTimeout".to_vec(),
575 headers: HashMap::default(),
576 status: 504,
577 url: middleware.url()?,
578 version: HttpVersion::Http11,
579 };
580 if self.options.cache_status_headers {
581 res.cache_status(HitOrMiss::MISS);
582 res.cache_lookup_status(HitOrMiss::MISS);
583 }
584 Ok(res)
585 }
586 _ => self.remote_fetch(&mut middleware).await,
587 }
588 }
589 }
590
591 fn cache_mode(&self, middleware: &impl Middleware) -> Result<CacheMode> {
592 Ok(if let Some(mode) = middleware.overridden_cache_mode() {
593 mode
594 } else if let Some(cache_mode_fn) = &self.options.cache_mode_fn {
595 cache_mode_fn(&middleware.parts()?)
596 } else {
597 self.mode
598 })
599 }
600
601 async fn remote_fetch(
602 &self,
603 middleware: &mut impl Middleware,
604 ) -> Result<HttpResponse> {
605 let mut res = middleware.remote_fetch().await?;
606 if self.options.cache_status_headers {
607 res.cache_status(HitOrMiss::MISS);
608 res.cache_lookup_status(HitOrMiss::MISS);
609 }
610 let policy = match self.options.cache_options {
611 Some(options) => middleware.policy_with_options(&res, options)?,
612 None => middleware.policy(&res)?,
613 };
614 let is_get_head = middleware.is_method_get_head();
615 let mode = self.cache_mode(middleware)?;
616 let mut is_cacheable = is_get_head
617 && mode != CacheMode::NoStore
618 && res.status == 200
619 && policy.is_storable();
620 if mode == CacheMode::IgnoreRules && res.status == 200 {
621 is_cacheable = true;
622 }
623 if is_cacheable {
624 Ok(self
625 .manager
626 .put(
627 self.options.create_cache_key(&middleware.parts()?, None),
628 res,
629 policy,
630 )
631 .await?)
632 } else if !is_get_head {
633 self.manager
634 .delete(
635 &self
636 .options
637 .create_cache_key(&middleware.parts()?, Some("GET")),
638 )
639 .await
640 .ok();
641 Ok(res)
642 } else {
643 Ok(res)
644 }
645 }
646
647 async fn conditional_fetch(
648 &self,
649 mut middleware: impl Middleware,
650 mut cached_res: HttpResponse,
651 mut policy: CachePolicy,
652 ) -> Result<HttpResponse> {
653 let before_req =
654 policy.before_request(&middleware.parts()?, SystemTime::now());
655 match before_req {
656 BeforeRequest::Fresh(parts) => {
657 cached_res.update_headers(&parts)?;
658 if self.options.cache_status_headers {
659 cached_res.cache_status(HitOrMiss::HIT);
660 cached_res.cache_lookup_status(HitOrMiss::HIT);
661 }
662 return Ok(cached_res);
663 }
664 BeforeRequest::Stale { request: parts, matches } => {
665 if matches {
666 middleware.update_headers(&parts)?;
667 }
668 }
669 }
670 let req_url = middleware.url()?;
671 match middleware.remote_fetch().await {
672 Ok(mut cond_res) => {
673 let status = StatusCode::from_u16(cond_res.status)?;
674 if status.is_server_error() && cached_res.must_revalidate() {
675 cached_res.add_warning(
681 &req_url,
682 111,
683 "Revalidation failed",
684 );
685 if self.options.cache_status_headers {
686 cached_res.cache_status(HitOrMiss::HIT);
687 }
688 Ok(cached_res)
689 } else if cond_res.status == 304 {
690 let after_res = policy.after_response(
691 &middleware.parts()?,
692 &cond_res.parts()?,
693 SystemTime::now(),
694 );
695 match after_res {
696 AfterResponse::Modified(new_policy, parts)
697 | AfterResponse::NotModified(new_policy, parts) => {
698 policy = new_policy;
699 cached_res.update_headers(&parts)?;
700 }
701 }
702 if self.options.cache_status_headers {
703 cached_res.cache_status(HitOrMiss::HIT);
704 cached_res.cache_lookup_status(HitOrMiss::HIT);
705 }
706 let res = self
707 .manager
708 .put(
709 self.options
710 .create_cache_key(&middleware.parts()?, None),
711 cached_res,
712 policy,
713 )
714 .await?;
715 Ok(res)
716 } else if cond_res.status == 200 {
717 let policy = match self.options.cache_options {
718 Some(options) => middleware
719 .policy_with_options(&cond_res, options)?,
720 None => middleware.policy(&cond_res)?,
721 };
722 if self.options.cache_status_headers {
723 cond_res.cache_status(HitOrMiss::MISS);
724 cond_res.cache_lookup_status(HitOrMiss::HIT);
725 }
726 let res = self
727 .manager
728 .put(
729 self.options
730 .create_cache_key(&middleware.parts()?, None),
731 cond_res,
732 policy,
733 )
734 .await?;
735 Ok(res)
736 } else {
737 if self.options.cache_status_headers {
738 cached_res.cache_status(HitOrMiss::HIT);
739 }
740 Ok(cached_res)
741 }
742 }
743 Err(e) => {
744 if cached_res.must_revalidate() {
745 Err(e)
746 } else {
747 cached_res.add_warning(
753 &req_url,
754 111,
755 "Revalidation failed",
756 );
757 if self.options.cache_status_headers {
758 cached_res.cache_status(HitOrMiss::HIT);
759 }
760 Ok(cached_res)
761 }
762 }
763 }
764 }
765}
766
767#[cfg(test)]
768mod test;