feat(client): Client will retry requests on fresh connections
If a request sees an error on a pooled connection before ever writing any bytes, it will now retry with a new connection. This can be configured with `Config::retry_canceled_requests(bool)`.
This commit is contained in:
@@ -4,8 +4,8 @@ use futures::sync::{mpsc, oneshot};
|
|||||||
use common::Never;
|
use common::Never;
|
||||||
use super::cancel::{Cancel, Canceled};
|
use super::cancel::{Cancel, Canceled};
|
||||||
|
|
||||||
pub type Callback<U> = oneshot::Sender<::Result<U>>;
|
pub type Callback<T, U> = oneshot::Sender<Result<U, (::Error, Option<T>)>>;
|
||||||
pub type Promise<U> = oneshot::Receiver<::Result<U>>;
|
pub type Promise<T, U> = oneshot::Receiver<Result<U, (::Error, Option<T>)>>;
|
||||||
|
|
||||||
pub fn channel<T, U>() -> (Sender<T, U>, Receiver<T, U>) {
|
pub fn channel<T, U>() -> (Sender<T, U>, Receiver<T, U>) {
|
||||||
let (tx, rx) = mpsc::unbounded();
|
let (tx, rx) = mpsc::unbounded();
|
||||||
@@ -23,7 +23,7 @@ pub fn channel<T, U>() -> (Sender<T, U>, Receiver<T, U>) {
|
|||||||
|
|
||||||
pub struct Sender<T, U> {
|
pub struct Sender<T, U> {
|
||||||
cancel: Cancel,
|
cancel: Cancel,
|
||||||
inner: mpsc::UnboundedSender<(T, Callback<U>)>,
|
inner: mpsc::UnboundedSender<(T, Callback<T, U>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> Sender<T, U> {
|
impl<T, U> Sender<T, U> {
|
||||||
@@ -35,7 +35,7 @@ impl<T, U> Sender<T, U> {
|
|||||||
self.cancel.cancel();
|
self.cancel.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send(&self, val: T) -> Result<Promise<U>, T> {
|
pub fn send(&self, val: T) -> Result<Promise<T, U>, T> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
self.inner.unbounded_send((val, tx))
|
self.inner.unbounded_send((val, tx))
|
||||||
.map(move |_| rx)
|
.map(move |_| rx)
|
||||||
@@ -54,11 +54,11 @@ impl<T, U> Clone for Sender<T, U> {
|
|||||||
|
|
||||||
pub struct Receiver<T, U> {
|
pub struct Receiver<T, U> {
|
||||||
canceled: Canceled,
|
canceled: Canceled,
|
||||||
inner: mpsc::UnboundedReceiver<(T, Callback<U>)>,
|
inner: mpsc::UnboundedReceiver<(T, Callback<T, U>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> Stream for Receiver<T, U> {
|
impl<T, U> Stream for Receiver<T, U> {
|
||||||
type Item = (T, Callback<U>);
|
type Item = (T, Callback<T, U>);
|
||||||
type Error = Never;
|
type Error = Never;
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||||
@@ -83,9 +83,9 @@ impl<T, U> Drop for Receiver<T, U> {
|
|||||||
// - Ready(None): the end. we want to stop looping
|
// - Ready(None): the end. we want to stop looping
|
||||||
// - NotReady: unreachable
|
// - NotReady: unreachable
|
||||||
// - Err: unreachable
|
// - Err: unreachable
|
||||||
while let Ok(Async::Ready(Some((_val, cb)))) = self.inner.poll() {
|
while let Ok(Async::Ready(Some((val, cb)))) = self.inner.poll() {
|
||||||
// maybe in future, we pass the value along with the error?
|
// maybe in future, we pass the value along with the error?
|
||||||
let _ = cb.send(Err(::Error::new_canceled(None)));
|
let _ = cb.send(Err((::Error::new_canceled(None), Some(val))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ mod pool;
|
|||||||
pub mod compat;
|
pub mod compat;
|
||||||
|
|
||||||
/// A Client to make outgoing HTTP requests.
|
/// A Client to make outgoing HTTP requests.
|
||||||
// If the Connector is clone, then the Client can be clone easily.
|
|
||||||
pub struct Client<C, B = proto::Body> {
|
pub struct Client<C, B = proto::Body> {
|
||||||
connector: C,
|
connector: Rc<C>,
|
||||||
executor: Exec,
|
executor: Exec,
|
||||||
h1_writev: bool,
|
h1_writev: bool,
|
||||||
pool: Pool<HyperClient<B>>,
|
pool: Pool<HyperClient<B>>,
|
||||||
|
retry_canceled_requests: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client<HttpConnector, proto::Body> {
|
impl Client<HttpConnector, proto::Body> {
|
||||||
@@ -95,10 +95,11 @@ impl<C, B> Client<C, B> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn configured(config: Config<C, B>, exec: Exec) -> Client<C, B> {
|
fn configured(config: Config<C, B>, exec: Exec) -> Client<C, B> {
|
||||||
Client {
|
Client {
|
||||||
connector: config.connector,
|
connector: Rc::new(config.connector),
|
||||||
executor: exec,
|
executor: exec,
|
||||||
h1_writev: config.h1_writev,
|
h1_writev: config.h1_writev,
|
||||||
pool: Pool::new(config.keep_alive, config.keep_alive_timeout)
|
pool: Pool::new(config.keep_alive, config.keep_alive_timeout),
|
||||||
|
retry_canceled_requests: config.retry_canceled_requests,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,8 +117,46 @@ where C: Connect,
|
|||||||
|
|
||||||
/// Send a constructed Request using this Client.
|
/// Send a constructed Request using this Client.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn request(&self, req: Request<B>) -> FutureResponse {
|
pub fn request(&self, mut req: Request<B>) -> FutureResponse {
|
||||||
self.call(req)
|
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::<Host>() {
|
||||||
|
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.
|
/// Send an `http::Request` using this Client.
|
||||||
@@ -132,68 +171,11 @@ where C: Connect,
|
|||||||
pub fn into_compat(self) -> compat::CompatClient<C, B> {
|
pub fn into_compat(self) -> compat::CompatClient<C, B> {
|
||||||
self::compat::client(self)
|
self::compat::client(self)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// A `Future` that will resolve to an HTTP Response.
|
|
||||||
#[must_use = "futures do nothing unless polled"]
|
|
||||||
pub struct FutureResponse(Box<Future<Item=Response, Error=::Error> + 'static>);
|
|
||||||
|
|
||||||
impl fmt::Debug for FutureResponse {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.pad("Future<Response>")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Future for FutureResponse {
|
|
||||||
type Item = Response;
|
|
||||||
type Error = ::Error;
|
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
|
||||||
self.0.poll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C, B> Service for Client<C, B>
|
|
||||||
where C: Connect,
|
|
||||||
B: Stream<Error=::Error> + 'static,
|
|
||||||
B::Item: AsRef<[u8]>,
|
|
||||||
{
|
|
||||||
type Request = Request<B>;
|
|
||||||
type Response = Response;
|
|
||||||
type Error = ::Error;
|
|
||||||
type Future = FutureResponse;
|
|
||||||
|
|
||||||
fn call(&self, req: Self::Request) -> Self::Future {
|
|
||||||
match req.version() {
|
|
||||||
HttpVersion::Http10 |
|
|
||||||
HttpVersion::Http11 => (),
|
|
||||||
other => {
|
|
||||||
error!("Request has unsupported version \"{}\"", other);
|
|
||||||
return FutureResponse(Box::new(future::err(::Error::Version)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
//TODO: replace with `impl Future` when stable
|
||||||
|
fn send_request(&self, req: Request<B>, domain: &Uri) -> Box<Future<Item=Response, Error=ClientError<B>>> {
|
||||||
let url = req.uri().clone();
|
let url = req.uri().clone();
|
||||||
let domain = match uri::scheme_and_authority(&url) {
|
let (head, body) = request::split(req);
|
||||||
Some(uri) => uri,
|
|
||||||
None => {
|
|
||||||
return FutureResponse(Box::new(future::err(::Error::Io(
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::InvalidInput,
|
|
||||||
"invalid URI for Client Request"
|
|
||||||
)
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let (mut head, body) = request::split(req);
|
|
||||||
if !head.headers.has::<Host>() {
|
|
||||||
let host = Host::new(
|
|
||||||
domain.host().expect("authority implies host").to_owned(),
|
|
||||||
domain.port(),
|
|
||||||
);
|
|
||||||
head.headers.set_pos(0, host);
|
|
||||||
}
|
|
||||||
|
|
||||||
let checkout = self.pool.checkout(domain.as_ref());
|
let checkout = self.pool.checkout(domain.as_ref());
|
||||||
let connect = {
|
let connect = {
|
||||||
let executor = self.executor.clone();
|
let executor = self.executor.clone();
|
||||||
@@ -220,53 +202,147 @@ where C: Connect,
|
|||||||
|
|
||||||
let race = checkout.select(connect)
|
let race = checkout.select(connect)
|
||||||
.map(|(client, _work)| client)
|
.map(|(client, _work)| client)
|
||||||
.map_err(|(e, _work)| {
|
.map_err(|(e, _checkout)| {
|
||||||
// the Pool Checkout cannot error, so the only error
|
// the Pool Checkout cannot error, so the only error
|
||||||
// is from the Connector
|
// is from the Connector
|
||||||
// XXX: should wait on the Checkout? Problem is
|
// XXX: should wait on the Checkout? Problem is
|
||||||
// that if the connector is failing, it may be that we
|
// that if the connector is failing, it may be that we
|
||||||
// never had a pooled stream at all
|
// never had a pooled stream at all
|
||||||
e.into()
|
ClientError::Normal(e.into())
|
||||||
});
|
});
|
||||||
|
|
||||||
let resp = race.and_then(move |client| {
|
let resp = race.and_then(move |client| {
|
||||||
|
let conn_reused = client.is_reused();
|
||||||
match client.tx.send((head, body)) {
|
match client.tx.send((head, body)) {
|
||||||
Ok(rx) => {
|
Ok(rx) => {
|
||||||
client.should_close.set(false);
|
client.should_close.set(false);
|
||||||
Either::A(rx.then(|res| {
|
Either::A(rx.then(move |res| {
|
||||||
match res {
|
match res {
|
||||||
Ok(Ok(res)) => Ok(res),
|
Ok(Ok(res)) => Ok(res),
|
||||||
Ok(Err(err)) => Err(err),
|
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(_) => panic!("dispatch dropped without returning error"),
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
Err(_) => {
|
Err(req) => {
|
||||||
error!("pooled connection was not ready, this is a hyper bug");
|
debug!("pooled connection was not ready");
|
||||||
Either::B(future::err(::Error::new_canceled(None)))
|
let err = ClientError::Canceled {
|
||||||
|
connection_reused: conn_reused,
|
||||||
|
reason: ::Error::new_canceled(None),
|
||||||
|
req: req,
|
||||||
|
};
|
||||||
|
Either::B(future::err(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
FutureResponse(Box::new(resp))
|
Box::new(resp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<C, B> Service for Client<C, B>
|
||||||
|
where C: Connect,
|
||||||
|
B: Stream<Error=::Error> + 'static,
|
||||||
|
B::Item: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
type Request = Request<B>;
|
||||||
|
type Response = Response;
|
||||||
|
type Error = ::Error;
|
||||||
|
type Future = FutureResponse;
|
||||||
|
|
||||||
|
fn call(&self, req: Self::Request) -> Self::Future {
|
||||||
|
self.request(req)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Clone, B> Clone for Client<C, B> {
|
impl<C, B> Clone for Client<C, B> {
|
||||||
fn clone(&self) -> Client<C, B> {
|
fn clone(&self) -> Client<C, B> {
|
||||||
Client {
|
Client {
|
||||||
connector: self.connector.clone(),
|
connector: self.connector.clone(),
|
||||||
executor: self.executor.clone(),
|
executor: self.executor.clone(),
|
||||||
h1_writev: self.h1_writev,
|
h1_writev: self.h1_writev,
|
||||||
pool: self.pool.clone(),
|
pool: self.pool.clone(),
|
||||||
|
retry_canceled_requests: self.retry_canceled_requests,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, B> fmt::Debug for Client<C, B> {
|
impl<C, B> fmt::Debug for Client<C, B> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
f.pad("Client")
|
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<Future<Item=Response, Error=::Error> + 'static>);
|
||||||
|
|
||||||
|
impl fmt::Debug for FutureResponse {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.pad("Future<Response>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for FutureResponse {
|
||||||
|
type Item = Response;
|
||||||
|
type Error = ::Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
self.0.poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RetryableSendRequest<C, B> {
|
||||||
|
client: Client<C, B>,
|
||||||
|
domain: Uri,
|
||||||
|
future: Box<Future<Item=Response, Error=ClientError<B>>>,
|
||||||
|
is_proxy: bool,
|
||||||
|
uri: Uri,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, B> Future for RetryableSendRequest<C, B>
|
||||||
|
where
|
||||||
|
C: Connect,
|
||||||
|
B: Stream<Error=::Error> + 'static,
|
||||||
|
B::Item: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
type Item = Response;
|
||||||
|
type Error = ::Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,6 +379,15 @@ impl<B> Drop for HyperClient<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) enum ClientError<B> {
|
||||||
|
Normal(::Error),
|
||||||
|
Canceled {
|
||||||
|
connection_reused: bool,
|
||||||
|
req: (::proto::RequestHead, Option<B>),
|
||||||
|
reason: ::Error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Configuration for a Client
|
/// Configuration for a Client
|
||||||
pub struct Config<C, B> {
|
pub struct Config<C, B> {
|
||||||
_body_type: PhantomData<B>,
|
_body_type: PhantomData<B>,
|
||||||
@@ -313,6 +398,7 @@ pub struct Config<C, B> {
|
|||||||
h1_writev: bool,
|
h1_writev: bool,
|
||||||
//TODO: make use of max_idle config
|
//TODO: make use of max_idle config
|
||||||
max_idle: usize,
|
max_idle: usize,
|
||||||
|
retry_canceled_requests: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Phantom type used to signal that `Config` should create a `HttpConnector`.
|
/// Phantom type used to signal that `Config` should create a `HttpConnector`.
|
||||||
@@ -323,12 +409,12 @@ impl Default for Config<UseDefaultConnector, proto::Body> {
|
|||||||
fn default() -> Config<UseDefaultConnector, proto::Body> {
|
fn default() -> Config<UseDefaultConnector, proto::Body> {
|
||||||
Config {
|
Config {
|
||||||
_body_type: PhantomData::<proto::Body>,
|
_body_type: PhantomData::<proto::Body>,
|
||||||
//connect_timeout: Duration::from_secs(10),
|
|
||||||
connector: UseDefaultConnector(()),
|
connector: UseDefaultConnector(()),
|
||||||
keep_alive: true,
|
keep_alive: true,
|
||||||
keep_alive_timeout: Some(Duration::from_secs(90)),
|
keep_alive_timeout: Some(Duration::from_secs(90)),
|
||||||
h1_writev: true,
|
h1_writev: true,
|
||||||
max_idle: 5,
|
max_idle: 5,
|
||||||
|
retry_canceled_requests: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,12 +433,12 @@ impl<C, B> Config<C, B> {
|
|||||||
pub fn body<BB>(self) -> Config<C, BB> {
|
pub fn body<BB>(self) -> Config<C, BB> {
|
||||||
Config {
|
Config {
|
||||||
_body_type: PhantomData::<BB>,
|
_body_type: PhantomData::<BB>,
|
||||||
//connect_timeout: self.connect_timeout,
|
|
||||||
connector: self.connector,
|
connector: self.connector,
|
||||||
keep_alive: self.keep_alive,
|
keep_alive: self.keep_alive,
|
||||||
keep_alive_timeout: self.keep_alive_timeout,
|
keep_alive_timeout: self.keep_alive_timeout,
|
||||||
h1_writev: self.h1_writev,
|
h1_writev: self.h1_writev,
|
||||||
max_idle: self.max_idle,
|
max_idle: self.max_idle,
|
||||||
|
retry_canceled_requests: self.retry_canceled_requests,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,12 +447,12 @@ impl<C, B> Config<C, B> {
|
|||||||
pub fn connector<CC>(self, val: CC) -> Config<CC, B> {
|
pub fn connector<CC>(self, val: CC) -> Config<CC, B> {
|
||||||
Config {
|
Config {
|
||||||
_body_type: self._body_type,
|
_body_type: self._body_type,
|
||||||
//connect_timeout: self.connect_timeout,
|
|
||||||
connector: val,
|
connector: val,
|
||||||
keep_alive: self.keep_alive,
|
keep_alive: self.keep_alive,
|
||||||
keep_alive_timeout: self.keep_alive_timeout,
|
keep_alive_timeout: self.keep_alive_timeout,
|
||||||
h1_writev: self.h1_writev,
|
h1_writev: self.h1_writev,
|
||||||
max_idle: self.max_idle,
|
max_idle: self.max_idle,
|
||||||
|
retry_canceled_requests: self.retry_canceled_requests,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,17 +476,6 @@ impl<C, B> Config<C, B> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Set the timeout for connecting to a URL.
|
|
||||||
///
|
|
||||||
/// Default is 10 seconds.
|
|
||||||
#[inline]
|
|
||||||
pub fn connect_timeout(mut self, val: Duration) -> Config<C, B> {
|
|
||||||
self.connect_timeout = val;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Set whether HTTP/1 connections should try to use vectored writes,
|
/// Set whether HTTP/1 connections should try to use vectored writes,
|
||||||
/// or always flatten into a single buffer.
|
/// or always flatten into a single buffer.
|
||||||
///
|
///
|
||||||
@@ -408,13 +483,30 @@ impl<C, B> Config<C, B> {
|
|||||||
/// but may also improve performance when an IO transport doesn't
|
/// but may also improve performance when an IO transport doesn't
|
||||||
/// support vectored writes well, such as most TLS implementations.
|
/// support vectored writes well, such as most TLS implementations.
|
||||||
///
|
///
|
||||||
/// Default is true.
|
/// Default is `true`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn http1_writev(mut self, val: bool) -> Config<C, B> {
|
pub fn http1_writev(mut self, val: bool) -> Config<C, B> {
|
||||||
self.h1_writev = val;
|
self.h1_writev = val;
|
||||||
self
|
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<C, B> {
|
||||||
|
self.retry_canceled_requests = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[deprecated(since="0.11.11", note="no_proto is always enabled")]
|
#[deprecated(since="0.11.11", note="no_proto is always enabled")]
|
||||||
pub fn no_proto(self) -> Config<C, B> {
|
pub fn no_proto(self) -> Config<C, B> {
|
||||||
|
|||||||
@@ -211,6 +211,12 @@ pub struct Pooled<T> {
|
|||||||
pool: Weak<RefCell<PoolInner<T>>>,
|
pool: Weak<RefCell<PoolInner<T>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Pooled<T> {
|
||||||
|
pub fn is_reused(&self) -> bool {
|
||||||
|
self.entry.is_reused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Deref for Pooled<T> {
|
impl<T> Deref for Pooled<T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
fn deref(&self) -> &T {
|
fn deref(&self) -> &T {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub struct Server<S: Service> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Client<B> {
|
pub struct Client<B> {
|
||||||
callback: Option<oneshot::Sender<::Result<::Response>>>,
|
callback: Option<oneshot::Sender<Result<::Response, (::Error, Option<ClientMsg<B>>)>>>,
|
||||||
rx: ClientRx<B>,
|
rx: ClientRx<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,12 +398,13 @@ where
|
|||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Some(cb) = self.callback.take() {
|
if let Some(cb) = self.callback.take() {
|
||||||
let _ = cb.send(Err(err));
|
let _ = cb.send(Err((err, None)));
|
||||||
Ok(())
|
Ok(())
|
||||||
} else if let Ok(Async::Ready(Some((_, cb)))) = self.rx.poll() {
|
} else if let Ok(Async::Ready(Some((req, cb)))) = self.rx.poll() {
|
||||||
|
trace!("canceling queued request with connection error: {}", err);
|
||||||
// in this case, the message was never even started, so it's safe to tell
|
// in this case, the message was never even started, so it's safe to tell
|
||||||
// the user that the request was completely canceled
|
// the user that the request was completely canceled
|
||||||
let _ = cb.send(Err(::Error::new_canceled(Some(err))));
|
let _ = cb.send(Err((::Error::new_canceled(Some(err)), Some(req))));
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(err)
|
Err(err)
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ impl<B> Request<B> {
|
|||||||
/// protected by TLS.
|
/// protected by TLS.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_proxy(&mut self, is_proxy: bool) { self.is_proxy = is_proxy; }
|
pub fn set_proxy(&mut self, is_proxy: bool) { self.is_proxy = is_proxy; }
|
||||||
|
|
||||||
|
pub(crate) fn is_proxy(&self) -> bool { self.is_proxy }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request<Body> {
|
impl Request<Body> {
|
||||||
@@ -165,16 +167,9 @@ impl<B> From<http::Request<B>> for Request<B> {
|
|||||||
|
|
||||||
/// Constructs a request using a received ResponseHead and optional body
|
/// Constructs a request using a received ResponseHead and optional body
|
||||||
pub fn from_wire(addr: Option<SocketAddr>, incoming: RequestHead, body: Option<Body>) -> Request<Body> {
|
pub fn from_wire(addr: Option<SocketAddr>, incoming: RequestHead, body: Option<Body>) -> Request<Body> {
|
||||||
let MessageHead { version, subject: RequestLine(method, uri), headers } = incoming;
|
|
||||||
|
|
||||||
Request {
|
Request {
|
||||||
method: method,
|
|
||||||
uri: uri,
|
|
||||||
headers: headers,
|
|
||||||
version: version,
|
|
||||||
remote_addr: addr,
|
remote_addr: addr,
|
||||||
body: body,
|
..join((incoming, body))
|
||||||
is_proxy: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,6 +187,20 @@ pub fn split<B>(req: Request<B>) -> (RequestHead, Option<B>) {
|
|||||||
(head, req.body)
|
(head, req.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn join<B>((head, body): (RequestHead, Option<B>)) -> Request<B> {
|
||||||
|
let MessageHead { version, subject: RequestLine(method, uri), headers } = head;
|
||||||
|
|
||||||
|
Request {
|
||||||
|
method: method,
|
||||||
|
uri: uri,
|
||||||
|
headers: headers,
|
||||||
|
version: version,
|
||||||
|
remote_addr: None,
|
||||||
|
body: body,
|
||||||
|
is_proxy: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn addr<B>(req: &mut Request<B>, addr: SocketAddr) {
|
pub fn addr<B>(req: &mut Request<B>, addr: SocketAddr) {
|
||||||
req.remote_addr = Some(addr);
|
req.remote_addr = Some(addr);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -573,7 +573,6 @@ fn client_keep_alive_connreset() {
|
|||||||
let mut core = Core::new().unwrap();
|
let mut core = Core::new().unwrap();
|
||||||
let handle = core.handle();
|
let handle = core.handle();
|
||||||
|
|
||||||
// This one seems to hang forever
|
|
||||||
let client = client(&handle);
|
let client = client(&handle);
|
||||||
|
|
||||||
let (tx1, rx1) = oneshot::channel();
|
let (tx1, rx1) = oneshot::channel();
|
||||||
@@ -594,6 +593,14 @@ fn client_keep_alive_connreset() {
|
|||||||
|
|
||||||
// Let client know it can try to reuse the connection
|
// Let client know it can try to reuse the connection
|
||||||
let _ = tx1.send(());
|
let _ = tx1.send(());
|
||||||
|
|
||||||
|
// use sock2 so that sock isn't dropped yet
|
||||||
|
let mut sock2 = server.accept().unwrap().0;
|
||||||
|
sock2.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||||
|
sock2.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||||
|
let mut buf = [0; 4096];
|
||||||
|
sock2.read(&mut buf).expect("read 2");
|
||||||
|
sock2.write_all(b"HTTP/1.1 222 OK\r\nContent-Length: 0\r\n\r\n").expect("write 2");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -606,7 +613,11 @@ fn client_keep_alive_connreset() {
|
|||||||
core.run(rx).unwrap();
|
core.run(rx).unwrap();
|
||||||
|
|
||||||
let t = Timeout::new(Duration::from_millis(100), &handle).unwrap();
|
let t = Timeout::new(Duration::from_millis(100), &handle).unwrap();
|
||||||
let res = client.get(format!("http://{}/b", addr).parse().unwrap());
|
let res = client.get(format!("http://{}/b", addr).parse().unwrap())
|
||||||
|
.map(|res| {
|
||||||
|
assert_eq!(res.status().as_u16(), 222);
|
||||||
|
});
|
||||||
|
|
||||||
let fut = res.select2(t).then(|result| match result {
|
let fut = res.select2(t).then(|result| match result {
|
||||||
Ok(Either::A((resp, _))) => Ok(resp),
|
Ok(Either::A((resp, _))) => Ok(resp),
|
||||||
Err(Either::A((err, _))) => Err(err),
|
Err(Either::A((err, _))) => Err(err),
|
||||||
@@ -614,16 +625,7 @@ fn client_keep_alive_connreset() {
|
|||||||
Err(Either::B(_)) => Err(hyper::Error::Timeout),
|
Err(Either::B(_)) => Err(hyper::Error::Timeout),
|
||||||
});
|
});
|
||||||
|
|
||||||
// for now, the 2nd request is just canceled, since the connection is found to be dead
|
core.run(fut).expect("req 2");
|
||||||
// at the same time the request is scheduled.
|
|
||||||
//
|
|
||||||
// in the future, it'd be nice to auto retry the request, but can't really be done yet
|
|
||||||
// as the `connector` isn't clone so we can't use it "later", when the future resolves.
|
|
||||||
let err = core.run(fut).unwrap_err();
|
|
||||||
match err {
|
|
||||||
hyper::Error::Cancel(..) => (),
|
|
||||||
other => panic!("expected Cancel error, got {:?}", other),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user