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:
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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::<(), ()>(())
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user