From 3b63fef29c58a11c57b3f52493196af48c35c0a0 Mon Sep 17 00:00:00 2001 From: Martin Algesten Date: Fri, 3 Jan 2025 11:23:22 +0100 Subject: [PATCH] Avoid boxing in DefaultConnector chain --- src/agent.rs | 20 ++- src/pool.rs | 6 +- src/proxy.rs | 8 +- src/tls/native_tls.rs | 20 +-- src/tls/rustls.rs | 22 +-- src/unversioned/transport/chain.rs | 151 ++++++++++++++---- src/unversioned/transport/io.rs | 24 +-- src/unversioned/transport/mod.rs | 245 ++++++++++++++++++++--------- src/unversioned/transport/socks.rs | 19 ++- src/unversioned/transport/tcp.rs | 13 +- src/unversioned/transport/test.rs | 15 +- 11 files changed, 374 insertions(+), 169 deletions(-) diff --git a/src/agent.rs b/src/agent.rs index 99968c66..b6111ef6 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -12,7 +12,7 @@ use crate::middleware::MiddlewareNext; use crate::pool::ConnectionPool; use crate::resolver::{DefaultResolver, Resolver}; use crate::send_body::AsSendBody; -use crate::transport::{Connector, DefaultConnector}; +use crate::transport::{boxed_connector, Connector, DefaultConnector, Transport}; use crate::{Error, RequestBuilder, SendBody}; use crate::{WithBody, WithoutBody}; @@ -96,18 +96,18 @@ pub struct Agent { impl Agent { /// Creates an agent with defaults. pub fn new_with_defaults() -> Self { - Self::with_parts( + Self::with_parts_inner( Config::default(), - DefaultConnector::default(), + Box::new(DefaultConnector::default()), DefaultResolver::default(), ) } /// Creates an agent with config. pub fn new_with_config(config: Config) -> Self { - Self::with_parts( + Self::with_parts_inner( config, - DefaultConnector::default(), + Box::new(DefaultConnector::default()), DefaultResolver::default(), ) } @@ -123,6 +123,16 @@ impl Agent { /// /// _This is low level API that isn't for regular use of ureq._ pub fn with_parts(config: Config, connector: impl Connector, resolver: impl Resolver) -> Self { + let boxed = boxed_connector(connector); + Self::with_parts_inner(config, boxed, resolver) + } + + /// Inner helper to avoid additional boxing of the [`DefaultConnector`]. + fn with_parts_inner( + config: Config, + connector: Box>>, + resolver: impl Resolver, + ) -> Self { let pool = Arc::new(ConnectionPool::new(connector, &config)); Agent { diff --git a/src/pool.rs b/src/pool.rs index 518a3957..1d3f09a1 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -14,14 +14,14 @@ use crate::util::DebugAuthority; use crate::Error; pub(crate) struct ConnectionPool { - connector: Box, + connector: Box>>, pool: Arc>, } impl ConnectionPool { - pub fn new(connector: impl Connector, config: &Config) -> Self { + pub fn new(connector: Box>>, config: &Config) -> Self { ConnectionPool { - connector: Box::new(connector), + connector, pool: Arc::new(Mutex::new(Pool::new(config))), } } diff --git a/src/proxy.rs b/src/proxy.rs index 1f8effb0..54aec181 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -188,12 +188,14 @@ impl Proxy { /// wrapped in TLS. pub struct ConnectProxyConnector; -impl Connector for ConnectProxyConnector { +impl Connector for ConnectProxyConnector { + type Out = In; + fn connect( &self, details: &ConnectionDetails, - chained: Option>, - ) -> Result>, Error> { + chained: Option, + ) -> Result, Error> { let Some(transport) = chained else { return Ok(None); }; diff --git a/src/tls/native_tls.rs b/src/tls/native_tls.rs index ff340349..b91ff90a 100644 --- a/src/tls/native_tls.rs +++ b/src/tls/native_tls.rs @@ -21,12 +21,14 @@ pub struct NativeTlsConnector { connector: OnceCell>, } -impl Connector for NativeTlsConnector { +impl Connector for NativeTlsConnector { + type Out = Either; + fn connect( &self, details: &ConnectionDetails, - chained: Option>, - ) -> Result>, Error> { + chained: Option, + ) -> Result, Error> { let Some(transport) = chained else { panic!("NativeTlsConnector requires a chained transport"); }; @@ -35,12 +37,12 @@ impl Connector for NativeTlsConnector { // already, otherwise use chained transport as is. if !details.needs_tls() || transport.is_tls() { trace!("Skip"); - return Ok(Some(transport)); + return Ok(Some(Either::A(transport))); } if details.config.tls_config().provider != TlsProvider::NativeTls { debug!("Skip because config is not set to Native TLS"); - return Ok(Some(transport)); + return Ok(Some(Either::A(transport))); } trace!("Try wrap TLS"); @@ -67,7 +69,7 @@ impl Connector for NativeTlsConnector { .host() .to_string(); - let adapter = TransportAdapter::new(transport); + let adapter = TransportAdapter::new(transport.boxed()); let stream = LazyStream::Unstarted(Some((connector, domain, adapter))); let buffers = LazyBuffers::new( @@ -75,11 +77,11 @@ impl Connector for NativeTlsConnector { details.config.output_buffer_size(), ); - let transport = Box::new(NativeTlsTransport { buffers, stream }); + let transport = NativeTlsTransport { buffers, stream }; debug!("Wrapped TLS"); - Ok(Some(transport)) + Ok(Some(Either::B(transport))) } } @@ -167,7 +169,7 @@ fn pemify(der: &[u8], label: &'static str) -> Result { Ok(pem) } -struct NativeTlsTransport { +pub struct NativeTlsTransport { buffers: LazyBuffers, stream: LazyStream, } diff --git a/src/tls/rustls.rs b/src/tls/rustls.rs index 0b89635b..3ea88518 100644 --- a/src/tls/rustls.rs +++ b/src/tls/rustls.rs @@ -12,7 +12,7 @@ use rustls_pki_types::{PrivateSec1KeyDer, ServerName}; use crate::tls::cert::KeyKind; use crate::tls::{RootCerts, TlsProvider}; use crate::transport::{Buffers, ConnectionDetails, Connector, LazyBuffers}; -use crate::transport::{NextTimeout, Transport, TransportAdapter}; +use crate::transport::{Either, NextTimeout, Transport, TransportAdapter}; use crate::Error; use super::TlsConfig; @@ -25,12 +25,14 @@ pub struct RustlsConnector { config: OnceCell>, } -impl Connector for RustlsConnector { +impl Connector for RustlsConnector { + type Out = Either; + fn connect( &self, details: &ConnectionDetails, - chained: Option>, - ) -> Result>, Error> { + chained: Option, + ) -> Result, Error> { let Some(transport) = chained else { panic!("RustlConnector requires a chained transport"); }; @@ -39,12 +41,12 @@ impl Connector for RustlsConnector { // already, otherwise use chained transport as is. if !details.needs_tls() || transport.is_tls() { trace!("Skip"); - return Ok(Some(transport)); + return Ok(Some(Either::A(transport))); } if details.config.tls_config().provider != TlsProvider::Rustls { debug!("Skip because config is not set to Rustls"); - return Ok(Some(transport)); + return Ok(Some(Either::A(transport))); } trace!("Try wrap in TLS"); @@ -71,7 +73,7 @@ impl Connector for RustlsConnector { let conn = ClientConnection::new(config, name)?; let stream = StreamOwned { conn, - sock: TransportAdapter::new(transport), + sock: TransportAdapter::new(transport.boxed()), }; let buffers = LazyBuffers::new( @@ -79,11 +81,11 @@ impl Connector for RustlsConnector { details.config.output_buffer_size(), ); - let transport = Box::new(RustlsTransport { buffers, stream }); + let transport = RustlsTransport { buffers, stream }; debug!("Wrapped TLS"); - Ok(Some(transport)) + Ok(Some(Either::B(transport))) } } @@ -166,7 +168,7 @@ fn build_config(tls_config: &TlsConfig) -> Arc { Arc::new(config) } -struct RustlsTransport { +pub struct RustlsTransport { buffers: LazyBuffers, stream: StreamOwned, } diff --git a/src/unversioned/transport/chain.rs b/src/unversioned/transport/chain.rs index 54309d16..72d6b394 100644 --- a/src/unversioned/transport/chain.rs +++ b/src/unversioned/transport/chain.rs @@ -1,50 +1,133 @@ -use crate::Error; +use std::fmt; +use std::marker::PhantomData; -use super::{ConnectionDetails, Connector, Transport}; +use super::{Connector, Transport}; -/// Helper for a chain of connectors. +/// Two chained connectors called one after another. /// -/// Each step of the chain, can decide whether to: -/// -/// * _Keep_ previous [`Transport`] -/// * _Wrap_ previous [`Transport`] -/// * _Ignore_ previous [`Transport`] in favor of some other connection. -/// -/// For each new connection, the chain will be called one by one and the previously chained -/// transport will be provided to the next as an argument in [`Connector::connect()`]. -/// -/// The chain is always looped fully. There is no early return. +/// Created by calling [`Connector::chain`] on the first connector. +pub struct ChainedConnector(First, Second, PhantomData); + +impl Connector for ChainedConnector +where + In: Transport, + First: Connector, + Second: Connector, +{ + type Out = Second::Out; + + fn connect( + &self, + details: &super::ConnectionDetails, + chained: Option, + ) -> Result, crate::Error> { + let f_out = self.0.connect(details, chained)?; + self.1.connect(details, f_out) + } +} + +impl ChainedConnector { + pub(crate) fn new(first: First, second: Second) -> Self { + ChainedConnector(first, second, PhantomData) + } +} + +impl fmt::Debug for ChainedConnector +where + In: Transport, + First: Connector, + Second: Connector, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("ChainedConnector") + .field(&self.0) + .field(&self.1) + .finish() + } +} + +/// A selection between two transports. #[derive(Debug)] -pub struct ChainedConnector { - chain: Vec>, +pub enum Either { + /// The first transport. + A(A), + /// The second transport. + B(B), } -impl ChainedConnector { - /// Creates a new chain of connectors. - /// - /// For each connection, the chain will be called one by one and the previously chained - /// transport will be provided to the next as an argument in [`Connector::connect()`]. - /// - /// The chain is always looped fully. There is no early return. - pub fn new(chain: impl IntoIterator>) -> Self { - Self { - chain: chain.into_iter().collect(), +impl Transport for Either { + fn buffers(&mut self) -> &mut dyn super::Buffers { + match self { + Either::A(a) => a.buffers(), + Either::B(b) => b.buffers(), + } + } + + fn transmit_output( + &mut self, + amount: usize, + timeout: super::NextTimeout, + ) -> Result<(), crate::Error> { + match self { + Either::A(a) => a.transmit_output(amount, timeout), + Either::B(b) => b.transmit_output(amount, timeout), + } + } + + fn await_input(&mut self, timeout: super::NextTimeout) -> Result { + match self { + Either::A(a) => a.await_input(timeout), + Either::B(b) => b.await_input(timeout), + } + } + + fn is_open(&mut self) -> bool { + match self { + Either::A(a) => a.is_open(), + Either::B(b) => b.is_open(), + } + } + + fn is_tls(&self) -> bool { + match self { + Either::A(a) => a.is_tls(), + Either::B(b) => b.is_tls(), } } } -impl Connector for ChainedConnector { +// Connector is implemented for () to start a chain of connectors. +// +// The `Out` transport is supposedly `()`, but this is never instantiated. +impl Connector<()> for () { + type Out = (); + fn connect( &self, - details: &ConnectionDetails, - chained: Option>, - ) -> Result>, Error> { - let mut conn = chained; + _: &super::ConnectionDetails, + _: Option<()>, + ) -> Result, crate::Error> { + Ok(None) + } +} - for connector in &self.chain { - conn = connector.connect(details, conn)?; - } +// () is a valid Transport for type reasons. +// +// It should never be instantiated as an actual transport. +impl Transport for () { + fn buffers(&mut self) -> &mut dyn super::Buffers { + panic!("Unit transport is not valid") + } + + fn transmit_output(&mut self, _: usize, _: super::NextTimeout) -> Result<(), crate::Error> { + panic!("Unit transport is not valid") + } + + fn await_input(&mut self, _: super::NextTimeout) -> Result { + panic!("Unit transport is not valid") + } - Ok(conn) + fn is_open(&mut self) -> bool { + panic!("Unit transport is not valid") } } diff --git a/src/unversioned/transport/io.rs b/src/unversioned/transport/io.rs index 5129b207..93d1aa78 100644 --- a/src/unversioned/transport/io.rs +++ b/src/unversioned/transport/io.rs @@ -8,16 +8,16 @@ use super::{NextTimeout, Transport}; /// Helper to turn a [`Transport`] into a std::io [`Read`](io::Read) and [`Write`](io::Write). /// /// This is useful when integrating with components that expect a regular `Read`/`Write`. In -/// ureq this is used both for the [`RustlsConnector`](crate::unversioned::transport::RustlsConnector) and the -/// [`NativeTlsConnector`](crate::unversioned::transport::NativeTlsConnector). -pub struct TransportAdapter { +/// ureq this is used both for the [`RustlsConnector`](crate::transport::RustlsConnector) and the +/// [`NativeTlsConnector`](crate::transport::NativeTlsConnector). +pub struct TransportAdapter> { timeout: NextTimeout, - transport: Box, + transport: T, } -impl TransportAdapter { +impl TransportAdapter { /// Creates a new adapter - pub fn new(transport: Box) -> Self { + pub fn new(transport: T) -> Self { Self { timeout: NextTimeout { after: Duration::NotHappening, @@ -34,26 +34,26 @@ impl TransportAdapter { /// Reference to the adapted transport pub fn get_ref(&self) -> &dyn Transport { - &*self.transport + &self.transport } /// Mut reference to the adapted transport pub fn get_mut(&mut self) -> &mut dyn Transport { - &mut *self.transport + &mut self.transport } /// Reference to the inner transport. pub fn inner(&self) -> &dyn Transport { - &*self.transport + &self.transport } /// Turn the adapter back into the wrapped transport - pub fn into_inner(self) -> Box { + pub fn into_inner(self) -> T { self.transport } } -impl io::Read for TransportAdapter { +impl io::Read for TransportAdapter { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.transport .await_input(self.timeout) @@ -68,7 +68,7 @@ impl io::Read for TransportAdapter { } } -impl io::Write for TransportAdapter { +impl io::Write for TransportAdapter { fn write(&mut self, buf: &[u8]) -> io::Result { let output = self.transport.buffers().output(); diff --git a/src/unversioned/transport/mod.rs b/src/unversioned/transport/mod.rs index 0b761680..d3601969 100644 --- a/src/unversioned/transport/mod.rs +++ b/src/unversioned/transport/mod.rs @@ -19,9 +19,7 @@ //! //! The [`Connector`] trait anticipates a chain of connectors that each decide //! whether to help perform the connection or not. It is for instance possible to make a -//! connector handling other schemes than `http`/`https` without affecting "regular" connections -//! using these schemes. See [`ChainedConnector`] for a helper connector that aids setting -//! up a chain of concrete connectors. +//! connector handling other schemes than `http`/`https` without affecting "regular" connections. use std::fmt::Debug; @@ -47,7 +45,7 @@ mod io; pub use io::TransportAdapter; mod chain; -pub use chain::ChainedConnector; +pub use chain::{ChainedConnector, Either}; #[cfg(feature = "_test")] mod test; @@ -83,18 +81,38 @@ pub use crate::timings::NextTimeout; /// in new ways. A user of ureq could implement bespoke connector (such as SCTP) and still use /// the `RustlsConnector` to wrap the underlying transport in TLS. /// -/// The built-in connectors provide SOCKS, TCP sockets and TLS wrapping. -pub trait Connector: Debug + Send + Sync + 'static { - /// Helper to quickly box a transport. - #[doc(hidden)] - fn boxed(self) -> Box - where - Self: Sized, - { - Box::new(self) - } - - /// Try to use this connector +/// The built-in [`DefaultConnector`] provides SOCKS, TCP sockets and TLS wrapping. +/// +/// # Example +/// +/// ``` +/// # #[cfg(all(feature = "rustls", not(feature = "_test")))] { +/// use ureq::{Agent, config::Config}; +/// +/// // These types are not covered by the promises of semver (yet) +/// use ureq::unversioned::transport::{Connector, TcpConnector, RustlsConnector}; +/// use ureq::unversioned::resolver::DefaultResolver; +/// +/// // A connector chain that opens a TCP transport, then wraps it in a TLS. +/// let connector = () +/// .chain(TcpConnector::default()) +/// .chain(RustlsConnector::default()); +/// +/// let config = Config::default(); +/// let resolver = DefaultResolver::default(); +/// +/// // Creates an agent with a bespoke connector +/// let agent = Agent::with_parts(config, connector, resolver); +/// +/// let mut res = agent.get("https://httpbin.org/get").call().unwrap(); +/// let body = res.body_mut().read_to_string().unwrap(); +/// # } +/// ``` +pub trait Connector: Debug + Send + Sync + 'static { + /// The type of transport produced by this connector. + type Out: Transport; + + /// Use this connector to make a [`Transport`]. /// /// * The [`ConnectionDetails`] parameter encapsulates config and the specific details of /// the connection being made currently (such as the [`Uri`]). @@ -103,13 +121,53 @@ pub trait Connector: Debug + Send + Sync + 'static { /// can decide whether they want to pass this `Transport` along as is, wrap it in something /// like TLS or even ignore it to provide some other connection instead. /// - /// Return the `Transport` as produced by this connector, which could be just + /// Returns the [`Transport`] as produced by this connector, which could be just /// the incoming `chained` argument. fn connect( &self, details: &ConnectionDetails, - chained: Option>, - ) -> Result>, Error>; + chained: Option, + ) -> Result, Error>; + + /// Chain this connector to another connector. + /// + /// This connector will be called first, and the output goes into the next connector. + fn chain>(self, next: Next) -> ChainedConnector + where + Self: Sized, + { + ChainedConnector::new(self, next) + } +} + +/// Box the transport to erase the types. +/// +/// This is typically used after the chain of connectors is set up. +pub(crate) fn boxed_connector(c: C) -> Box>> +where + In: Transport, + C: Connector, +{ + #[derive(Debug)] + struct BoxingConnector; + + impl Connector for BoxingConnector { + type Out = Box; + + fn connect( + &self, + _: &ConnectionDetails, + chained: Option, + ) -> Result, Error> { + if let Some(transport) = chained { + Ok(Some(Box::new(transport))) + } else { + Ok(None) + } + } + } + + Box::new(c.chain(BoxingConnector)) } /// The parameters needed to create a [`Transport`]. @@ -183,7 +241,7 @@ impl<'a> ConnectionDetails<'a> { /// It's important to call [`Buffers::input_consume()`] also with 0 consumed bytes since that's /// how we keep track of whether the input is making progress. /// -pub trait Transport: Debug + Send + Sync { +pub trait Transport: Debug + Send + Sync + 'static { /// Provide buffers for this transport. fn buffers(&mut self) -> &mut dyn Buffers; @@ -210,11 +268,21 @@ pub trait Transport: Debug + Send + Sync { fn is_tls(&self) -> bool { false } + + /// Turn this transport in a boxed version. + // TODO(martin): is is complicating the public API? + #[doc(hidden)] + fn boxed(self) -> Box + where + Self: Sized + 'static, + { + Box::new(self) + } } /// Default connector providing TCP sockets, TLS and SOCKS proxy. /// -/// This connector is a [`ChainedConnector`] with the following chain: +/// This connector is the following chain: /// /// 1. [`SocksConnector`] to handle proxy settings if set. /// 2. [`TcpConnector`] to open a socket directly if a proxy is not used. @@ -228,7 +296,7 @@ pub trait Transport: Debug + Send + Sync { /// #[derive(Debug)] pub struct DefaultConnector { - chain: ChainedConnector, + inner: Box>>, } impl DefaultConnector { @@ -240,58 +308,62 @@ impl DefaultConnector { impl Default for DefaultConnector { fn default() -> Self { - let chain = ChainedConnector::new([ - // - // When enabled, all tests are connected to a dummy server and will not - // make requests to the internet. - #[cfg(feature = "_test")] - test::TestConnector.boxed(), - // - // If we are using socks-proxy, that takes precedence over TcpConnector. - #[cfg(feature = "socks-proxy")] - SocksConnector::default().boxed(), - // - // If the config indicates we ought to use a socks proxy - // and the feature flag isn't enabled, we should warn the user. - #[cfg(not(feature = "socks-proxy"))] - no_proxy::WarnOnNoSocksConnector.boxed(), - // - // If we didn't get a socks-proxy, open a Tcp connection - TcpConnector::default().boxed(), - // - // If rustls is enabled, prefer that - #[cfg(feature = "rustls")] - RustlsConnector::default().boxed(), - // - // Panic if the config calls for rustls, the uri scheme is https and that - // TLS provider is not enabled by feature flags. - #[cfg(feature = "_tls")] - no_tls::WarnOnMissingTlsProvider(crate::tls::TlsProvider::Rustls).boxed(), - // - // As a fallback if rustls isn't enabled, use native-tls - #[cfg(feature = "native-tls")] - NativeTlsConnector::default().boxed(), - // - // Panic if the config calls for native-tls, the uri scheme is https and that - // TLS provider is not enabled by feature flags. - #[cfg(feature = "_tls")] - no_tls::WarnOnMissingTlsProvider(crate::tls::TlsProvider::NativeTls).boxed(), - // - // Do the final CONNECT proxy on top of the connection if indicated by config. - ConnectProxyConnector.boxed(), - ]); - - DefaultConnector { chain } + let inner = (); + + // When enabled, all tests are connected to a dummy server and will not + // make requests to the internet. + #[cfg(feature = "_test")] + let inner = inner.chain(test::TestConnector); + + // If we are using socks-proxy, that takes precedence over TcpConnector. + #[cfg(feature = "socks-proxy")] + let inner = inner.chain(SocksConnector::default()); + + // If the config indicates we ought to use a socks proxy + // and the feature flag isn't enabled, we should warn the user. + #[cfg(not(feature = "socks-proxy"))] + let inner = inner.chain(no_proxy::WarnOnNoSocksConnector); + + // If we didn't get a socks-proxy, open a Tcp connection + let inner = inner.chain(TcpConnector::default()); + + // If rustls is enabled, prefer that + #[cfg(feature = "rustls")] + let inner = inner.chain(RustlsConnector::default()); + + // Panic if the config calls for rustls, the uri scheme is https and that + // TLS provider is not enabled by feature flags. + #[cfg(feature = "_tls")] + let inner = inner.chain(no_tls::WarnOnMissingTlsProvider( + crate::tls::TlsProvider::Rustls, + )); + + // As a fallback if rustls isn't enabled, use native-tls + #[cfg(feature = "native-tls")] + let inner = inner.chain(NativeTlsConnector::default()); + + // Panic if the config calls for native-tls, the uri scheme is https and that + // TLS provider is not enabled by feature flags. + #[cfg(feature = "_tls")] + let inner = inner.chain(no_tls::WarnOnMissingTlsProvider( + crate::tls::TlsProvider::NativeTls, + )); + + DefaultConnector { + inner: boxed_connector(inner), + } } } -impl Connector for DefaultConnector { +impl Connector<()> for DefaultConnector { + type Out = Box; + fn connect( &self, details: &ConnectionDetails, - chained: Option>, - ) -> Result>, Error> { - self.chain.connect(details, chained) + chained: Option<()>, + ) -> Result, Error> { + self.inner.connect(details, chained) } } @@ -302,12 +374,14 @@ mod no_proxy { #[derive(Debug)] pub(crate) struct WarnOnNoSocksConnector; - impl Connector for WarnOnNoSocksConnector { + impl Connector for WarnOnNoSocksConnector { + type Out = In; + fn connect( &self, details: &ConnectionDetails, - chained: Option>, - ) -> Result>, Error> { + chained: Option, + ) -> Result, Error> { if chained.is_none() { if let Some(proxy) = details.config.proxy() { if proxy.proto().is_socks() { @@ -341,12 +415,14 @@ mod no_tls { #[derive(Debug)] pub(crate) struct WarnOnMissingTlsProvider(pub TlsProvider); - impl Connector for WarnOnMissingTlsProvider { + impl Connector for WarnOnMissingTlsProvider { + type Out = In; + fn connect( &self, details: &ConnectionDetails, - chained: Option>, - ) -> Result>, Error> { + chained: Option, + ) -> Result, Error> { let already_tls = chained.as_ref().map(|c| c.is_tls()).unwrap_or(false); if already_tls { @@ -370,3 +446,24 @@ mod no_tls { } } } + +impl Transport for Box +where + T: ?Sized, +{ + fn buffers(&mut self) -> &mut dyn Buffers { + (**self).buffers() + } + + fn transmit_output(&mut self, amount: usize, timeout: NextTimeout) -> Result<(), Error> { + (**self).transmit_output(amount, timeout) + } + + fn await_input(&mut self, timeout: NextTimeout) -> Result { + (**self).await_input(timeout) + } + + fn is_open(&mut self) -> bool { + (**self).is_open() + } +} diff --git a/src/unversioned/transport/socks.rs b/src/unversioned/transport/socks.rs index 783c6a67..0d285939 100644 --- a/src/unversioned/transport/socks.rs +++ b/src/unversioned/transport/socks.rs @@ -8,6 +8,7 @@ use socks::{Socks4Stream, Socks5Stream}; use crate::proxy::{Proto, Proxy}; use crate::Error; +use super::chain::Either; use super::ResolvedSocketAddrs; use super::tcp::TcpTransport; @@ -20,26 +21,28 @@ use super::{ConnectionDetails, Connector, LazyBuffers, NextTimeout, Transport}; /// The connector looks at the proxy settings in [`proxy`](crate::config::ConfigBuilder::proxy) to /// determine whether to attempt a proxy connection or not. #[derive(Default)] -pub struct SocksConnector {} +pub struct SocksConnector(()); + +impl Connector for SocksConnector { + type Out = Either; -impl Connector for SocksConnector { fn connect( &self, details: &ConnectionDetails, - chained: Option>, - ) -> Result>, Error> { + chained: Option, + ) -> Result, Error> { let proxy = match details.config.proxy() { Some(v) if v.proto().is_socks() => v, // If there is no proxy configured, or it isn't a SOCKS proxy, use whatever is chained. _ => { trace!("SOCKS not configured"); - return Ok(chained); + return Ok(chained.map(Either::A)); } }; if chained.is_some() { trace!("Skip"); - return Ok(chained); + return Ok(chained.map(Either::A)); } let proxy_addrs = details @@ -56,9 +59,9 @@ impl Connector for SocksConnector { details.config.input_buffer_size(), details.config.output_buffer_size(), ); - let transport = Box::new(TcpTransport::new(stream, buffers)); + let transport = TcpTransport::new(stream, buffers); - Ok(Some(transport)) + Ok(Some(Either::B(transport))) } } diff --git a/src/unversioned/transport/tcp.rs b/src/unversioned/transport/tcp.rs index 92d593b7..f07fe450 100644 --- a/src/unversioned/transport/tcp.rs +++ b/src/unversioned/transport/tcp.rs @@ -6,6 +6,7 @@ use crate::config::Config; use crate::util::IoResultExt; use crate::Error; +use super::chain::Either; use super::ResolvedSocketAddrs; use super::time::Duration; @@ -15,17 +16,19 @@ use super::{Buffers, ConnectionDetails, Connector, LazyBuffers, NextTimeout, Tra /// Connector for regular TCP sockets. pub struct TcpConnector(()); -impl Connector for TcpConnector { +impl Connector for TcpConnector { + type Out = Either; + fn connect( &self, details: &ConnectionDetails, - chained: Option>, - ) -> Result>, crate::Error> { + chained: Option, + ) -> Result, Error> { if chained.is_some() { // The chained connection overrides whatever we were to open here. // In the DefaultConnector chain this would be a SOCKS proxy connection. trace!("Skip"); - return Ok(chained); + return Ok(chained.map(Either::A)); } let config = &details.config; @@ -34,7 +37,7 @@ impl Connector for TcpConnector { let buffers = LazyBuffers::new(config.input_buffer_size(), config.output_buffer_size()); let transport = TcpTransport::new(stream, buffers); - Ok(Some(Box::new(transport))) + Ok(Some(Either::B(transport))) } } diff --git a/src/unversioned/transport/test.rs b/src/unversioned/transport/test.rs index 48c5df42..9f6c4443 100644 --- a/src/unversioned/transport/test.rs +++ b/src/unversioned/transport/test.rs @@ -12,6 +12,7 @@ use http::{Method, Request, Uri}; use crate::http; use crate::Error; +use super::chain::Either; use super::time::Duration; use super::{Buffers, ConnectionDetails, Connector, LazyBuffers, NextTimeout, Transport}; @@ -20,16 +21,18 @@ pub(crate) struct TestConnector; thread_local!(static HANDLERS: RefCell> = const { RefCell::new(Vec::new()) }); -impl Connector for TestConnector { +impl Connector for TestConnector { + type Out = Either; + fn connect( &self, details: &ConnectionDetails, - chained: Option>, - ) -> Result>, Error> { + chained: Option, + ) -> Result, Error> { if chained.is_some() { // The chained connection overrides whatever we were to open here. trace!("Skip"); - return Ok(chained); + return Ok(chained.map(Either::A)); } let config = details.config; @@ -52,7 +55,7 @@ impl Connector for TestConnector { connected: true, }; - Ok(Some(Box::new(transport))) + Ok(Some(Either::B(transport))) } } @@ -436,7 +439,7 @@ impl io::Write for TxWrite { } } -struct TestTransport { +pub(crate) struct TestTransport { buffers: LazyBuffers, tx: mpsc::SyncSender>, rx: SyncReceiver>,