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

@@ -25,7 +25,7 @@ use super::{HyperService, Request, Response, Service};
pub struct Connection<I, S>
where
S: HyperService,
S::ResponseBody: Entity<Error=::Error>,
S::ResponseBody: Entity,
{
pub(super) conn: proto::dispatch::Dispatcher<
proto::dispatch::Server<S>,
@@ -59,9 +59,11 @@ pub struct Parts<T> {
// ===== impl Connection =====
impl<I, B, S> Connection<I, S>
where S: Service<Request = Request<Body>, Response = Response<B>, Error = ::Error> + 'static,
I: AsyncRead + AsyncWrite + 'static,
B: Entity<Error=::Error> + 'static,
where
S: Service<Request=Request<Body>, Response=Response<B>> + 'static,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
I: AsyncRead + AsyncWrite + 'static,
B: Entity + 'static,
{
/// Disables keep-alive for this connection.
pub fn disable_keep_alive(&mut self) {
@@ -96,9 +98,11 @@ where S: Service<Request = Request<Body>, Response = Response<B>, Error = ::Erro
}
impl<I, B, S> Future for Connection<I, S>
where S: Service<Request = Request<Body>, Response = Response<B>, Error = ::Error> + 'static,
I: AsyncRead + AsyncWrite + 'static,
B: Entity<Error=::Error> + 'static,
where
S: Service<Request=Request<Body>, Response=Response<B>> + 'static,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
I: AsyncRead + AsyncWrite + 'static,
B: Entity + 'static,
{
type Item = ();
type Error = ::Error;
@@ -111,7 +115,7 @@ where S: Service<Request = Request<Body>, Response = Response<B>, Error = ::Erro
impl<I, S> fmt::Debug for Connection<I, S>
where
S: HyperService,
S::ResponseBody: Entity<Error=::Error>,
S::ResponseBody: Entity,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Connection")

View File

@@ -51,7 +51,7 @@ pub struct Http<B = ::Chunk> {
/// address and then serving TCP connections accepted with the service provided.
pub struct Server<S, B>
where
B: Entity<Error=::Error>,
B: Entity,
{
protocol: Http<B::Data>,
new_service: S,
@@ -165,12 +165,14 @@ impl<B: AsRef<[u8]> + 'static> Http<B> {
/// The returned `Server` contains one method, `run`, which is used to
/// actually run the server.
pub fn bind<S, Bd>(&self, addr: &SocketAddr, new_service: S) -> ::Result<Server<S, Bd>>
where S: NewService<Request = Request<Body>, Response = Response<Bd>, Error = ::Error> + 'static,
Bd: Entity<Data=B, Error=::Error>,
where
S: NewService<Request=Request<Body>, Response=Response<Bd>> + 'static,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
Bd: Entity<Data=B>,
{
let handle = Handle::current();
let std_listener = StdTcpListener::bind(addr)?;
let listener = try!(TcpListener::from_std(std_listener, &handle));
let std_listener = StdTcpListener::bind(addr).map_err(::Error::new_listen)?;
let listener = TcpListener::from_std(std_listener, &handle).map_err(::Error::new_listen)?;
Ok(Server {
new_service: new_service,
@@ -188,13 +190,15 @@ impl<B: AsRef<[u8]> + 'static> Http<B> {
/// `new_service` object provided as well, creating a new service per
/// connection.
pub fn serve_addr<S, Bd>(&self, addr: &SocketAddr, new_service: S) -> ::Result<Serve<AddrIncoming, S>>
where S: NewService<Request = Request<Body>, Response = Response<Bd>, Error = ::Error>,
Bd: Entity<Data=B, Error=::Error>,
where
S: NewService<Request=Request<Body>, Response=Response<Bd>>,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
Bd: Entity<Data=B>,
{
let handle = Handle::current();
let std_listener = StdTcpListener::bind(addr)?;
let listener = TcpListener::from_std(std_listener, &handle)?;
let mut incoming = AddrIncoming::new(listener, handle.clone(), self.sleep_on_errors)?;
let std_listener = StdTcpListener::bind(addr).map_err(::Error::new_listen)?;
let listener = TcpListener::from_std(std_listener, &handle).map_err(::Error::new_listen)?;
let mut incoming = AddrIncoming::new(listener, handle.clone(), self.sleep_on_errors).map_err(::Error::new_listen)?;
if self.keep_alive {
incoming.set_keepalive(Some(Duration::from_secs(90)));
}
@@ -210,12 +214,15 @@ impl<B: AsRef<[u8]> + 'static> Http<B> {
/// `new_service` object provided as well, creating a new service per
/// connection.
pub fn serve_addr_handle<S, Bd>(&self, addr: &SocketAddr, handle: &Handle, new_service: S) -> ::Result<Serve<AddrIncoming, S>>
where S: NewService<Request = Request<Body>, Response = Response<Bd>, Error = ::Error>,
Bd: Entity<Data=B, Error=::Error>,
where
S: NewService<Request = Request<Body>, Response = Response<Bd>>,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
Bd: Entity<Data=B>,
{
let std_listener = StdTcpListener::bind(addr)?;
let listener = TcpListener::from_std(std_listener, &handle)?;
let mut incoming = AddrIncoming::new(listener, handle.clone(), self.sleep_on_errors)?;
let std_listener = StdTcpListener::bind(addr).map_err(::Error::new_listen)?;
let listener = TcpListener::from_std(std_listener, &handle).map_err(::Error::new_listen)?;
let mut incoming = AddrIncoming::new(listener, handle.clone(), self.sleep_on_errors).map_err(::Error::new_listen)?;
if self.keep_alive {
incoming.set_keepalive(Some(Duration::from_secs(90)));
}
@@ -226,10 +233,12 @@ impl<B: AsRef<[u8]> + 'static> Http<B> {
///
/// This method allows the ability to share a `Core` with multiple servers.
pub fn serve_incoming<I, S, Bd>(&self, incoming: I, new_service: S) -> Serve<I, S>
where I: Stream<Error=::std::io::Error>,
I::Item: AsyncRead + AsyncWrite,
S: NewService<Request = Request<Body>, Response = Response<Bd>, Error = ::Error>,
Bd: Entity<Data=B, Error=::Error>,
where
I: Stream<Error=::std::io::Error>,
I::Item: AsyncRead + AsyncWrite,
S: NewService<Request = Request<Body>, Response = Response<Bd>>,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
Bd: Entity<Data=B>,
{
Serve {
incoming: incoming,
@@ -279,9 +288,11 @@ impl<B: AsRef<[u8]> + 'static> Http<B> {
/// # fn main() {}
/// ```
pub fn serve_connection<S, I, Bd>(&self, io: I, service: S) -> Connection<I, S>
where S: Service<Request = Request<Body>, Response = Response<Bd>, Error = ::Error>,
Bd: Entity<Error=::Error>,
I: AsyncRead + AsyncWrite,
where
S: Service<Request = Request<Body>, Response = Response<Bd>>,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
Bd: Entity,
I: AsyncRead + AsyncWrite,
{
let mut conn = proto::Conn::new(io);
if !self.keep_alive {
@@ -341,15 +352,19 @@ impl Future for Run {
impl<S, B> Server<S, B>
where S: NewService<Request = Request<Body>, Response = Response<B>, Error = ::Error> + Send + 'static,
<S as NewService>::Instance: Send,
<<S as NewService>::Instance as Service>::Future: Send,
B: Entity<Error=::Error> + Send + 'static,
B::Data: Send,
where
S: NewService<Request = Request<Body>, Response = Response<B>> + Send + 'static,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
<S as NewService>::Instance: Send,
<<S as NewService>::Instance as Service>::Future: Send,
B: Entity + Send + 'static,
B::Data: Send,
{
/// Returns the local address that this server is bound to.
pub fn local_addr(&self) -> ::Result<SocketAddr> {
Ok(try!(self.listener.local_addr()))
//TODO: this shouldn't return an error at all, but should get the
//local_addr at construction
self.listener.local_addr().map_err(::Error::new_io)
}
/// Configure the amount of time this server will wait for a "graceful
@@ -393,7 +408,7 @@ impl<S, B> Server<S, B>
let mut incoming = match AddrIncoming::new(listener, handle.clone(), protocol.sleep_on_errors) {
Ok(incoming) => incoming,
Err(err) => return Run(Box::new(future::err(err.into()))),
Err(err) => return Run(Box::new(future::err(::Error::new_listen(err)))),
};
if protocol.keep_alive {
@@ -439,7 +454,7 @@ impl<S, B> Server<S, B>
let main_execution = shutdown_signal.select(srv).then(move |result| {
match result {
Ok(((), _incoming)) => {},
Err((e, _other)) => return future::Either::A(future::err(e.into()))
Err((e, _other)) => return future::Either::A(future::err(::Error::new_accept(e))),
}
// Ok we've stopped accepting new connections at this point, but we want
@@ -454,7 +469,8 @@ impl<S, B> Server<S, B>
future::Either::B(wait.select(timeout).then(|result| {
match result {
Ok(_) => Ok(()),
Err((e, _)) => Err(e.into())
//TODO: error variant should be "timed out waiting for graceful shutdown"
Err((e, _)) => Err(::Error::new_io(e))
}
}))
});
@@ -463,11 +479,10 @@ impl<S, B> Server<S, B>
}
}
impl<S: fmt::Debug, B: Entity<Error=::Error>> fmt::Debug for Server<S, B>
impl<S: fmt::Debug, B: Entity> fmt::Debug for Server<S, B>
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Server")
.field("reactor", &"...")
.field("listener", &self.listener)
.field("new_service", &self.new_service)
.field("protocol", &self.protocol)
@@ -499,15 +514,16 @@ impl<I, S, B> Stream for Serve<I, S>
where
I: Stream<Error=io::Error>,
I::Item: AsyncRead + AsyncWrite,
S: NewService<Request=Request<Body>, Response=Response<B>, Error=::Error>,
B: Entity<Error=::Error>,
S: NewService<Request=Request<Body>, Response=Response<B>>,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
B: Entity,
{
type Item = Connection<I::Item, S::Instance>;
type Error = ::Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(io) = try_ready!(self.incoming.poll()) {
let service = self.new_service.new_service()?;
if let Some(io) = try_ready!(self.incoming.poll().map_err(::Error::new_accept)) {
let service = self.new_service.new_service().map_err(::Error::new_user_new_service)?;
Ok(Async::Ready(Some(self.protocol.serve_connection(io, service))))
} else {
Ok(Async::Ready(None))
@@ -579,6 +595,12 @@ impl AddrIncoming {
fn set_keepalive(&mut self, dur: Option<Duration>) {
self.keep_alive_timeout = dur;
}
/*
fn set_sleep_on_errors(&mut self, val: bool) {
self.sleep_on_errors = val;
}
*/
}
impl Stream for AddrIncoming {
@@ -802,9 +824,9 @@ mod hyper_service {
S: Service<
Request=Request<Body>,
Response=Response<B>,
Error=::Error,
>,
B: Entity<Error=::Error>,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
B: Entity,
{}
impl<S, B> HyperService for S
@@ -812,10 +834,10 @@ mod hyper_service {
S: Service<
Request=Request<Body>,
Response=Response<B>,
Error=::Error,
>,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
S: Sealed,
B: Entity<Error=::Error>,
B: Entity,
{
type ResponseBody = B;
type Sealed = Opaque;