//! HTTP Client use std::cell::Cell; use std::fmt; use std::io; use std::marker::PhantomData; use std::rc::Rc; use std::time::Duration; use futures::{Async, Future, Poll, Stream}; use futures::future::{self, Either, Executor}; #[cfg(feature = "compat")] use http; use tokio::reactor::Handle; pub use tokio_service::Service; use header::{Host}; use proto; use proto::request; use method::Method; use self::pool::Pool; use uri::{self, Uri}; use version::HttpVersion; pub use proto::response::Response; pub use proto::request::Request; pub use self::connect::{HttpConnector, Connect}; use self::background::{bg, Background}; mod cancel; mod connect; //TODO(easy): move cancel and dispatch into common instead pub(crate) mod dispatch; mod dns; mod pool; #[cfg(feature = "compat")] pub mod compat; /// A Client to make outgoing HTTP requests. pub struct Client { connector: Rc, executor: Exec, h1_writev: bool, pool: Pool>, retry_canceled_requests: bool, } impl Client { /// Create a new Client with the default config. #[inline] pub fn new(handle: &Handle) -> Client { Config::default().build(handle) } } impl Client { /// Configure a Client. /// /// # Example /// /// ```no_run /// # extern crate hyper; /// # extern crate tokio_core; /// /// # fn main() { /// # let core = tokio_core::reactor::Core::new().unwrap(); /// # let handle = core.handle(); /// let client = hyper::Client::configure() /// .keep_alive(true) /// .build(&handle); /// # drop(client); /// # } /// ``` #[inline] pub fn configure() -> Config { Config::default() } } impl Client { // Eventually, a Client won't really care about a tokio Handle, and only // the executor used to spawn background tasks. Removing this method is // a breaking change, so for now, it's just deprecated. #[doc(hidden)] #[deprecated] pub fn handle(&self) -> &Handle { match self.executor { Exec::Handle(ref h) => h, Exec::Executor(..) => panic!("Client not built with a Handle"), } } #[inline] fn configured(config: Config, exec: Exec) -> Client { Client { connector: Rc::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, } } } impl Client where C: Connect, B: Stream + 'static, B::Item: AsRef<[u8]>, { /// Send a GET Request using this Client. #[inline] pub fn get(&self, url: Uri) -> FutureResponse { self.request(Request::new(Method::Get, url)) } /// Send a constructed Request using this Client. #[inline] pub fn request(&self, mut req: Request) -> FutureResponse { // TODO(0.12): do this at construction time. // // It cannot be done in the constructor because the Client::configured // does not have `B: 'static` bounds, which are required to spawn // the interval. In 0.12, add a static bounds to the constructor, // and move this. self.schedule_pool_timer(); match req.version() { HttpVersion::Http10 | HttpVersion::Http11 => (), other => { error!("Request has unsupported version \"{}\"", other); return FutureResponse(Box::new(future::err(::Error::Version))); } } let domain = match uri::scheme_and_authority(req.uri()) { Some(uri) => uri, None => { return FutureResponse(Box::new(future::err(::Error::Io( io::Error::new( io::ErrorKind::InvalidInput, "invalid URI for Client Request" ) )))); } }; if !req.headers().has::() { let host = Host::new( domain.host().expect("authority implies host").to_owned(), domain.port(), ); req.headers_mut().set_pos(0, host); } let client = self.clone(); let is_proxy = req.is_proxy(); let uri = req.uri().clone(); let fut = RetryableSendRequest { client: client, future: self.send_request(req, &domain), domain: domain, is_proxy: is_proxy, uri: uri, }; FutureResponse(Box::new(fut)) } /// Send an `http::Request` using this Client. #[inline] #[cfg(feature = "compat")] pub fn request_compat(&self, req: http::Request) -> compat::CompatFutureResponse { self::compat::future(self.call(req.into())) } /// Convert into a client accepting `http::Request`. #[cfg(feature = "compat")] pub fn into_compat(self) -> compat::CompatClient { self::compat::client(self) } //TODO: replace with `impl Future` when stable fn send_request(&self, req: Request, domain: &Uri) -> Box>> { let url = req.uri().clone(); let (head, body) = request::split(req); let checkout = self.pool.checkout(domain.as_ref()); let connect = { let executor = self.executor.clone(); let pool = self.pool.clone(); let pool_key = Rc::new(domain.to_string()); let h1_writev = self.h1_writev; let connector = self.connector.clone(); future::lazy(move || { connector.connect(url) .and_then(move |io| { let (tx, rx) = dispatch::channel(); let tx = HyperClient { tx: tx, should_close: Cell::new(true), }; let pooled = pool.pooled(pool_key, tx); let mut conn = proto::Conn::<_, _, proto::ClientTransaction, _>::new(io, pooled.clone()); if !h1_writev { conn.set_write_strategy_flatten(); } let dispatch = proto::dispatch::Dispatcher::new(proto::dispatch::Client::new(rx), conn); executor.execute(dispatch.map_err(|e| debug!("client connection error: {}", e)))?; Ok(pooled) }) }) }; let race = checkout.select(connect) .map(|(client, _work)| client) .map_err(|(e, _checkout)| { // the Pool Checkout cannot error, so the only error // is from the Connector // XXX: should wait on the Checkout? Problem is // that if the connector is failing, it may be that we // never had a pooled stream at all ClientError::Normal(e.into()) }); let resp = race.and_then(move |client| { let conn_reused = client.is_reused(); match client.tx.send((head, body)) { Ok(rx) => { client.should_close.set(false); Either::A(rx.then(move |res| { match res { Ok(Ok(res)) => Ok(res), Ok(Err((err, orig_req))) => Err(match orig_req { Some(req) => ClientError::Canceled { connection_reused: conn_reused, reason: err, req: req, }, None => ClientError::Normal(err), }), // this is definite bug if it happens, but it shouldn't happen! Err(_) => panic!("dispatch dropped without returning error"), } })) }, Err(req) => { debug!("pooled connection was not ready"); let err = ClientError::Canceled { connection_reused: conn_reused, reason: ::Error::new_canceled(None), req: req, }; Either::B(future::err(err)) } } }); Box::new(resp) } fn schedule_pool_timer(&self) { if let Exec::Handle(ref h) = self.executor { self.pool.spawn_expired_interval(h); } } } impl Service for Client where C: Connect, B: Stream + 'static, B::Item: AsRef<[u8]>, { type Request = Request; type Response = Response; type Error = ::Error; type Future = FutureResponse; fn call(&self, req: Self::Request) -> Self::Future { self.request(req) } } impl Clone for Client { fn clone(&self) -> Client { Client { connector: self.connector.clone(), executor: self.executor.clone(), h1_writev: self.h1_writev, pool: self.pool.clone(), retry_canceled_requests: self.retry_canceled_requests, } } } impl fmt::Debug for Client { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Client") .finish() } } /// A `Future` that will resolve to an HTTP Response. #[must_use = "futures do nothing unless polled"] pub struct FutureResponse(Box + 'static>); impl fmt::Debug for FutureResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.pad("Future") } } impl Future for FutureResponse { type Item = Response; type Error = ::Error; fn poll(&mut self) -> Poll { self.0.poll() } } struct RetryableSendRequest { client: Client, domain: Uri, future: Box>>, is_proxy: bool, uri: Uri, } impl Future for RetryableSendRequest where C: Connect, B: Stream + 'static, B::Item: AsRef<[u8]>, { type Item = Response; type Error = ::Error; fn poll(&mut self) -> Poll { loop { match self.future.poll() { Ok(Async::Ready(resp)) => return Ok(Async::Ready(resp)), Ok(Async::NotReady) => return Ok(Async::NotReady), Err(ClientError::Normal(err)) => return Err(err), Err(ClientError::Canceled { connection_reused, req, reason, }) => { if !self.client.retry_canceled_requests || !connection_reused { // if client disabled, don't retry // a fresh connection means we definitely can't retry return Err(reason); } trace!("unstarted request canceled, trying again (reason={:?})", reason); let mut req = request::join(req); req.set_proxy(self.is_proxy); req.set_uri(self.uri.clone()); self.future = self.client.send_request(req, &self.domain); } } } } } struct HyperClient { should_close: Cell, tx: dispatch::Sender, ::Response>, } impl Clone for HyperClient { fn clone(&self) -> HyperClient { HyperClient { tx: self.tx.clone(), should_close: self.should_close.clone(), } } } impl self::pool::Ready for HyperClient { fn poll_ready(&mut self) -> Poll<(), ()> { if self.tx.is_closed() { Err(()) } else { Ok(Async::Ready(())) } } } impl Drop for HyperClient { fn drop(&mut self) { if self.should_close.get() { self.should_close.set(false); self.tx.cancel(); } } } pub(crate) enum ClientError { Normal(::Error), Canceled { connection_reused: bool, req: (::proto::RequestHead, Option), reason: ::Error, } } /// Configuration for a Client pub struct Config { _body_type: PhantomData, //connect_timeout: Duration, connector: C, keep_alive: bool, keep_alive_timeout: Option, h1_writev: bool, //TODO: make use of max_idle config max_idle: usize, retry_canceled_requests: bool, } /// Phantom type used to signal that `Config` should create a `HttpConnector`. #[derive(Debug, Clone, Copy)] pub struct UseDefaultConnector(()); impl Default for Config { fn default() -> Config { Config { _body_type: PhantomData::, connector: UseDefaultConnector(()), keep_alive: true, keep_alive_timeout: Some(Duration::from_secs(90)), h1_writev: true, max_idle: 5, retry_canceled_requests: true, } } } impl Config { /// Set the body stream to be used by the `Client`. /// /// # Example /// /// ```rust /// # use hyper::client::Config; /// let cfg = Config::default() /// .body::(); /// # drop(cfg); #[inline] pub fn body(self) -> Config { Config { _body_type: PhantomData::, 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 the `Connect` type to be used. #[inline] pub fn connector(self, val: CC) -> Config { 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, } } /// Enable or disable keep-alive mechanics. /// /// Default is enabled. #[inline] pub fn keep_alive(mut self, val: bool) -> Config { self.keep_alive = val; self } /// Set an optional timeout for idle sockets being kept-alive. /// /// Pass `None` to disable timeout. /// /// Default is 90 seconds. #[inline] pub fn keep_alive_timeout(mut self, val: Option) -> Config { self.keep_alive_timeout = val; self } /// Set whether HTTP/1 connections should try to use vectored writes, /// or always flatten into a single buffer. /// /// Note that setting this to false may mean more copies of body data, /// but may also improve performance when an IO transport doesn't /// support vectored writes well, such as most TLS implementations. /// /// Default is `true`. #[inline] pub fn http1_writev(mut self, val: bool) -> Config { self.h1_writev = val; self } /// Set whether to retry requests that get disrupted before ever starting /// to write. /// /// This means a request that is queued, and gets given an idle, reused /// connection, and then encounters an error immediately as the idle /// connection was found to be unusable. /// /// When this is set to `false`, the related `FutureResponse` would instead /// resolve to an `Error::Cancel`. /// /// Default is `true`. #[inline] pub fn retry_canceled_requests(mut self, val: bool) -> Config { self.retry_canceled_requests = val; self } #[doc(hidden)] #[deprecated(since="0.11.11", note="no_proto is always enabled")] pub fn no_proto(self) -> Config { self } } impl Config where C: Connect, B: Stream, B::Item: AsRef<[u8]>, { /// Construct the Client with this configuration. #[inline] pub fn build(self, handle: &Handle) -> Client { Client::configured(self, Exec::Handle(handle.clone())) } /// 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(self, executor: E) -> Client where E: Executor + 'static, { Client::configured(self, Exec::Executor(Rc::new(executor))) } } impl Config where B: Stream, B::Item: AsRef<[u8]>, { /// Construct the Client with this configuration. #[inline] pub fn build(self, handle: &Handle) -> Client { let mut connector = HttpConnector::new(4, handle); if self.keep_alive { connector.set_keepalive(self.keep_alive_timeout); } self.connector(connector).build(handle) } } impl fmt::Debug for Config { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Config") .field("keep_alive", &self.keep_alive) .field("keep_alive_timeout", &self.keep_alive_timeout) .field("http1_writev", &self.h1_writev) .field("max_idle", &self.max_idle) .finish() } } impl Clone for Config { fn clone(&self) -> Config { Config { connector: self.connector.clone(), .. *self } } } // ===== impl Exec ===== #[derive(Clone)] enum Exec { Handle(Handle), Executor(Rc>), } impl Exec { fn execute(&self, fut: F) -> io::Result<()> where F: Future + 'static, { match *self { Exec::Handle(ref h) => h.spawn(fut), Exec::Executor(ref e) => { e.execute(bg(Box::new(fut))) .map_err(|err| { debug!("executor error: {:?}", err.kind()); io::Error::new( io::ErrorKind::Other, "executor error", ) })? }, } Ok(()) } } // ===== 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>, } pub fn bg(fut: Box>) -> Background { Background { inner: fut, } } impl Future for Background { type Item = (); type Error = (); fn poll(&mut self) -> Poll { self.inner.poll() } } }