reth_rpc_layer/
compression_layer.rs1use jsonrpsee_http_client::{HttpBody, HttpRequest, HttpResponse};
2use std::{
3 future::Future,
4 pin::Pin,
5 task::{Context, Poll},
6};
7use tower::{Layer, Service};
8use tower_http::compression::{Compression, CompressionLayer as TowerCompressionLayer};
9
10#[expect(missing_debug_implementations)]
14#[derive(Clone)]
15pub struct CompressionLayer {
16 inner_layer: TowerCompressionLayer,
17}
18
19impl CompressionLayer {
20 pub fn new() -> Self {
22 Self {
23 inner_layer: TowerCompressionLayer::new().gzip(true).br(true).deflate(true).zstd(true),
24 }
25 }
26
27 pub fn with_algorithms(algos: &[impl AsRef<str>]) -> Self {
34 let mut layer = TowerCompressionLayer::new().no_zstd().no_gzip().no_deflate().no_br();
36
37 for algo in algos {
38 match algo.as_ref() {
39 "zstd" => layer = layer.zstd(true),
40 "deflate" => layer = layer.deflate(true),
41 "gzip" => layer = layer.gzip(true),
42 "br" | "brotli" => layer = layer.br(true),
43 _ => {}
44 }
45 }
46
47 Self { inner_layer: layer }
48 }
49}
50
51impl Default for CompressionLayer {
52 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl<S> Layer<S> for CompressionLayer {
60 type Service = CompressionService<S>;
61
62 fn layer(&self, inner: S) -> Self::Service {
63 CompressionService { compression: self.inner_layer.layer(inner) }
64 }
65}
66
67#[expect(missing_debug_implementations)]
71#[derive(Clone)]
72pub struct CompressionService<S> {
73 compression: Compression<S>,
74}
75
76impl<S> Service<HttpRequest> for CompressionService<S>
77where
78 S: Service<HttpRequest, Response = HttpResponse>,
79 S::Future: Send + 'static,
80{
81 type Response = HttpResponse;
82 type Error = S::Error;
83 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
84
85 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
86 self.compression.poll_ready(cx)
87 }
88
89 fn call(&mut self, req: HttpRequest) -> Self::Future {
90 let fut = self.compression.call(req);
91
92 Box::pin(async move {
93 let resp = fut.await?;
94 let (parts, compressed_body) = resp.into_parts();
95 let http_body = HttpBody::new(compressed_body);
96
97 Ok(Self::Response::from_parts(parts, http_body))
98 })
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use http::header::{ACCEPT_ENCODING, CONTENT_ENCODING};
106 use http_body_util::BodyExt;
107 use jsonrpsee_http_client::{HttpRequest, HttpResponse};
108 use std::{convert::Infallible, future::ready};
109
110 const TEST_DATA: &str = "compress test data ";
111 const REPEAT_COUNT: usize = 1000;
112
113 #[derive(Clone)]
114 struct MockRequestService;
115
116 impl Service<HttpRequest> for MockRequestService {
117 type Response = HttpResponse;
118 type Error = Infallible;
119 type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
120
121 fn poll_ready(
122 &mut self,
123 _: &mut std::task::Context<'_>,
124 ) -> std::task::Poll<Result<(), Self::Error>> {
125 std::task::Poll::Ready(Ok(()))
126 }
127
128 fn call(&mut self, _: HttpRequest) -> Self::Future {
129 let body = HttpBody::from(TEST_DATA.repeat(REPEAT_COUNT));
130 let response = HttpResponse::builder().body(body).unwrap();
131 ready(Ok(response))
132 }
133 }
134
135 fn setup_compression_service(
136 ) -> impl Service<HttpRequest, Response = HttpResponse, Error = Infallible> {
137 CompressionLayer::new().layer(MockRequestService)
138 }
139
140 async fn get_response_size(response: HttpResponse) -> usize {
141 response.into_body().collect().await.unwrap().to_bytes().len()
143 }
144
145 #[tokio::test]
146 async fn test_gzip_compression() {
147 let mut service = setup_compression_service();
148 let request =
149 HttpRequest::builder().header(ACCEPT_ENCODING, "gzip").body(HttpBody::empty()).unwrap();
150
151 let uncompressed_len = TEST_DATA.repeat(REPEAT_COUNT).len();
152
153 let response = service.call(request).await.unwrap();
155
156 assert_eq!(
158 response.headers().get(CONTENT_ENCODING).unwrap(),
159 "gzip",
160 "Response should be gzip encoded"
161 );
162
163 let compressed_size = get_response_size(response).await;
165 assert!(
166 compressed_size < uncompressed_len,
167 "Compressed size ({compressed_size}) should be smaller than original size ({uncompressed_len})"
168 );
169 }
170
171 #[tokio::test]
172 async fn test_no_compression_when_not_requested() {
173 let mut service = setup_compression_service();
175 let request = HttpRequest::builder().body(HttpBody::empty()).unwrap();
176
177 let response = service.call(request).await.unwrap();
178 assert!(
179 response.headers().get(CONTENT_ENCODING).is_none(),
180 "Response should not be compressed when not requested"
181 );
182
183 let uncompressed_len = TEST_DATA.repeat(REPEAT_COUNT).len();
184
185 let response_size = get_response_size(response).await;
187 assert!(
188 response_size == uncompressed_len,
189 "Response size ({response_size}) should equal original size ({uncompressed_len})"
190 );
191 }
192}