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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +257 -102
  3. data/ext/wreq_rb/Cargo.toml +4 -4
  4. data/ext/wreq_rb/src/client.rs +1 -1
  5. data/lib/wreq-rb/version.rb +1 -1
  6. data/patches/0001-add-transfer-size-tracking.patch +11 -15
  7. data/vendor/wreq/Cargo.toml +9 -8
  8. data/vendor/wreq/README.md +5 -5
  9. data/vendor/wreq/bench/support/bench.rs +6 -2
  10. data/vendor/wreq/bench/support/client.rs +88 -1
  11. data/vendor/wreq/bench/support/exec.rs +0 -0
  12. data/vendor/wreq/bench/support/rt.rs +34 -0
  13. data/vendor/wreq/bench/support/server.rs +1 -1
  14. data/vendor/wreq/bench/support.rs +1 -15
  15. data/vendor/wreq/examples/cert_store.rs +13 -13
  16. data/vendor/wreq/examples/request_with_emulate.rs +1 -1
  17. data/vendor/wreq/examples/tcp_linger.rs +22 -0
  18. data/vendor/wreq/src/client/layer/client/pool.rs +17 -17
  19. data/vendor/wreq/src/client/layer/client.rs +2 -0
  20. data/vendor/wreq/src/client/layer/decoder.rs +71 -17
  21. data/vendor/wreq/src/client/layer/redirect/future.rs +49 -63
  22. data/vendor/wreq/src/client/layer/redirect/policy.rs +2 -26
  23. data/vendor/wreq/src/client/layer/redirect.rs +48 -60
  24. data/vendor/wreq/src/client/layer/retry.rs +12 -15
  25. data/vendor/wreq/src/client/layer/timeout/body.rs +27 -21
  26. data/vendor/wreq/src/client/layer/timeout/future.rs +33 -58
  27. data/vendor/wreq/src/client/layer/timeout.rs +8 -14
  28. data/vendor/wreq/src/client/request.rs +4 -0
  29. data/vendor/wreq/src/client.rs +53 -31
  30. data/vendor/wreq/src/conn/connector.rs +99 -129
  31. data/vendor/wreq/src/conn/http.rs +25 -18
  32. data/vendor/wreq/src/conn/net/tcp.rs +601 -107
  33. data/vendor/wreq/src/conn/proxy/socks.rs +6 -6
  34. data/vendor/wreq/src/conn/timeout.rs +166 -0
  35. data/vendor/wreq/src/conn.rs +5 -4
  36. data/vendor/wreq/src/cookie/jar.rs +1225 -0
  37. data/vendor/wreq/src/cookie/store.rs +321 -0
  38. data/vendor/wreq/src/cookie.rs +108 -612
  39. data/vendor/wreq/src/dns/resolve.rs +8 -2
  40. data/vendor/wreq/src/dns.rs +4 -4
  41. data/vendor/wreq/src/error.rs +53 -20
  42. data/vendor/wreq/src/lib.rs +1 -0
  43. data/vendor/wreq/src/proxy/matcher.rs +26 -12
  44. data/vendor/wreq/src/proxy/win.rs +39 -9
  45. data/vendor/wreq/src/redirect.rs +515 -100
  46. data/vendor/wreq/src/tls/conn.rs +3 -11
  47. data/vendor/wreq/src/tls/session.rs +7 -8
  48. data/vendor/wreq/src/tls/trust/store.rs +4 -4
  49. data/vendor/wreq/src/util.rs +23 -0
  50. data/vendor/wreq/tests/badssl.rs +72 -7
  51. data/vendor/wreq/tests/brotli.rs +1 -1
  52. data/vendor/wreq/tests/client.rs +24 -0
  53. data/vendor/wreq/tests/connector_layers.rs +8 -4
  54. data/vendor/wreq/tests/cookie.rs +59 -0
  55. data/vendor/wreq/tests/deflate.rs +1 -1
  56. data/vendor/wreq/tests/gzip.rs +53 -1
  57. data/vendor/wreq/tests/layers.rs +8 -4
  58. data/vendor/wreq/tests/redirect.rs +180 -97
  59. data/vendor/wreq/tests/timeouts.rs +47 -12
  60. data/vendor/wreq/tests/zstd.rs +1 -1
  61. metadata +7 -1
