1use crate::HeaderMap;
2use crate::layer::compression::pin_project_cfg::pin_project_cfg;
3use crate::layer::util::compression::CompressionLevel;
4
5use compression_codecs::{
6 BrotliEncoder, EncodeV2, GzipEncoder, ZlibEncoder, ZstdEncoder,
7 brotli::params::EncoderParams as BrotliEncoderParams,
8};
9use compression_core::util;
10use rama_core::bytes::BytesMut;
11use rama_core::{
12 bytes::{Buf, Bytes},
13 error::BoxError,
14};
15
16use pin_project_lite::pin_project;
17use rama_http_types::StreamingBody;
18use rama_http_types::body::{Frame, SizeHint};
19use std::{
20 io,
21 pin::Pin,
22 task::{Context, Poll},
23};
24
25pin_project! {
26 pub struct StreamCompressionBody<B>
30 where
31 B: StreamingBody,
32 {
33 #[pin]
34 inner: BodyInner<B>,
35 }
36}
37
38impl<B> Default for StreamCompressionBody<B>
39where
40 B: StreamingBody + Default,
41{
42 fn default() -> Self {
43 Self {
44 inner: BodyInner::Identity {
45 inner: B::default(),
46 },
47 }
48 }
49}
50
51enum Encoder {
52 Gzip(GzipEncoder),
53 Deflate(ZlibEncoder),
54 Brotli(Box<BrotliEncoder>),
55 Zstd(ZstdEncoder),
56}
57
58impl EncodeV2 for Encoder {
59 fn encode(
60 &mut self,
61 input: &mut util::PartialBuffer<&[u8]>,
62 output: &mut util::WriteBuffer<'_>,
63 ) -> io::Result<()> {
64 match self {
65 Self::Gzip(e) => e.encode(input, output),
66 Self::Deflate(e) => e.encode(input, output),
67 Self::Brotli(e) => e.encode(input, output),
68 Self::Zstd(e) => e.encode(input, output),
69 }
70 }
71
72 fn flush(&mut self, output: &mut util::WriteBuffer<'_>) -> io::Result<bool> {
73 match self {
74 Self::Gzip(e) => e.flush(output),
75 Self::Deflate(e) => e.flush(output),
76 Self::Brotli(e) => e.flush(output),
77 Self::Zstd(e) => e.flush(output),
78 }
79 }
80
81 fn finish(&mut self, output: &mut util::WriteBuffer<'_>) -> io::Result<bool> {
82 match self {
83 Self::Gzip(e) => e.finish(output),
84 Self::Deflate(e) => e.finish(output),
85 Self::Brotli(e) => e.finish(output),
86 Self::Zstd(e) => e.finish(output),
87 }
88 }
89}
90
91struct CompressData {
92 encoder: Encoder,
93 output_buffer: BytesMut,
94 always_flush: bool,
95 state: CompressState,
96 pending_trailers: Option<HeaderMap>,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100enum CompressState {
101 Reading,
103 Finishing,
105 Trailers,
107 Done,
109}
110
111pin_project_cfg! {
112 #[project = BodyInnerProj]
113 enum BodyInner<B>
114 where
115 B: StreamingBody,
116 {
117 Compress {
118 #[pin]
119 inner: B,
120 data: CompressData,
121 },
122 Identity {
123 #[pin]
124 inner: B,
125 },
126 }
127}
128
129impl CompressData {
130 const INTERNAL_BUF_CAPACITY: usize = 8096;
131
132 fn new(encoder: Encoder, always_flush: bool) -> Self {
133 Self {
134 encoder,
135 output_buffer: BytesMut::with_capacity(Self::INTERNAL_BUF_CAPACITY),
136 always_flush,
137 state: CompressState::Reading,
138 pending_trailers: None,
139 }
140 }
141
142 fn poll_compressed<B>(
144 &mut self,
145 cx: &mut Context<'_>,
146 mut inner: Pin<&mut B>,
147 ) -> Poll<Option<Result<Frame<Bytes>, io::Error>>>
148 where
149 B: StreamingBody,
150 B::Data: Buf,
151 B::Error: Into<BoxError>,
152 {
153 loop {
154 match self.state {
155 CompressState::Done => return Poll::Ready(None),
156
157 CompressState::Trailers => {
158 if let Some(trailers) = self.pending_trailers.take() {
159 self.state = CompressState::Done;
160 return Poll::Ready(Some(Ok(Frame::trailers(trailers))));
161 } else {
162 self.state = CompressState::Done;
163 return Poll::Ready(None);
164 }
165 }
166
167 CompressState::Finishing => {
168 self.output_buffer.reserve(Self::INTERNAL_BUF_CAPACITY);
169 let mut output = util::WriteBuffer::new_uninitialized(
170 self.output_buffer.spare_capacity_mut(),
171 );
172
173 match self.encoder.finish(&mut output) {
174 Ok(done) => {
175 let written = output.written_len();
176 unsafe {
178 self.output_buffer
179 .set_len(self.output_buffer.len() + written);
180 }
181
182 if written > 0 {
183 let data = self.output_buffer.split().freeze();
184 if done {
185 self.state = if self.pending_trailers.is_some() {
186 CompressState::Trailers
187 } else {
188 CompressState::Done
189 };
190 }
191 return Poll::Ready(Some(Ok(Frame::data(data))));
192 } else if done {
193 self.state = if self.pending_trailers.is_some() {
194 CompressState::Trailers
195 } else {
196 CompressState::Done
197 };
198 } else {
199 return Poll::Pending;
202 }
203 }
204 Err(e) => return Poll::Ready(Some(Err(io::Error::other(e)))),
205 }
206 }
207
208 CompressState::Reading => {
209 match inner.as_mut().poll_frame(cx) {
210 Poll::Pending => return Poll::Pending,
211 Poll::Ready(None) => {
212 self.state = CompressState::Finishing;
213 }
214 Poll::Ready(Some(Err(e))) => {
215 return Poll::Ready(Some(Err(io::Error::other(e.into()))));
216 }
217 Poll::Ready(Some(Ok(frame))) => {
218 match frame.into_data() {
219 Ok(mut data) => {
220 let input_bytes = data.copy_to_bytes(data.remaining());
221 match self.compress_chunk(&input_bytes) {
222 Ok(Some(frame)) => return Poll::Ready(Some(Ok(frame))),
223 Ok(None) => (),
226 Err(e) => return Poll::Ready(Some(Err(e))),
227 }
228 }
229 Err(frame) => {
230 if let Ok(trailers) = frame.into_trailers() {
231 self.pending_trailers = Some(trailers);
232 self.state = CompressState::Finishing;
233 }
234 }
235 }
236 }
237 }
238 }
239 }
240 }
241 }
242
243 fn compress_chunk(&mut self, input: &[u8]) -> io::Result<Option<Frame<Bytes>>> {
245 let mut input_buf = util::PartialBuffer::new(input);
246
247 loop {
248 self.output_buffer.reserve(Self::INTERNAL_BUF_CAPACITY);
249 let mut output =
250 util::WriteBuffer::new_uninitialized(self.output_buffer.spare_capacity_mut());
251
252 self.encoder.encode(&mut input_buf, &mut output)?;
253
254 let written = output.written_len();
255 unsafe {
256 self.output_buffer
257 .set_len(self.output_buffer.len() + written);
258 }
259
260 if input_buf.written_len() >= input.len() || written == 0 {
261 break;
262 }
263 }
264
265 if self.always_flush {
266 self.output_buffer.reserve(Self::INTERNAL_BUF_CAPACITY);
267 let mut output =
268 util::WriteBuffer::new_uninitialized(self.output_buffer.spare_capacity_mut());
269 _ = self.encoder.flush(&mut output)?;
270 let written = output.written_len();
271 unsafe {
272 self.output_buffer
273 .set_len(self.output_buffer.len() + written);
274 }
275 }
276
277 if self.output_buffer.is_empty() {
278 Ok(None)
279 } else {
280 Ok(Some(Frame::data(self.output_buffer.split().freeze())))
281 }
282 }
283}
284
285impl<B> StreamCompressionBody<B>
286where
287 B: StreamingBody,
288{
289 #[inline(always)]
290 pub(super) fn gzip(inner: B, level: CompressionLevel, always_flush: bool) -> Self {
291 Self::compressed(
292 inner,
293 Encoder::Gzip(GzipEncoder::new(level.into_compression_core().into())),
294 always_flush,
295 )
296 }
297
298 #[inline(always)]
299 pub(super) fn deflate(inner: B, level: CompressionLevel, always_flush: bool) -> Self {
300 Self::compressed(
301 inner,
302 Encoder::Deflate(ZlibEncoder::new(level.into_compression_core().into())),
303 always_flush,
304 )
305 }
306
307 #[inline(always)]
308 pub(super) fn brotli(inner: B, level: CompressionLevel, always_flush: bool) -> Self {
309 let params = BrotliEncoderParams::default().quality(level.into_compression_core());
310 Self::compressed(
311 inner,
312 Encoder::Brotli(Box::new(BrotliEncoder::new(params))),
313 always_flush,
314 )
315 }
316
317 #[inline(always)]
318 pub(super) fn zstd(inner: B, level: CompressionLevel, always_flush: bool) -> Self {
319 let needs_window_limit = match level {
330 CompressionLevel::Best => true, CompressionLevel::Precise(level) => level >= 17,
332 CompressionLevel::Default | CompressionLevel::Fastest => false,
333 };
334
335 let level = match level {
336 CompressionLevel::Fastest => 1,
337 CompressionLevel::Best => 21,
338 CompressionLevel::Default => 0,
339 CompressionLevel::Precise(level) => level as i32,
340 };
341
342 let encoder = if needs_window_limit {
345 let params = [compression_codecs::zstd::params::CParameter::window_log(23)];
346 ZstdEncoder::new_with_params(level, ¶ms)
347 } else {
348 ZstdEncoder::new(level)
349 };
350
351 Self::compressed(inner, Encoder::Zstd(encoder), always_flush)
352 }
353
354 fn compressed(inner: B, encoder: Encoder, always_flush: bool) -> Self {
355 Self {
356 inner: BodyInner::Compress {
357 inner,
358 data: CompressData::new(encoder, always_flush),
359 },
360 }
361 }
362
363 pub(super) fn identity(inner: B) -> Self {
364 Self {
365 inner: BodyInner::Identity { inner },
366 }
367 }
368}
369
370impl<B> StreamingBody for StreamCompressionBody<B>
371where
372 B: StreamingBody,
373 B::Data: Buf,
374 B::Error: Into<BoxError>,
375{
376 type Data = Bytes;
377 type Error = io::Error;
378
379 fn poll_frame(
380 self: Pin<&mut Self>,
381 cx: &mut Context<'_>,
382 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
383 match self.project().inner.project() {
384 BodyInnerProj::Identity { inner } => {
385 match inner.poll_frame(cx) {
387 Poll::Pending => Poll::Pending,
388 Poll::Ready(None) => Poll::Ready(None),
389 Poll::Ready(Some(Ok(frame))) => {
390 let frame = frame.map_data(|mut data| data.copy_to_bytes(data.remaining()));
391 Poll::Ready(Some(Ok(frame)))
392 }
393 Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(io::Error::other(e.into())))),
394 }
395 }
396 BodyInnerProj::Compress { inner, data } => data.poll_compressed(cx, inner),
397 }
398 }
399
400 fn is_end_stream(&self) -> bool {
401 match &self.inner {
402 BodyInner::Identity { inner } => inner.is_end_stream(),
403 BodyInner::Compress { data, .. } => data.state == CompressState::Done,
404 }
405 }
406
407 fn size_hint(&self) -> SizeHint {
408 match &self.inner {
409 BodyInner::Identity { inner } => inner.size_hint(),
410 BodyInner::Compress { .. } => SizeHint::default(),
412 }
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419 use std::collections::VecDeque;
420
421 struct TestBody {
423 frames: VecDeque<Frame<Bytes>>,
424 }
425
426 impl TestBody {
427 fn new(frames: Vec<Frame<Bytes>>) -> Self {
428 Self {
429 frames: frames.into(),
430 }
431 }
432 }
433
434 impl StreamingBody for TestBody {
435 type Data = Bytes;
436 type Error = std::convert::Infallible;
437
438 fn poll_frame(
439 mut self: Pin<&mut Self>,
440 _cx: &mut Context<'_>,
441 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
442 match self.frames.pop_front() {
443 Some(frame) => Poll::Ready(Some(Ok(frame))),
444 None => Poll::Ready(None),
445 }
446 }
447 }
448
449 fn poll_body<B: StreamingBody + Unpin>(
450 body: &mut B,
451 ) -> Option<Result<Frame<B::Data>, B::Error>> {
452 let waker = std::task::Waker::noop();
453 let mut cx = Context::from_waker(waker);
454 match Pin::new(body).poll_frame(&mut cx) {
455 Poll::Ready(result) => result,
456 Poll::Pending => None,
457 }
458 }
459
460 #[test]
461 fn test_identity_data() {
462 let inner = TestBody::new(vec![Frame::data(Bytes::from("hello world"))]);
463 let mut body = StreamCompressionBody::identity(inner);
464
465 let frame = poll_body(&mut body).unwrap().unwrap();
466 assert!(frame.is_data());
467 assert_eq!(frame.into_data().unwrap(), Bytes::from("hello world"));
468
469 assert!(poll_body(&mut body).is_none());
470 }
471
472 #[test]
473 fn test_passthrough_trailers() {
474 let mut trailers = HeaderMap::new();
475 trailers.insert("x-checksum", "abc123".parse().unwrap());
476
477 let inner = TestBody::new(vec![
478 Frame::data(Bytes::from("data")),
479 Frame::trailers(trailers.clone()),
480 ]);
481 let mut body = StreamCompressionBody::identity(inner);
482
483 let frame = poll_body(&mut body).unwrap().unwrap();
485 assert!(frame.is_data());
486
487 let frame = poll_body(&mut body).unwrap().unwrap();
489 assert!(frame.is_trailers());
490 let received_trailers = frame.into_trailers().unwrap();
491 assert_eq!(received_trailers.get("x-checksum").unwrap(), "abc123");
492
493 assert!(poll_body(&mut body).is_none());
494 }
495
496 #[test]
497 fn test_compressed_produces_output() {
498 let mk_inner = || TestBody::new(vec![Frame::data(Bytes::from("hello world"))]);
499 for mut body in [
500 StreamCompressionBody::gzip(mk_inner(), Default::default(), false),
501 StreamCompressionBody::deflate(mk_inner(), Default::default(), false),
502 StreamCompressionBody::brotli(mk_inner(), Default::default(), false),
503 StreamCompressionBody::zstd(mk_inner(), Default::default(), false),
504 ] {
505 let frame = poll_body(&mut body).unwrap().unwrap();
507 assert!(frame.is_data());
508 let data = frame.into_data().unwrap();
509 assert!(!data.is_empty());
511
512 while let Some(Ok(frame)) = poll_body(&mut body) {
514 assert!(frame.is_data());
515 }
516 }
517 }
518
519 #[test]
520 fn test_compressed_with_trailers() {
521 let mk_inner = || {
522 let mut trailers = HeaderMap::new();
523 trailers.insert("x-checksum", "abc123".parse().unwrap());
524
525 TestBody::new(vec![
526 Frame::data(Bytes::from("hello world")),
527 Frame::trailers(trailers),
528 ])
529 };
530
531 for mut body in [
532 StreamCompressionBody::gzip(mk_inner(), Default::default(), false),
533 StreamCompressionBody::deflate(mk_inner(), Default::default(), false),
534 StreamCompressionBody::brotli(mk_inner(), Default::default(), false),
535 StreamCompressionBody::zstd(mk_inner(), Default::default(), false),
536 ] {
537 let mut data_frames = 0;
539 let mut trailer_frame = None;
540 while let Some(Ok(frame)) = poll_body(&mut body) {
541 if frame.is_data() {
542 data_frames += 1;
543 } else if frame.is_trailers() {
544 trailer_frame = Some(frame);
545 }
546 }
547
548 assert!(data_frames >= 1);
550
551 let trailers = trailer_frame
553 .expect("Expected trailers frame")
554 .into_trailers()
555 .unwrap();
556 assert_eq!(trailers.get("x-checksum").unwrap(), "abc123");
557 }
558 }
559}