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,270 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
future::Future,
|
|
3
|
+
pin::Pin,
|
|
4
|
+
str,
|
|
5
|
+
task::{Context, Poll, ready},
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
use futures_util::future::Either;
|
|
9
|
+
use http::{
|
|
10
|
+
HeaderMap, Method, Request, Response, StatusCode, Uri,
|
|
11
|
+
header::{CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE, LOCATION, TRANSFER_ENCODING},
|
|
12
|
+
request::Parts,
|
|
13
|
+
};
|
|
14
|
+
use http_body::Body;
|
|
15
|
+
use pin_project_lite::pin_project;
|
|
16
|
+
use tower::{Service, util::Oneshot};
|
|
17
|
+
use url::Url;
|
|
18
|
+
|
|
19
|
+
use super::{Action, Attempt, BodyRepr, Policy};
|
|
20
|
+
use crate::{Error, error::BoxError, ext::RequestUri, into_uri::IntoUriSealed};
|
|
21
|
+
|
|
22
|
+
/// Pending future state for handling redirects.
|
|
23
|
+
pub struct Pending<ReqBody, Response> {
|
|
24
|
+
future: Pin<Box<dyn Future<Output = Action> + Send>>,
|
|
25
|
+
location: Uri,
|
|
26
|
+
body: ReqBody,
|
|
27
|
+
res: Response,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pin_project! {
|
|
31
|
+
/// Response future for [`FollowRedirect`].
|
|
32
|
+
#[project = ResponseFutureProj]
|
|
33
|
+
pub enum ResponseFuture<S, B, P>
|
|
34
|
+
where
|
|
35
|
+
S: Service<Request<B>>,
|
|
36
|
+
{
|
|
37
|
+
Redirect {
|
|
38
|
+
#[pin]
|
|
39
|
+
future: Either<S::Future, Oneshot<S, Request<B>>>,
|
|
40
|
+
pending_future: Option<Pending<B, S::Response>>,
|
|
41
|
+
service: S,
|
|
42
|
+
policy: P,
|
|
43
|
+
parts: Parts,
|
|
44
|
+
body_repr: BodyRepr<B>,
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
Direct {
|
|
48
|
+
#[pin]
|
|
49
|
+
future: S::Future,
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
impl<S, ReqBody, ResBody, P> Future for ResponseFuture<S, ReqBody, P>
|
|
55
|
+
where
|
|
56
|
+
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
|
|
57
|
+
S::Error: From<BoxError>,
|
|
58
|
+
P: Policy<ReqBody, S::Error>,
|
|
59
|
+
ReqBody: Body + Default,
|
|
60
|
+
{
|
|
61
|
+
type Output = Result<Response<ResBody>, S::Error>;
|
|
62
|
+
|
|
63
|
+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
64
|
+
match self.project() {
|
|
65
|
+
ResponseFutureProj::Direct { mut future } => future.as_mut().poll(cx),
|
|
66
|
+
ResponseFutureProj::Redirect {
|
|
67
|
+
mut future,
|
|
68
|
+
pending_future,
|
|
69
|
+
service,
|
|
70
|
+
policy,
|
|
71
|
+
parts,
|
|
72
|
+
body_repr,
|
|
73
|
+
} => {
|
|
74
|
+
// Check if we have a pending action to resolve
|
|
75
|
+
if let Some(mut state) = pending_future.take() {
|
|
76
|
+
let action = match state.future.as_mut().poll(cx) {
|
|
77
|
+
Poll::Ready(action) => action,
|
|
78
|
+
Poll::Pending => {
|
|
79
|
+
*pending_future = Some(state);
|
|
80
|
+
return Poll::Pending;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return handle_action(
|
|
85
|
+
cx,
|
|
86
|
+
RedirectAction {
|
|
87
|
+
action,
|
|
88
|
+
future: &mut future,
|
|
89
|
+
service,
|
|
90
|
+
policy,
|
|
91
|
+
parts,
|
|
92
|
+
body: state.body,
|
|
93
|
+
body_repr,
|
|
94
|
+
res: state.res,
|
|
95
|
+
location: state.location,
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Poll the current future to get the response
|
|
101
|
+
let mut res = {
|
|
102
|
+
let mut res = ready!(future.as_mut().poll(cx)?);
|
|
103
|
+
res.extensions_mut().insert(RequestUri(parts.uri.clone()));
|
|
104
|
+
res
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Determine if the response is a redirect
|
|
108
|
+
match res.status() {
|
|
109
|
+
StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND => {
|
|
110
|
+
// User agents MAY change the request method from POST to GET
|
|
111
|
+
// (RFC 7231 section 6.4.2. and 6.4.3.).
|
|
112
|
+
if parts.method == Method::POST {
|
|
113
|
+
parts.method = Method::GET;
|
|
114
|
+
*body_repr = BodyRepr::Empty;
|
|
115
|
+
drop_payload_headers(&mut parts.headers);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
StatusCode::SEE_OTHER => {
|
|
119
|
+
// A user agent can perform a GET or HEAD request (RFC 7231 section 6.4.4.).
|
|
120
|
+
if parts.method != Method::HEAD {
|
|
121
|
+
parts.method = Method::GET;
|
|
122
|
+
}
|
|
123
|
+
*body_repr = BodyRepr::Empty;
|
|
124
|
+
drop_payload_headers(&mut parts.headers);
|
|
125
|
+
}
|
|
126
|
+
StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => {}
|
|
127
|
+
_ => {
|
|
128
|
+
// Not a redirect status code, return the response as is.
|
|
129
|
+
policy.on_response(&mut res);
|
|
130
|
+
return Poll::Ready(Ok(res));
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Extract the request body for potential reuse
|
|
135
|
+
let Some(body) = body_repr.take() else {
|
|
136
|
+
return Poll::Ready(Ok(res));
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Get and resolve the Location header
|
|
140
|
+
let Some(location) = res
|
|
141
|
+
.headers()
|
|
142
|
+
.get(LOCATION)
|
|
143
|
+
.and_then(|loc| loc.to_str().ok())
|
|
144
|
+
.and_then(|loc| resolve_uri(loc, &parts.uri))
|
|
145
|
+
else {
|
|
146
|
+
return Poll::Ready(Ok(res));
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Prepare the attempt for the policy decision
|
|
150
|
+
let attempt = Attempt {
|
|
151
|
+
status: res.status(),
|
|
152
|
+
headers: res.headers(),
|
|
153
|
+
location: &location,
|
|
154
|
+
previous: &parts.uri,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Resolve the action, awaiting if it's pending
|
|
158
|
+
let action = match policy.redirect(attempt)? {
|
|
159
|
+
Action::Pending(future) => {
|
|
160
|
+
// Save the task and necessary state for next poll
|
|
161
|
+
*pending_future = Some(Pending {
|
|
162
|
+
future,
|
|
163
|
+
location,
|
|
164
|
+
body,
|
|
165
|
+
res,
|
|
166
|
+
});
|
|
167
|
+
cx.waker().wake_by_ref();
|
|
168
|
+
return Poll::Pending;
|
|
169
|
+
}
|
|
170
|
+
action => action,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
handle_action(
|
|
174
|
+
cx,
|
|
175
|
+
RedirectAction {
|
|
176
|
+
action,
|
|
177
|
+
future: &mut future,
|
|
178
|
+
service,
|
|
179
|
+
policy,
|
|
180
|
+
parts,
|
|
181
|
+
body,
|
|
182
|
+
body_repr,
|
|
183
|
+
res,
|
|
184
|
+
location,
|
|
185
|
+
},
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Try to resolve a URI reference `relative` against a base URI `base`.
|
|
193
|
+
fn resolve_uri(relative: &str, base: &Uri) -> Option<Uri> {
|
|
194
|
+
Url::parse(&base.to_string())
|
|
195
|
+
.ok()?
|
|
196
|
+
.join(relative)
|
|
197
|
+
.map(String::from)
|
|
198
|
+
.ok()?
|
|
199
|
+
.into_uri()
|
|
200
|
+
.ok()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/// Handle the response based on its status code
|
|
204
|
+
fn drop_payload_headers(headers: &mut HeaderMap) {
|
|
205
|
+
for header in &[
|
|
206
|
+
CONTENT_TYPE,
|
|
207
|
+
CONTENT_LENGTH,
|
|
208
|
+
CONTENT_ENCODING,
|
|
209
|
+
TRANSFER_ENCODING,
|
|
210
|
+
] {
|
|
211
|
+
headers.remove(header);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
type RedirectFuturePin<'a, S, ReqBody> =
|
|
216
|
+
Pin<&'a mut Either<<S as Service<Request<ReqBody>>>::Future, Oneshot<S, Request<ReqBody>>>>;
|
|
217
|
+
|
|
218
|
+
struct RedirectAction<'a, S, ReqBody, ResBody, P>
|
|
219
|
+
where
|
|
220
|
+
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
|
|
221
|
+
P: Policy<ReqBody, S::Error>,
|
|
222
|
+
{
|
|
223
|
+
action: Action,
|
|
224
|
+
future: &'a mut RedirectFuturePin<'a, S, ReqBody>,
|
|
225
|
+
service: &'a S,
|
|
226
|
+
policy: &'a mut P,
|
|
227
|
+
parts: &'a mut Parts,
|
|
228
|
+
body: ReqBody,
|
|
229
|
+
body_repr: &'a mut BodyRepr<ReqBody>,
|
|
230
|
+
res: Response<ResBody>,
|
|
231
|
+
location: Uri,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fn handle_action<S, ReqBody, ResBody, P>(
|
|
235
|
+
cx: &mut Context<'_>,
|
|
236
|
+
redirect: RedirectAction<'_, S, ReqBody, ResBody, P>,
|
|
237
|
+
) -> Poll<Result<Response<ResBody>, S::Error>>
|
|
238
|
+
where
|
|
239
|
+
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
|
|
240
|
+
S::Error: From<BoxError>,
|
|
241
|
+
P: Policy<ReqBody, S::Error>,
|
|
242
|
+
ReqBody: Body + Default,
|
|
243
|
+
{
|
|
244
|
+
match redirect.action {
|
|
245
|
+
Action::Follow => {
|
|
246
|
+
redirect.parts.uri = redirect.location;
|
|
247
|
+
redirect
|
|
248
|
+
.body_repr
|
|
249
|
+
.try_clone_from(&redirect.body, redirect.policy);
|
|
250
|
+
|
|
251
|
+
let mut req = Request::from_parts(redirect.parts.clone(), redirect.body);
|
|
252
|
+
redirect.policy.on_request(&mut req);
|
|
253
|
+
redirect
|
|
254
|
+
.future
|
|
255
|
+
.set(Either::Right(Oneshot::new(redirect.service.clone(), req)));
|
|
256
|
+
|
|
257
|
+
cx.waker().wake_by_ref();
|
|
258
|
+
Poll::Pending
|
|
259
|
+
}
|
|
260
|
+
Action::Stop => Poll::Ready(Ok(redirect.res)),
|
|
261
|
+
Action::Pending(_) => Poll::Ready(Err(S::Error::from(
|
|
262
|
+
Error::redirect(
|
|
263
|
+
"Nested pending Action is not supported in redirect policy",
|
|
264
|
+
redirect.parts.uri.clone(),
|
|
265
|
+
)
|
|
266
|
+
.into(),
|
|
267
|
+
))),
|
|
268
|
+
Action::Error(err) => Poll::Ready(Err(err.into())),
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//! Tools for customizing the behavior of a [`FollowRedirect`][super::FollowRedirect] middleware.
|
|
2
|
+
|
|
3
|
+
use std::{fmt, pin::Pin};
|
|
4
|
+
|
|
5
|
+
use http::{HeaderMap, Request, Response, StatusCode, Uri};
|
|
6
|
+
|
|
7
|
+
use crate::error::BoxError;
|
|
8
|
+
|
|
9
|
+
/// Trait for the policy on handling redirection responses.
|
|
10
|
+
pub trait Policy<B, E> {
|
|
11
|
+
/// Invoked when the service received a response with a redirection status code (`3xx`).
|
|
12
|
+
///
|
|
13
|
+
/// This method returns an [`Action`] which indicates whether the service should follow
|
|
14
|
+
/// the redirection.
|
|
15
|
+
fn redirect(&mut self, attempt: Attempt<'_>) -> Result<Action, E>;
|
|
16
|
+
|
|
17
|
+
/// Returns whether redirection is currently permitted by this policy.
|
|
18
|
+
///
|
|
19
|
+
/// This method is called to determine whether the client should follow redirects at all.
|
|
20
|
+
/// It allows policies to enable or disable redirection behavior based on the [`Request`].
|
|
21
|
+
fn follow_redirects(&mut self, _request: &mut Request<B>) -> bool;
|
|
22
|
+
|
|
23
|
+
/// Invoked right before the service makes a [`Request`].
|
|
24
|
+
fn on_request(&mut self, _request: &mut Request<B>);
|
|
25
|
+
|
|
26
|
+
/// Invoked right after the service received a [`Response`].
|
|
27
|
+
fn on_response<Body>(&mut self, _response: &mut Response<Body>);
|
|
28
|
+
|
|
29
|
+
/// Try to clone a request body before the service makes a redirected request.
|
|
30
|
+
fn clone_body(&self, _body: &B) -> Option<B>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// A type that holds information on a redirection attempt.
|
|
34
|
+
pub struct Attempt<'a> {
|
|
35
|
+
pub(crate) status: StatusCode,
|
|
36
|
+
pub(crate) headers: &'a HeaderMap,
|
|
37
|
+
pub(crate) location: &'a Uri,
|
|
38
|
+
pub(crate) previous: &'a Uri,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// A value returned by [`Policy::redirect`] which indicates the action
|
|
42
|
+
/// [`FollowRedirect`][super::FollowRedirect] should take for a redirection response.
|
|
43
|
+
pub enum Action {
|
|
44
|
+
/// Follow the redirection.
|
|
45
|
+
Follow,
|
|
46
|
+
/// Do not follow the redirection, and return the redirection response as-is.
|
|
47
|
+
Stop,
|
|
48
|
+
/// Pending async decision. The async task will be awaited to determine the final action.
|
|
49
|
+
Pending(Pin<Box<dyn Future<Output = Action> + Send>>),
|
|
50
|
+
/// An error occurred while determining the redirection action.
|
|
51
|
+
Error(BoxError),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
impl fmt::Debug for Action {
|
|
55
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
56
|
+
match self {
|
|
57
|
+
Action::Follow => f.debug_tuple("Follow").finish(),
|
|
58
|
+
Action::Stop => f.debug_tuple("Stop").finish(),
|
|
59
|
+
Action::Pending(_) => f.debug_tuple("Pending").finish(),
|
|
60
|
+
Action::Error(_) => f.debug_tuple("Error").finish(),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
//! Middleware for following redirections.
|
|
2
|
+
|
|
3
|
+
mod future;
|
|
4
|
+
mod policy;
|
|
5
|
+
|
|
6
|
+
use std::{
|
|
7
|
+
mem,
|
|
8
|
+
task::{Context, Poll},
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
use futures_util::future::Either;
|
|
12
|
+
use http::{Request, Response};
|
|
13
|
+
use http_body::Body;
|
|
14
|
+
use tower::{Layer, Service};
|
|
15
|
+
|
|
16
|
+
use self::future::ResponseFuture;
|
|
17
|
+
pub use self::policy::{Action, Attempt, Policy};
|
|
18
|
+
use crate::error::BoxError;
|
|
19
|
+
|
|
20
|
+
enum BodyRepr<B> {
|
|
21
|
+
Some(B),
|
|
22
|
+
Empty,
|
|
23
|
+
None,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
impl<B> BodyRepr<B>
|
|
27
|
+
where
|
|
28
|
+
B: Body + Default,
|
|
29
|
+
{
|
|
30
|
+
fn take(&mut self) -> Option<B> {
|
|
31
|
+
match mem::replace(self, BodyRepr::None) {
|
|
32
|
+
BodyRepr::Some(body) => Some(body),
|
|
33
|
+
BodyRepr::Empty => {
|
|
34
|
+
*self = BodyRepr::Empty;
|
|
35
|
+
Some(B::default())
|
|
36
|
+
}
|
|
37
|
+
BodyRepr::None => None,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fn try_clone_from<P, E>(&mut self, body: &B, policy: &P)
|
|
42
|
+
where
|
|
43
|
+
P: Policy<B, E>,
|
|
44
|
+
{
|
|
45
|
+
match self {
|
|
46
|
+
BodyRepr::Some(_) | BodyRepr::Empty => {}
|
|
47
|
+
BodyRepr::None => {
|
|
48
|
+
if body.size_hint().exact() == Some(0) {
|
|
49
|
+
*self = BodyRepr::Some(B::default());
|
|
50
|
+
} else if let Some(cloned) = policy.clone_body(body) {
|
|
51
|
+
*self = BodyRepr::Some(cloned);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// [`Layer`] for retrying requests with a [`Service`] to follow redirection responses.
|
|
59
|
+
#[derive(Clone, Copy, Default)]
|
|
60
|
+
pub struct FollowRedirectLayer<P> {
|
|
61
|
+
policy: P,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
impl<P> FollowRedirectLayer<P> {
|
|
65
|
+
/// Create a new [`FollowRedirectLayer`] with the given redirection [`Policy`].
|
|
66
|
+
#[inline(always)]
|
|
67
|
+
pub const fn with_policy(policy: P) -> Self {
|
|
68
|
+
FollowRedirectLayer { policy }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
impl<S, P> Layer<S> for FollowRedirectLayer<P>
|
|
73
|
+
where
|
|
74
|
+
S: Clone,
|
|
75
|
+
P: Clone,
|
|
76
|
+
{
|
|
77
|
+
type Service = FollowRedirect<S, P>;
|
|
78
|
+
|
|
79
|
+
#[inline(always)]
|
|
80
|
+
fn layer(&self, inner: S) -> Self::Service {
|
|
81
|
+
FollowRedirect::with_policy(inner, self.policy.clone())
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Middleware that retries requests with a [`Service`] to follow redirection responses.
|
|
86
|
+
#[derive(Clone, Copy)]
|
|
87
|
+
pub struct FollowRedirect<S, P> {
|
|
88
|
+
inner: S,
|
|
89
|
+
policy: P,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
impl<S, P> FollowRedirect<S, P>
|
|
93
|
+
where
|
|
94
|
+
P: Clone,
|
|
95
|
+
{
|
|
96
|
+
/// Create a new [`FollowRedirect`] with the given redirection [`Policy`].
|
|
97
|
+
#[inline(always)]
|
|
98
|
+
pub const fn with_policy(inner: S, policy: P) -> Self {
|
|
99
|
+
FollowRedirect { inner, policy }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
impl<ReqBody, ResBody, S, P> Service<Request<ReqBody>> for FollowRedirect<S, P>
|
|
104
|
+
where
|
|
105
|
+
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
|
|
106
|
+
S::Error: From<BoxError>,
|
|
107
|
+
P: Policy<ReqBody, S::Error> + Clone,
|
|
108
|
+
ReqBody: Body + Default,
|
|
109
|
+
{
|
|
110
|
+
type Response = Response<ResBody>;
|
|
111
|
+
type Error = S::Error;
|
|
112
|
+
type Future = ResponseFuture<S, ReqBody, P>;
|
|
113
|
+
|
|
114
|
+
#[inline]
|
|
115
|
+
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
116
|
+
self.inner.poll_ready(cx)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
|
|
120
|
+
if self.policy.follow_redirects(&mut req) {
|
|
121
|
+
let service = self.inner.clone();
|
|
122
|
+
let mut service = mem::replace(&mut self.inner, service);
|
|
123
|
+
let mut policy = self.policy.clone();
|
|
124
|
+
|
|
125
|
+
let mut body_repr = BodyRepr::None;
|
|
126
|
+
body_repr.try_clone_from(req.body(), &policy);
|
|
127
|
+
policy.on_request(&mut req);
|
|
128
|
+
|
|
129
|
+
let (parts, body) = req.into_parts();
|
|
130
|
+
let req = Request::from_parts(parts.clone(), body);
|
|
131
|
+
ResponseFuture::Redirect {
|
|
132
|
+
future: Either::Left(service.call(req)),
|
|
133
|
+
pending_future: None,
|
|
134
|
+
service,
|
|
135
|
+
policy,
|
|
136
|
+
parts,
|
|
137
|
+
body_repr,
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
ResponseFuture::Direct {
|
|
141
|
+
future: self.inner.call(req),
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
use std::{error::Error as StdError, sync::Arc};
|
|
2
|
+
|
|
3
|
+
use http::{Method, StatusCode, Uri};
|
|
4
|
+
|
|
5
|
+
use super::{Req, Res};
|
|
6
|
+
use crate::error::BoxError;
|
|
7
|
+
|
|
8
|
+
pub trait Classify: Send + Sync + 'static {
|
|
9
|
+
fn classify(&self, req_rep: ReqRep<'_>) -> Action;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// For Future Whoever: making a blanket impl for any closure sounds nice,
|
|
13
|
+
// but it causes inference issues at the call site. Every closure would
|
|
14
|
+
// need to include `: ReqRep` in the arguments.
|
|
15
|
+
//
|
|
16
|
+
// An alternative is to make things like `ClassifyFn`. Slightly more
|
|
17
|
+
// annoying, but also more forwards-compatible. :shrug:
|
|
18
|
+
pub struct ClassifyFn<F>(pub(crate) F);
|
|
19
|
+
|
|
20
|
+
impl<F> Classify for ClassifyFn<F>
|
|
21
|
+
where
|
|
22
|
+
F: Fn(ReqRep<'_>) -> Action + Send + Sync + 'static,
|
|
23
|
+
{
|
|
24
|
+
fn classify(&self, req_rep: ReqRep<'_>) -> Action {
|
|
25
|
+
(self.0)(req_rep)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Represents a request-response pair for classification purposes.
|
|
30
|
+
#[derive(Debug)]
|
|
31
|
+
pub struct ReqRep<'a>(&'a Req, Result<StatusCode, &'a BoxError>);
|
|
32
|
+
|
|
33
|
+
impl ReqRep<'_> {
|
|
34
|
+
/// Returns the HTTP method of the request.
|
|
35
|
+
pub fn method(&self) -> &Method {
|
|
36
|
+
self.0.method()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Returns the URI of the request.
|
|
40
|
+
pub fn uri(&self) -> &Uri {
|
|
41
|
+
self.0.uri()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Returns the HTTP status code if the response was successful.
|
|
45
|
+
pub fn status(&self) -> Option<StatusCode> {
|
|
46
|
+
self.1.ok()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Returns the error if the request failed.
|
|
50
|
+
pub fn error(&self) -> Option<&(dyn StdError + 'static)> {
|
|
51
|
+
self.1.as_ref().err().map(|&e| &**e as _)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Returns a retryable action.
|
|
55
|
+
pub fn retryable(self) -> Action {
|
|
56
|
+
Action::Retryable
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Returns a success action.
|
|
60
|
+
pub fn success(self) -> Action {
|
|
61
|
+
Action::Success
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// The action to take after classifying a request/response pair.
|
|
66
|
+
#[must_use]
|
|
67
|
+
pub enum Action {
|
|
68
|
+
/// The request was successful and should not be retried.
|
|
69
|
+
Success,
|
|
70
|
+
/// The request failed but can be retried.
|
|
71
|
+
Retryable,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Determines whether a request should be retried based on the response or error.
|
|
75
|
+
#[derive(Clone)]
|
|
76
|
+
pub(crate) enum Classifier {
|
|
77
|
+
/// Never retry any requests.
|
|
78
|
+
Never,
|
|
79
|
+
/// Retry protocol-level errors (connection issues, timeouts, etc.).
|
|
80
|
+
ProtocolNacks,
|
|
81
|
+
/// Use custom classification logic.
|
|
82
|
+
Dyn(Arc<dyn Classify>),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
impl Classifier {
|
|
86
|
+
/// Classifies a request/response pair to determine the appropriate retry action.
|
|
87
|
+
pub(super) fn classify(&mut self, req: &Req, res: &Result<Res, BoxError>) -> Action {
|
|
88
|
+
let req_rep = ReqRep(req, res.as_ref().map(|r| r.status()));
|
|
89
|
+
match self {
|
|
90
|
+
Classifier::Never => Action::Success,
|
|
91
|
+
Classifier::ProtocolNacks => {
|
|
92
|
+
let is_protocol_nack = req_rep
|
|
93
|
+
.error()
|
|
94
|
+
.map(super::is_retryable_error)
|
|
95
|
+
.unwrap_or(false);
|
|
96
|
+
if is_protocol_nack {
|
|
97
|
+
Action::Retryable
|
|
98
|
+
} else {
|
|
99
|
+
Action::Success
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
Classifier::Dyn(c) => c.classify(req_rep),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
use std::sync::Arc;
|
|
2
|
+
|
|
3
|
+
use super::Req;
|
|
4
|
+
|
|
5
|
+
pub trait Scope: Send + Sync + 'static {
|
|
6
|
+
fn applies_to(&self, req: &super::Req) -> bool;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// I think scopes likely make the most sense being to hosts.
|
|
10
|
+
// If that's the case, then it should probably be easiest to check for
|
|
11
|
+
// the host. Perhaps also considering the ability to add more things
|
|
12
|
+
// to scope off in the future...
|
|
13
|
+
|
|
14
|
+
// For Future Whoever: making a blanket impl for any closure sounds nice,
|
|
15
|
+
// but it causes inference issues at the call site. Every closure would
|
|
16
|
+
// need to include `: ReqRep` in the arguments.
|
|
17
|
+
//
|
|
18
|
+
// An alternative is to make things like `ScopeFn`. Slightly more annoying,
|
|
19
|
+
// but also more forwards-compatible. :shrug:
|
|
20
|
+
|
|
21
|
+
pub struct ScopeFn<F>(pub(crate) F);
|
|
22
|
+
|
|
23
|
+
impl<F> Scope for ScopeFn<F>
|
|
24
|
+
where
|
|
25
|
+
F: Fn(&Req) -> bool + Send + Sync + 'static,
|
|
26
|
+
{
|
|
27
|
+
fn applies_to(&self, req: &Req) -> bool {
|
|
28
|
+
(self.0)(req)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Defines the scope of requests that are eligible for retry.
|
|
33
|
+
#[derive(Clone)]
|
|
34
|
+
pub(crate) enum Scoped {
|
|
35
|
+
/// All requests are eligible for retry regardless of their properties.
|
|
36
|
+
Unscoped,
|
|
37
|
+
/// Use custom logic to determine if a request is eligible for retry.
|
|
38
|
+
Dyn(Arc<dyn Scope>),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl Scoped {
|
|
42
|
+
/// Checks if the given request falls within the retry scope.
|
|
43
|
+
pub(super) fn applies_to(&self, req: &super::Req) -> bool {
|
|
44
|
+
let ret = match self {
|
|
45
|
+
Scoped::Unscoped => true,
|
|
46
|
+
Scoped::Dyn(s) => s.applies_to(req),
|
|
47
|
+
};
|
|
48
|
+
trace!("retry in scope: {ret}");
|
|
49
|
+
ret
|
|
50
|
+
}
|
|
51
|
+
}
|