Skip to content

Commit 8c52c2d

Browse files
committed
feat(client): redesign the Connect trait
The original `Connect` trait had some limitations: - There was no way to provide more details to the connector about how to connect, other than the `Uri`. - There was no way for the connector to return any extra information about the connected transport. - The `Error` was forced to be an `std::io::Error`. - The transport and future had `'static` requirements. As hyper gains HTTP/2 support, some of these things needed to be changed. We want to allow the user to configure whether they hope to us ALPN to start an HTTP/2 connection, and the connector needs to be able to return back to hyper if it did so. The new `Connect` trait is meant to solve this. - The `connect` method now receives a `Destination` type, instead of a `Uri`. This allows us to include additional data about how to connect. - The `Future` returned from `connect` now must be a tuple of the transport, and a `Connected` metadata value. The `Connected` includes possibly extra data about what happened when connecting. BREAKING CHANGE: Custom connectors should now implement `Connect` directly, instead of `Service`. Calls to `connect` no longer take `Uri`s, but `Destination`. There are `scheme`, `host`, and `port` methods to query relevant information. The returned future must be a tuple of the transport and `Connected`. If no relevant extra information is needed, simply return `Connected::new()`. Closes #1428
1 parent fbc449e commit 8c52c2d

File tree

5 files changed

+276
-109
lines changed

5 files changed

+276
-109
lines changed

src/client/connect.rs

+156-53
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
//! The `Connect` trait, and supporting types.
2+
//!
3+
//! This module contains:
4+
//!
5+
//! - A default [`HttpConnector`](HttpConnector) that does DNS resolution and
6+
//! establishes connections over TCP.
7+
//! - The [`Connect`](Connect) trait and related types to build custom connectors.
18
use std::error::Error as StdError;
29
use std::fmt;
310
use std::io;
@@ -14,38 +21,121 @@ use http::uri::Scheme;
1421
use tokio_io::{AsyncRead, AsyncWrite};
1522
use tokio::reactor::Handle;
1623
use tokio::net::{TcpStream, TcpStreamNew};
17-
use tokio_service::Service;
1824

1925
use super::dns;
26+
use self::http_connector::HttpConnectorBlockingTask;
2027

21-
/// A connector creates an Io to a remote address..
28+
/// Connect to a destination, returning an IO transport.
2229
///
23-
/// This trait is not implemented directly, and only exists to make
24-
/// the intent clearer. A connector should implement `Service` with
25-
/// `Request=Uri` and `Response: Io` instead.
26-
pub trait Connect: Service<Request=Uri, Error=io::Error> + 'static {
27-
/// The connected Io Stream.
28-
type Output: AsyncRead + AsyncWrite + 'static;
29-
/// A Future that will resolve to the connected Stream.
30-
type Future: Future<Item=Self::Output, Error=io::Error> + 'static;
31-
/// Connect to a remote address.
32-
fn connect(&self, Uri) -> <Self as Connect>::Future;
30+
/// A connector receives a [`Destination`](Destination) describing how a
31+
/// connection should be estabilished, and returns a `Future` of the
32+
/// ready connection.
33+
pub trait Connect {
34+
/// The connected IO Stream.
35+
type Transport: AsyncRead + AsyncWrite + 'static;
36+
/// An error occured when trying to connect.
37+
type Error;
38+
/// A Future that will resolve to the connected Transport.
39+
type Future: Future<Item=(Self::Transport, Connected), Error=Self::Error>;
40+
/// Connect to a destination.
41+
fn connect(&self, dst: Destination) -> Self::Future;
3342
}
3443