@@ -1,31 +1,35 @@
1
1
  use std::{
2
2
  borrow::Cow,
3
- future::Future,
4
- pin::Pin,
5
3
  sync::Arc,
6
4
  task::{Context, Poll},
7
5
  time::Duration,
8
6
  };
9
7
 
8
+ use futures_util::future::BoxFuture;
10
9
  use tokio_btls::SslStream;
11
10
  use tower::{
12
- BoxError, Service, ServiceBuilder, ServiceExt,
13
- timeout::TimeoutLayer,
14
- util::{BoxCloneSyncService, MapRequestLayer},
11
+ BoxError, Layer, Service, ServiceBuilder, ServiceExt,
12
+ util::{BoxCloneSyncService, Either, MapRequest, MapRequestLayer},
15
13
  };
16
14
 
17
15
  #[cfg(unix)]
18
16
  use super::net::UnixConnector;
19
17
  use super::{
20
- AsyncConnWithInfo, BoxedConnectorLayer, BoxedConnectorService, Conn, Connection, HttpConnector,
21
- TlsConn, TlsInfoFactory, Unnameable, descriptor::ConnectionDescriptor, http::HttpConnect,
22
- net::TcpConnector, proxy, verbose::Verbose,
18
+ AsyncConnWithInfo, BoxedConnectorLayer, BoxedTransportConnector, Conn, Connection,
19
+ HttpConnector, TlsConn, TlsInfoFactory, Unnameable,
20
+ descriptor::ConnectionDescriptor,
21
+ http::HttpConnect,
22
+ net::TcpConnector,
23
+ proxy,
24
+ timeout::{Timeout, TimeoutLayer},
25
+ verbose::Verbose,
23
26
  };
