Skip to content

Commit ce78205

Browse files
authored
perf(decoder): avoid unnecessary clone of decompression service (#828)
1 parent d620a25 commit ce78205

2 files changed

Lines changed: 50 additions & 20 deletions

File tree

src/client/layer/decoder/layer.rs

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ use std::task::{Context, Poll};
33
use http::{Request, Response};
44
use http_body::Body;
55
use tower::{Layer, Service};
6-
use tower_http::decompression::{
7-
Decompression as TowerDecompression, DecompressionBody, ResponseFuture,
8-
};
6+
use tower_http::decompression::{self, DecompressionBody, ResponseFuture};
97

108
use super::AcceptEncoding;
119
use crate::{client::layer::config::RequestAcceptEncoding, core::ext::RequestConfig};
@@ -20,7 +18,7 @@ pub struct DecompressionLayer {
2018
}
2119

2220
impl DecompressionLayer {
23-
/// Creates a new `DecompressionLayer` with the specified `Accepts`.
21+
/// Creates a new `DecompressionLayer` with the specified `AcceptEncoding`.
2422
pub const fn new(accept: AcceptEncoding) -> Self {
2523
Self { accept }
2624
}
@@ -30,9 +28,11 @@ impl<S> Layer<S> for DecompressionLayer {
3028
type Service = Decompression<S>;
3129

3230
fn layer(&self, service: S) -> Self::Service {
33-
let decoder = TowerDecompression::new(service);
34-
let decoder = Decompression::<S>::accept(decoder, &self.accept);
35-
Decompression { decoder }
31+
let decoder = decompression::Decompression::new(service);
32+
let decoder = Decompression::<S>::accept_in_place(decoder, &self.accept);
33+
Decompression {
34+
decoder: Some(decoder),
35+
}
3636
}
3737
}
3838

@@ -42,14 +42,15 @@ impl<S> Layer<S> for DecompressionLayer {
4242
/// bodies based on the `Content-Encoding` header.
4343
#[derive(Clone)]
4444
pub struct Decompression<S> {
45-
decoder: TowerDecompression<S>,
45+
decoder: Option<decompression::Decompression<S>>,
4646
}
4747

4848
impl<S> Decompression<S> {
49-
fn accept(
50-
mut decoder: TowerDecompression<S>,
49+
// replaces the current decoder with a new one based on the `AcceptEncoding`.
50+
fn accept_in_place(
51+
mut decoder: decompression::Decompression<S>,
5152
accept: &AcceptEncoding,
52-
) -> TowerDecompression<S> {
53+
) -> decompression::Decompression<S> {
5354
#[cfg(feature = "gzip")]
5455
{
5556
decoder = decoder.gzip(accept.gzip);
@@ -86,16 +87,33 @@ where
8687

8788
#[inline(always)]
8889
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
89-
self.decoder.poll_ready(cx)
90+
match self.decoder.as_mut() {
91+
Some(decoder) => decoder.poll_ready(cx),
92+
None => unreachable!("Decompression service is not initialized"),
93+
}
9094
}
9195

9296
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
97+
// If the accept encoding is set, we need to update the decoder
98+
// to handle the specified encodings.
9399
if let Some(accept) = RequestConfig::<RequestAcceptEncoding>::get(req.extensions()) {
94-
let mut decoder = self.decoder.clone();
95-
decoder = Decompression::accept(decoder, accept);
96-
std::mem::swap(&mut self.decoder, &mut decoder);
100+
if let Some(mut decoder) = self.decoder.take() {
101+
decoder = Decompression::accept_in_place(decoder, accept);
102+
self.decoder = Some(decoder);
103+
}
97104
}
98105

99-
self.decoder.call(req)
106+
// Call the underlying service with the request
107+
match self.decoder.as_mut() {
108+
Some(decoder) => decoder.call(req),
109+
None => {
110+
// This branch should never be reached: decoder is always initialized in
111+
// DecompressionLayer::layer(). If this panic occurs, it indicates a
112+
// bug in the service setup or unexpected internal state.
113+
unreachable!(
114+
"Decompression service was not initialized; this indicates a bug in service setup"
115+
);
116+
}
117+
}
100118
}
101119
}

src/client/layer/decoder/mod.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,49 @@ mod layer;
44

55
pub use layer::{Decompression, DecompressionLayer};
66

7+
/// Configuration for supported content-encoding algorithms.
8+
///
9+
/// `AcceptEncoding` controls which compression formats are enabled for decoding
10+
/// response bodies. Each field corresponds to a specific algorithm and is only
11+
/// available if the corresponding feature is enabled.
712
#[derive(Clone, Debug)]
813
pub(crate) struct AcceptEncoding {
914
#[cfg(feature = "gzip")]
10-
pub(super) gzip: bool,
15+
gzip: bool,
16+
1117
#[cfg(feature = "brotli")]
12-
pub(super) brotli: bool,
18+
brotli: bool,
19+
1320
#[cfg(feature = "zstd")]
14-
pub(super) zstd: bool,
21+
zstd: bool,
22+
1523
#[cfg(feature = "deflate")]
16-
pub(super) deflate: bool,
24+
deflate: bool,
1725
}
1826

1927
impl AcceptEncoding {
28+
/// Enable or disable gzip decoding.
2029
#[inline(always)]
2130
#[cfg(feature = "gzip")]
2231
pub fn gzip(&mut self, enabled: bool) {
2332
self.gzip = enabled;
2433
}
2534

35+
/// Enable or disable brotli decoding.
2636
#[inline(always)]
2737
#[cfg(feature = "brotli")]
2838
pub fn brotli(&mut self, enabled: bool) {
2939
self.brotli = enabled;
3040
}
3141

42+
/// Enable or disable zstd decoding.
3243
#[inline(always)]
3344
#[cfg(feature = "zstd")]
3445
pub fn zstd(&mut self, enabled: bool) {
3546
self.zstd = enabled;
3647
}
3748

49+
/// Enable or disable deflate decoding.
3850
#[inline(always)]
3951
#[cfg(feature = "deflate")]
4052
pub fn deflate(&mut self, enabled: bool) {

0 commit comments

Comments
 (0)