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

@@ -18,8 +18,7 @@ pub trait Entity {
type Data: AsRef<[u8]>;
/// The error type of this stream.
//TODO: add bounds Into<::error::User> (or whatever it is called)
type Error;
type Error: Into<Box<::std::error::Error + Send + Sync>>;
/// Poll for a `Data` buffer.
///
@@ -141,7 +140,7 @@ enum Kind {
_close_tx: oneshot::Sender<()>,
rx: mpsc::Receiver<Result<Chunk, ::Error>>,
},
Wrapped(Box<Stream<Item=Chunk, Error=::Error> + Send>),
Wrapped(Box<Stream<Item=Chunk, Error=Box<::std::error::Error + Send + Sync>> + Send>),
Once(Option<Chunk>),
Empty,
}
@@ -212,17 +211,22 @@ impl Body {
/// " ",
/// "world",
/// ];
/// let stream = futures::stream::iter_ok(chunks);
///
/// let stream = futures::stream::iter_ok::<_, ::std::io::Error>(chunks);
///
/// let body = Body::wrap_stream(stream);
/// # }
/// ```
pub fn wrap_stream<S>(stream: S) -> Body
where
S: Stream<Error=::Error> + Send + 'static,
S: Stream + Send + 'static,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
Chunk: From<S::Item>,
{
Body::new(Kind::Wrapped(Box::new(stream.map(Chunk::from))))
let mapped = stream
.map(Chunk::from)
.map_err(Into::into);
Body::new(Kind::Wrapped(Box::new(mapped)))
}
/// Convert this `Body` into a `Stream<Item=Chunk, Error=hyper::Error>`.
@@ -327,7 +331,7 @@ impl Body {
Async::Ready(None) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
},
Kind::Wrapped(ref mut s) => s.poll(),
Kind::Wrapped(ref mut s) => s.poll().map_err(::Error::new_body),
Kind::Once(ref mut val) => Ok(Async::Ready(val.take())),
Kind::Empty => Ok(Async::Ready(None)),
}

View File

@@ -20,7 +20,7 @@ use super::{EncodedBuf, Encoder, Decoder};
/// The connection will determine when a message begins and ends as well as
/// determine if this connection can be kept alive after the message,
/// or if it is complete.
pub struct Conn<I, B, T> {
pub(crate) struct Conn<I, B, T> {
io: Buffered<I, EncodedBuf<Cursor<B>>>,
state: State,
_marker: PhantomData<T>
@@ -146,7 +146,8 @@ where I: AsyncRead + AsyncWrite,
_ => {
error!("unimplemented HTTP Version = {:?}", version);
self.state.close_read();
return Err(::Error::Version);
//TODO: replace this with a more descriptive error
return Err(::Error::new_version());
}
};
self.state.version = version;
@@ -245,7 +246,7 @@ where I: AsyncRead + AsyncWrite,
if self.is_mid_message() {
self.maybe_park_read();
} else {
self.require_empty_read()?;
self.require_empty_read().map_err(::Error::new_io)?;
}
Ok(())
}

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::<(), ()>(())

View File

