wreq-rb 0.3.0

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 (167) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.lock +2688 -0
  3. data/Cargo.toml +6 -0
  4. data/README.md +179 -0
  5. data/ext/wreq_rb/Cargo.toml +39 -0
  6. data/ext/wreq_rb/extconf.rb +22 -0
  7. data/ext/wreq_rb/src/client.rs +565 -0
  8. data/ext/wreq_rb/src/error.rs +25 -0
  9. data/ext/wreq_rb/src/lib.rs +20 -0
  10. data/ext/wreq_rb/src/response.rs +132 -0
  11. data/lib/wreq-rb/version.rb +5 -0
  12. data/lib/wreq-rb.rb +17 -0
  13. data/patches/0001-add-transfer-size-tracking.patch +292 -0
  14. data/vendor/wreq/Cargo.toml +306 -0
  15. data/vendor/wreq/LICENSE +202 -0
  16. data/vendor/wreq/README.md +122 -0
  17. data/vendor/wreq/examples/cert_store.rs +77 -0
  18. data/vendor/wreq/examples/connect_via_lower_priority_tokio_runtime.rs +258 -0
  19. data/vendor/wreq/examples/emulation.rs +118 -0
  20. data/vendor/wreq/examples/form.rs +14 -0
  21. data/vendor/wreq/examples/http1_websocket.rs +37 -0
  22. data/vendor/wreq/examples/http2_websocket.rs +45 -0
  23. data/vendor/wreq/examples/json_dynamic.rs +41 -0
  24. data/vendor/wreq/examples/json_typed.rs +47 -0
  25. data/vendor/wreq/examples/keylog.rs +16 -0
  26. data/vendor/wreq/examples/request_with_emulation.rs +115 -0
  27. data/vendor/wreq/examples/request_with_interface.rs +37 -0
  28. data/vendor/wreq/examples/request_with_local_address.rs +16 -0
  29. data/vendor/wreq/examples/request_with_proxy.rs +13 -0
  30. data/vendor/wreq/examples/request_with_redirect.rs +22 -0
  31. data/vendor/wreq/examples/request_with_version.rs +15 -0
  32. data/vendor/wreq/examples/tor_socks.rs +24 -0
  33. data/vendor/wreq/examples/unix_socket.rs +33 -0
  34. data/vendor/wreq/src/client/body.rs +304 -0
  35. data/vendor/wreq/src/client/conn/conn.rs +231 -0
  36. data/vendor/wreq/src/client/conn/connector.rs +549 -0
  37. data/vendor/wreq/src/client/conn/http.rs +1023 -0
  38. data/vendor/wreq/src/client/conn/proxy/socks.rs +233 -0
  39. data/vendor/wreq/src/client/conn/proxy/tunnel.rs +260 -0
  40. data/vendor/wreq/src/client/conn/proxy.rs +39 -0
  41. data/vendor/wreq/src/client/conn/tls_info.rs +98 -0
  42. data/vendor/wreq/src/client/conn/uds.rs +44 -0
  43. data/vendor/wreq/src/client/conn/verbose.rs +149 -0
  44. data/vendor/wreq/src/client/conn.rs +323 -0
  45. data/vendor/wreq/src/client/core/body/incoming.rs +485 -0
  46. data/vendor/wreq/src/client/core/body/length.rs +118 -0
  47. data/vendor/wreq/src/client/core/body.rs +34 -0
  48. data/vendor/wreq/src/client/core/common/buf.rs +149 -0
  49. data/vendor/wreq/src/client/core/common/rewind.rs +141 -0
  50. data/vendor/wreq/src/client/core/common/watch.rs +76 -0
  51. data/vendor/wreq/src/client/core/common.rs +3 -0
  52. data/vendor/wreq/src/client/core/conn/http1.rs +342 -0
  53. data/vendor/wreq/src/client/core/conn/http2.rs +307 -0
  54. data/vendor/wreq/src/client/core/conn.rs +11 -0
  55. data/vendor/wreq/src/client/core/dispatch.rs +299 -0
  56. data/vendor/wreq/src/client/core/error.rs +435 -0
  57. data/vendor/wreq/src/client/core/ext.rs +201 -0
  58. data/vendor/wreq/src/client/core/http1.rs +178 -0
  59. data/vendor/wreq/src/client/core/http2.rs +483 -0
  60. data/vendor/wreq/src/client/core/proto/h1/conn.rs +988 -0
  61. data/vendor/wreq/src/client/core/proto/h1/decode.rs +1170 -0
  62. data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +684 -0
  63. data/vendor/wreq/src/client/core/proto/h1/encode.rs +580 -0
  64. data/vendor/wreq/src/client/core/proto/h1/io.rs +879 -0
  65. data/vendor/wreq/src/client/core/proto/h1/role.rs +694 -0
  66. data/vendor/wreq/src/client/core/proto/h1.rs +104 -0
  67. data/vendor/wreq/src/client/core/proto/h2/client.rs +650 -0
  68. data/vendor/wreq/src/client/core/proto/h2/ping.rs +539 -0
  69. data/vendor/wreq/src/client/core/proto/h2.rs +379 -0
  70. data/vendor/wreq/src/client/core/proto/headers.rs +138 -0
  71. data/vendor/wreq/src/client/core/proto.rs +58 -0
  72. data/vendor/wreq/src/client/core/rt/bounds.rs +57 -0
  73. data/vendor/wreq/src/client/core/rt/timer.rs +150 -0
  74. data/vendor/wreq/src/client/core/rt/tokio.rs +99 -0
  75. data/vendor/wreq/src/client/core/rt.rs +25 -0
  76. data/vendor/wreq/src/client/core/upgrade.rs +267 -0
  77. data/vendor/wreq/src/client/core.rs +16 -0
  78. data/vendor/wreq/src/client/emulation.rs +161 -0
  79. data/vendor/wreq/src/client/http/client/error.rs +142 -0
  80. data/vendor/wreq/src/client/http/client/exec.rs +29 -0
  81. data/vendor/wreq/src/client/http/client/extra.rs +77 -0
  82. data/vendor/wreq/src/client/http/client/lazy.rs +79 -0
  83. data/vendor/wreq/src/client/http/client/pool.rs +1105 -0
  84. data/vendor/wreq/src/client/http/client/util.rs +104 -0
  85. data/vendor/wreq/src/client/http/client.rs +1003 -0
  86. data/vendor/wreq/src/client/http/future.rs +99 -0
  87. data/vendor/wreq/src/client/http.rs +1629 -0
  88. data/vendor/wreq/src/client/layer/config/options.rs +156 -0
  89. data/vendor/wreq/src/client/layer/config.rs +116 -0
  90. data/vendor/wreq/src/client/layer/cookie.rs +161 -0
  91. data/vendor/wreq/src/client/layer/decoder.rs +139 -0
  92. data/vendor/wreq/src/client/layer/redirect/future.rs +270 -0
  93. data/vendor/wreq/src/client/layer/redirect/policy.rs +63 -0
  94. data/vendor/wreq/src/client/layer/redirect.rs +145 -0
  95. data/vendor/wreq/src/client/layer/retry/classify.rs +105 -0
  96. data/vendor/wreq/src/client/layer/retry/scope.rs +51 -0
  97. data/vendor/wreq/src/client/layer/retry.rs +151 -0
  98. data/vendor/wreq/src/client/layer/timeout/body.rs +233 -0
  99. data/vendor/wreq/src/client/layer/timeout/future.rs +90 -0
  100. data/vendor/wreq/src/client/layer/timeout.rs +177 -0
  101. data/vendor/wreq/src/client/layer.rs +15 -0
  102. data/vendor/wreq/src/client/multipart.rs +717 -0
  103. data/vendor/wreq/src/client/request.rs +818 -0
  104. data/vendor/wreq/src/client/response.rs +534 -0
  105. data/vendor/wreq/src/client/ws/json.rs +99 -0
  106. data/vendor/wreq/src/client/ws/message.rs +453 -0
  107. data/vendor/wreq/src/client/ws.rs +714 -0
  108. data/vendor/wreq/src/client.rs +27 -0
  109. data/vendor/wreq/src/config.rs +140 -0
  110. data/vendor/wreq/src/cookie.rs +579 -0
  111. data/vendor/wreq/src/dns/gai.rs +249 -0
  112. data/vendor/wreq/src/dns/hickory.rs +78 -0
  113. data/vendor/wreq/src/dns/resolve.rs +180 -0
  114. data/vendor/wreq/src/dns.rs +69 -0
  115. data/vendor/wreq/src/error.rs +502 -0
  116. data/vendor/wreq/src/ext.rs +398 -0
  117. data/vendor/wreq/src/hash.rs +143 -0
  118. data/vendor/wreq/src/header.rs +506 -0
  119. data/vendor/wreq/src/into_uri.rs +187 -0
  120. data/vendor/wreq/src/lib.rs +586 -0
  121. data/vendor/wreq/src/proxy/mac.rs +82 -0
  122. data/vendor/wreq/src/proxy/matcher.rs +806 -0
  123. data/vendor/wreq/src/proxy/uds.rs +66 -0
  124. data/vendor/wreq/src/proxy/win.rs +31 -0
  125. data/vendor/wreq/src/proxy.rs +569 -0
  126. data/vendor/wreq/src/redirect.rs +575 -0
  127. data/vendor/wreq/src/retry.rs +198 -0
  128. data/vendor/wreq/src/sync.rs +129 -0
  129. data/vendor/wreq/src/tls/conn/cache.rs +123 -0
  130. data/vendor/wreq/src/tls/conn/cert_compression.rs +125 -0
  131. data/vendor/wreq/src/tls/conn/ext.rs +82 -0
  132. data/vendor/wreq/src/tls/conn/macros.rs +34 -0
  133. data/vendor/wreq/src/tls/conn/service.rs +138 -0
  134. data/vendor/wreq/src/tls/conn.rs +681 -0
  135. data/vendor/wreq/src/tls/keylog/handle.rs +64 -0
  136. data/vendor/wreq/src/tls/keylog.rs +99 -0
  137. data/vendor/wreq/src/tls/options.rs +464 -0
  138. data/vendor/wreq/src/tls/x509/identity.rs +122 -0
  139. data/vendor/wreq/src/tls/x509/parser.rs +71 -0
  140. data/vendor/wreq/src/tls/x509/store.rs +228 -0
  141. data/vendor/wreq/src/tls/x509.rs +68 -0
  142. data/vendor/wreq/src/tls.rs +154 -0
  143. data/vendor/wreq/src/trace.rs +55 -0
  144. data/vendor/wreq/src/util.rs +122 -0
  145. data/vendor/wreq/tests/badssl.rs +228 -0
  146. data/vendor/wreq/tests/brotli.rs +350 -0
  147. data/vendor/wreq/tests/client.rs +1098 -0
  148. data/vendor/wreq/tests/connector_layers.rs +227 -0
  149. data/vendor/wreq/tests/cookie.rs +306 -0
  150. data/vendor/wreq/tests/deflate.rs +347 -0
  151. data/vendor/wreq/tests/emulation.rs +260 -0
  152. data/vendor/wreq/tests/gzip.rs +347 -0
  153. data/vendor/wreq/tests/layers.rs +261 -0
  154. data/vendor/wreq/tests/multipart.rs +165 -0
  155. data/vendor/wreq/tests/proxy.rs +438 -0
  156. data/vendor/wreq/tests/redirect.rs +629 -0
  157. data/vendor/wreq/tests/retry.rs +135 -0
  158. data/vendor/wreq/tests/support/delay_server.rs +117 -0
  159. data/vendor/wreq/tests/support/error.rs +16 -0
  160. data/vendor/wreq/tests/support/layer.rs +183 -0
  161. data/vendor/wreq/tests/support/mod.rs +9 -0
  162. data/vendor/wreq/tests/support/server.rs +232 -0
  163. data/vendor/wreq/tests/timeouts.rs +281 -0
  164. data/vendor/wreq/tests/unix_socket.rs +135 -0
  165. data/vendor/wreq/tests/upgrade.rs +98 -0
  166. data/vendor/wreq/tests/zstd.rs +559 -0
  167. metadata +225 -0
