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}