1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//! This crate implements a zero-copy, zero-allocation writer for HTTP [chunked response
//! bodies](https://tools.ietf.org/html/rfc7230#section-4.1). The result can be written
//! directly into a
//! [`TcpStream`](https://doc.rust-lang.org/stable/std/net/struct.TcpStream.html) or any
//! other object that implements
//! [`Write`](https://doc.rust-lang.org/stable/std/io/trait.Write.html).
//!
//! ## Example
//!
//! ```rust
//! use uhttp_chunked_write::ChunkedWrite;
//! use std::io::Write;
//!
//! let mut buf = [0; 25];
//!
//! {
//!     let mut body = ChunkedWrite::new(&mut buf[..]);
//!     write!(&mut body, "hello {}", 1337).unwrap();
//! }
//!
//! assert_eq!(&buf[..], &b"6\r\nhello \r\n4\r\n1337\r\n0\r\n\r\n"[..]);
//! ```

use std::io::Write;

/// Writes bytes in the HTTP chunked encoding protocol.
///
/// When the object goes out of scope the chunked message is terminated and the stream is
/// flushed.
///
/// To reduce the number of write syscalls to the underlying stream when using `write!` or
/// byte-based serialization, wrap the object in a
/// [`BufWriter`](https://doc.rust-lang.org/stable/std/io/struct.BufWriter.html), for
/// example `BufWriter::new(ChunkedWrite::new(stream))`.
pub struct ChunkedWrite<W: Write>(W);

impl<W: Write> ChunkedWrite<W> {
    /// Create a new `ChunkedWrite` to write into the given stream.
    pub fn new(sink: W) -> Self {
        ChunkedWrite(sink)
    }

    /// Send the given data in chunked encoding.
    fn send(&mut self, data: &[u8]) -> std::io::Result<()> {
        try!(write!(self.0, "{:x}\r\n", data.len()));
        try!(self.0.write_all(data));
        try!(write!(self.0, "\r\n"));

        Ok(())
    }
}

impl<W: Write> Write for ChunkedWrite<W> {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        try!(self.send(buf));
        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> { self.0.flush() }
}

impl<W: Write> Drop for ChunkedWrite<W> {
    fn drop(&mut self) {
        // Send terminating empty chunk and flush the stream.
        self.send(&[]).is_ok();
        self.flush().is_ok();
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::io::Write;

    #[test]
    fn test_chunked_write() {
        let mut buf = [0; 32];

        {
            let mut w = ChunkedWrite::new(&mut buf[..]);
            w.write_all(b"abc def").unwrap();
            w.write_all(b"gh\nijklmno").unwrap();
        }

        assert_eq!(&buf[..], b"7\r\nabc def\r\na\r\ngh\nijklmno\r\n0\r\n\r\n");
    }
}