Refactor errors (#46)

This patch does a bunch of refactoring, mostly around error types, but it also
paves the way to allow `Codec` to be used standalone.

* `Codec` (and `FramedRead` / `FramedWrite`) is broken out into a codec module.
* An h2-codec crate is created that re-exports the frame and codec modules.
* New error types are introduced in the internals:
  * `RecvError` represents errors caused by trying to receive a frame.
  * `SendError` represents errors caused by trying to send a frame.
  * `UserError` is an enum of potential errors caused by invalid usage
    by the user of the lib.
  * `ProtoError` is either a `Reason` or an `io::Error`. However it doesn't
    specify connection or stream level.
  * `h2::Error` is an opaque error type and is the only error type exposed
    by the public API (used to be `ConnectionError`).

There are misc code changes to enable this as well. The biggest is a new "sink"
API for `Codec`. It provides buffer which queues up a frame followed by flush
which writes everything that is queued. This departs from the `Sink` trait in
order to provide more accurate error values. For example, buffer can never fail
(but it will panic if `poll_ready` is not called first).
This commit is contained in:
Carl Lerche
2017-09-02 11:12:50 -07:00
committed by GitHub
parent 6fd9674759
commit c122e97127
37 changed files with 1043 additions and 1027 deletions

View File

@@ -1,263 +1,101 @@
use http;
use codec::{SendError, UserError};
use proto;
use std::{error, fmt, io};
pub use frame::Reason;
/// The error type for HTTP/2 operations
///
/// XXX does this sufficiently destinguish stream-level errors from connection-level errors?
#[derive(Debug)]
pub enum ConnectionError {
pub struct Error {
kind: Kind,
}
#[derive(Debug)]
enum Kind {
/// An error caused by an action taken by the remote peer.
///
/// This is either an error received by the peer or caused by an invalid
/// action taken by the peer (i.e. a protocol error).
Proto(Reason),
/// An `io::Error` occurred while trying to read or write.
Io(io::Error),
/// An error resulting from an invalid action taken by the user of this
/// library.
User(User),
User(UserError),
// TODO: reserve additional variants
/// An `io::Error` occurred while trying to read or write.
Io(io::Error),
}
#[derive(Debug)]
pub struct StreamError(Reason);
// ===== impl Error =====
impl StreamError {
pub fn new(r: Reason) -> StreamError {
StreamError(r)
}
impl From<proto::Error> for Error {
fn from(src: proto::Error) -> Error {
use proto::Error::*;
pub fn reason(&self) -> Reason {
self.0
}
}
impl From<Reason> for StreamError {
fn from(r: Reason) -> Self {
StreamError(r)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Reason {
NoError,
ProtocolError,
InternalError,
FlowControlError,
SettingsTimeout,
StreamClosed,
FrameSizeError,
RefusedStream,
Cancel,
CompressionError,
ConnectError,
EnhanceYourCalm,
InadequateSecurity,
Http11Required,
Other(u32),
// TODO: reserve additional variants
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum User {
/// The specified stream ID is invalid.
///
/// For example, using a stream ID reserved for a push promise from the
/// client or using a non-zero stream ID for settings.
InvalidStreamId,
/// The stream ID is no longer accepting frames.
InactiveStreamId,
/// The stream is not currently expecting a frame of this type.
UnexpectedFrameType,
/// The connection or stream does not have a sufficient flow control window to
/// transmit a Data frame to the remote.
FlowControlViolation,
/// The payload size is too big
PayloadTooBig,
/// The connection state is corrupt and the connection should be dropped.
Corrupt,
/// The stream state has been reset.
StreamReset(Reason),
/// The application attempted to initiate too many streams to remote.
Rejected,
// TODO: reserve additional variants
}
macro_rules! reason_desc {
($reason:expr) => (reason_desc!($reason, ""));
($reason:expr, $prefix:expr) => ({
use self::Reason::*;
match $reason {
NoError => concat!($prefix, "not a result of an error"),
ProtocolError => concat!($prefix, "unspecific protocol error detected"),
InternalError => concat!($prefix, "unexpected internal error encountered"),
FlowControlError => concat!($prefix, "flow-control protocol violated"),
SettingsTimeout => concat!($prefix, "settings ACK not received in timely manner"),
StreamClosed => concat!($prefix, "received frame when stream half-closed"),
FrameSizeError => concat!($prefix, "frame sent with invalid size"),
RefusedStream => concat!($prefix, "refused stream before processing any application logic"),
Cancel => concat!($prefix, "stream no longer needed"),
CompressionError => concat!($prefix, "unable to maintain the header compression context"),
ConnectError => concat!($prefix, "connection established in response to a CONNECT request was reset or abnormally closed"),
EnhanceYourCalm => concat!($prefix, "detected excessive load generating behavior"),
InadequateSecurity => concat!($prefix, "security properties do not meet minimum requirements"),
Http11Required => concat!($prefix, "endpoint requires HTTP/1.1"),
Other(_) => concat!($prefix, "other reason (ain't no tellin')"),
Error {
kind: match src {
Proto(reason) => Kind::Proto(reason),
Io(e) => Kind::Io(e),
},
}
});
}
}
macro_rules! user_desc {
($reason:expr) => (user_desc!($reason, ""));
($reason:expr, $prefix:expr) => ({
use self::User::*;
impl From<io::Error> for Error {
fn from(src: io::Error) -> Error {
Error { kind: Kind::Io(src) }
}
}
match $reason {
InvalidStreamId => concat!($prefix, "invalid stream ID"),
InactiveStreamId => concat!($prefix, "inactive stream ID"),
UnexpectedFrameType => concat!($prefix, "unexpected frame type"),
FlowControlViolation => concat!($prefix, "flow control violation"),
StreamReset(_) => concat!($prefix, "frame sent on reset stream"),
Corrupt => concat!($prefix, "connection state corrupt"),
Rejected => concat!($prefix, "stream would exceed remote max concurrency"),
PayloadTooBig => concat!($prefix, "payload too big"),
impl From<Reason> for Error {
fn from(src: Reason) -> Error {
Error { kind: Kind::Proto(src) }
}
}
impl From<SendError> for Error {
fn from(src: SendError) -> Error {
match src {
SendError::User(e) => e.into(),
SendError::Io(e) => e.into(),
}
});
}
// ===== impl ConnectionError =====
impl From<io::Error> for ConnectionError {
fn from(src: io::Error) -> ConnectionError {
ConnectionError::Io(src)
}
}
impl From<Reason> for ConnectionError {
fn from(src: Reason) -> ConnectionError {
ConnectionError::Proto(src)
impl From<UserError> for Error {
fn from(src: UserError) -> Error {
Error { kind: Kind::User(src) }
}
}
impl From<User> for ConnectionError {
fn from(src: User) -> ConnectionError {
ConnectionError::User(src)
}
}
impl From<ConnectionError> for io::Error {
fn from(src: ConnectionError) -> io::Error {
io::Error::new(io::ErrorKind::Other, src)
}
}
impl From<http::Error> for ConnectionError {
fn from(_: http::Error) -> Self {
// TODO: Should this always be a protocol error?
Reason::ProtocolError.into()
}
}
impl fmt::Display for ConnectionError {
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use self::ConnectionError::*;
use self::Kind::*;
match *self {
Proto(reason) => write!(fmt, "protocol error: {}", reason),
match self.kind {
Proto(ref reason) => write!(fmt, "protocol error: {}", reason),
User(ref e) => write!(fmt, "user error: {}", e),
Io(ref e) => fmt::Display::fmt(e, fmt),
User(e) => write!(fmt, "user error: {}", e),
}
}
}
impl error::Error for ConnectionError {
impl error::Error for Error {
fn description(&self) -> &str {
use self::ConnectionError::*;
use self::Kind::*;
match *self {
match self.kind {
Io(ref e) => error::Error::description(e),
Proto(reason) => reason_desc!(reason, "protocol error: "),
User(user) => user_desc!(user, "user error: "),
Proto(ref reason) => reason.description(),
User(ref user) => user.description(),
}
}
}
// ===== impl Reason =====
impl Reason {
pub fn description(&self) -> &str {
reason_desc!(*self)
}
}
impl From<u32> for Reason {
fn from(src: u32) -> Reason {
use self::Reason::*;
match src {
0x0 => NoError,
0x1 => ProtocolError,
0x2 => InternalError,
0x3 => FlowControlError,
0x4 => SettingsTimeout,
0x5 => StreamClosed,
0x6 => FrameSizeError,
0x7 => RefusedStream,
0x8 => Cancel,
0x9 => CompressionError,
0xa => ConnectError,
0xb => EnhanceYourCalm,
0xc => InadequateSecurity,
0xd => Http11Required,
_ => Other(src),
}
}
}
impl From<Reason> for u32 {
fn from(src: Reason) -> u32 {
use self::Reason::*;
match src {
NoError => 0x0,
ProtocolError => 0x1,
InternalError => 0x2,
FlowControlError => 0x3,
SettingsTimeout => 0x4,
StreamClosed => 0x5,
FrameSizeError => 0x6,
RefusedStream => 0x7,
Cancel => 0x8,
CompressionError => 0x9,
ConnectError => 0xa,
EnhanceYourCalm => 0xb,
InadequateSecurity => 0xc,
Http11Required => 0xd,
Other(v) => v,
}
}
}
impl fmt::Display for Reason {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.description())
}
}
// ===== impl User =====
/*
impl User {
pub fn description(&self) -> &str {
user_desc!(*self)
@@ -269,3 +107,4 @@ impl fmt::Display for User {
write!(fmt, "{}", self.description())
}
}
*/