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,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
+ }