Skip to main content

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}