wreq-rb 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +1922 -397
- data/LICENSE +203 -0
- data/README.md +19 -15
- data/ext/wreq_rb/Cargo.toml +4 -6
- data/ext/wreq_rb/src/client.rs +41 -48
- data/lib/wreq-rb/version.rb +1 -1
- data/patches/0001-add-transfer-size-tracking.patch +76 -67
- data/vendor/wreq/Cargo.toml +119 -71
- data/vendor/wreq/README.md +25 -20
- data/vendor/wreq/bench/http1.rs +25 -0
- data/vendor/wreq/bench/http1_over_tls.rs +25 -0
- data/vendor/wreq/bench/http2.rs +25 -0
- data/vendor/wreq/bench/http2_over_tls.rs +25 -0
- data/vendor/wreq/bench/support/bench.rs +91 -0
- data/vendor/wreq/bench/support/client.rs +217 -0
- data/vendor/wreq/bench/support/server.rs +188 -0
- data/vendor/wreq/bench/support.rs +56 -0
- data/vendor/wreq/examples/cert_store.rs +4 -4
- data/vendor/wreq/examples/{emulation.rs → emulate.rs} +2 -2
- data/vendor/wreq/examples/http2_websocket.rs +2 -2
- data/vendor/wreq/examples/keylog.rs +3 -3
- data/vendor/wreq/examples/{request_with_emulation.rs → request_with_emulate.rs} +2 -2
- data/vendor/wreq/examples/rt.rs +23 -0
- data/vendor/wreq/src/client/body.rs +23 -61
- data/vendor/wreq/src/client/emulate.rs +119 -0
- data/vendor/wreq/src/client/{http/future.rs → future.rs} +11 -32
- data/vendor/wreq/src/client/{http → layer}/client/pool.rs +66 -61
- data/vendor/wreq/src/client/{http → layer}/client.rs +416 -270
- data/vendor/wreq/src/client/layer/config.rs +27 -6
- data/vendor/wreq/src/client/layer/decoder.rs +9 -4
- data/vendor/wreq/src/client/layer/redirect/future.rs +6 -3
- data/vendor/wreq/src/client/layer/redirect.rs +4 -5
- data/vendor/wreq/src/client/layer/retry.rs +8 -5
- data/vendor/wreq/src/client/layer/timeout/body.rs +15 -6
- data/vendor/wreq/src/client/layer/timeout/future.rs +23 -18
- data/vendor/wreq/src/client/layer/timeout.rs +24 -74
- data/vendor/wreq/src/client/layer.rs +1 -2
- data/vendor/wreq/src/client/multipart.rs +137 -154
- data/vendor/wreq/src/client/request.rs +202 -118
- data/vendor/wreq/src/client/response.rs +46 -45
- data/vendor/wreq/src/client/upgrade.rs +15 -0
- data/vendor/wreq/src/client/ws.rs +73 -25
- data/vendor/wreq/src/client.rs +1655 -17
- data/vendor/wreq/src/config.rs +11 -11
- data/vendor/wreq/src/{client/conn → conn}/connector.rs +139 -137
- data/vendor/wreq/src/conn/descriptor.rs +143 -0
- data/vendor/wreq/src/conn/http.rs +484 -0
- data/vendor/wreq/src/conn/net/io.rs +75 -0
- data/vendor/wreq/src/conn/net/tcp/compio.rs +71 -0
- data/vendor/wreq/src/conn/net/tcp/tokio.rs +57 -0
- data/vendor/wreq/src/conn/net/tcp.rs +561 -0
- data/vendor/wreq/src/conn/net/uds/compio.rs +60 -0
- data/vendor/wreq/src/{client/conn/uds.rs → conn/net/uds/tokio.rs} +18 -12
- data/vendor/wreq/src/conn/net/uds.rs +11 -0
- data/vendor/wreq/src/conn/net.rs +130 -0
- data/vendor/wreq/src/{client/conn → conn}/proxy/socks.rs +2 -9
- data/vendor/wreq/src/{client/conn → conn}/proxy/tunnel.rs +21 -56
- data/vendor/wreq/src/conn/tls_info.rs +47 -0
- data/vendor/wreq/src/{client/conn.rs → conn.rs} +202 -54
- data/vendor/wreq/src/cookie.rs +302 -142
- data/vendor/wreq/src/dns/gai/compio.rs +77 -0
- data/vendor/wreq/src/dns/gai/tokio.rs +90 -0
- data/vendor/wreq/src/dns/gai.rs +14 -164
- data/vendor/wreq/src/dns/hickory.rs +16 -23
- data/vendor/wreq/src/dns/resolve.rs +7 -41
- data/vendor/wreq/src/dns.rs +90 -7
- data/vendor/wreq/src/error.rs +57 -31
- data/vendor/wreq/src/ext.rs +25 -0
- data/vendor/wreq/src/group.rs +211 -0
- data/vendor/wreq/src/header.rs +100 -112
- data/vendor/wreq/src/lib.rs +124 -73
- data/vendor/wreq/src/proxy.rs +6 -20
- data/vendor/wreq/src/redirect.rs +1 -1
- data/vendor/wreq/src/rt.rs +208 -0
- data/vendor/wreq/src/sync.rs +97 -98
- data/vendor/wreq/src/tls/compress.rs +124 -0
- data/vendor/wreq/src/tls/conn/ext.rs +54 -45
- data/vendor/wreq/src/tls/conn/service.rs +14 -18
- data/vendor/wreq/src/tls/conn.rs +169 -241
- data/vendor/wreq/src/tls/keylog.rs +68 -5
- data/vendor/wreq/src/tls/session.rs +205 -0
- data/vendor/wreq/src/tls/{x509 → trust}/identity.rs +4 -21
- data/vendor/wreq/src/tls/{x509/parser.rs → trust/parse.rs} +1 -1
- data/vendor/wreq/src/tls/{x509 → trust}/store.rs +42 -81
- data/vendor/wreq/src/tls/{x509.rs → trust.rs} +8 -2
- data/vendor/wreq/src/tls.rs +489 -25
- data/vendor/wreq/src/trace.rs +0 -12
- data/vendor/wreq/src/util.rs +1 -1
- data/vendor/wreq/tests/badssl.rs +10 -10
- data/vendor/wreq/tests/client.rs +3 -9
- data/vendor/wreq/tests/cookie.rs +6 -8
- data/vendor/wreq/tests/{emulation.rs → emulate.rs} +130 -22
- data/vendor/wreq/tests/multipart.rs +43 -1
- data/vendor/wreq/tests/proxy.rs +1 -1
- data/vendor/wreq/tests/support/layer.rs +1 -0
- metadata +49 -71
- data/patches/0002-add-cancel-connections.patch +0 -181
- data/vendor/wreq/src/client/conn/conn.rs +0 -231
- data/vendor/wreq/src/client/conn/http.rs +0 -1023
- data/vendor/wreq/src/client/conn/tls_info.rs +0 -98
- data/vendor/wreq/src/client/core/body/incoming.rs +0 -485
- data/vendor/wreq/src/client/core/body/length.rs +0 -118
- data/vendor/wreq/src/client/core/body.rs +0 -34
- data/vendor/wreq/src/client/core/common/buf.rs +0 -149
- data/vendor/wreq/src/client/core/common/rewind.rs +0 -141
- data/vendor/wreq/src/client/core/common/watch.rs +0 -76
- data/vendor/wreq/src/client/core/common.rs +0 -3
- data/vendor/wreq/src/client/core/conn/http1.rs +0 -342
- data/vendor/wreq/src/client/core/conn/http2.rs +0 -307
- data/vendor/wreq/src/client/core/conn.rs +0 -11
- data/vendor/wreq/src/client/core/dispatch.rs +0 -299
- data/vendor/wreq/src/client/core/error.rs +0 -435
- data/vendor/wreq/src/client/core/ext.rs +0 -201
- data/vendor/wreq/src/client/core/http1.rs +0 -178
- data/vendor/wreq/src/client/core/http2.rs +0 -483
- data/vendor/wreq/src/client/core/proto/h1/conn.rs +0 -988
- data/vendor/wreq/src/client/core/proto/h1/decode.rs +0 -1170
- data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +0 -684
- data/vendor/wreq/src/client/core/proto/h1/encode.rs +0 -580
- data/vendor/wreq/src/client/core/proto/h1/io.rs +0 -879
- data/vendor/wreq/src/client/core/proto/h1/role.rs +0 -694
- data/vendor/wreq/src/client/core/proto/h1.rs +0 -104
- data/vendor/wreq/src/client/core/proto/h2/client.rs +0 -650
- data/vendor/wreq/src/client/core/proto/h2/ping.rs +0 -539
- data/vendor/wreq/src/client/core/proto/h2.rs +0 -379
- data/vendor/wreq/src/client/core/proto/headers.rs +0 -138
- data/vendor/wreq/src/client/core/proto.rs +0 -58
- data/vendor/wreq/src/client/core/rt/bounds.rs +0 -57
- data/vendor/wreq/src/client/core/rt/timer.rs +0 -150
- data/vendor/wreq/src/client/core/rt/tokio.rs +0 -99
- data/vendor/wreq/src/client/core/rt.rs +0 -25
- data/vendor/wreq/src/client/core/upgrade.rs +0 -267
- data/vendor/wreq/src/client/core.rs +0 -16
- data/vendor/wreq/src/client/emulation.rs +0 -161
- data/vendor/wreq/src/client/http/client/error.rs +0 -142
- data/vendor/wreq/src/client/http/client/exec.rs +0 -29
- data/vendor/wreq/src/client/http/client/extra.rs +0 -77
- data/vendor/wreq/src/client/http/client/util.rs +0 -104
- data/vendor/wreq/src/client/http.rs +0 -1629
- data/vendor/wreq/src/client/layer/config/options.rs +0 -156
- data/vendor/wreq/src/client/layer/cookie.rs +0 -161
- data/vendor/wreq/src/hash.rs +0 -143
- data/vendor/wreq/src/tls/conn/cache.rs +0 -123
- data/vendor/wreq/src/tls/conn/cert_compression.rs +0 -125
- data/vendor/wreq/src/tls/keylog/handle.rs +0 -64
- data/vendor/wreq/src/tls/options.rs +0 -464
- /data/vendor/wreq/src/client/{http → layer}/client/lazy.rs +0 -0
- /data/vendor/wreq/src/{client/conn → conn}/proxy.rs +0 -0
- /data/vendor/wreq/src/{client/conn → conn}/verbose.rs +0 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
hash::{BuildHasher, Hash, Hasher},
|
|
3
|
+
num::NonZeroU64,
|
|
4
|
+
sync::{
|
|
5
|
+
Arc, LazyLock,
|
|
6
|
+
atomic::{AtomicU64, Ordering},
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
use http::{Uri, Version};
|
|
11
|
+
use lru::DefaultHasher;
|
|
12
|
+
|
|
13
|
+
use crate::{conn::net::SocketBindOptions, group::Group, proxy::Matcher, tls::TlsOptions};
|
|
14
|
+
|
|
15
|
+
/// A key that uniquely identifies a group of interchangeable connections for pooling.
|
|
16
|
+
///
|
|
17
|
+
/// This ID is derived from all parameters that define a connection endpoint,
|
|
18
|
+
/// such as URI, proxy, and local socket bindings. Connections with the same
|
|
19
|
+
/// ID are considered equivalent and can be reused.
|
|
20
|
+
#[derive(Debug, Clone)]
|
|
21
|
+
pub(crate) struct ConnectionId(Arc<(Group, AtomicU64)>);
|
|
22
|
+
|
|
23
|
+
/// A blueprint for creating a new client connection, containing all necessary parameters.
|
|
24
|
+
///
|
|
25
|
+
/// This descriptor bundles the target `Uri`, HTTP version, `TlsOptions`, proxy settings,
|
|
26
|
+
/// and other configurations needed to establish a connection.
|
|
27
|
+
#[must_use]
|
|
28
|
+
#[derive(Clone)]
|
|
29
|
+
pub(crate) struct ConnectionDescriptor {
|
|
30
|
+
uri: Uri,
|
|
31
|
+
version: Option<Version>,
|
|
32
|
+
proxy: Option<Matcher>,
|
|
33
|
+
tls_options: Option<TlsOptions>,
|
|
34
|
+
socket_bind: Option<SocketBindOptions>,
|
|
35
|
+
connection_id: ConnectionId,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ===== impl ConnectionId =====
|
|
39
|
+
|
|
40
|
+
impl Hash for ConnectionId {
|
|
41
|
+
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
42
|
+
let hash = self.0.1.load(Ordering::Relaxed);
|
|
43
|
+
if hash != 0 {
|
|
44
|
+
state.write_u64(hash);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static HASHER: LazyLock<DefaultHasher> = LazyLock::new(DefaultHasher::default);
|
|
49
|
+
let computed_hash = NonZeroU64::new(HASHER.hash_one(&self.0.0))
|
|
50
|
+
.map(NonZeroU64::get)
|
|
51
|
+
.unwrap_or(1);
|
|
52
|
+
|
|
53
|
+
let _ = self.0.1.compare_exchange(
|
|
54
|
+
u64::MIN,
|
|
55
|
+
computed_hash,
|
|
56
|
+
Ordering::Relaxed,
|
|
57
|
+
Ordering::Relaxed,
|
|
58
|
+
);
|
|
59
|
+
state.write_u64(computed_hash);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
impl PartialEq for ConnectionId {
|
|
64
|
+
#[inline]
|
|
65
|
+
fn eq(&self, other: &Self) -> bool {
|
|
66
|
+
self.0.0.eq(&other.0.0)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
impl Eq for ConnectionId {}
|
|
71
|
+
|
|
72
|
+
// ===== impl ConnectionDescriptor =====
|
|
73
|
+
|
|
74
|
+
impl ConnectionDescriptor {
|
|
75
|
+
/// Create a new [`ConnectionDescriptor`].
|
|
76
|
+
pub(crate) fn new(
|
|
77
|
+
uri: Uri,
|
|
78
|
+
mut group: Group,
|
|
79
|
+
proxy: Option<Matcher>,
|
|
80
|
+
version: Option<Version>,
|
|
81
|
+
tls_options: Option<TlsOptions>,
|
|
82
|
+
socket_bind: Option<SocketBindOptions>,
|
|
83
|
+
) -> ConnectionDescriptor {
|
|
84
|
+
let connection_id = {
|
|
85
|
+
group
|
|
86
|
+
.uri(uri.clone())
|
|
87
|
+
.version(version)
|
|
88
|
+
.proxy(proxy.clone())
|
|
89
|
+
.socket_bind(socket_bind.clone());
|
|
90
|
+
ConnectionId(Arc::new((group, AtomicU64::new(u64::MIN))))
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
ConnectionDescriptor {
|
|
94
|
+
uri,
|
|
95
|
+
proxy,
|
|
96
|
+
version,
|
|
97
|
+
tls_options,
|
|
98
|
+
socket_bind,
|
|
99
|
+
connection_id,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Returns a [`ConnectionId`] group ID for this descriptor.
|
|
104
|
+
#[inline]
|
|
105
|
+
pub(crate) fn id(&self) -> ConnectionId {
|
|
106
|
+
self.connection_id.clone()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Returns a reference to the [`Uri`].
|
|
110
|
+
#[inline]
|
|
111
|
+
pub(crate) fn uri(&self) -> &Uri {
|
|
112
|
+
&self.uri
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Returns a mutable reference to the [`Uri`].
|
|
116
|
+
#[inline]
|
|
117
|
+
pub(crate) fn uri_mut(&mut self) -> &mut Uri {
|
|
118
|
+
&mut self.uri
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Return the negotiated HTTP version, if any.
|
|
122
|
+
pub(crate) fn version(&self) -> Option<Version> {
|
|
123
|
+
self.version
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Return a reference to the [`TlsOptions`].
|
|
127
|
+
#[inline]
|
|
128
|
+
pub(crate) fn tls_options(&self) -> Option<&TlsOptions> {
|
|
129
|
+
self.tls_options.as_ref()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Return a reference to the [`Matcher`].
|
|
133
|
+
#[inline]
|
|
134
|
+
pub(crate) fn proxy(&self) -> Option<&Matcher> {
|
|
135
|
+
self.proxy.as_ref()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Return a reference to the [`SocketBindOptions`].
|
|
139
|
+
#[inline]
|
|
140
|
+
pub(crate) fn socket_bind_options(&self) -> Option<&SocketBindOptions> {
|
|
141
|
+
self.socket_bind.as_ref()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
future::Future,
|
|
3
|
+
marker::PhantomData,
|
|
4
|
+
net::{Ipv4Addr, Ipv6Addr, SocketAddr},
|
|
5
|
+
pin::Pin,
|
|
6
|
+
sync::Arc,
|
|
7
|
+
task::{self, Poll},
|
|
8
|
+
time::Duration,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
use http::uri::{Scheme, Uri};
|
|
12
|
+
use pin_project_lite::pin_project;
|
|
13
|
+
use tokio::io::{AsyncRead, AsyncWrite};
|
|
14
|
+
use tower::{BoxError, Service};
|
|
15
|
+
|
|
16
|
+
use super::{
|
|
17
|
+
Connection,
|
|
18
|
+
net::{
|
|
19
|
+
SocketBindOptions,
|
|
20
|
+
tcp::{ConnectError, ConnectingTcp, TcpConnector, TcpKeepaliveOptions, TcpOptions},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
use crate::dns::{self, InternalResolve};
|
|
24
|
+
|
|
25
|
+
static INVALID_NOT_HTTP: &str = "invalid URI, scheme is not http";
|
|
26
|
+
static INVALID_MISSING_SCHEME: &str = "invalid URI, scheme is missing";
|
|
27
|
+
static INVALID_MISSING_HOST: &str = "invalid URI, host is missing";
|
|
28
|
+
|
|
29
|
+
type ConnectResult<S> = Result<<S as TcpConnector>::Connection, ConnectError>;
|
|
30
|
+
type BoxConnecting<S> = Pin<Box<dyn Future<Output = ConnectResult<S>> + Send>>;
|
|
31
|
+
|
|
32
|
+
/// A trait for configuring HTTP transport options on a [`Service<Uri>`] connector.
|
|
33
|
+
///
|
|
34
|
+
/// Provides methods to adjust TCP/socket-level settings such as keepalive,
|
|
35
|
+
/// timeouts, buffer sizes, and local address binding. [`HttpConnector`]
|
|
36
|
+
/// is the default implementation.
|
|
37
|
+
pub trait HttpConnect: Service<Uri> + Clone + Send + Sized + 'static
|
|
38
|
+
where
|
|
39
|
+
Self::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
|
|
40
|
+
Self::Error: Into<BoxError>,
|
|
41
|
+
Self::Future: Unpin + Send + 'static,
|
|
42
|
+
{
|
|
43
|
+
/// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration
|
|
44
|
+
/// to remain idle before sending TCP keepalive probes.
|
|
45
|
+
fn enforce_http(&mut self, enforced: bool);
|
|
46
|
+
|
|
47
|
+
/// Set that all sockets have `SO_NODELAY` set to the supplied value `nodelay`.
|
|
48
|
+
fn set_nodelay(&mut self, nodelay: bool);
|
|
49
|
+
|
|
50
|
+
/// Sets the value of the `SO_SNDBUF` option on the socket.
|
|
51
|
+
fn set_send_buffer_size(&mut self, size: Option<usize>);
|
|
52
|
+
|
|
53
|
+
/// Sets the value of the `SO_RCVBUF` option on the socket.
|
|
54
|
+
fn set_recv_buffer_size(&mut self, size: Option<usize>);
|
|
55
|
+
|
|
56
|
+
/// Set that all socket have `SO_REUSEADDR` set to the supplied value `reuse_address`.
|
|
57
|
+
fn set_reuse_address(&mut self, reuse: bool);
|
|
58
|
+
|
|
59
|
+
/// Sets the value of the `TCP_USER_TIMEOUT` option on the socket.
|
|
60
|
+
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
61
|
+
fn set_tcp_user_timeout(&mut self, time: Option<Duration>);
|
|
62
|
+
|
|
63
|
+
/// Set the connect timeout.
|
|
64
|
+
fn set_connect_timeout(&mut self, dur: Option<Duration>);
|
|
65
|
+
|
|
66
|
+
/// Set timeout for [RFC 6555 (Happy Eyeballs)][RFC 6555] algorithm.
|
|
67
|
+
///
|
|
68
|
+
/// [RFC 6555]: https://tools.ietf.org/html/rfc6555
|
|
69
|
+
fn set_happy_eyeballs_timeout(&mut self, dur: Option<Duration>);
|
|
70
|
+
|
|
71
|
+
/// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration
|
|
72
|
+
/// to remain idle before sending TCP keepalive probes.
|
|
73
|
+
fn set_keepalive(&mut self, time: Option<Duration>);
|
|
74
|
+
|
|
75
|
+
/// Set the duration between two successive TCP keepalive retransmissions,
|
|
76
|
+
/// if acknowledgement to the previous keepalive transmission is not received.
|
|
77
|
+
fn set_keepalive_interval(&mut self, interval: Option<Duration>);
|
|
78
|
+
|
|
79
|
+
/// Set the number of retransmissions to be carried out before declaring that remote end is not
|
|
80
|
+
/// available.
|
|
81
|
+
fn set_keepalive_retries(&mut self, retries: Option<u32>);
|
|
82
|
+
|
|
83
|
+
/// Sets the name of the interface to bind sockets produced.
|
|
84
|
+
#[cfg(any(
|
|
85
|
+
target_os = "android",
|
|
86
|
+
target_os = "fuchsia",
|
|
87
|
+
target_os = "illumos",
|
|
88
|
+
target_os = "ios",
|
|
89
|
+
target_os = "linux",
|
|
90
|
+
target_os = "macos",
|
|
91
|
+
target_os = "solaris",
|
|
92
|
+
target_os = "tvos",
|
|
93
|
+
target_os = "visionos",
|
|
94
|
+
target_os = "watchos",
|
|
95
|
+
))]
|
|
96
|
+
fn set_interface<I: Into<std::borrow::Cow<'static, str>>>(&mut self, interface: I);
|
|
97
|
+
|
|
98
|
+
/// Set that all sockets are bound to the configured IPv4 or IPv6 address (depending on host's
|
|
99
|
+
/// preferences) before connection.
|
|
100
|
+
fn set_local_addresses<V4, V6>(&mut self, ipv4_address: V4, ipv6_address: V6)
|
|
101
|
+
where
|
|
102
|
+
V4: Into<Option<Ipv4Addr>>,
|
|
103
|
+
V6: Into<Option<Ipv6Addr>>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// A connector for the `http` scheme.
|
|
107
|
+
///
|
|
108
|
+
/// Performs DNS resolution in a thread pool, and then connects over TCP.
|
|
109
|
+
///
|
|
110
|
+
/// # Note
|
|
111
|
+
///
|
|
112
|
+
/// Sets the [`HttpInfo`] value on responses, which includes
|
|
113
|
+
/// transport information such as the remote socket address used.
|
|
114
|
+
#[derive(Clone)]
|
|
115
|
+
pub struct HttpConnector<R, S> {
|
|
116
|
+
options: Arc<TcpOptions>,
|
|
117
|
+
resolver: R,
|
|
118
|
+
connector: S,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Extra information about the transport when an HttpConnector is used.
|
|
122
|
+
///
|
|
123
|
+
/// # Example
|
|
124
|
+
///
|
|
125
|
+
/// ```
|
|
126
|
+
/// # fn doc(res: http::Response<()>) {
|
|
127
|
+
/// use crate::util::client::connect::HttpInfo;
|
|
128
|
+
///
|
|
129
|
+
/// // res = http::Response
|
|
130
|
+
/// res.extensions().get::<HttpInfo>().map(|info| {
|
|
131
|
+
/// println!("remote addr = {}", info.remote_addr());
|
|
132
|
+
/// });
|
|
133
|
+
/// # }
|
|
134
|
+
/// ```
|
|
135
|
+
///
|
|
136
|
+
/// # Note
|
|
137
|
+
///
|
|
138
|
+
/// If a different connector is used besides [`HttpConnector`],
|
|
139
|
+
/// this value will not exist in the extensions. Consult that specific
|
|
140
|
+
/// connector to see what "extra" information it might provide to responses.
|
|
141
|
+
#[derive(Clone, Debug)]
|
|
142
|
+
pub struct HttpInfo {
|
|
143
|
+
pub(crate) remote_addr: SocketAddr,
|
|
144
|
+
pub(crate) local_addr: SocketAddr,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ===== impl HttpConnector =====
|
|
148
|
+
|
|
149
|
+
impl<R, S> HttpConnector<R, S> {
|
|
150
|
+
/// Construct a new [`HttpConnector`].
|
|
151
|
+
pub fn new(resolver: R, connector: S) -> HttpConnector<R, S> {
|
|
152
|
+
HttpConnector {
|
|
153
|
+
options: Arc::new(TcpOptions {
|
|
154
|
+
enforce_http: true,
|
|
155
|
+
connect_timeout: None,
|
|
156
|
+
happy_eyeballs_timeout: Some(Duration::from_millis(300)),
|
|
157
|
+
nodelay: false,
|
|
158
|
+
reuse_address: false,
|
|
159
|
+
send_buffer_size: None,
|
|
160
|
+
recv_buffer_size: None,
|
|
161
|
+
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
162
|
+
tcp_user_timeout: None,
|
|
163
|
+
tcp_keepalive: TcpKeepaliveOptions::default(),
|
|
164
|
+
socket_bind: SocketBindOptions::default(),
|
|
165
|
+
}),
|
|
166
|
+
resolver,
|
|
167
|
+
connector,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fn config_mut(&mut self) -> &mut TcpOptions {
|
|
172
|
+
// If the are HttpConnector clones, this will clone the inner
|
|
173
|
+
// config. So mutating the config won't ever affect previous
|
|
174
|
+
// clones.
|
|
175
|
+
Arc::make_mut(&mut self.options)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
impl<R, S> HttpConnect for HttpConnector<R, S>
|
|
180
|
+
where
|
|
181
|
+
R: InternalResolve + Clone + Send + Sync + 'static,
|
|
182
|
+
R::Future: Send,
|
|
183
|
+
S: TcpConnector,
|
|
184
|
+
{
|
|
185
|
+
/// Option to enforce all `Uri`s have the `http` scheme.
|
|
186
|
+
///
|
|
187
|
+
/// Enabled by default.
|
|
188
|
+
#[inline]
|
|
189
|
+
fn enforce_http(&mut self, is_enforced: bool) {
|
|
190
|
+
self.config_mut().enforce_http = is_enforced;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// Set that all sockets have `SO_NODELAY` set to the supplied value `nodelay`.
|
|
194
|
+
///
|
|
195
|
+
/// Default is `false`.
|
|
196
|
+
#[inline]
|
|
197
|
+
fn set_nodelay(&mut self, nodelay: bool) {
|
|
198
|
+
self.config_mut().nodelay = nodelay;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/// Sets the value of the SO_SNDBUF option on the socket.
|
|
202
|
+
#[inline]
|
|
203
|
+
fn set_send_buffer_size(&mut self, size: Option<usize>) {
|
|
204
|
+
self.config_mut().send_buffer_size = size;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// Sets the value of the SO_RCVBUF option on the socket.
|
|
208
|
+
#[inline]
|
|
209
|
+
fn set_recv_buffer_size(&mut self, size: Option<usize>) {
|
|
210
|
+
self.config_mut().recv_buffer_size = size;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/// Set that all socket have `SO_REUSEADDR` set to the supplied value `reuse_address`.
|
|
214
|
+
///
|
|
215
|
+
/// Default is `false`.
|
|
216
|
+
#[inline]
|
|
217
|
+
fn set_reuse_address(&mut self, reuse_address: bool) {
|
|
218
|
+
self.config_mut().reuse_address = reuse_address;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// Sets the value of the TCP_USER_TIMEOUT option on the socket.
|
|
222
|
+
#[inline]
|
|
223
|
+
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
224
|
+
fn set_tcp_user_timeout(&mut self, time: Option<Duration>) {
|
|
225
|
+
self.config_mut().tcp_user_timeout = time;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/// Set the connect timeout.
|
|
229
|
+
///
|
|
230
|
+
/// If a domain resolves to multiple IP addresses, the timeout will be
|
|
231
|
+
/// evenly divided across them.
|
|
232
|
+
///
|
|
233
|
+
/// Default is `None`.
|
|
234
|
+
#[inline]
|
|
235
|
+
fn set_connect_timeout(&mut self, dur: Option<Duration>) {
|
|
236
|
+
self.config_mut().connect_timeout = dur;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// Set timeout for [RFC 6555 (Happy Eyeballs)][RFC 6555] algorithm.
|
|
240
|
+
///
|
|
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.
|
|
245
|
+
///
|
|
246
|
+
/// If `None`, parallel connection attempts are disabled.
|
|
247
|
+
///
|
|
248
|
+
/// Default is 300 milliseconds.
|
|
249
|
+
///
|
|
250
|
+
/// [RFC 6555]: https://tools.ietf.org/html/rfc6555
|
|
251
|
+
#[inline]
|
|
252
|
+
fn set_happy_eyeballs_timeout(&mut self, dur: Option<Duration>) {
|
|
253
|
+
self.config_mut().happy_eyeballs_timeout = dur;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration
|
|
257
|
+
/// to remain idle before sending TCP keepalive probes.
|
|
258
|
+
///
|
|
259
|
+
/// If `None`, keepalive is disabled.
|
|
260
|
+
///
|
|
261
|
+
/// Default is `None`.
|
|
262
|
+
#[inline]
|
|
263
|
+
fn set_keepalive(&mut self, time: Option<Duration>) {
|
|
264
|
+
self.config_mut().tcp_keepalive.time = time;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/// Set the duration between two successive TCP keepalive retransmissions,
|
|
268
|
+
/// if acknowledgement to the previous keepalive transmission is not received.
|
|
269
|
+
#[inline]
|
|
270
|
+
fn set_keepalive_interval(&mut self, interval: Option<Duration>) {
|
|
271
|
+
self.config_mut().tcp_keepalive.interval = interval;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/// Set the number of retransmissions to be carried out before declaring that remote end is not
|
|
275
|
+
/// available.
|
|
276
|
+
#[inline]
|
|
277
|
+
fn set_keepalive_retries(&mut self, retries: Option<u32>) {
|
|
278
|
+
self.config_mut().tcp_keepalive.retries = retries;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/// Sets the name of the interface to bind sockets produced by this
|
|
282
|
+
/// connector.
|
|
283
|
+
///
|
|
284
|
+
/// On Linux, this sets the `SO_BINDTODEVICE` option on this socket (see
|
|
285
|
+
/// [`man 7 socket`] for details). On macOS (and macOS-derived systems like
|
|
286
|
+
/// iOS), illumos, and Solaris, this will instead use the `IP_BOUND_IF`
|
|
287
|
+
/// socket option (see [`man 7p ip`]).
|
|
288
|
+
///
|
|
289
|
+
/// If a socket is bound to an interface, only packets received from that particular
|
|
290
|
+
/// interface are processed by the socket. Note that this only works for some socket
|
|
291
|
+
/// types, particularly `AF_INET`` sockets.
|
|
292
|
+
///
|
|
293
|
+
/// On Linux it can be used to specify a [VRF], but the binary needs
|
|
294
|
+
/// to either have `CAP_NET_RAW` or to be run as root.
|
|
295
|
+
///
|
|
296
|
+
/// This function is only available on the following operating systems:
|
|
297
|
+
/// - Linux, including Android
|
|
298
|
+
/// - Fuchsia
|
|
299
|
+
/// - illumos and Solaris
|
|
300
|
+
/// - macOS, iOS, visionOS, watchOS, and tvOS
|
|
301
|
+
///
|
|
302
|
+
/// [VRF]: https://www.kernel.org/doc/Documentation/networking/vrf.txt
|
|
303
|
+
/// [`man 7 socket`]: https://man7.org/linux/man-pages/man7/socket.7.html
|
|
304
|
+
/// [`man 7p ip`]: https://docs.oracle.com/cd/E86824_01/html/E54777/ip-7p.html
|
|
305
|
+
#[cfg(any(
|
|
306
|
+
target_os = "android",
|
|
307
|
+
target_os = "fuchsia",
|
|
308
|
+
target_os = "illumos",
|
|
309
|
+
target_os = "ios",
|
|
310
|
+
target_os = "linux",
|
|
311
|
+
target_os = "macos",
|
|
312
|
+
target_os = "solaris",
|
|
313
|
+
target_os = "tvos",
|
|
314
|
+
target_os = "visionos",
|
|
315
|
+
target_os = "watchos",
|
|
316
|
+
))]
|
|
317
|
+
fn set_interface<I: Into<std::borrow::Cow<'static, str>>>(&mut self, interface: I) {
|
|
318
|
+
self.config_mut().socket_bind.set_interface(interface);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/// Set that all sockets are bound to the configured IPv4 or IPv6 address (depending on host's
|
|
322
|
+
/// preferences) before connection.
|
|
323
|
+
///
|
|
324
|
+
/// If `None`, the sockets will not be bound.
|
|
325
|
+
///
|
|
326
|
+
/// Default is `None`.
|
|
327
|
+
fn set_local_addresses<V4, V6>(&mut self, ipv4_address: V4, ipv6_address: V6)
|
|
328
|
+
where
|
|
329
|
+
V4: Into<Option<Ipv4Addr>>,
|
|
330
|
+
V6: Into<Option<Ipv6Addr>>,
|
|
331
|
+
{
|
|
332
|
+
self.config_mut()
|
|
333
|
+
.socket_bind
|
|
334
|
+
.set_local_addresses(ipv4_address, ipv6_address);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
impl<R, S> Service<Uri> for HttpConnector<R, S>
|
|
339
|
+
where
|
|
340
|
+
R: InternalResolve + Clone + Send + Sync + 'static,
|
|
341
|
+
R::Future: Send,
|
|
342
|
+
S: TcpConnector,
|
|
343
|
+
S::TcpStream: From<socket2::Socket>,
|
|
344
|
+
{
|
|
345
|
+
type Response = S::Connection;
|
|
346
|
+
type Error = ConnectError;
|
|
347
|
+
type Future = HttpConnecting<R, S>;
|
|
348
|
+
|
|
349
|
+
#[inline]
|
|
350
|
+
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
351
|
+
self.resolver.poll_ready(cx).map_err(ConnectError::dns)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
fn call(&mut self, dst: Uri) -> Self::Future {
|
|
355
|
+
let mut this = self.clone();
|
|
356
|
+
|
|
357
|
+
let fut = async move {
|
|
358
|
+
let options = &this.options;
|
|
359
|
+
|
|
360
|
+
let (host, port) = get_host_port(options, &dst)?;
|
|
361
|
+
let host = host.trim_start_matches('[').trim_end_matches(']');
|
|
362
|
+
|
|
363
|
+
let addrs = if let Some(addrs) = dns::SocketAddrs::try_parse(host, port) {
|
|
364
|
+
addrs
|
|
365
|
+
} else {
|
|
366
|
+
let addrs = dns::resolve(&mut this.resolver, dns::Name::new(host.into()))
|
|
367
|
+
.await
|
|
368
|
+
.map_err(ConnectError::dns)?;
|
|
369
|
+
let addrs = addrs
|
|
370
|
+
.map(|mut addr| {
|
|
371
|
+
set_port(&mut addr, port, dst.port().is_some());
|
|
372
|
+
addr
|
|
373
|
+
})
|
|
374
|
+
.collect();
|
|
375
|
+
dns::SocketAddrs::new(addrs)
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
ConnectingTcp::new(addrs, options, this.connector)
|
|
379
|
+
.connect(options)
|
|
380
|
+
.await
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
HttpConnecting {
|
|
384
|
+
fut: Box::pin(fut),
|
|
385
|
+
_marker: PhantomData,
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
fn get_host_port<'u>(options: &TcpOptions, dst: &'u Uri) -> Result<(&'u str, u16), ConnectError> {
|
|
391
|
+
trace!(
|
|
392
|
+
"Http::connect; scheme={:?}, host={:?}, port={:?}",
|
|
393
|
+
dst.scheme(),
|
|
394
|
+
dst.host(),
|
|
395
|
+
dst.port(),
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
if options.enforce_http {
|
|
399
|
+
if dst.scheme() != Some(&Scheme::HTTP) {
|
|
400
|
+
return Err(ConnectError {
|
|
401
|
+
msg: INVALID_NOT_HTTP,
|
|
402
|
+
addr: None,
|
|
403
|
+
cause: None,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
} else if dst.scheme().is_none() {
|
|
407
|
+
return Err(ConnectError {
|
|
408
|
+
msg: INVALID_MISSING_SCHEME,
|
|
409
|
+
addr: None,
|
|
410
|
+
cause: None,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
let host = match dst.host() {
|
|
415
|
+
Some(s) => s,
|
|
416
|
+
None => {
|
|
417
|
+
return Err(ConnectError {
|
|
418
|
+
msg: INVALID_MISSING_HOST,
|
|
419
|
+
addr: None,
|
|
420
|
+
cause: None,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
let port = match dst.port() {
|
|
425
|
+
Some(port) => port.as_u16(),
|
|
426
|
+
None => {
|
|
427
|
+
if dst.scheme() == Some(&Scheme::HTTPS) {
|
|
428
|
+
443
|
|
429
|
+
} else {
|
|
430
|
+
80
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
Ok((host, port))
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/// Respect explicit ports in the URI, if none, either
|
|
439
|
+
/// keep non `0` ports resolved from a custom dns resolver,
|
|
440
|
+
/// or use the default port for the scheme.
|
|
441
|
+
fn set_port(addr: &mut SocketAddr, host_port: u16, explicit: bool) {
|
|
442
|
+
if explicit || addr.port() == 0 {
|
|
443
|
+
addr.set_port(host_port)
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
impl HttpInfo {
|
|
448
|
+
/// Get the remote address of the transport used.
|
|
449
|
+
pub fn remote_addr(&self) -> SocketAddr {
|
|
450
|
+
self.remote_addr
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/// Get the local address of the transport used.
|
|
454
|
+
pub fn local_addr(&self) -> SocketAddr {
|
|
455
|
+
self.local_addr
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
pin_project! {
|
|
460
|
+
// Not publicly exported (so missing_docs doesn't trigger).
|
|
461
|
+
//
|
|
462
|
+
// We return this `Future` instead of the `Pin<Box<dyn Future>>` directly
|
|
463
|
+
// so that users don't rely on it fitting in a `Pin<Box<dyn Future>>` slot
|
|
464
|
+
// (and thus we can change the type in the future).
|
|
465
|
+
#[must_use = "futures do nothing unless polled"]
|
|
466
|
+
pub struct HttpConnecting<R, S: TcpConnector> {
|
|
467
|
+
#[pin]
|
|
468
|
+
fut: BoxConnecting<S>,
|
|
469
|
+
_marker: PhantomData<R>,
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
impl<R, S> Future for HttpConnecting<R, S>
|
|
474
|
+
where
|
|
475
|
+
R: InternalResolve,
|
|
476
|
+
S: TcpConnector,
|
|
477
|
+
{
|
|
478
|
+
type Output = ConnectResult<S>;
|
|
479
|
+
|
|
480
|
+
#[inline]
|
|
481
|
+
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
|
482
|
+
self.project().fut.poll(cx)
|
|
483
|
+
}
|
|
484
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//! I/O types and utilities for network connections.
|
|
2
|
+
|
|
3
|
+
#![cfg(feature = "compio-rt")]
|
|
4
|
+
|
|
5
|
+
use std::{
|
|
6
|
+
io,
|
|
7
|
+
net::SocketAddr,
|
|
8
|
+
pin::Pin,
|
|
9
|
+
task::{Context, Poll, ready},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
use compio::io::{AsyncRead, AsyncWrite, util::Splittable};
|
|
13
|
+
use wreq_rt::rt::compio::io::CompioIO;
|
|
14
|
+
|
|
15
|
+
/// [`compio`] with peer and local socket addresses.
|
|
16
|
+
#[derive(Debug)]
|
|
17
|
+
pub struct CompioConnection<T: Splittable> {
|
|
18
|
+
pub(super) inner: CompioIO<T>,
|
|
19
|
+
pub(super) peer_addr: Option<SocketAddr>,
|
|
20
|
+
pub(super) local_addr: Option<SocketAddr>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ===== impl CompioConnection =====
|
|
24
|
+
|
|
25
|
+
impl<S> tokio::io::AsyncRead for CompioConnection<S>
|
|
26
|
+
where
|
|
27
|
+
S: Splittable + 'static,
|
|
28
|
+
S::ReadHalf: AsyncRead + Unpin,
|
|
29
|
+
S::WriteHalf: AsyncWrite + Unpin,
|
|
30
|
+
{
|
|
31
|
+
#[inline(always)]
|
|
32
|
+
fn poll_read(
|
|
33
|
+
mut self: Pin<&mut Self>,
|
|
34
|
+
cx: &mut Context<'_>,
|
|
35
|
+
buf: &mut tokio::io::ReadBuf<'_>,
|
|
36
|
+
) -> Poll<io::Result<()>> {
|
|
37
|
+
// Flush any buffered writes before reading. This is necessary
|
|
38
|
+
// because code like hyper_util::rt::write_all (used by Tunnel
|
|
39
|
+
// and SOCKS handshakes) and hyper's own body encoder may call
|
|
40
|
+
// poll_write without poll_flush, leaving data buffered in
|
|
41
|
+
// compio's AsyncWriteStream. Since HTTP/1.1 is half-duplex
|
|
42
|
+
// (write then read), flushing here ensures the remote peer
|
|
43
|
+
// receives our data before we wait for its response.
|
|
44
|
+
// In HTTP/2 the stream is split, so this combined poll_read
|
|
45
|
+
// is not called and concurrent reads/writes are unaffected.
|
|
46
|
+
ready!(tokio::io::AsyncWrite::poll_flush(self.as_mut(), cx))?;
|
|
47
|
+
Pin::new(&mut self.get_mut().inner).poll_read(cx, buf)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
impl<S> tokio::io::AsyncWrite for CompioConnection<S>
|
|
52
|
+
where
|
|
53
|
+
S: Splittable + 'static,
|
|
54
|
+
S::ReadHalf: AsyncRead + Unpin,
|
|
55
|
+
S::WriteHalf: AsyncWrite + Unpin,
|
|
56
|
+
{
|
|
57
|
+
#[inline(always)]
|
|
58
|
+
fn poll_write(
|
|
59
|
+
self: Pin<&mut Self>,
|
|
60
|
+
cx: &mut Context<'_>,
|
|
61
|
+
buf: &[u8],
|
|
62
|
+
) -> Poll<io::Result<usize>> {
|
|
63
|
+
Pin::new(&mut self.get_mut().inner).poll_write(cx, buf)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#[inline(always)]
|
|
67
|
+
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
68
|
+
Pin::new(&mut self.get_mut().inner).poll_flush(cx)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[inline(always)]
|
|
72
|
+
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
73
|
+
Pin::new(&mut self.get_mut().inner).poll_shutdown(cx)
|
|
74
|
+
}
|
|
75
|
+
}
|