24
27
  use crate::{
25
28
  dns::DynResolver,
26
- error::{ProxyConnect, TimedOut, map_timeout_to_connector_error},
29
+ error::{ProxyConnect, map_timeout_to_connector_error},
27
30
  ext::UriExt,
28
31
  proxy::{Intercepted, Matcher as ProxyMatcher, matcher::Intercept},
32
+ rt::Timer,
29
33
  tls::{
30
34
  TlsOptions,
31
35
  conn::{
@@ -34,41 +38,51 @@ use crate::{
34
38
  },
35
39
  };
36
40
 
37
- type Connecting = Pin<Box<dyn Future<Output = Result<Conn, BoxError>> + Send>>;
38
-
39
- /// Configuration for the connector service.
41
+ /// Client-wide connection settings retained by each transport connector.
42
+ ///
43
+ /// These defaults control proxy selection, socket behavior, stream instrumentation, and TLS
44
+ /// metadata. Settings that can vary per request remain in [`ConnectionDescriptor`] and are
45
+ /// applied when a connection starts.
40
46
  #[derive(Clone)]
41
47
  struct Config {
42
48
  proxies: Arc<Vec<ProxyMatcher>>,
43
49
  verbose: Verbose,
44
50
  nodelay: bool,
45
51
  tls_info: bool,
46
- /// When there is a single timeout layer and no other layers,
47
- /// we embed it directly inside our base Service::call().
48
- /// This lets us avoid an extra `Box::pin` indirection layer
49
- /// since `tokio::time::Timeout` is `Unpin`
50
- timeout: Option<Duration>,
51
52
  }
52
53
 
53
- /// Builder for `Connector`.
54
+ /// Assembles the transport service graph used by a client.
55
+ ///
56
+ /// The builder owns DNS, TCP, TLS, and timeout configuration until [`ConnectorBuilder::build`]
57
+ /// places the timeout around the concrete connector and any user-provided layers.
54
58
  pub struct ConnectorBuilder {
55
59
  config: Config,
60
+ timer: Timer,
61
+ timeout: Option<Duration>,
56
62
  #[cfg(feature = "socks")]
57
63
  resolver: DynResolver,
58
64
  http: HttpConnector,
59
65
  builder: TlsConnectorBuilder,
60
66
  }
61
67
 
62
- /// Connector service that establishes connections.
63
- #[derive(Clone)]
64
- pub enum Connector {
65
- Simple(ConnectorService),
66
- WithLayers(BoxedConnectorService),
67
- }
68
-
69
- /// Service that establishes connections to HTTP servers.
68
+ /// Client-owned transport service selected when the connector graph is assembled.
69
+ ///
70
+ /// The left branch keeps the graph concrete when no custom layers are present. The right branch
71
+ /// wraps the type-erased custom-layer graph so it also accepts [`ConnectionDescriptor`]. Both
72
+ /// branches remain with the client and create a separately timed future for each connection
73
+ /// attempt.
74
+ pub type Connector = Either<
75
+ Timeout<TransportConnector>,
76
+ MapRequest<BoxedTransportConnector, fn(ConnectionDescriptor) -> Unnameable>,
77
+ >;
78
+
79
+ /// Establishes the transport consumed by the HTTP protocol layer.
80
+ ///
81
+ /// Each call selects the proxy path, applies request-specific socket and TLS settings, and returns
82
+ /// an established [`Conn`]. The service is cloned into each in-flight connection future, while its
83
+ /// immutable TLS builder is shared across those attempts.
70
84
  #[derive(Clone)]
71
- pub struct ConnectorService {
85
+ pub struct TransportConnector {
72
86
  config: Config,
73
87
  #[cfg(feature = "socks")]
74
88
  resolver: DynResolver,
@@ -80,6 +94,24 @@ pub struct ConnectorService {
80
94
  // ===== impl ConnectorBuilder =====
81
95
 
82
96
  impl ConnectorBuilder {
97
+ /// Creates a builder with the client's proxy and DNS configuration.
98
+ pub(crate) fn new(proxies: Vec<ProxyMatcher>, resolver: DynResolver) -> Self {
99
+ Self {
100
+ config: Config {
101
+ proxies: Arc::new(proxies),
102
+ verbose: Verbose::OFF,
103
+ nodelay: true,
104
+ tls_info: false,
105
+ },
106
+ timer: Timer::default(),
107
+ timeout: None,
108
+ #[cfg(feature = "socks")]
109
+ resolver: resolver.clone(),
110
+ http: HttpConnector::new(resolver, TcpConnector::new()),
111
+ builder: TlsConnector::builder(),
112
+ }
113
+ }
114
+
83
115
  /// Set the HTTP connector to use.
84
116
  #[inline]
85
117
  pub fn with_http<F>(mut self, call: F) -> ConnectorBuilder
@@ -101,12 +133,16 @@ impl ConnectorBuilder {
101
133
  }
102
134
 
103
135
  /// Set the connect timeout.
104
- ///
105
- /// If a domain resolves to multiple IP addresses, the timeout will be
106
- /// evenly divided across them.
107
136
  #[inline]
108
137
  pub fn timeout(mut self, timeout: Option<Duration>) -> ConnectorBuilder {
109
- self.config.timeout = timeout;
138
+ self.timeout = timeout;
139
+ self
140
+ }
141
+
142
+ /// Set the timer used to drive the connect timeout.
143
+ #[inline]
144
+ pub fn timer(mut self, timer: Timer) -> ConnectorBuilder {
145
+ self.timer = timer;
110
146
  self
111
147
  }
112
148
 
@@ -137,7 +173,8 @@ impl ConnectorBuilder {
137
173
  tls_options: Option<TlsOptions>,
138
174
  layers: Vec<BoxedConnectorLayer>,
139
175
  ) -> crate::Result<Connector> {
140
- let mut service = ConnectorService {
176
+ let timeout = TimeoutLayer::new(self.timer, self.timeout);
177
+ let service = TransportConnector {
141
178
  config: self.config,
142
179
  #[cfg(feature = "socks")]
143
180
  resolver: self.resolver.clone(),
@@ -150,12 +187,9 @@ impl ConnectorBuilder {
150
187
 
151
188
  // we have no user-provided layers, only use concrete types
152
189
  if layers.is_empty() {
153
- return Ok(Connector::Simple(service));
190
+ return Ok(Either::Left(timeout.layer(service)));
154
191
  }
155
192
 
156
- // user-provided layers exist, the timeout will be applied as an additional layer.
157
- let timeout = service.config.timeout.take();
158
-
159
193
  // otherwise we have user provided layers
160
194
  // so we need type erasure all the way through
161
195
  // as well as mapping the unnameable type of the layers back to ConnectionDescriptor for the
@@ -169,78 +203,25 @@ impl ConnectorBuilder {
169
203
  |service, layer| ServiceBuilder::new().layer(layer).service(service),
170
204
  );
171
205
 
172
- // now we handle the concrete stuff - any `connect_timeout`,
173
- // plus a final map_err layer we can use to cast default tower layer
174
- // errors to internal errors
175
- match timeout {
176
- Some(timeout) => {
177
- let service = ServiceBuilder::new()
178
- .layer(TimeoutLayer::new(timeout))
179
- .service(service)
180
- .map_err(map_timeout_to_connector_error);
181
-
182
- Ok(Connector::WithLayers(BoxCloneSyncService::new(service)))
183
- }
184
- None => {
185
- // no timeout, but still map err
186
- // no named timeout layer but we still map errors since
187
- // we might have user-provided timeout layer
188
- let service = ServiceBuilder::new()
189
- .service(service)
190
- .map_err(map_timeout_to_connector_error);
191
-
192
- Ok(Connector::WithLayers(BoxCloneSyncService::new(service)))
193
- }
194
- }
195
- }
196
- }
197
-
198
- // ===== impl Connector =====
199
-
200
- impl Connector {
201
- /// Creates a new [`Connector`] with the provided configuration and optional layers.
202
- pub(crate) fn builder(proxies: Vec<ProxyMatcher>, resolver: DynResolver) -> ConnectorBuilder {
203
- ConnectorBuilder {
204
- config: Config {
205
- proxies: Arc::new(proxies),
206
- verbose: Verbose::OFF,
207
- nodelay: true,
208
- tls_info: false,
209
- timeout: None,
210
- },
211
- #[cfg(feature = "socks")]
212
- resolver: resolver.clone(),
213
- http: HttpConnector::new(resolver, TcpConnector::new()),
214
- builder: TlsConnector::builder(),
215
- }
216
- }
217
- }
206
+ // Keep the built-in timeout outside user layers so it covers their work too.
207
+ // The final mapping also handles a tower timeout supplied by the caller.
208
+ let service = ServiceBuilder::new()
209
+ .layer(timeout)
210
+ .service(service)
211
+ .map_err(map_timeout_to_connector_error);
218
212
 
219
- impl Service<ConnectionDescriptor> for Connector {
220
- type Response = Conn;
221
- type Error = BoxError;
222
- type Future = Connecting;
223
-
224
- #[inline]
225
- fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
226
- match self {
227
- Connector::Simple(service) => service.poll_ready(cx),
228
- Connector::WithLayers(service) => service.poll_ready(cx),
229
- }
230
- }
213
+ let service = MapRequest::new(
214
+ BoxCloneSyncService::new(service),
215
+ Unnameable as fn(ConnectionDescriptor) -> Unnameable,
216
+ );
231
217
 
232
- #[inline]
233
- fn call(&mut self, descriptor: ConnectionDescriptor) -> Self::Future {
234
- match self {
235
- Connector::Simple(service) => service.call(descriptor),
236
- Connector::WithLayers(service) => service.call(Unnameable(descriptor)),
237
- }
218
+ Ok(Either::Right(service))
238
219
  }
239
220
  }
240
221
 
241
- // ===== impl ConnectorService =====
222
+ // ===== impl TransportConnector =====
242
223
 
243
- impl ConnectorService {
224
+ impl TransportConnector {
244
225
  fn build_https_connector(
245
226
  &self,
246
227
  https: bool,
@@ -503,41 +484,30 @@ impl ConnectorService {
503
484
  async fn connect_auto(self, req: ConnectionDescriptor) -> Result<Conn, BoxError> {
504
485
  debug!("starting new connection: {:?}", req.uri());
505
486
 
506
- let timeout = self.config.timeout;
507
-
508
487
  // Determine if a proxy should be used for this request.
509
- let fut = async {
510
- let intercepted = req
511
- .proxy()
512
- .and_then(|prox| prox.intercept(req.uri()))
513
- .or_else(|| {
514
- self.config
515
- .proxies
516
- .iter()
517
- .find_map(|prox| prox.intercept(req.uri()))
518
- });
519
-
520
- // If a proxy is matched, connect via proxy; otherwise, connect directly.
521
- if let Some(intercepted) = intercepted {
522
- self.connect_via_proxy(req, intercepted).await
523
- } else {
524
- self.connect_auto_proxy(req, None).await
525
- }
526
- };
527
-
528
- // Apply timeout if configured.
529
- if let Some(to) = timeout {
530
- tokio::time::timeout(to, fut).await.map_err(|_| TimedOut)?
488
+ let intercepted = req
489
+ .proxy()
490
+ .and_then(|prox| prox.intercept(req.uri()))
491
+ .or_else(|| {
492
+ self.config
493
+ .proxies
494
+ .iter()
495
+ .find_map(|prox| prox.intercept(req.uri()))
496
+ });
497
+
498
+ // If a proxy is matched, connect via proxy; otherwise, connect directly.
499
+ if let Some(intercepted) = intercepted {
500
+ self.connect_via_proxy(req, intercepted).await
531
501
  } else {
532
- fut.await
502
+ self.connect_auto_proxy(req, None).await
533
503
  }
534
504
  }
535
505
  }
536
506
 
537
- impl Service<ConnectionDescriptor> for ConnectorService {
507
+ impl Service<ConnectionDescriptor> for TransportConnector {
538
508
  type Response = Conn;
539
509
  type Error = BoxError;
540
- type Future = Connecting;
510
+ type Future = BoxFuture<'static, Result<Conn, BoxError>>;
541
511
 
542
512
  #[inline]
543
513
  fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
@@ -20,7 +20,7 @@ use super::{
20
20
  tcp::{ConnectError, ConnectingTcp, TcpConnector, TcpKeepaliveOptions, TcpOptions},
21
21
  },
22
22
  };
23
- use crate::dns::{self, InternalResolve};
23
+ use crate::dns::{self, DnsResolver};
24
24
 
25
25
  static INVALID_NOT_HTTP: &str = "invalid URI, scheme is not http";
26
26
  static INVALID_MISSING_SCHEME: &str = "invalid URI, scheme is missing";
@@ -56,16 +56,19 @@ where
56
56
  /// Set that all socket have `SO_REUSEADDR` set to the supplied value `reuse_address`.
57
57
  fn set_reuse_address(&mut self, reuse: bool);
58
58
 
59
+ /// Sets the value of the `SO_LINGER` option on the socket.
60
+ fn set_linger(&mut self, linger: Option<Duration>);
61
+
59
62
  /// Sets the value of the `TCP_USER_TIMEOUT` option on the socket.
60
63
  #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
61
64
  fn set_tcp_user_timeout(&mut self, time: Option<Duration>);
62
65
 
63
- /// Set the connect timeout.
66
+ /// Set the timeout for the complete TCP connection race.
64
67
  fn set_connect_timeout(&mut self, dur: Option<Duration>);
65
68
 
66
- /// Set timeout for [RFC 6555 (Happy Eyeballs)][RFC 6555] algorithm.
69
+ /// Set the delay between connection attempts for [RFC 8305 (Happy Eyeballs v2)][RFC 8305].
67
70
  ///
68
- /// [RFC 6555]: https://tools.ietf.org/html/rfc6555
71
+ /// [RFC 8305]: https://www.rfc-editor.org/rfc/rfc8305.html#section-5
69
72
  fn set_happy_eyeballs_timeout(&mut self, dur: Option<Duration>);
70
73
 
71
74
  /// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration
@@ -156,6 +159,7 @@ impl<R, S> HttpConnector<R, S> {
156
159
  happy_eyeballs_timeout: Some(Duration::from_millis(300)),
157
160
  nodelay: false,
158
161
  reuse_address: false,
162
+ linger: None,
159
163
  send_buffer_size: None,
160
164
  recv_buffer_size: None,
161
165
  #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
@@ -178,7 +182,7 @@ impl<R, S> HttpConnector<R, S> {
178
182
 
179
183
  impl<R, S> HttpConnect for HttpConnector<R, S>
180
184
  where
181
- R: InternalResolve + Clone + Send + Sync + 'static,
185
+ R: DnsResolver + Clone + Send + Sync + 'static,
182
186
  R::Future: Send,
183
187
  S: TcpConnector,
184
188
  {
@@ -218,6 +222,14 @@ where
218
222
  self.config_mut().reuse_address = reuse_address;
219
223
  }
220
224
 
225
+ /// Sets the value of the SO_LINGER option on the socket.
226
+ ///
227
+ /// Default is `None`, which leaves the option untouched.
228
+ #[inline]
229
+ fn set_linger(&mut self, linger: Option<Duration>) {
230
+ self.config_mut().linger = linger;
231
+ }
232
+
221
233
  /// Sets the value of the TCP_USER_TIMEOUT option on the socket.
222
234
  #[inline]
223
235
  #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
@@ -225,10 +237,7 @@ where
225
237
  self.config_mut().tcp_user_timeout = time;
226
238
  }
227
239
 
228
- /// Set the connect timeout.
229
- ///
230
- /// If a domain resolves to multiple IP addresses, the timeout will be
231
- /// evenly divided across them.
240
+ /// Set the timeout for the complete TCP connection race.
232
241
  ///
233
242
  /// Default is `None`.
234
243
  #[inline]
@@ -236,18 +245,16 @@ where
236
245
  self.config_mut().connect_timeout = dur;
237
246
  }
238
247
 
239
- /// Set timeout for [RFC 6555 (Happy Eyeballs)][RFC 6555] algorithm.
248
+ /// Set the delay between connection attempts for [RFC 8305 (Happy Eyeballs v2)][RFC 8305].
240
249
  ///
241
- /// If hostname resolves to both IPv4 and IPv6 addresses and connection
242
- /// cannot be established using preferred address family before timeout
243
- /// elapses, then connector will in parallel attempt connection using other
244
- /// address family.
250
+ /// The first address is attempted immediately. Later addresses are started
251
+ /// after this delay without waiting for the previous attempt to finish.
245
252
  ///
246
253
  /// If `None`, parallel connection attempts are disabled.
247
254
  ///
248
255
  /// Default is 300 milliseconds.
249
256
  ///
250
- /// [RFC 6555]: https://tools.ietf.org/html/rfc6555
257
+ /// [RFC 8305]: https://www.rfc-editor.org/rfc/rfc8305.html#section-5
251
258
  #[inline]
252
259
  fn set_happy_eyeballs_timeout(&mut self, dur: Option<Duration>) {
253
260
  self.config_mut().happy_eyeballs_timeout = dur;
@@ -337,7 +344,7 @@ where
337
344
 
338
345
  impl<R, S> Service<Uri> for HttpConnector<R, S>
339
346
  where
340
- R: InternalResolve + Clone + Send + Sync + 'static,
347
+ R: DnsResolver + Clone + Send + Sync + 'static,
341
348
  R::Future: Send,
342
349
  S: TcpConnector,
343
350
  S::TcpStream: From<socket2::Socket>,
@@ -358,7 +365,7 @@ where
358
365
  let options = &this.options;
359
366
 
360
367
  let (host, port) = get_host_port(options, &dst)?;
361
- let host = host.trim_start_matches('[').trim_end_matches(']');
368
+ let host = crate::util::strip_ipv6_brackets(host);
362
369
 
363
370
  let addrs = if let Some(addrs) = dns::SocketAddrs::try_parse(host, port) {
364
371
  addrs
@@ -472,7 +479,7 @@ pin_project! {
472
479
 
473
480
  impl<R, S> Future for HttpConnecting<R, S>
474
481
  where
475
- R: InternalResolve,
482
+ R: DnsResolver,
476
483
  S: TcpConnector,
477
484
  {
478
485
  type Output = ConnectResult<S>;