feat(client): update construction of Clients

- `Client::new()` no longer needs a `Handle`, and instead makes use of
  tokio's implicit default.
- Changed `Client::configure()` to `Client::builder()`.
- `Builder` is a by-ref builder, since all configuration is now
  cloneable pieces.

BREAKING CHANGE: `Client:new(&handle)` and `Client::configure()` are now
  `Client::new()` and `Client::builder()`.
This commit is contained in:
Sean McArthur
2018-04-10 17:30:10 -07:00
parent dfdca25c00
commit fe1578acf6
7 changed files with 156 additions and 266 deletions

View File

@@ -135,26 +135,30 @@ impl Connected {
*/
}
fn connect(addr: &SocketAddr, handle: &Handle) -> io::Result<ConnectFuture> {
let builder = match addr {
&SocketAddr::V4(_) => TcpBuilder::new_v4()?,
&SocketAddr::V6(_) => TcpBuilder::new_v6()?,
};
if cfg!(windows) {
// Windows requires a socket be bound before calling connect
let any: SocketAddr = match addr {
&SocketAddr::V4(_) => {
([0, 0, 0, 0], 0).into()
},
&SocketAddr::V6(_) => {
([0, 0, 0, 0, 0, 0, 0, 0], 0).into()
}
fn connect(addr: &SocketAddr, handle: &Option<Handle>) -> io::Result<ConnectFuture> {
if let Some(ref handle) = *handle {
let builder = match addr {
&SocketAddr::V4(_) => TcpBuilder::new_v4()?,
&SocketAddr::V6(_) => TcpBuilder::new_v6()?,
};
builder.bind(any)?;
}
Ok(TcpStream::connect_std(builder.to_tcp_stream()?, addr, handle))
if cfg!(windows) {
// Windows requires a socket be bound before calling connect
let any: SocketAddr = match addr {
&SocketAddr::V4(_) => {
([0, 0, 0, 0], 0).into()
},
&SocketAddr::V6(_) => {
([0, 0, 0, 0, 0, 0, 0, 0], 0).into()
}
};
builder.bind(any)?;
}
Ok(TcpStream::connect_std(builder.to_tcp_stream()?, addr, handle))
} else {
Ok(TcpStream::connect(addr))
}
}
/// A connector for the `http` scheme.
@@ -164,7 +168,7 @@ fn connect(addr: &SocketAddr, handle: &Handle) -> io::Result<ConnectFuture> {
pub struct HttpConnector {
executor: HttpConnectExecutor,
enforce_http: bool,
handle: Handle,
handle: Option<Handle>,
keep_alive_timeout: Option<Duration>,
}
@@ -173,7 +177,16 @@ impl HttpConnector {
///
/// Takes number of DNS worker threads.
#[inline]
pub fn new(threads: usize, handle: &Handle) -> HttpConnector {
pub fn new(threads: usize) -> HttpConnector {
HttpConnector::new_with_handle_opt(threads, None)
}
/// Construct a new HttpConnector with a specific Tokio handle.
pub fn new_with_handle(threads: usize, handle: Handle) -> HttpConnector {
HttpConnector::new_with_handle_opt(threads, Some(handle))
}
fn new_with_handle_opt(threads: usize, handle: Option<Handle>) -> HttpConnector {
let pool = CpuPoolBuilder::new()
.name_prefix("hyper-dns")
.pool_size(threads)
@@ -184,14 +197,13 @@ impl HttpConnector {
/// Construct a new HttpConnector.
///
/// Takes an executor to run blocking tasks on.
#[inline]
pub fn new_with_executor<E: 'static>(executor: E, handle: &Handle) -> HttpConnector
pub fn new_with_executor<E: 'static>(executor: E, handle: Option<Handle>) -> HttpConnector
where E: Executor<HttpConnectorBlockingTask> + Send + Sync
{
HttpConnector {
executor: HttpConnectExecutor(Arc::new(executor)),
enforce_http: true,
handle: handle.clone(),
handle,
keep_alive_timeout: None,
}
}
@@ -257,7 +269,7 @@ impl Connect for HttpConnector {
}
#[inline]
fn invalid_url(err: InvalidUrl, handle: &Handle) -> HttpConnecting {
fn invalid_url(err: InvalidUrl, handle: &Option<Handle>) -> HttpConnecting {
HttpConnecting {
state: State::Error(Some(io::Error::new(io::ErrorKind::InvalidInput, err))),
handle: handle.clone(),
@@ -292,7 +304,7 @@ impl StdError for InvalidUrl {
#[must_use = "futures do nothing unless polled"]
pub struct HttpConnecting {
state: State,
handle: Handle,
handle: Option<Handle>,
keep_alive_timeout: Option<Duration>,
}
@@ -365,7 +377,7 @@ struct ConnectingTcp {
impl ConnectingTcp {
// not a Future, since passing a &Handle to poll
fn poll(&mut self, handle: &Handle) -> Poll<TcpStream, io::Error> {
fn poll(&mut self, handle: &Option<Handle>) -> Poll<TcpStream, io::Error> {
let mut err = None;
loop {
if let Some(ref mut current) = self.current {
@@ -431,29 +443,26 @@ mod tests {
#![allow(deprecated)]
use std::io;
use futures::Future;
use tokio::runtime::Runtime;
use super::{Connect, Destination, HttpConnector};
#[test]
fn test_errors_missing_authority() {
let runtime = Runtime::new().unwrap();
let uri = "/foo/bar?baz".parse().unwrap();
let dst = Destination {
uri,
};
let connector = HttpConnector::new(1, runtime.handle());
let connector = HttpConnector::new(1);
assert_eq!(connector.connect(dst).wait().unwrap_err().kind(), io::ErrorKind::InvalidInput);
}
#[test]
fn test_errors_enforce_http() {
let runtime = Runtime::new().unwrap();
let uri = "https://example.domain/foo/bar?baz".parse().unwrap();
let dst = Destination {
uri,
};
let connector = HttpConnector::new(1, runtime.handle());
let connector = HttpConnector::new(1);
assert_eq!(connector.connect(dst).wait().unwrap_err().kind(), io::ErrorKind::InvalidInput);
}
@@ -461,12 +470,11 @@ mod tests {
#[test]
fn test_errors_missing_scheme() {
let runtime = Runtime::new().unwrap();
let uri = "example.domain".parse().unwrap();
let dst = Destination {
uri,
};
let connector = HttpConnector::new(1, runtime.handle());
let connector = HttpConnector::new(1);
assert_eq!(connector.connect(dst).wait().unwrap_err().kind(), io::ErrorKind::InvalidInput);
}

View File

@@ -2,7 +2,6 @@
use std::fmt;
use std::io;
use std::marker::PhantomData;
use std::sync::Arc;
use std::time::Duration;
@@ -12,7 +11,6 @@ use futures::sync::oneshot;
use http::{Method, Request, Response, Uri, Version};
use http::header::{Entry, HeaderValue, HOST};
use http::uri::Scheme;
use tokio::reactor::Handle;
use tokio_executor::spawn;
pub use tokio_service::Service;
@@ -21,7 +19,6 @@ use self::pool::Pool;
pub use self::connect::{Connect, HttpConnector};
use self::background::{bg, Background};
use self::connect::Destination;
pub mod conn;
@@ -45,14 +42,14 @@ pub struct Client<C, B = Body> {
impl Client<HttpConnector, Body> {
/// Create a new Client with the default config.
#[inline]
pub fn new(handle: &Handle) -> Client<HttpConnector, Body> {
Config::default().build(handle)
pub fn new() -> Client<HttpConnector, Body> {
Builder::default().build_http()
}
}
impl Default for Client<HttpConnector, Body> {
fn default() -> Client<HttpConnector, Body> {
Client::new(&Handle::current())
Client::new()
}
}
@@ -61,36 +58,18 @@ impl Client<HttpConnector, Body> {
///
/// # Example
///
/// ```no_run
/// # extern crate hyper;
/// # extern crate tokio;
/// ```
/// use hyper::Client;
///
/// # fn main() {
/// # let runtime = tokio::runtime::Runtime::new().unwrap();
/// # let handle = runtime.handle();
/// let client = hyper::Client::configure()
/// let client = Client::builder()
/// .keep_alive(true)
/// .build(&handle);
/// # drop(client);
/// # }
/// .build_http();
/// # let infer: Client<_, hyper::Body> = client;
/// # drop(infer);
/// ```
#[inline]
pub fn configure() -> Config<UseDefaultConnector, Body> {
Config::default()
}
}
impl<C, B> Client<C, B> {
#[inline]
fn configured(config: Config<C, B>, exec: Exec) -> Client<C, B> {
Client {
connector: Arc::new(config.connector),
executor: exec,
h1_writev: config.h1_writev,
pool: Pool::new(config.keep_alive, config.keep_alive_timeout),
retry_canceled_requests: config.retry_canceled_requests,
set_host: config.set_host,
}
pub fn builder() -> Builder {
Builder::default()
}
}
@@ -425,11 +404,11 @@ fn set_relative_uri(uri: &mut Uri, is_proxied: bool) {
*uri = path;
}
/// Configuration for a Client
pub struct Config<C, B> {
_body_type: PhantomData<B>,
/// Builder for a Client
#[derive(Clone)]
pub struct Builder {
//connect_timeout: Duration,
connector: C,
exec: Exec,
keep_alive: bool,
keep_alive_timeout: Option<Duration>,
h1_writev: bool,
@@ -439,15 +418,10 @@ pub struct Config<C, B> {
set_host: bool,
}
/// Phantom type used to signal that `Config` should create a `HttpConnector`.
#[derive(Debug, Clone, Copy)]
pub struct UseDefaultConnector(());
impl Default for Config<UseDefaultConnector, Body> {
fn default() -> Config<UseDefaultConnector, Body> {
Config {
_body_type: PhantomData::<Body>,
connector: UseDefaultConnector(()),
impl Default for Builder {
fn default() -> Self {
Self {
exec: Exec::Default,
keep_alive: true,
keep_alive_timeout: Some(Duration::from_secs(90)),
h1_writev: true,
@@ -458,50 +432,12 @@ impl Default for Config<UseDefaultConnector, Body> {
}
}
impl<C, B> Config<C, B> {
/// Set the body stream to be used by the `Client`.
///
/// # Example
///
/// ```rust
/// # use hyper::client::Config;
/// let cfg = Config::default()
/// .body::<hyper::Body>();
/// # drop(cfg);
#[inline]
pub fn body<BB>(self) -> Config<C, BB> {
Config {
_body_type: PhantomData::<BB>,
connector: self.connector,
keep_alive: self.keep_alive,
keep_alive_timeout: self.keep_alive_timeout,
h1_writev: self.h1_writev,
max_idle: self.max_idle,
retry_canceled_requests: self.retry_canceled_requests,
set_host: self.set_host,
}
}
/// Set the `Connect` type to be used.
#[inline]
pub fn connector<CC>(self, val: CC) -> Config<CC, B> {
Config {
_body_type: self._body_type,
connector: val,
keep_alive: self.keep_alive,
keep_alive_timeout: self.keep_alive_timeout,
h1_writev: self.h1_writev,
max_idle: self.max_idle,
retry_canceled_requests: self.retry_canceled_requests,
set_host: self.set_host,
}
}
impl Builder {
/// Enable or disable keep-alive mechanics.
///
/// Default is enabled.
#[inline]
pub fn keep_alive(mut self, val: bool) -> Config<C, B> {
pub fn keep_alive(&mut self, val: bool) -> &mut Self {
self.keep_alive = val;
self
}
@@ -512,7 +448,7 @@ impl<C, B> Config<C, B> {
///
/// Default is 90 seconds.
#[inline]
pub fn keep_alive_timeout(mut self, val: Option<Duration>) -> Config<C, B> {
pub fn keep_alive_timeout(&mut self, val: Option<Duration>) -> &mut Self {
self.keep_alive_timeout = val;
self
}
@@ -526,7 +462,7 @@ impl<C, B> Config<C, B> {
///
/// Default is `true`.
#[inline]
pub fn http1_writev(mut self, val: bool) -> Config<C, B> {
pub fn http1_writev(&mut self, val: bool) -> &mut Self {
self.h1_writev = val;
self
}
@@ -543,7 +479,7 @@ impl<C, B> Config<C, B> {
///
/// Default is `true`.
#[inline]
pub fn retry_canceled_requests(mut self, val: bool) -> Config<C, B> {
pub fn retry_canceled_requests(&mut self, val: bool) -> &mut Self {
self.retry_canceled_requests = val;
self
}
@@ -555,71 +491,56 @@ impl<C, B> Config<C, B> {
///
/// Default is `true`.
#[inline]
pub fn set_host(mut self, val: bool) -> Config<C, B> {
pub fn set_host(&mut self, val: bool) -> &mut Self {
self.set_host = val;
self
}
}
impl<C, B> Config<C, B>
where C: Connect,
C::Transport: 'static,
C::Future: 'static,
B: Payload + Send,
B::Data: Send,
{
/// Construct the Client with this configuration.
#[inline]
pub fn build(self) -> Client<C, B> {
Client::configured(self, Exec::Default)
}
/// Construct a Client with this configuration and an executor.
///
/// The executor will be used to spawn "background" connection tasks
/// to drive requests and responses.
pub fn executor<E>(self, executor: E) -> Client<C, B>
/// Provide an executor to execute background `Connection` tasks.
pub fn executor<E>(&mut self, exec: E) -> &mut Self
where
E: Executor<Background> + Send + Sync + 'static,
E: Executor<Box<Future<Item=(), Error=()> + Send>> + Send + Sync + 'static,
{
Client::configured(self, Exec::new(executor))
self.exec = Exec::Executor(Arc::new(exec));
self
}
}
impl<B> Config<UseDefaultConnector, B>
where
B: Payload + Send,
B::Data: Send,
{
/// Construct the Client with this configuration.
#[inline]
pub fn build(self, handle: &Handle) -> Client<HttpConnector, B> {
let mut connector = HttpConnector::new(4, handle);
/// Builder a client with this configuration and the default `HttpConnector`.
pub fn build_http<B>(&self) -> Client<HttpConnector, B>
where
B: Payload + Send,
B::Data: Send,
{
let mut connector = HttpConnector::new(4);
if self.keep_alive {
connector.set_keepalive(self.keep_alive_timeout);
}
self.connector(connector).build()
self.build(connector)
}
/// Construct a Client with this configuration and an executor.
///
/// The executor will be used to spawn "background" connection tasks
/// to drive requests and responses.
pub fn build_with_executor<E>(self, handle: &Handle, executor: E) -> Client<HttpConnector, B>
/// Combine the configuration of this builder with a connector to create a `Client`.
pub fn build<C, B>(&self, connector: C) -> Client<C, B>
where
E: Executor<Background> + Send + Sync + 'static,
C: Connect,
C::Transport: 'static,
C::Future: 'static,
B: Payload + Send,
B::Data: Send,
{
let mut connector = HttpConnector::new(4, handle);
if self.keep_alive {
connector.set_keepalive(self.keep_alive_timeout);
Client {
connector: Arc::new(connector),
executor: self.exec.clone(),
h1_writev: self.h1_writev,
pool: Pool::new(self.keep_alive, self.keep_alive_timeout),
retry_canceled_requests: self.retry_canceled_requests,
set_host: self.set_host,
}
self.connector(connector).executor(executor)
}
}
impl<C, B> fmt::Debug for Config<C, B> {
impl fmt::Debug for Builder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Config")
f.debug_struct("Builder")
.field("keep_alive", &self.keep_alive)
.field("keep_alive_timeout", &self.keep_alive_timeout)
.field("http1_writev", &self.h1_writev)
@@ -629,29 +550,16 @@ impl<C, B> fmt::Debug for Config<C, B> {
}
}
impl<C: Clone, B> Clone for Config<C, B> {
fn clone(&self) -> Config<C, B> {
Config {
connector: self.connector.clone(),
.. *self
}
}
}
// ===== impl Exec =====
#[derive(Clone)]
enum Exec {
Default,
Executor(Arc<Executor<Background> + Send + Sync>),
Executor(Arc<Executor<Box<Future<Item=(), Error=()> + Send>> + Send + Sync>),
}
impl Exec {
pub(crate) fn new<E: Executor<Background> + Send + Sync + 'static>(executor: E) -> Exec {
Exec::Executor(Arc::new(executor))
}
fn execute<F>(&self, fut: F)
where
F: Future<Item=(), Error=()> + Send + 'static,
@@ -659,7 +567,7 @@ impl Exec {
match *self {
Exec::Default => spawn(fut),
Exec::Executor(ref e) => {
let _ = e.execute(bg(Box::new(fut)))
let _ = e.execute(Box::new(fut))
.map_err(|err| {
panic!("executor error: {:?}", err.kind());
});
@@ -668,33 +576,3 @@ impl Exec {
}
}
// ===== impl Background =====
// The types inside this module are not exported out of the crate,
// so they are in essence un-nameable.
mod background {
use futures::{Future, Poll};
// This is basically `impl Future`, since the type is un-nameable,
// and only implementeds `Future`.
#[allow(missing_debug_implementations)]
pub struct Background {
inner: Box<Future<Item=(), Error=()> + Send>,
}
pub fn bg(fut: Box<Future<Item=(), Error=()> + Send>) -> Background {
Background {
inner: fut,
}
}
impl Future for Background {
type Item = ();
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.inner.poll()
}
}
}

View File

@@ -503,11 +503,12 @@ mod tests {
#[test]
fn test_pool_timer_removes_expired() {
use std::sync::Arc;
let runtime = ::tokio::runtime::Runtime::new().unwrap();
let pool = Pool::new(true, Some(Duration::from_millis(100)));
let executor = runtime.executor();
pool.spawn_expired_interval(&Exec::new(executor));
pool.spawn_expired_interval(&Exec::Executor(Arc::new(executor)));
let key = Arc::new("foo".to_string());
pool.pooled(key.clone(), 41);

View File

@@ -20,9 +20,9 @@ fn retryable_request() {
let sock1 = connector.mock("http://mock.local");
let sock2 = connector.mock("http://mock.local");
let client = Client::configure()
.connector(connector)
.executor(executor.sender().clone());
let client = Client::builder()
.executor(executor.sender().clone())
.build::<_, ::Body>(connector);
{
@@ -66,9 +66,9 @@ fn conn_reset_after_write() {
let sock1 = connector.mock("http://mock.local");
let client = Client::configure()
.connector(connector)
.executor(executor.sender().clone());
let client = Client::builder()
.executor(executor.sender().clone())
.build::<_, ::Body>(connector);
{
let req = Request::builder()