@@ -108,7 +108,7 @@ where
}
}
pub fn parse<S: Http1Transaction>(&mut self) -> Poll<MessageHead<S::Incoming>, ::Error> {
pub(super) fn parse<S: Http1Transaction>(&mut self) -> Poll<MessageHead<S::Incoming>, ::Error> {
loop {
match try!(S::parse(&mut self.read_buf)) {
Some((head, len)) => {
@@ -118,14 +118,14 @@ where
None => {
if self.read_buf.capacity() >= self.max_buf_size {
debug!("max_buf_size ({}) reached, closing", self.max_buf_size);
return Err(::Error::TooLarge);
return Err(::Error::new_too_large());
}
},
}
match try_ready!(self.read_from_io()) {
match try_ready!(self.read_from_io().map_err(::Error::new_io)) {
0 => {
trace!("parse eof");
return Err(::Error::Incomplete);
return Err(::Error::new_incomplete());
}
_ => {},
}

View File

@@ -1,11 +1,11 @@
pub use self::conn::Conn;
pub(crate) use self::conn::Conn;
pub use self::decode::Decoder;
pub use self::encode::{EncodedBuf, Encoder};
mod conn;
mod date;
mod decode;
pub mod dispatch;
pub(crate) mod dispatch;
mod encode;
mod io;
pub mod role;

View File

@@ -40,7 +40,7 @@ where
let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS];
trace!("Request.parse([Header; {}], [u8; {}])", headers.len(), buf.len());
let mut req = httparse::Request::new(&mut headers);
match try!(req.parse(&buf)) {
match req.parse(&buf)? {
httparse::Status::Complete(len) => {
trace!("Request.parse Complete({})", len);
let method = Method::from_bytes(req.method.unwrap().as_bytes())?;
@@ -104,18 +104,18 @@ where
// mal-formed. A server should respond with 400 Bad Request.
if head.version == Version::HTTP_10 {
debug!("HTTP/1.0 cannot have Transfer-Encoding header");
Err(::Error::Header)
Err(::Error::new_header())
} else if headers::transfer_encoding_is_chunked(&head.headers) {
Ok(Decode::Normal(Decoder::chunked()))
} else {
debug!("request with transfer-encoding header, but not chunked, bad request");
Err(::Error::Header)
Err(::Error::new_header())
}
} else if let Some(len) = headers::content_length_parse(&head.headers) {
Ok(Decode::Normal(Decoder::length(len)))
} else if head.headers.contains_key(CONTENT_LENGTH) {
debug!("illegal Content-Length header");
Err(::Error::Header)
Err(::Error::new_header())
} else {
Ok(Decode::Normal(Decoder::length(0)))
}
@@ -146,7 +146,8 @@ where
head = MessageHead::default();
head.subject = StatusCode::INTERNAL_SERVER_ERROR;
headers::content_length_zero(&mut head.headers);
Err(::Error::Status)
//TODO: change this to a more descriptive error than just a parse error
Err(::Error::new_status())
} else {
Ok(Server::set_length(&mut head, body, method.as_ref()))
};
@@ -184,14 +185,15 @@ where
}
fn on_error(err: &::Error) -> Option<MessageHead<Self::Outgoing>> {
let status = match err {
&::Error::Method |
&::Error::Version |
&::Error::Header /*|
&::Error::Uri(_)*/ => {
use ::error::{Kind, Parse};
let status = match *err.kind() {
Kind::Parse(Parse::Method) |
Kind::Parse(Parse::Version) |
Kind::Parse(Parse::Header) |
Kind::Parse(Parse::Uri) => {
StatusCode::BAD_REQUEST
},
&::Error::TooLarge => {
Kind::Parse(Parse::TooLarge) => {
StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE
}
_ => return None,
@@ -271,7 +273,7 @@ where
match try!(res.parse(bytes)) {
httparse::Status::Complete(len) => {
trace!("Response.parse Complete({})", len);
let status = try!(StatusCode::from_u16(res.code.unwrap()).map_err(|_| ::Error::Status));
let status = StatusCode::from_u16(res.code.unwrap())?;
let version = if res.version.unwrap() == 1 {
Version::HTTP_11
} else {
@@ -343,7 +345,7 @@ where
// mal-formed. A server should respond with 400 Bad Request.
if inc.version == Version::HTTP_10 {
debug!("HTTP/1.0 cannot have Transfer-Encoding header");
Err(::Error::Header)
Err(::Error::new_header())
} else if headers::transfer_encoding_is_chunked(&inc.headers) {
Ok(Decode::Normal(Decoder::chunked()))
} else {
@@ -354,7 +356,7 @@ where
Ok(Decode::Normal(Decoder::length(len)))
} else if inc.headers.contains_key(CONTENT_LENGTH) {
debug!("illegal Content-Length header");
Err(::Error::Header)
Err(::Error::new_header())
} else {
trace!("neither Transfer-Encoding nor Content-Length");
Ok(Decode::Normal(Decoder::eof()))
@@ -577,12 +579,13 @@ impl OnUpgrade for NoUpgrades {
*head = MessageHead::default();
head.subject = ::StatusCode::INTERNAL_SERVER_ERROR;
headers::content_length_zero(&mut head.headers);
Err(::Error::Status)
//TODO: replace with more descriptive error
return Err(::Error::new_status());
}
fn on_decode_upgrade() -> ::Result<Decoder> {
debug!("received 101 upgrade response, not supported");
return Err(::Error::Upgrade);
return Err(::Error::new_upgrade());
}
}

View File

@@ -6,7 +6,7 @@ use headers;
pub use self::body::Body;
pub use self::chunk::Chunk;
pub use self::h1::{dispatch, Conn};
pub(crate) use self::h1::{dispatch, Conn};
pub mod body;
mod chunk;
@@ -60,14 +60,14 @@ pub fn expecting_continue(version: Version, headers: &HeaderMap) -> bool {
version == Version::HTTP_11 && headers::expect_continue(headers)
}
pub type ServerTransaction = h1::role::Server<h1::role::YesUpgrades>;
pub(crate) type ServerTransaction = h1::role::Server<h1::role::YesUpgrades>;
//pub type ServerTransaction = h1::role::Server<h1::role::NoUpgrades>;
//pub type ServerUpgradeTransaction = h1::role::Server<h1::role::YesUpgrades>;
pub type ClientTransaction = h1::role::Client<h1::role::NoUpgrades>;
pub type ClientUpgradeTransaction = h1::role::Client<h1::role::YesUpgrades>;
pub(crate) type ClientTransaction = h1::role::Client<h1::role::NoUpgrades>;
pub(crate) type ClientUpgradeTransaction = h1::role::Client<h1::role::YesUpgrades>;
pub trait Http1Transaction {
pub(crate) trait Http1Transaction {
type Incoming;
type Outgoing: Default;
fn parse(bytes: &mut BytesMut) -> ParseResult<Self::Incoming>;
@@ -84,7 +84,7 @@ pub trait Http1Transaction {
fn should_read_first() -> bool;
}
pub type ParseResult<T> = ::Result<Option<(MessageHead<T>, usize)>>;
pub(crate) type ParseResult<T> = Result<Option<(MessageHead<T>, usize)>, ::error::Parse>;
#[derive(Debug)]
pub enum BodyLength {