1 | use crate::stream::Stream; |
2 | use std::fmt; |
3 | use std::io::{self, copy, empty, Cursor, Read, Write}; |
4 | |
5 | #[cfg (feature = "charset" )] |
6 | use crate::response::DEFAULT_CHARACTER_SET; |
7 | #[cfg (feature = "charset" )] |
8 | use encoding_rs::Encoding; |
9 | |
10 | /// The different kinds of bodies to send. |
11 | /// |
12 | /// *Internal API* |
13 | pub(crate) enum Payload<'a> { |
14 | Empty, |
15 | Text(&'a str, String), |
16 | Reader(Box<dyn Read + 'a>), |
17 | Bytes(&'a [u8]), |
18 | } |
19 | |
20 | impl fmt::Debug for Payload<'_> { |
21 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
22 | match self { |
23 | Payload::Empty => write!(f, "Empty" ), |
24 | Payload::Text(t: &&str, _) => write!(f, " {}" , t), |
25 | Payload::Reader(_) => write!(f, "Reader" ), |
26 | Payload::Bytes(v: &&[u8]) => write!(f, " {:?}" , v), |
27 | } |
28 | } |
29 | } |
30 | |
31 | #[allow (clippy::derivable_impls)] |
32 | impl Default for Payload<'_> { |
33 | fn default() -> Self { |
34 | Payload::Empty |
35 | } |
36 | } |
37 | |
38 | /// The size of the body. |
39 | /// |
40 | /// *Internal API* |
41 | #[derive (Debug)] |
42 | pub(crate) enum BodySize { |
43 | Empty, |
44 | Unknown, |
45 | Known(u64), |
46 | } |
47 | |
48 | /// Payloads are turned into this type where we can hold both a size and the reader. |
49 | /// |
50 | /// *Internal API* |
51 | pub(crate) struct SizedReader<'a> { |
52 | pub size: BodySize, |
53 | pub reader: Box<dyn Read + 'a>, |
54 | } |
55 | |
56 | impl fmt::Debug for SizedReader<'_> { |
57 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
58 | write!(f, "SizedReader[size= {:?},reader]" , self.size) |
59 | } |
60 | } |
61 | |
62 | impl<'a> SizedReader<'a> { |
63 | fn new(size: BodySize, reader: Box<dyn Read + 'a>) -> Self { |
64 | SizedReader { size, reader } |
65 | } |
66 | } |
67 | |
68 | impl<'a> Payload<'a> { |
69 | pub fn into_read(self) -> SizedReader<'a> { |
70 | match self { |
71 | Payload::Empty => SizedReader::new(BodySize::Empty, Box::new(empty())), |
72 | Payload::Text(text, _charset) => { |
73 | #[cfg (feature = "charset" )] |
74 | let bytes = { |
75 | let encoding = Encoding::for_label(_charset.as_bytes()) |
76 | .or_else(|| Encoding::for_label(DEFAULT_CHARACTER_SET.as_bytes())) |
77 | .unwrap(); |
78 | encoding.encode(text).0 |
79 | }; |
80 | #[cfg (not(feature = "charset" ))] |
81 | let bytes = text.as_bytes(); |
82 | let len = bytes.len(); |
83 | let cursor = Cursor::new(bytes); |
84 | SizedReader::new(BodySize::Known(len as u64), Box::new(cursor)) |
85 | } |
86 | Payload::Reader(read) => SizedReader::new(BodySize::Unknown, read), |
87 | Payload::Bytes(bytes) => { |
88 | let len = bytes.len(); |
89 | let cursor = Cursor::new(bytes); |
90 | SizedReader::new(BodySize::Known(len as u64), Box::new(cursor)) |
91 | } |
92 | } |
93 | } |
94 | } |
95 | |
96 | const CHUNK_MAX_SIZE: usize = 0x4000; // Maximum size of a TLS fragment |
97 | const CHUNK_HEADER_MAX_SIZE: usize = 6; // four hex digits plus "\r\n" |
98 | const CHUNK_FOOTER_SIZE: usize = 2; // "\r\n" |
99 | const CHUNK_MAX_PAYLOAD_SIZE: usize = CHUNK_MAX_SIZE - CHUNK_HEADER_MAX_SIZE - CHUNK_FOOTER_SIZE; |
100 | |
101 | // copy_chunks() improves over chunked_transfer's Encoder + io::copy with the |
102 | // following performance optimizations: |
103 | // 1) It avoid copying memory. |
104 | // 2) chunked_transfer's Encoder issues 4 separate write() per chunk. This is costly |
105 | // overhead. Instead, we do a single write() per chunk. |
106 | // The measured benefit on a Linux machine is a 50% reduction in CPU usage on a https connection. |
107 | fn copy_chunked<R: Read, W: Write>(reader: &mut R, writer: &mut W) -> io::Result<u64> { |
108 | // The chunk layout is: |
109 | // header:header_max_size | payload:max_payload_size | footer:footer_size |
110 | let mut chunk = Vec::with_capacity(CHUNK_MAX_SIZE); |
111 | let mut written = 0; |
112 | loop { |
113 | // We first read the payload |
114 | chunk.resize(CHUNK_HEADER_MAX_SIZE, 0); |
115 | let payload_size = reader |
116 | .take(CHUNK_MAX_PAYLOAD_SIZE as u64) |
117 | .read_to_end(&mut chunk)?; |
118 | |
119 | // Then write the header |
120 | let header_str = format!(" {:x}\r\n" , payload_size); |
121 | let header = header_str.as_bytes(); |
122 | assert!(header.len() <= CHUNK_HEADER_MAX_SIZE); |
123 | let start_index = CHUNK_HEADER_MAX_SIZE - header.len(); |
124 | (&mut chunk[start_index..]).write_all(header).unwrap(); |
125 | |
126 | // And add the footer |
127 | chunk.extend_from_slice(b" \r\n" ); |
128 | |
129 | // Finally Write the chunk |
130 | writer.write_all(&chunk[start_index..])?; |
131 | written += payload_size as u64; |
132 | |
133 | // On EOF, we wrote a 0 sized chunk. This is what the chunked encoding protocol requires. |
134 | if payload_size == 0 { |
135 | return Ok(written); |
136 | } |
137 | } |
138 | } |
139 | |
140 | /// Helper to send a body, either as chunked or not. |
141 | pub(crate) fn send_body( |
142 | mut body: SizedReader, |
143 | do_chunk: bool, |
144 | stream: &mut Stream, |
145 | ) -> io::Result<()> { |
146 | if do_chunk { |
147 | copy_chunked(&mut body.reader, writer:stream)?; |
148 | } else { |
149 | copy(&mut body.reader, writer:stream)?; |
150 | }; |
151 | |
152 | Ok(()) |
153 | } |
154 | |
155 | #[cfg (test)] |
156 | mod tests { |
157 | use super::*; |
158 | |
159 | #[test ] |
160 | fn test_copy_chunked() { |
161 | let mut source = Vec::<u8>::new(); |
162 | source.resize(CHUNK_MAX_PAYLOAD_SIZE, 33); |
163 | source.extend_from_slice(b"hello world" ); |
164 | |
165 | let mut dest = Vec::<u8>::new(); |
166 | copy_chunked(&mut &source[..], &mut dest).unwrap(); |
167 | |
168 | let mut dest_expected = Vec::<u8>::new(); |
169 | dest_expected.extend_from_slice(format!(" {:x}\r\n" , CHUNK_MAX_PAYLOAD_SIZE).as_bytes()); |
170 | dest_expected.resize(dest_expected.len() + CHUNK_MAX_PAYLOAD_SIZE, 33); |
171 | dest_expected.extend_from_slice(b" \r\n" ); |
172 | |
173 | dest_expected.extend_from_slice(b"b \r\nhello world \r\n" ); |
174 | dest_expected.extend_from_slice(b"0 \r\n\r\n" ); |
175 | |
176 | assert_eq!(dest, dest_expected); |
177 | } |
178 | } |
179 | |