trillium/request_body.rs
1use crate::{Error, Transport};
2use futures_lite::AsyncRead;
3use trillium_http::ReceivedBody;
4use trillium_macros::AsyncRead;
5
6/// A received request body
7///
8/// This type represents a body that will be read from the underlying transport
9///
10/// ```rust
11/// # use trillium_testing::TestServer;
12/// # trillium_testing::block_on(async move {
13/// let app = TestServer::new(|mut conn: trillium::Conn| async move {
14/// let body = conn.request_body(); // 100-continue sent lazily on first read if needed
15/// let Ok(body_string) = body.with_max_len(1024).read_string().await else {
16/// return conn.with_status(500);
17/// };
18///
19/// conn.with_body(format!("received: {body_string}"))
20/// })
21/// .await;
22///
23/// app.get("/").await.assert_body("received: ");
24/// app.post("/")
25/// .with_body("hello")
26/// .await
27/// .assert_body("received: hello");
28/// # });
29/// ```
30///
31/// ## Bounds checking
32///
33/// Every `RequestBody` has a maximum length beyond which it will return an error, expressed as a
34/// u64. To override this on the specific `RequestBody`, use [`RequestBody::with_max_len`] or
35/// [`RequestBody::set_max_len`]
36#[derive(AsyncRead, Debug)]
37pub struct RequestBody<'a>(ReceivedBody<'a, Box<dyn Transport>>);
38
39impl RequestBody<'_> {
40 /// Similar to [`RequestBody::read_string`], but returns the raw bytes. This is useful for
41 /// bodies that are not text.
42 ///
43 /// You can use this in conjunction with `encoding` if you need different handling of malformed
44 /// character encoding than the lossy conversion provided by [`RequestBody::read_string`].
45 ///
46 /// An empty or nonexistent body will yield an empty Vec, not an error.
47 ///
48 /// # Errors
49 ///
50 /// This will return an error if there is an IO error on the underlying transport such as a
51 /// disconnect
52 ///
53 /// This will also return an error if the length exceeds the maximum length. To configure the
54 /// value on this specific request body, use [`RequestBody::with_max_len`] or
55 /// [`RequestBody::set_max_len`]
56 pub async fn read_bytes(self) -> Result<Vec<u8>, Error> {
57 self.0.read_bytes().await
58 }
59
60 /// # Reads the entire body to `String`.
61 ///
62 /// This uses the encoding determined by the content-type (mime) charset. If an encoding problem
63 /// is encountered, the String returned by [`RequestBody::read_string`] will contain utf8
64 /// replacement characters.
65 ///
66 /// Note that this can only be performed once per Conn, as the underlying data is not cached
67 /// anywhere. This is the only copy of the body contents.
68 ///
69 /// An empty or nonexistent body will yield an empty String, not an error
70 ///
71 /// # Errors
72 ///
73 /// This will return an error if there is an IO error on the
74 /// underlying transport such as a disconnect
75 ///
76 ///
77 /// This will also return an error if the length exceeds the maximum length. To configure the
78 /// value on this specific request body, use [`RequestBody::with_max_len`] or
79 /// [`RequestBody::set_max_len`].
80 pub async fn read_string(self) -> Result<String, Error> {
81 self.0.read_string().await
82 }
83
84 /// Set the maximum content length to read, returning self
85 ///
86 /// This protects against an memory-use denial-of-service attack wherein an untrusted peer sends
87 /// an unbounded request body. This is especially important when using
88 /// [`RequestBody::read_string`] and [`RequestBody::read_bytes`] instead of streaming with
89 /// `AsyncRead`.
90 ///
91 /// The default value can be found documented [in the trillium-http
92 /// crate](https://docs.trillium.rs/trillium_http/struct.httpconfig#received_body_max_len)
93 #[must_use]
94 pub fn with_max_len(mut self, max_len: u64) -> Self {
95 self.0.set_max_len(max_len);
96 self
97 }
98
99 /// Set the maximum content length to read
100 ///
101 /// This protects against an memory-use denial-of-service attack wherein an untrusted peer sends
102 /// an unbounded request body. This is especially important when using
103 /// [`RequestBody::read_string`] and [`RequestBody::read_bytes`] instead of streaming with
104 /// `AsyncRead`.
105 ///
106 /// The default value can be found documented [in the trillium-http
107 /// crate](https://docs.trillium.rs/trillium_http/struct.httpconfig#received_body_max_len)
108 pub fn set_max_len(&mut self, max_len: u64) -> &mut Self {
109 self.0.set_max_len(max_len);
110 self
111 }
112
113 /// The content-length of this body, if available.
114 ///
115 /// This value usually is derived from the content-length header. If the request that this body
116 /// is attached to uses transfer-encoding chunked, this will be None.
117 pub fn content_length(&self) -> Option<u64> {
118 self.0.content_length()
119 }
120}
121
122impl<'a> From<RequestBody<'a>> for ReceivedBody<'a, Box<dyn Transport>> {
123 fn from(value: RequestBody<'a>) -> Self {
124 value.0
125 }
126}
127
128impl<'a> From<ReceivedBody<'a, Box<dyn Transport>>> for RequestBody<'a> {
129 fn from(received_body: ReceivedBody<'a, Box<dyn Transport>>) -> Self {
130 Self(received_body)
131 }
132}