http_cache_surf/
lib.rs

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))]
15//! The surf middleware implementation for http-cache.
16//! ```no_run
17//! use http_cache_surf::{Cache, CacheMode, CACacheManager, HttpCache, HttpCacheOptions};
18//!
19//! #[async_std::main]
20//! async fn main() -> surf::Result<()> {
21//!     let req = surf::get("https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching");
22//!     surf::client()
23//!         .with(Cache(HttpCache {
24//!             mode: CacheMode::Default,
25//!             manager: CACacheManager::default(),
26//!             options: HttpCacheOptions::default(),
27//!         }))
28//!         .send(req)
29//!         .await?;
30//!     Ok(())
31//! }
32//! ```
33mod error;
34
35use anyhow::anyhow;
36use std::{
37    collections::HashMap, convert::TryInto, str::FromStr, time::SystemTime,
38};
39
40pub use http::request::Parts;
41use http::{header::CACHE_CONTROL, request};
42use http_cache::{
43    BadHeader, BoxError, HitOrMiss, Middleware, Result, XCACHE, XCACHELOOKUP,
44};
45use http_cache_semantics::CachePolicy;
46use http_types::{headers::HeaderValue, Method, Response, StatusCode, Version};
47use surf::{middleware::Next, Client, Request};
48use url::Url;
49
50pub use http_cache::{
51    CacheManager, CacheMode, CacheOptions, HttpCache, HttpCacheOptions,
52    HttpResponse,
53};
54
55#[cfg(feature = "manager-cacache")]
56#[cfg_attr(docsrs, doc(cfg(feature = "manager-cacache")))]
57pub use http_cache::CACacheManager;
58
59#[cfg(feature = "manager-moka")]
60#[cfg_attr(docsrs, doc(cfg(feature = "manager-moka")))]
61pub use http_cache::{MokaCache, MokaCacheBuilder, MokaManager};
62
63/// Wrapper for [`HttpCache`]
64#[derive(Debug)]
65pub struct Cache<T: CacheManager>(pub HttpCache<T>);
66
67/// Implements ['Middleware'] for surf
68pub(crate) struct SurfMiddleware<'a> {
69    pub req: Request,
70    pub client: Client,
71    pub next: Next<'a>,
72}
73
74#[async_trait::async_trait]
75impl Middleware for SurfMiddleware<'_> {
76    fn is_method_get_head(&self) -> bool {
77        self.req.method() == Method::Get || self.req.method() == Method::Head
78    }
79    fn policy(&self, response: &HttpResponse) -> Result<CachePolicy> {
80        Ok(CachePolicy::new(&self.parts()?, &response.parts()?))
81    }
82    fn policy_with_options(
83        &self,
84        response: &HttpResponse,
85        options: CacheOptions,
86    ) -> Result<CachePolicy> {
87        Ok(CachePolicy::new_options(
88            &self.parts()?,
89            &response.parts()?,
90            SystemTime::now(),
91            options,
92        ))
93    }
94    fn update_headers(&mut self, parts: &Parts) -> Result<()> {
95        for header in parts.headers.iter() {
96            let value = match HeaderValue::from_str(header.1.to_str()?) {
97                Ok(v) => v,
98                Err(_e) => return Err(Box::new(BadHeader)),
99            };
100            self.req.set_header(header.0.as_str(), value);
101        }
102        Ok(())
103    }
104    fn force_no_cache(&mut self) -> Result<()> {
105        self.req.insert_header(CACHE_CONTROL.as_str(), "no-cache");
106        Ok(())
107    }
108    fn parts(&self) -> Result<Parts> {
109        let mut converted = request::Builder::new()
110            .method(self.req.method().as_ref())
111            .uri(self.req.url().as_str())
112            .body(())?;
113        {
114            let headers = converted.headers_mut();
115            for header in self.req.iter() {
116                headers.insert(
117                    http::header::HeaderName::from_str(header.0.as_str())?,
118                    http::HeaderValue::from_str(header.1.as_str())?,
119                );
120            }
121        }
122        Ok(converted.into_parts().0)
123    }
124    fn url(&self) -> Result<Url> {
125        Ok(self.req.url().clone())
126    }
127    fn method(&self) -> Result<String> {
128        Ok(self.req.method().as_ref().to_string())
129    }
130    async fn remote_fetch(&mut self) -> Result<HttpResponse> {
131        let url = self.req.url().clone();
132        let mut res =
133            self.next.run(self.req.clone(), self.client.clone()).await?;
134        let mut headers = HashMap::new();
135        for header in res.iter() {
136            headers.insert(
137                header.0.as_str().to_owned(),
138                header.1.as_str().to_owned(),
139            );
140        }
141        let status = res.status().into();
142        let version = res.version().unwrap_or(Version::Http1_1);
143        let body: Vec<u8> = res.body_bytes().await?;
144        Ok(HttpResponse {
145            body,
146            headers,
147            status,
148            url,
149            version: version.try_into()?,
150        })
151    }
152}
153
154fn to_http_types_error(e: BoxError) -> http_types::Error {
155    http_types::Error::from(anyhow!(e))
156}
157
158#[surf::utils::async_trait]
159impl<T: CacheManager> surf::middleware::Middleware for Cache<T> {
160    async fn handle(
161        &self,
162        req: Request,
163        client: Client,
164        next: Next<'_>,
165    ) -> std::result::Result<surf::Response, http_types::Error> {
166        let mut middleware = SurfMiddleware { req, client, next };
167        if self
168            .0
169            .can_cache_request(&middleware)
170            .map_err(|e| http_types::Error::from(anyhow!(e)))?
171        {
172            let res =
173                self.0.run(middleware).await.map_err(to_http_types_error)?;
174            let mut converted = Response::new(StatusCode::Ok);
175            for header in &res.headers {
176                let val =
177                    HeaderValue::from_bytes(header.1.as_bytes().to_vec())?;
178                converted.insert_header(header.0.as_str(), val);
179            }
180            converted.set_status(res.status.try_into()?);
181            converted.set_version(Some(res.version.into()));
182            converted.set_body(res.body);
183            Ok(surf::Response::from(converted))
184        } else {
185            self.0
186                .run_no_cache(&mut middleware)
187                .await
188                .map_err(to_http_types_error)?;
189            let mut res =
190                middleware.next.run(middleware.req, middleware.client).await?;
191            let miss = HitOrMiss::MISS.to_string();
192            res.append_header(XCACHE, miss.clone());
193            res.append_header(XCACHELOOKUP, miss);
194            Ok(res)
195        }
196    }
197}
198
199#[cfg(test)]
200mod test;