35-
impl<T> Connect for T
36-
where T: Service<Request=Uri, Error=io::Error> + 'static,
37-
T::Response: AsyncRead + AsyncWrite,
38-
T::Future: Future<Error=io::Error>,
39-
{
40-
type Output = T::Response;
41-
type Future = T::Future;
44+
/// A set of properties to describe where and how to try to connect.
45+
#[derive(Debug)]
46+
pub struct Destination {
47+
//pub(super) alpn: Alpn,
48+
pub(super) uri: Uri,
49+
}
50+
51+
/// Extra information about the connected transport.
52+
///
53+
/// This can be used to inform recipients about things like if ALPN
54+
/// was used, or if connected to an HTTP proxy.
55+
#[derive(Debug)]
56+
pub struct Connected {
57+
//alpn: Alpn,
58+
pub(super) is_proxied: bool,
59+
}
60+
61+
/*TODO: when HTTP1 Upgrades to H2 are added, this will be needed
62+
#[derive(Debug)]
63+
pub(super) enum Alpn {
64+
Http1,
65+
//H2,
66+
//Http1OrH2
67+
}
68+
*/
69+
70+
impl Destination {
71+
/// Get the protocol scheme.
72+
#[inline]
73+
pub fn scheme(&self) -> &str {
74+
self.uri
75+
.scheme_part()
76+
.expect("destination uri has scheme")
77+
.as_str()
78+
}
79+
80+
/// Get the hostname.
81+
#[inline]
82+
pub fn host(&self) -> &str {
83+
self.uri
84+
.host()
85+
.expect("destination uri has host")
86+
}
87+
88+
/// Get the port, if specified.
89+
#[inline]
90+
pub fn port(&self) -> Option<u16> {
91+
self.uri.port()
92+
}
93+
94+
/*
95+
/// Returns whether this connection must negotiate HTTP/2 via ALPN.
96+
pub fn must_h2(&self) -> bool {
97+
match self.alpn {
98+
Alpn::Http1 => false,
99+
Alpn::H2 => true,
100+
}
101+
}
102+
*/
103+
}
42104

43-
fn connect(&self, url: Uri) -> <Self as Connect>::Future {
44-
self.call(url)
105+
impl Connected {
106+
/// Create new `Connected` type with empty metadata.
107+
pub fn new() -> Connected {
108+
Connected {
109+
//alpn: Alpn::Http1,
110+
is_proxied: false,
111+
}
45112
}
113+
114+
/// Set whether the connected transport is to an HTTP proxy.
115+
///
116+
/// This setting will affect if HTTP/1 requests written on the transport
117+
/// will have the request-target in absolute-form or origin-form (such as
118+
/// `GET http://hyper.rs/guide HTTP/1.1` or `GET /guide HTTP/1.1`).
119+
///
120+
/// Default is `false`.
121+
pub fn proxy(mut self, is_proxied: bool) -> Connected {
122+
self.is_proxied = is_proxied;
123+
self
124+
}
125+
126+
/*
127+
/// Set that the connected transport negotiated HTTP/2 as it's
128+
/// next protocol.
129+
pub fn h2(mut self) -> Connected {
130+
self.alpn = Alpn::H2;
131+
self
132+
}
133+
*/
46134
}
47135

48136
/// A connector for the `http` scheme.
137+
///
138+
/// Performs DNS resolution in a thread pool, and then connects over TCP.
49139
#[derive(Clone)]
50140
pub struct HttpConnector {
51141
executor: HttpConnectExecutor,
@@ -109,30 +199,29 @@ impl fmt::Debug for HttpConnector {
109199
}
110200
}
111201

112-
impl Service for HttpConnector {
113-
type Request = Uri;
114-
type Response = TcpStream;
202+
impl Connect for HttpConnector {
203+
type Transport = TcpStream;
115204
type Error = io::Error;
116205
type Future = HttpConnecting;
117206

118-
fn call(&self, uri: Uri) -> Self::Future {
119-
trace!("Http::connect({:?})", uri);
207+
fn connect(&self, dst: Destination) -> Self::Future {
208+
trace!("Http::connect({:?})", dst.uri);
120209

121210
if self.enforce_http {
122-
if uri.scheme_part() != Some(&Scheme::HTTP) {
211+
if dst.uri.scheme_part() != Some(&Scheme::HTTP) {
123212
return invalid_url(InvalidUrl::NotHttp, &self.handle);
124213
}
125-
} else if uri.scheme_part().is_none() {
214+
} else if dst.uri.scheme_part().is_none() {
126215
return invalid_url(InvalidUrl::MissingScheme, &self.handle);
127216
}
128217

129-
let host = match uri.host() {
218+
let host = match dst.uri.host() {
130219
Some(s) => s,
131220
None => return invalid_url(InvalidUrl::MissingAuthority, &self.handle),
132221
};
133-
let port = match uri.port() {
222+
let port = match dst.uri.port() {
134223
Some(port) => port,
135-
None => if uri.scheme_part() == Some(&Scheme::HTTPS) { 443 } else { 80 },
224+
None => if dst.uri.scheme_part() == Some(&Scheme::HTTPS) { 443 } else { 80 },
136225
};
137226

138227
HttpConnecting {
@@ -191,7 +280,7 @@ enum State {
191280
}
192281

193282
impl Future for HttpConnecting {
194-
type Item = TcpStream;
283+
type Item = (TcpStream, Connected);
195284
type Error = io::Error;
196285

197286
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -230,7 +319,7 @@ impl Future for HttpConnecting {
230319
sock.set_keepalive(Some(dur))?;
231320
}
232321

233-
return Ok(Async::Ready(sock));
322+
return Ok(Async::Ready((sock, Connected::new())));
234323
},
235324
State::Error(ref mut e) => return Err(e.take().expect("polled more than once")),
236325
}
@@ -279,23 +368,27 @@ impl ConnectingTcp {
279368
}
280369
}
281370

282-
/// Blocking task to be executed on a thread pool.
283-
pub struct HttpConnectorBlockingTask {
284-
work: oneshot::Execute<dns::Work>
285-
}
371+
// Make this Future unnameable outside of this crate.
372+
mod http_connector {
373+
use super::*;
374+
// Blocking task to be executed on a thread pool.
375+
pub struct HttpConnectorBlockingTask {
376+
pub(super) work: oneshot::Execute<dns::Work>
377+
}
286378

287-
impl fmt::Debug for HttpConnectorBlockingTask {
288-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
289-
f.pad("HttpConnectorBlockingTask")
379+
impl fmt::Debug for HttpConnectorBlockingTask {
380+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
381+
f.pad("HttpConnectorBlockingTask")
382+
}
290383
}
291-
}
292384

293-
impl Future for HttpConnectorBlockingTask {
294-
type Item = ();
295-
type Error = ();
385+
impl Future for HttpConnectorBlockingTask {
386+
type Item = ();
387+
type Error = ();
296388

297-
fn poll(&mut self) -> Poll<(), ()> {
298-
self.work.poll()
389+
fn poll(&mut self) -> Poll<(), ()> {
390+
self.work.poll()
391+
}
299392
}
300393
}
301394

@@ -311,35 +404,45 @@ impl Executor<oneshot::Execute<dns::Work>> for HttpConnectExecutor {
311404

312405
#[cfg(test)]
313406
mod tests {
407+
#![allow(deprecated)]
314408
use std::io;
315409
use tokio::reactor::Core;
316-
use super::{Connect, HttpConnector};
410+
use super::{Connect, Destination, HttpConnector};
317411

318412
#[test]
319413
fn test_errors_missing_authority() {
320414
let mut core = Core::new().unwrap();
321-
let url = "/foo/bar?baz".parse().unwrap();
415+
let uri = "/foo/bar?baz".parse().unwrap();
416+
let dst = Destination {
417+
uri,
418+
};
322419
let connector = HttpConnector::new(1, &core.handle());
323420

324-
assert_eq!(core.run(connector.connect(url)).unwrap_err().kind(), io::ErrorKind::InvalidInput);
421+
assert_eq!(core.run(connector.connect(dst)).unwrap_err().kind(), io::ErrorKind::InvalidInput);
325422
}
326423

327424
#[test]
328425
fn test_errors_enforce_http() {
329426
let mut core = Core::new().unwrap();
330-
let url = "https://example.domain/foo/bar?baz".parse().unwrap();
427+
let uri = "https://example.domain/foo/bar?baz".parse().unwrap();
428+
let dst = Destination {
429+
uri,
430+
};
331431
let connector = HttpConnector::new(1, &core.handle());
332432

333-
assert_eq!(core.run(connector.connect(url)).unwrap_err().kind(), io::ErrorKind::InvalidInput);
433+
assert_eq!(core.run(connector.connect(dst)).unwrap_err().kind(), io::ErrorKind::InvalidInput);
334434
}
335435

336436

337437
#[test]
338438
fn test_errors_missing_scheme() {
339439
let mut core = Core::new().unwrap();
340-
let url = "example.domain".parse().unwrap();
440+
let uri = "example.domain".parse().unwrap();
441+
let dst = Destination {
442+
uri,
443+
};
341444
let connector = HttpConnector::new(1, &core.handle());
342445

343-
assert_eq!(core.run(connector.connect(url)).unwrap_err().kind(), io::ErrorKind::InvalidInput);
446+
assert_eq!(core.run(connector.connect(dst)).unwrap_err().kind(), io::ErrorKind::InvalidInput);
344447
}
345448
}

0 commit comments

Comments
 (0)