Skip to content

Commit 5b96703

Browse files
e2e test for chunked encoding from server
1 parent 737c8ae commit 5b96703

File tree

8 files changed

+89
-46
lines changed

8 files changed

+89
-46
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ log = "0.4"
2424

2525
[dev-dependencies]
2626
pretty_assertions = "0.6.1"
27+
async-std = { version = "1.4.0", features = ["unstable", "attributes"] }
28+
tempfile = "3.1.0"

src/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub async fn encode(req: Request) -> Result<Encoder, std::io::Error> {
6868
// If the body isn't streaming, we can set the content-length ahead of time. Else we need to
6969
// send all items in chunks.
7070
if let Some(len) = req.len() {
71-
let val = format!("Content-Length: {}\r\n", len);
71+
let val = format!("content-length: {}\r\n", len);
7272
log::trace!("> {}", &val);
7373
buf.write_all(val.as_bytes()).await?;
7474
} else {

src/server.rs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ use crate::date::fmt_http_date;
1818
use crate::error::HttpError;
1919
use crate::{Exception, MAX_HEADERS};
2020

21+
const CR: u8 = b'\r';
22+
const LF: u8 = b'\n';
23+
2124
/// Parse an incoming HTTP connection.
2225
///
2326
/// Supports `KeepAlive` requests by default.
@@ -35,6 +38,7 @@ where
3538
// Decode a request. This may be the first of many since the connection is Keep-Alive by default.
3639
let r = io.clone();
3740
let req = decode(addr, r).await?;
41+
3842
if let Some(mut req) = req {
3943
loop {
4044
match num_requests {
@@ -113,13 +117,13 @@ impl Encoder {
113117
// If the body isn't streaming, we can set the content-length ahead of time. Else we need to
114118
// send all items in chunks.
115119
if let Some(len) = self.res.len() {
116-
std::io::Write::write_fmt(&mut head, format_args!("Content-Length: {}\r\n", len))?;
120+
std::io::Write::write_fmt(&mut head, format_args!("content-length: {}\r\n", len))?;
117121
} else {
118-
std::io::Write::write_fmt(&mut head, format_args!("Transfer-Encoding: chunked\r\n"))?;
122+
std::io::Write::write_fmt(&mut head, format_args!("transfer-encoding: chunked\r\n"))?;
119123
}
120124

121125
let date = fmt_http_date(std::time::SystemTime::now());
122-
std::io::Write::write_fmt(&mut head, format_args!("Date: {}\r\n", date))?;
126+
std::io::Write::write_fmt(&mut head, format_args!("date: {}\r\n", date))?;
123127

124128
for (header, values) in self.res.iter() {
125129
for value in values.iter() {
@@ -213,14 +217,15 @@ impl Read for Encoder {
213217
body_bytes_read
214218
);
215219
// If we've read the `len` number of bytes, end
216-
self.state = if body_len == body_bytes_read {
217-
EncoderState::Done
220+
if body_len == body_bytes_read {
221+
self.state = EncoderState::Done;
222+
break;
218223
} else {
219-
EncoderState::Body {
224+
self.state = EncoderState::Body {
220225
body_bytes_read,
221226
body_len,
222227
}
223-
};
228+
}
224229
}
225230
EncoderState::UncomputedChunked => {
226231
// We can read a maximum of the buffer's total size
@@ -261,8 +266,8 @@ impl Read for Encoder {
261266
bytes_read += chunk_length_bytes_len;
262267

263268
// follow chunk length with CRLF
264-
buf[bytes_read] = b'\r';
265-
buf[bytes_read + 1] = b'\n';
269+
buf[bytes_read] = CR;
270+
buf[bytes_read + 1] = LF;
266271
bytes_read += 2;
267272

268273
// copy chunk into buf
@@ -271,12 +276,13 @@ impl Read for Encoder {
271276
bytes_read += chunk_length;
272277

273278
// follow chunk with CRLF
274-
buf[bytes_read] = b'\r';
275-
buf[bytes_read + 1] = b'\n';
279+
buf[bytes_read] = CR;
280+
buf[bytes_read + 1] = LF;
276281
bytes_read += 2;
277282

278283
if chunk_length == 0 {
279284
self.state = EncoderState::Done;
285+
break;
280286
}
281287
} else {
282288
let mut chunk = vec![0; total_chunk_size];
@@ -286,8 +292,8 @@ impl Read for Encoder {
286292
bytes_written += chunk_length_bytes_len;
287293

288294
// follow chunk length with CRLF
289-
chunk[bytes_written] = b'\r';
290-
chunk[bytes_written + 1] = b'\n';
295+
chunk[bytes_written] = CR;
296+
chunk[bytes_written + 1] = LF;
291297
bytes_written += 2;
292298

293299
// copy chunk into buf
@@ -296,8 +302,8 @@ impl Read for Encoder {
296302
bytes_written += chunk_length;
297303

298304
// follow chunk with CRLF
299-
chunk[bytes_written] = b'\r';
300-
chunk[bytes_written + 1] = b'\n';
305+
chunk[bytes_written] = CR;
306+
chunk[bytes_written + 1] = LF;
301307
bytes_read += 2;
302308
self.state = EncoderState::ComputedChunked {
303309
chunk: io::Cursor::new(chunk),
@@ -341,7 +347,7 @@ where
341347

342348
// Keep reading bytes from the stream until we hit the end of the stream.
343349
loop {
344-
let bytes_read = reader.read_until(b'\n', &mut buf).await?;
350+
let bytes_read = reader.read_until(LF, &mut buf).await?;
345351
// No more bytes are yielded from the stream.
346352
if bytes_read == 0 {
347353
return Ok(None);

tests/common/mod.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use async_std::fs::File;
2-
use async_std::fs::OpenOptions;
32
use async_std::io::{self, Read, SeekFrom, Write};
43
use async_std::path::PathBuf;
54
use async_std::sync::Arc;
@@ -33,15 +32,8 @@ impl TestCase {
3332
));
3433
let response_fixture = Arc::new(Mutex::new(response_fixture));
3534

36-
let temp = std::env::temp_dir().join("result.txt");
37-
let temp = OpenOptions::new()
38-
.read(true)
39-
.write(true)
40-
.create(true)
41-
.open(temp)
42-
.await
43-
.expect("Could not read temporary file where response will be written to");
44-
let result = Arc::new(Mutex::new(temp));
35+
let temp = tempfile::tempfile().expect("Failed to create tempfile");
36+
let result = Arc::new(Mutex::new(temp.into()));
4537

4638
TestCase {
4739
request_fixture,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
GET / HTTP/1.1
2+
Host: example.com
3+
User-Agent: curl/7.54.0
4+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
HTTP/1.1 200 OK
2+
transfer-encoding: chunked
3+
date: {DATE}
4+
content-type: text/plain
5+
6+
7
7+
Mozilla
8+
9
9+
Developer
10+
7
11+
Network
12+
0
13+

tests/fixtures/response1.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
HTTP/1.1 200 OK
2-
Content-Length: 0
3-
Date: {DATE}
2+
content-length: 0
3+
date: {DATE}
44
content-type: text/plain; charset=utf-8
55

tests/server.rs

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,49 @@
1-
mod common;
21
use async_h1::server;
3-
use async_std::task;
2+
use async_std::io::Cursor;
3+
use async_std::prelude::*;
44
use common::TestCase;
5-
use http_types::{Response, StatusCode};
5+
use http_types::{mime, Body, Response, StatusCode};
6+
7+
mod common;
8+
9+
#[async_std::test]
10+
async fn test_basic_request() {
11+
let case = TestCase::new("fixtures/request1.txt", "fixtures/response1.txt").await;
12+
let addr = "http://example.com";
13+
14+
server::accept(addr, case.clone(), |_req| async {
15+
let mut resp = Response::new(StatusCode::Ok);
16+
resp.set_body("");
17+
Ok(resp)
18+
})
19+
.await
20+
.unwrap();
21+
22+
case.assert().await;
23+
}
624

7-
#[test]
8-
fn test_basic_request() {
9-
task::block_on(async {
10-
let case = TestCase::new("fixtures/request1.txt", "fixtures/response1.txt").await;
11-
let addr = "http://example.com";
25+
#[async_std::test]
26+
async fn test_chunked_basic() {
27+
let case = TestCase::new(
28+
"fixtures/request-chunked-basic.txt",
29+
"fixtures/response-chunked-basic.txt",
30+
)
31+
.await;
32+
let addr = "http://example.com";
1233

13-
server::accept(addr, case.clone(), |_req| async {
14-
let mut resp = Response::new(StatusCode::Ok);
15-
resp.set_body("");
16-
Ok(resp)
17-
})
18-
.await
19-
.unwrap();
34+
server::accept(addr, case.clone(), |_req| async {
35+
let mut resp = Response::new(StatusCode::Ok);
36+
resp.set_body(Body::from_reader(
37+
Cursor::new(b"Mozilla")
38+
.chain(Cursor::new(b"Developer"))
39+
.chain(Cursor::new(b"Network")),
40+
None,
41+
));
42+
resp.set_content_type(mime::PLAIN);
43+
Ok(resp)
44+
})
45+
.await
46+
.unwrap();
2047

21-
case.assert().await;
22-
});
48+
case.assert().await;
2349
}

0 commit comments

Comments
 (0)