feat(server): change http1_half_close option default to disabled

Detecting a read hangup is a useful way to determine that a connection
has closed. It's also possible that a client shuts down its read half
without closing the connection, but this is rarer. Thus, by default,
hyper will now assume a read EOF means the connection has closed.

BREAKING CHANGE: The server's behavior will now by default close
  connections when receiving a read EOF. To allow for clients to close
  the read half, call `http1_half_close(true)` when configuring a
  server.
This commit is contained in:
Sean McArthur
2019-10-18 13:08:06 -07:00
parent 8e7ebd80cd
commit 7e31fd88a8
5 changed files with 67 additions and 45 deletions

View File

@@ -38,7 +38,7 @@ where I: AsyncRead + AsyncWrite + Unpin,
Conn {
io: Buffered::new(io),
state: State {
allow_half_close: true,
allow_half_close: false,
cached_headers: None,
error: None,
keep_alive: KA::Busy,
@@ -76,8 +76,8 @@ where I: AsyncRead + AsyncWrite + Unpin,
self.state.title_case_headers = true;
}
pub(crate) fn set_disable_half_close(&mut self) {
self.state.allow_half_close = false;
pub(crate) fn set_allow_half_close(&mut self) {
self.state.allow_half_close = true;
}
pub fn into_inner(self) -> (I, Bytes) {
@@ -172,7 +172,7 @@ where I: AsyncRead + AsyncWrite + Unpin,
// message should be reported as an error. If not, it is just
// the connection closing gracefully.
let must_error = self.should_error_on_eof();
self.state.close_read();
self.close_read();
self.io.consume_leading_lines();
let was_mid_parse = e.is_parse() || !self.io.read_buf().is_empty();
if was_mid_parse || must_error {
@@ -185,6 +185,7 @@ where I: AsyncRead + AsyncWrite + Unpin,
}
} else {
debug!("read eof");
self.close_write();
Poll::Ready(None)
}
}
@@ -204,7 +205,7 @@ where I: AsyncRead + AsyncWrite + Unpin,
None
})
} else if slice.is_empty() {
error!("decode stream unexpectedly ended");
error!("incoming body unexpectedly ended");
// This should be unreachable, since all 3 decoders
// either set eof=true or return an Err when reading
// an empty slice...
@@ -216,7 +217,7 @@ where I: AsyncRead + AsyncWrite + Unpin,
},
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => {
debug!("decode stream error: {}", e);
debug!("incoming body decode error: {}", e);
(Reading::Closed, Poll::Ready(Some(Err(e))))
},
}
@@ -294,6 +295,10 @@ where I: AsyncRead + AsyncWrite + Unpin,
return Poll::Pending;
}
if self.state.is_read_closed() {
return Poll::Ready(Err(crate::Error::new_incomplete()));
}
let num_read = ready!(self.force_io_read(cx)).map_err(crate::Error::new_io)?;
if num_read == 0 {
@@ -306,6 +311,8 @@ where I: AsyncRead + AsyncWrite + Unpin,
}
fn force_io_read(&mut self, cx: &mut task::Context<'_>) -> Poll<io::Result<usize>> {
debug_assert!(!self.state.is_read_closed());
let result = ready!(self.io.poll_read_from_io(cx));
Poll::Ready(result.map_err(|e| {
trace!("force_io_read; io error = {:?}", e);
@@ -619,8 +626,10 @@ where I: AsyncRead + AsyncWrite + Unpin,
pub fn disable_keep_alive(&mut self) {
if self.state.is_idle() {
self.state.close_read();
trace!("disable_keep_alive; closing idle connection");
self.state.close();
} else {
trace!("disable_keep_alive; in-progress connection");
self.state.disable_keep_alive();
}
}

View File

@@ -60,7 +60,10 @@ where
}
pub fn disable_keep_alive(&mut self) {
self.conn.disable_keep_alive()
self.conn.disable_keep_alive();
if self.conn.is_write_closed() {
self.close();
}
}
pub fn into_inner(self) -> (I, Bytes, D) {
@@ -233,10 +236,17 @@ where
// if here, the dispatcher gave the user the error
// somewhere else. we still need to shutdown, but
// not as a second error.
self.close();
Poll::Ready(Ok(()))
},
None => {
// read eof, conn will start to shutdown automatically
// read eof, the write side will have been closed too unless
// allow_read_close was set to true, in which case just do
// nothing...
debug_assert!(self.conn.is_read_closed());
if self.conn.is_write_closed() {
self.close();
}
Poll::Ready(Ok(()))
}
}