@@ -0,0 +1,151 @@
1
+ //! Middleware for retrying requests.
2
+
3
+ mod classify;
4
+ mod scope;
5
+
6
+ use std::{error::Error as StdError, future::Ready, sync::Arc, time::Duration};
7
+
8
+ use http::{Request, Response};
9
+ use tower::retry::{
10
+ Policy,
11
+ budget::{Budget, TpsBudget},
12
+ };
13
+
14
+ pub(crate) use self::{
15
+ classify::{Action, Classifier, ClassifyFn, ReqRep},
16
+ scope::{ScopeFn, Scoped},
17
+ };
18
+ use super::super::core::body::Incoming;
19
+ use crate::{Body, error::BoxError, retry};
20
+
21
+ /// A retry policy for HTTP requests.
22
+ #[derive(Clone)]
23
+ pub struct RetryPolicy {
24
+ budget: Option<Arc<TpsBudget>>,
25
+ classifier: Classifier,
26
+ max_retries_per_request: u32,
27
+ retry_cnt: u32,
28
+ scope: Scoped,
29
+ }
30
+
31
+ impl RetryPolicy {
32
+ /// Create a new `RetryPolicy`.
33
+ #[inline]
34
+ pub fn new(policy: retry::Policy) -> Self {
35
+ Self {
36
+ budget: policy
37
+ .budget
38
+ .map(|budget| Arc::new(TpsBudget::new(Duration::from_secs(10), 10, budget))),
39
+ classifier: policy.classifier,
40
+ max_retries_per_request: policy.max_retries_per_request,
41
+ retry_cnt: 0,
42
+ scope: policy.scope,
43
+ }
44
+ }
45
+ }
46
+
47
+ type Req = Request<Body>;
48
+
49
+ type Res = Response<Incoming>;
50
+
51
+ impl Policy<Req, Res, BoxError> for RetryPolicy {
52
+ type Future = Ready<()>;
53
+
54
+ fn retry(&mut self, req: &mut Req, result: &mut Result<Res, BoxError>) -> Option<Self::Future> {
55
+ match self.classifier.classify(req, result) {
56
+ Action::Success => {
57
+ trace!(
58
+ "Request successful, no retry needed: {} {}",
59
+ req.method(),
60
+ req.uri()
61
+ );
62
+
63
+ if let Some(ref budget) = self.budget {
64
+ budget.deposit();
65
+ trace!("Token deposited back to retry budget");
66
+ }
67
+ None
68
+ }
69
+ Action::Retryable => {
70
+ if self.budget.as_ref().map(|b| b.withdraw()).unwrap_or(true) {
71
+ self.retry_cnt += 1;
72
+
73
+ trace!(
74
+ "Retrying request ({}/{} attempts): {} {} - {}",
75
+ self.retry_cnt,
76
+ self.max_retries_per_request,
77
+ req.method(),
78
+ req.uri(),
79
+ match result {
80
+ Ok(res) => format!("HTTP {}", res.status()),
81
+ Err(e) => format!("Error: {}", e),
82
+ }
83
+ );
84
+
85
+ Some(std::future::ready(()))
86
+ } else {
87
+ debug!(
88
+ "Request is retryable but retry budget exhausted: {} {}",
89
+ req.method(),
90
+ req.uri()
91
+ );
92
+ None
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ fn clone_request(&mut self, req: &Req) -> Option<Req> {
99
+ if self.retry_cnt > 0 && !self.scope.applies_to(req) {
100
+ trace!("not in scope, not retrying");
101
+ return None;
102
+ }
103
+
104
+ if self.retry_cnt >= self.max_retries_per_request {
105
+ trace!("max_retries_per_request hit");
106
+ return None;
107
+ }
108
+
109
+ let body = req.body().try_clone()?;
110
+ let mut new = http::Request::new(body);
111
+ *new.method_mut() = req.method().clone();
112
+ *new.uri_mut() = req.uri().clone();
113
+ *new.version_mut() = req.version();
114
+ *new.headers_mut() = req.headers().clone();
115
+ *new.extensions_mut() = req.extensions().clone();
116
+
117
+ Some(new)
118
+ }
119
+ }
120
+
121
+ /// Determines whether the given error is considered retryable for HTTP/2 requests.
122
+ ///
123
+ /// Returns `true` if the error type or content indicates that the request can be retried,
124
+ /// otherwise returns `false`.
125
+ fn is_retryable_error(err: &(dyn StdError + 'static)) -> bool {
126
+ let err = if let Some(err) = err.source() {
127
+ err
128
+ } else {
129
+ return false;
130
+ };
131
+
132
+ if let Some(cause) = err.source() {
133
+ if let Some(err) = cause.downcast_ref::<http2::Error>() {
134
+ // They sent us a graceful shutdown, try with a new connection!
135
+ if err.is_go_away() && err.is_remote() && err.reason() == Some(http2::Reason::NO_ERROR)
136
+ {
137
+ return true;
138
+ }
139
+
140
+ // REFUSED_STREAM was sent from the server, which is safe to retry.
141
+ // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.7-3.2
142
+ if err.is_reset()
143
+ && err.is_remote()
144
+ && err.reason() == Some(http2::Reason::REFUSED_STREAM)
145
+ {
146
+ return true;
147
+ }
148
+ }
149
+ }
150
+ false
151
+ }
@@ -0,0 +1,233 @@
1
+ use std::{
2
+ future::Future,
3
+ pin::Pin,
4
+ task::{Context, Poll, ready},
5
+ time::Duration,
6
+ };
7
+
8
+ use http_body::Body;
9
+ use pin_project_lite::pin_project;
10
+ use tokio::time::{Sleep, sleep};
11
+
12
+ use crate::{
13
+ Error,
14
+ error::{BoxError, TimedOut},
15
+ };
16
+
17
+ pin_project! {
18
+ /// A wrapper body that applies timeout strategies to an inner HTTP body.
19
+ #[project = TimeoutBodyProj]
20
+ pub enum TimeoutBody<B> {
21
+ Plain {
22
+ #[pin]
23
+ body: B,
24
+ },
25
+ TotalTimeout {
26
+ #[pin]
27
+ body: TotalTimeoutBody<B>,
28
+ },
29
+ ReadTimeout {
30
+ #[pin]
31
+ body: ReadTimeoutBody<B>
32
+ },
33
+ CombinedTimeout {
34
+ #[pin]
35
+ body: TotalTimeoutBody<ReadTimeoutBody<B>>,
36
+ }
37
+ }
38
+ }
39
+
40
+ pin_project! {
41
+ /// A body wrapper that enforces a total timeout for the entire stream.
42
+ ///
43
+ /// The timeout applies to the whole body: if the deadline is reached before
44
+ /// the body is fully read, an error is returned. The timer does **not** reset
45
+ /// between chunks.
46
+ pub struct TotalTimeoutBody<B> {
47
+ #[pin]
48
+ body: B,
49
+ timeout: Pin<Box<Sleep>>,
50
+ }
51
+ }
52
+
53
+ pin_project! {
54
+ /// A body wrapper that enforces a timeout for each read operation.
55
+ ///
56
+ /// The timeout resets after every successful read. If a single read
57
+ /// takes longer than the specified duration, an error is returned.
58
+ pub struct ReadTimeoutBody<B> {
59
+ timeout: Duration,
60
+ #[pin]
61
+ sleep: Option<Sleep>,
62
+ #[pin]
63
+ body: B,
64
+ }
65
+ }
66
+
67
+ /// ==== impl TimeoutBody ====
68
+ impl<B> TimeoutBody<B> {
69
+ /// Creates a new [`TimeoutBody`] with no timeout.
70
+ pub fn new(deadline: Option<Duration>, read_timeout: Option<Duration>, body: B) -> Self {
71
+ let deadline = deadline.map(sleep).map(Box::pin);
72
+ match (deadline, read_timeout) {
73
+ (Some(total_timeout), Some(read_timeout)) => TimeoutBody::CombinedTimeout {
74
+ body: TotalTimeoutBody {
75
+ timeout: total_timeout,
76
+ body: ReadTimeoutBody {
77
+ timeout: read_timeout,
78
+ sleep: None,
79
+ body,
80
+ },
81
+ },
82
+ },
83
+ (Some(timeout), None) => TimeoutBody::TotalTimeout {
84
+ body: TotalTimeoutBody { body, timeout },
85
+ },
86
+ (None, Some(timeout)) => TimeoutBody::ReadTimeout {
87
+ body: ReadTimeoutBody {
88
+ timeout,
89
+ sleep: None,
90
+ body,
91
+ },
92
+ },
93
+ (None, None) => TimeoutBody::Plain { body },
94
+ }
95
+ }
96
+ }
97
+
98
+ impl<B> Body for TimeoutBody<B>
99
+ where
100
+ B: Body,
101
+ B::Error: Into<BoxError>,
102
+ {
103
+ type Data = B::Data;
104
+ type Error = BoxError;
105
+
106
+ #[inline(always)]
107
+ fn poll_frame(
108
+ self: Pin<&mut Self>,
109
+ cx: &mut Context<'_>,
110
+ ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
111
+ match self.project() {
112
+ TimeoutBodyProj::TotalTimeout { body } => body.poll_frame(cx),
113
+ TimeoutBodyProj::ReadTimeout { body } => body.poll_frame(cx),
114
+ TimeoutBodyProj::CombinedTimeout { body } => body.poll_frame(cx),
115
+ TimeoutBodyProj::Plain { body } => poll_and_map_body(body, cx),
116
+ }
117
+ }
118
+
119
+ #[inline(always)]
120
+ fn size_hint(&self) -> http_body::SizeHint {
121
+ match self {
122
+ TimeoutBody::TotalTimeout { body } => body.size_hint(),
123
+ TimeoutBody::ReadTimeout { body } => body.size_hint(),
124
+ TimeoutBody::CombinedTimeout { body } => body.size_hint(),
125
+ TimeoutBody::Plain { body } => body.size_hint(),
126
+ }
127
+ }
128
+
129
+ #[inline(always)]
130
+ fn is_end_stream(&self) -> bool {
131
+ match self {
132
+ TimeoutBody::TotalTimeout { body } => body.is_end_stream(),
133
+ TimeoutBody::ReadTimeout { body } => body.is_end_stream(),
134
+ TimeoutBody::CombinedTimeout { body } => body.is_end_stream(),
135
+ TimeoutBody::Plain { body } => body.is_end_stream(),
136
+ }
137
+ }
138
+ }
139
+
140
+ #[inline(always)]
141
+ fn poll_and_map_body<B>(
142
+ body: Pin<&mut B>,
143
+ cx: &mut Context<'_>,
144
+ ) -> Poll<Option<Result<http_body::Frame<B::Data>, BoxError>>>
145
+ where
146
+ B: Body,
147
+ B::Error: Into<BoxError>,
148
+ {
149
+ Poll::Ready(
150
+ ready!(body.poll_frame(cx)).map(|opt| opt.map_err(Error::decode).map_err(Into::into)),
151
+ )
152
+ }
153
+
154
+ // ==== impl TotalTimeoutBody ====
155
+ impl<B> Body for TotalTimeoutBody<B>
156
+ where
157
+ B: Body,
158
+ B::Error: Into<BoxError>,
159
+ {
160
+ type Data = B::Data;
161
+ type Error = BoxError;
162
+
163
+ fn poll_frame(
164
+ self: Pin<&mut Self>,
165
+ cx: &mut Context,
166
+ ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
167
+ let this = self.project();
168
+ if let Poll::Ready(()) = this.timeout.as_mut().poll(cx) {
169
+ return Poll::Ready(Some(Err(Error::body(TimedOut).into())));
170
+ }
171
+ poll_and_map_body(this.body, cx)
172
+ }
173
+
174
+ #[inline(always)]
175
+ fn size_hint(&self) -> http_body::SizeHint {
176
+ self.body.size_hint()
177
+ }
178
+
179
+ #[inline(always)]
180
+ fn is_end_stream(&self) -> bool {
181
+ self.body.is_end_stream()
182
+ }
183
+ }
184
+
185
+ /// ==== impl ReadTimeoutBody ====
186
+ impl<B> Body for ReadTimeoutBody<B>
187
+ where
188
+ B: Body,
189
+ B::Error: Into<BoxError>,
190
+ {
191
+ type Data = B::Data;
192
+ type Error = BoxError;
193
+
194
+ fn poll_frame(
195
+ self: Pin<&mut Self>,
196
+ cx: &mut Context<'_>,
197
+ ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
198
+ let mut this = self.project();
199
+
200
+ // Error if the timeout has expired.
201
+ if this.sleep.is_none() {
202
+ this.sleep.set(Some(sleep(*this.timeout)));
203
+ }
204
+
205
+ // Error if the timeout has expired.
206
+ if let Some(sleep) = this.sleep.as_mut().as_pin_mut() {
207
+ if sleep.poll(cx).is_ready() {
208
+ return Poll::Ready(Some(Err(Box::new(TimedOut))));
209
+ }
210
+ }
211
+
212
+ // Poll the actual body
213
+ match ready!(this.body.poll_frame(cx)) {
214
+ Some(Ok(frame)) => {
215
+ // Reset timeout on successful read
216
+ this.sleep.set(None);
217
+ Poll::Ready(Some(Ok(frame)))
218
+ }
219
+ Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
220
+ None => Poll::Ready(None),
221
+ }
222
+ }
223
+
224
+ #[inline(always)]
225
+ fn size_hint(&self) -> http_body::SizeHint {
226
+ self.body.size_hint()
227
+ }
228
+
229
+ #[inline(always)]
230
+ fn is_end_stream(&self) -> bool {
231
+ self.body.is_end_stream()
232
+ }
233
+ }
@@ -0,0 +1,90 @@
1
+ use std::{
2
+ future::Future,
3
+ pin::Pin,
4
+ task::{Context, Poll, ready},
5
+ time::Duration,
6
+ };
7
+
8
+ use http::Response;
9
+ use pin_project_lite::pin_project;
10
+ use tokio::time::Sleep;
11
+
12
+ use super::body::TimeoutBody;
13
+ use crate::error::{BoxError, Error, TimedOut};
14
+
15
+ pin_project! {
16
+ /// [`Timeout`] response future
17
+ pub struct ResponseFuture<F> {
18
+ #[pin]
19
+ pub(crate) response: F,
20
+ #[pin]
21
+ pub(crate) total_timeout: Option<Sleep>,
22
+ #[pin]
23
+ pub(crate) read_timeout: Option<Sleep>,
24
+ }
25
+ }
26
+
27
+ impl<F, T, E> Future for ResponseFuture<F>
28
+ where
29
+ F: Future<Output = Result<T, E>>,
30
+ E: Into<BoxError>,
31
+ {
32
+ type Output = Result<T, BoxError>;
33
+
34
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
35
+ let mut this = self.project();
36
+
37
+ // First, try polling the future
38
+ match this.response.poll(cx) {
39
+ Poll::Ready(v) => return Poll::Ready(v.map_err(Into::into)),
40
+ Poll::Pending => {}
41
+ }
42
+
43
+ // Helper closure for polling a timeout and returning a TimedOut error
44
+ let mut check_timeout = |sleep: Option<Pin<&mut Sleep>>| {
45
+ if let Some(sleep) = sleep {
46
+ if sleep.poll(cx).is_ready() {
47
+ return Some(Poll::Ready(Err(Error::request(TimedOut).into())));
48
+ }
49
+ }
50
+ None
51
+ };
52
+
53
+ // Check total timeout first
54
+ if let Some(poll) = check_timeout(this.total_timeout.as_mut().as_pin_mut()) {
55
+ return poll;
56
+ }
57
+
58
+ // Check read timeout
59
+ if let Some(poll) = check_timeout(this.read_timeout.as_mut().as_pin_mut()) {
60
+ return poll;
61
+ }
62
+
63
+ Poll::Pending
64
+ }
65
+ }
66
+
67
+ pin_project! {
68
+ /// Response future for [`ResponseBodyTimeout`].
69
+ pub struct ResponseBodyTimeoutFuture<Fut> {
70
+ #[pin]
71
+ pub(super) inner: Fut,
72
+ pub(super) total_timeout: Option<Duration>,
73
+ pub(super) read_timeout: Option<Duration>,
74
+ }
75
+ }
76
+
77
+ impl<Fut, ResBody, E> Future for ResponseBodyTimeoutFuture<Fut>
78
+ where
79
+ Fut: Future<Output = Result<Response<ResBody>, E>>,
80
+ {
81
+ type Output = Result<Response<TimeoutBody<ResBody>>, E>;
82
+
83
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
84
+ let total_timeout = self.total_timeout;
85
+ let read_timeout = self.read_timeout;
86
+ let res = ready!(self.project().inner.poll(cx))?
87
+ .map(|body| TimeoutBody::new(total_timeout, read_timeout, body));
88
+ Poll::Ready(Ok(res))
89
+ }
90
+ }
@@ -0,0 +1,177 @@
1
+ //! Middleware for setting a timeout on the response.
2
+
3
+ mod body;
4
+ mod future;
5
+
6
+ use std::{
7
+ task::{Context, Poll},
8
+ time::Duration,
9
+ };
10
+
11
+ use http::{Request, Response};
12
+ use tower::{Layer, Service};
13
+
14
+ pub use self::body::TimeoutBody;
15
+ use self::future::{ResponseBodyTimeoutFuture, ResponseFuture};
16
+ use crate::{config::RequestConfig, error::BoxError};
17
+
18
+ /// Options for configuring timeouts.
19
+ #[derive(Clone, Copy, Default)]
20
+ pub struct TimeoutOptions {
21
+ total_timeout: Option<Duration>,
22
+ read_timeout: Option<Duration>,
23
+ }
24
+
25
+ impl TimeoutOptions {
26
+ /// Sets the read timeout for the options.
27
+ #[inline]
28
+ pub fn read_timeout(&mut self, read_timeout: Duration) -> &mut Self {
29
+ self.read_timeout = Some(read_timeout);
30
+ self
31
+ }
32
+
33
+ /// Sets the total timeout for the options.
34
+ #[inline]
35
+ pub fn total_timeout(&mut self, total_timeout: Duration) -> &mut Self {
36
+ self.total_timeout = Some(total_timeout);
37
+ self
38
+ }
39
+ }
40
+
41
+ impl_request_config_value!(TimeoutOptions);
42
+
43
+ /// [`Layer`] that applies a [`Timeout`] middleware to a service.
44
+ // This layer allows you to set a total timeout and a read timeout for requests.
45
+ #[derive(Clone)]
46
+ pub struct TimeoutLayer {
47
+ timeout: RequestConfig<TimeoutOptions>,
48
+ }
49
+
50
+ impl TimeoutLayer {
51
+ /// Create a new [`TimeoutLayer`].
52
+ #[inline(always)]
53
+ pub const fn new(options: TimeoutOptions) -> Self {
54
+ TimeoutLayer {
55
+ timeout: RequestConfig::new(Some(options)),
56
+ }
57
+ }
58
+ }
59
+
60
+ impl<S> Layer<S> for TimeoutLayer {
61
+ type Service = Timeout<S>;
62
+
63
+ #[inline(always)]
64
+ fn layer(&self, service: S) -> Self::Service {
65
+ Timeout {
66
+ inner: service,
67
+ timeout: self.timeout,
68
+ }
69
+ }
70
+ }
71
+
72
+ /// Middleware that applies total and per-read timeouts to a [`Service`] response body.
73
+ #[derive(Clone)]
74
+ pub struct Timeout<T> {
75
+ inner: T,
76
+ timeout: RequestConfig<TimeoutOptions>,
77
+ }
78
+
79
+ impl<ReqBody, ResBody, S> Service<Request<ReqBody>> for Timeout<S>
80
+ where
81
+ S: Service<Request<ReqBody>, Response = Response<ResBody>, Error = BoxError>,
82
+ {
83
+ type Response = S::Response;
84
+ type Error = BoxError;
85
+ type Future = ResponseFuture<S::Future>;
86
+
87
+ #[inline(always)]
88
+ fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
89
+ self.inner.poll_ready(cx)
90
+ }
91
+
92
+ #[inline(always)]
93
+ fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
94
+ let (total_timeout, read_timeout) = fetch_timeout_options(&self.timeout, req.extensions());
95
+ ResponseFuture {
96
+ response: self.inner.call(req),
97
+ total_timeout: total_timeout.map(tokio::time::sleep),
98
+ read_timeout: read_timeout.map(tokio::time::sleep),
99
+ }
100
+ }
101
+ }
102
+
103
+ /// [`Layer`] that applies a [`ResponseBodyTimeout`] middleware to a service.
104
+ // This layer allows you to set a total timeout and a read timeout for the response body.
105
+ #[derive(Clone)]
106
+ pub struct ResponseBodyTimeoutLayer {
107
+ timeout: RequestConfig<TimeoutOptions>,
108
+ }
109
+
110
+ impl ResponseBodyTimeoutLayer {
111
+ /// Creates a new [`ResponseBodyTimeoutLayer`].
112
+ #[inline(always)]
113
+ pub const fn new(options: TimeoutOptions) -> Self {
114
+ Self {
115
+ timeout: RequestConfig::new(Some(options)),
116
+ }
117
+ }
118
+ }
119
+
120
+ impl<S> Layer<S> for ResponseBodyTimeoutLayer {
121
+ type Service = ResponseBodyTimeout<S>;
122
+
123
+ #[inline(always)]
124
+ fn layer(&self, inner: S) -> Self::Service {
125
+ ResponseBodyTimeout {
126
+ inner,
127
+ timeout: self.timeout,
128
+ }
129
+ }
130
+ }
131
+
132
+ /// Middleware that timeouts the response body of a request with a [`Service`] to a total timeout
133
+ /// and a read timeout.
134
+ #[derive(Clone)]
135
+ pub struct ResponseBodyTimeout<S> {
136
+ inner: S,
137
+ timeout: RequestConfig<TimeoutOptions>,
138
+ }
139
+
140
+ impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for ResponseBodyTimeout<S>
141
+ where
142
+ S: Service<Request<ReqBody>, Response = Response<ResBody>>,
143
+ {
144
+ type Response = Response<TimeoutBody<ResBody>>;
145
+ type Error = S::Error;
146
+ type Future = ResponseBodyTimeoutFuture<S::Future>;
147
+
148
+ #[inline(always)]
149
+ fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
150
+ self.inner.poll_ready(cx)
151
+ }
152
+
153
+ #[inline(always)]
154
+ fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
155
+ let (total_timeout, read_timeout) = fetch_timeout_options(&self.timeout, req.extensions());
156
+ ResponseBodyTimeoutFuture {
157
+ inner: self.inner.call(req),
158
+ total_timeout,
159
+ read_timeout,
160
+ }
161
+ }
162
+ }
163
+
164
+ fn fetch_timeout_options(
165
+ opts: &RequestConfig<TimeoutOptions>,
166
+ extensions: &http::Extensions,
167
+ ) -> (Option<Duration>, Option<Duration>) {
168
+ match (opts.as_ref(), opts.fetch(extensions)) {
169
+ (Some(opts), Some(request_opts)) => (
170
+ request_opts.total_timeout.or(opts.total_timeout),
171
+ request_opts.read_timeout.or(opts.read_timeout),
172
+ ),
173
+ (Some(opts), None) => (opts.total_timeout, opts.read_timeout),
174
+ (None, Some(opts)) => (opts.total_timeout, opts.read_timeout),
175
+ (None, None) => (None, None),
176
+ }
177
+ }
@@ -0,0 +1,15 @@
1
+ //! Middleware for the client.
2
+
3
+ pub mod config;
4
+ #[cfg(feature = "cookies")]
5
+ pub mod cookie;
6
+ #[cfg(any(
7
+ feature = "gzip",
8
+ feature = "zstd",
9
+ feature = "brotli",
10
+ feature = "deflate",
11
+ ))]
12
+ pub mod decoder;
13
+ pub mod redirect;
14
+ pub mod retry;
15
+ pub mod timeout;