actix_web/response/
builder.rs

1use std::{
2    cell::{Ref, RefMut},
3    future::Future,
4    pin::Pin,
5    task::{Context, Poll},
6};
7
8use actix_http::{error::HttpError, Response, ResponseHead};
9use bytes::Bytes;
10use futures_core::Stream;
11use serde::Serialize;
12
13use crate::{
14    body::{BodyStream, BoxBody, MessageBody},
15    dev::Extensions,
16    error::{Error, JsonPayloadError},
17    http::{
18        header::{self, HeaderName, TryIntoHeaderPair, TryIntoHeaderValue},
19        ConnectionType, StatusCode,
20    },
21    BoxError, HttpRequest, HttpResponse, Responder,
22};
23
24/// An HTTP response builder.
25///
26/// This type can be used to construct an instance of `Response` through a builder-like pattern.
27pub struct HttpResponseBuilder {
28    res: Option<Response<BoxBody>>,
29    error: Option<HttpError>,
30}
31
32impl HttpResponseBuilder {
33    #[inline]
34    /// Create response builder
35    pub fn new(status: StatusCode) -> Self {
36        Self {
37            res: Some(Response::with_body(status, BoxBody::new(()))),
38            error: None,
39        }
40    }
41
42    /// Set HTTP status code of this response.
43    #[inline]
44    pub fn status(&mut self, status: StatusCode) -> &mut Self {
45        if let Some(parts) = self.inner() {
46            parts.status = status;
47        }
48        self
49    }
50
51    /// Insert a header, replacing any that were set with an equivalent field name.
52    ///
53    /// ```
54    /// use actix_web::{HttpResponse, http::header};
55    ///
56    /// HttpResponse::Ok()
57    ///     .insert_header(header::ContentType(mime::APPLICATION_JSON))
58    ///     .insert_header(("X-TEST", "value"))
59    ///     .finish();
60    /// ```
61    pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
62        if let Some(parts) = self.inner() {
63            match header.try_into_pair() {
64                Ok((key, value)) => {
65                    parts.headers.insert(key, value);
66                }
67                Err(err) => self.error = Some(err.into()),
68            };
69        }
70
71        self
72    }
73
74    /// Append a header, keeping any that were set with an equivalent field name.
75    ///
76    /// ```
77    /// use actix_web::{HttpResponse, http::header};
78    ///
79    /// HttpResponse::Ok()
80    ///     .append_header(header::ContentType(mime::APPLICATION_JSON))
81    ///     .append_header(("X-TEST", "value1"))
82    ///     .append_header(("X-TEST", "value2"))
83    ///     .finish();
84    /// ```
85    pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
86        if let Some(parts) = self.inner() {
87            match header.try_into_pair() {
88                Ok((key, value)) => parts.headers.append(key, value),
89                Err(err) => self.error = Some(err.into()),
90            };
91        }
92
93        self
94    }
95
96    /// Replaced with [`Self::insert_header()`].
97    #[doc(hidden)]
98    #[deprecated(
99        since = "4.0.0",
100        note = "Replaced with `insert_header((key, value))`. Will be removed in v5."
101    )]
102    pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
103    where
104        K: TryInto<HeaderName>,
105        K::Error: Into<HttpError>,
106        V: TryIntoHeaderValue,
107    {
108        if self.error.is_some() {
109            return self;
110        }
111
112        match (key.try_into(), value.try_into_value()) {
113            (Ok(name), Ok(value)) => return self.insert_header((name, value)),
114            (Err(err), _) => self.error = Some(err.into()),
115            (_, Err(err)) => self.error = Some(err.into()),
116        }
117
118        self
119    }
120
121    /// Replaced with [`Self::append_header()`].
122    #[doc(hidden)]
123    #[deprecated(
124        since = "4.0.0",
125        note = "Replaced with `append_header((key, value))`. Will be removed in v5."
126    )]
127    pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
128    where
129        K: TryInto<HeaderName>,
130        K::Error: Into<HttpError>,
131        V: TryIntoHeaderValue,
132    {
133        if self.error.is_some() {
134            return self;
135        }
136
137        match (key.try_into(), value.try_into_value()) {
138            (Ok(name), Ok(value)) => return self.append_header((name, value)),
139            (Err(err), _) => self.error = Some(err.into()),
140            (_, Err(err)) => self.error = Some(err.into()),
141        }
142
143        self
144    }
145
146    /// Set the custom reason for the response.
147    #[inline]
148    pub fn reason(&mut self, reason: &'static str) -> &mut Self {
149        if let Some(parts) = self.inner() {
150            parts.reason = Some(reason);
151        }
152        self
153    }
154
155    /// Set connection type to KeepAlive
156    #[inline]
157    pub fn keep_alive(&mut self) -> &mut Self {
158        if let Some(parts) = self.inner() {
159            parts.set_connection_type(ConnectionType::KeepAlive);
160        }
161        self
162    }
163
164    /// Set connection type to Upgrade
165    #[inline]
166    pub fn upgrade<V>(&mut self, value: V) -> &mut Self
167    where
168        V: TryIntoHeaderValue,
169    {
170        if let Some(parts) = self.inner() {
171            parts.set_connection_type(ConnectionType::Upgrade);
172        }
173
174        if let Ok(value) = value.try_into_value() {
175            self.insert_header((header::UPGRADE, value));
176        }
177
178        self
179    }
180
181    /// Force close connection, even if it is marked as keep-alive
182    #[inline]
183    pub fn force_close(&mut self) -> &mut Self {
184        if let Some(parts) = self.inner() {
185            parts.set_connection_type(ConnectionType::Close);
186        }
187        self
188    }
189
190    /// Disable chunked transfer encoding for HTTP/1.1 streaming responses.
191    #[inline]
192    pub fn no_chunking(&mut self, len: u64) -> &mut Self {
193        let mut buf = itoa::Buffer::new();
194        self.insert_header((header::CONTENT_LENGTH, buf.format(len)));
195
196        if let Some(parts) = self.inner() {
197            parts.no_chunking(true);
198        }
199        self
200    }
201
202    /// Set response content type.
203    #[inline]
204    pub fn content_type<V>(&mut self, value: V) -> &mut Self
205    where
206        V: TryIntoHeaderValue,
207    {
208        if let Some(parts) = self.inner() {
209            match value.try_into_value() {
210                Ok(value) => {
211                    parts.headers.insert(header::CONTENT_TYPE, value);
212                }
213                Err(err) => self.error = Some(err.into()),
214            };
215        }
216        self
217    }
218
219    /// Add a cookie to the response.
220    ///
221    /// To send a "removal" cookie, call [`.make_removal()`](cookie::Cookie::make_removal) on the
222    /// given cookie. See [`HttpResponse::add_removal_cookie()`] to learn more.
223    ///
224    /// # Examples
225    /// Send a new cookie:
226    /// ```
227    /// use actix_web::{HttpResponse, cookie::Cookie};
228    ///
229    /// let res = HttpResponse::Ok()
230    ///     .cookie(
231    ///         Cookie::build("name", "value")
232    ///             .domain("www.rust-lang.org")
233    ///             .path("/")
234    ///             .secure(true)
235    ///             .http_only(true)
236    ///             .finish(),
237    ///     )
238    ///     .finish();
239    /// ```
240    ///
241    /// Send a removal cookie:
242    /// ```
243    /// use actix_web::{HttpResponse, cookie::Cookie};
244    ///
245    /// // the name, domain and path match the cookie created in the previous example
246    /// let mut cookie = Cookie::build("name", "value-does-not-matter")
247    ///     .domain("www.rust-lang.org")
248    ///     .path("/")
249    ///     .finish();
250    /// cookie.make_removal();
251    ///
252    /// let res = HttpResponse::Ok()
253    ///     .cookie(cookie)
254    ///     .finish();
255    /// ```
256    #[cfg(feature = "cookies")]
257    pub fn cookie(&mut self, cookie: cookie::Cookie<'_>) -> &mut Self {
258        match cookie.to_string().try_into_value() {
259            Ok(hdr_val) => self.append_header((header::SET_COOKIE, hdr_val)),
260            Err(err) => {
261                self.error = Some(err.into());
262                self
263            }
264        }
265    }
266
267    /// Returns a reference to the response-local data/extensions container.
268    #[inline]
269    pub fn extensions(&self) -> Ref<'_, Extensions> {
270        self.res
271            .as_ref()
272            .expect("cannot reuse response builder")
273            .extensions()
274    }
275
276    /// Returns a mutable reference to the response-local data/extensions container.
277    #[inline]
278    pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
279        self.res
280            .as_mut()
281            .expect("cannot reuse response builder")
282            .extensions_mut()
283    }
284
285    /// Set a body and build the `HttpResponse`.
286    ///
287    /// Unlike [`message_body`](Self::message_body), errors are converted into error
288    /// responses immediately.
289    ///
290    /// `HttpResponseBuilder` can not be used after this call.
291    pub fn body<B>(&mut self, body: B) -> HttpResponse<BoxBody>
292    where
293        B: MessageBody + 'static,
294    {
295        match self.message_body(body) {
296            Ok(res) => res.map_into_boxed_body(),
297            Err(err) => HttpResponse::from_error(err),
298        }
299    }
300
301    /// Set a body and build the `HttpResponse`.
302    ///
303    /// `HttpResponseBuilder` can not be used after this call.
304    pub fn message_body<B>(&mut self, body: B) -> Result<HttpResponse<B>, Error> {
305        if let Some(err) = self.error.take() {
306            return Err(err.into());
307        }
308
309        let res = self
310            .res
311            .take()
312            .expect("cannot reuse response builder")
313            .set_body(body);
314
315        Ok(HttpResponse::from(res))
316    }
317
318    /// Set a streaming body and build the `HttpResponse`.
319    ///
320    /// `HttpResponseBuilder` can not be used after this call.
321    #[inline]
322    pub fn streaming<S, E>(&mut self, stream: S) -> HttpResponse
323    where
324        S: Stream<Item = Result<Bytes, E>> + 'static,
325        E: Into<BoxError> + 'static,
326    {
327        self.body(BodyStream::new(stream))
328    }
329
330    /// Set a JSON body and build the `HttpResponse`.
331    ///
332    /// `HttpResponseBuilder` can not be used after this call.
333    pub fn json(&mut self, value: impl Serialize) -> HttpResponse {
334        match serde_json::to_string(&value) {
335            Ok(body) => {
336                let contains = if let Some(parts) = self.inner() {
337                    parts.headers.contains_key(header::CONTENT_TYPE)
338                } else {
339                    true
340                };
341
342                if !contains {
343                    self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
344                }
345
346                self.body(body)
347            }
348            Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)),
349        }
350    }
351
352    /// Set an empty body and build the `HttpResponse`.
353    ///
354    /// `HttpResponseBuilder` can not be used after this call.
355    #[inline]
356    pub fn finish(&mut self) -> HttpResponse {
357        self.body(())
358    }
359
360    /// This method construct new `HttpResponseBuilder`
361    pub fn take(&mut self) -> Self {
362        Self {
363            res: self.res.take(),
364            error: self.error.take(),
365        }
366    }
367
368    fn inner(&mut self) -> Option<&mut ResponseHead> {
369        if self.error.is_some() {
370            return None;
371        }
372
373        self.res.as_mut().map(Response::head_mut)
374    }
375}
376
377impl From<HttpResponseBuilder> for HttpResponse {
378    fn from(mut builder: HttpResponseBuilder) -> Self {
379        builder.finish()
380    }
381}
382
383impl From<HttpResponseBuilder> for Response<BoxBody> {
384    fn from(mut builder: HttpResponseBuilder) -> Self {
385        builder.finish().into()
386    }
387}
388
389impl Future for HttpResponseBuilder {
390    type Output = Result<HttpResponse, Error>;
391
392    fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
393        Poll::Ready(Ok(self.finish()))
394    }
395}
396
397impl Responder for HttpResponseBuilder {
398    type Body = BoxBody;
399
400    #[inline]
401    fn respond_to(mut self, _: &HttpRequest) -> HttpResponse<Self::Body> {
402        self.finish()
403    }
404}
405
406#[cfg(test)]
407mod tests {
408    use super::*;
409    use crate::{
410        body,
411        http::header::{HeaderValue, CONTENT_TYPE},
412        test::assert_body_eq,
413    };
414
415    #[test]
416    fn test_basic_builder() {
417        let resp = HttpResponse::Ok()
418            .insert_header(("X-TEST", "value"))
419            .finish();
420        assert_eq!(resp.status(), StatusCode::OK);
421    }
422
423    #[test]
424    fn test_upgrade() {
425        let resp = HttpResponseBuilder::new(StatusCode::OK)
426            .upgrade("websocket")
427            .finish();
428        assert!(resp.upgrade());
429        assert_eq!(
430            resp.headers().get(header::UPGRADE).unwrap(),
431            HeaderValue::from_static("websocket")
432        );
433    }
434
435    #[test]
436    fn test_force_close() {
437        let resp = HttpResponseBuilder::new(StatusCode::OK)
438            .force_close()
439            .finish();
440        assert!(!resp.keep_alive())
441    }
442
443    #[test]
444    fn test_content_type() {
445        let resp = HttpResponseBuilder::new(StatusCode::OK)
446            .content_type("text/plain")
447            .body(Bytes::new());
448        assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
449    }
450
451    #[actix_rt::test]
452    async fn test_json() {
453        let res = HttpResponse::Ok().json(vec!["v1", "v2", "v3"]);
454        let ct = res.headers().get(CONTENT_TYPE).unwrap();
455        assert_eq!(ct, HeaderValue::from_static("application/json"));
456        assert_body_eq!(res, br#"["v1","v2","v3"]"#);
457
458        let res = HttpResponse::Ok().json(["v1", "v2", "v3"]);
459        let ct = res.headers().get(CONTENT_TYPE).unwrap();
460        assert_eq!(ct, HeaderValue::from_static("application/json"));
461        assert_body_eq!(res, br#"["v1","v2","v3"]"#);
462
463        // content type override
464        let res = HttpResponse::Ok()
465            .insert_header((CONTENT_TYPE, "text/json"))
466            .json(["v1", "v2", "v3"]);
467        let ct = res.headers().get(CONTENT_TYPE).unwrap();
468        assert_eq!(ct, HeaderValue::from_static("text/json"));
469        assert_body_eq!(res, br#"["v1","v2","v3"]"#);
470    }
471
472    #[actix_rt::test]
473    async fn test_serde_json_in_body() {
474        let resp = HttpResponse::Ok()
475            .body(serde_json::to_vec(&serde_json::json!({ "test-key": "test-value" })).unwrap());
476
477        assert_eq!(
478            body::to_bytes(resp.into_body()).await.unwrap().as_ref(),
479            br#"{"test-key":"test-value"}"#
480        );
481    }
482
483    #[test]
484    fn response_builder_header_insert_kv() {
485        let mut res = HttpResponse::Ok();
486        res.insert_header(("Content-Type", "application/octet-stream"));
487        let res = res.finish();
488
489        assert_eq!(
490            res.headers().get("Content-Type"),
491            Some(&HeaderValue::from_static("application/octet-stream"))
492        );
493    }
494
495    #[test]
496    fn response_builder_header_insert_typed() {
497        let mut res = HttpResponse::Ok();
498        res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
499        let res = res.finish();
500
501        assert_eq!(
502            res.headers().get("Content-Type"),
503            Some(&HeaderValue::from_static("application/octet-stream"))
504        );
505    }
506
507    #[test]
508    fn response_builder_header_append_kv() {
509        let mut res = HttpResponse::Ok();
510        res.append_header(("Content-Type", "application/octet-stream"));
511        res.append_header(("Content-Type", "application/json"));
512        let res = res.finish();
513
514        let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
515        assert_eq!(headers.len(), 2);
516        assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
517        assert!(headers.contains(&HeaderValue::from_static("application/json")));
518    }
519
520    #[test]
521    fn response_builder_header_append_typed() {
522        let mut res = HttpResponse::Ok();
523        res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
524        res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
525        let res = res.finish();
526
527        let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
528        assert_eq!(headers.len(), 2);
529        assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
530        assert!(headers.contains(&HeaderValue::from_static("application/json")));
531    }
532}