wreq 1.2.4 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +3 -7
  3. data/Cargo.toml +6 -1
  4. data/crates/wreq-util/.github/FUNDING.yml +15 -0
  5. data/crates/wreq-util/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  6. data/crates/wreq-util/.github/ISSUE_TEMPLATE/custom.md +10 -0
  7. data/crates/wreq-util/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  8. data/crates/wreq-util/.github/dependabot.yml +22 -0
  9. data/crates/wreq-util/.github/workflows/ci.yml +82 -0
  10. data/crates/wreq-util/.github/workflows/release-plz.yml +52 -0
  11. data/crates/wreq-util/.gitignore +24 -0
  12. data/crates/wreq-util/CHANGELOG.md +74 -0
  13. data/crates/wreq-util/Cargo.toml +94 -0
  14. data/crates/wreq-util/LICENSE +201 -0
  15. data/crates/wreq-util/README.md +62 -0
  16. data/crates/wreq-util/examples/emulate.rs +39 -0
  17. data/crates/wreq-util/examples/tower_delay.rs +191 -0
  18. data/crates/wreq-util/release-plz.toml +2 -0
  19. data/crates/wreq-util/rustfmt.toml +5 -0
  20. data/crates/wreq-util/src/emulate/compress.rs +85 -0
  21. data/crates/wreq-util/src/emulate/macros.rs +370 -0
  22. data/crates/wreq-util/src/emulate/profile/chrome/header.rs +59 -0
  23. data/crates/wreq-util/src/emulate/profile/chrome/http2.rs +65 -0
  24. data/crates/wreq-util/src/emulate/profile/chrome/tls.rs +153 -0
  25. data/crates/wreq-util/src/emulate/profile/chrome.rs +2028 -0
  26. data/crates/wreq-util/src/emulate/profile/firefox/header.rs +21 -0
  27. data/crates/wreq-util/src/emulate/profile/firefox/http2.rs +113 -0
  28. data/crates/wreq-util/src/emulate/profile/firefox/tls.rs +253 -0
  29. data/crates/wreq-util/src/emulate/profile/firefox.rs +518 -0
  30. data/crates/wreq-util/src/emulate/profile/okhttp.rs +241 -0
  31. data/crates/wreq-util/src/emulate/profile/opera/header.rs +30 -0
  32. data/crates/wreq-util/src/emulate/profile/opera/http2.rs +50 -0
  33. data/crates/wreq-util/src/emulate/profile/opera/tls.rs +97 -0
  34. data/crates/wreq-util/src/emulate/profile/opera.rs +299 -0
  35. data/crates/wreq-util/src/emulate/profile/safari/header.rs +59 -0
  36. data/crates/wreq-util/src/emulate/profile/safari/http2.rs +116 -0
  37. data/crates/wreq-util/src/emulate/profile/safari/tls.rs +177 -0
  38. data/crates/wreq-util/src/emulate/profile/safari.rs +222 -0
  39. data/crates/wreq-util/src/emulate/profile.rs +47 -0
  40. data/crates/wreq-util/src/emulate.rs +369 -0
  41. data/crates/wreq-util/src/lib.rs +14 -0
  42. data/crates/wreq-util/src/rand.rs +24 -0
  43. data/crates/wreq-util/src/tower/delay/future.rs +43 -0
  44. data/crates/wreq-util/src/tower/delay/layer.rs +201 -0
  45. data/crates/wreq-util/src/tower/delay/service.rs +190 -0
  46. data/crates/wreq-util/src/tower/delay.rs +98 -0
  47. data/crates/wreq-util/src/tower.rs +7 -0
  48. data/crates/wreq-util/tests/client.rs +51 -0
  49. data/crates/wreq-util/tests/emulate_chrome.rs +270 -0
  50. data/crates/wreq-util/tests/emulate_firefox.rs +92 -0
  51. data/crates/wreq-util/tests/emulate_okhttp.rs +46 -0
  52. data/crates/wreq-util/tests/emulate_opera.rs +113 -0
  53. data/crates/wreq-util/tests/emulate_safari.rs +151 -0
  54. data/crates/wreq-util/tests/support/mod.rs +55 -0
  55. data/crates/wreq-util/tests/support/server.rs +232 -0
  56. data/examples/emulate_request.rb +1 -1
  57. data/lib/wreq.rb +20 -18
  58. data/lib/wreq_ruby/body.rb +3 -0
  59. data/lib/wreq_ruby/client.rb +18 -18
  60. data/lib/wreq_ruby/cookie.rb +2 -2
  61. data/lib/wreq_ruby/emulate.rb +3 -1
  62. data/lib/wreq_ruby/header.rb +205 -114
  63. data/lib/wreq_ruby/response.rb +28 -3
  64. data/src/emulate.rs +1 -0
  65. data/src/error.rs +2 -6
  66. data/src/header/helper.rs +112 -0
  67. data/src/header.rs +198 -133
  68. data/test/header_test.rb +228 -192
  69. data/test/stream_test.rb +36 -0
  70. metadata +54 -1
