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,1023 @@
1
+ use std::{
2
+ error::Error as StdError,
3
+ fmt,
4
+ future::Future,
5
+ io,
6
+ marker::PhantomData,
7
+ net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
8
+ pin::Pin,
9
+ sync::Arc,
10
+ task::{self, Poll},
11
+ time::Duration,
12
+ };
13
+
14
+ use futures_util::future::Either;
15
+ use http::uri::{Scheme, Uri};
16
+ use pin_project_lite::pin_project;
17
+ use socket2::TcpKeepalive;
18
+ use tokio::{
19
+ net::{TcpSocket, TcpStream},
20
+ time::Sleep,
21
+ };
22
+
23
+ use super::{Connected, Connection};
24
+ use crate::{
25
+ dns::{self, GaiResolver, InternalResolve, resolve},
26
+ error::BoxError,
27
+ };
28
+
29
+ /// A connector for the `http` scheme.
30
+ ///
31
+ /// Performs DNS resolution in a thread pool, and then connects over TCP.
32
+ ///
33
+ /// # Note
34
+ ///
35
+ /// Sets the [`HttpInfo`] value on responses, which includes
36
+ /// transport information such as the remote socket address used.
37
+ #[derive(Clone)]
38
+ pub struct HttpConnector<R = GaiResolver> {
39
+ config: Arc<Config>,
40
+ resolver: R,
41
+ }
42
+
43
+ /// Extra information about the transport when an HttpConnector is used.
44
+ ///
45
+ /// # Example
46
+ ///
47
+ /// ```
48
+ /// # fn doc(res: http::Response<()>) {
49
+ /// use crate::util::client::connect::HttpInfo;
50
+ ///
51
+ /// // res = http::Response
52
+ /// res.extensions().get::<HttpInfo>().map(|info| {
53
+ /// println!("remote addr = {}", info.remote_addr());
54
+ /// });
55
+ /// # }
56
+ /// ```
57
+ ///
58
+ /// # Note
59
+ ///
60
+ /// If a different connector is used besides [`HttpConnector`],
61
+ /// this value will not exist in the extensions. Consult that specific
62
+ /// connector to see what "extra" information it might provide to responses.
63
+ #[derive(Clone, Debug)]
64
+ pub struct HttpInfo {
65
+ remote_addr: SocketAddr,
66
+ local_addr: SocketAddr,
67
+ }
68
+
69
+ /// Options for configuring a TCP network connection.
70
+ ///
71
+ /// `TcpConnectOptions` allows fine-grained control over how TCP sockets
72
+ /// are created and connected. It can be used to:
73
+ ///
74
+ /// - Bind a socket to a specific **network interface**
75
+ /// - Bind to a **local IPv4 or IPv6 address**
76
+ ///
77
+ /// This is especially useful for scenarios involving:
78
+ /// - Virtual routing tables (e.g. Linux VRFs)
79
+ /// - Multiple NICs (network interface cards)
80
+ /// - Explicit source IP routing or firewall rules
81
+ ///
82
+ /// Platform-specific behavior is handled internally, with the interface binding
83
+ /// mechanism differing across Unix-like systems.
84
+ ///
85
+ /// # Platform Notes
86
+ ///
87
+ /// ## Interface binding (`set_interface`)
88
+ ///
89
+ /// - **Linux / Android / Fuchsia**: uses the `SO_BINDTODEVICE` socket option See [`man 7 socket`](https://man7.org/linux/man-pages/man7/socket.7.html)
90
+ ///
91
+ /// - **macOS / iOS / tvOS / watchOS / visionOS / illumos / Solaris**: uses the `IP_BOUND_IF` socket
92
+ /// option See [`man 7p ip`](https://docs.oracle.com/cd/E86824_01/html/E54777/ip-7p.html)
93
+ ///
94
+ /// Binding to an interface ensures that:
95
+ /// - **Outgoing packets** are sent through the specified interface
96
+ /// - **Incoming packets** are only accepted if received via that interface
97
+ ///
98
+ /// ❗ This only applies to certain socket types (e.g. `AF_INET`), and may require
99
+ /// elevated permissions (e.g. `CAP_NET_RAW` on Linux).
100
+ #[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
101
+ #[non_exhaustive]
102
+ pub struct TcpConnectOptions {
103
+ #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
104
+ interface: Option<std::borrow::Cow<'static, str>>,
105
+ #[cfg(any(
106
+ target_os = "illumos",
107
+ target_os = "ios",
108
+ target_os = "macos",
109
+ target_os = "solaris",
110
+ target_os = "tvos",
111
+ target_os = "visionos",
112
+ target_os = "watchos",
113
+ ))]
114
+ interface: Option<std::ffi::CString>,
115
+ local_ipv4: Option<Ipv4Addr>,
116
+ local_ipv6: Option<Ipv6Addr>,
117
+ }
118
+
119
+ impl TcpConnectOptions {
120
+ /// Sets the name of the network interface to bind the socket to.
121
+ ///
122
+ /// ## Platform behavior
123
+ /// - On Linux/Fuchsia/Android: sets `SO_BINDTODEVICE`
124
+ /// - On macOS/illumos/Solaris/iOS/etc.: sets `IP_BOUND_IF`
125
+ ///
126
+ /// If `interface` is `None`, the socket will not be explicitly bound to any device.
127
+ ///
128
+ /// # Errors
129
+ ///
130
+ /// On platforms that require a `CString` (e.g. macOS), this will return an error if the
131
+ /// interface name contains an internal null byte (`\0`), which is invalid in C strings.
132
+ ///
133
+ /// # See Also
134
+ /// - [VRF documentation](https://www.kernel.org/doc/Documentation/networking/vrf.txt)
135
+ /// - [`man 7 socket`](https://man7.org/linux/man-pages/man7/socket.7.html)
136
+ /// - [`man 7p ip`](https://docs.oracle.com/cd/E86824_01/html/E54777/ip-7p.html)
137
+ #[cfg(any(
138
+ target_os = "android",
139
+ target_os = "fuchsia",
140
+ target_os = "illumos",
141
+ target_os = "ios",
142
+ target_os = "linux",
143
+ target_os = "macos",
144
+ target_os = "solaris",
145
+ target_os = "tvos",
146
+ target_os = "visionos",
147
+ target_os = "watchos",
148
+ ))]
149
+ #[inline]
150
+ pub fn set_interface<S>(&mut self, interface: S) -> &mut Self
151
+ where
152
+ S: Into<std::borrow::Cow<'static, str>>,
153
+ {
154
+ #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
155
+ {
156
+ self.interface = Some(interface.into());
157
+ }
158
+
159
+ #[cfg(not(any(target_os = "android", target_os = "fuchsia", target_os = "linux")))]
160
+ {
161
+ self.interface = std::ffi::CString::new(interface.into().into_owned()).ok()
162
+ }
163
+
164
+ self
165
+ }
166
+
167
+ /// Set that all sockets are bound to the configured address before connection.
168
+ ///
169
+ /// If `None`, the sockets will not be bound.
170
+ ///
171
+ /// Default is `None`.
172
+ #[inline]
173
+ pub fn set_local_address(&mut self, local_addr: Option<IpAddr>) {
174
+ match local_addr {
175
+ Some(IpAddr::V4(a)) => {
176
+ self.local_ipv4 = Some(a);
177
+ }
178
+ Some(IpAddr::V6(a)) => {
179
+ self.local_ipv6 = Some(a);
180
+ }
181
+ _ => {}
182
+ };
183
+ }
184
+
185
+ /// Set that all sockets are bound to the configured IPv4 or IPv6 address (depending on host's
186
+ /// preferences) before connection.
187
+ #[inline]
188
+ pub fn set_local_addresses<V4, V6>(&mut self, local_ipv4: V4, local_ipv6: V6)
189
+ where
190
+ V4: Into<Option<Ipv4Addr>>,
191
+ V6: Into<Option<Ipv6Addr>>,
192
+ {
193
+ self.local_ipv4 = local_ipv4.into();
194
+ self.local_ipv6 = local_ipv6.into();
195
+ }
196
+ }
197
+
198
+ #[derive(Clone)]
199
+ struct Config {
200
+ connect_timeout: Option<Duration>,
201
+ enforce_http: bool,
202
+ happy_eyeballs_timeout: Option<Duration>,
203
+ tcp_keepalive_config: TcpKeepaliveConfig,
204
+ tcp_connect_options: TcpConnectOptions,
205
+ nodelay: bool,
206
+ reuse_address: bool,
207
+ send_buffer_size: Option<usize>,
208
+ recv_buffer_size: Option<usize>,
209
+ #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
210
+ tcp_user_timeout: Option<Duration>,
211
+ }
212
+
213
+ #[derive(Default, Debug, Clone, Copy)]
214
+ struct TcpKeepaliveConfig {
215
+ time: Option<Duration>,
216
+ #[cfg(any(
217
+ target_os = "android",
218
+ target_os = "dragonfly",
219
+ target_os = "freebsd",
220
+ target_os = "fuchsia",
221
+ target_os = "illumos",
222
+ target_os = "ios",
223
+ target_os = "visionos",
224
+ target_os = "linux",
225
+ target_os = "macos",
226
+ target_os = "netbsd",
227
+ target_os = "tvos",
228
+ target_os = "watchos",
229
+ target_os = "windows",
230
+ target_os = "cygwin",
231
+ ))]
232
+ interval: Option<Duration>,
233
+ #[cfg(any(
234
+ target_os = "android",
235
+ target_os = "dragonfly",
236
+ target_os = "freebsd",
237
+ target_os = "fuchsia",
238
+ target_os = "illumos",
239
+ target_os = "ios",
240
+ target_os = "visionos",
241
+ target_os = "linux",
242
+ target_os = "macos",
243
+ target_os = "netbsd",
244
+ target_os = "tvos",
245
+ target_os = "watchos",
246
+ target_os = "cygwin",
247
+ target_os = "windows",
248
+ ))]
249
+ retries: Option<u32>,
250
+ }
251
+
252
+ impl TcpKeepaliveConfig {
253
+ /// Converts into a `socket2::TcpKeealive` if there is any keep alive configuration.
254
+ fn into_tcpkeepalive(self) -> Option<TcpKeepalive> {
255
+ let mut dirty = false;
256
+ let mut ka = TcpKeepalive::new();
257
+ if let Some(time) = self.time {
258
+ ka = ka.with_time(time);
259
+ dirty = true
260
+ }
261
+
262
+ // Set the value of the `TCP_KEEPINTVL` option. On Windows, this sets the
263
+ // value of the `tcp_keepalive` struct's `keepaliveinterval` field.
264
+ //
265
+ // Sets the time interval between TCP keepalive probes.
266
+ //
267
+ // Some platforms specify this value in seconds, so sub-second
268
+ // specifications may be omitted.
269
+ #[cfg(any(
270
+ target_os = "android",
271
+ target_os = "dragonfly",
272
+ target_os = "freebsd",
273
+ target_os = "fuchsia",
274
+ target_os = "illumos",
275
+ target_os = "ios",
276
+ target_os = "visionos",
277
+ target_os = "linux",
278
+ target_os = "macos",
279
+ target_os = "netbsd",
280
+ target_os = "tvos",
281
+ target_os = "watchos",
282
+ target_os = "windows",
283
+ target_os = "cygwin",
284
+ ))]
285
+ {
286
+ if let Some(interval) = self.interval {
287
+ dirty = true;
288
+ ka = ka.with_interval(interval)
289
+ };
290
+ }
291
+
292
+ // Set the value of the `TCP_KEEPCNT` option.
293
+ //
294
+ // Set the maximum number of TCP keepalive probes that will be sent before
295
+ // dropping a connection, if TCP keepalive is enabled on this socket.
296
+ #[cfg(any(
297
+ target_os = "android",
298
+ target_os = "dragonfly",
299
+ target_os = "freebsd",
300
+ target_os = "fuchsia",
301
+ target_os = "illumos",
302
+ target_os = "ios",
303
+ target_os = "visionos",
304
+ target_os = "linux",
305
+ target_os = "macos",
306
+ target_os = "netbsd",
307
+ target_os = "tvos",
308
+ target_os = "watchos",
309
+ target_os = "cygwin",
310
+ target_os = "windows",
311
+ ))]
312
+ if let Some(retries) = self.retries {
313
+ dirty = true;
314
+ ka = ka.with_retries(retries)
315
+ };
316
+
317
+ if dirty { Some(ka) } else { None }
318
+ }
319
+ }
320
+
321
+ // ===== impl HttpConnector =====
322
+
323
+ impl Default for HttpConnector {
324
+ fn default() -> Self {
325
+ Self::new()
326
+ }
327
+ }
328
+
329
+ impl HttpConnector {
330
+ /// Construct a new HttpConnector.
331
+ pub fn new() -> HttpConnector {
332
+ HttpConnector::new_with_resolver(GaiResolver::new())
333
+ }
334
+ }
335
+
336
+ impl<R> HttpConnector<R> {
337
+ /// Construct a new [`HttpConnector`].
338
+ pub fn new_with_resolver(resolver: R) -> HttpConnector<R> {
339
+ HttpConnector {
340
+ config: Arc::new(Config {
341
+ connect_timeout: None,
342
+ enforce_http: true,
343
+ happy_eyeballs_timeout: Some(Duration::from_millis(300)),
344
+ tcp_keepalive_config: TcpKeepaliveConfig::default(),
345
+ tcp_connect_options: TcpConnectOptions::default(),
346
+ nodelay: false,
347
+ reuse_address: false,
348
+ send_buffer_size: None,
349
+ recv_buffer_size: None,
350
+ #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
351
+ tcp_user_timeout: None,
352
+ }),
353
+ resolver,
354
+ }
355
+ }
356
+
357
+ /// Option to enforce all `Uri`s have the `http` scheme.
358
+ ///
359
+ /// Enabled by default.
360
+ #[inline]
361
+ pub fn enforce_http(&mut self, is_enforced: bool) {
362
+ self.config_mut().enforce_http = is_enforced;
363
+ }
364
+
365
+ /// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration
366
+ /// to remain idle before sending TCP keepalive probes.
367
+ ///
368
+ /// If `None`, keepalive is disabled.
369
+ ///
370
+ /// Default is `None`.
371
+ #[inline]
372
+ pub fn set_keepalive(&mut self, time: Option<Duration>) {
373
+ self.config_mut().tcp_keepalive_config.time = time;
374
+ }
375
+
376
+ /// Set the duration between two successive TCP keepalive retransmissions,
377
+ /// if acknowledgement to the previous keepalive transmission is not received.
378
+ #[inline]
379
+ pub fn set_keepalive_interval(&mut self, interval: Option<Duration>) {
380
+ self.config_mut().tcp_keepalive_config.interval = interval;
381
+ }
382
+
383
+ /// Set the number of retransmissions to be carried out before declaring that remote end is not
384
+ /// available.
385
+ #[inline]
386
+ pub fn set_keepalive_retries(&mut self, retries: Option<u32>) {
387
+ self.config_mut().tcp_keepalive_config.retries = retries;
388
+ }
389
+
390
+ /// Set that all sockets have `SO_NODELAY` set to the supplied value `nodelay`.
391
+ ///
392
+ /// Default is `false`.
393
+ #[inline]
394
+ pub fn set_nodelay(&mut self, nodelay: bool) {
395
+ self.config_mut().nodelay = nodelay;
396
+ }
397
+
398
+ /// Sets the value of the SO_SNDBUF option on the socket.
399
+ #[inline]
400
+ pub fn set_send_buffer_size(&mut self, size: Option<usize>) {
401
+ self.config_mut().send_buffer_size = size;
402
+ }
403
+
404
+ /// Sets the value of the SO_RCVBUF option on the socket.
405
+ #[inline]
406
+ pub fn set_recv_buffer_size(&mut self, size: Option<usize>) {
407
+ self.config_mut().recv_buffer_size = size;
408
+ }
409
+
410
+ /// Set the connect options to be used when connecting.
411
+ #[inline]
412
+ pub fn set_connect_options(&mut self, opts: TcpConnectOptions) {
413
+ let this = self.config_mut();
414
+
415
+ #[cfg(any(
416
+ target_os = "android",
417
+ target_os = "fuchsia",
418
+ target_os = "illumos",
419
+ target_os = "ios",
420
+ target_os = "linux",
421
+ target_os = "macos",
422
+ target_os = "solaris",
423
+ target_os = "tvos",
424
+ target_os = "visionos",
425
+ target_os = "watchos",
426
+ ))]
427
+ if let Some(interface) = opts.interface {
428
+ this.tcp_connect_options.interface = Some(interface);
429
+ }
430
+
431
+ if let Some(local_ipv4) = opts.local_ipv4 {
432
+ this.tcp_connect_options
433
+ .set_local_address(Some(local_ipv4.into()));
434
+ }
435
+
436
+ if let Some(local_ipv6) = opts.local_ipv6 {
437
+ this.tcp_connect_options
438
+ .set_local_address(Some(local_ipv6.into()));
439
+ }
440
+ }
441
+
442
+ /// Set the connect timeout.
443
+ ///
444
+ /// If a domain resolves to multiple IP addresses, the timeout will be
445
+ /// evenly divided across them.
446
+ ///
447
+ /// Default is `None`.
448
+ #[inline]
449
+ pub fn set_connect_timeout(&mut self, dur: Option<Duration>) {
450
+ self.config_mut().connect_timeout = dur;
451
+ }
452
+
453
+ /// Set timeout for [RFC 6555 (Happy Eyeballs)][RFC 6555] algorithm.
454
+ ///
455
+ /// If hostname resolves to both IPv4 and IPv6 addresses and connection
456
+ /// cannot be established using preferred address family before timeout
457
+ /// elapses, then connector will in parallel attempt connection using other
458
+ /// address family.
459
+ ///
460
+ /// If `None`, parallel connection attempts are disabled.
461
+ ///
462
+ /// Default is 300 milliseconds.
463
+ ///
464
+ /// [RFC 6555]: https://tools.ietf.org/html/rfc6555
465
+ #[inline]
466
+ pub fn set_happy_eyeballs_timeout(&mut self, dur: Option<Duration>) {
467
+ self.config_mut().happy_eyeballs_timeout = dur;
468
+ }
469
+
470
+ /// Set that all socket have `SO_REUSEADDR` set to the supplied value `reuse_address`.
471
+ ///
472
+ /// Default is `false`.
473
+ #[inline]
474
+ pub fn set_reuse_address(&mut self, reuse_address: bool) -> &mut Self {
475
+ self.config_mut().reuse_address = reuse_address;
476
+ self
477
+ }
478
+
479
+ /// Sets the value of the TCP_USER_TIMEOUT option on the socket.
480
+ #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
481
+ #[inline]
482
+ pub fn set_tcp_user_timeout(&mut self, time: Option<Duration>) {
483
+ self.config_mut().tcp_user_timeout = time;
484
+ }
485
+
486
+ // private
487
+
488
+ fn config_mut(&mut self) -> &mut Config {
489
+ // If the are HttpConnector clones, this will clone the inner
490
+ // config. So mutating the config won't ever affect previous
491
+ // clones.
492
+ Arc::make_mut(&mut self.config)
493
+ }
494
+ }
495
+
496
+ static INVALID_NOT_HTTP: &str = "invalid URI, scheme is not http";
497
+ static INVALID_MISSING_SCHEME: &str = "invalid URI, scheme is missing";
498
+ static INVALID_MISSING_HOST: &str = "invalid URI, host is missing";
499
+
500
+ // R: Debug required for now to allow adding it to debug output later...
501
+ impl<R: fmt::Debug> fmt::Debug for HttpConnector<R> {
502
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503
+ f.debug_struct("HttpConnector").finish()
504
+ }
505
+ }
506
+
507
+ impl<R> tower::Service<Uri> for HttpConnector<R>
508
+ where
509
+ R: InternalResolve + Clone + Send + Sync + 'static,
510
+ R::Future: Send,
511
+ {
512
+ type Response = TcpStream;
513
+ type Error = ConnectError;
514
+ type Future = HttpConnecting<R>;
515
+
516
+ #[inline]
517
+ fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
518
+ self.resolver.poll_ready(cx).map_err(ConnectError::dns)
519
+ }
520
+
521
+ fn call(&mut self, dst: Uri) -> Self::Future {
522
+ let mut self_ = self.clone();
523
+ HttpConnecting {
524
+ fut: Box::pin(async move { self_.call_async(dst).await }),
525
+ _marker: PhantomData,
526
+ }
527
+ }
528
+ }
529
+
530
+ fn get_host_port<'u>(config: &Config, dst: &'u Uri) -> Result<(&'u str, u16), ConnectError> {
531
+ trace!(
532
+ "Http::connect; scheme={:?}, host={:?}, port={:?}",
533
+ dst.scheme(),
534
+ dst.host(),
535
+ dst.port(),
536
+ );
537
+
538
+ if config.enforce_http {
539
+ if dst.scheme() != Some(&Scheme::HTTP) {
540
+ return Err(ConnectError {
541
+ msg: INVALID_NOT_HTTP,
542
+ addr: None,
543
+ cause: None,
544
+ });
545
+ }
546
+ } else if dst.scheme().is_none() {
547
+ return Err(ConnectError {
548
+ msg: INVALID_MISSING_SCHEME,
549
+ addr: None,
550
+ cause: None,
551
+ });
552
+ }
553
+
554
+ let host = match dst.host() {
555
+ Some(s) => s,
556
+ None => {
557
+ return Err(ConnectError {
558
+ msg: INVALID_MISSING_HOST,
559
+ addr: None,
560
+ cause: None,
561
+ });
562
+ }
563
+ };
564
+ let port = match dst.port() {
565
+ Some(port) => port.as_u16(),
566
+ None => {
567
+ if dst.scheme() == Some(&Scheme::HTTPS) {
568
+ 443
569
+ } else {
570
+ 80
571
+ }
572
+ }
573
+ };
574
+
575
+ Ok((host, port))
576
+ }
577
+
578
+ impl<R> HttpConnector<R>
579
+ where
580
+ R: InternalResolve,
581
+ {
582
+ async fn call_async(&mut self, dst: Uri) -> Result<TcpStream, ConnectError> {
583
+ let config = &self.config;
584
+
585
+ let (host, port) = get_host_port(config, &dst)?;
586
+ let host = host.trim_start_matches('[').trim_end_matches(']');
587
+
588
+ // If the host is already an IP addr (v4 or v6),
589
+ // skip resolving the dns and start connecting right away.
590
+ let addrs = if let Some(addrs) = dns::SocketAddrs::try_parse(host, port) {
591
+ addrs
592
+ } else {
593
+ let addrs = resolve(&mut self.resolver, dns::Name::new(host.into()))
594
+ .await
595
+ .map_err(ConnectError::dns)?;
596
+ let addrs = addrs
597
+ .map(|mut addr| {
598
+ set_port(&mut addr, port, dst.port().is_some());
599
+ addr
600
+ })
601
+ .collect();
602
+ dns::SocketAddrs::new(addrs)
603
+ };
604
+
605
+ let c = ConnectingTcp::new(addrs, config);
606
+
607
+ let sock = c.connect().await?;
608
+
609
+ if let Err(_e) = sock.set_nodelay(config.nodelay) {
610
+ warn!("tcp set_nodelay error: {_e}");
611
+ }
612
+
613
+ Ok(sock)
614
+ }
615
+ }
616
+
617
+ impl Connection for TcpStream {
618
+ fn connected(&self) -> Connected {
619
+ let connected = Connected::new();
620
+ if let (Ok(remote_addr), Ok(local_addr)) = (self.peer_addr(), self.local_addr()) {
621
+ connected.extra(HttpInfo {
622
+ remote_addr,
623
+ local_addr,
624
+ })
625
+ } else {
626
+ connected
627
+ }
628
+ }
629
+ }
630
+
631
+ impl HttpInfo {
632
+ /// Get the remote address of the transport used.
633
+ pub fn remote_addr(&self) -> SocketAddr {
634
+ self.remote_addr
635
+ }
636
+
637
+ /// Get the local address of the transport used.
638
+ pub fn local_addr(&self) -> SocketAddr {
639
+ self.local_addr
640
+ }
641
+ }
642
+
643
+ pin_project! {
644
+ // Not publicly exported (so missing_docs doesn't trigger).
645
+ //
646
+ // We return this `Future` instead of the `Pin<Box<dyn Future>>` directly
647
+ // so that users don't rely on it fitting in a `Pin<Box<dyn Future>>` slot
648
+ // (and thus we can change the type in the future).
649
+ #[must_use = "futures do nothing unless polled"]
650
+ pub struct HttpConnecting<R> {
651
+ #[pin]
652
+ fut: BoxConnecting,
653
+ _marker: PhantomData<R>,
654
+ }
655
+ }
656
+
657
+ type ConnectResult = Result<TcpStream, ConnectError>;
658
+ type BoxConnecting = Pin<Box<dyn Future<Output = ConnectResult> + Send>>;
659
+
660
+ impl<R: InternalResolve> Future for HttpConnecting<R> {
661
+ type Output = ConnectResult;
662
+
663
+ fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
664
+ self.project().fut.poll(cx)
665
+ }
666
+ }
667
+
668
+ // Not publicly exported (so missing_docs doesn't trigger).
669
+ pub struct ConnectError {
670
+ msg: &'static str,
671
+ addr: Option<SocketAddr>,
672
+ cause: Option<BoxError>,
673
+ }
674
+
675
+ impl ConnectError {
676
+ fn new<E>(msg: &'static str, cause: E) -> ConnectError
677
+ where
678
+ E: Into<BoxError>,
679
+ {
680
+ ConnectError {
681
+ msg,
682
+ addr: None,
683
+ cause: Some(cause.into()),
684
+ }
685
+ }
686
+
687
+ fn dns<E>(cause: E) -> ConnectError
688
+ where
689
+ E: Into<BoxError>,
690
+ {
691
+ ConnectError::new("dns error", cause)
692
+ }
693
+
694
+ fn m<E>(msg: &'static str) -> impl FnOnce(E) -> ConnectError
695
+ where
696
+ E: Into<BoxError>,
697
+ {
698
+ move |cause| ConnectError::new(msg, cause)
699
+ }
700
+ }
701
+
702
+ impl fmt::Debug for ConnectError {
703
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
704
+ let mut b = f.debug_tuple("ConnectError");
705
+ b.field(&self.msg);
706
+ if let Some(ref addr) = self.addr {
707
+ b.field(addr);
708
+ }
709
+ if let Some(ref cause) = self.cause {
710
+ b.field(cause);
711
+ }
712
+ b.finish()
713
+ }
714
+ }
715
+
716
+ impl fmt::Display for ConnectError {
717
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
718
+ f.write_str(self.msg)
719
+ }
720
+ }
721
+
722
+ impl StdError for ConnectError {
723
+ fn source(&self) -> Option<&(dyn StdError + 'static)> {
724
+ self.cause.as_ref().map(|e| &**e as _)
725
+ }
726
+ }
727
+
728
+ struct ConnectingTcp<'a> {
729
+ preferred: ConnectingTcpRemote,
730
+ fallback: Option<ConnectingTcpFallback>,
731
+ config: &'a Config,
732
+ }
733
+
734
+ impl<'a> ConnectingTcp<'a> {
735
+ fn new(remote_addrs: dns::SocketAddrs, config: &'a Config) -> Self {
736
+ if let Some(fallback_timeout) = config.happy_eyeballs_timeout {
737
+ let (preferred_addrs, fallback_addrs) = remote_addrs.split_by_preference(
738
+ config.tcp_connect_options.local_ipv4,
739
+ config.tcp_connect_options.local_ipv6,
740
+ );
741
+ if fallback_addrs.is_empty() {
742
+ return ConnectingTcp {
743
+ preferred: ConnectingTcpRemote::new(preferred_addrs, config.connect_timeout),
744
+ fallback: None,
745
+ config,
746
+ };
747
+ }
748
+
749
+ ConnectingTcp {
750
+ preferred: ConnectingTcpRemote::new(preferred_addrs, config.connect_timeout),
751
+ fallback: Some(ConnectingTcpFallback {
752
+ delay: tokio::time::sleep(fallback_timeout),
753
+ remote: ConnectingTcpRemote::new(fallback_addrs, config.connect_timeout),
754
+ }),
755
+ config,
756
+ }
757
+ } else {
758
+ ConnectingTcp {
759
+ preferred: ConnectingTcpRemote::new(remote_addrs, config.connect_timeout),
760
+ fallback: None,
761
+ config,
762
+ }
763
+ }
764
+ }
765
+ }
766
+
767
+ struct ConnectingTcpFallback {
768
+ delay: Sleep,
769
+ remote: ConnectingTcpRemote,
770
+ }
771
+
772
+ struct ConnectingTcpRemote {
773
+ addrs: dns::SocketAddrs,
774
+ connect_timeout: Option<Duration>,
775
+ }
776
+
777
+ impl ConnectingTcpRemote {
778
+ fn new(addrs: dns::SocketAddrs, connect_timeout: Option<Duration>) -> Self {
779
+ let connect_timeout = connect_timeout.and_then(|t| t.checked_div(addrs.len() as u32));
780
+
781
+ Self {
782
+ addrs,
783
+ connect_timeout,
784
+ }
785
+ }
786
+ }
787
+
788
+ impl ConnectingTcpRemote {
789
+ async fn connect(&mut self, config: &Config) -> Result<TcpStream, ConnectError> {
790
+ let mut err = None;
791
+ for addr in &mut self.addrs {
792
+ debug!("connecting to {}", addr);
793
+ match connect(&addr, config, self.connect_timeout)?.await {
794
+ Ok(tcp) => {
795
+ debug!("connected to {}", addr);
796
+ return Ok(tcp);
797
+ }
798
+ Err(mut e) => {
799
+ e.addr = Some(addr);
800
+ // Only return the first error; assume it’s the most relevant.
801
+ if err.is_none() {
802
+ err = Some(e);
803
+ }
804
+ }
805
+ }
806
+ }
807
+
808
+ match err {
809
+ Some(e) => Err(e),
810
+ None => Err(ConnectError::new(
811
+ "tcp connect error",
812
+ io::Error::new(io::ErrorKind::NotConnected, "Network unreachable"),
813
+ )),
814
+ }
815
+ }
816
+ }
817
+
818
+ fn bind_local_address(
819
+ socket: &socket2::Socket,
820
+ dst_addr: &SocketAddr,
821
+ local_addr_ipv4: &Option<Ipv4Addr>,
822
+ local_addr_ipv6: &Option<Ipv6Addr>,
823
+ ) -> io::Result<()> {
824
+ match (*dst_addr, local_addr_ipv4, local_addr_ipv6) {
825
+ (SocketAddr::V4(_), Some(addr), _) => {
826
+ socket.bind(&SocketAddr::new((*addr).into(), 0).into())?;
827
+ }
828
+ (SocketAddr::V6(_), _, Some(addr)) => {
829
+ socket.bind(&SocketAddr::new((*addr).into(), 0).into())?;
830
+ }
831
+ _ => {
832
+ if cfg!(windows) {
833
+ // Windows requires a socket be bound before calling connect
834
+ let any: SocketAddr = match *dst_addr {
835
+ SocketAddr::V4(_) => ([0, 0, 0, 0], 0).into(),
836
+ SocketAddr::V6(_) => ([0, 0, 0, 0, 0, 0, 0, 0], 0).into(),
837
+ };
838
+ socket.bind(&any.into())?;
839
+ }
840
+ }
841
+ }
842
+
843
+ Ok(())
844
+ }
845
+
846
+ fn connect(
847
+ addr: &SocketAddr,
848
+ config: &Config,
849
+ connect_timeout: Option<Duration>,
850
+ ) -> Result<impl Future<Output = Result<TcpStream, ConnectError>>, ConnectError> {
851
+ // TODO(eliza): if Tokio's `TcpSocket` gains support for setting the
852
+ // keepalive timeout, it would be nice to use that instead of socket2,
853
+ // and avoid the unsafe `into_raw_fd`/`from_raw_fd` dance...
854
+ use socket2::{Domain, Protocol, Socket, Type};
855
+
856
+ let domain = Domain::for_address(*addr);
857
+ let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))
858
+ .map_err(ConnectError::m("tcp open error"))?;
859
+
860
+ // When constructing a Tokio `TcpSocket` from a raw fd/socket, the user is
861
+ // responsible for ensuring O_NONBLOCK is set.
862
+ socket
863
+ .set_nonblocking(true)
864
+ .map_err(ConnectError::m("tcp set_nonblocking error"))?;
865
+
866
+ if let Some(tcp_keepalive) = &config.tcp_keepalive_config.into_tcpkeepalive() {
867
+ if let Err(_e) = socket.set_tcp_keepalive(tcp_keepalive) {
868
+ warn!("tcp set_keepalive error: {_e}");
869
+ }
870
+ }
871
+
872
+ // That this only works for some socket types, particularly AF_INET sockets.
873
+ #[cfg(any(
874
+ target_os = "android",
875
+ target_os = "fuchsia",
876
+ target_os = "illumos",
877
+ target_os = "ios",
878
+ target_os = "linux",
879
+ target_os = "macos",
880
+ target_os = "solaris",
881
+ target_os = "tvos",
882
+ target_os = "visionos",
883
+ target_os = "watchos",
884
+ ))]
885
+ if let Some(interface) = &config.tcp_connect_options.interface {
886
+ // On Linux-like systems, set the interface to bind using
887
+ // `SO_BINDTODEVICE`.
888
+ #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
889
+ socket
890
+ .bind_device(Some(interface.as_bytes()))
891
+ .map_err(ConnectError::m("tcp bind interface error"))?;
892
+
893
+ // On macOS-like and Solaris-like systems, we instead use `IP_BOUND_IF`.
894
+ // This socket option desires an integer index for the interface, so we
895
+ // must first determine the index of the requested interface name using
896
+ // `if_nametoindex`.
897
+ #[cfg(any(
898
+ target_os = "illumos",
899
+ target_os = "ios",
900
+ target_os = "macos",
901
+ target_os = "solaris",
902
+ target_os = "tvos",
903
+ target_os = "visionos",
904
+ target_os = "watchos",
905
+ ))]
906
+ {
907
+ #[allow(unsafe_code)]
908
+ let idx = unsafe { libc::if_nametoindex(interface.as_ptr()) };
909
+ let idx = std::num::NonZeroU32::new(idx).ok_or_else(|| {
910
+ // If the index is 0, check errno and return an I/O error.
911
+ ConnectError::new(
912
+ "error converting interface name to index",
913
+ io::Error::last_os_error(),
914
+ )
915
+ })?;
916
+
917
+ // Different setsockopt calls are necessary depending on whether the
918
+ // address is IPv4 or IPv6.
919
+ match addr {
920
+ SocketAddr::V4(_) => socket.bind_device_by_index_v4(Some(idx)),
921
+ SocketAddr::V6(_) => socket.bind_device_by_index_v6(Some(idx)),
922
+ }
923
+ .map_err(ConnectError::m("tcp bind interface error"))?;
924
+ }
925
+ }
926
+
927
+ #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
928
+ if let Some(tcp_user_timeout) = &config.tcp_user_timeout {
929
+ if let Err(_e) = socket.set_tcp_user_timeout(Some(*tcp_user_timeout)) {
930
+ warn!("tcp set_tcp_user_timeout error: {_e}");
931
+ }
932
+ }
933
+
934
+ bind_local_address(
935
+ &socket,
936
+ addr,
937
+ &config.tcp_connect_options.local_ipv4,
938
+ &config.tcp_connect_options.local_ipv6,
939
+ )
940
+ .map_err(ConnectError::m("tcp bind local error"))?;
941
+
942
+ // Safely convert socket2::Socket to tokio TcpSocket.
943
+ let socket = TcpSocket::from_std_stream(socket.into());
944
+
945
+ if config.reuse_address {
946
+ if let Err(_e) = socket.set_reuseaddr(true) {
947
+ warn!("tcp set_reuse_address error: {_e}");
948
+ }
949
+ }
950
+
951
+ if let Some(size) = config.send_buffer_size {
952
+ if let Err(_e) = socket.set_send_buffer_size(size.try_into().unwrap_or(u32::MAX)) {
953
+ warn!("tcp set_buffer_size error: {_e}");
954
+ }
955
+ }
956
+
957
+ if let Some(size) = config.recv_buffer_size {
958
+ if let Err(_e) = socket.set_recv_buffer_size(size.try_into().unwrap_or(u32::MAX)) {
959
+ warn!("tcp set_recv_buffer_size error: {_e}");
960
+ }
961
+ }
962
+
963
+ let connect = socket.connect(*addr);
964
+ Ok(async move {
965
+ match connect_timeout {
966
+ Some(dur) => match tokio::time::timeout(dur, connect).await {
967
+ Ok(Ok(s)) => Ok(s),
968
+ Ok(Err(e)) => Err(e),
969
+ Err(e) => Err(io::Error::new(io::ErrorKind::TimedOut, e)),
970
+ },
971
+ None => connect.await,
972
+ }
973
+ .map_err(ConnectError::m("tcp connect error"))
974
+ })
975
+ }
976
+
977
+ impl ConnectingTcp<'_> {
978
+ async fn connect(mut self) -> Result<TcpStream, ConnectError> {
979
+ match self.fallback {
980
+ None => self.preferred.connect(self.config).await,
981
+ Some(mut fallback) => {
982
+ let preferred_fut = self.preferred.connect(self.config);
983
+ futures_util::pin_mut!(preferred_fut);
984
+
985
+ let fallback_fut = fallback.remote.connect(self.config);
986
+ futures_util::pin_mut!(fallback_fut);
987
+
988
+ let fallback_delay = fallback.delay;
989
+ futures_util::pin_mut!(fallback_delay);
990
+
991
+ let (result, future) =
992
+ match futures_util::future::select(preferred_fut, fallback_delay).await {
993
+ Either::Left((result, _fallback_delay)) => {
994
+ (result, Either::Right(fallback_fut))
995
+ }
996
+ Either::Right(((), preferred_fut)) => {
997
+ // Delay is done, start polling both the preferred and the fallback
998
+ futures_util::future::select(preferred_fut, fallback_fut)
999
+ .await
1000
+ .factor_first()
1001
+ }
1002
+ };
1003
+
1004
+ if result.is_err() {
1005
+ // Fallback to the remaining future (could be preferred or fallback)
1006
+ // if we get an error
1007
+ future.await
1008
+ } else {
1009
+ result
1010
+ }
1011
+ }
1012
+ }
1013
+ }
1014
+ }
1015
+
1016
+ /// Respect explicit ports in the URI, if none, either
1017
+ /// keep non `0` ports resolved from a custom dns resolver,
1018
+ /// or use the default port for the scheme.
1019
+ fn set_port(addr: &mut SocketAddr, host_port: u16, explicit: bool) {
1020
+ if explicit || addr.port() == 0 {
1021
+ addr.set_port(host_port)
1022
+ };
1023
+ }