Skip to content

Commit 7e31fd8

Browse files
committed
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.
1 parent 8e7ebd8 commit 7e31fd8

File tree

5 files changed

+67
-45
lines changed

5 files changed

+67
-45
lines changed

src/proto/h1/conn.rs

+16-7
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ where I: AsyncRead + AsyncWrite + Unpin,
3838
Conn {
3939
io: Buffered::new(io),
4040
state: State {
41-
allow_half_close: true,
41+
allow_half_close: false,
4242
cached_headers: None,
4343
error: None,
4444
keep_alive: KA::Busy,
@@ -76,8 +76,8 @@ where I: AsyncRead + AsyncWrite + Unpin,
7676
self.state.title_case_headers = true;
7777
}
7878

79-
pub(crate) fn set_disable_half_close(&mut self) {
80-
self.state.allow_half_close = false;
79+
pub(crate) fn set_allow_half_close(&mut self) {
80+
self.state.allow_half_close = true;
8181
}
8282

8383
pub fn into_inner(self) -> (I, Bytes) {
@@ -172,7 +172,7 @@ where I: AsyncRead + AsyncWrite + Unpin,
172172
// message should be reported as an error. If not, it is just
173173
// the connection closing gracefully.
174174
let must_error = self.should_error_on_eof();
175-
self.state.close_read();
175+
self.close_read();
176176
self.io.consume_leading_lines();
177177
let was_mid_parse = e.is_parse() || !self.io.read_buf().is_empty();
178178
if was_mid_parse || must_error {
@@ -185,6 +185,7 @@ where I: AsyncRead + AsyncWrite + Unpin,
185185
}
186186
} else {
187187
debug!("read eof");
188+
self.close_write();
188189
Poll::Ready(None)
189190
}
190191
}
@@ -204,7 +205,7 @@ where I: AsyncRead + AsyncWrite + Unpin,
204205
None
205206
})
206207
} else if slice.is_empty() {
207-
error!("decode stream unexpectedly ended");
208+
error!("incoming body unexpectedly ended");
208209
// This should be unreachable, since all 3 decoders
209210
// either set eof=true or return an Err when reading
210211
// an empty slice...
@@ -216,7 +217,7 @@ where I: AsyncRead + AsyncWrite + Unpin,
216217
},
217218
Poll::Pending => return Poll::Pending,
218219
Poll::Ready(Err(e)) => {
219-
debug!("decode stream error: {}", e);
220+
debug!("incoming body decode error: {}", e);
220221
(Reading::Closed, Poll::Ready(Some(Err(e))))
221222
},
222223
}
@@ -294,6 +295,10 @@ where I: AsyncRead + AsyncWrite + Unpin,
294295
return Poll::Pending;
295296
}
296297

298+
if self.state.is_read_closed() {
299+
return Poll::Ready(Err(crate::Error::new_incomplete()));
300+
}
301+
297302
let num_read = ready!(self.force_io_read(cx)).map_err(crate::Error::new_io)?;
298303

299304
if num_read == 0 {
@@ -306,6 +311,8 @@ where I: AsyncRead + AsyncWrite + Unpin,
306311
}
307312