@@ -0,0 +1,43 @@
1
+ use std::{
2
+ pin::Pin,
3
+ task::{Context, Poll, ready},
4
+ };
5
+
6
+ use pin_project_lite::pin_project;
7
+ use tokio::time::Sleep;
8
+ use tower::BoxError;
9
+
10
+ pin_project! {
11
+ /// Response future for [`Delay`].
12
+ ///
13
+ /// [`Delay`]: super::Delay
14
+ #[derive(Debug)]
15
+ pub struct ResponseFuture<S> {
16
+ #[pin]
17
+ response: S,
18
+ #[pin]
19
+ sleep: Sleep,
20
+ }
21
+ }
22
+
23
+ impl<S> ResponseFuture<S> {
24
+ // Create a new [`ResponseFuture`]
25
+ #[inline]
26
+ pub(crate) fn new(response: S, sleep: Sleep) -> Self {
27
+ ResponseFuture { response, sleep }
28
+ }
29
+ }
30
+
31
+ impl<F, S, E> Future for ResponseFuture<F>
32
+ where
33
+ F: Future<Output = Result<S, E>>,
34
+ E: Into<BoxError>,
35
+ {
36
+ type Output = Result<S, BoxError>;
37
+
38
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
39
+ let this = self.project();
40
+ ready!(this.sleep.poll(cx));
41
+ this.response.poll(cx).map_err(Into::into)
42
+ }
43
+ }
@@ -0,0 +1,201 @@
1
+ use std::time::Duration;
2
+
3
+ use tower::Layer;
4
+
5
+ use super::service::{Delay, DelayWith, JitterDelay, JitterDelayWith};
6
+
7
+ /// A Tower [`Layer`] that introduces a fixed delay before each request.
8
+ #[derive(Clone, Debug)]
9
+ pub struct DelayLayer {
10
+ delay: Duration,
11
+ }
12
+
13
+ /// Conditional delay [`Layer`], applies delay based on a predicate.
14
+ ///
15
+ /// Created via [`DelayLayer::when`].
16
+ #[derive(Clone, Debug)]
17
+ pub struct DelayLayerWith<P> {
18
+ delay: Duration,
19
+ predicate: P,
20
+ }
21
+
22
+ /// A Tower [`Layer`] that introduces a jittered delay before each request.
23
+ ///
24
+ /// The actual delay for each request will be randomly chosen within the range
25
+ /// `[base - base*pct, base + base*pct]`.
26
+ ///
27
+ /// # Example
28
+ ///
29
+ /// ```no_run
30
+ /// use std::time::Duration;
31
+ /// use wreq::Client;
32
+ /// use wreq_util::middleware::delay::JitterDelayLayer;
33
+ ///
34
+ /// // Creates delays in range [0.8s, 1.2s] (1s ± 20%)
35
+ /// let client = Client::builder()
36
+ /// .layer(JitterDelayLayer::new(Duration::from_secs(1), 0.2))
37
+ /// .build()?;
38
+ /// # Ok::<(), wreq::Error>(())
39
+ /// ```
40
+ #[derive(Clone, Debug)]
41
+ pub struct JitterDelayLayer {
42
+ base: Duration,
43
+ pct: f64,
44
+ }
45
+
46
+ /// Conditional jitter delay [`Layer`], applies delay based on a predicate.
47
+ ///
48
+ /// Created via [`JitterDelayLayer::when`].
49
+ #[derive(Clone, Debug)]
50
+ pub struct JitterDelayLayerWith<P> {
51
+ base: Duration,
52
+ pct: f64,
53
+ predicate: P,
54
+ }
55
+
56
+ // ===== impl DelayLayer =====
57
+
58
+ impl DelayLayer {
59
+ /// Create a new [`DelayLayer`] with the given delay duration.
60
+ #[inline]
61
+ pub const fn new(delay: Duration) -> Self {
62
+ DelayLayer { delay }
63
+ }
64
+
65
+ /// Apply delay only to requests that satisfy a predicate.
66
+ ///
67
+ /// Requests that don't match the predicate will pass through without delay.
68
+ ///
69
+ /// # Example
70
+ ///
71
+ /// ```ignore
72
+ /// use std::time::Duration;
73
+ /// use http::Request;
74
+ /// use wreq_util::middleware::delay::DelayLayer;
75
+ ///
76
+ /// // Only delay POST requests
77
+ /// let layer = DelayLayer::new(Duration::from_secs(1))
78
+ /// .when(|req: &Request<_>| req.method() == http::Method::POST);
79
+ /// ```
80
+ pub fn when<P, Req>(self, predicate: P) -> DelayLayerWith<P>
81
+ where
82
+ P: Fn(&Req) -> bool + Clone,
83
+ {
84
+ DelayLayerWith::new(self.delay, predicate)
85
+ }
86
+ }
87
+
88
+ impl<S> Layer<S> for DelayLayer {
89
+ type Service = Delay<S>;
90
+
91
+ #[inline]
92
+ fn layer(&self, service: S) -> Self::Service {
93
+ Delay::new(service, self.delay)
94
+ }
95
+ }
96
+
97
+ // ===== impl DelayLayerWith =====
98
+
99
+ impl<P> DelayLayerWith<P> {
100
+ /// Creates a new [`DelayLayerWith`].
101
+ #[inline]
102
+ pub fn new(delay: Duration, predicate: P) -> Self {
103
+ Self { delay, predicate }
104
+ }
105
+ }
106
+
107
+ impl<P, S> Layer<S> for DelayLayerWith<P>
108
+ where
109
+ P: Clone,
110
+ {
111
+ type Service = DelayWith<S, P>;
112
+
113
+ #[inline]
114
+ fn layer(&self, inner: S) -> Self::Service {
115
+ DelayWith::new(inner, self.delay, self.predicate.clone())
116
+ }
117
+ }
118
+
119
+ // ===== impl JitterDelayLayer =====
120
+
121
+ impl JitterDelayLayer {
122
+ /// Create a new [`JitterDelayLayer`] with the given base delay and jitter percentage.
123
+ ///
124
+ /// # Arguments
125
+ /// * `base` - The base delay duration
126
+ /// * `pct` - The jitter percentage (0.0 to 1.0), representing ±pct deviation
127
+ ///
128
+ /// # Example
129
+ ///
130
+ /// ```
131
+ /// use std::time::Duration;
132
+ /// use wreq_util::middleware::delay::JitterDelayLayer;
133
+ ///
134
+ /// // Creates delays in range [800ms, 1200ms]
135
+ /// let layer = JitterDelayLayer::new(Duration::from_secs(1), 0.2);
136
+ /// ```
137
+ #[inline]
138
+ pub fn new(base: Duration, pct: f64) -> Self {
139
+ Self {
140
+ base,
141
+ pct: pct.clamp(0.0, 1.0),
142
+ }
143
+ }
144
+
145
+ /// Apply jitter delay only to requests that satisfy a predicate.
146
+ ///
147
+ /// Requests that don't match the predicate will pass through without delay.
148
+ ///
149
+ /// # Example
150
+ ///
151
+ /// ```ignore
152
+ /// use std::time::Duration;
153
+ /// use http::Request;
154
+ /// use wreq_util::middleware::delay::JitterDelayLayer;
155
+ ///
156
+ /// // Only delay requests to paths starting with "/slow"
157
+ /// let layer = JitterDelayLayer::new(Duration::from_secs(1), 0.2)
158
+ /// .when(|req: &Request<_>| req.uri().path().starts_with("/slow"));
159
+ /// ```
160
+ pub fn when<P, Req>(self, predicate: P) -> JitterDelayLayerWith<P>
161
+ where
162
+ P: Fn(&Req) -> bool + Clone,
163
+ {
164
+ JitterDelayLayerWith::new(self.base, self.pct, predicate)
165
+ }
166
+ }
167
+
168
+ impl<S> Layer<S> for JitterDelayLayer {
169
+ type Service = JitterDelay<S>;
170
+
171
+ #[inline]
172
+ fn layer(&self, inner: S) -> Self::Service {
173
+ JitterDelay::new(inner, self.base, self.pct)
174
+ }
175
+ }
176
+
177
+ // ===== impl JitterDelayLayerWith =====
178
+
179
+ impl<P> JitterDelayLayerWith<P> {
180
+ /// Creates a new [`JitterDelayLayerWith`].
181
+ #[inline]
182
+ pub fn new(base: Duration, pct: f64, predicate: P) -> Self {
183
+ Self {
184
+ base,
185
+ pct: pct.clamp(0.0, 1.0),
186
+ predicate,
187
+ }
188
+ }
189
+ }
190
+
191
+ impl<P, S> Layer<S> for JitterDelayLayerWith<P>
192
+ where
193
+ P: Clone,
194
+ {
195
+ type Service = JitterDelayWith<S, P>;
196
+
197
+ #[inline]
198
+ fn layer(&self, inner: S) -> Self::Service {
199
+ JitterDelayWith::new(inner, self.base, self.pct, self.predicate.clone())
200
+ }
201
+ }
@@ -0,0 +1,190 @@
1
+ use std::{
2
+ task::{Context, Poll},
3
+ time::Duration,
4
+ };
5
+
6
+ use tower::{BoxError, Service};
7
+
8
+ use super::{future::ResponseFuture, jittered_duration};
9
+
10
+ /// A Tower [`Service`] that introduces a fixed delay before each request.
11
+ #[derive(Debug, Clone)]
12
+ pub struct Delay<S> {
13
+ inner: S,
14
+ delay: Duration,
15
+ }
16
+
17
+ /// A Tower [`Service`] that conditionally applies fixed delay based on a predicate.
18
+ ///
19
+ /// Requests that match the predicate will have the delay applied;
20
+ /// other requests pass through immediately.
21
+ #[derive(Clone, Debug)]
22
+ pub struct DelayWith<S, P> {
23
+ inner: Delay<S>,
24
+ predicate: P,
25
+ }
26
+
27
+ /// A Tower [`Service`] that applies jittered delay to requests.
28
+ ///
29
+ /// This service wraps an inner service and introduces a random delay
30
+ /// (within a configured range) before each request.
31
+ #[derive(Clone, Debug)]
32
+ pub struct JitterDelay<S> {
33
+ inner: S,
34
+ base: Duration,
35
+ pct: f64,
36
+ }
37
+
38
+ /// A Tower [`Service`] that conditionally applies jittered delay based on a predicate.
39
+ ///
40
+ /// Requests that match the predicate will have a jittered delay applied;
41
+ /// other requests pass through immediately.
42
+ #[derive(Clone, Debug)]
43
+ pub struct JitterDelayWith<S, P> {
44
+ inner: JitterDelay<S>,
45
+ predicate: P,
46
+ }
47
+
48
+ // ===== impl Delay =====
49
+
50
+ impl<S> Delay<S> {
51
+ /// Create a new [`Delay`] service wrapping the given inner service
52
+ #[inline]
53
+ pub fn new(inner: S, delay: Duration) -> Self {
54
+ Delay { inner, delay }
55
+ }
56
+ }
57
+
58
+ impl<S, Request> Service<Request> for Delay<S>
59
+ where
60
+ S: Service<Request>,
61
+ S::Error: Into<BoxError>,
62
+ {
63
+ type Response = S::Response;
64
+ type Error = BoxError;
65
+ type Future = ResponseFuture<S::Future>;
66
+
67
+ #[inline]
68
+ fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
69
+ self.inner.poll_ready(cx).map_err(Into::into)
70
+ }
71
+
72
+ fn call(&mut self, req: Request) -> Self::Future {
73
+ let response = self.inner.call(req);
74
+ let sleep = tokio::time::sleep(self.delay);
75
+ ResponseFuture::new(response, sleep)
76
+ }
77
+ }
78
+
79
+ // ===== impl DelayWith =====
80
+
81
+ impl<S, P> DelayWith<S, P> {
82
+ /// Creates a new [`DelayWith`].
83
+ #[inline]
84
+ pub fn new(inner: S, delay: Duration, predicate: P) -> Self {
85
+ Self {
86
+ inner: Delay::new(inner, delay),
87
+ predicate,
88
+ }
89
+ }
90
+ }
91
+
92
+ impl<S, Req, P> Service<Req> for DelayWith<S, P>
93
+ where
94
+ S: Service<Req>,
95
+ S::Error: Into<BoxError>,
96
+ P: Fn(&Req) -> bool,
97
+ {
98
+ type Response = S::Response;
99
+ type Error = BoxError;
100
+ type Future = ResponseFuture<S::Future>;
101
+
102
+ #[inline]
103
+ fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
104
+ self.inner.poll_ready(cx).map_err(Into::into)
105
+ }
106
+
107
+ fn call(&mut self, req: Req) -> Self::Future {
108
+ if !(self.predicate)(&req) {
109
+ self.inner.delay = Duration::ZERO;
110
+ }
111
+ self.inner.call(req)
112
+ }
113
+ }
114
+
115
+ // ===== impl JitterDelay =====
116
+
117
+ impl<S> JitterDelay<S> {
118
+ /// Creates a new [`JitterDelay`].
119
+ #[inline]
120
+ pub fn new(inner: S, base: Duration, pct: f64) -> Self {
121
+ Self {
122
+ inner,
123
+ base,
124
+ pct: pct.clamp(0.0, 1.0),
125
+ }
126
+ }
127
+ }
128
+
129
+ impl<S, Req> Service<Req> for JitterDelay<S>
130
+ where
131
+ S: Service<Req>,
132
+ S::Error: Into<BoxError>,
133
+ {
134
+ type Response = S::Response;
135
+ type Error = BoxError;
136
+ type Future = ResponseFuture<S::Future>;
137
+
138
+ #[inline]
139
+ fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
140
+ self.inner.poll_ready(cx).map_err(Into::into)
141
+ }
142
+
143
+ fn call(&mut self, req: Req) -> Self::Future {
144
+ let delay = jittered_duration(self.base, self.pct);
145
+ let sleep = tokio::time::sleep(delay);
146
+ let fut = self.inner.call(req);
147
+ ResponseFuture::new(fut, sleep)
148
+ }
149
+ }
150
+
151
+ // ===== impl JitterDelayWith =====
152
+
153
+ impl<S, P> JitterDelayWith<S, P> {
154
+ /// Creates a new [`JitterDelayWith`].
155
+ #[inline]
156
+ pub fn new(inner: S, base: Duration, pct: f64, predicate: P) -> Self {
157
+ Self {
158
+ inner: JitterDelay::new(inner, base, pct),
159
+ predicate,
160
+ }
161
+ }
162
+ }
163
+
164
+ impl<S, Req, P> Service<Req> for JitterDelayWith<S, P>
165
+ where
166
+ S: Service<Req>,
167
+ S::Error: Into<BoxError>,
168
+ P: Fn(&Req) -> bool,
169
+ {
170
+ type Response = S::Response;
171
+ type Error = BoxError;
172
+ type Future = ResponseFuture<S::Future>;
173
+
174
+ #[inline]
175
+ fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
176
+ self.inner.poll_ready(cx).map_err(Into::into)
177
+ }
178
+
179
+ fn call(&mut self, req: Req) -> Self::Future {
180
+ let delay = if (self.predicate)(&req) {
181
+ jittered_duration(self.inner.base, self.inner.pct)
182
+ } else {
183
+ Duration::ZERO
184
+ };
185
+
186
+ let sleep = tokio::time::sleep(delay);
187
+ let fut = self.inner.inner.call(req);
188
+ ResponseFuture::new(fut, sleep)
189
+ }
190
+ }
@@ -0,0 +1,98 @@
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
+
54
+ mod future;
55
+ mod layer;
56
+ mod service;
57
+
58
+ use std::time::Duration;
59
+
60
+ pub 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)]`.
67
+ fn 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
+ }
@@ -0,0 +1,7 @@
1
+ //! Tower middleware helpers for `wreq`.
2
+ //!
3
+ //! Modules here are feature-gated. Enable the feature to make the tower middleware
4
+ //! available and to include its docs.
5
+
6
+ #[cfg(feature = "tower-delay")]
7
+ pub mod delay;
@@ -0,0 +1,51 @@
1
+ #![cfg(not(target_arch = "wasm32"))]
2
+ mod support;
3
+
4
+ use support::server;
5
+ use wreq::Client;
6
+ use wreq_util::{Emulation, Platform};
7
+
8
+ #[tokio::test]
9
+ async fn test_client_emulation_device() {
10
+ let server = server::http(move |req| async move {
11
+ for (name, value) in req.headers() {
12
+ if name == "user-agent" {
13
+ assert_eq!(
14
+ value,
15
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
16
+ );
17
+ }
18
+ if name == "sec-ch-ua" {
19
+ assert_eq!(
20
+ value,
21
+ r#""Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133""#
22
+ );
23
+ }
24
+ if name == "sec-ch-ua-mobile" {
25
+ assert_eq!(value, "?0");
26
+ }
27
+ if name == "sec-ch-ua-platform" {
28
+ assert_eq!(value, "\"Linux\"");
29
+ }
30
+ }
31
+ http::Response::default()
32
+ });
33
+
34
+ let url = format!("http://{}/ua", server.addr());
35
+ let res = Client::builder()
36
+ .emulation(
37
+ Emulation::builder()
38
+ .profile(Emulation::Chrome133)
39
+ .platform(Platform::Linux)
40
+ .http2(true)
41
+ .build(),
42
+ )
43
+ .build()
44
+ .expect("Unable to build client")
45
+ .get(&url)
46
+ .send()
47
+ .await
48
+ .expect("request");
49
+
50
+ assert_eq!(res.status(), wreq::StatusCode::OK);
51
+ }