Skip to content

Commit 15fdd53

Browse files
committed
fix(client): detect valid eof after reading a body
If a connection closes immediately after reading the end of the body, the next call to `conn.poll()` should not error, but just indicate that the connection is done. Closes #1396
1 parent 7b59311 commit 15fdd53

File tree

1 file changed

+67
-5
lines changed

1 file changed

+67
-5
lines changed

src/proto/conn.rs

+67-5
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ where I: AsyncRead + AsyncWrite,
8888
trace!("poll when on keep-alive");
8989
if !T::should_read_first() {
9090
self.try_empty_read()?;
91+
if self.is_read_closed() {
92+
return Ok(Async::Ready(None));
93+
}
9194
}
9295
self.maybe_park_read();
9396
return Ok(Async::NotReady);
@@ -134,6 +137,11 @@ where I: AsyncRead + AsyncWrite,
134137
}
135138
}
136139

140+
fn should_error_on_eof(&self) -> bool {
141+
// If we're idle, it's probably just the connection closing gracefully.
142+
T::should_error_on_parse_eof() && !self.state.is_idle()
143+
}
144+
137145
pub fn read_head(&mut self) -> Poll<Option<(super::MessageHead<T::Incoming>, bool)>, ::Error> {
138146
debug_assert!(self.can_read_head());
139147
trace!("Conn::read_head");
@@ -145,7 +153,7 @@ where I: AsyncRead + AsyncWrite,
145153
// If we are currently waiting on a message, then an empty
146154
// message should be reported as an error. If not, it is just
147155
// the connection closing gracefully.
148-
let must_error = !self.state.is_idle() && T::should_error_on_parse_eof();
156+
let must_error = self.should_error_on_eof();
149157
self.state.close_read();
150158
self.io.consume_leading_lines();
151159
let was_mid_parse = !self.io.read_buf().is_empty();
@@ -185,6 +193,9 @@ where I: AsyncRead + AsyncWrite,
185193
(true, Reading::Body(decoder))
186194
};
187195
self.state.reading = reading;
196+
if !body {
197+
self.try_keep_alive();
198+
}
188199
Ok(Async::Ready(Some((head, body))))
189200
},
190201
_ => {
@@ -219,6 +230,7 @@ where I: AsyncRead + AsyncWrite,
219230
};
220231

221232
self.state.reading = reading;
233+
self.try_keep_alive();
222234
ret
223235
}
224236

@@ -251,11 +263,12 @@ where I: AsyncRead + AsyncWrite,
251263
} else {
252264
match self.io.read_from_io() {
253265
Ok(Async::Ready(0)) => {
254-
trace!("try_empty_read; found EOF on connection");
266+
trace!("try_empty_read; found EOF on connection: {:?}", self.state);
267+
let must_error = self.should_error_on_eof();
268+
// order is important: must_error needs state BEFORE close_read
255269
self.state.close_read();
256-
let must_error = !self.state.is_idle() && T::should_error_on_parse_eof();
257270
if must_error {
258-
Err(io::ErrorKind::UnexpectedEof.into())
271+
Err(io::Error::new(io::ErrorKind::UnexpectedEof, "unexpected EOF waiting for response"))
259272
} else {
260273
Ok(())
261274
}
@@ -860,7 +873,7 @@ mod tests {
860873
use super::super::h1::Encoder;
861874
use mock::AsyncIo;
862875

863-
use super::{Conn, Reading, Writing};
876+
use super::{Conn, Decoder, Reading, Writing};
864877
use ::uri::Uri;
865878

866879
use std::str::FromStr;
@@ -960,6 +973,55 @@ mod tests {
960973
}).wait();
961974
}
962975

976+
#[test]
977+
fn test_conn_body_finish_read_eof() {
978+
let _: Result<(), ()> = future::lazy(|| {
979+
let io = AsyncIo::new_eof();
980+
let mut conn = Conn::<_, proto::Chunk, ClientTransaction>::new(io, Default::default());
981+
conn.state.busy();
982+
conn.state.writing = Writing::KeepAlive;
983+
conn.state.reading = Reading::Body(Decoder::length(0));
984+
985+
match conn.poll() {
986+
Ok(Async::Ready(Some(Frame::Body { chunk: None }))) => (),
987+
other => panic!("unexpected frame: {:?}", other)
988+
}
989+
990+
// conn eofs, but tokio-proto will call poll() again, before calling flush()
991+
// the conn eof in this case is perfectly fine
992+
993+
match conn.poll() {
994+
Ok(Async::Ready(None)) => (),
995+
other => panic!("unexpected frame: {:?}", other)
996+
}
997+
Ok(())
998+
}).wait();
999+
}
1000+
1001+
#[test]
1002+
fn test_conn_message_empty_body_read_eof() {
1003+
let _: Result<(), ()> = future::lazy(|| {
1004+
let io = AsyncIo::new_buf(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".to_vec(), 1024);
1005+
let mut conn = Conn::<_, proto::Chunk, ClientTransaction>::new(io, Default::default());
1006+
conn.state.busy();
1007+
conn.state.writing = Writing::KeepAlive;
1008+
1009+
match conn.poll() {
1010+
Ok(Async::Ready(Some(Frame::Message { body: false, .. }))) => (),
1011+
other => panic!("unexpected frame: {:?}", other)
1012+
}
1013+
1014+
// conn eofs, but tokio-proto will call poll() again, before calling flush()
1015+
// the conn eof in this case is perfectly fine
1016+
1017+
match conn.poll() {
1018+
Ok(Async::Ready(None)) => (),
1019+
other => panic!("unexpected frame: {:?}", other)
1020+
}
1021+
Ok(())
1022+
}).wait();
1023+
}
1024+
9631025
#[test]
9641026
fn test_conn_closed_read() {
9651027
let io = AsyncIo::new_buf(vec![], 0);

0 commit comments

Comments
 (0)