Skip to main content

ureq/unversioned/transport/
mod.rs

1//! HTTP/1.1 data transport.
2//!
3//! **NOTE: transport does not (yet) [follow semver][super].**
4//!
5//! _NOTE: Transport is deep configuration of ureq and is not required for regular use._
6//!
7//! ureq provides a pluggable transport layer making it possible to write bespoke
8//! transports using the HTTP/1.1 protocol from point A to B. The
9//! [`Agent::with_parts()`](crate::Agent::with_parts) constructor takes an implementation
10//! of the [`Connector`] trait which is used for all connections made using that
11//! agent.
12//!
13//! The [DefaultConnector] covers the regular needs for HTTP/1.1:
14//!
15//! * TCP Sockets
16//! * SOCKS-proxy sockets
17//! * HTTPS/TLS using rustls (feature flag **rustls**)
18//! * HTTPS/TLS using native-tls (feature flag **native-tls** + [config](crate::tls::TlsProvider::NativeTls))
19//!
20//! The [`Connector`] trait anticipates a chain of connectors that each decide
21//! whether to help perform the connection or not. It is for instance possible to make a
22//! connector handling other schemes than `http`/`https` without affecting "regular" connections.
23
24use std::fmt::Debug;
25use std::sync::Arc;
26
27use http::Uri;
28use http::uri::Scheme;
29
30use crate::Error;
31use crate::config::Config;
32use crate::http;
33
34use super::resolver::{ResolvedSocketAddrs, Resolver};
35
36mod buf;
37pub use buf::{Buffers, LazyBuffers};
38
39mod tcp;
40pub use self::tcp::TcpConnector;
41
42mod io;
43pub use io::TransportAdapter;
44
45mod chain;
46pub use chain::{ChainedConnector, Either};
47
48mod connect;
49pub use connect::ConnectProxyConnector;
50
51#[cfg(feature = "_test")]
52mod test;
53#[cfg(feature = "_test")]
54pub use test::{set_handler, set_handler_cb};
55
56#[cfg(feature = "socks-proxy")]
57mod socks;
58#[cfg(feature = "socks-proxy")]
59pub use self::socks::SocksConnector;
60
61#[cfg(feature = "_rustls")]
62pub use crate::tls::rustls::RustlsConnector;
63
64#[cfg(feature = "native-tls")]
65pub use crate::tls::native_tls::NativeTlsConnector;
66
67pub mod time;
68use self::time::Instant;
69
70pub use crate::timings::NextTimeout;
71
72/// Trait for components providing some aspect of connecting.
73///
74/// A connector instance is reused to produce multiple [`Transport`] instances (where `Transport`
75/// instance would typically be a socket connection).
76///
77/// A connector can be part of a chain of connectors. The [`DefaultConnector`] provides a chain that
78/// first tries to make a concrete socket connection (using [`TcpConnector`]) and then pass the
79/// resulting [`Transport`] to a TLS wrapping connector
80/// (see [`RustlsConnector`]). This makes it possible combine connectors
81/// in new ways. A user of ureq could implement bespoke connector (such as SCTP) and still use
82/// the `RustlsConnector` to wrap the underlying transport in TLS.
83///
84/// The built-in [`DefaultConnector`] provides SOCKS, TCP sockets and TLS wrapping.
85///
86/// # Errors
87///
88/// When writing a bespoke connector chain we recommend handling errors like this:
89///
90/// 1. Map to [`Error::Io`] as far as possible.
91/// 2. Map to any other [`Error`] where reasonable.
92/// 3. Fall back on [`Error::Other`] preserving the original error.
93/// 4. As a last resort [`Error::ConnectionFailed`] + logging.
94///
95/// # Example
96///
97/// ```
98/// # #[cfg(all(feature = "rustls", not(feature = "_test")))] {
99/// use ureq::{Agent, config::Config};
100///
101/// // These types are not covered by the promises of semver (yet)
102/// use ureq::unversioned::transport::{Connector, TcpConnector, RustlsConnector};
103/// use ureq::unversioned::resolver::DefaultResolver;
104///
105/// // A connector chain that opens a TCP transport, then wraps it in a TLS.
106/// let connector = ()
107///     .chain(TcpConnector::default())
108///     .chain(RustlsConnector::default());
109///
110/// let config = Config::default();
111/// let resolver = DefaultResolver::default();
112///
113/// // Creates an agent with a bespoke connector
114/// let agent = Agent::with_parts(config, connector, resolver);
115///
116/// let mut res = agent.get("https://httpbin.org/get").call().unwrap();
117/// let body = res.body_mut().read_to_string().unwrap();
118/// # }
119/// ```
120pub trait Connector<In: Transport = ()>: Debug + Send + Sync + 'static {
121    /// The type of transport produced by this connector.
122    type Out: Transport;
123
124    /// Use this connector to make a [`Transport`].
125    ///
126    /// * The [`ConnectionDetails`] parameter encapsulates config and the specific details of
127    ///   the connection being made currently (such as the [`Uri`]).
128    /// * The `chained` parameter is used for connector chains and contains the [`Transport`]
129    ///   instantiated one of the previous connectors in the chain. All `Connector` instances
130    ///   can decide whether they want to pass this `Transport` along as is, wrap it in something
131    ///   like TLS or even ignore it to provide some other connection instead.
132    ///
133    /// Returns the [`Transport`] as produced by this connector, which could be just
134    /// the incoming `chained` argument.
135    fn connect(
136        &self,
137        details: &ConnectionDetails,
138        chained: Option<In>,
139    ) -> Result<Option<Self::Out>, Error>;
140
141    /// Chain this connector to another connector.
142    ///
143    /// This connector will be called first, and the output goes into the next connector.
144    fn chain<Next: Connector<Self::Out>>(self, next: Next) -> ChainedConnector<In, Self, Next>
145    where
146        Self: Sized,
147    {
148        ChainedConnector::new(self, next)
149    }
150}
151
152/// Box a connector to erase the types.
153///
154/// This is typically used after the chain of connectors is set up.
155pub(crate) fn boxed_connector<In, C>(c: C) -> Box<dyn Connector<In, Out = Box<dyn Transport>>>
156where
157    In: Transport,
158    C: Connector<In>,
159{
160    #[derive(Debug)]
161    struct BoxingConnector;
162
163    impl<In: Transport> Connector<In> for BoxingConnector {
164        type Out = Box<dyn Transport>;
165
166        fn connect(
167            &self,
168            _: &ConnectionDetails,
169            chained: Option<In>,
170        ) -> Result<Option<Self::Out>, Error> {
171            if let Some(transport) = chained {
172                Ok(Some(Box::new(transport)))
173            } else {
174                Ok(None)
175            }
176        }
177    }
178
179    Box::new(c.chain(BoxingConnector))
180}
181
182/// The parameters needed to create a [`Transport`].
183pub struct ConnectionDetails<'a> {
184    /// Full uri that is being requested.
185    ///
186    /// In the case of CONNECT (HTTP) proxy, this is the URI of the
187    /// proxy, and the actual URI is in the `proxied` field.
188    pub uri: &'a Uri,
189
190    /// The resolved IP address + port for the uri being requested. See [`Resolver`].
191    ///
192    /// For proxies, whetherh this holds real addresses depends on
193    /// [`Proxy::resolve_target()`](crate::Proxy::resolve_target).
194    pub addrs: ResolvedSocketAddrs,
195
196    /// The configuration.
197    ///
198    /// Agent or Request level.
199    pub config: &'a Config,
200
201    /// Whether the config is request level.
202    pub request_level: bool,
203
204    /// The resolver configured on [`Agent`](crate::Agent).
205    ///
206    /// Typically the IP address of the host in the uri is already resolved to the `addr`
207    /// property. However there might be cases where additional DNS lookups need to be
208    /// made in the connector itself, such as resolving a SOCKS proxy server.
209    pub resolver: &'a dyn Resolver,
210
211    /// Current time.
212    ///
213    /// Time the ConnectionDetails was created.
214    pub now: Instant,
215
216    /// The next timeout for making the connection.
217    // TODO(martin): Make mechanism to lower duration for each step in the connector chain.
218    pub timeout: NextTimeout,
219
220    /// Provides the current time.
221    pub current_time: Arc<dyn Fn() -> Instant + Send + Sync + 'static>,
222
223    /// Run the connector chain.
224    ///
225    /// Used for CONNECT proxy to establish a connection to the proxy server itself.
226    pub run_connector: Arc<RunConnector>,
227}
228
229pub(crate) type RunConnector =
230    dyn Fn(&ConnectionDetails) -> Result<Box<dyn Transport>, Error> + Send + Sync;
231
232impl<'a> ConnectionDetails<'a> {
233    /// Tell if the requested socket need TLS wrapping.
234    pub fn needs_tls(&self) -> bool {
235        self.uri.scheme() == Some(&Scheme::HTTPS)
236    }
237}
238
239/// Transport of HTTP/1.1 as created by a [`Connector`].
240///
241/// In ureq, [`Transport`] and [`Buffers`] go hand in hand. The rest of ureq tries to minimize
242/// the allocations, and the transport is responsible for providing the buffers required
243/// to perform the request. Unless the transport requires special buffer handling, the
244/// [`LazyBuffers`] implementation can be used.
245///
246/// For sending data, the order of calls are:
247///
248/// 1. [`Transport::buffers()`] to obtain the buffers.
249/// 2. [`Buffers::output()`] or [`Buffers::tmp_and_output`]
250///    depending where in the life cycle of the request ureq is.
251/// 3. [`Transport::transmit_output()`] to ask the transport to send/flush the `amount` of
252///    buffers used in 2.
253///
254/// For receiving data, the order of calls are:
255///
256/// 1. [`Transport::maybe_await_input()`]
257/// 2. The transport impl itself uses [`Buffers::input_append_buf()`] to fill a number
258///    of bytes from the underlying transport and use [`Buffers::input_appended()`] to
259///    tell the buffer how much been filled.
260/// 3. [`Transport::buffers()`] to obtain the buffers
261/// 4. [`Buffers::input()`] followed by [`Buffers::input_consume()`]. It's important to retain the
262///    unconsumed bytes for the next call to `maybe_await_input()`. This is handled by [`LazyBuffers`].
263///    It's important to call [`Buffers::input_consume()`] also with 0 consumed bytes since that's
264///    how we keep track of whether the input is making progress.
265///
266pub trait Transport: Debug + Send + Sync + 'static {
267    /// Provide buffers for this transport.
268    fn buffers(&mut self) -> &mut dyn Buffers;
269
270    /// Transmit `amount` of the output buffer. ureq will always transmit the entirety
271    /// of the data written to the output buffer. It is expected that the transport will
272    /// transmit the entire requested `amount`.
273    ///
274    /// The timeout should be used to abort the transmission if the amount can't be written in time.
275    /// If that happens the transport must return an [`Error::Timeout`] instance.
276    fn transmit_output(&mut self, amount: usize, timeout: NextTimeout) -> Result<(), Error>;
277
278    /// Await input from the transport.
279    ///
280    /// Early returns if [`Buffers::can_use_input()`], return true.
281    #[doc(hidden)]
282    fn maybe_await_input(&mut self, timeout: NextTimeout) -> Result<bool, Error> {
283        // If we already have input available, we don't wait.
284        // This might be false even when there is input in the buffer
285        // because the last use of the buffer made no progress.
286        // Example: we might want to read the _entire_ http request headers,
287        //          not partially.
288        if self.buffers().can_use_input() {
289            return Ok(true);
290        }
291
292        self.await_input(timeout)
293    }
294
295    /// Wait for input and fill the buffer.
296    ///
297    /// 1. Use [`Buffers::input_append_buf()`] to fill the buffer
298    /// 2. Followed by [`Buffers::input_appended()`] to report how many bytes were read.
299    ///
300    /// Returns `true` if it made progress, i.e. if it managed to fill the input buffer with any bytes.
301    fn await_input(&mut self, timeout: NextTimeout) -> Result<bool, Error>;
302
303    /// Tell whether this transport is still functional. This must provide an accurate answer
304    /// for connection pooling to work.
305    fn is_open(&mut self) -> bool;
306
307    /// Whether the transport is TLS.
308    ///
309    /// Defaults to `false`, override in TLS transports.
310    fn is_tls(&self) -> bool {
311        false
312    }
313
314    /// Turn this transport in a boxed version.
315    // TODO(martin): is is complicating the public API?
316    #[doc(hidden)]
317    fn boxed(self) -> Box<dyn Transport>
318    where
319        Self: Sized + 'static,
320    {
321        Box::new(self)
322    }
323}
324
325/// Default connector providing TCP sockets, TLS and SOCKS proxy.
326///
327/// This connector is the following chain:
328///
329/// 1. [`SocksConnector`] to handle proxy settings if set.
330/// 2. [`TcpConnector`] to open a socket directly if a proxy is not used.
331/// 3. [`RustlsConnector`] which wraps the
332///    connection from 1 or 2 in TLS if the scheme is `https` and the
333///    [`TlsConfig`](crate::tls::TlsConfig) indicate we are using **rustls**.
334///    This is the default TLS provider.
335/// 4. [`NativeTlsConnector`] which wraps
336///    the connection from 1 or 2 in TLS if the scheme is `https` and
337///    [`TlsConfig`](crate::tls::TlsConfig) indicate we are using **native-tls**.
338///
339#[derive(Debug)]
340pub struct DefaultConnector {
341    inner: Box<dyn Connector<(), Out = Box<dyn Transport>>>,
342}
343
344impl DefaultConnector {
345    /// Creates a default connector.
346    pub fn new() -> Self {
347        Self::default()
348    }
349}
350
351impl Default for DefaultConnector {
352    fn default() -> Self {
353        let inner = ();
354
355        // When enabled, all tests are connected to a dummy server and will not
356        // make requests to the internet.
357        #[cfg(feature = "_test")]
358        let inner = inner.chain(test::TestConnector);
359
360        // If we are using socks-proxy, that takes precedence over TcpConnector.
361        #[cfg(feature = "socks-proxy")]
362        let inner = inner.chain(SocksConnector::default());
363
364        // If the config indicates we ought to use a socks proxy
365        // and the feature flag isn't enabled, we should warn the user.
366        #[cfg(not(feature = "socks-proxy"))]
367        let inner = inner.chain(no_proxy::WarnOnNoSocksConnector);
368
369        // If this is a CONNECT proxy, we must "prepare" the socket
370        // by setting up another connection and sending the `CONNECT host:port` line.
371        let inner = inner.chain(ConnectProxyConnector::default());
372
373        // If we didn't get a socks-proxy, open a Tcp connection
374        let inner = inner.chain(TcpConnector::default());
375
376        // If rustls is enabled, prefer that
377        #[cfg(feature = "_rustls")]
378        let inner = inner.chain(RustlsConnector::default());
379
380        // Panic if the config calls for rustls, the uri scheme is https and that
381        // TLS provider is not enabled by feature flags.
382        #[cfg(feature = "_tls")]
383        let inner = inner.chain(no_tls::WarnOnMissingTlsProvider(
384            crate::tls::TlsProvider::Rustls,
385        ));
386
387        // As a fallback if rustls isn't enabled, use native-tls
388        #[cfg(feature = "native-tls")]
389        let inner = inner.chain(NativeTlsConnector::default());
390
391        // Panic if the config calls for native-tls, the uri scheme is https and that
392        // TLS provider is not enabled by feature flags.
393        #[cfg(feature = "_tls")]
394        let inner = inner.chain(no_tls::WarnOnMissingTlsProvider(
395            crate::tls::TlsProvider::NativeTls,
396        ));
397
398        DefaultConnector {
399            inner: boxed_connector(inner),
400        }
401    }
402}
403
404impl Connector<()> for DefaultConnector {
405    type Out = Box<dyn Transport>;
406
407    fn connect(
408        &self,
409        details: &ConnectionDetails,
410        chained: Option<()>,
411    ) -> Result<Option<Self::Out>, Error> {
412        self.inner.connect(details, chained)
413    }
414}
415
416#[cfg(not(feature = "socks-proxy"))]
417mod no_proxy {
418    use super::{ConnectionDetails, Connector, Debug, Error, Transport};
419
420    #[derive(Debug)]
421    pub(crate) struct WarnOnNoSocksConnector;
422
423    impl<In: Transport> Connector<In> for WarnOnNoSocksConnector {
424        type Out = In;
425
426        fn connect(
427            &self,
428            details: &ConnectionDetails,
429            chained: Option<In>,
430        ) -> Result<Option<Self::Out>, Error> {
431            if chained.is_none() {
432                if let Some(proxy) = details.config.proxy() {
433                    if proxy.protocol().is_socks() {
434                        if proxy.is_from_env() {
435                            warn!(
436                                "Enable feature socks-proxy to use proxy
437                                configured by environment variables"
438                            );
439                        } else {
440                            // If a user bothered to manually create a Config.proxy setting,
441                            // and it's not honored, assume it's a serious error.
442                            panic!(
443                                "Enable feature socks-proxy to use
444                                manually configured proxy"
445                            );
446                        }
447                    }
448                }
449            }
450            Ok(chained)
451        }
452    }
453}
454
455#[cfg(feature = "_tls")]
456mod no_tls {
457    use crate::tls::TlsProvider;
458
459    use super::{ConnectionDetails, Connector, Debug, Error, Transport};
460
461    #[derive(Debug)]
462    pub(crate) struct WarnOnMissingTlsProvider(pub TlsProvider);
463
464    impl<In: Transport> Connector<In> for WarnOnMissingTlsProvider {
465        type Out = In;
466
467        fn connect(
468            &self,
469            details: &ConnectionDetails,
470            chained: Option<In>,
471        ) -> Result<Option<Self::Out>, Error> {
472            let already_tls = chained.as_ref().map(|c| c.is_tls()).unwrap_or(false);
473
474            if already_tls {
475                return Ok(chained);
476            }
477
478            let tls_config = details.config.tls_config();
479
480            if details.needs_tls()
481                && tls_config.provider() == self.0
482                && !self.0.is_feature_enabled()
483            {
484                panic!(
485                    "uri scheme is https, provider is {:?} but feature is not enabled: {}",
486                    self.0,
487                    self.0.feature_name()
488                );
489            }
490
491            Ok(chained)
492        }
493    }
494}
495
496impl<T: Transport> Transport for Box<T>
497where
498    T: ?Sized,
499{
500    fn buffers(&mut self) -> &mut dyn Buffers {
501        (**self).buffers()
502    }
503
504    fn transmit_output(&mut self, amount: usize, timeout: NextTimeout) -> Result<(), Error> {
505        (**self).transmit_output(amount, timeout)
506    }
507
508    fn await_input(&mut self, timeout: NextTimeout) -> Result<bool, Error> {
509        (**self).await_input(timeout)
510    }
511
512    fn is_open(&mut self) -> bool {
513        (**self).is_open()
514    }
515
516    fn is_tls(&self) -> bool {
517        (**self).is_tls()
518    }
519}