refactor(error): improve organization of Error kinds

- Placed all cases of "unexpected bytes" errors into the
  `UnexpectedMessage` variant.
- Placed all cases of "unexpected EOF" errors into the
  `IncompleteMessage` variant. Description is now generic about
  "connection closed before message completed", instead of mentioning
  "request" or "response.
- Added `Error::is_incomplete_message()` accessor to help checking for
  unexpected closures.
- Renamed some variants to be clearer when viewing the `Debug` format.
- Collected all "user" errors into an internal `User` enum, to prevent
  forgetting to update the `is_user()` method.
This commit is contained in:
Sean McArthur
2019-04-25 15:47:38 -07:00
parent 4133181bb2
commit 271bba1667
15 changed files with 213 additions and 211 deletions

View File

@@ -98,7 +98,6 @@ where I: AsyncRead + AsyncWrite,
pub fn can_read_head(&self) -> bool {
match self.state.reading {
//Reading::Init => true,
Reading::Init => {
if T::should_read_first() {
true
@@ -228,13 +227,19 @@ where I: AsyncRead + AsyncWrite,
ret
}
pub fn read_keep_alive(&mut self) -> Result<(), ::Error> {
pub fn wants_read_again(&mut self) -> bool {
let ret = self.state.notify_read;
self.state.notify_read = false;
ret
}
pub fn read_keep_alive(&mut self) -> Poll<(), ::Error> {
debug_assert!(!self.can_read_head() && !self.can_read_body());
if self.is_mid_message() {
self.mid_message_detect_eof().map_err(::Error::new_io)
self.mid_message_detect_eof()
} else {
self.require_empty_read().map_err(::Error::new_io)
self.require_empty_read()
}
}
@@ -245,87 +250,65 @@ where I: AsyncRead + AsyncWrite,
}
}
pub fn wants_read_again(&mut self) -> bool {
let ret = self.state.notify_read;
self.state.notify_read = false;
ret
}
// This will check to make sure the io object read is empty.
//
// This should only be called for Clients wanting to enter the idle
// state.
fn require_empty_read(&mut self) -> io::Result<()> {
fn require_empty_read(&mut self) -> Poll<(), ::Error> {
debug_assert!(!self.can_read_head() && !self.can_read_body());
debug_assert!(!self.is_mid_message());
debug_assert!(T::is_client());
if !self.io.read_buf().is_empty() {
debug!("received an unexpected {} bytes", self.io.read_buf().len());
Err(io::Error::new(io::ErrorKind::InvalidData, "unexpected bytes after message ended"))
} else {
match self.try_io_read()? {
Async::Ready(0) => {
// case handled in try_io_read
Ok(())
},
Async::Ready(n) => {
debug!("received {} bytes on an idle connection", n);
let desc = if self.state.is_idle() {
"unexpected bytes after message ended"
} else {
"unexpected bytes before writing message"
};
Err(io::Error::new(io::ErrorKind::InvalidData, desc))
},
Async::NotReady => {
trace!("read_keep_alive check: ok, nothing to read yet");
Ok(())
},
}
return Err(::Error::new_unexpected_message());
}
let num_read = try_ready!(self.force_io_read().map_err(::Error::new_io));
if num_read == 0 {
let ret = if self.should_error_on_eof() {
trace!("found unexpected EOF on busy connection: {:?}", self.state);
Err(::Error::new_incomplete())
} else {
trace!("found EOF on idle connection, closing");
Ok(Async::Ready(()))
};
// order is important: should_error needs state BEFORE close_read
self.state.close_read();
return ret;
}
debug!("received unexpected {} bytes on an idle connection", num_read);
Err(::Error::new_unexpected_message())
}
fn mid_message_detect_eof(&mut self) -> io::Result<()> {
fn mid_message_detect_eof(&mut self) -> Poll<(), ::Error> {
debug_assert!(!self.can_read_head() && !self.can_read_body());
debug_assert!(self.is_mid_message());
if self.state.allow_half_close || !self.io.read_buf().is_empty() {
Ok(())
return Ok(Async::NotReady);
}
let num_read = try_ready!(self.force_io_read().map_err(::Error::new_io));
if num_read == 0 {
trace!("found unexpected EOF on busy connection: {:?}", self.state);
self.state.close_read();
Err(::Error::new_incomplete())
} else {
self.try_io_read().map(|_| ())
Ok(Async::Ready(()))
}
}
fn try_io_read(&mut self) -> Poll<usize, io::Error> {
match self.io.read_from_io() {
Ok(Async::Ready(0)) => {
trace!("try_io_read; found EOF on connection: {:?}", self.state);
let must_error = !self.state.is_idle();
let ret = if must_error {
let desc = if self.is_mid_message() {
"unexpected EOF waiting for response"
} else {
"unexpected EOF before writing message"
};
Err(io::Error::new(io::ErrorKind::UnexpectedEof, desc))
} else {
Ok(Async::Ready(0))
};
// order is important: must_error needs state BEFORE close_read
self.state.close_read();
ret
},
Ok(Async::Ready(n)) => {
Ok(Async::Ready(n))
},
Ok(Async::NotReady) => {
Ok(Async::NotReady)
},
Err(e) => {
trace!("try_io_read; error = {}", e);
self.state.close();
Err(e)
}
}
fn force_io_read(&mut self) -> Poll<usize, io::Error> {
self.io.read_from_io().map_err(|e| {
trace!("force_io_read; io error = {:?}", e);
self.state.close();
e
})
}

View File

@@ -162,7 +162,6 @@ where
self.conn.close_read();
}
}
}
},
Ok(Async::Ready(None)) => {
@@ -180,7 +179,7 @@ where
// just drop, the body will close automatically
}
} else {
return self.conn.read_keep_alive().map(Async::Ready);
return self.conn.read_keep_alive();
}
}
}
@@ -486,7 +485,10 @@ where
let _ = cb.send(Ok(res));
Ok(())
} else {
Err(::Error::new_mismatched_response())
// Getting here is likely a bug! An error should have happened
// in Conn::require_empty_read() before ever parsing a
// full message!
Err(::Error::new_unexpected_message())
}
},
Err(err) => {
@@ -497,7 +499,7 @@ where
trace!("canceling queued request with connection error: {}", err);
// in this case, the message was never even started, so it's safe to tell
// the user that the request was completely canceled
let _ = cb.send(Err((::Error::new_canceled(Some(err)), Some(req))));
let _ = cb.send(Err((::Error::new_canceled().with(err), Some(req))));
Ok(())
} else {
Err(err)

View File

@@ -31,8 +31,21 @@ pub(crate) trait Http1Transaction {
fn on_error(err: &::Error) -> Option<MessageHead<Self::Outgoing>>;
fn should_error_on_parse_eof() -> bool;
fn should_read_first() -> bool;
fn is_client() -> bool {
!Self::is_server()
}
fn is_server() -> bool {
!Self::is_client()
}
fn should_error_on_parse_eof() -> bool {
Self::is_client()
}
fn should_read_first() -> bool {
Self::is_server()
}
fn update_date() {}
}

View File

@@ -294,7 +294,7 @@ impl Http1Transaction for Server {
if wrote_len {
warn!("unexpected content-length found, canceling");
rewind(dst);
return Err(::Error::new_header());
return Err(::Error::new_user_header());
}
match msg.body {
Some(BodyLength::Known(known_len)) => {
@@ -354,7 +354,7 @@ impl Http1Transaction for Server {
if fold.0 != len {
warn!("multiple Content-Length values found: [{}, {}]", fold.0, len);
rewind(dst);
return Err(::Error::new_header());
return Err(::Error::new_user_header());
}
folded = Some(fold);
} else {
@@ -363,7 +363,7 @@ impl Http1Transaction for Server {
} else {
warn!("illegal Content-Length value: {:?}", value);
rewind(dst);
return Err(::Error::new_header());
return Err(::Error::new_user_header());
}
}
if let Some((len, value)) = folded {
@@ -403,7 +403,7 @@ impl Http1Transaction for Server {
if wrote_len {
warn!("unexpected transfer-encoding found, canceling");
rewind(dst);
return Err(::Error::new_header());
return Err(::Error::new_user_header());
}
// check that we actually can send a chunked body...
if msg.head.version == Version::HTTP_10 || !Server::can_chunked(msg.req_method, msg.head.subject) {
@@ -531,11 +531,7 @@ impl Http1Transaction for Server {
Some(msg)
}
fn should_error_on_parse_eof() -> bool {
false
}
fn should_read_first() -> bool {
fn is_server() -> bool {
true
}
@@ -692,13 +688,9 @@ impl Http1Transaction for Client {
None
}
fn should_error_on_parse_eof() -> bool {
fn is_client() -> bool {
true
}
fn should_read_first() -> bool {
false
}
}
impl Client {

View File

@@ -100,7 +100,7 @@ where
Ok(Async::Ready(Some((req, cb)))) => {
// check that future hasn't been canceled already
if cb.is_canceled() {
trace!("request canceled");
trace!("request callback is canceled");
continue;
}
let (head, body) = req.into_parts();
@@ -159,11 +159,11 @@ where
Ok(Async::NotReady) => return Ok(Async::NotReady),
Ok(Async::Ready(None)) |
Err(_) => {
Ok(Async::Ready(None)) => {
trace!("client::dispatch::Sender dropped");
return Ok(Async::Ready(Dispatched::Shutdown));
}
},
Err(never) => match never {},
}
},
};

View File

@@ -126,7 +126,7 @@ where
match try_ready!(self.body_tx.poll_capacity().map_err(::Error::new_body_write)) {
Some(0) => {}
Some(_) => break,
None => return Err(::Error::new_canceled(None::<::Error>)),
None => return Err(::Error::new_canceled()),
}
}
} else {