monolake_services/http/handlers/content_handler.rs
1//! Content encoding and decoding handler for HTTP requests and responses.
2//!
3//! This module provides a `ContentHandler` that manages content encoding and decoding
4//! for both incoming requests and outgoing responses in an HTTP service stack. It supports
5//! various content encodings and can be easily integrated into a service pipeline.
6//!
7//! # Key Components
8//!
9//! - [`ContentHandler`]: The main service component responsible for content encoding/decoding.
10//!
11//! # Features
12//!
13//! - Transparent content decoding for incoming requests
14//! - Content encoding for outgoing responses based on client preferences
15//! - Support for various content encodings (e.g., gzip, deflate)
16//! - Integration with service-async framework for easy composition
17//! - Error handling for decoding and encoding failures
18//!
19//! # Usage
20//!
21//! This handler is typically used as part of a larger service stack. Here's a basic example:
22//!
23//! ```rust
24//! use monolake_services::{
25//! common::ContextService,
26//! http::{
27//! core::HttpCoreService,
28//! detect::H2Detect,
29//! handlers::{
30//! route::RouteConfig, ConnectionReuseHandler, ContentHandler, RewriteAndRouteHandler,
31//! UpstreamHandler,
32//! },
33//! HttpServerTimeout,
34//! },
35//! };
36//! use service_async::{layer::FactoryLayer, stack::FactoryStack, Param};
37//!
38//! // Dummy struct to satisfy Param trait requirements
39//! struct DummyConfig;
40//!
41//! // Implement Param for DummyConfig to return Vec<RouteConfig>
42//! impl Param<Vec<RouteConfig>> for DummyConfig {
43//! fn param(&self) -> Vec<RouteConfig> {
44//! vec![]
45//! }
46//! }
47//! impl Param<HttpServerTimeout> for DummyConfig {
48//! fn param(&self) -> HttpServerTimeout {
49//! HttpServerTimeout::default()
50//! }
51//! }
52//!
53//! let config = DummyConfig;
54//! let stacks = FactoryStack::new(config)
55//! .replace(UpstreamHandler::factory(
56//! Default::default(),
57//! Default::default(),
58//! ))
59//! .push(ContentHandler::layer())
60//! .push(RewriteAndRouteHandler::layer())
61//! .push(ConnectionReuseHandler::layer())
62//! .push(HttpCoreService::layer())
63//! .push(H2Detect::layer());
64//!
65//! // Use the service to handle HTTP requests
66//! ```
67//! # Error Handling
68//!
69//! - Decoding errors for incoming requests result in 400 Bad Request responses
70//! - Encoding errors for outgoing responses result in 500 Internal Server Error responses
71//!
72//! # Performance Considerations
73//!
74//! - Content encoding/decoding is only performed when necessary (i.e., non-identity encoding)
75//! - The handler avoids unnecessary allocations and copies where possible
76use std::fmt::Debug;
77
78use http::{Request, StatusCode};
79use monoio_http::common::{
80 body::{BodyEncodeExt, FixedBody},
81 response::Response,
82};
83use monolake_core::http::{HttpHandler, ResponseWithContinue};
84use service_async::{
85 layer::{layer_fn, FactoryLayer},
86 AsyncMakeService, MakeService, Service,
87};
88
89use crate::http::generate_response;
90
91/// Handles content encoding and decoding for HTTP requests and responses.
92///
93/// `ContentHandler` is responsible for:
94/// 1. Decoding the content of incoming requests based on their Content-Encoding header.
95/// 2. Encoding the content of outgoing responses based on the client's Accept-Encoding preferences.
96///
97/// It wraps an inner handler and preprocesses requests before passing them to the inner handler,
98/// as well as postprocessing responses from the inner handler. For implementation details and
99/// example usage, see the [module level documentation](crate::http::handlers::content_handler).
100#[derive(Clone)]
101pub struct ContentHandler<H> {
102 inner: H,
103}
104
105impl<H, CX, B> Service<(Request<B>, CX)> for ContentHandler<H>
106where
107 H: HttpHandler<CX, B>,
108 B: BodyEncodeExt + FixedBody,
109 H::Body: BodyEncodeExt + FixedBody,
110 B::EncodeDecodeError: Debug,
111 <H::Body as BodyEncodeExt>::EncodeDecodeError: Debug,
112{
113 type Response = ResponseWithContinue<H::Body>;
114 type Error = H::Error;
115
116 async fn call(&self, (request, ctx): (Request<B>, CX)) -> Result<Self::Response, Self::Error> {
117 let content_encoding = request
118 .headers()
119 .get(http::header::CONTENT_ENCODING)
120 .and_then(|value: &http::HeaderValue| value.to_str().ok())
121 .unwrap_or("identity")
122 .to_string();
123
124 let accept_encoding = request
125 .headers()
126 .get(http::header::ACCEPT_ENCODING)
127 .and_then(|value| value.to_str().ok())
128 .unwrap_or("identity")
129 .to_string();
130
131 let content_length = request
132 .headers()
133 .get(http::header::CONTENT_LENGTH)
134 .and_then(|value| value.to_str().ok())
135 .map(|value| value.parse::<usize>().unwrap_or_default())
136 .unwrap_or_default();
137
138 if content_length == 0 || content_encoding == "identity" {
139 let (response, _) = self.inner.handle(request, ctx).await?;
140 return Ok((response, true));
141 }
142
143 let (parts, body) = request.into_parts();
144 match body.decode_content(content_encoding).await {
145 Ok(decodec_data) => {
146 let req = Request::from_parts(parts, B::fixed_body(Some(decodec_data)));
147 let (mut response, _) = self.inner.handle(req, ctx).await?;
148 if accept_encoding != "identity" {
149 let (parts, body) = response.into_parts();
150 match body.encode_content(accept_encoding).await {
151 Ok(encoded_data) => {
152 response =
153 Response::from_parts(parts, H::Body::fixed_body(Some(encoded_data)))
154 }
155 Err(e) => {
156 tracing::error!("Response content encoding failed {e:?}");
157 return Ok((
158 generate_response(StatusCode::INTERNAL_SERVER_ERROR, false),
159 true,
160 ));
161 }
162 }
163 }
164 Ok((response, true))
165 }
166 Err(e) => {
167 tracing::error!("Request content decode failed {e:?}");
168 Ok((generate_response(StatusCode::BAD_REQUEST, false), true))
169 }
170 }
171 }
172}
173
174// ContentHandler is a Service and a MakeService.
175impl<F> MakeService for ContentHandler<F>
176where
177 F: MakeService,
178{
179 type Service = ContentHandler<F::Service>;
180 type Error = F::Error;
181
182 fn make_via_ref(&self, old: Option<&Self::Service>) -> Result<Self::Service, Self::Error> {
183 Ok(ContentHandler {
184 inner: self.inner.make_via_ref(old.map(|o| &o.inner))?,
185 })
186 }
187}
188
189impl<F: AsyncMakeService> AsyncMakeService for ContentHandler<F> {
190 type Service = ContentHandler<F::Service>;
191 type Error = F::Error;
192
193 async fn make_via_ref(
194 &self,
195 old: Option<&Self::Service>,
196 ) -> Result<Self::Service, Self::Error> {
197 Ok(ContentHandler {
198 inner: self.inner.make_via_ref(old.map(|o| &o.inner)).await?,
199 })
200 }
201}
202
203impl<F> ContentHandler<F> {
204 pub fn layer<C>() -> impl FactoryLayer<C, F, Factory = Self> {
205 layer_fn(|_: &C, inner| Self { inner })
206 }
207
208 /// Returns a factory layer for the `ContentHandler`.
209 ///
210 /// This allows the 'ContentHandler to be selectively enabled or
211 /// disabled based on a configuration at runtime.
212 pub fn opt_layer<C>(enabled: bool) -> Option<impl FactoryLayer<C, F, Factory = Self>> {
213 if enabled {
214 Some(layer_fn(|_: &C, inner| Self { inner }))
215 } else {
216 None
217 }
218 }
219}