feat(http2): check Error::source() for an HTTP2 error code to send in reset
This commit is contained in:
67
src/error.rs
67
src/error.rs
@@ -5,6 +5,7 @@ use std::io;
|
||||
|
||||
use httparse;
|
||||
use http;
|
||||
use h2;
|
||||
|
||||
/// Result type often returned from methods that can have hyper `Error`s.
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
@@ -137,10 +138,8 @@ impl Error {
|
||||
self.inner.kind == Kind::Connect
|
||||
}
|
||||
|
||||
/// Returns the error's cause.
|
||||
///
|
||||
/// This is identical to `Error::cause` except that it provides extra
|
||||
/// bounds required to be able to downcast the error.
|
||||
#[doc(hidden)]
|
||||
#[cfg_attr(error_source, deprecated(note = "use Error::source instead"))]
|
||||
pub fn cause2(&self) -> Option<&(StdError + 'static + Sync + Send)> {
|
||||
self.inner.cause.as_ref().map(|e| &**e)
|
||||
}
|
||||
@@ -163,6 +162,45 @@ impl Error {
|
||||
&self.inner.kind
|
||||
}
|
||||
|
||||
#[cfg(not(error_source))]
|
||||
pub(crate) fn h2_reason(&self) -> h2::Reason {
|
||||
// Since we don't have access to `Error::source`, we can only
|
||||
// look so far...
|
||||
let mut cause = self.cause2();
|
||||
while let Some(err) = cause {
|
||||
if let Some(h2_err) = err.downcast_ref::<h2::Error>() {
|
||||
return h2_err
|
||||
.reason()
|
||||
.unwrap_or(h2::Reason::INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
cause = err
|
||||
.downcast_ref::<Error>()
|
||||
.and_then(Error::cause2);
|
||||
}
|
||||
|
||||
// else
|
||||
h2::Reason::INTERNAL_ERROR
|
||||
}
|
||||
|
||||
#[cfg(error_source)]
|
||||
pub(crate) fn h2_reason(&self) -> h2::Reason {
|
||||
// Find an h2::Reason somewhere in the cause stack, if it exists,
|
||||
// otherwise assume an INTERNAL_ERROR.
|
||||
let mut cause = self.source();
|
||||
while let Some(err) = cause {
|
||||
if let Some(h2_err) = err.downcast_ref::<h2::Error>() {
|
||||
return h2_err
|
||||
.reason()
|
||||
.unwrap_or(h2::Reason::INTERNAL_ERROR);
|
||||
}
|
||||
cause = err.source();
|
||||
}
|
||||
|
||||
// else
|
||||
h2::Reason::INTERNAL_ERROR
|
||||
}
|
||||
|
||||
pub(crate) fn new_canceled<E: Into<Cause>>(cause: Option<E>) -> Error {
|
||||
Error::new(Kind::Canceled, cause.map(Into::into))
|
||||
}
|
||||
@@ -400,4 +438,25 @@ impl AssertSendSync for Error {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn h2_reason_unknown() {
|
||||
let closed = Error::new_closed();
|
||||
assert_eq!(closed.h2_reason(), h2::Reason::INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn h2_reason_one_level() {
|
||||
let body_err = Error::new_user_body(h2::Error::from(h2::Reason::ENHANCE_YOUR_CALM));
|
||||
assert_eq!(body_err.h2_reason(), h2::Reason::ENHANCE_YOUR_CALM);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn h2_reason_nested() {
|
||||
let recvd = Error::new_h2(h2::Error::from(h2::Reason::HTTP_1_1_REQUIRED));
|
||||
// Suppose a user were proxying the received error
|
||||
let svc_err = Error::new_user_service(recvd);
|
||||
assert_eq!(svc_err.h2_reason(), h2::Reason::HTTP_1_1_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use bytes::Buf;
|
||||
use futures::{Async, Future, Poll};
|
||||
use h2::{Reason, SendStream};
|
||||
use h2::{SendStream};
|
||||
use http::header::{
|
||||
HeaderName, CONNECTION, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, TE, TRAILER,
|
||||
TRANSFER_ENCODING, UPGRADE,
|
||||
@@ -91,10 +91,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn on_err(&mut self, err: S::Error) -> ::Error {
|
||||
fn on_user_err(&mut self, err: S::Error) -> ::Error {
|
||||
let err = ::Error::new_user_body(err);
|
||||
trace!("send body user stream error: {}", err);
|
||||
self.body_tx.send_reset(Reason::INTERNAL_ERROR);
|
||||
debug!("send body user stream error: {}", err);
|
||||
self.body_tx.send_reset(err.h2_reason());
|
||||
err
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
match try_ready!(self.stream.poll_data().map_err(|e| self.on_err(e))) {
|
||||
match try_ready!(self.stream.poll_data().map_err(|e| self.on_user_err(e))) {
|
||||
Some(chunk) => {
|
||||
let is_eos = self.stream.is_end_stream();
|
||||
trace!(
|
||||
@@ -175,7 +175,7 @@ where
|
||||
return Err(::Error::new_body_write(::h2::Error::from(reason)));
|
||||
}
|
||||
|
||||
match try_ready!(self.stream.poll_trailers().map_err(|e| self.on_err(e))) {
|
||||
match try_ready!(self.stream.poll_trailers().map_err(|e| self.on_user_err(e))) {
|
||||
Some(trailers) => {
|
||||
self.body_tx
|
||||
.send_trailers(trailers)
|
||||
|
||||
@@ -211,7 +211,7 @@ where
|
||||
Err(e) => {
|
||||
let err = ::Error::new_user_service(e);
|
||||
warn!("http2 service errored: {}", err);
|
||||
self.reply.send_reset(Reason::INTERNAL_ERROR);
|
||||
self.reply.send_reset(err.h2_reason());
|
||||
return Err(err);
|
||||
},
|
||||
};
|
||||
@@ -232,7 +232,7 @@ where
|
||||
match self.reply.send_response(res, $eos) {
|
||||
Ok(tx) => tx,
|
||||
Err(e) => {
|
||||
trace!("send response error: {}", e);
|
||||
debug!("send response error: {}", e);
|
||||
self.reply.send_reset(Reason::INTERNAL_ERROR);
|
||||
return Err(::Error::new_h2(e));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user