1use std::{
4 error::Error as StdError,
5 future::Future,
6 io::{self, Write as _},
7 pin::Pin,
8 task::{Context, Poll},
9};
10
11use bytes::Bytes;
12use derive_more::Display;
13#[cfg(feature = "compress-gzip")]
14use flate2::write::{GzEncoder, ZlibEncoder};
15use futures_core::ready;
16use pin_project_lite::pin_project;
17use tokio::task::{spawn_blocking, JoinHandle};
18use tracing::trace;
19#[cfg(feature = "compress-zstd")]
20use zstd::stream::write::Encoder as ZstdEncoder;
21
22use super::Writer;
23use crate::{
24 body::{self, BodySize, MessageBody},
25 header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING},
26 ResponseHead, StatusCode,
27};
28
29const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024;
30
31pin_project! {
32 pub struct Encoder<B> {
41 #[pin]
42 body: EncoderBody<B>,
43 encoder: Option<ContentEncoder>,
44 fut: Option<JoinHandle<Result<ContentEncoder, io::Error>>>,
45 needs_flush: bool,
46 eof: bool,
47 }
48}
49
50impl<B: MessageBody> Encoder<B> {
51 fn none() -> Self {
52 Encoder {
53 body: EncoderBody::None {
54 body: body::None::new(),
55 },
56 encoder: None,
57 fut: None,
58 needs_flush: false,
59 eof: true,
60 }
61 }
62
63 fn empty() -> Self {
64 Encoder {
65 body: EncoderBody::Full { body: Bytes::new() },
66 encoder: None,
67 fut: None,
68 needs_flush: false,
69 eof: true,
70 }
71 }
72
73 pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, body: B) -> Self {
87 match body.size() {
89 BodySize::None => return Self::none(),
90 BodySize::Sized(0) => return Self::empty(),
91 _ => {}
92 }
93
94 let should_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
95 || head.status == StatusCode::SWITCHING_PROTOCOLS
96 || head.status == StatusCode::NO_CONTENT
97 || head.status == StatusCode::PARTIAL_CONTENT
98 || encoding == ContentEncoding::Identity);
99
100 let body = match body.try_into_bytes() {
101 Ok(body) => EncoderBody::Full { body },
102 Err(body) => EncoderBody::Stream { body },
103 };
104
105 if should_encode {
106 if let Some(enc) = ContentEncoder::select(encoding) {
108 update_head(encoding, head);
109
110 return Encoder {
111 body,
112 encoder: Some(enc),
113 fut: None,
114 needs_flush: false,
115 eof: false,
116 };
117 }
118 }
119
120 Encoder {
121 body,
122 encoder: None,
123 fut: None,
124 needs_flush: false,
125 eof: false,
126 }
127 }
128}
129
130pin_project! {
131 #[project = EncoderBodyProj]
132 enum EncoderBody<B> {
133 None { body: body::None },
134 Full { body: Bytes },
135 Stream { #[pin] body: B },
136 }
137}
138
139impl<B> MessageBody for EncoderBody<B>
140where
141 B: MessageBody,
142{
143 type Error = EncoderError;
144
145 #[inline]
146 fn size(&self) -> BodySize {
147 match self {
148 EncoderBody::None { body } => body.size(),
149 EncoderBody::Full { body } => body.size(),
150 EncoderBody::Stream { body } => body.size(),
151 }
152 }
153
154 fn poll_next(
155 self: Pin<&mut Self>,
156 cx: &mut Context<'_>,
157 ) -> Poll<Option<Result<Bytes, Self::Error>>> {
158 match self.project() {
159 EncoderBodyProj::None { body } => {
160 Pin::new(body).poll_next(cx).map_err(|err| match err {})
161 }
162 EncoderBodyProj::Full { body } => {
163 Pin::new(body).poll_next(cx).map_err(|err| match err {})
164 }
165 EncoderBodyProj::Stream { body } => body
166 .poll_next(cx)
167 .map_err(|err| EncoderError::Body(err.into())),
168 }
169 }
170
171 #[inline]
172 fn try_into_bytes(self) -> Result<Bytes, Self>
173 where
174 Self: Sized,
175 {
176 match self {
177 EncoderBody::None { body } => Ok(body.try_into_bytes().unwrap()),
178 EncoderBody::Full { body } => Ok(body.try_into_bytes().unwrap()),
179 _ => Err(self),
180 }
181 }
182}
183
184impl<B> MessageBody for Encoder<B>
185where
186 B: MessageBody,
187{
188 type Error = EncoderError;
189
190 #[inline]
191 fn size(&self) -> BodySize {
192 if self.encoder.is_some() {
193 BodySize::Stream
194 } else {
195 self.body.size()
196 }
197 }
198
199 fn poll_next(
200 self: Pin<&mut Self>,
201 cx: &mut Context<'_>,
202 ) -> Poll<Option<Result<Bytes, Self::Error>>> {
203 let mut this = self.project();
204
205 loop {
206 if *this.eof {
207 return Poll::Ready(None);
208 }
209
210 if let Some(ref mut fut) = this.fut {
211 let mut encoder = ready!(Pin::new(fut).poll(cx))
212 .map_err(|_| {
213 EncoderError::Io(io::Error::other(
214 "Blocking task was cancelled unexpectedly",
215 ))
216 })?
217 .map_err(EncoderError::Io)?;
218
219 let chunk = encoder.take();
220 *this.encoder = Some(encoder);
221 this.fut.take();
222
223 if !chunk.is_empty() {
224 return Poll::Ready(Some(Ok(chunk)));
225 }
226 }
227
228 let result = match this.body.as_mut().poll_next(cx) {
229 Poll::Ready(result) => result,
230
231 Poll::Pending => {
232 if *this.needs_flush {
233 if let Some(encoder) = this.encoder.as_mut() {
234 encoder.flush().map_err(EncoderError::Io)?;
236 *this.needs_flush = false;
237
238 let chunk = encoder.take();
239
240 if !chunk.is_empty() {
241 return Poll::Ready(Some(Ok(chunk)));
242 }
243 }
244 }
245
246 return Poll::Pending;
247 }
248 };
249
250 match result {
251 Some(Err(err)) => return Poll::Ready(Some(Err(err))),
252
253 Some(Ok(chunk)) => {
254 if let Some(mut encoder) = this.encoder.take() {
255 *this.needs_flush |= !chunk.is_empty();
256
257 if chunk.len() < MAX_CHUNK_SIZE_ENCODE_IN_PLACE {
258 encoder.write(&chunk).map_err(EncoderError::Io)?;
259 let chunk = encoder.take();
260 *this.encoder = Some(encoder);
261
262 if !chunk.is_empty() {
263 return Poll::Ready(Some(Ok(chunk)));
264 }
265 } else {
266 *this.fut = Some(spawn_blocking(move || {
267 encoder.write(&chunk)?;
268 Ok(encoder)
269 }));
270 }
271 } else {
272 return Poll::Ready(Some(Ok(chunk)));
273 }
274 }
275
276 None => {
277 if let Some(encoder) = this.encoder.take() {
278 let chunk = encoder.finish().map_err(EncoderError::Io)?;
279
280 if chunk.is_empty() {
281 return Poll::Ready(None);
282 } else {
283 *this.eof = true;
284 return Poll::Ready(Some(Ok(chunk)));
285 }
286 } else {
287 return Poll::Ready(None);
288 }
289 }
290 }
291 }
292 }
293
294 #[inline]
295 fn try_into_bytes(mut self) -> Result<Bytes, Self>
296 where
297 Self: Sized,
298 {
299 if self.encoder.is_some() {
300 Err(self)
301 } else {
302 match self.body.try_into_bytes() {
303 Ok(body) => Ok(body),
304 Err(body) => {
305 self.body = body;
306 Err(self)
307 }
308 }
309 }
310 }
311}
312
313fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
314 head.headers_mut()
315 .insert(header::CONTENT_ENCODING, encoding.to_header_value());
316 head.headers_mut()
317 .append(header::VARY, HeaderValue::from_static("accept-encoding"));
318
319 head.no_chunking(false);
320}
321
322enum ContentEncoder {
323 #[cfg(feature = "compress-gzip")]
324 Deflate(ZlibEncoder<Writer>),
325
326 #[cfg(feature = "compress-gzip")]
327 Gzip(GzEncoder<Writer>),
328
329 #[cfg(feature = "compress-brotli")]
330 Brotli(Box<brotli::CompressorWriter<Writer>>),
331
332 #[cfg(feature = "compress-zstd")]
335 Zstd(ZstdEncoder<'static, Writer>),
336}
337
338impl ContentEncoder {
339 fn select(encoding: ContentEncoding) -> Option<Self> {
340 match encoding {
341 #[cfg(feature = "compress-gzip")]
342 ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new(
343 Writer::new(),
344 flate2::Compression::fast(),
345 ))),
346
347 #[cfg(feature = "compress-gzip")]
348 ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new(
349 Writer::new(),
350 flate2::Compression::fast(),
351 ))),
352
353 #[cfg(feature = "compress-brotli")]
354 ContentEncoding::Brotli => Some(ContentEncoder::Brotli(new_brotli_compressor())),
355
356 #[cfg(feature = "compress-zstd")]
357 ContentEncoding::Zstd => {
358 let encoder = ZstdEncoder::new(Writer::new(), 3).ok()?;
359 Some(ContentEncoder::Zstd(encoder))
360 }
361
362 _ => None,
363 }
364 }
365
366 #[inline]
367 pub(crate) fn take(&mut self) -> Bytes {
368 match *self {
369 #[cfg(feature = "compress-brotli")]
370 ContentEncoder::Brotli(ref mut encoder) => encoder.get_mut().take(),
371
372 #[cfg(feature = "compress-gzip")]
373 ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
374
375 #[cfg(feature = "compress-gzip")]
376 ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
377
378 #[cfg(feature = "compress-zstd")]
379 ContentEncoder::Zstd(ref mut encoder) => encoder.get_mut().take(),
380 }
381 }
382
383 fn flush(&mut self) -> Result<(), io::Error> {
384 match self {
385 #[cfg(feature = "compress-brotli")]
386 ContentEncoder::Brotli(encoder) => encoder.flush(),
387
388 #[cfg(feature = "compress-gzip")]
389 ContentEncoder::Gzip(encoder) => encoder.flush(),
390
391 #[cfg(feature = "compress-gzip")]
392 ContentEncoder::Deflate(encoder) => encoder.flush(),
393
394 #[cfg(feature = "compress-zstd")]
395 ContentEncoder::Zstd(encoder) => encoder.flush(),
396 }
397 }
398
399 fn finish(self) -> Result<Bytes, io::Error> {
400 match self {
401 #[cfg(feature = "compress-brotli")]
402 ContentEncoder::Brotli(mut encoder) => match encoder.flush() {
403 Ok(()) => Ok(encoder.into_inner().buf.freeze()),
404 Err(err) => Err(err),
405 },
406
407 #[cfg(feature = "compress-gzip")]
408 ContentEncoder::Gzip(encoder) => match encoder.finish() {
409 Ok(writer) => Ok(writer.buf.freeze()),
410 Err(err) => Err(err),
411 },
412
413 #[cfg(feature = "compress-gzip")]
414 ContentEncoder::Deflate(encoder) => match encoder.finish() {
415 Ok(writer) => Ok(writer.buf.freeze()),
416 Err(err) => Err(err),
417 },
418
419 #[cfg(feature = "compress-zstd")]
420 ContentEncoder::Zstd(encoder) => match encoder.finish() {
421 Ok(writer) => Ok(writer.buf.freeze()),
422 Err(err) => Err(err),
423 },
424 }
425 }
426
427 fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
428 match *self {
429 #[cfg(feature = "compress-brotli")]
430 ContentEncoder::Brotli(ref mut encoder) => match encoder.write_all(data) {
431 Ok(_) => Ok(()),
432 Err(err) => {
433 trace!("Error encoding br data: {}", err);
434 Err(err)
435 }
436 },
437
438 #[cfg(feature = "compress-gzip")]
439 ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
440 Ok(_) => Ok(()),
441 Err(err) => {
442 trace!("Error encoding gzip data: {}", err);
443 Err(err)
444 }
445 },
446
447 #[cfg(feature = "compress-gzip")]
448 ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
449 Ok(_) => Ok(()),
450 Err(err) => {
451 trace!("Error encoding deflate data: {}", err);
452 Err(err)
453 }
454 },
455
456 #[cfg(feature = "compress-zstd")]
457 ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) {
458 Ok(_) => Ok(()),
459 Err(err) => {
460 trace!("Error encoding zstd data: {}", err);
461 Err(err)
462 }
463 },
464 }
465 }
466}
467
468#[cfg(feature = "compress-brotli")]
469fn new_brotli_compressor() -> Box<brotli::CompressorWriter<Writer>> {
470 Box::new(brotli::CompressorWriter::new(
471 Writer::new(),
472 32 * 1024, 3, 22, ))
476}
477
478#[derive(Debug, Display)]
480#[non_exhaustive]
481pub enum EncoderError {
482 #[display("Body")]
484 Body(Box<dyn StdError>),
485
486 #[display("Io")]
488 Io(io::Error),
489}
490
491impl StdError for EncoderError {
492 fn source(&self) -> Option<&(dyn StdError + 'static)> {
493 match self {
494 EncoderError::Body(err) => Some(&**err),
495 EncoderError::Io(err) => Some(err),
496 }
497 }
498}
499
500impl From<EncoderError> for crate::Error {
501 fn from(err: EncoderError) -> Self {
502 crate::Error::new_encoder().with_cause(err)
503 }
504}