wreq_util/tower/delay.rs
1//! Request delay middleware.
2//!
3//! Adds configurable delays before HTTP requests — useful for rate limiting,
4//! testing under slow network conditions, or just being polite to APIs.
5//!
6//! # Quick Start
7//!
8//! Fixed 1-second delay:
9//!
10//! ```no_run
11//! use std::time::Duration;
12//! use wreq::Client;
13//! use wreq_util::middleware::delay::DelayLayer;
14//!
15//! let client = Client::builder()
16//! .layer(DelayLayer::new(Duration::from_secs(1)))
17//! .build()?;
18//! # Ok::<(), wreq::Error>(())
19//! ```
20//!
21//! Random jitter (0.8s ~ 1.2s):
22//!
23//! ```no_run
24//! use std::time::Duration;
25//! use wreq::Client;
26//! use wreq_util::middleware::delay::JitterDelayLayer;
27//!
28//! let client = Client::builder()
29//! .layer(JitterDelayLayer::new(Duration::from_secs(1), 0.2))
30//! .build()?;
31//! # Ok::<(), wreq::Error>(())
32//! ```
33//!
34//! # Conditional Delays
35//!
36//! Use `.when()` to apply delays only to matching requests:
37//!
38//! ```ignore
39//! // Only delay POST requests
40//! DelayLayer::new(Duration::from_secs(1))
41//! .when(|req: &http::Request<_>| req.method() == http::Method::POST)
42//!
43//! // Jitter on specific paths
44//! JitterDelayLayer::new(Duration::from_millis(500), 0.3)
45//! .when(|req: &http::Request<_>| req.uri().path().starts_with("/api"))
46//! ```
47//!
48//! # Notes
49//!
50//! - Delays are async and won't block the runtime
51//! - Not a substitute for proper rate limiters — servers can still see timing patterns
52//! - Keep delays short in hot paths
53
54mod future;
55mod layer;
56mod service;
57
58use std::time::Duration;
59
60pub use self::{
61 future::ResponseFuture,
62 layer::{DelayLayer, DelayLayerWith, JitterDelayLayer, JitterDelayLayerWith},
63 service::{Delay, DelayWith, JitterDelay, JitterDelayWith},
64};
65
66/// Compute a randomized duration in `[base * (1 - pct), base * (1 + pct)]`.
67fn jittered_duration(base: Duration, pct: f64) -> Duration {
68 let jitter = base.mul_f64(pct);
69 let low = base.saturating_sub(jitter);
70 let high = base.saturating_add(jitter);
71
72 if low >= high {
73 return base;
74 }
75
76 // Generate pseudo-random value using multiple entropy sources
77 let time_entropy = {
78 use std::time::SystemTime;
79 SystemTime::now()
80 .duration_since(SystemTime::UNIX_EPOCH)
81 .map(|d| d.as_nanos() as u64)
82 .unwrap_or(0)
83 };
84
85 // Use stack address as additional entropy source
86 let addr_entropy = (&time_entropy as *const u64 as u64).wrapping_mul(0x517cc1b727220a95);
87
88 // Mix entropy sources with a simple but effective hash
89 let mixed = time_entropy
90 .wrapping_add(addr_entropy)
91 .wrapping_mul(0x9e3779b97f4a7c15);
92
93 // Convert to fraction in [0, 1)
94 let frac = (mixed as f64) / (u64::MAX as f64);
95
96 let span = (high - low).as_secs_f64();
97 low + Duration::from_secs_f64(span * frac)
98}