wreq-rb 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Cargo.lock +2688 -0
- data/Cargo.toml +6 -0
- data/README.md +179 -0
- data/ext/wreq_rb/Cargo.toml +39 -0
- data/ext/wreq_rb/extconf.rb +22 -0
- data/ext/wreq_rb/src/client.rs +565 -0
- data/ext/wreq_rb/src/error.rs +25 -0
- data/ext/wreq_rb/src/lib.rs +20 -0
- data/ext/wreq_rb/src/response.rs +132 -0
- data/lib/wreq-rb/version.rb +5 -0
- data/lib/wreq-rb.rb +17 -0
- data/patches/0001-add-transfer-size-tracking.patch +292 -0
- data/vendor/wreq/Cargo.toml +306 -0
- data/vendor/wreq/LICENSE +202 -0
- data/vendor/wreq/README.md +122 -0
- data/vendor/wreq/examples/cert_store.rs +77 -0
- data/vendor/wreq/examples/connect_via_lower_priority_tokio_runtime.rs +258 -0
- data/vendor/wreq/examples/emulation.rs +118 -0
- data/vendor/wreq/examples/form.rs +14 -0
- data/vendor/wreq/examples/http1_websocket.rs +37 -0
- data/vendor/wreq/examples/http2_websocket.rs +45 -0
- data/vendor/wreq/examples/json_dynamic.rs +41 -0
- data/vendor/wreq/examples/json_typed.rs +47 -0
- data/vendor/wreq/examples/keylog.rs +16 -0
- data/vendor/wreq/examples/request_with_emulation.rs +115 -0
- data/vendor/wreq/examples/request_with_interface.rs +37 -0
- data/vendor/wreq/examples/request_with_local_address.rs +16 -0
- data/vendor/wreq/examples/request_with_proxy.rs +13 -0
- data/vendor/wreq/examples/request_with_redirect.rs +22 -0
- data/vendor/wreq/examples/request_with_version.rs +15 -0
- data/vendor/wreq/examples/tor_socks.rs +24 -0
- data/vendor/wreq/examples/unix_socket.rs +33 -0
- data/vendor/wreq/src/client/body.rs +304 -0
- data/vendor/wreq/src/client/conn/conn.rs +231 -0
- data/vendor/wreq/src/client/conn/connector.rs +549 -0
- data/vendor/wreq/src/client/conn/http.rs +1023 -0
- data/vendor/wreq/src/client/conn/proxy/socks.rs +233 -0
- data/vendor/wreq/src/client/conn/proxy/tunnel.rs +260 -0
- data/vendor/wreq/src/client/conn/proxy.rs +39 -0
- data/vendor/wreq/src/client/conn/tls_info.rs +98 -0
- data/vendor/wreq/src/client/conn/uds.rs +44 -0
- data/vendor/wreq/src/client/conn/verbose.rs +149 -0
- data/vendor/wreq/src/client/conn.rs +323 -0
- data/vendor/wreq/src/client/core/body/incoming.rs +485 -0
- data/vendor/wreq/src/client/core/body/length.rs +118 -0
- data/vendor/wreq/src/client/core/body.rs +34 -0
- data/vendor/wreq/src/client/core/common/buf.rs +149 -0
- data/vendor/wreq/src/client/core/common/rewind.rs +141 -0
- data/vendor/wreq/src/client/core/common/watch.rs +76 -0
- data/vendor/wreq/src/client/core/common.rs +3 -0
- data/vendor/wreq/src/client/core/conn/http1.rs +342 -0
- data/vendor/wreq/src/client/core/conn/http2.rs +307 -0
- data/vendor/wreq/src/client/core/conn.rs +11 -0
- data/vendor/wreq/src/client/core/dispatch.rs +299 -0
- data/vendor/wreq/src/client/core/error.rs +435 -0
- data/vendor/wreq/src/client/core/ext.rs +201 -0
- data/vendor/wreq/src/client/core/http1.rs +178 -0
- data/vendor/wreq/src/client/core/http2.rs +483 -0
- data/vendor/wreq/src/client/core/proto/h1/conn.rs +988 -0
- data/vendor/wreq/src/client/core/proto/h1/decode.rs +1170 -0
- data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +684 -0
- data/vendor/wreq/src/client/core/proto/h1/encode.rs +580 -0
- data/vendor/wreq/src/client/core/proto/h1/io.rs +879 -0
- data/vendor/wreq/src/client/core/proto/h1/role.rs +694 -0
- data/vendor/wreq/src/client/core/proto/h1.rs +104 -0
- data/vendor/wreq/src/client/core/proto/h2/client.rs +650 -0
- data/vendor/wreq/src/client/core/proto/h2/ping.rs +539 -0
- data/vendor/wreq/src/client/core/proto/h2.rs +379 -0
- data/vendor/wreq/src/client/core/proto/headers.rs +138 -0
- data/vendor/wreq/src/client/core/proto.rs +58 -0
- data/vendor/wreq/src/client/core/rt/bounds.rs +57 -0
- data/vendor/wreq/src/client/core/rt/timer.rs +150 -0
- data/vendor/wreq/src/client/core/rt/tokio.rs +99 -0
- data/vendor/wreq/src/client/core/rt.rs +25 -0
- data/vendor/wreq/src/client/core/upgrade.rs +267 -0
- data/vendor/wreq/src/client/core.rs +16 -0
- data/vendor/wreq/src/client/emulation.rs +161 -0
- data/vendor/wreq/src/client/http/client/error.rs +142 -0
- data/vendor/wreq/src/client/http/client/exec.rs +29 -0
- data/vendor/wreq/src/client/http/client/extra.rs +77 -0
- data/vendor/wreq/src/client/http/client/lazy.rs +79 -0
- data/vendor/wreq/src/client/http/client/pool.rs +1105 -0
- data/vendor/wreq/src/client/http/client/util.rs +104 -0
- data/vendor/wreq/src/client/http/client.rs +1003 -0
- data/vendor/wreq/src/client/http/future.rs +99 -0
- data/vendor/wreq/src/client/http.rs +1629 -0
- data/vendor/wreq/src/client/layer/config/options.rs +156 -0
- data/vendor/wreq/src/client/layer/config.rs +116 -0
- data/vendor/wreq/src/client/layer/cookie.rs +161 -0
- data/vendor/wreq/src/client/layer/decoder.rs +139 -0
- data/vendor/wreq/src/client/layer/redirect/future.rs +270 -0
- data/vendor/wreq/src/client/layer/redirect/policy.rs +63 -0
- data/vendor/wreq/src/client/layer/redirect.rs +145 -0
- data/vendor/wreq/src/client/layer/retry/classify.rs +105 -0
- data/vendor/wreq/src/client/layer/retry/scope.rs +51 -0
- data/vendor/wreq/src/client/layer/retry.rs +151 -0
- data/vendor/wreq/src/client/layer/timeout/body.rs +233 -0
- data/vendor/wreq/src/client/layer/timeout/future.rs +90 -0
- data/vendor/wreq/src/client/layer/timeout.rs +177 -0
- data/vendor/wreq/src/client/layer.rs +15 -0
- data/vendor/wreq/src/client/multipart.rs +717 -0
- data/vendor/wreq/src/client/request.rs +818 -0
- data/vendor/wreq/src/client/response.rs +534 -0
- data/vendor/wreq/src/client/ws/json.rs +99 -0
- data/vendor/wreq/src/client/ws/message.rs +453 -0
- data/vendor/wreq/src/client/ws.rs +714 -0
- data/vendor/wreq/src/client.rs +27 -0
- data/vendor/wreq/src/config.rs +140 -0
- data/vendor/wreq/src/cookie.rs +579 -0
- data/vendor/wreq/src/dns/gai.rs +249 -0
- data/vendor/wreq/src/dns/hickory.rs +78 -0
- data/vendor/wreq/src/dns/resolve.rs +180 -0
- data/vendor/wreq/src/dns.rs +69 -0
- data/vendor/wreq/src/error.rs +502 -0
- data/vendor/wreq/src/ext.rs +398 -0
- data/vendor/wreq/src/hash.rs +143 -0
- data/vendor/wreq/src/header.rs +506 -0
- data/vendor/wreq/src/into_uri.rs +187 -0
- data/vendor/wreq/src/lib.rs +586 -0
- data/vendor/wreq/src/proxy/mac.rs +82 -0
- data/vendor/wreq/src/proxy/matcher.rs +806 -0
- data/vendor/wreq/src/proxy/uds.rs +66 -0
- data/vendor/wreq/src/proxy/win.rs +31 -0
- data/vendor/wreq/src/proxy.rs +569 -0
- data/vendor/wreq/src/redirect.rs +575 -0
- data/vendor/wreq/src/retry.rs +198 -0
- data/vendor/wreq/src/sync.rs +129 -0
- data/vendor/wreq/src/tls/conn/cache.rs +123 -0
- data/vendor/wreq/src/tls/conn/cert_compression.rs +125 -0
- data/vendor/wreq/src/tls/conn/ext.rs +82 -0
- data/vendor/wreq/src/tls/conn/macros.rs +34 -0
- data/vendor/wreq/src/tls/conn/service.rs +138 -0
- data/vendor/wreq/src/tls/conn.rs +681 -0
- data/vendor/wreq/src/tls/keylog/handle.rs +64 -0
- data/vendor/wreq/src/tls/keylog.rs +99 -0
- data/vendor/wreq/src/tls/options.rs +464 -0
- data/vendor/wreq/src/tls/x509/identity.rs +122 -0
- data/vendor/wreq/src/tls/x509/parser.rs +71 -0
- data/vendor/wreq/src/tls/x509/store.rs +228 -0
- data/vendor/wreq/src/tls/x509.rs +68 -0
- data/vendor/wreq/src/tls.rs +154 -0
- data/vendor/wreq/src/trace.rs +55 -0
- data/vendor/wreq/src/util.rs +122 -0
- data/vendor/wreq/tests/badssl.rs +228 -0
- data/vendor/wreq/tests/brotli.rs +350 -0
- data/vendor/wreq/tests/client.rs +1098 -0
- data/vendor/wreq/tests/connector_layers.rs +227 -0
- data/vendor/wreq/tests/cookie.rs +306 -0
- data/vendor/wreq/tests/deflate.rs +347 -0
- data/vendor/wreq/tests/emulation.rs +260 -0
- data/vendor/wreq/tests/gzip.rs +347 -0
- data/vendor/wreq/tests/layers.rs +261 -0
- data/vendor/wreq/tests/multipart.rs +165 -0
- data/vendor/wreq/tests/proxy.rs +438 -0
- data/vendor/wreq/tests/redirect.rs +629 -0
- data/vendor/wreq/tests/retry.rs +135 -0
- data/vendor/wreq/tests/support/delay_server.rs +117 -0
- data/vendor/wreq/tests/support/error.rs +16 -0
- data/vendor/wreq/tests/support/layer.rs +183 -0
- data/vendor/wreq/tests/support/mod.rs +9 -0
- data/vendor/wreq/tests/support/server.rs +232 -0
- data/vendor/wreq/tests/timeouts.rs +281 -0
- data/vendor/wreq/tests/unix_socket.rs +135 -0
- data/vendor/wreq/tests/upgrade.rs +98 -0
- data/vendor/wreq/tests/zstd.rs +559 -0
- metadata +225 -0
|
@@ -0,0 +1,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
|
+
}
|