Skip to content

Commit cca3bcc

Browse files
http: Improve the Chunked reader
1 parent a96edc1 commit cca3bcc

File tree

1 file changed

+75
-10
lines changed

1 file changed

+75
-10
lines changed
Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,62 @@
11
use std::io::{Read, Result, Write};
22

3-
const CHUNK_SIZE: usize = 1024;
4-
pub struct Chunked<R: Read> {
3+
/// A reader for [HTTP Chunked transfer encoding]
4+
///
5+
/// [HTTP Chunked transfer encoding]: <https://en.wikipedia.org/wiki/Chunked_transfer_encoding>
6+
pub struct Chunked<R: Read, const CHUNK_SIZE: usize = 1024> {
57
reader: R,
68
chunk: Vec<u8>,
79
offset: usize,
8-
finish: bool,
910
}
1011

1112
impl<R: Read> Chunked<R> {
13+
/// Creates a `Chunked` struct with the default size.
14+
pub fn with_default_size(reader: R) -> Chunked<R> {
15+
Self::new(reader)
16+
}
17+
}
18+
19+
impl<R: Read, const CHUNK_SIZE: usize> Chunked<R, CHUNK_SIZE> {
20+
21+
/// The size of the chunks
22+
pub const CHUNK_SIZE: usize = CHUNK_SIZE;
23+
1224
pub fn new(reader: R) -> Self {
13-
Self {
25+
Chunked {
1426
reader,
1527
chunk: Vec::with_capacity(CHUNK_SIZE + 8),
1628
offset: 0,
17-
finish: false,
1829
}
1930
}
2031
fn next_chunk(&mut self) -> Result<bool> {
21-
if self.finish {
22-
return Ok(false);
23-
}
2432
self.chunk.clear();
2533
self.offset = 0;
2634

2735
let mut tmpbuf: [u8; CHUNK_SIZE] = [0; CHUNK_SIZE];
2836
let n = self.reader.read(&mut tmpbuf)?;
2937
if n == 0 {
30-
self.finish = true;
38+
return Ok(false)
3139
}
3240
self.chunk.write_all(format!("{n:X}\r\n").as_bytes())?;
3341
self.chunk.write_all(&tmpbuf[0..n])?;
3442
self.chunk.write_all(b"\r\n")?;
3543
Ok(true)
3644
}
45+
46+
/// Returns the current chunk
47+
///
48+
/// # NOTE
49+
/// This method returns the whole chunk, even the parts alredy
50+
/// read. If you want to know the remaining portion of the chunk
51+
/// that hasn't been polled, see [offset](Self::offset)
52+
pub fn current_chunk(&self) -> &[u8] { &self.chunk }
53+
54+
/// Returns the current offset. This is: The offset to the
55+
/// part of the current chunk that hasn't been read yet
56+
pub fn offset(&self) -> usize { self.offset }
3757
}
3858

39-
impl<R: Read> Read for Chunked<R> {
59+
impl<R: Read, const CHUNK_SIZE: usize> Read for Chunked<R, CHUNK_SIZE> {
4060
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
4161
if self.offset >= self.chunk.len() && !self.next_chunk()? {
4262
return Ok(0);
@@ -51,3 +71,48 @@ impl<R: Read> Read for Chunked<R> {
5171
Ok(n)
5272
}
5373
}
74+
75+
impl<R: Read + Default> Default for Chunked<R> {
76+
fn default() -> Self {
77+
Self::new(R::default())
78+
}
79+
}
80+
81+
#[cfg(test)]
82+
mod test {
83+
pub use super::*;
84+
85+
const SIZE: usize = 1024;
86+
87+
fn test_chunks(input: &str) {
88+
let mut chunked = Chunked::<_, SIZE>::new(input.as_bytes());
89+
let mut out = Vec::new();
90+
91+
chunked.read_to_end(&mut out).unwrap();
92+
93+
let mut expected = Vec::new();
94+
for chunk in input.as_bytes().chunks(SIZE) {
95+
expected.extend_from_slice(format!("{:X}\r\n", chunk.len()).as_bytes());
96+
expected.extend_from_slice(chunk);
97+
expected.extend_from_slice(b"\r\n");
98+
}
99+
assert_eq!(out, expected);
100+
}
101+
102+
#[test]
103+
fn small() {
104+
test_chunks("abcdefg");
105+
test_chunks(&"0".repeat(SIZE - 50));
106+
}
107+
108+
#[test]
109+
fn exact_chunks() {
110+
test_chunks(&"a".repeat(SIZE));
111+
test_chunks(&"a".repeat(SIZE * 2));
112+
}
113+
114+
#[test]
115+
fn with_remaining() {
116+
test_chunks(&"a".repeat(SIZE + 200));
117+
}
118+
}

0 commit comments

Comments
 (0)