308313
fn force_io_read(&mut self, cx: &mut task::Context<'_>) -> Poll<io::Result<usize>> {
314+
debug_assert!(!self.state.is_read_closed());
315+
309316
let result = ready!(self.io.poll_read_from_io(cx));
310317
Poll::Ready(result.map_err(|e| {
311318
trace!("force_io_read; io error = {:?}", e);
@@ -619,8 +626,10 @@ where I: AsyncRead + AsyncWrite + Unpin,
619626

620627
pub fn disable_keep_alive(&mut self) {
621628
if self.state.is_idle() {
622-
self.state.close_read();
629+
trace!("disable_keep_alive; closing idle connection");
630+
self.state.close();
623631
} else {
632+
trace!("disable_keep_alive; in-progress connection");
624633
self.state.disable_keep_alive();
625634
}
626635
}

src/proto/h1/dispatch.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ where
6060
}
6161

6262
pub fn disable_keep_alive(&mut self) {
63-
self.conn.disable_keep_alive()
63+
self.conn.disable_keep_alive();
64+
if self.conn.is_write_closed() {
65+
self.close();
66+
}
6467
}
6568

6669
pub fn into_inner(self) -> (I, Bytes, D) {
@@ -233,10 +236,17 @@ where
233236
// if here, the dispatcher gave the user the error
234237
// somewhere else. we still need to shutdown, but
235238
// not as a second error.
239+
self.close();
236240
Poll::Ready(Ok(()))
237241
},
238242
None => {
239-
// read eof, conn will start to shutdown automatically
243+
// read eof, the write side will have been closed too unless
244+
// allow_read_close was set to true, in which case just do
245+
// nothing...
246+
debug_assert!(self.conn.is_read_closed());
247+
if self.conn.is_write_closed() {
248+
self.close();
249+
}
240250
Poll::Ready(Ok(()))
241251
}
242252
}

src/server/conn.rs

+7-8
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ impl Http {
194194

195195
Http {
196196
exec: Exec::Default,
197-
h1_half_close: true,
197+
h1_half_close: false,
198198
h1_writev: true,
199199
h2_builder,
200200
mode: ConnectionMode::Fallback,
@@ -221,12 +221,11 @@ impl<E> Http<E> {
221221
/// Set whether HTTP/1 connections should support half-closures.
222222
///
223223
/// Clients can chose to shutdown their write-side while waiting
224-
/// for the server to respond. Setting this to `false` will
225-
/// automatically close any connection immediately if `read`
226-
/// detects an EOF.
224+
/// for the server to respond. Setting this to `true` will
225+
/// prevent closing the connection immediately if `read`
226+
/// detects an EOF in the middle of a request.
227227
///
228-
/// Default is `true`.
229-
#[inline]
228+
/// Default is `false`.
230229
pub fn http1_half_close(&mut self, val: bool) -> &mut Self {
231230
self.h1_half_close = val;
232231
self
@@ -390,8 +389,8 @@ impl<E> Http<E> {
390389
if !self.keep_alive {
391390
conn.disable_keep_alive();
392391
}
393-
if !self.h1_half_close {
394-
conn.set_disable_half_close();
392+
if self.h1_half_close {
393+
conn.set_allow_half_close();
395394
}
396395
if !self.h1_writev {
397396
conn.set_write_strategy_flatten();

src/server/mod.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,11 @@ impl<I, E> Builder<I, E> {
252252
/// Set whether HTTP/1 connections should support half-closures.
253253
///
254254
/// Clients can chose to shutdown their write-side while waiting
255-
/// for the server to respond. Setting this to `false` will
256-
/// automatically close any connection immediately if `read`
257-
/// detects an EOF.
255+
/// for the server to respond. Setting this to `true` will
256+
/// prevent closing the connection immediately if `read`
257+
/// detects an EOF in the middle of a request.
258258
///
259-
/// Default is `true`.
259+
/// Default is `false`.
260260
pub fn http1_half_close(mut self, val: bool) -> Self {
261261
self.protocol.http1_half_close(val);
262262
self

tests/server.rs

+28-24
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,7 @@ fn disable_keep_alive_post_request() {
945945

946946
#[test]
947947
fn empty_parse_eof_does_not_return_error() {
948+
let _ = pretty_env_logger::try_init();
948949
let mut rt = Runtime::new().unwrap();
949950
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
950951
let addr = listener.local_addr().unwrap();
@@ -983,13 +984,13 @@ fn nonempty_parse_eof_returns_error() {
983984
}
984985

985986
#[test]
986-
fn socket_half_closed() {
987+
fn http1_allow_half_close() {
987988
let _ = pretty_env_logger::try_init();
988989
let mut rt = Runtime::new().unwrap();
989990
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
990991
let addr = listener.local_addr().unwrap();
991992

992-
thread::spawn(move || {
993+
let t1 = thread::spawn(move || {
993994
let mut tcp = connect(&addr);
994995
tcp.write_all(b"GET / HTTP/1.1\r\n\r\n").unwrap();
995996
tcp.shutdown(::std::net::Shutdown::Write).expect("SHDN_WR");
@@ -1005,13 +1006,16 @@ fn socket_half_closed() {
10051006
.map(Option::unwrap)
10061007
.map_err(|_| unreachable!())
10071008
.and_then(|socket| {
1008-
Http::new().serve_connection(socket, service_fn(|_| {
1009+
Http::new()
1010+
.http1_half_close(true)
1011+
.serve_connection(socket, service_fn(|_| {
10091012
tokio_timer::delay_for(Duration::from_millis(500))
10101013
.map(|_| Ok::<_, hyper::Error>(Response::new(Body::empty())))
10111014
}))
10121015
});
10131016

10141017
rt.block_on(fut).unwrap();
1018+
t1.join().expect("client thread");
10151019
}
10161020

10171021
#[test]
@@ -1852,28 +1856,28 @@ impl tower_service::Service<Request<Body>> for TestService {
18521856
Ok(()).into()
18531857
}
18541858

1855-
fn call(&mut self, req: Request<Body>) -> Self::Future {
1856-
let tx1 = self.tx.clone();
1857-
let tx2 = self.tx.clone();
1859+
fn call(&mut self, mut req: Request<Body>) -> Self::Future {
1860+
let tx = self.tx.clone();
18581861
let replies = self.reply.clone();
1859-
req
1860-
.into_body()
1861-
.try_concat()
1862-
.map_ok(move |chunk| {
1863-
tx1.send(Msg::Chunk(chunk.to_vec())).unwrap();
1864-
()
1865-
})
1866-
.map(move |result| {
1867-
let msg = match result {
1868-
Ok(()) => Msg::End,
1869-
Err(e) => Msg::Error(e),
1870-
};
1871-
tx2.send(msg).unwrap();
1872-
})
1873-
.map(move |_| {
1874-
TestService::build_reply(replies)
1875-
})
1876-
.boxed()
1862+
hyper::rt::spawn(async move {
1863+
while let Some(chunk) = req.body_mut().next().await {
1864+
match chunk {
1865+
Ok(chunk) => {
1866+
tx.send(Msg::Chunk(chunk.to_vec())).unwrap();
1867+
},
1868+
Err(err) => {
1869+
tx.send(Msg::Error(err)).unwrap();
1870+
return;
1871+
},
1872+
}
1873+
}
1874+
1875+
tx.send(Msg::End).unwrap();
1876+
});
1877+
1878+
Box::pin(async move {
1879+
TestService::build_reply(replies)
1880+
})
18771881
}
18781882
}
18791883

0 commit comments

Comments
 (0)