wreq-rb 0.6.0 → 0.6.1
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 +4 -4
- data/Cargo.lock +257 -102
- data/ext/wreq_rb/Cargo.toml +4 -4
- data/ext/wreq_rb/src/client.rs +1 -1
- data/lib/wreq-rb/version.rb +1 -1
- data/patches/0001-add-transfer-size-tracking.patch +11 -15
- data/vendor/wreq/Cargo.toml +9 -8
- data/vendor/wreq/README.md +5 -5
- data/vendor/wreq/bench/support/bench.rs +6 -2
- data/vendor/wreq/bench/support/client.rs +88 -1
- data/vendor/wreq/bench/support/exec.rs +0 -0
- data/vendor/wreq/bench/support/rt.rs +34 -0
- data/vendor/wreq/bench/support/server.rs +1 -1
- data/vendor/wreq/bench/support.rs +1 -15
- data/vendor/wreq/examples/cert_store.rs +13 -13
- data/vendor/wreq/examples/request_with_emulate.rs +1 -1
- data/vendor/wreq/examples/tcp_linger.rs +22 -0
- data/vendor/wreq/src/client/layer/client/pool.rs +17 -17
- data/vendor/wreq/src/client/layer/client.rs +2 -0
- data/vendor/wreq/src/client/layer/decoder.rs +71 -17
- data/vendor/wreq/src/client/layer/redirect/future.rs +49 -63
- data/vendor/wreq/src/client/layer/redirect/policy.rs +2 -26
- data/vendor/wreq/src/client/layer/redirect.rs +48 -60
- data/vendor/wreq/src/client/layer/retry.rs +12 -15
- data/vendor/wreq/src/client/layer/timeout/body.rs +27 -21
- data/vendor/wreq/src/client/layer/timeout/future.rs +33 -58
- data/vendor/wreq/src/client/layer/timeout.rs +8 -14
- data/vendor/wreq/src/client/request.rs +4 -0
- data/vendor/wreq/src/client.rs +53 -31
- data/vendor/wreq/src/conn/connector.rs +99 -129
- data/vendor/wreq/src/conn/http.rs +25 -18
- data/vendor/wreq/src/conn/net/tcp.rs +601 -107
- data/vendor/wreq/src/conn/proxy/socks.rs +6 -6
- data/vendor/wreq/src/conn/timeout.rs +166 -0
- data/vendor/wreq/src/conn.rs +5 -4
- data/vendor/wreq/src/cookie/jar.rs +1225 -0
- data/vendor/wreq/src/cookie/store.rs +321 -0
- data/vendor/wreq/src/cookie.rs +108 -612
- data/vendor/wreq/src/dns/resolve.rs +8 -2
- data/vendor/wreq/src/dns.rs +4 -4
- data/vendor/wreq/src/error.rs +53 -20
- data/vendor/wreq/src/lib.rs +1 -0
- data/vendor/wreq/src/proxy/matcher.rs +26 -12
- data/vendor/wreq/src/proxy/win.rs +39 -9
- data/vendor/wreq/src/redirect.rs +515 -100
- data/vendor/wreq/src/tls/conn.rs +3 -11
- data/vendor/wreq/src/tls/session.rs +7 -8
- data/vendor/wreq/src/tls/trust/store.rs +4 -4
- data/vendor/wreq/src/util.rs +23 -0
- data/vendor/wreq/tests/badssl.rs +72 -7
- data/vendor/wreq/tests/brotli.rs +1 -1
- data/vendor/wreq/tests/client.rs +24 -0
- data/vendor/wreq/tests/connector_layers.rs +8 -4
- data/vendor/wreq/tests/cookie.rs +59 -0
- data/vendor/wreq/tests/deflate.rs +1 -1
- data/vendor/wreq/tests/gzip.rs +53 -1
- data/vendor/wreq/tests/layers.rs +8 -4
- data/vendor/wreq/tests/redirect.rs +180 -97
- data/vendor/wreq/tests/timeouts.rs +47 -12
- data/vendor/wreq/tests/zstd.rs +1 -1
- metadata +7 -1
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
//! TCP connection types and utilities.
|
|
2
|
+
//!
|
|
3
|
+
//! DNS resolution finishes before this module receives the candidate addresses.
|
|
4
|
+
//! For that address list, Happy Eyeballs follows curl's connection strategy:
|
|
5
|
+
//! address families are alternated, later attempts are staggered, and the next
|
|
6
|
+
//! address starts immediately when no attempt remains active. Up to six attempts
|
|
7
|
+
//! are kept in flight, and the first successful connection cancels the rest.
|
|
8
|
+
//!
|
|
9
|
+
//! `connect_timeout` is one deadline for the complete TCP race instead of a
|
|
10
|
+
//! separate timeout for each address. When Happy Eyeballs is disabled, addresses
|
|
11
|
+
//! are tried sequentially in resolver order.
|
|
12
|
+
//!
|
|
13
|
+
//! See [RFC 8305 section 5] and [curl's Happy Eyeballs implementation].
|
|
14
|
+
//!
|
|
15
|
+
//! [RFC 8305 section 5]: https://www.rfc-editor.org/rfc/rfc8305.html#section-5
|
|
16
|
+
//! [curl's Happy Eyeballs implementation]: https://github.com/curl/curl/blob/master/lib/cf-ip-happy.c
|
|
2
17
|
|
|
3
18
|
#[cfg(feature = "tokio-rt")]
|
|
4
19
|
pub mod tokio;
|
|
@@ -7,12 +22,14 @@ pub mod tokio;
|
|
|
7
22
|
pub mod compio;
|
|
8
23
|
|
|
9
24
|
use std::{
|
|
25
|
+
collections::VecDeque,
|
|
10
26
|
error::Error as StdError,
|
|
11
27
|
fmt,
|
|
12
28
|
future::Future,
|
|
13
29
|
io,
|
|
14
30
|
net::{Ipv4Addr, Ipv6Addr, SocketAddr},
|
|
15
31
|
pin::{Pin, pin},
|
|
32
|
+
task::{Context, Poll},
|
|
16
33
|
time::Duration,
|
|
17
34
|
};
|
|
18
35
|
|
|
@@ -27,6 +44,8 @@ use crate::{
|
|
|
27
44
|
|
|
28
45
|
type BoxConnecting<T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + Send>>;
|
|
29
46
|
|
|
47
|
+
const MAX_PARALLEL_CONNECT_ATTEMPTS: usize = 6;
|
|
48
|
+
|
|
30
49
|
/// A builder for tcp connections.
|
|
31
50
|
pub trait TcpConnector: Clone + Send + Sync + 'static {
|
|
32
51
|
/// The underlying stream type.
|
|
@@ -44,7 +63,7 @@ pub trait TcpConnector: Clone + Send + Sync + 'static {
|
|
|
44
63
|
type Error: Into<Box<dyn StdError + Send + Sync>>;
|
|
45
64
|
|
|
46
65
|
/// The future type returned by this builder.
|
|
47
|
-
type Future: Future<Output = Result<Self::Connection, Self::Error>> + Send + 'static;
|
|
66
|
+
type Future: Future<Output = Result<Self::Connection, Self::Error>> + Send + Unpin + 'static;
|
|
48
67
|
|
|
49
68
|
/// The future type returned by this builder's sleep.
|
|
50
69
|
type Sleep: Future<Output = ()> + Send + 'static;
|
|
@@ -72,6 +91,35 @@ struct ConnectingTcpRemote<S: TcpConnector> {
|
|
|
72
91
|
connector: S,
|
|
73
92
|
}
|
|
74
93
|
|
|
94
|
+
/// Schedules staggered connection attempts for a resolved address list.
|
|
95
|
+
///
|
|
96
|
+
/// The state owns every active future, so dropping it cancels the remaining
|
|
97
|
+
/// attempts.
|
|
98
|
+
struct ConnectingTcpState<S: TcpConnector> {
|
|
99
|
+
preferred: ConnectingTcpRemote<S>,
|
|
100
|
+
fallback: Option<ConnectingTcpRemote<S>>,
|
|
101
|
+
initial_delay: Option<S::Sleep>,
|
|
102
|
+
next_fallback: bool,
|
|
103
|
+
happy_eyeballs_timeout: Option<Duration>,
|
|
104
|
+
attempts: VecDeque<ConnectingTcpAttempt<S>>,
|
|
105
|
+
next_attempt_order: usize,
|
|
106
|
+
first_error: Option<(usize, ConnectError)>,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// One active TCP connection attempt and its position in launch order.
|
|
110
|
+
struct ConnectingTcpAttempt<S: TcpConnector> {
|
|
111
|
+
addr: SocketAddr,
|
|
112
|
+
order: usize,
|
|
113
|
+
future: S::Future,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// An event that advances the address race.
|
|
117
|
+
enum TcpEvent<C> {
|
|
118
|
+
Connected(C),
|
|
119
|
+
AllAttemptsFailed,
|
|
120
|
+
DelayElapsed,
|
|
121
|
+
}
|
|
122
|
+
|
|
75
123
|
impl<S: TcpConnector> ConnectingTcp<S>
|
|
76
124
|
where
|
|
77
125
|
S::TcpStream: From<socket2::Socket>,
|
|
@@ -119,6 +167,19 @@ where
|
|
|
119
167
|
}
|
|
120
168
|
}
|
|
121
169
|
}
|
|
170
|
+
|
|
171
|
+
/// Connects through the sequential fast path or the staggered address race.
|
|
172
|
+
pub(crate) async fn connect(self, config: &TcpOptions) -> Result<S::Connection, ConnectError> {
|
|
173
|
+
if self.fallback.is_none()
|
|
174
|
+
&& (config.happy_eyeballs_timeout.is_none() || self.preferred.addrs.len() <= 1)
|
|
175
|
+
{
|
|
176
|
+
return self.preferred.connect(config).await;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
ConnectingTcpState::new(self, config.happy_eyeballs_timeout)
|
|
180
|
+
.connect(config)
|
|
181
|
+
.await
|
|
182
|
+
}
|
|
122
183
|
}
|
|
123
184
|
|
|
124
185
|
impl<S: TcpConnector> ConnectingTcpRemote<S>
|
|
@@ -126,7 +187,6 @@ where
|
|
|
126
187
|
S::TcpStream: From<socket2::Socket>,
|
|
127
188
|
{
|
|
128
189
|
fn new(addrs: dns::SocketAddrs, connect_timeout: Option<Duration>, connector: S) -> Self {
|
|
129
|
-
let connect_timeout = connect_timeout.and_then(|t| t.checked_div(addrs.len() as u32));
|
|
130
190
|
Self {
|
|
131
191
|
addrs,
|
|
132
192
|
connect_timeout,
|
|
@@ -134,44 +194,272 @@ where
|
|
|
134
194
|
}
|
|
135
195
|
}
|
|
136
196
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
197
|
+
/// Prepares a connection future for the next address.
|
|
198
|
+
fn connect_next(
|
|
199
|
+
&mut self,
|
|
200
|
+
config: &TcpOptions,
|
|
201
|
+
) -> Option<(SocketAddr, Result<S::Future, ConnectError>)> {
|
|
202
|
+
let addr = self.addrs.next()?;
|
|
203
|
+
debug!("connecting to {}", addr);
|
|
204
|
+
Some((addr, connect(&addr, config, &self.connector)))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// Tries this address list sequentially under one shared deadline.
|
|
208
|
+
async fn connect(mut self, config: &TcpOptions) -> Result<S::Connection, ConnectError> {
|
|
209
|
+
let timeout = self
|
|
210
|
+
.connect_timeout
|
|
211
|
+
.map(|duration| self.connector.sleep(duration));
|
|
212
|
+
connect_with_timeout(self.connect_inner(config), timeout).await
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// Tries addresses in resolver order until one connects.
|
|
216
|
+
async fn connect_inner(&mut self, config: &TcpOptions) -> Result<S::Connection, ConnectError> {
|
|
217
|
+
let mut first_error = None;
|
|
218
|
+
|
|
219
|
+
while let Some((addr, result)) = self.connect_next(config) {
|
|
220
|
+
let result = match result {
|
|
221
|
+
Ok(future) => future.await.map_err(ConnectError::tcp),
|
|
222
|
+
Err(error) => Err(error),
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
match result {
|
|
226
|
+
Ok(connection) => {
|
|
227
|
+
debug!("connected to {}", addr);
|
|
228
|
+
return Ok(connection);
|
|
229
|
+
}
|
|
230
|
+
Err(error) => {
|
|
231
|
+
let error = error.with_addr(addr);
|
|
232
|
+
trace!("connect error for {}: {:?}", addr, error);
|
|
233
|
+
if first_error.is_none() {
|
|
234
|
+
first_error = Some(error);
|
|
146
235
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
Err(first_error.unwrap_or_else(ConnectError::network_unreachable))
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
impl<S: TcpConnector> ConnectingTcpState<S>
|
|
245
|
+
where
|
|
246
|
+
S::TcpStream: From<socket2::Socket>,
|
|
247
|
+
{
|
|
248
|
+
/// Builds the scheduler while preserving the initial fallback delay.
|
|
249
|
+
fn new(connecting: ConnectingTcp<S>, happy_eyeballs_timeout: Option<Duration>) -> Self {
|
|
250
|
+
let (initial_delay, fallback) = match connecting.fallback {
|
|
251
|
+
Some(fallback) => (Some(fallback.delay), Some(fallback.remote)),
|
|
252
|
+
None => (None, None),
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
Self {
|
|
256
|
+
preferred: connecting.preferred,
|
|
257
|
+
fallback,
|
|
258
|
+
initial_delay,
|
|
259
|
+
next_fallback: false,
|
|
260
|
+
happy_eyeballs_timeout,
|
|
261
|
+
attempts: VecDeque::with_capacity(MAX_PARALLEL_CONNECT_ATTEMPTS),
|
|
262
|
+
next_attempt_order: 0,
|
|
263
|
+
first_error: None,
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/// Returns whether any resolved address has not been attempted.
|
|
268
|
+
fn has_remaining_addrs(&self) -> bool {
|
|
269
|
+
!self.preferred.addrs.is_empty()
|
|
270
|
+
|| self
|
|
271
|
+
.fallback
|
|
272
|
+
.as_ref()
|
|
273
|
+
.is_some_and(|fallback| !fallback.addrs.is_empty())
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/// Alternates address families while both still have candidates.
|
|
277
|
+
fn next_remote(&mut self) -> Option<&mut ConnectingTcpRemote<S>> {
|
|
278
|
+
let has_preferred = !self.preferred.addrs.is_empty();
|
|
279
|
+
let has_fallback = self
|
|
280
|
+
.fallback
|
|
281
|
+
.as_ref()
|
|
282
|
+
.is_some_and(|fallback| !fallback.addrs.is_empty());
|
|
283
|
+
|
|
284
|
+
let use_fallback = match (self.next_fallback, has_preferred, has_fallback) {
|
|
285
|
+
(_, false, false) => return None,
|
|
286
|
+
(true, _, true) | (_, false, true) => true,
|
|
287
|
+
_ => false,
|
|
288
|
+
};
|
|
289
|
+
self.next_fallback = !use_fallback;
|
|
290
|
+
|
|
291
|
+
if use_fallback {
|
|
292
|
+
self.fallback.as_mut()
|
|
293
|
+
} else {
|
|
294
|
+
Some(&mut self.preferred)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/// Keeps the error from the earliest failed attempt.
|
|
299
|
+
fn record_error(&mut self, order: usize, error: ConnectError) {
|
|
300
|
+
match &self.first_error {
|
|
301
|
+
Some((first_order, _)) if *first_order <= order => {}
|
|
302
|
+
_ => self.first_error = Some((order, error)),
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/// Starts the next candidate, skipping synchronous socket setup failures.
|
|
307
|
+
fn launch_next(&mut self, config: &TcpOptions) -> bool {
|
|
308
|
+
while let Some(remote) = self.next_remote() {
|
|
309
|
+
let Some((addr, result)) = remote.connect_next(config) else {
|
|
310
|
+
continue;
|
|
311
|
+
};
|
|
312
|
+
let order = self.next_attempt_order;
|
|
313
|
+
self.next_attempt_order = self.next_attempt_order.saturating_add(1);
|
|
314
|
+
|
|
315
|
+
match result {
|
|
316
|
+
Ok(future) => {
|
|
317
|
+
if order != 0 {
|
|
318
|
+
self.initial_delay = None;
|
|
153
319
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
320
|
+
self.attempts.push_back(ConnectingTcpAttempt {
|
|
321
|
+
addr,
|
|
322
|
+
order,
|
|
323
|
+
future,
|
|
324
|
+
});
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
Err(error) => {
|
|
328
|
+
let error = error.with_addr(addr);
|
|
329
|
+
trace!("connect error for {}: {:?}", addr, error);
|
|
330
|
+
self.record_error(order, error);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
false
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/// Polls every active attempt and reports failure only when none remain.
|
|
339
|
+
fn poll_attempts(&mut self, cx: &mut Context<'_>) -> Poll<TcpEvent<S::Connection>> {
|
|
340
|
+
let mut index = 0;
|
|
341
|
+
let mut failed = false;
|
|
342
|
+
while let Some(attempt) = self.attempts.get_mut(index) {
|
|
343
|
+
let addr = attempt.addr;
|
|
344
|
+
let order = attempt.order;
|
|
345
|
+
let result = Pin::new(&mut attempt.future).poll(cx);
|
|
346
|
+
|
|
347
|
+
match result {
|
|
348
|
+
Poll::Pending => index += 1,
|
|
349
|
+
Poll::Ready(result) => {
|
|
350
|
+
let _ = self.attempts.remove(index);
|
|
351
|
+
match result {
|
|
352
|
+
Ok(connection) => {
|
|
353
|
+
debug!("connected to {}", addr);
|
|
354
|
+
return Poll::Ready(TcpEvent::Connected(connection));
|
|
355
|
+
}
|
|
356
|
+
Err(error) => {
|
|
357
|
+
let error = ConnectError::tcp(error).with_addr(addr);
|
|
358
|
+
trace!("connect error for {}: {:?}", addr, error);
|
|
359
|
+
self.record_error(order, error);
|
|
360
|
+
failed = true;
|
|
361
|
+
}
|
|
160
362
|
}
|
|
161
363
|
}
|
|
162
364
|
}
|
|
163
365
|
}
|
|
164
366
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
367
|
+
if failed && self.attempts.is_empty() {
|
|
368
|
+
Poll::Ready(TcpEvent::AllAttemptsFailed)
|
|
369
|
+
} else {
|
|
370
|
+
Poll::Pending
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/// Cancels the oldest attempt when the parallel limit is reached.
|
|
375
|
+
fn make_room_for_next_attempt(&mut self) {
|
|
376
|
+
if self.attempts.len() < MAX_PARALLEL_CONNECT_ATTEMPTS {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if let Some(attempt) = self.attempts.pop_front() {
|
|
381
|
+
trace!("canceling stale connection attempt to {}", attempt.addr);
|
|
382
|
+
drop(attempt);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/// Takes the earliest error or reports that no address was reachable.
|
|
387
|
+
fn take_error(&mut self) -> ConnectError {
|
|
388
|
+
self.first_error
|
|
389
|
+
.take()
|
|
390
|
+
.map(|(_, error)| error)
|
|
391
|
+
.unwrap_or_else(ConnectError::network_unreachable)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/// Runs the complete address race under one shared deadline.
|
|
395
|
+
async fn connect(mut self, config: &TcpOptions) -> Result<S::Connection, ConnectError> {
|
|
396
|
+
// One deadline covers the complete address race. Dividing it by the
|
|
397
|
+
// number of resolved addresses can make every attempt unusably short.
|
|
398
|
+
let timeout = self
|
|
399
|
+
.preferred
|
|
400
|
+
.connect_timeout
|
|
401
|
+
.map(|duration| self.preferred.connector.sleep(duration));
|
|
402
|
+
connect_with_timeout(self.connect_inner(config), timeout).await
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/// Drives staggered attempts until one connects or all addresses are exhausted.
|
|
406
|
+
async fn connect_inner(&mut self, config: &TcpOptions) -> Result<S::Connection, ConnectError> {
|
|
407
|
+
loop {
|
|
408
|
+
if self.attempts.is_empty() && !self.launch_next(config) {
|
|
409
|
+
return Err(self.take_error());
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// RFC 8305 section 5 starts later addresses after a short delay
|
|
413
|
+
// while earlier attempts remain active.
|
|
414
|
+
// https://www.rfc-editor.org/rfc/rfc8305.html#section-5
|
|
415
|
+
let event = match (self.happy_eyeballs_timeout, self.has_remaining_addrs()) {
|
|
416
|
+
(Some(delay), true) => {
|
|
417
|
+
let sleep = self
|
|
418
|
+
.initial_delay
|
|
419
|
+
.take()
|
|
420
|
+
.unwrap_or_else(|| self.preferred.connector.sleep(delay));
|
|
421
|
+
let mut sleep = pin!(sleep);
|
|
422
|
+
std::future::poll_fn(|cx| match self.poll_attempts(cx) {
|
|
423
|
+
Poll::Ready(event) => Poll::Ready(event),
|
|
424
|
+
Poll::Pending => sleep.as_mut().poll(cx).map(|()| TcpEvent::DelayElapsed),
|
|
425
|
+
})
|
|
426
|
+
.await
|
|
427
|
+
}
|
|
428
|
+
_ => std::future::poll_fn(|cx| self.poll_attempts(cx)).await,
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
match event {
|
|
432
|
+
TcpEvent::Connected(connection) => return Ok(connection),
|
|
433
|
+
TcpEvent::AllAttemptsFailed => {}
|
|
434
|
+
TcpEvent::DelayElapsed => self.make_room_for_next_attempt(),
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
self.launch_next(config);
|
|
171
438
|
}
|
|
172
439
|
}
|
|
173
440
|
}
|
|
174
441
|
|
|
442
|
+
/// Applies one optional deadline to a complete connection operation.
|
|
443
|
+
async fn connect_with_timeout<C, F, T>(connecting: F, timeout: Option<T>) -> Result<C, ConnectError>
|
|
444
|
+
where
|
|
445
|
+
F: Future<Output = Result<C, ConnectError>>,
|
|
446
|
+
T: Future<Output = ()>,
|
|
447
|
+
{
|
|
448
|
+
let Some(timeout) = timeout else {
|
|
449
|
+
return connecting.await;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// Poll the connection first so a result ready at the deadline wins the
|
|
453
|
+
// tie, matching Tokio and Tower timeout semantics.
|
|
454
|
+
match futures_util::future::select(pin!(connecting), pin!(timeout)).await {
|
|
455
|
+
Either::Left((result, _)) => result,
|
|
456
|
+
Either::Right(((), _)) => Err(ConnectError::tcp(io::Error::new(
|
|
457
|
+
io::ErrorKind::TimedOut,
|
|
458
|
+
"connect timeout",
|
|
459
|
+
))),
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
175
463
|
fn bind_local_address(
|
|
176
464
|
socket: &socket2::Socket,
|
|
177
465
|
dst_addr: &SocketAddr,
|
|
@@ -203,9 +491,8 @@ fn bind_local_address(
|
|
|
203
491
|
fn connect<S: TcpConnector>(
|
|
204
492
|
addr: &SocketAddr,
|
|
205
493
|
config: &TcpOptions,
|
|
206
|
-
connect_timeout: Option<Duration>,
|
|
207
494
|
connector: &S,
|
|
208
|
-
) -> Result<
|
|
495
|
+
) -> Result<S::Future, ConnectError>
|
|
209
496
|
where
|
|
210
497
|
S::TcpStream: From<socket2::Socket>,
|
|
211
498
|
{
|
|
@@ -221,10 +508,10 @@ where
|
|
|
221
508
|
.set_nonblocking(true)
|
|
222
509
|
.map_err(ConnectError::m("tcp set_nonblocking error"))?;
|
|
223
510
|
|
|
224
|
-
if let Some(tcp_keepalive) = &config.tcp_keepalive.into_tcpkeepalive()
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
511
|
+
if let Some(tcp_keepalive) = &config.tcp_keepalive.into_tcpkeepalive()
|
|
512
|
+
&& let Err(_e) = socket.set_tcp_keepalive(tcp_keepalive)
|
|
513
|
+
{
|
|
514
|
+
warn!("tcp set_keepalive error: {_e}");
|
|
228
515
|
}
|
|
229
516
|
|
|
230
517
|
// That this only works for some socket types, particularly AF_INET sockets.
|
|
@@ -283,10 +570,10 @@ where
|
|
|
283
570
|
}
|
|
284
571
|
|
|
285
572
|
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
286
|
-
if let Some(tcp_user_timeout) = &config.tcp_user_timeout
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
573
|
+
if let Some(tcp_user_timeout) = &config.tcp_user_timeout
|
|
574
|
+
&& let Err(_e) = socket.set_tcp_user_timeout(Some(*tcp_user_timeout))
|
|
575
|
+
{
|
|
576
|
+
warn!("tcp set_tcp_user_timeout error: {_e}");
|
|
290
577
|
}
|
|
291
578
|
|
|
292
579
|
bind_local_address(
|
|
@@ -297,84 +584,35 @@ where
|
|
|
297
584
|
)
|
|
298
585
|
.map_err(ConnectError::m("tcp bind local error"))?;
|
|
299
586
|
|
|
300
|
-
if config.reuse_address
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
587
|
+
if config.reuse_address
|
|
588
|
+
&& let Err(_e) = socket.set_reuse_address(true)
|
|
589
|
+
{
|
|
590
|
+
warn!("tcp set_reuse_address error: {_e}");
|
|
304
591
|
}
|
|
305
592
|
|
|
306
|
-
if let Some(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
593
|
+
if let Some(linger) = config.linger
|
|
594
|
+
&& let Err(_e) = socket.set_linger(Some(linger))
|
|
595
|
+
{
|
|
596
|
+
warn!("tcp set_linger error: {_e}");
|
|
310
597
|
}
|
|
311
598
|
|
|
312
|
-
if let Some(size) = config.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
599
|
+
if let Some(size) = config.send_buffer_size
|
|
600
|
+
&& let Err(_e) = socket.set_send_buffer_size(size)
|
|
601
|
+
{
|
|
602
|
+
warn!("tcp set_buffer_size error: {_e}");
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if let Some(size) = config.recv_buffer_size
|
|
606
|
+
&& let Err(_e) = socket.set_recv_buffer_size(size)
|
|
607
|
+
{
|
|
608
|
+
warn!("tcp set_recv_buffer_size error: {_e}");
|
|
316
609
|
}
|
|
317
610
|
|
|
318
611
|
if let Err(_e) = socket.set_tcp_nodelay(config.nodelay) {
|
|
319
612
|
warn!("tcp set_tcp_nodelay error: {_e}");
|
|
320
613
|
}
|
|
321
614
|
|
|
322
|
-
|
|
323
|
-
let sleep = connect_timeout.map(|dur| connector.sleep(dur));
|
|
324
|
-
|
|
325
|
-
Ok(async move {
|
|
326
|
-
match sleep {
|
|
327
|
-
Some(sleep) => match futures_util::future::select(pin!(sleep), pin!(connect)).await {
|
|
328
|
-
Either::Left(((), _)) => {
|
|
329
|
-
Err(io::Error::new(io::ErrorKind::TimedOut, "connect timeout").into())
|
|
330
|
-
}
|
|
331
|
-
Either::Right((Ok(s), _)) => Ok(s),
|
|
332
|
-
Either::Right((Err(e), _)) => Err(e.into()),
|
|
333
|
-
},
|
|
334
|
-
None => connect.await.map_err(Into::into),
|
|
335
|
-
}
|
|
336
|
-
.map_err(ConnectError::m("tcp connect error"))
|
|
337
|
-
})
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
impl<S: TcpConnector> ConnectingTcp<S>
|
|
341
|
-
where
|
|
342
|
-
S::TcpStream: From<socket2::Socket>,
|
|
343
|
-
{
|
|
344
|
-
pub(crate) async fn connect(
|
|
345
|
-
mut self,
|
|
346
|
-
config: &TcpOptions,
|
|
347
|
-
) -> Result<S::Connection, ConnectError> {
|
|
348
|
-
match self.fallback {
|
|
349
|
-
None => self.preferred.connect(config).await,
|
|
350
|
-
Some(mut fallback) => {
|
|
351
|
-
let preferred_fut = pin!(self.preferred.connect(config));
|
|
352
|
-
let fallback_fut = pin!(fallback.remote.connect(config));
|
|
353
|
-
let fallback_delay = pin!(fallback.delay);
|
|
354
|
-
|
|
355
|
-
let (result, future) =
|
|
356
|
-
match futures_util::future::select(preferred_fut, fallback_delay).await {
|
|
357
|
-
Either::Left((result, _fallback_delay)) => {
|
|
358
|
-
(result, Either::Right(fallback_fut))
|
|
359
|
-
}
|
|
360
|
-
Either::Right(((), preferred_fut)) => {
|
|
361
|
-
// Delay is done, start polling both the preferred and the fallback
|
|
362
|
-
futures_util::future::select(preferred_fut, fallback_fut)
|
|
363
|
-
.await
|
|
364
|
-
.factor_first()
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
if result.is_err() {
|
|
369
|
-
// Fallback to the remaining future (could be preferred or fallback)
|
|
370
|
-
// if we get an error
|
|
371
|
-
future.await
|
|
372
|
-
} else {
|
|
373
|
-
result
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
615
|
+
Ok(connector.connect(socket.into(), *addr))
|
|
378
616
|
}
|
|
379
617
|
|
|
380
618
|
// Not publicly exported (so missing_docs doesn't trigger).
|
|
@@ -396,6 +634,22 @@ impl ConnectError {
|
|
|
396
634
|
}
|
|
397
635
|
}
|
|
398
636
|
|
|
637
|
+
/// Wraps an error produced while opening a TCP connection.
|
|
638
|
+
fn tcp<E>(cause: E) -> ConnectError
|
|
639
|
+
where
|
|
640
|
+
E: Into<BoxError>,
|
|
641
|
+
{
|
|
642
|
+
ConnectError::new("tcp connect error", cause)
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/// Creates the fallback error used when no address can be attempted.
|
|
646
|
+
fn network_unreachable() -> ConnectError {
|
|
647
|
+
ConnectError::tcp(io::Error::new(
|
|
648
|
+
io::ErrorKind::NotConnected,
|
|
649
|
+
"Network unreachable",
|
|
650
|
+
))
|
|
651
|
+
}
|
|
652
|
+
|
|
399
653
|
pub(crate) fn dns<E>(cause: E) -> ConnectError
|
|
400
654
|
where
|
|
401
655
|
E: Into<BoxError>,
|
|
@@ -409,6 +663,12 @@ impl ConnectError {
|
|
|
409
663
|
{
|
|
410
664
|
move |cause| ConnectError::new(msg, cause)
|
|
411
665
|
}
|
|
666
|
+
|
|
667
|
+
/// Attaches the address associated with this connection error.
|
|
668
|
+
fn with_addr(mut self, addr: SocketAddr) -> Self {
|
|
669
|
+
self.addr = Some(addr);
|
|
670
|
+
self
|
|
671
|
+
}
|
|
412
672
|
}
|
|
413
673
|
|
|
414
674
|
impl fmt::Debug for ConnectError {
|
|
@@ -444,6 +704,7 @@ pub(crate) struct TcpOptions {
|
|
|
444
704
|
pub happy_eyeballs_timeout: Option<Duration>,
|
|
445
705
|
pub nodelay: bool,
|
|
446
706
|
pub reuse_address: bool,
|
|
707
|
+
pub linger: Option<Duration>,
|
|
447
708
|
pub send_buffer_size: Option<usize>,
|
|
448
709
|
pub recv_buffer_size: Option<usize>,
|
|
449
710
|
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
@@ -559,3 +820,236 @@ impl TcpKeepaliveOptions {
|
|
|
559
820
|
if dirty { Some(ka) } else { None }
|
|
560
821
|
}
|
|
561
822
|
}
|
|
823
|
+
|
|
824
|
+
#[cfg(test)]
|
|
825
|
+
mod tests {
|
|
826
|
+
use std::{
|
|
827
|
+
io,
|
|
828
|
+
net::{Ipv6Addr, SocketAddr},
|
|
829
|
+
sync::{Arc, Mutex},
|
|
830
|
+
time::Duration,
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
use super::{
|
|
834
|
+
BoxConnecting, ConnectingTcp, TcpConnector, TcpKeepaliveOptions, TcpOptions,
|
|
835
|
+
connect_with_timeout,
|
|
836
|
+
};
|
|
837
|
+
use crate::{
|
|
838
|
+
conn::{Connected, Connection, net::SocketBindOptions},
|
|
839
|
+
dns,
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
#[derive(Default)]
|
|
843
|
+
struct TestState {
|
|
844
|
+
launched: Vec<SocketAddr>,
|
|
845
|
+
active: usize,
|
|
846
|
+
max_active: usize,
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
#[derive(Clone)]
|
|
850
|
+
struct TestConnector {
|
|
851
|
+
outcomes: Arc<[(SocketAddr, TestOutcome)]>,
|
|
852
|
+
state: Arc<Mutex<TestState>>,
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
#[derive(Clone, Copy)]
|
|
856
|
+
enum TestOutcome {
|
|
857
|
+
Pending,
|
|
858
|
+
SuccessAfter(Duration),
|
|
859
|
+
FailAfter(Duration),
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
struct ActiveAttempt(Arc<Mutex<TestState>>);
|
|
863
|
+
|
|
864
|
+
impl Drop for ActiveAttempt {
|
|
865
|
+
fn drop(&mut self) {
|
|
866
|
+
let mut state = self.0.lock().unwrap();
|
|
867
|
+
state.active -= 1;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
impl TestConnector {
|
|
872
|
+
fn new(success: SocketAddr) -> Self {
|
|
873
|
+
Self::with_outcomes([(success, TestOutcome::SuccessAfter(Duration::ZERO))])
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
fn with_outcomes(outcomes: impl IntoIterator<Item = (SocketAddr, TestOutcome)>) -> Self {
|
|
877
|
+
Self {
|
|
878
|
+
outcomes: outcomes.into_iter().collect::<Vec<_>>().into(),
|
|
879
|
+
state: Arc::new(Mutex::new(TestState::default())),
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
fn snapshot(&self) -> (Vec<SocketAddr>, usize, usize) {
|
|
884
|
+
let state = self.state.lock().unwrap();
|
|
885
|
+
(state.launched.clone(), state.active, state.max_active)
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
impl TcpConnector for TestConnector {
|
|
890
|
+
type TcpStream = std::net::TcpStream;
|
|
891
|
+
type Connection = ::tokio::io::DuplexStream;
|
|
892
|
+
type Error = io::Error;
|
|
893
|
+
type Future = BoxConnecting<Self::Connection, Self::Error>;
|
|
894
|
+
type Sleep = ::tokio::time::Sleep;
|
|
895
|
+
|
|
896
|
+
fn connect(&self, _socket: Self::TcpStream, addr: SocketAddr) -> Self::Future {
|
|
897
|
+
{
|
|
898
|
+
let mut state = self.state.lock().unwrap();
|
|
899
|
+
state.launched.push(addr);
|
|
900
|
+
state.active += 1;
|
|
901
|
+
state.max_active = state.max_active.max(state.active);
|
|
902
|
+
}
|
|
903
|
+
let outcome = self
|
|
904
|
+
.outcomes
|
|
905
|
+
.iter()
|
|
906
|
+
.find_map(|(candidate, outcome)| (*candidate == addr).then_some(*outcome))
|
|
907
|
+
.unwrap_or(TestOutcome::Pending);
|
|
908
|
+
let attempt = ActiveAttempt(self.state.clone());
|
|
909
|
+
|
|
910
|
+
Box::pin(async move {
|
|
911
|
+
let _attempt = attempt;
|
|
912
|
+
match outcome {
|
|
913
|
+
TestOutcome::Pending => std::future::pending().await,
|
|
914
|
+
TestOutcome::SuccessAfter(delay) => {
|
|
915
|
+
::tokio::time::sleep(delay).await;
|
|
916
|
+
Ok(::tokio::io::duplex(64).0)
|
|
917
|
+
}
|
|
918
|
+
TestOutcome::FailAfter(delay) => {
|
|
919
|
+
::tokio::time::sleep(delay).await;
|
|
920
|
+
Err(io::ErrorKind::ConnectionRefused.into())
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
})
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
fn sleep(&self, duration: Duration) -> Self::Sleep {
|
|
927
|
+
::tokio::time::sleep(duration)
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
impl Connection for ::tokio::io::DuplexStream {
|
|
932
|
+
fn connected(&self) -> Connected {
|
|
933
|
+
Connected::new()
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
fn tcp_options(
|
|
938
|
+
happy_eyeballs_timeout: Option<Duration>,
|
|
939
|
+
connect_timeout: Option<Duration>,
|
|
940
|
+
) -> TcpOptions {
|
|
941
|
+
TcpOptions {
|
|
942
|
+
enforce_http: false,
|
|
943
|
+
connect_timeout,
|
|
944
|
+
happy_eyeballs_timeout,
|
|
945
|
+
nodelay: false,
|
|
946
|
+
reuse_address: false,
|
|
947
|
+
linger: None,
|
|
948
|
+
send_buffer_size: None,
|
|
949
|
+
recv_buffer_size: None,
|
|
950
|
+
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
951
|
+
tcp_user_timeout: None,
|
|
952
|
+
tcp_keepalive: TcpKeepaliveOptions::default(),
|
|
953
|
+
socket_bind: SocketBindOptions::default(),
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
fn ipv4(last: u8) -> SocketAddr {
|
|
958
|
+
([192, 0, 2, last], 443).into()
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
fn ipv6(last: u16) -> SocketAddr {
|
|
962
|
+
(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, last), 443).into()
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
#[::tokio::test(start_paused = true)]
|
|
966
|
+
async fn races_resolved_addresses_in_order_with_a_bounded_set() {
|
|
967
|
+
let result =
|
|
968
|
+
connect_with_timeout(std::future::ready(Ok(())), Some(std::future::ready(()))).await;
|
|
969
|
+
assert!(
|
|
970
|
+
result.is_ok(),
|
|
971
|
+
"a ready connection should win a timeout tie"
|
|
972
|
+
);
|
|
973
|
+
|
|
974
|
+
let delay = Duration::from_millis(100);
|
|
975
|
+
let sequential = [ipv4(1), ipv4(2)];
|
|
976
|
+
let connector = TestConnector::with_outcomes([
|
|
977
|
+
(sequential[0], TestOutcome::FailAfter(delay / 2)),
|
|
978
|
+
(sequential[1], TestOutcome::SuccessAfter(Duration::ZERO)),
|
|
979
|
+
]);
|
|
980
|
+
let options = tcp_options(None, None);
|
|
981
|
+
let started = ::tokio::time::Instant::now();
|
|
982
|
+
|
|
983
|
+
ConnectingTcp::new(
|
|
984
|
+
dns::SocketAddrs::new(sequential.to_vec()),
|
|
985
|
+
&options,
|
|
986
|
+
connector.clone(),
|
|
987
|
+
)
|
|
988
|
+
.connect(&options)
|
|
989
|
+
.await
|
|
990
|
+
.unwrap();
|
|
991
|
+
|
|
992
|
+
assert_eq!(started.elapsed(), delay / 2);
|
|
993
|
+
assert_eq!(connector.snapshot(), (sequential.to_vec(), 0, 1));
|
|
994
|
+
|
|
995
|
+
let paced = [ipv4(1), ipv4(2), ipv4(3)];
|
|
996
|
+
let connector = TestConnector::with_outcomes([
|
|
997
|
+
(paced[1], TestOutcome::FailAfter(delay / 10)),
|
|
998
|
+
(paced[2], TestOutcome::SuccessAfter(Duration::ZERO)),
|
|
999
|
+
]);
|
|
1000
|
+
let options = tcp_options(Some(delay), None);
|
|
1001
|
+
let started = ::tokio::time::Instant::now();
|
|
1002
|
+
|
|
1003
|
+
ConnectingTcp::new(
|
|
1004
|
+
dns::SocketAddrs::new(paced.to_vec()),
|
|
1005
|
+
&options,
|
|
1006
|
+
connector.clone(),
|
|
1007
|
+
)
|
|
1008
|
+
.connect(&options)
|
|
1009
|
+
.await
|
|
1010
|
+
.unwrap();
|
|
1011
|
+
|
|
1012
|
+
assert_eq!(started.elapsed(), delay * 2);
|
|
1013
|
+
assert_eq!(connector.snapshot(), (paced.to_vec(), 0, 2));
|
|
1014
|
+
|
|
1015
|
+
let v4 = [ipv4(1), ipv4(2), ipv4(3), ipv4(4), ipv4(5), ipv4(6)];
|
|
1016
|
+
let v6 = [ipv6(1), ipv6(2)];
|
|
1017
|
+
let addrs = [v4.as_slice(), v6.as_slice()].concat();
|
|
1018
|
+
let expected = [v4[0], v6[0], v4[1], v6[1], v4[2], v4[3], v4[4], v4[5]];
|
|
1019
|
+
let connector = TestConnector::new(v4[5]);
|
|
1020
|
+
let started = ::tokio::time::Instant::now();
|
|
1021
|
+
|
|
1022
|
+
ConnectingTcp::new(dns::SocketAddrs::new(addrs), &options, connector.clone())
|
|
1023
|
+
.connect(&options)
|
|
1024
|
+
.await
|
|
1025
|
+
.unwrap();
|
|
1026
|
+
|
|
1027
|
+
assert_eq!(started.elapsed(), delay * 7);
|
|
1028
|
+
assert_eq!(connector.snapshot(), (expected.to_vec(), 0, 6));
|
|
1029
|
+
|
|
1030
|
+
let timeout = Duration::from_millis(250);
|
|
1031
|
+
let options = tcp_options(Some(delay), Some(timeout));
|
|
1032
|
+
let addrs = [ipv4(1), ipv4(2), ipv4(3), ipv4(4)];
|
|
1033
|
+
let connector = TestConnector::new(ipv4(255));
|
|
1034
|
+
let started = ::tokio::time::Instant::now();
|
|
1035
|
+
|
|
1036
|
+
let error = ConnectingTcp::new(
|
|
1037
|
+
dns::SocketAddrs::new(addrs.to_vec()),
|
|
1038
|
+
&options,
|
|
1039
|
+
connector.clone(),
|
|
1040
|
+
)
|
|
1041
|
+
.connect(&options)
|
|
1042
|
+
.await
|
|
1043
|
+
.unwrap_err();
|
|
1044
|
+
|
|
1045
|
+
assert_eq!(started.elapsed(), timeout);
|
|
1046
|
+
assert!(
|
|
1047
|
+
error
|
|
1048
|
+
.cause
|
|
1049
|
+
.as_deref()
|
|
1050
|
+
.and_then(|cause| cause.downcast_ref::<io::Error>())
|
|
1051
|
+
.is_some_and(|cause| cause.kind() == io::ErrorKind::TimedOut)
|
|
1052
|
+
);
|
|
1053
|
+
assert_eq!(connector.snapshot(), (addrs[..3].to_vec(), 0, 3));
|
|
1054
|
+
}
|
|
1055
|
+
}
|