Skip to content

Commit 6cf1279

Browse files
authored
refactor(conn): modular connector component (#1100)
1 parent 3f154d3 commit 6cf1279

26 files changed

Lines changed: 1402 additions & 1271 deletions

File tree

src/client/conn.rs

Lines changed: 176 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
1-
#[allow(clippy::module_inception)]
2-
mod conn;
31
mod connector;
42
mod http;
53
mod proxy;
4+
mod tcp;
65
mod tls_info;
76
#[cfg(unix)]
87
mod uds;
98
mod verbose;
109

1110
use std::{
1211
fmt::{self, Debug, Formatter},
12+
io,
13+
io::IoSlice,
14+
pin::Pin,
1315
sync::{
1416
Arc,
1517
atomic::{AtomicBool, Ordering},
1618
},
19+
task::{Context, Poll},
1720
};
1821

1922
use ::http::{Extensions, HeaderMap, HeaderValue};
20-
use tokio::io::{AsyncRead, AsyncWrite};
23+
use pin_project_lite::pin_project;
24+
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
25+
use tokio_btls::SslStream;
2126
use tower::{
2227
BoxError,
2328
util::{BoxCloneSyncService, BoxCloneSyncServiceLayer},
@@ -26,16 +31,16 @@ use tower::{
2631
#[cfg(feature = "socks")]
2732
pub(super) use self::proxy::socks;
2833
pub(super) use self::{
29-
conn::Conn,
3034
connector::Connector,
31-
http::{HttpInfo, TcpConnectOptions},
35+
http::{HttpInfo, HttpTransport},
3236
proxy::tunnel,
37+
tcp::{SocketBindOptions, tokio::TokioTcpConnector},
3338
tls_info::TlsInfoFactory,
3439
};
35-
use crate::{client::ConnectRequest, dns::DynResolver, proxy::matcher::Intercept};
40+
use crate::{client::ConnectRequest, dns::DynResolver, proxy::matcher::Intercept, tls::TlsInfo};
3641

3742
/// HTTP connector with dynamic DNS resolver.
38-
pub type HttpConnector = self::http::HttpConnector<DynResolver>;
43+
pub type HttpConnector = self::http::HttpConnector<DynResolver, TokioTcpConnector>;
3944

4045
/// Boxed connector service for establishing connections.
4146
pub type BoxedConnectorService = BoxCloneSyncService<Unnameable, Conn, BoxError>;
@@ -69,6 +74,31 @@ impl<T> AsyncConn for T where T: AsyncRead + AsyncWrite + Connection + Send + Sy
6974

7075
impl<T> AsyncConnWithInfo for T where T: AsyncConn + TlsInfoFactory {}
7176

77+
pin_project! {
78+
/// Note: the `is_proxy` member means *is plain text HTTP proxy*.
79+
/// This tells core whether the URI should be written in
80+
/// * origin-form (`GET /just/a/path HTTP/1.1`), when `is_proxy == false`, or
81+
/// * absolute-form (`GET http://foo.bar/and/a/path HTTP/1.1`), otherwise.
82+
pub struct Conn {
83+
tls_info: bool,
84+
proxy: Option<Intercept>,
85+
#[pin]
86+
stream: Box<dyn AsyncConnWithInfo>,
87+
}
88+
}
89+
90+
pin_project! {
91+
/// A wrapper around `SslStream` that adapts it for use as a generic async connection.
92+
///
93+
/// This type enables unified handling of plain TCP and TLS-encrypted streams by providing
94+
/// implementations of `Connection`, `Read`, `Write`, and `TlsInfoFactory`.
95+
/// It is mainly used internally to abstract over different connection types.
96+
pub struct TlsConn<T> {
97+
#[pin]
98+
stream: SslStream<T>,
99+
}
100+
}
101+
72102
/// Describes a type returned by a connector.
73103
pub trait Connection {
74104
/// Return metadata describing the connection.
@@ -129,6 +159,145 @@ pub struct Connected {
129159
poisoned: PoisonPill,
130160
}
131161

162+
// ==== impl Conn ====
163+
164+
impl Connection for Conn {
165+
fn connected(&self) -> Connected {
166+
let mut connected = self.stream.connected();
167+
168+
if let Some(proxy) = &self.proxy {
169+
connected = connected.proxy(proxy.clone());
170+
}
171+
172+
if self.tls_info {
173+
if let Some(tls_info) = self.stream.tls_info() {
174+
connected.extra(tls_info)
175+
} else {
176+
connected
177+
}
178+
} else {
179+
connected
180+
}
181+
}
182+
}
183+
184+
impl AsyncRead for Conn {
185+
#[inline]
186+
fn poll_read(
187+
self: Pin<&mut Self>,
188+
cx: &mut Context,
189+
buf: &mut ReadBuf<'_>,
190+
) -> Poll<io::Result<()>> {
191+
AsyncRead::poll_read(self.project().stream, cx, buf)
192+
}
193+
}
194+
195+
impl AsyncWrite for Conn {
196+
#[inline]
197+
fn poll_write(
198+
self: Pin<&mut Self>,
199+
cx: &mut Context,
200+
buf: &[u8],
201+
) -> Poll<Result<usize, io::Error>> {
202+
AsyncWrite::poll_write(self.project().stream, cx, buf)
203+
}
204+
205+
#[inline]
206+
fn poll_write_vectored(
207+
self: Pin<&mut Self>,
208+
cx: &mut Context<'_>,
209+
bufs: &[IoSlice<'_>],
210+
) -> Poll<Result<usize, io::Error>> {
211+
AsyncWrite::poll_write_vectored(self.project().stream, cx, bufs)
212+
}
213+
214+
#[inline]
215+
fn is_write_vectored(&self) -> bool {
216+
self.stream.is_write_vectored()
217+
}
218+
219+
#[inline]
220+
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), io::Error>> {
221+
AsyncWrite::poll_flush(self.project().stream, cx)
222+
}
223+
224+
#[inline]
225+
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), io::Error>> {
226+
AsyncWrite::poll_shutdown(self.project().stream, cx)
227+
}
228+
}
229+
230+
// ===== impl TlsConn =====
231+
232+
impl<T> Connection for TlsConn<T>
233+
where
234+
T: Connection,
235+
{
236+
fn connected(&self) -> Connected {
237+
let connected = self.stream.get_ref().connected();
238+
if self.stream.ssl().selected_alpn_protocol() == Some(b"h2") {
239+
connected.negotiated_h2()
240+
} else {
241+
connected
242+
}
243+
}
244+
}
245+
246+
impl<T: AsyncRead + AsyncWrite + Unpin> AsyncRead for TlsConn<T> {
247+
#[inline]
248+
fn poll_read(
249+
self: Pin<&mut Self>,
250+
cx: &mut Context,
251+
buf: &mut ReadBuf<'_>,
252+
) -> Poll<tokio::io::Result<()>> {
253+
AsyncRead::poll_read(self.project().stream, cx, buf)
254+
}
255+
}
256+
257+
impl<T: AsyncRead + AsyncWrite + Unpin> AsyncWrite for TlsConn<T> {
258+
#[inline]
259+
fn poll_write(
260+
self: Pin<&mut Self>,
261+
cx: &mut Context,
262+
buf: &[u8],
263+
) -> Poll<Result<usize, tokio::io::Error>> {
264+
AsyncWrite::poll_write(self.project().stream, cx, buf)
265+
}
266+
267+
#[inline]
268+
fn poll_write_vectored(
269+
self: Pin<&mut Self>,
270+
cx: &mut Context<'_>,
271+
bufs: &[IoSlice<'_>],
272+
) -> Poll<Result<usize, io::Error>> {
273+
AsyncWrite::poll_write_vectored(self.project().stream, cx, bufs)
274+
}
275+
276+
#[inline]
277+
fn is_write_vectored(&self) -> bool {
278+
self.stream.is_write_vectored()
279+
}
280+
281+
#[inline]
282+
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), tokio::io::Error>> {
283+
AsyncWrite::poll_flush(self.project().stream, cx)
284+
}
285+
286+
#[inline]
287+
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), tokio::io::Error>> {
288+
AsyncWrite::poll_shutdown(self.project().stream, cx)
289+
}
290+
}
291+
292+
impl<T> TlsInfoFactory for TlsConn<T>
293+
where
294+
SslStream<T>: TlsInfoFactory,
295+
{
296+
fn tls_info(&self) -> Option<TlsInfo> {
297+
self.stream.tls_info()
298+
}
299+
}
300+
132301
// ===== impl PoisonPill =====
133302

134303
impl fmt::Debug for PoisonPill {

0 commit comments

Comments
 (0)