Skip to content

Commit fe60c7b

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 `Connect2` 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. The `Connect` trait is deprecated, with the hopes of `Connect2` taking it's place in the next breaking release. For backwards compatibility, any type that implements `Connect` now will automatically implement `Connect2`, ignoring any of the extra data from `Destination` and `Connected`.
1 parent 44af273 commit fe60c7b

File tree

3 files changed

+200
-52
lines changed

3 files changed

+200
-52
lines changed

src/client/compat.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
//! Wrappers to build compatibility with the `http` crate.
22
3+
use std::io;
4+
35
use futures::{Future, Poll, Stream};
46
use http;
57
use tokio_service::Service;
68

7-
use client::{Connect, Client, FutureResponse};
9+
use client::{Connect2, Client, FutureResponse};
810
use error::Error;
911
use proto::Body;
1012

@@ -19,7 +21,9 @@ pub(super) fn client<C, B>(client: Client<C, B>) -> CompatClient<C, B> {
1921
}
2022

2123
impl<C, B> Service for CompatClient<C, B>
22-
where C: Connect,
24+
where C: Connect2<Error=io::Error>,
25+
C::Transport: 'static,
26+
C::Future: 'static,
2327
B: Stream<Error=Error> + 'static,
2428
B::Item: AsRef<[u8]>,
2529
{

src/client/connect.rs

Lines changed: 174 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
//! Contains the `Connect2` trait, and supporting types.
12
use std::error::Error as StdError;
23
use std::fmt;
34
use std::io;
45
use std::mem;
56
use std::sync::Arc;
6-
//use std::net::SocketAddr;
77

88
use futures::{Future, Poll, Async};
99
use futures::future::{Executor, ExecuteError};
@@ -16,31 +16,79 @@ use tokio_service::Service;
1616
use Uri;
1717

1818
use super::dns;
19+
use self::http_connector::HttpConnectorBlockingTask;
20+
21+
/// Connect to a destination, returning an IO transport.
22+
pub trait Connect2 {
23+
/// The connected IO Stream.
24+
type Transport: AsyncRead + AsyncWrite;
25+
/// An error occured when trying to connect.
26+
type Error;
27+
/// A Future that will resolve to the connected Transport.
28+
type Future: Future<Item=(Self::Transport, Connected), Error=Self::Error>;
29+
/// Connect to a destination.
30+
fn connect(&self, dst: Destination) -> Self::Future;
31+
}
1932

20-
/// A connector creates an Io to a remote address..
21-
///
22-
/// This trait is not implemented directly, and only exists to make
23-
/// the intent clearer. A connector should implement `Service` with
24-
/// `Request=Uri` and `Response: Io` instead.
25-
pub trait Connect: Service<Request=Uri, Error=io::Error> + 'static {
26-
/// The connected Io Stream.
27-
type Output: AsyncRead + AsyncWrite + 'static;
28-
/// A Future that will resolve to the connected Stream.
29-
type Future: Future<Item=Self::Output, Error=io::Error> + 'static;
30-
/// Connect to a remote address.
31-
fn connect(&self, Uri) -> <Self as Connect>::Future;
33+
/// A set of properties to describe where and how to try to connect.
34+
#[derive(Debug)]
35+
pub struct Destination {
36+
pub(super) alpn: Alpn,
37+
pub(super) uri: Uri,
3238
}
3339

34-
impl<T> Connect for T
35-
where T: Service<Request=Uri, Error=io::Error> + 'static,
36-
T::Response: AsyncRead + AsyncWrite,
37-
T::Future: Future<Error=io::Error>,
38-
{
39-
type Output = T::Response;
40-
type Future = T::Future;
40+
/// Extra information about the connected transport.
41+
#[derive(Debug)]
42+
pub struct Connected {
43+
alpn: Alpn,
44+
is_proxy: bool,
45+
}
4146

42-
fn connect(&self, url: Uri) -> <Self as Connect>::Future {
43-
self.call(url)
47+
#[derive(Debug)]
48+
pub(super) enum Alpn {
49+
Http1,
50+
H2,
51+
}
52+
53+
impl Destination {
54+
/// Get a reference to the requested `Uri`.
55+
pub fn uri(&self) -> &Uri {
56+
&self.uri
57+
}
58+
59+
/// Returns whether this connection must negotiate HTTP/2 via ALPN.
60+
pub fn h2(&self) -> bool {
61+
match self.alpn {
62+
Alpn::Http1 => false,
63+
Alpn::H2 => true,
64+
}
65+
}
66+
}
67+
68+
impl Connected {
69+
/// Create new `Connected` type with empty metadata.
70+
pub fn new() -> Connected {
71+
Connected {
72+
alpn: Alpn::Http1,
73+
is_proxy: false,
74+
}
75+
}
76+
77+
/// Set that the connected transport is to an HTTP proxy.
78+
///
79+
/// This setting will affect if HTTP/1 requests written on the transport
80+
/// will have the request-target in absolute-form or origin-form (such as
81+
/// `GET http://hyper.rs/guide HTTP/1.1` or `GET /guide HTTP/1.1`).
82+
pub fn proxy(mut self) -> Connected {
83+
self.is_proxy = true;
84+
self
85+
}
86+
87+
/// Set that the connected transport negotiated HTTP/2 as it's
88+
/// next protocol.
89+
pub fn h2(mut self) -> Connected {
90+
self.alpn = Alpn::H2;
91+
self
4492
}
4593
}
4694

@@ -96,6 +144,8 @@ impl fmt::Debug for HttpConnector {
96144
}
97145
}
98146

147+
// deprecated, will be gone in 0.12
148+
#[doc(hidden)]
99149
impl Service for HttpConnector {
100150
type Request = Uri;
101151
type Response = TcpStream;
@@ -258,23 +308,27 @@ impl ConnectingTcp {
258308
}
259309
}
260310

261-
/// Blocking task to be executed on a thread pool.
262-
pub struct HttpConnectorBlockingTask {
263-
work: oneshot::Execute<dns::Work>
264-
}
311+
// Make this Future unnameable outside of this crate.
312+
mod http_connector {
313+
use super::*;
314+
// Blocking task to be executed on a thread pool.
315+
pub struct HttpConnectorBlockingTask {
316+
pub(super) work: oneshot::Execute<dns::Work>
317+
}
265318

266-
impl fmt::Debug for HttpConnectorBlockingTask {
267-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
268-
f.pad("HttpConnectorBlockingTask")
319+
impl fmt::Debug for HttpConnectorBlockingTask {
320+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
321+
f.pad("HttpConnectorBlockingTask")
322+
}
269323
}
270-
}
271324

272-
impl Future for HttpConnectorBlockingTask {
273-
type Item = ();
274-
type Error = ();
325+
impl Future for HttpConnectorBlockingTask {
326+
type Item = ();
327+
type Error = ();
275328

276-
fn poll(&mut self) -> Poll<(), ()> {
277-
self.work.poll()
329+
fn poll(&mut self) -> Poll<(), ()> {
330+
self.work.poll()
331+
}
278332
}
279333
}
280334

@@ -288,20 +342,97 @@ impl Executor<oneshot::Execute<dns::Work>> for HttpConnectExecutor {
288342
}
289343
}
290344

291-
/*
292-
impl<S: SslClient> HttpsConnector<S> {
293-
/// Create a new connector using the provided SSL implementation.
294-
pub fn new(s: S) -> HttpsConnector<S> {
295-
HttpsConnector {
296-
http: HttpConnector::default(),
297-
ssl: s,
345+
#[doc(hidden)]
346+
#[deprecated(since="0.11.16", note="Use the Connect2 trait, which will become Connect in 0.12")]
347+
pub trait Connect: Service<Request=Uri, Error=io::Error> + 'static {
348+
/// The connected Io Stream.
349+
type Output: AsyncRead + AsyncWrite + 'static;
350+
/// A Future that will resolve to the connected Stream.
351+
type Future: Future<Item=Self::Output, Error=io::Error> + 'static;
352+
/// Connect to a remote address.
353+
fn connect(&self, Uri) -> <Self as Connect>::Future;
354+
}
355+
356+
#[doc(hidden)]
357+
#[allow(deprecated)]
358+
impl<T> Connect for T
359+
where T: Service<Request=Uri, Error=io::Error> + 'static,
360+
T::Response: AsyncRead + AsyncWrite,
361+
T::Future: Future<Error=io::Error>,
362+
{
363+
type Output = T::Response;
364+
type Future = T::Future;
365+
366+
fn connect(&self, url: Uri) -> <Self as Connect>::Future {
367+
self.call(url)
368+
}
369+
}
370+
371+
#[doc(hidden)]
372+
#[allow(deprecated)]
373+
impl<T> Connect2 for T
374+
where
375+
T: Connect,
376+
{
377+
type Transport = <T as Connect>::Output;
378+
type Error = io::Error;
379+
type Future = ConnectToConnect2Future<<T as Connect>::Future>;
380+
381+
fn connect(&self, dst: Destination) -> <Self as Connect2>::Future {
382+
ConnectToConnect2Future {
383+
inner: <Self as Connect>::connect(self, dst.uri),
298384
}
299385
}
300386
}
301-
*/
387+
388+
#[doc(hidden)]
389+
#[deprecated(since="0.11.16")]
390+
#[allow(missing_debug_implementations)]
391+
pub struct ConnectToConnect2Future<F> {
392+
inner: F,
393+
}
394+
395+
#[allow(deprecated)]
396+
impl<F> Future for ConnectToConnect2Future<F>
397+
where
398+
F: Future,
399+
{
400+
type Item = (F::Item, Connected);
401+
type Error = F::Error;
402+
403+
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
404+
self.inner.poll()
405+
.map(|async| async.map(|t| (t, Connected::new())))
406+
}
407+
}
408+
409+
// even though deprecated, we need to make sure the HttpConnector still
410+
// implements Connect (and Service apparently...)
411+
412+
#[allow(deprecated)]
413+
fn _assert_http_connector() {
414+
fn assert_connect<T>()
415+
where
416+
T: Connect2<
417+
Transport=TcpStream,
418+
Error=io::Error,
419+
Future=ConnectToConnect2Future<HttpConnecting>
420+
>,
421+
T: Connect<Output=TcpStream, Future=HttpConnecting>,
422+
T: Service<
423+
Request=Uri,
424+
Response=TcpStream,
425+
Future=HttpConnecting,
426+
Error=io::Error
427+
>,
428+
{}
429+
430+
assert_connect::<HttpConnector>();
431+
}
302432

303433
#[cfg(test)]
304434
mod tests {
435+
#![allow(deprecated)]
305436
use std::io;
306437
use tokio::reactor::Core;
307438
use super::{Connect, HttpConnector};

src/client/mod.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ use version::HttpVersion;
2424

2525
pub use proto::response::Response;
2626
pub use proto::request::Request;
27-
pub use self::connect::{HttpConnector, Connect};
27+
pub use self::connect::{Connect2, HttpConnector};
28+
#[allow(deprecated)]
29+
pub use self::connect::Connect;
2830

2931
use self::background::{bg, Background};
32+
use self::connect::Destination;
3033

31-
mod connect;
34+
pub mod connect;
3235
mod dns;
3336
mod pool;
3437
#[cfg(feature = "compat")]
@@ -99,7 +102,9 @@ impl<C, B> Client<C, B> {
99102
}
100103

101104
impl<C, B> Client<C, B>
102-
where C: Connect,
105+
where C: Connect2<Error=io::Error>,
106+
C::Transport: 'static,
107+
C::Future: 'static,
103108
B: Stream<Error=::Error> + 'static,
104109
B::Item: AsRef<[u8]>,
105110
{
@@ -149,7 +154,9 @@ impl Future for FutureResponse {
149154
}
150155

151156
impl<C, B> Service for Client<C, B>
152-
where C: Connect,
157+
where C: Connect2<Error=io::Error>,
158+
C::Transport: 'static,
159+
C::Future: 'static,
153160
B: Stream<Error=::Error> + 'static,
154161
B::Item: AsRef<[u8]>,
155162
{
@@ -195,8 +202,12 @@ where C: Connect,
195202
let executor = self.executor.clone();
196203
let pool = self.pool.clone();
197204
let pool_key = Rc::new(domain.to_string());
198-
self.connector.connect(url)
199-
.and_then(move |io| {
205+
let dst = Destination {
206+
uri: url,
207+
alpn: self::connect::Alpn::Http1,
208+
};
209+
self.connector.connect(dst)
210+
.and_then(move |(io, _connected)| {
200211
let (tx, rx) = mpsc::channel(0);
201212
let tx = HyperClient {
202213
tx: RefCell::new(tx),
@@ -409,7 +420,9 @@ impl<C, B> Config<C, B> {
409420
}
410421

411422
impl<C, B> Config<C, B>
412-
where C: Connect,
423+
where C: Connect2<Error=io::Error>,
424+
C::Transport: 'static,
425+
C::Future: 'static,
413426
B: Stream<Error=::Error>,
414427
B::Item: AsRef<[u8]>,
415428
{

0 commit comments

Comments
 (0)