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.
- checksums.yaml +7 -0
- data/Cargo.lock +2688 -0
- data/Cargo.toml +6 -0
- data/README.md +179 -0
- data/ext/wreq_rb/Cargo.toml +39 -0
- data/ext/wreq_rb/extconf.rb +22 -0
- data/ext/wreq_rb/src/client.rs +565 -0
- data/ext/wreq_rb/src/error.rs +25 -0
- data/ext/wreq_rb/src/lib.rs +20 -0
- data/ext/wreq_rb/src/response.rs +132 -0
- data/lib/wreq-rb/version.rb +5 -0
- data/lib/wreq-rb.rb +17 -0
- data/patches/0001-add-transfer-size-tracking.patch +292 -0
- data/vendor/wreq/Cargo.toml +306 -0
- data/vendor/wreq/LICENSE +202 -0
- data/vendor/wreq/README.md +122 -0
- data/vendor/wreq/examples/cert_store.rs +77 -0
- data/vendor/wreq/examples/connect_via_lower_priority_tokio_runtime.rs +258 -0
- data/vendor/wreq/examples/emulation.rs +118 -0
- data/vendor/wreq/examples/form.rs +14 -0
- data/vendor/wreq/examples/http1_websocket.rs +37 -0
- data/vendor/wreq/examples/http2_websocket.rs +45 -0
- data/vendor/wreq/examples/json_dynamic.rs +41 -0
- data/vendor/wreq/examples/json_typed.rs +47 -0
- data/vendor/wreq/examples/keylog.rs +16 -0
- data/vendor/wreq/examples/request_with_emulation.rs +115 -0
- data/vendor/wreq/examples/request_with_interface.rs +37 -0
- data/vendor/wreq/examples/request_with_local_address.rs +16 -0
- data/vendor/wreq/examples/request_with_proxy.rs +13 -0
- data/vendor/wreq/examples/request_with_redirect.rs +22 -0
- data/vendor/wreq/examples/request_with_version.rs +15 -0
- data/vendor/wreq/examples/tor_socks.rs +24 -0
- data/vendor/wreq/examples/unix_socket.rs +33 -0
- data/vendor/wreq/src/client/body.rs +304 -0
- data/vendor/wreq/src/client/conn/conn.rs +231 -0
- data/vendor/wreq/src/client/conn/connector.rs +549 -0
- data/vendor/wreq/src/client/conn/http.rs +1023 -0
- data/vendor/wreq/src/client/conn/proxy/socks.rs +233 -0
- data/vendor/wreq/src/client/conn/proxy/tunnel.rs +260 -0
- data/vendor/wreq/src/client/conn/proxy.rs +39 -0
- data/vendor/wreq/src/client/conn/tls_info.rs +98 -0
- data/vendor/wreq/src/client/conn/uds.rs +44 -0
- data/vendor/wreq/src/client/conn/verbose.rs +149 -0
- data/vendor/wreq/src/client/conn.rs +323 -0
- data/vendor/wreq/src/client/core/body/incoming.rs +485 -0
- data/vendor/wreq/src/client/core/body/length.rs +118 -0
- data/vendor/wreq/src/client/core/body.rs +34 -0
- data/vendor/wreq/src/client/core/common/buf.rs +149 -0
- data/vendor/wreq/src/client/core/common/rewind.rs +141 -0
- data/vendor/wreq/src/client/core/common/watch.rs +76 -0
- data/vendor/wreq/src/client/core/common.rs +3 -0
- data/vendor/wreq/src/client/core/conn/http1.rs +342 -0
- data/vendor/wreq/src/client/core/conn/http2.rs +307 -0
- data/vendor/wreq/src/client/core/conn.rs +11 -0
- data/vendor/wreq/src/client/core/dispatch.rs +299 -0
- data/vendor/wreq/src/client/core/error.rs +435 -0
- data/vendor/wreq/src/client/core/ext.rs +201 -0
- data/vendor/wreq/src/client/core/http1.rs +178 -0
- data/vendor/wreq/src/client/core/http2.rs +483 -0
- data/vendor/wreq/src/client/core/proto/h1/conn.rs +988 -0
- data/vendor/wreq/src/client/core/proto/h1/decode.rs +1170 -0
- data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +684 -0
- data/vendor/wreq/src/client/core/proto/h1/encode.rs +580 -0
- data/vendor/wreq/src/client/core/proto/h1/io.rs +879 -0
- data/vendor/wreq/src/client/core/proto/h1/role.rs +694 -0
- data/vendor/wreq/src/client/core/proto/h1.rs +104 -0
- data/vendor/wreq/src/client/core/proto/h2/client.rs +650 -0
- data/vendor/wreq/src/client/core/proto/h2/ping.rs +539 -0
- data/vendor/wreq/src/client/core/proto/h2.rs +379 -0
- data/vendor/wreq/src/client/core/proto/headers.rs +138 -0
- data/vendor/wreq/src/client/core/proto.rs +58 -0
- data/vendor/wreq/src/client/core/rt/bounds.rs +57 -0
- data/vendor/wreq/src/client/core/rt/timer.rs +150 -0
- data/vendor/wreq/src/client/core/rt/tokio.rs +99 -0
- data/vendor/wreq/src/client/core/rt.rs +25 -0
- data/vendor/wreq/src/client/core/upgrade.rs +267 -0
- data/vendor/wreq/src/client/core.rs +16 -0
- data/vendor/wreq/src/client/emulation.rs +161 -0
- data/vendor/wreq/src/client/http/client/error.rs +142 -0
- data/vendor/wreq/src/client/http/client/exec.rs +29 -0
- data/vendor/wreq/src/client/http/client/extra.rs +77 -0
- data/vendor/wreq/src/client/http/client/lazy.rs +79 -0
- data/vendor/wreq/src/client/http/client/pool.rs +1105 -0
- data/vendor/wreq/src/client/http/client/util.rs +104 -0
- data/vendor/wreq/src/client/http/client.rs +1003 -0
- data/vendor/wreq/src/client/http/future.rs +99 -0
- data/vendor/wreq/src/client/http.rs +1629 -0
- data/vendor/wreq/src/client/layer/config/options.rs +156 -0
- data/vendor/wreq/src/client/layer/config.rs +116 -0
- data/vendor/wreq/src/client/layer/cookie.rs +161 -0
- data/vendor/wreq/src/client/layer/decoder.rs +139 -0
- data/vendor/wreq/src/client/layer/redirect/future.rs +270 -0
- data/vendor/wreq/src/client/layer/redirect/policy.rs +63 -0
- data/vendor/wreq/src/client/layer/redirect.rs +145 -0
- data/vendor/wreq/src/client/layer/retry/classify.rs +105 -0
- data/vendor/wreq/src/client/layer/retry/scope.rs +51 -0
- data/vendor/wreq/src/client/layer/retry.rs +151 -0
- data/vendor/wreq/src/client/layer/timeout/body.rs +233 -0
- data/vendor/wreq/src/client/layer/timeout/future.rs +90 -0
- data/vendor/wreq/src/client/layer/timeout.rs +177 -0
- data/vendor/wreq/src/client/layer.rs +15 -0
- data/vendor/wreq/src/client/multipart.rs +717 -0
- data/vendor/wreq/src/client/request.rs +818 -0
- data/vendor/wreq/src/client/response.rs +534 -0
- data/vendor/wreq/src/client/ws/json.rs +99 -0
- data/vendor/wreq/src/client/ws/message.rs +453 -0
- data/vendor/wreq/src/client/ws.rs +714 -0
- data/vendor/wreq/src/client.rs +27 -0
- data/vendor/wreq/src/config.rs +140 -0
- data/vendor/wreq/src/cookie.rs +579 -0
- data/vendor/wreq/src/dns/gai.rs +249 -0
- data/vendor/wreq/src/dns/hickory.rs +78 -0
- data/vendor/wreq/src/dns/resolve.rs +180 -0
- data/vendor/wreq/src/dns.rs +69 -0
- data/vendor/wreq/src/error.rs +502 -0
- data/vendor/wreq/src/ext.rs +398 -0
- data/vendor/wreq/src/hash.rs +143 -0
- data/vendor/wreq/src/header.rs +506 -0
- data/vendor/wreq/src/into_uri.rs +187 -0
- data/vendor/wreq/src/lib.rs +586 -0
- data/vendor/wreq/src/proxy/mac.rs +82 -0
- data/vendor/wreq/src/proxy/matcher.rs +806 -0
- data/vendor/wreq/src/proxy/uds.rs +66 -0
- data/vendor/wreq/src/proxy/win.rs +31 -0
- data/vendor/wreq/src/proxy.rs +569 -0
- data/vendor/wreq/src/redirect.rs +575 -0
- data/vendor/wreq/src/retry.rs +198 -0
- data/vendor/wreq/src/sync.rs +129 -0
- data/vendor/wreq/src/tls/conn/cache.rs +123 -0
- data/vendor/wreq/src/tls/conn/cert_compression.rs +125 -0
- data/vendor/wreq/src/tls/conn/ext.rs +82 -0
- data/vendor/wreq/src/tls/conn/macros.rs +34 -0
- data/vendor/wreq/src/tls/conn/service.rs +138 -0
- data/vendor/wreq/src/tls/conn.rs +681 -0
- data/vendor/wreq/src/tls/keylog/handle.rs +64 -0
- data/vendor/wreq/src/tls/keylog.rs +99 -0
- data/vendor/wreq/src/tls/options.rs +464 -0
- data/vendor/wreq/src/tls/x509/identity.rs +122 -0
- data/vendor/wreq/src/tls/x509/parser.rs +71 -0
- data/vendor/wreq/src/tls/x509/store.rs +228 -0
- data/vendor/wreq/src/tls/x509.rs +68 -0
- data/vendor/wreq/src/tls.rs +154 -0
- data/vendor/wreq/src/trace.rs +55 -0
- data/vendor/wreq/src/util.rs +122 -0
- data/vendor/wreq/tests/badssl.rs +228 -0
- data/vendor/wreq/tests/brotli.rs +350 -0
- data/vendor/wreq/tests/client.rs +1098 -0
- data/vendor/wreq/tests/connector_layers.rs +227 -0
- data/vendor/wreq/tests/cookie.rs +306 -0
- data/vendor/wreq/tests/deflate.rs +347 -0
- data/vendor/wreq/tests/emulation.rs +260 -0
- data/vendor/wreq/tests/gzip.rs +347 -0
- data/vendor/wreq/tests/layers.rs +261 -0
- data/vendor/wreq/tests/multipart.rs +165 -0
- data/vendor/wreq/tests/proxy.rs +438 -0
- data/vendor/wreq/tests/redirect.rs +629 -0
- data/vendor/wreq/tests/retry.rs +135 -0
- data/vendor/wreq/tests/support/delay_server.rs +117 -0
- data/vendor/wreq/tests/support/error.rs +16 -0
- data/vendor/wreq/tests/support/layer.rs +183 -0
- data/vendor/wreq/tests/support/mod.rs +9 -0
- data/vendor/wreq/tests/support/server.rs +232 -0
- data/vendor/wreq/tests/timeouts.rs +281 -0
- data/vendor/wreq/tests/unix_socket.rs +135 -0
- data/vendor/wreq/tests/upgrade.rs +98 -0
- data/vendor/wreq/tests/zstd.rs +559 -0
- 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;
|