actix_http/responses/
builder.rs

1//! HTTP response builder.
2
3use std::{cell::RefCell, fmt, str};
4
5use crate::{
6    body::{EitherBody, MessageBody},
7    error::{Error, HttpError},
8    header::{self, TryIntoHeaderPair, TryIntoHeaderValue},
9    responses::{BoxedResponseHead, ResponseHead},
10    ConnectionType, Extensions, Response, StatusCode,
11};
12
13/// An HTTP response builder.
14///
15/// Used to construct an instance of `Response` using a builder pattern. Response builders are often
16/// created using [`Response::build`].
17///
18/// # Examples
19/// ```
20/// use actix_http::{Response, ResponseBuilder, StatusCode, body, header};
21///
22/// # actix_rt::System::new().block_on(async {
23/// let mut res: Response<_> = Response::build(StatusCode::OK)
24///     .content_type(mime::APPLICATION_JSON)
25///     .insert_header((header::SERVER, "my-app/1.0"))
26///     .append_header((header::SET_COOKIE, "a=1"))
27///     .append_header((header::SET_COOKIE, "b=2"))
28///     .body("1234");
29///
30/// assert_eq!(res.status(), StatusCode::OK);
31///
32/// assert!(res.headers().contains_key("server"));
33/// assert_eq!(res.headers().get_all("set-cookie").count(), 2);
34///
35/// assert_eq!(body::to_bytes(res.into_body()).await.unwrap(), &b"1234"[..]);
36/// # })
37/// ```
38pub struct ResponseBuilder {
39    head: Option<BoxedResponseHead>,
40    err: Option<HttpError>,
41}
42
43impl ResponseBuilder {
44    /// Create response builder
45    ///
46    /// # Examples
47    /// ```
48    /// use actix_http::{Response, ResponseBuilder, StatusCode};
49    /// let res: Response<_> = ResponseBuilder::default().finish();
50    /// assert_eq!(res.status(), StatusCode::OK);
51    /// ```
52    #[inline]
53    pub fn new(status: StatusCode) -> Self {
54        ResponseBuilder {
55            head: Some(BoxedResponseHead::new(status)),
56            err: None,
57        }
58    }
59
60    /// Set HTTP status code of this response.
61    ///
62    /// # Examples
63    /// ```
64    /// use actix_http::{ResponseBuilder, StatusCode};
65    /// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish();
66    /// assert_eq!(res.status(), StatusCode::NOT_FOUND);
67    /// ```
68    #[inline]
69    pub fn status(&mut self, status: StatusCode) -> &mut Self {
70        if let Some(parts) = self.inner() {
71            parts.status = status;
72        }
73        self
74    }
75
76    /// Insert a header, replacing any that were set with an equivalent field name.
77    ///
78    /// # Examples
79    /// ```
80    /// use actix_http::{ResponseBuilder, header};
81    ///
82    /// let res = ResponseBuilder::default()
83    ///     .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON))
84    ///     .insert_header(("X-TEST", "value"))
85    ///     .finish();
86    ///
87    /// assert!(res.headers().contains_key("content-type"));
88    /// assert!(res.headers().contains_key("x-test"));
89    /// ```
90    pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
91        if let Some(parts) = self.inner() {
92            match header.try_into_pair() {
93                Ok((key, value)) => {
94                    parts.headers.insert(key, value);
95                }
96                Err(err) => self.err = Some(err.into()),
97            };
98        }
99
100        self
101    }
102
103    /// Append a header, keeping any that were set with an equivalent field name.
104    ///
105    /// # Examples
106    /// ```
107    /// use actix_http::{ResponseBuilder, header};
108    ///
109    /// let res = ResponseBuilder::default()
110    ///     .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON))
111    ///     .append_header(("X-TEST", "value1"))
112    ///     .append_header(("X-TEST", "value2"))
113    ///     .finish();
114    ///
115    /// assert_eq!(res.headers().get_all("content-type").count(), 1);
116    /// assert_eq!(res.headers().get_all("x-test").count(), 2);
117    /// ```
118    pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
119        if let Some(parts) = self.inner() {
120            match header.try_into_pair() {
121                Ok((key, value)) => parts.headers.append(key, value),
122                Err(err) => self.err = Some(err.into()),
123            };
124        }
125
126        self
127    }
128
129    /// Set the custom reason for the response.
130    #[inline]
131    pub fn reason(&mut self, reason: &'static str) -> &mut Self {
132        if let Some(parts) = self.inner() {
133            parts.reason = Some(reason);
134        }
135        self
136    }
137
138    /// Set connection type to KeepAlive
139    #[inline]
140    pub fn keep_alive(&mut self) -> &mut Self {
141        if let Some(parts) = self.inner() {
142            parts.set_connection_type(ConnectionType::KeepAlive);
143        }
144        self
145    }
146
147    /// Set connection type to `Upgrade`.
148    #[inline]
149    pub fn upgrade<V>(&mut self, value: V) -> &mut Self
150    where
151        V: TryIntoHeaderValue,
152    {
153        if let Some(parts) = self.inner() {
154            parts.set_connection_type(ConnectionType::Upgrade);
155        }
156
157        if let Ok(value) = value.try_into_value() {
158            self.insert_header((header::UPGRADE, value));
159        }
160
161        self
162    }
163
164    /// Force-close connection, even if it is marked as keep-alive.
165    #[inline]
166    pub fn force_close(&mut self) -> &mut Self {
167        if let Some(parts) = self.inner() {
168            parts.set_connection_type(ConnectionType::Close);
169        }
170        self
171    }
172
173    /// Disable chunked transfer encoding for HTTP/1.1 streaming responses.
174    #[inline]
175    pub fn no_chunking(&mut self, len: u64) -> &mut Self {
176        let mut buf = itoa::Buffer::new();
177        self.insert_header((header::CONTENT_LENGTH, buf.format(len)));
178
179        if let Some(parts) = self.inner() {
180            parts.no_chunking(true);
181        }
182        self
183    }
184
185    /// Set response content type.
186    #[inline]
187    pub fn content_type<V>(&mut self, value: V) -> &mut Self
188    where
189        V: TryIntoHeaderValue,
190    {
191        if let Some(parts) = self.inner() {
192            match value.try_into_value() {
193                Ok(value) => {
194                    parts.headers.insert(header::CONTENT_TYPE, value);
195                }
196                Err(err) => self.err = Some(err.into()),
197            };
198        }
199        self
200    }
201
202    /// Generate response with a wrapped body.
203    ///
204    /// This `ResponseBuilder` will be left in a useless state.
205    pub fn body<B>(&mut self, body: B) -> Response<EitherBody<B>>
206    where
207        B: MessageBody + 'static,
208    {
209        match self.message_body(body) {
210            Ok(res) => res.map_body(|_, body| EitherBody::left(body)),
211            Err(err) => Response::from(err).map_body(|_, body| EitherBody::right(body)),
212        }
213    }
214
215    /// Generate response with a body.
216    ///
217    /// This `ResponseBuilder` will be left in a useless state.
218    pub fn message_body<B>(&mut self, body: B) -> Result<Response<B>, Error> {
219        if let Some(err) = self.err.take() {
220            return Err(Error::new_http().with_cause(err));
221        }
222
223        let head = self.head.take().expect("cannot reuse response builder");
224
225        Ok(Response {
226            head,
227            body,
228            extensions: RefCell::new(Extensions::new()),
229        })
230    }
231
232    /// Generate response with an empty body.
233    ///
234    /// This `ResponseBuilder` will be left in a useless state.
235    #[inline]
236    pub fn finish(&mut self) -> Response<EitherBody<()>> {
237        self.body(())
238    }
239
240    /// Create an owned `ResponseBuilder`, leaving the original in a useless state.
241    pub fn take(&mut self) -> ResponseBuilder {
242        ResponseBuilder {
243            head: self.head.take(),
244            err: self.err.take(),
245        }
246    }
247
248    /// Get access to the inner response head if there has been no error.
249    fn inner(&mut self) -> Option<&mut ResponseHead> {
250        if self.err.is_some() {
251            return None;
252        }
253
254        self.head.as_deref_mut()
255    }
256}
257
258impl Default for ResponseBuilder {
259    fn default() -> Self {
260        Self::new(StatusCode::OK)
261    }
262}
263
264/// Convert `Response` to a `ResponseBuilder`. Body get dropped.
265impl<B> From<Response<B>> for ResponseBuilder {
266    fn from(res: Response<B>) -> ResponseBuilder {
267        ResponseBuilder {
268            head: Some(res.head),
269            err: None,
270        }
271    }
272}
273
274/// Convert `ResponseHead` to a `ResponseBuilder`
275impl<'a> From<&'a ResponseHead> for ResponseBuilder {
276    fn from(head: &'a ResponseHead) -> ResponseBuilder {
277        let mut msg = BoxedResponseHead::new(head.status);
278        msg.version = head.version;
279        msg.reason = head.reason;
280
281        for (k, v) in head.headers.iter() {
282            msg.headers.append(k.clone(), v.clone());
283        }
284
285        msg.no_chunking(!head.chunked());
286
287        ResponseBuilder {
288            head: Some(msg),
289            err: None,
290        }
291    }
292}
293
294impl fmt::Debug for ResponseBuilder {
295    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296        let head = self.head.as_ref().unwrap();
297
298        let res = writeln!(
299            f,
300            "\nResponseBuilder {:?} {}{}",
301            head.version,
302            head.status,
303            head.reason.unwrap_or(""),
304        );
305        let _ = writeln!(f, "  headers:");
306        for (key, val) in head.headers.iter() {
307            let _ = writeln!(f, "    {:?}: {:?}", key, val);
308        }
309        res
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use bytes::Bytes;
316
317    use super::*;
318    use crate::header::{HeaderName, HeaderValue, CONTENT_TYPE};
319
320    #[test]
321    fn test_basic_builder() {
322        let resp = Response::build(StatusCode::OK)
323            .insert_header(("X-TEST", "value"))
324            .finish();
325        assert_eq!(resp.status(), StatusCode::OK);
326    }
327
328    #[test]
329    fn test_upgrade() {
330        let resp = Response::build(StatusCode::OK)
331            .upgrade("websocket")
332            .finish();
333        assert!(resp.upgrade());
334        assert_eq!(
335            resp.headers().get(header::UPGRADE).unwrap(),
336            HeaderValue::from_static("websocket")
337        );
338    }
339
340    #[test]
341    fn test_force_close() {
342        let resp = Response::build(StatusCode::OK).force_close().finish();
343        assert!(!resp.keep_alive());
344    }
345
346    #[test]
347    fn test_content_type() {
348        let resp = Response::build(StatusCode::OK)
349            .content_type("text/plain")
350            .body(Bytes::new());
351        assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain");
352
353        let resp = Response::build(StatusCode::OK)
354            .content_type(mime::TEXT_JAVASCRIPT)
355            .body(Bytes::new());
356        assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/javascript");
357    }
358
359    #[test]
360    fn test_into_builder() {
361        let mut resp: Response<_> = "test".into();
362        assert_eq!(resp.status(), StatusCode::OK);
363
364        resp.headers_mut().insert(
365            HeaderName::from_static("cookie"),
366            HeaderValue::from_static("cookie1=val100"),
367        );
368
369        let mut builder: ResponseBuilder = resp.into();
370        let resp = builder.status(StatusCode::BAD_REQUEST).finish();
371        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
372
373        let cookie = resp.headers().get_all("Cookie").next().unwrap();
374        assert_eq!(cookie.to_str().unwrap(), "cookie1=val100");
375    }
376
377    #[test]
378    fn response_builder_header_insert_kv() {
379        let mut res = Response::build(StatusCode::OK);
380        res.insert_header(("Content-Type", "application/octet-stream"));
381        let res = res.finish();
382
383        assert_eq!(
384            res.headers().get("Content-Type"),
385            Some(&HeaderValue::from_static("application/octet-stream"))
386        );
387    }
388
389    #[test]
390    fn response_builder_header_insert_typed() {
391        let mut res = Response::build(StatusCode::OK);
392        res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
393        let res = res.finish();
394
395        assert_eq!(
396            res.headers().get("Content-Type"),
397            Some(&HeaderValue::from_static("application/octet-stream"))
398        );
399    }
400
401    #[test]
402    fn response_builder_header_append_kv() {
403        let mut res = Response::build(StatusCode::OK);
404        res.append_header(("Content-Type", "application/octet-stream"));
405        res.append_header(("Content-Type", "application/json"));
406        let res = res.finish();
407
408        let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
409        assert_eq!(headers.len(), 2);
410        assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
411        assert!(headers.contains(&HeaderValue::from_static("application/json")));
412    }
413
414    #[test]
415    fn response_builder_header_append_typed() {
416        let mut res = Response::build(StatusCode::OK);
417        res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
418        res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
419        let res = res.finish();
420
421        let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
422        assert_eq!(headers.len(), 2);
423        assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
424        assert!(headers.contains(&HeaderValue::from_static("application/json")));
425    }
426}