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))]
15pub use http_cache::{BadRequest, HttpCacheError};
204
205use std::{
206 collections::HashMap, result::Result, str::FromStr, time::SystemTime,
207};
208
209use async_trait::async_trait;
210
211pub use http::request::Parts;
212use http::{header::CACHE_CONTROL, Method};
213use http_cache::{
214 BoxError, CacheManager, CacheOptions, HitOrMiss, HttpResponse, Middleware,
215 XCACHE, XCACHELOOKUP,
216};
217use http_cache_semantics::CachePolicy;
218use url::Url;
219
220pub use http_cache::{
221 CacheMode, HttpCache, HttpCacheOptions, ResponseCacheModeFn,
222};
223
224#[cfg(feature = "manager-cacache")]
225#[cfg_attr(docsrs, doc(cfg(feature = "manager-cacache")))]
226pub use http_cache::CACacheManager;
227
228#[cfg(feature = "manager-moka")]
229#[cfg_attr(docsrs, doc(cfg(feature = "manager-moka")))]
230pub use http_cache::{MokaCache, MokaCacheBuilder, MokaManager};
231
232#[cfg(feature = "rate-limiting")]
233#[cfg_attr(docsrs, doc(cfg(feature = "rate-limiting")))]
234pub use http_cache::rate_limiting::{
235 CacheAwareRateLimiter, DirectRateLimiter, DomainRateLimiter, Quota,
236};
237
238#[derive(Debug, Clone)]
240pub struct CachedAgent<T: CacheManager> {
241 agent: ureq::Agent,
242 cache: HttpCache<T>,
243}
244
245#[derive(Debug)]
247pub struct CachedAgentBuilder<T: CacheManager> {
248 agent_config: Option<ureq::config::Config>,
249 cache_manager: Option<T>,
250 cache_mode: CacheMode,
251 cache_options: HttpCacheOptions,
252}
253
254impl<T: CacheManager> Default for CachedAgentBuilder<T> {
255 fn default() -> Self {
256 Self {
257 agent_config: None,
258 cache_manager: None,
259 cache_mode: CacheMode::Default,
260 cache_options: HttpCacheOptions::default(),
261 }
262 }
263}
264
265impl<T: CacheManager> CachedAgentBuilder<T> {
266 pub fn new() -> Self {
268 Self::default()
269 }
270
271 pub fn agent_config(mut self, config: ureq::config::Config) -> Self {
280 self.agent_config = Some(config);
281 self
282 }
283
284 pub fn cache_manager(mut self, manager: T) -> Self {
286 self.cache_manager = Some(manager);
287 self
288 }
289
290 pub fn cache_mode(mut self, mode: CacheMode) -> Self {
292 self.cache_mode = mode;
293 self
294 }
295
296 pub fn cache_options(mut self, options: HttpCacheOptions) -> Self {
298 self.cache_options = options;
299 self
300 }
301
302 pub fn build(self) -> Result<CachedAgent<T>, HttpCacheError> {
304 let agent = if let Some(user_config) = self.agent_config {
305 let mut config_builder =
307 ureq::config::Config::builder().http_status_as_error(false); let timeouts = user_config.timeouts();
311 if timeouts.global.is_some()
312 || timeouts.connect.is_some()
313 || timeouts.send_request.is_some()
314 {
315 if let Some(global) = timeouts.global {
316 config_builder =
317 config_builder.timeout_global(Some(global));
318 }
319 if let Some(connect) = timeouts.connect {
320 config_builder =
321 config_builder.timeout_connect(Some(connect));
322 }
323 if let Some(send_request) = timeouts.send_request {
324 config_builder =
325 config_builder.timeout_send_request(Some(send_request));
326 }
327 }
328
329 if let Some(proxy) = user_config.proxy() {
331 config_builder = config_builder.proxy(Some(proxy.clone()));
332 }
333
334 let tls_config = user_config.tls_config();
336 config_builder = config_builder.tls_config(tls_config.clone());
337
338 let user_agent = user_config.user_agent();
340 config_builder = config_builder.user_agent(user_agent.clone());
341
342 let config = config_builder.build();
343 ureq::Agent::new_with_config(config)
344 } else {
345 let config = ureq::config::Config::builder()
347 .http_status_as_error(false)
348 .build();
349 ureq::Agent::new_with_config(config)
350 };
351
352 let cache_manager = self.cache_manager.ok_or_else(|| {
353 HttpCacheError::Cache("Cache manager is required".to_string())
354 })?;
355
356 Ok(CachedAgent {
357 agent,
358 cache: HttpCache {
359 mode: self.cache_mode,
360 manager: cache_manager,
361 options: self.cache_options,
362 },
363 })
364 }
365}
366
367impl<T: CacheManager> CachedAgent<T> {
368 pub fn builder() -> CachedAgentBuilder<T> {
370 CachedAgentBuilder::new()
371 }
372
373 pub fn get(&self, url: &str) -> CachedRequestBuilder<'_, T> {
375 CachedRequestBuilder {
376 agent: self,
377 method: "GET".to_string(),
378 url: url.to_string(),
379 headers: Vec::new(),
380 }
381 }
382
383 pub fn post(&self, url: &str) -> CachedRequestBuilder<'_, T> {
385 CachedRequestBuilder {
386 agent: self,
387 method: "POST".to_string(),
388 url: url.to_string(),
389 headers: Vec::new(),
390 }
391 }
392
393 pub fn put(&self, url: &str) -> CachedRequestBuilder<'_, T> {
395 CachedRequestBuilder {
396 agent: self,
397 method: "PUT".to_string(),
398 url: url.to_string(),
399 headers: Vec::new(),
400 }
401 }
402
403 pub fn delete(&self, url: &str) -> CachedRequestBuilder<'_, T> {
405 CachedRequestBuilder {
406 agent: self,
407 method: "DELETE".to_string(),
408 url: url.to_string(),
409 headers: Vec::new(),
410 }
411 }
412
413 pub fn head(&self, url: &str) -> CachedRequestBuilder<'_, T> {
415 CachedRequestBuilder {
416 agent: self,
417 method: "HEAD".to_string(),
418 url: url.to_string(),
419 headers: Vec::new(),
420 }
421 }
422
423 pub fn request(
425 &self,
426 method: &str,
427 url: &str,
428 ) -> CachedRequestBuilder<'_, T> {
429 CachedRequestBuilder {
430 agent: self,
431 method: method.to_string(),
432 url: url.to_string(),
433 headers: Vec::new(),
434 }
435 }
436}
437
438#[derive(Debug)]
440pub struct CachedRequestBuilder<'a, T: CacheManager> {
441 agent: &'a CachedAgent<T>,
442 method: String,
443 url: String,
444 headers: Vec<(String, String)>,
445}
446
447impl<'a, T: CacheManager> CachedRequestBuilder<'a, T> {
448 pub fn set(mut self, header: &str, value: &str) -> Self {
450 self.headers.push((header.to_string(), value.to_string()));
451 self
452 }
453
454 #[cfg(feature = "json")]
456 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
457 pub async fn send_json(
458 self,
459 data: serde_json::Value,
460 ) -> Result<CachedResponse, HttpCacheError> {
461 let agent = self.agent.agent.clone();
462 let url = self.url.clone();
463 let method = self.method;
464 let headers = self.headers.clone();
465 let url_for_response = url.clone();
466
467 let response = smol::unblock(move || {
468 execute_json_request(&agent, &method, &url, &headers, data).map_err(
469 |e| {
470 HttpCacheError::http(Box::new(std::io::Error::other(
471 e.to_string(),
472 )))
473 },
474 )
475 })
476 .await?;
477
478 let cached = smol::unblock(move || {
479 Ok::<_, HttpCacheError>(CachedResponse::from_ureq_response(
480 response,
481 &url_for_response,
482 ))
483 })
484 .await?;
485
486 Ok(cached)
487 }
488
489 pub async fn send_string(
491 self,
492 data: &str,
493 ) -> Result<CachedResponse, HttpCacheError> {
494 let data = data.to_string();
495 let agent = self.agent.agent.clone();
496 let url = self.url.clone();
497 let method = self.method;
498 let headers = self.headers.clone();
499 let url_for_response = url.clone();
500
501 let response = smol::unblock(move || {
502 execute_request(&agent, &method, &url, &headers, Some(&data))
503 .map_err(|e| {
504 HttpCacheError::http(Box::new(std::io::Error::other(
505 e.to_string(),
506 )))
507 })
508 })
509 .await?;
510
511 let cached = smol::unblock(move || {
512 Ok::<_, HttpCacheError>(CachedResponse::from_ureq_response(
513 response,
514 &url_for_response,
515 ))
516 })
517 .await?;
518
519 Ok(cached)
520 }
521
522 pub async fn call(self) -> Result<CachedResponse, HttpCacheError> {
524 let mut middleware = UreqMiddleware {
525 method: self.method.to_string(),
526 url: self.url.clone(),
527 headers: self.headers.clone(),
528 agent: &self.agent.agent,
529 };
530
531 if self
533 .agent
534 .cache
535 .can_cache_request(&middleware)
536 .map_err(|e| HttpCacheError::Cache(e.to_string()))?
537 {
538 let response = self
540 .agent
541 .cache
542 .run(middleware)
543 .await
544 .map_err(|e| HttpCacheError::Cache(e.to_string()))?;
545
546 Ok(CachedResponse::from(response))
547 } else {
548 self.agent
550 .cache
551 .run_no_cache(&mut middleware)
552 .await
553 .map_err(|e| HttpCacheError::Cache(e.to_string()))?;
554
555 let agent = self.agent.agent.clone();
557 let url = self.url.clone();
558 let method = self.method;
559 let headers = self.headers.clone();
560 let url_for_response = url.clone();
561 let cache_status_headers =
562 self.agent.cache.options.cache_status_headers;
563
564 let response = smol::unblock(move || {
565 execute_request(&agent, &method, &url, &headers, None).map_err(
566 |e| {
567 HttpCacheError::http(Box::new(std::io::Error::other(
568 e.to_string(),
569 )))
570 },
571 )
572 })
573 .await?;
574
575 let mut cached_response = smol::unblock(move || {
576 Ok::<_, HttpCacheError>(CachedResponse::from_ureq_response(
577 response,
578 &url_for_response,
579 ))
580 })
581 .await?;
582
583 if cache_status_headers {
585 cached_response
586 .headers
587 .entry(XCACHE.to_string())
588 .or_insert_with(Vec::new)
589 .push(HitOrMiss::MISS.to_string());
590 cached_response
591 .headers
592 .entry(XCACHELOOKUP.to_string())
593 .or_insert_with(Vec::new)
594 .push(HitOrMiss::MISS.to_string());
595 }
596
597 Ok(cached_response)
598 }
599 }
600}
601
602struct UreqMiddleware<'a> {
604 method: String,
605 url: String,
606 headers: Vec<(String, String)>,
607 agent: &'a ureq::Agent,
608}
609
610fn is_cacheable_method(method: &str) -> bool {
611 matches!(method, "GET" | "HEAD")
612}
613
614fn execute_request(
616 agent: &ureq::Agent,
617 method: &str,
618 url: &str,
619 headers: &[(String, String)],
620 body: Option<&str>,
621) -> Result<http::Response<ureq::Body>, ureq::Error> {
622 let mut http_request = http::Request::builder().method(method).uri(url);
624
625 for (name, value) in headers {
627 http_request = http_request.header(name, value);
628 }
629
630 let request = match body {
632 Some(data) => http_request.body(data.as_bytes().to_vec()),
633 None => http_request.body(Vec::new()),
634 }
635 .map_err(|e| ureq::Error::BadUri(e.to_string()))?;
636
637 agent.run(request)
639}
640
641#[cfg(feature = "json")]
642fn execute_json_request(
644 agent: &ureq::Agent,
645 method: &str,
646 url: &str,
647 headers: &[(String, String)],
648 data: serde_json::Value,
649) -> Result<http::Response<ureq::Body>, ureq::Error> {
650 let json_string = serde_json::to_string(&data).map_err(|e| {
651 ureq::Error::Io(std::io::Error::new(
652 std::io::ErrorKind::InvalidData,
653 format!("JSON serialization error: {}", e),
654 ))
655 })?;
656
657 let mut json_headers = headers.to_vec();
659 json_headers
660 .push(("Content-Type".to_string(), "application/json".to_string()));
661
662 execute_request(agent, method, url, &json_headers, Some(&json_string))
663}
664
665fn convert_ureq_response_to_http_response(
666 mut response: http::Response<ureq::Body>,
667 url: &str,
668) -> Result<HttpResponse, HttpCacheError> {
669 let status = response.status();
670
671 let headers = response.headers().into();
673
674 let body = response.body_mut().read_to_vec().map_err(|e| {
676 HttpCacheError::http(Box::new(std::io::Error::other(format!(
677 "Failed to read response body: {}",
678 e
679 ))))
680 })?;
681
682 let parsed_url = Url::parse(url).map_err(|e| {
684 HttpCacheError::http(Box::new(std::io::Error::other(format!(
685 "Invalid URL '{}': {}",
686 url, e
687 ))))
688 })?;
689
690 Ok(HttpResponse {
691 body,
692 headers,
693 status: status.as_u16(),
694 url: parsed_url,
695 version: http_cache::HttpVersion::Http11,
696 metadata: None,
697 })
698}
699
700#[derive(Debug)]
702pub struct CachedResponse {
703 status: u16,
704 headers: HashMap<String, Vec<String>>,
705 body: Vec<u8>,
706 url: String,
707 cached: bool,
708}
709
710impl CachedResponse {
711 pub fn status(&self) -> u16 {
713 self.status
714 }
715
716 pub fn url(&self) -> &str {
718 &self.url
719 }
720
721 pub fn header(&self, name: &str) -> Option<&str> {
723 self.headers
724 .get(name)
725 .and_then(|values| values.first().map(|s| s.as_str()))
726 }
727
728 pub fn headers_names(&self) -> impl Iterator<Item = &String> {
730 self.headers.keys()
731 }
732
733 pub fn is_cached(&self) -> bool {
735 self.cached
736 }
737
738 pub fn into_string(self) -> Result<String, HttpCacheError> {
740 String::from_utf8(self.body).map_err(|e| {
741 HttpCacheError::http(Box::new(std::io::Error::other(format!(
742 "Invalid UTF-8 in response body: {}",
743 e
744 ))))
745 })
746 }
747
748 pub fn as_bytes(&self) -> &[u8] {
750 &self.body
751 }
752
753 pub fn into_bytes(self) -> Vec<u8> {
755 self.body
756 }
757
758 #[cfg(feature = "json")]
760 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
761 pub fn into_json<T: serde::de::DeserializeOwned>(
762 self,
763 ) -> Result<T, HttpCacheError> {
764 serde_json::from_slice(&self.body).map_err(|e| {
765 HttpCacheError::http(Box::new(std::io::Error::other(format!(
766 "JSON parse error: {}",
767 e
768 ))))
769 })
770 }
771}
772
773impl CachedResponse {
774 fn from_ureq_response(
776 mut response: http::Response<ureq::Body>,
777 url: &str,
778 ) -> Self {
779 let status = response.status().as_u16();
780
781 let mut headers = HashMap::new();
782 for (name, value) in response.headers() {
783 let value_str = value.to_str().unwrap_or("");
784 headers
785 .entry(name.as_str().to_string())
786 .or_insert_with(Vec::new)
787 .push(value_str.to_string());
788 }
789
790 let body = response.body_mut().read_to_vec().unwrap_or_default();
795
796 Self { status, headers, body, url: url.to_string(), cached: false }
797 }
798}
799
800impl From<HttpResponse> for CachedResponse {
801 fn from(response: HttpResponse) -> Self {
802 Self {
805 status: response.status,
806 headers: response.headers.into(),
807 body: response.body,
808 url: response.url.to_string(),
809 cached: true,
810 }
811 }
812}
813
814#[async_trait]
815impl Middleware for UreqMiddleware<'_> {
816 fn is_method_get_head(&self) -> bool {
817 is_cacheable_method(&self.method)
818 }
819
820 fn policy(
821 &self,
822 response: &HttpResponse,
823 ) -> http_cache::Result<CachePolicy> {
824 let parts = self.build_http_parts()?;
825 Ok(CachePolicy::new(&parts, &response.parts()?))
826 }
827
828 fn policy_with_options(
829 &self,
830 response: &HttpResponse,
831 options: CacheOptions,
832 ) -> http_cache::Result<CachePolicy> {
833 let parts = self.build_http_parts()?;
834 Ok(CachePolicy::new_options(
835 &parts,
836 &response.parts()?,
837 SystemTime::now(),
838 options,
839 ))
840 }
841
842 fn update_headers(&mut self, parts: &Parts) -> http_cache::Result<()> {
843 for (name, value) in parts.headers.iter() {
844 let value_str = value.to_str().map_err(|e| {
845 BoxError::from(format!("Invalid header value: {}", e))
846 })?;
847 self.headers
848 .push((name.as_str().to_string(), value_str.to_string()));
849 }
850 Ok(())
851 }
852
853 fn force_no_cache(&mut self) -> http_cache::Result<()> {
854 self.headers
855 .push((CACHE_CONTROL.as_str().to_string(), "no-cache".to_string()));
856 Ok(())
857 }
858
859 fn parts(&self) -> http_cache::Result<Parts> {
860 self.build_http_parts()
861 }
862
863 fn url(&self) -> http_cache::Result<Url> {
864 Url::parse(&self.url).map_err(BoxError::from)
865 }
866
867 fn method(&self) -> http_cache::Result<String> {
868 Ok(self.method.clone())
869 }
870
871 async fn remote_fetch(&mut self) -> http_cache::Result<HttpResponse> {
872 let agent = self.agent.clone();
873 let method = self.method.clone();
874 let url = self.url.clone();
875 let headers = self.headers.clone();
876
877 let url_for_conversion = url.clone();
878 let response = smol::unblock(move || {
879 execute_request(&agent, &method, &url, &headers, None)
880 .map_err(|e| e.to_string())
881 })
882 .await
883 .map_err(BoxError::from)?;
884
885 let http_response = smol::unblock(move || {
887 convert_ureq_response_to_http_response(
888 response,
889 &url_for_conversion,
890 )
891 .map_err(|e| e.to_string())
892 })
893 .await
894 .map_err(BoxError::from)?;
895
896 Ok(http_response)
897 }
898}
899
900impl UreqMiddleware<'_> {
901 fn build_http_parts(&self) -> http_cache::Result<Parts> {
902 let method = Method::from_str(&self.method)
903 .map_err(|e| BoxError::from(format!("Invalid method: {}", e)))?;
904
905 let uri = self
906 .url
907 .parse::<http::Uri>()
908 .map_err(|e| BoxError::from(format!("Invalid URI: {}", e)))?;
909
910 let mut http_request = http::Request::builder().method(method).uri(uri);
911
912 for (name, value) in &self.headers {
914 http_request = http_request.header(name, value);
915 }
916
917 let req = http_request.body(()).map_err(|e| {
918 BoxError::from(format!("Failed to build HTTP request: {}", e))
919 })?;
920
921 Ok(req.into_parts().0)
922 }
923}
924
925#[cfg(test)]
926mod test;