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
This commit is contained in:
Sean McArthur
2018-03-14 14:12:36 -07:00
parent fbc449e49c
commit 8c52c2dfd3
5 changed files with 277 additions and 110 deletions

View File

@@ -638,9 +638,8 @@ mod dispatch_impl {
use tokio_core::net::TcpStream;
use tokio_io::{AsyncRead, AsyncWrite};
use hyper::client::HttpConnector;
use hyper::server::Service;
use hyper::{Client, Uri};
use hyper::client::connect::{Connect, Connected, Destination, HttpConnector};
use hyper::Client;
use hyper;
@@ -1264,11 +1263,51 @@ mod dispatch_impl {
assert_eq!(connects.load(Ordering::Relaxed), 2);
}
#[test]
fn connect_proxy_sends_absolute_uri() {
let _ = pretty_env_logger::try_init();
let server = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = server.local_addr().unwrap();
let mut core = Core::new().unwrap();
let handle = core.handle();
let connector = DebugConnector::new(&handle)
.proxy();
let client = Client::configure()
.connector(connector)
.build(&handle);
let (tx1, rx1) = oneshot::channel();
thread::spawn(move || {
let mut sock = server.accept().unwrap().0;
//drop(server);
sock.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
sock.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
let mut buf = [0; 4096];
let n = sock.read(&mut buf).expect("read 1");
let expected = format!("GET http://{addr}/foo/bar HTTP/1.1\r\nhost: {addr}\r\n\r\n", addr=addr);
assert_eq!(s(&buf[..n]), expected);
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").expect("write 1");
let _ = tx1.send(());
});
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let req = Request::builder()
.uri(&*format!("http://{}/foo/bar", addr))
.body(Body::empty())
.unwrap();
let res = client.request(req);
core.run(res.join(rx).map(|r| r.0)).unwrap();
}
struct DebugConnector {
http: HttpConnector,
closes: mpsc::Sender<()>,
connects: Arc<AtomicUsize>,
is_proxy: bool,
}
impl DebugConnector {
@@ -1283,21 +1322,27 @@ mod dispatch_impl {
http: http,
closes: closes,
connects: Arc::new(AtomicUsize::new(0)),
is_proxy: false,
}
}
fn proxy(mut self) -> Self {
self.is_proxy = true;
self
}
}
impl Service for DebugConnector {
type Request = Uri;
type Response = DebugStream;
impl Connect for DebugConnector {
type Transport = DebugStream;
type Error = io::Error;
type Future = Box<Future<Item = DebugStream, Error = io::Error>>;
type Future = Box<Future<Item = (DebugStream, Connected), Error = io::Error>>;
fn call(&self, uri: Uri) -> Self::Future {
fn connect(&self, dst: Destination) -> Self::Future {
self.connects.fetch_add(1, Ordering::SeqCst);
let closes = self.closes.clone();
Box::new(self.http.call(uri).map(move |s| {
DebugStream(s, closes)
let is_proxy = self.is_proxy;
Box::new(self.http.connect(dst).map(move |(s, c)| {
(DebugStream(s, closes), c.proxy(is_proxy))
}))
}
}