Skip to content

Commit e798b22

Browse files
authored
Merge pull request #69 from http-rs/fix-unexpected-end
Fix stream freezing when body ends unexpectedly
2 parents 739e481 + e2ec881 commit e798b22

File tree

6 files changed

+107
-10
lines changed

6 files changed

+107
-10
lines changed

src/chunked.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ impl<R: Read + Unpin> ChunkedDecoder<R> {
9999
read += n;
100100
let new_state = if new_current == len {
101101
State::ChunkEnd
102+
} else if n == 0 {
103+
// Unexpected end
104+
// TODO: do something?
105+
State::Done
102106
} else {
103107
State::Chunk(new_current, len)
104108
};
@@ -279,6 +283,15 @@ impl<R: Read + Unpin> Read for ChunkedDecoder<R> {
279283
return Poll::Pending;
280284
}
281285
};
286+
match (bytes_read, &this.state) {
287+
(0, State::Done) => {}
288+
(0, _) => {
289+
// Unexpected end
290+
// TODO: do something?
291+
this.state = State::Done;
292+
}
293+
_ => {}
294+
}
282295
n.end += bytes_read;
283296
}
284297
match this.poll_read_inner(cx, buffer, &n, buf)? {

src/client.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
use async_std::io::{self, BufReader, Read, Write};
44
use async_std::prelude::*;
55
use async_std::task::{Context, Poll};
6-
use futures_core::ready;
76
use http_types::{ensure, ensure_eq, format_err};
87
use http_types::{
98
headers::{HeaderName, HeaderValue, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
@@ -241,7 +240,19 @@ impl Read for Encoder {
241240
}
242241

243242
if !self.body_done {
244-
let n = ready!(Pin::new(&mut self.request).poll_read(cx, &mut buf[bytes_read..]))?;
243+
let inner_poll_result =
244+
Pin::new(&mut self.request).poll_read(cx, &mut buf[bytes_read..]);
245+
let n = match inner_poll_result {
246+
Poll::Ready(Ok(n)) => n,
247+
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
248+
Poll::Pending => {
249+
if bytes_read == 0 {
250+
return Poll::Pending;
251+
} else {
252+
return Poll::Ready(Ok(bytes_read as usize));
253+
}
254+
}
255+
};
245256
bytes_read += n;
246257
self.body_bytes_read += n;
247258
if bytes_read == 0 {

src/server.rs

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use async_std::io::{self, BufReader};
99
use async_std::io::{Read, Write};
1010
use async_std::prelude::*;
1111
use async_std::task::{Context, Poll};
12-
use futures_core::ready;
1312
use http_types::headers::{HeaderName, HeaderValue, CONTENT_LENGTH, TRANSFER_ENCODING};
1413
use http_types::{ensure, ensure_eq, format_err};
1514
use http_types::{Body, Method, Request, Response};
@@ -198,9 +197,19 @@ impl Read for Encoder {
198197
// Figure out how many bytes we can read.
199198
let upper_bound = (bytes_read + body_len - body_bytes_read).min(buf.len());
200199
// Read bytes from body
201-
let new_body_bytes_read =
202-
ready!(Pin::new(&mut self.res)
203-
.poll_read(cx, &mut buf[bytes_read..upper_bound]))?;
200+
let inner_poll_result =
201+
Pin::new(&mut self.res).poll_read(cx, &mut buf[bytes_read..upper_bound]);
202+
let new_body_bytes_read = match inner_poll_result {
203+
Poll::Ready(Ok(n)) => n,
204+
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
205+
Poll::Pending => {
206+
if bytes_read == 0 {
207+
return Poll::Pending;
208+
} else {
209+
break;
210+
}
211+
}
212+
};
204213
body_bytes_read += new_body_bytes_read;
205214
bytes_read += new_body_bytes_read;
206215

@@ -212,8 +221,13 @@ impl Read for Encoder {
212221
body_len,
213222
body_bytes_read
214223
);
215-
// If we've read the `len` number of bytes, end
216224
if body_len == body_bytes_read {
225+
// If we've read the `len` number of bytes, end
226+
self.state = EncoderState::Done;
227+
break;
228+
} else if new_body_bytes_read == 0 {
229+
// If we've reached unexpected EOF, end anyway
230+
// TODO: do something?
217231
self.state = EncoderState::Done;
218232
break;
219233
} else {
@@ -237,8 +251,18 @@ impl Read for Encoder {
237251
// it into the actual buffer
238252
let mut chunk_buf = vec![0; buffer_remaining];
239253
// Read bytes from body reader
240-
let chunk_length =
241-
ready!(Pin::new(&mut self.res).poll_read(cx, &mut chunk_buf))?;
254+
let inner_poll_result = Pin::new(&mut self.res).poll_read(cx, &mut chunk_buf);
255+
let chunk_length = match inner_poll_result {
256+
Poll::Ready(Ok(n)) => n,
257+
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
258+
Poll::Pending => {
259+
if bytes_read == 0 {
260+
return Poll::Pending;
261+
} else {
262+
break;
263+
}
264+
}
265+
};
242266

243267
// serialize chunk length as hex
244268
let chunk_length_string = format!("{:X}", chunk_length);
@@ -311,7 +335,18 @@ impl Read for Encoder {
311335
ref mut chunk,
312336
is_last,
313337
} => {
314-
bytes_read += ready!(Pin::new(chunk).poll_read(cx, &mut buf))?;
338+
let inner_poll_result = Pin::new(chunk).poll_read(cx, &mut buf);
339+
bytes_read += match inner_poll_result {
340+
Poll::Ready(Ok(n)) => n,
341+
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
342+
Poll::Pending => {
343+
if bytes_read == 0 {
344+
return Poll::Pending;
345+
} else {
346+
break;
347+
}
348+
}
349+
};
315350
if bytes_read == 0 {
316351
self.state = match is_last {
317352
true => EncoderState::Done,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
POST / HTTP/1.1
2+
content-type: text/plain
3+
content-length: 11
4+
5+
aaaaabbbbb
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
HTTP/1.1 200 OK
2+
content-length: 11
3+
date: {DATE}
4+
content-type: text/plain
5+
6+
aaaaabbbbb

tests/server.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,30 @@ async fn test_chunked_echo() {
7272

7373
case.assert().await;
7474
}
75+
76+
#[async_std::test]
77+
async fn test_unexpected_eof() {
78+
// We can't predict unexpected EOF, so the response content-length is still 11
79+
let case = TestCase::new_server(
80+
"fixtures/request-unexpected-eof.txt",
81+
"fixtures/response-unexpected-eof.txt",
82+
)
83+
.await;
84+
let addr = "http://example.com";
85+
86+
async_h1::accept(addr, case.clone(), |req| async {
87+
let mut resp = Response::new(StatusCode::Ok);
88+
let ct = req.content_type();
89+
let body: Body = req.into();
90+
resp.set_body(body);
91+
if let Some(ct) = ct {
92+
resp.set_content_type(ct);
93+
}
94+
95+
Ok(resp)
96+
})
97+
.await
98+
.unwrap();
99+
100+
case.assert().await;
101+
}

0 commit comments

Comments
 (0)