hyper/client/connect/
mod.rs

1//! Connectors used by the `Client`.
2//!
3//! This module contains:
4//!
5//! - A default [`HttpConnector`][] that does DNS resolution and establishes
6//!   connections over TCP.
7//! - Types to build custom connectors.
8//!
9//! # Connectors
10//!
11//! A "connector" is a [`Service`][] that takes a [`Uri`][] destination, and
12//! its `Response` is some type implementing [`AsyncRead`][], [`AsyncWrite`][],
13//! and [`Connection`][].
14//!
15//! ## Custom Connectors
16//!
17//! A simple connector that ignores the `Uri` destination and always returns
18//! a TCP connection to the same address could be written like this:
19//!
20//! ```rust,ignore
21//! let connector = tower::service_fn(|_dst| async {
22//!     tokio::net::TcpStream::connect("127.0.0.1:1337")
23//! })
24//! ```
25//!
26//! Or, fully written out:
27//!
28//! ```
29//! # #[cfg(feature = "runtime")]
30//! # mod rt {
31//! use std::{future::Future, net::SocketAddr, pin::Pin, task::{self, Poll}};
32//! use hyper::{service::Service, Uri};
33//! use tokio::net::TcpStream;
34//!
35//! #[derive(Clone)]
36//! struct LocalConnector;
37//!
38//! impl Service<Uri> for LocalConnector {
39//!     type Response = TcpStream;
40//!     type Error = std::io::Error;
41//!     // We can't "name" an `async` generated future.
42//!     type Future = Pin<Box<
43//!         dyn Future<Output = Result<Self::Response, Self::Error>> + Send
44//!     >>;
45//!
46//!     fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
47//!         // This connector is always ready, but others might not be.
48//!         Poll::Ready(Ok(()))
49//!     }
50//!
51//!     fn call(&mut self, _: Uri) -> Self::Future {
52//!         Box::pin(TcpStream::connect(SocketAddr::from(([127, 0, 0, 1], 1337))))
53//!     }
54//! }
55//! # }
56//! ```
57//!
58//! It's worth noting that for `TcpStream`s, the [`HttpConnector`][] is a
59//! better starting place to extend from.
60//!
61//! Using either of the above connector examples, it can be used with the
62//! `Client` like this:
63//!
64//! ```
65//! # #[cfg(feature = "runtime")]
66//! # fn rt () {
67//! # let connector = hyper::client::HttpConnector::new();
68//! // let connector = ...
69//!
70//! let client = hyper::Client::builder()
71//!     .build::<_, hyper::Body>(connector);
72//! # }
73//! ```
74//!
75//!
76//! [`HttpConnector`]: HttpConnector
77//! [`Service`]: crate::service::Service
78//! [`Uri`]: ::http::Uri
79//! [`AsyncRead`]: tokio::io::AsyncRead
80//! [`AsyncWrite`]: tokio::io::AsyncWrite
81//! [`Connection`]: Connection
82use std::fmt;
83
84use ::http::Extensions;
85
86cfg_feature! {
87    #![feature = "tcp"]
88
89    pub use self::http::{HttpConnector, HttpInfo};
90
91    pub mod dns;
92    mod http;
93}
94
95cfg_feature! {
96    #![any(feature = "http1", feature = "http2")]
97
98    pub use self::sealed::Connect;
99}
100
101/// Describes a type returned by a connector.
102pub trait Connection {
103    /// Return metadata describing the connection.
104    fn connected(&self) -> Connected;
105}
106
107/// Extra information about the connected transport.
108///
109/// This can be used to inform recipients about things like if ALPN
110/// was used, or if connected to an HTTP proxy.
111#[derive(Debug)]
112pub struct Connected {
113    pub(super) alpn: Alpn,
114    pub(super) is_proxied: bool,
115    pub(super) extra: Option<Extra>,
116}
117
118pub(super) struct Extra(Box<dyn ExtraInner>);
119
120#[derive(Clone, Copy, Debug, PartialEq)]
121pub(super) enum Alpn {
122    H2,
123    None,
124}
125
126impl Connected {
127    /// Create new `Connected` type with empty metadata.
128    pub fn new() -> Connected {
129        Connected {
130            alpn: Alpn::None,
131            is_proxied: false,
132            extra: None,
133        }
134    }
135
136    /// Set whether the connected transport is to an HTTP proxy.
137    ///
138    /// This setting will affect if HTTP/1 requests written on the transport
139    /// will have the request-target in absolute-form or origin-form:
140    ///
141    /// - When `proxy(false)`:
142    ///
143    /// ```http
144    /// GET /guide HTTP/1.1
145    /// ```
146    ///
147    /// - When `proxy(true)`:
148    ///
149    /// ```http
150    /// GET http://hyper.rs/guide HTTP/1.1
151    /// ```
152    ///
153    /// Default is `false`.
154    pub fn proxy(mut self, is_proxied: bool) -> Connected {
155        self.is_proxied = is_proxied;
156        self
157    }
158
159    /// Determines if the connected transport is to an HTTP proxy.
160    pub fn is_proxied(&self) -> bool {
161        self.is_proxied
162    }
163
164    /// Set extra connection information to be set in the extensions of every `Response`.
165    pub fn extra<T: Clone + Send + Sync + 'static>(mut self, extra: T) -> Connected {
166        if let Some(prev) = self.extra {
167            self.extra = Some(Extra(Box::new(ExtraChain(prev.0, extra))));
168        } else {
169            self.extra = Some(Extra(Box::new(ExtraEnvelope(extra))));
170        }
171        self
172    }
173
174    /// Copies the extra connection information into an `Extensions` map.
175    pub fn get_extras(&self, extensions: &mut Extensions) {
176        if let Some(extra) = &self.extra {
177            extra.set(extensions);
178        }
179    }
180
181    /// Set that the connected transport negotiated HTTP/2 as its next protocol.
182    pub fn negotiated_h2(mut self) -> Connected {
183        self.alpn = Alpn::H2;
184        self
185    }
186
187    /// Determines if the connected transport negotiated HTTP/2 as its next protocol.
188    pub fn is_negotiated_h2(&self) -> bool {
189        self.alpn == Alpn::H2
190    }
191
192    // Don't public expose that `Connected` is `Clone`, unsure if we want to
193    // keep that contract...
194    #[cfg(feature = "http2")]
195    pub(super) fn clone(&self) -> Connected {
196        Connected {
197            alpn: self.alpn.clone(),
198            is_proxied: self.is_proxied,
199            extra: self.extra.clone(),
200        }
201    }
202}
203
204// ===== impl Extra =====
205
206impl Extra {
207    pub(super) fn set(&self, res: &mut Extensions) {
208        self.0.set(res);
209    }
210}
211
212impl Clone for Extra {
213    fn clone(&self) -> Extra {
214        Extra(self.0.clone_box())
215    }
216}
217
218impl fmt::Debug for Extra {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        f.debug_struct("Extra").finish()
221    }
222}
223
224trait ExtraInner: Send + Sync {
225    fn clone_box(&self) -> Box<dyn ExtraInner>;
226    fn set(&self, res: &mut Extensions);
227}
228
229// This indirection allows the `Connected` to have a type-erased "extra" value,
230// while that type still knows its inner extra type. This allows the correct
231// TypeId to be used when inserting into `res.extensions_mut()`.
232#[derive(Clone)]
233struct ExtraEnvelope<T>(T);
234
235impl<T> ExtraInner for ExtraEnvelope<T>
236where
237    T: Clone + Send + Sync + 'static,
238{
239    fn clone_box(&self) -> Box<dyn ExtraInner> {
240        Box::new(self.clone())
241    }
242
243    fn set(&self, res: &mut Extensions) {
244        res.insert(self.0.clone());
245    }
246}
247
248struct ExtraChain<T>(Box<dyn ExtraInner>, T);
249
250impl<T: Clone> Clone for ExtraChain<T> {
251    fn clone(&self) -> Self {
252        ExtraChain(self.0.clone_box(), self.1.clone())
253    }
254}
255
256impl<T> ExtraInner for ExtraChain<T>
257where
258    T: Clone + Send + Sync + 'static,
259{
260    fn clone_box(&self) -> Box<dyn ExtraInner> {
261        Box::new(self.clone())
262    }
263
264    fn set(&self, res: &mut Extensions) {
265        self.0.set(res);
266        res.insert(self.1.clone());
267    }
268}
269
270#[cfg(any(feature = "http1", feature = "http2"))]
271pub(super) mod sealed {
272    use std::error::Error as StdError;
273
274    use ::http::Uri;
275    use tokio::io::{AsyncRead, AsyncWrite};
276
277    use super::Connection;
278    use crate::common::{Future, Unpin};
279
280    /// Connect to a destination, returning an IO transport.
281    ///
282    /// A connector receives a [`Uri`](::http::Uri) and returns a `Future` of the
283    /// ready connection.
284    ///
285    /// # Trait Alias
286    ///
287    /// This is really just an *alias* for the `tower::Service` trait, with
288    /// additional bounds set for convenience *inside* hyper. You don't actually
289    /// implement this trait, but `tower::Service<Uri>` instead.
290    // The `Sized` bound is to prevent creating `dyn Connect`, since they cannot
291    // fit the `Connect` bounds because of the blanket impl for `Service`.
292    pub trait Connect: Sealed + Sized {
293        #[doc(hidden)]
294        type _Svc: ConnectSvc;
295        #[doc(hidden)]
296        fn connect(self, internal_only: Internal, dst: Uri) -> <Self::_Svc as ConnectSvc>::Future;
297    }
298
299    pub trait ConnectSvc {
300        type Connection: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static;
301        type Error: Into<Box<dyn StdError + Send + Sync>>;
302        type Future: Future<Output = Result<Self::Connection, Self::Error>> + Unpin + Send + 'static;
303
304        fn connect(self, internal_only: Internal, dst: Uri) -> Self::Future;
305    }
306
307    impl<S, T> Connect for S
308    where
309        S: tower_service::Service<Uri, Response = T> + Send + 'static,
310        S::Error: Into<Box<dyn StdError + Send + Sync>>,
311        S::Future: Unpin + Send,
312        T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
313    {
314        type _Svc = S;
315
316        fn connect(self, _: Internal, dst: Uri) -> crate::service::Oneshot<S, Uri> {
317            crate::service::oneshot(self, dst)
318        }
319    }
320
321    impl<S, T> ConnectSvc for S
322    where
323        S: tower_service::Service<Uri, Response = T> + Send + 'static,
324        S::Error: Into<Box<dyn StdError + Send + Sync>>,
325        S::Future: Unpin + Send,
326        T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
327    {
328        type Connection = T;
329        type Error = S::Error;
330        type Future = crate::service::Oneshot<S, Uri>;
331
332        fn connect(self, _: Internal, dst: Uri) -> Self::Future {
333            crate::service::oneshot(self, dst)
334        }
335    }
336
337    impl<S, T> Sealed for S
338    where
339        S: tower_service::Service<Uri, Response = T> + Send,
340        S::Error: Into<Box<dyn StdError + Send + Sync>>,
341        S::Future: Unpin + Send,
342        T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
343    {
344    }
345
346    pub trait Sealed {}
347    #[allow(missing_debug_implementations)]
348    pub struct Internal;
349}
350
351#[cfg(test)]
352mod tests {
353    use super::Connected;
354
355    #[derive(Clone, Debug, PartialEq)]
356    struct Ex1(usize);
357
358    #[derive(Clone, Debug, PartialEq)]
359    struct Ex2(&'static str);
360
361    #[derive(Clone, Debug, PartialEq)]
362    struct Ex3(&'static str);
363
364    #[test]
365    fn test_connected_extra() {
366        let c1 = Connected::new().extra(Ex1(41));
367
368        let mut ex = ::http::Extensions::new();
369
370        assert_eq!(ex.get::<Ex1>(), None);
371
372        c1.extra.as_ref().expect("c1 extra").set(&mut ex);
373
374        assert_eq!(ex.get::<Ex1>(), Some(&Ex1(41)));
375    }
376
377    #[test]
378    fn test_connected_extra_chain() {
379        // If a user composes connectors and at each stage, there's "extra"
380        // info to attach, it shouldn't override the previous extras.
381
382        let c1 = Connected::new()
383            .extra(Ex1(45))
384            .extra(Ex2("zoom"))
385            .extra(Ex3("pew pew"));
386
387        let mut ex1 = ::http::Extensions::new();
388
389        assert_eq!(ex1.get::<Ex1>(), None);
390        assert_eq!(ex1.get::<Ex2>(), None);
391        assert_eq!(ex1.get::<Ex3>(), None);
392
393        c1.extra.as_ref().expect("c1 extra").set(&mut ex1);
394
395        assert_eq!(ex1.get::<Ex1>(), Some(&Ex1(45)));
396        assert_eq!(ex1.get::<Ex2>(), Some(&Ex2("zoom")));
397        assert_eq!(ex1.get::<Ex3>(), Some(&Ex3("pew pew")));
398
399        // Just like extensions, inserting the same type overrides previous type.
400        let c2 = Connected::new()
401            .extra(Ex1(33))
402            .extra(Ex2("hiccup"))
403            .extra(Ex1(99));
404
405        let mut ex2 = ::http::Extensions::new();
406
407        c2.extra.as_ref().expect("c2 extra").set(&mut ex2);
408
409        assert_eq!(ex2.get::<Ex1>(), Some(&Ex1(99)));
410        assert_eq!(ex2.get::<Ex2>(), Some(&Ex2("hiccup")));
411    }
412}