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 .insert(XCACHE.to_string(), HitOrMiss::MISS.to_string());
588 cached_response.headers.insert(
589 XCACHELOOKUP.to_string(),
590 HitOrMiss::MISS.to_string(),
591 );
592 }
593
594 Ok(cached_response)
595 }
596 }
597}
598
599struct UreqMiddleware<'a> {
601 method: String,
602 url: String,
603 headers: Vec<(String, String)>,
604 agent: &'a ureq::Agent,
605}
606
607fn is_cacheable_method(method: &str) -> bool {
608 matches!(method, "GET" | "HEAD")
609}
610
611fn execute_request(
613 agent: &ureq::Agent,
614 method: &str,
615 url: &str,
616 headers: &[(String, String)],
617 body: Option<&str>,
618) -> Result<http::Response<ureq::Body>, ureq::Error> {
619 let mut http_request = http::Request::builder().method(method).uri(url);
621
622 for (name, value) in headers {
624 http_request = http_request.header(name, value);
625 }
626
627 let request = match body {
629 Some(data) => http_request.body(data.as_bytes().to_vec()),
630 None => http_request.body(Vec::new()),
631 }
632 .map_err(|e| ureq::Error::BadUri(e.to_string()))?;
633
634 agent.run(request)
636}
637
638#[cfg(feature = "json")]
639fn execute_json_request(
641 agent: &ureq::Agent,
642 method: &str,
643 url: &str,
644 headers: &[(String, String)],
645 data: serde_json::Value,
646) -> Result<http::Response<ureq::Body>, ureq::Error> {
647 let json_string = serde_json::to_string(&data).map_err(|e| {
648 ureq::Error::Io(std::io::Error::new(
649 std::io::ErrorKind::InvalidData,
650 format!("JSON serialization error: {}", e),
651 ))
652 })?;
653
654 let mut json_headers = headers.to_vec();
656 json_headers
657 .push(("Content-Type".to_string(), "application/json".to_string()));
658
659 execute_request(agent, method, url, &json_headers, Some(&json_string))
660}
661
662fn convert_ureq_response_to_http_response(
663 mut response: http::Response<ureq::Body>,
664 url: &str,
665) -> Result<HttpResponse, HttpCacheError> {
666 let status = response.status();
667 let mut headers = HashMap::new();
668
669 for (name, value) in response.headers() {
671 let value_str = value.to_str().map_err(|e| {
672 HttpCacheError::http(Box::new(std::io::Error::other(format!(
673 "Invalid header value: {}",
674 e
675 ))))
676 })?;
677 headers.insert(name.as_str().to_string(), value_str.to_string());
678 }
679
680 let body_string = response.body_mut().read_to_string().map_err(|e| {
682 HttpCacheError::http(Box::new(std::io::Error::other(format!(
683 "Failed to read response body: {}",
684 e
685 ))))
686 })?;
687
688 let body = body_string.into_bytes();
689
690 let parsed_url = Url::parse(url).map_err(|e| {
692 HttpCacheError::http(Box::new(std::io::Error::other(format!(
693 "Invalid URL '{}': {}",
694 url, e
695 ))))
696 })?;
697
698 Ok(HttpResponse {
699 body,
700 headers,
701 status: status.as_u16(),
702 url: parsed_url,
703 version: http_cache::HttpVersion::Http11,
704 })
705}
706
707#[derive(Debug)]
709pub struct CachedResponse {
710 status: u16,
711 headers: HashMap<String, String>,
712 body: Vec<u8>,
713 url: String,
714 cached: bool,
715}
716
717impl CachedResponse {
718 pub fn status(&self) -> u16 {
720 self.status
721 }
722
723 pub fn url(&self) -> &str {
725 &self.url
726 }
727
728 pub fn header(&self, name: &str) -> Option<&str> {
730 self.headers.get(name).map(|s| s.as_str())
731 }
732
733 pub fn headers_names(&self) -> impl Iterator<Item = &String> {
735 self.headers.keys()
736 }
737
738 pub fn is_cached(&self) -> bool {
740 self.cached
741 }
742
743 pub fn into_string(self) -> Result<String, HttpCacheError> {
745 String::from_utf8(self.body).map_err(|e| {
746 HttpCacheError::http(Box::new(std::io::Error::other(format!(
747 "Invalid UTF-8 in response body: {}",
748 e
749 ))))
750 })
751 }
752
753 pub fn as_bytes(&self) -> &[u8] {
755 &self.body
756 }
757
758 pub fn into_bytes(self) -> Vec<u8> {
760 self.body
761 }
762
763 #[cfg(feature = "json")]
765 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
766 pub fn into_json<T: serde::de::DeserializeOwned>(
767 self,
768 ) -> Result<T, HttpCacheError> {
769 serde_json::from_slice(&self.body).map_err(|e| {
770 HttpCacheError::http(Box::new(std::io::Error::other(format!(
771 "JSON parse error: {}",
772 e
773 ))))
774 })
775 }
776}
777
778impl CachedResponse {
779 fn from_ureq_response(
781 mut response: http::Response<ureq::Body>,
782 url: &str,
783 ) -> Self {
784 let status = response.status().as_u16();
785
786 let mut headers = HashMap::new();
787 for (name, value) in response.headers() {
788 let value_str = value.to_str().unwrap_or("");
789 headers.insert(name.as_str().to_string(), value_str.to_string());
790 }
791
792 let body = if let Ok(body_string) = response.body_mut().read_to_string()
797 {
798 body_string.into_bytes()
799 } else {
800 Vec::new()
801 };
802
803 Self { status, headers, body, url: url.to_string(), cached: false }
804 }
805}
806
807impl From<HttpResponse> for CachedResponse {
808 fn from(response: HttpResponse) -> Self {
809 Self {
812 status: response.status,
813 headers: response.headers,
814 body: response.body,
815 url: response.url.to_string(),
816 cached: true,
817 }
818 }
819}
820
821#[async_trait]
822impl Middleware for UreqMiddleware<'_> {
823 fn is_method_get_head(&self) -> bool {
824 is_cacheable_method(&self.method)
825 }
826
827 fn policy(
828 &self,
829 response: &HttpResponse,
830 ) -> http_cache::Result<CachePolicy> {
831 let parts = self.build_http_parts()?;
832 Ok(CachePolicy::new(&parts, &response.parts()?))
833 }
834
835 fn policy_with_options(
836 &self,
837 response: &HttpResponse,
838 options: CacheOptions,
839 ) -> http_cache::Result<CachePolicy> {
840 let parts = self.build_http_parts()?;
841 Ok(CachePolicy::new_options(
842 &parts,
843 &response.parts()?,
844 SystemTime::now(),
845 options,
846 ))
847 }
848
849 fn update_headers(&mut self, parts: &Parts) -> http_cache::Result<()> {
850 for (name, value) in parts.headers.iter() {
851 let value_str = value.to_str().map_err(|e| {
852 BoxError::from(format!("Invalid header value: {}", e))
853 })?;
854 self.headers
855 .push((name.as_str().to_string(), value_str.to_string()));
856 }
857 Ok(())
858 }
859
860 fn force_no_cache(&mut self) -> http_cache::Result<()> {
861 self.headers
862 .push((CACHE_CONTROL.as_str().to_string(), "no-cache".to_string()));
863 Ok(())
864 }
865
866 fn parts(&self) -> http_cache::Result<Parts> {
867 self.build_http_parts()
868 }
869
870 fn url(&self) -> http_cache::Result<Url> {
871 Url::parse(&self.url).map_err(BoxError::from)
872 }
873
874 fn method(&self) -> http_cache::Result<String> {
875 Ok(self.method.clone())
876 }
877
878 async fn remote_fetch(&mut self) -> http_cache::Result<HttpResponse> {
879 let agent = self.agent.clone();
880 let method = self.method.clone();
881 let url = self.url.clone();
882 let headers = self.headers.clone();
883
884 let url_for_conversion = url.clone();
885 let response = smol::unblock(move || {
886 execute_request(&agent, &method, &url, &headers, None)
887 .map_err(|e| e.to_string())
888 })
889 .await
890 .map_err(BoxError::from)?;
891
892 let http_response = smol::unblock(move || {
894 convert_ureq_response_to_http_response(
895 response,
896 &url_for_conversion,
897 )
898 .map_err(|e| e.to_string())
899 })
900 .await
901 .map_err(BoxError::from)?;
902
903 Ok(http_response)
904 }
905}
906
907impl UreqMiddleware<'_> {
908 fn build_http_parts(&self) -> http_cache::Result<Parts> {
909 let method = Method::from_str(&self.method)
910 .map_err(|e| BoxError::from(format!("Invalid method: {}", e)))?;
911
912 let uri = self
913 .url
914 .parse::<http::Uri>()
915 .map_err(|e| BoxError::from(format!("Invalid URI: {}", e)))?;
916
917 let mut http_request = http::Request::builder().method(method).uri(uri);
918
919 for (name, value) in &self.headers {
921 http_request = http_request.header(name, value);
922 }
923
924 let req = http_request.body(()).map_err(|e| {
925 BoxError::from(format!("Failed to build HTTP request: {}", e))
926 })?;
927
928 Ok(req.into_parts().0)
929 }
930}
931
932#[cfg(test)]
933mod test;