feat(error): revamp hyper::Error type

**The `Error` is now an opaque struct**, which allows for more variants to
be added freely, and the internal representation to change without being
breaking changes.

For inspecting an `Error`, there are several `is_*` methods to check for
certain classes of errors, such as `Error::is_parse()`. The `cause` can
also be inspected, like before. This likely seems like a downgrade, but
more inspection can be added as needed!

The `Error` now knows about more states, which gives much more context
around when a certain error occurs. This is also expressed in the
description and `fmt` messages.

**Most places where a user would provide an error to hyper can now pass
any error type** (`E: Into<Box<std::error::Error>>`). This error is passed
back in relevant places, and can be useful for logging. This should make
it much clearer about what error a user should provide to hyper: any it
feels is relevant!

Closes #1128
Closes #1130
Closes #1431
Closes #1338

BREAKING CHANGE: `Error` is no longer an enum to pattern match over, or
  to construct. Code will need to be updated accordingly.

  For body streams or `Service`s, inference might be unable to determine
  what error type you mean to return. Starting in Rust 1.26, you could
  just label that as `!` if you never return an error.
This commit is contained in:
Sean McArthur
2018-04-10 14:29:34 -07:00
parent 33874f9a75
commit 5d3c472228
22 changed files with 519 additions and 407 deletions

View File

@@ -1,5 +1,3 @@
use std::io;
use bytes::Bytes;
use futures::{Async, Future, Poll, Stream};
use http::{Request, Response, StatusCode};
@@ -9,7 +7,7 @@ use tokio_service::Service;
use proto::body::Entity;
use proto::{Body, BodyLength, Conn, Http1Transaction, MessageHead, RequestHead, RequestLine, ResponseHead};
pub struct Dispatcher<D, Bs, I, B, T> {
pub(crate) struct Dispatcher<D, Bs, I, B, T> {
conn: Conn<I, B, T>,
dispatch: D,
body_tx: Option<::proto::body::Sender>,
@@ -17,7 +15,7 @@ pub struct Dispatcher<D, Bs, I, B, T> {
is_closing: bool,
}
pub trait Dispatch {
pub(crate) trait Dispatch {
type PollItem;
type PollBody;
type RecvItem;
@@ -47,7 +45,7 @@ where
I: AsyncRead + AsyncWrite,
B: AsRef<[u8]>,
T: Http1Transaction,
Bs: Entity<Data=B, Error=::Error>,
Bs: Entity<Data=B>,
{
pub fn new(dispatch: D, conn: Conn<I, B, T>) -> Self {
Dispatcher {
@@ -98,7 +96,7 @@ where
if self.is_done() {
if should_shutdown {
try_ready!(self.conn.shutdown());
try_ready!(self.conn.shutdown().map_err(::Error::new_shutdown));
}
self.conn.take_error()?;
Ok(Async::Ready(()))
@@ -152,7 +150,7 @@ where
return Ok(Async::NotReady);
}
Err(e) => {
body.send_error(::Error::Io(e));
body.send_error(::Error::new_body(e));
}
}
} else {
@@ -225,14 +223,14 @@ where
} else if !self.conn.can_buffer_body() {
try_ready!(self.poll_flush());
} else if let Some(mut body) = self.body_rx.take() {
let chunk = match body.poll_data()? {
let chunk = match body.poll_data().map_err(::Error::new_user_body)? {
Async::Ready(Some(chunk)) => {
self.body_rx = Some(body);
chunk
},
Async::Ready(None) => {
if self.conn.can_write_body() {
self.conn.write_body(None)?;
self.conn.write_body(None).map_err(::Error::new_body_write)?;
}
continue;
},
@@ -243,7 +241,7 @@ where
};
if self.conn.can_write_body() {
assert!(self.conn.write_body(Some(chunk))?.is_ready());
self.conn.write_body(Some(chunk)).map_err(::Error::new_body_write)?;
// This allows when chunk is `None`, or `Some([])`.
} else if chunk.as_ref().len() == 0 {
// ok
@@ -259,7 +257,7 @@ where
fn poll_flush(&mut self) -> Poll<(), ::Error> {
self.conn.flush().map_err(|err| {
debug!("error writing: {}", err);
err.into()
::Error::new_body_write(err)
})
}
@@ -294,7 +292,7 @@ where
I: AsyncRead + AsyncWrite,
B: AsRef<[u8]>,
T: Http1Transaction,
Bs: Entity<Data=B, Error=::Error>,
Bs: Entity<Data=B>,
{
type Item = ();
type Error = ::Error;
@@ -318,8 +316,9 @@ impl<S> Server<S> where S: Service {
impl<S, Bs> Dispatch for Server<S>
where
S: Service<Request=Request<Body>, Response=Response<Bs>, Error=::Error>,
Bs: Entity<Error=::Error>,
S: Service<Request=Request<Body>, Response=Response<Bs>>,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
Bs: Entity,
{
type PollItem = MessageHead<StatusCode>;
type PollBody = Bs;
@@ -327,7 +326,7 @@ where
fn poll_msg(&mut self) -> Poll<Option<(Self::PollItem, Option<Self::PollBody>)>, ::Error> {
if let Some(mut fut) = self.in_flight.take() {
let resp = match fut.poll()? {
let resp = match fut.poll().map_err(::Error::new_user_service)? {
Async::Ready(res) => res,
Async::NotReady => {
self.in_flight = Some(fut);
@@ -389,7 +388,7 @@ impl<B> Client<B> {
impl<B> Dispatch for Client<B>
where
B: Entity<Error=::Error>,
B: Entity,
{
type PollItem = RequestHead;
type PollBody = B;
@@ -443,7 +442,7 @@ where
let _ = cb.send(Ok(res));
Ok(())
} else {
Err(::Error::Io(io::Error::new(io::ErrorKind::InvalidData, "response received without matching request")))
Err(::Error::new_mismatched_response())
}
},
Err(err) => {
@@ -507,8 +506,15 @@ mod tests {
.expect("callback poll")
.expect_err("callback response");
match err {
(::Error::Cancel(_), Some(_)) => (),
/*
let err = match async {
Async::Ready(result) => result.unwrap_err(),
Async::Pending => panic!("callback should be ready"),
};
*/
match (err.0.kind(), err.1) {
(&::error::Kind::Canceled, Some(_)) => (),
other => panic!("expected Canceled, got {:?}", other),
}
Ok::<(), ()>(())