Skip to main content

rama_http/layer/
body_capture.rs

1//! Asynchronous streaming request and response body capture.
2//!
3//! [`BodyCaptureLayer`] creates a dedicated [`BodyCaptureSink`] for each
4//! selected HTTP message. Each sink receives owned frame copies while the
5//! original body continues downstream.
6
7use std::{fmt, future::Future};
8
9use rama_core::{Layer, Service, bytes::Bytes, error::BoxError};
10use rama_http_types::{
11    Body, BodyCaptureSink, CaptureBody, Request, Response, StreamingBody, body::util::BodyExt as _,
12    request, response,
13};
14use rama_utils::macros::define_inner_service_accessors;
15
16/// The HTTP head associated with a captured body stream.
17#[derive(Clone)]
18pub enum CapturedHead {
19    /// An inbound request head.
20    Request(request::Parts),
21    /// An outbound response head.
22    Response(response::Parts),
23}
24
25impl fmt::Debug for CapturedHead {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        match self {
28            Self::Request(parts) => f.debug_tuple("Request").field(parts).finish(),
29            Self::Response(parts) => f.debug_tuple("Response").field(parts).finish(),
30        }
31    }
32}
33
34/// Creates a dedicated streaming capture sink for an HTTP message.
35///
36/// Returning `None` skips body capture for that message. The factory future is
37/// awaited after the HTTP head is available and before its body starts flowing.
38pub trait MakeBodyCaptureSink: Send + Sync + 'static {
39    /// The per-message capture sink.
40    type Sink: BodyCaptureSink;
41
42    /// Create a capture sink from the request or response head.
43    fn make_sink(&self, head: CapturedHead)
44    -> impl Future<Output = Option<Self::Sink>> + Send + '_;
45}
46
47impl<F, Fut, S> MakeBodyCaptureSink for F
48where
49    F: Fn(CapturedHead) -> Fut + Send + Sync + 'static,
50    Fut: Future<Output = Option<S>> + Send + 'static,
51    S: BodyCaptureSink,
52{
53    type Sink = S;
54
55    fn make_sink(
56        &self,
57        head: CapturedHead,
58    ) -> impl Future<Output = Option<Self::Sink>> + Send + '_ {
59        self(head)
60    }
61}
62
63/// Layer that captures request bodies, response bodies, or both while they
64/// continue streaming through the service.
65#[derive(Clone)]
66pub struct BodyCaptureLayer<M> {
67    make_sink: M,
68    capture_request: bool,
69    capture_response: bool,
70}
71
72impl<M> BodyCaptureLayer<M> {
73    /// Capture request bodies.
74    #[must_use]
75    pub const fn request(make_sink: M) -> Self {
76        Self {
77            make_sink,
78            capture_request: true,
79            capture_response: false,
80        }
81    }
82
83    /// Capture response bodies.
84    #[must_use]
85    pub const fn response(make_sink: M) -> Self {
86        Self {
87            make_sink,
88            capture_request: false,
89            capture_response: true,
90        }
91    }
92
93    /// Capture request and response bodies.
94    #[must_use]
95    pub const fn bidirectional(make_sink: M) -> Self {
96        Self {
97            make_sink,
98            capture_request: true,
99            capture_response: true,
100        }
101    }
102
103    /// Return the capture sink factory.
104    #[must_use]
105    pub const fn make_sink(&self) -> &M {
106        &self.make_sink
107    }
108
109    /// Return whether request capture is enabled.
110    #[must_use]
111    pub const fn captures_requests(&self) -> bool {
112        self.capture_request
113    }
114
115    /// Return whether response capture is enabled.
116    #[must_use]
117    pub const fn captures_responses(&self) -> bool {
118        self.capture_response
119    }
120}
121
122impl<M: fmt::Debug> fmt::Debug for BodyCaptureLayer<M> {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        f.debug_struct("BodyCaptureLayer")
125            .field("make_sink", &self.make_sink)
126            .field("capture_request", &self.capture_request)
127            .field("capture_response", &self.capture_response)
128            .finish()
129    }
130}
131
132impl<M, S> Layer<S> for BodyCaptureLayer<M>
133where
134    M: Clone,
135{
136    type Service = BodyCaptureService<M, S>;
137
138    fn layer(&self, inner: S) -> Self::Service {
139        BodyCaptureService {
140            inner,
141            make_sink: self.make_sink.clone(),
142            capture_request: self.capture_request,
143            capture_response: self.capture_response,
144        }
145    }
146
147    fn into_layer(self, inner: S) -> Self::Service {
148        BodyCaptureService {
149            inner,
150            make_sink: self.make_sink,
151            capture_request: self.capture_request,
152            capture_response: self.capture_response,
153        }
154    }
155}
156
157/// Service produced by [`BodyCaptureLayer`].
158#[derive(Clone)]
159pub struct BodyCaptureService<M, S> {
160    inner: S,
161    make_sink: M,
162    capture_request: bool,
163    capture_response: bool,
164}
165
166impl<M, S> BodyCaptureService<M, S> {
167    /// Create a service that captures request and response bodies.
168    #[must_use]
169    pub const fn bidirectional(inner: S, make_sink: M) -> Self {
170        Self {
171            inner,
172            make_sink,
173            capture_request: true,
174            capture_response: true,
175        }
176    }
177
178    define_inner_service_accessors!();
179}
180
181impl<M: fmt::Debug, S: fmt::Debug> fmt::Debug for BodyCaptureService<M, S> {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        f.debug_struct("BodyCaptureService")
184            .field("inner", &self.inner)
185            .field("make_sink", &self.make_sink)
186            .field("capture_request", &self.capture_request)
187            .field("capture_response", &self.capture_response)
188            .finish()
189    }
190}
191
192impl<M, S, ReqBody, ResBody> Service<Request<ReqBody>> for BodyCaptureService<M, S>
193where
194    M: MakeBodyCaptureSink,
195    S: Service<Request<Body>, Output = Response<ResBody>>,
196    ReqBody: StreamingBody<Data = Bytes, Error: Into<BoxError>> + Send + Sync + 'static,
197    ResBody: StreamingBody<Data = Bytes, Error: Into<BoxError>> + Send + Sync + 'static,
198{
199    type Output = Response<Body>;
200    type Error = S::Error;
201
202    async fn serve(&self, request: Request<ReqBody>) -> Result<Self::Output, Self::Error> {
203        let request = if self.capture_request {
204            let (parts, body) = request.into_parts();
205            let body = match self
206                .make_sink
207                .make_sink(CapturedHead::Request(parts.clone()))
208                .await
209            {
210                Some(sink) => Body::new(CaptureBody::new(body.map_err(Into::into), sink)),
211                None => Body::new(body),
212            };
213            Request::from_parts(parts, body)
214        } else {
215            request.map(Body::new)
216        };
217
218        let response = self.inner.serve(request).await?;
219        Ok(if self.capture_response {
220            let (parts, body) = response.into_parts();
221            let body = match self
222                .make_sink
223                .make_sink(CapturedHead::Response(parts.clone()))
224                .await
225            {
226                Some(sink) => Body::new(CaptureBody::new(body.map_err(Into::into), sink)),
227                None => Body::new(body),
228            };
229            Response::from_parts(parts, body)
230        } else {
231            response.map(Body::new)
232        })
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use std::{
239        convert::Infallible,
240        sync::{
241            Arc,
242            atomic::{AtomicUsize, Ordering},
243        },
244    };
245
246    use rama_core::{
247        Layer as _, Service as _, bytes::Bytes, futures::StreamExt as _, service::service_fn,
248    };
249    use rama_http_types::{
250        Body, BodyCaptureEvent, CaptureOutcome, Request, Response, StatusCode, body::Frame,
251        body::util::BodyExt as _,
252    };
253
254    use super::{BodyCaptureLayer, BodyCaptureService, CapturedHead};
255
256    type Capture = (
257        CapturedHead,
258        tokio::sync::mpsc::UnboundedReceiver<BodyCaptureEvent>,
259    );
260
261    async fn recv<T>(receiver: &mut tokio::sync::mpsc::UnboundedReceiver<T>) -> T {
262        tokio::time::timeout(std::time::Duration::from_secs(1), receiver.recv())
263            .await
264            .expect("capture should arrive promptly")
265            .expect("capture channel should remain open")
266    }
267
268    #[tokio::test]
269    async fn bidirectional_capture_streams_both_messages() {
270        let request_polls = Arc::new(AtomicUsize::new(0));
271        let polled = Arc::clone(&request_polls);
272        let request_body = Body::from_stream(
273            rama_core::futures::stream::iter([
274                Ok::<_, Infallible>(Bytes::from_static(b"request ")),
275                Ok(Bytes::from_static(b"body")),
276            ])
277            .inspect(move |_| {
278                polled.fetch_add(1, Ordering::Relaxed);
279            }),
280        );
281
282        let (captures, mut captured) = tokio::sync::mpsc::unbounded_channel::<Capture>();
283        let make_sink = move |head| {
284            let captures = captures.clone();
285            async move {
286                let (events, receiver) = tokio::sync::mpsc::unbounded_channel();
287                captures.send((head, receiver)).unwrap();
288                Some(events)
289            }
290        };
291        let layer = BodyCaptureLayer::bidirectional(make_sink);
292        let service_polls = Arc::clone(&request_polls);
293        let service = layer.into_layer(service_fn(move |request: Request<Body>| {
294            let request_polls = Arc::clone(&service_polls);
295            async move {
296                assert_eq!(request_polls.load(Ordering::Relaxed), 0);
297                assert_eq!(
298                    request.into_body().collect().await.unwrap().to_bytes(),
299                    Bytes::from_static(b"request body")
300                );
301                Ok::<_, Infallible>(
302                    Response::builder()
303                        .status(StatusCode::CREATED)
304                        .body(Body::from("response body"))
305                        .unwrap(),
306                )
307            }
308        }));
309
310        let response = service
311            .serve(
312                Request::builder()
313                    .method("POST")
314                    .uri("https://example.com/")
315                    .body(request_body)
316                    .unwrap(),
317            )
318            .await
319            .unwrap();
320
321        let (head, mut request_events) = recv(&mut captured).await;
322        assert!(matches!(head, CapturedHead::Request(ref parts) if parts.method == "POST"));
323        let mut request_bytes = Vec::new();
324        loop {
325            let event = recv(&mut request_events).await;
326            match event {
327                BodyCaptureEvent::Frame(frame) => {
328                    if let Ok(data) = frame.into_data() {
329                        request_bytes.extend_from_slice(&data);
330                    }
331                }
332                BodyCaptureEvent::End(outcome) => {
333                    assert_eq!(outcome, CaptureOutcome::Complete);
334                    break;
335                }
336            }
337        }
338        assert_eq!(request_bytes, b"request body");
339
340        let (head, mut response_events) = recv(&mut captured).await;
341        assert!(
342            matches!(head, CapturedHead::Response(ref parts) if parts.status == StatusCode::CREATED)
343        );
344        response_events.try_recv().unwrap_err();
345
346        assert_eq!(
347            response.into_body().collect().await.unwrap().to_bytes(),
348            Bytes::from_static(b"response body")
349        );
350        let BodyCaptureEvent::Frame(frame) = recv(&mut response_events).await else {
351            panic!("expected a captured response frame");
352        };
353        assert_eq!(
354            frame.into_data().unwrap(),
355            Bytes::from_static(b"response body")
356        );
357        assert!(matches!(
358            recv(&mut response_events).await,
359            BodyCaptureEvent::End(CaptureOutcome::Complete)
360        ));
361    }
362
363    #[tokio::test]
364    async fn factory_can_skip_capture_from_the_head() {
365        let make_sink = |head| async move {
366            match head {
367                CapturedHead::Request(parts) if parts.method == "GET" => None,
368                _ => Some(|_event: BodyCaptureEvent| async {}),
369            }
370        };
371        let service = BodyCaptureLayer::request(make_sink).into_layer(service_fn(
372            |request: Request<Body>| async move {
373                request.into_body().collect().await.unwrap();
374                Ok::<_, Infallible>(Response::new(Body::empty()))
375            },
376        ));
377
378        service
379            .serve(
380                Request::builder()
381                    .method("GET")
382                    .body(Body::empty())
383                    .unwrap(),
384            )
385            .await
386            .unwrap();
387    }
388
389    #[test]
390    fn frame_is_cloneable_when_its_data_is_cloneable() {
391        let frame = Frame::data(Bytes::from_static(b"shared"));
392        assert_eq!(
393            frame.clone().into_data().unwrap(),
394            frame.into_data().unwrap()
395        );
396    }
397
398    #[test]
399    fn constructors_and_debug_report_capture_directions() {
400        let request = BodyCaptureLayer::request("factory");
401        assert_eq!(request.make_sink(), &"factory");
402        assert!(request.captures_requests());
403        assert!(!request.captures_responses());
404        assert!(format!("{request:?}").contains("BodyCaptureLayer"));
405
406        let response = BodyCaptureLayer::response("factory");
407        assert!(!response.captures_requests());
408        assert!(response.captures_responses());
409
410        let both = BodyCaptureLayer::bidirectional("factory");
411        assert!(both.captures_requests());
412        assert!(both.captures_responses());
413
414        let service = BodyCaptureService::bidirectional("inner", "factory");
415        assert!(format!("{service:?}").contains("BodyCaptureService"));
416
417        let (request_parts, ()) = Request::builder()
418            .method("POST")
419            .body(())
420            .unwrap()
421            .into_parts();
422        let request_head = format!("{:?}", CapturedHead::Request(request_parts));
423        assert!(request_head.starts_with("Request("));
424        assert!(request_head.contains("POST"));
425
426        let (response_parts, ()) = Response::builder()
427            .status(StatusCode::CREATED)
428            .body(())
429            .unwrap()
430            .into_parts();
431        let response_head = format!("{:?}", CapturedHead::Response(response_parts));
432        assert!(response_head.starts_with("Response("));
433        assert!(response_head.contains("201"));
434    }
435}