actix_web/response/
response.rs

1use std::{
2    cell::{Ref, RefMut},
3    fmt,
4};
5
6use actix_http::{
7    body::{BoxBody, EitherBody, MessageBody},
8    header::HeaderMap,
9    Extensions, Response, ResponseHead, StatusCode,
10};
11#[cfg(feature = "cookies")]
12use {
13    actix_http::{
14        error::HttpError,
15        header::{self, HeaderValue},
16    },
17    cookie::Cookie,
18};
19
20use crate::{error::Error, HttpRequest, HttpResponseBuilder, Responder};
21
22/// An outgoing response.
23pub struct HttpResponse<B = BoxBody> {
24    res: Response<B>,
25    error: Option<Error>,
26}
27
28impl HttpResponse<BoxBody> {
29    /// Constructs a response.
30    #[inline]
31    pub fn new(status: StatusCode) -> Self {
32        Self {
33            res: Response::new(status),
34            error: None,
35        }
36    }
37
38    /// Constructs a response builder with specific HTTP status.
39    #[inline]
40    pub fn build(status: StatusCode) -> HttpResponseBuilder {
41        HttpResponseBuilder::new(status)
42    }
43
44    /// Create an error response.
45    #[inline]
46    pub fn from_error(error: impl Into<Error>) -> Self {
47        let error = error.into();
48        let mut response = error.as_response_error().error_response();
49        response.error = Some(error);
50        response
51    }
52}
53
54impl<B> HttpResponse<B> {
55    /// Constructs a response with body
56    #[inline]
57    pub fn with_body(status: StatusCode, body: B) -> Self {
58        Self {
59            res: Response::with_body(status, body),
60            error: None,
61        }
62    }
63
64    /// Returns a reference to response head.
65    #[inline]
66    pub fn head(&self) -> &ResponseHead {
67        self.res.head()
68    }
69
70    /// Returns a mutable reference to response head.
71    #[inline]
72    pub fn head_mut(&mut self) -> &mut ResponseHead {
73        self.res.head_mut()
74    }
75
76    /// The source `error` for this response
77    #[inline]
78    pub fn error(&self) -> Option<&Error> {
79        self.error.as_ref()
80    }
81
82    /// Get the response status code
83    #[inline]
84    pub fn status(&self) -> StatusCode {
85        self.res.status()
86    }
87
88    /// Set the `StatusCode` for this response
89    #[inline]
90    pub fn status_mut(&mut self) -> &mut StatusCode {
91        self.res.status_mut()
92    }
93
94    /// Get the headers from the response
95    #[inline]
96    pub fn headers(&self) -> &HeaderMap {
97        self.res.headers()
98    }
99
100    /// Get a mutable reference to the headers
101    #[inline]
102    pub fn headers_mut(&mut self) -> &mut HeaderMap {
103        self.res.headers_mut()
104    }
105
106    /// Get an iterator for the cookies set by this response.
107    #[cfg(feature = "cookies")]
108    pub fn cookies(&self) -> CookieIter<'_> {
109        CookieIter {
110            iter: self.headers().get_all(header::SET_COOKIE),
111        }
112    }
113
114    /// Add a cookie to this response.
115    ///
116    /// # Errors
117    /// Returns an error if the cookie results in a malformed `Set-Cookie` header.
118    #[cfg(feature = "cookies")]
119    pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> {
120        HeaderValue::from_str(&cookie.to_string())
121            .map(|cookie| self.headers_mut().append(header::SET_COOKIE, cookie))
122            .map_err(Into::into)
123    }
124
125    /// Add a "removal" cookie to the response that matches attributes of given cookie.
126    ///
127    /// This will cause browsers/clients to remove stored cookies with this name.
128    ///
129    /// The `Set-Cookie` header added to the response will have:
130    /// - name matching given cookie;
131    /// - domain matching given cookie;
132    /// - path matching given cookie;
133    /// - an empty value;
134    /// - a max-age of `0`;
135    /// - an expiration date far in the past.
136    ///
137    /// If the cookie you're trying to remove has an explicit path or domain set, those attributes
138    /// will need to be included in the cookie passed in here.
139    ///
140    /// # Errors
141    /// Returns an error if the given name results in a malformed `Set-Cookie` header.
142    #[cfg(feature = "cookies")]
143    pub fn add_removal_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> {
144        let mut removal_cookie = cookie.to_owned();
145        removal_cookie.make_removal();
146
147        HeaderValue::from_str(&removal_cookie.to_string())
148            .map(|cookie| self.headers_mut().append(header::SET_COOKIE, cookie))
149            .map_err(Into::into)
150    }
151
152    /// Remove all cookies with the given name from this response.
153    ///
154    /// Returns the number of cookies removed.
155    ///
156    /// This method can _not_ cause a browser/client to delete any of its stored cookies. Its only
157    /// purpose is to delete cookies that were added to this response using [`add_cookie`]
158    /// and [`add_removal_cookie`]. Use [`add_removal_cookie`] to send a "removal" cookie.
159    ///
160    /// [`add_cookie`]: Self::add_cookie
161    /// [`add_removal_cookie`]: Self::add_removal_cookie
162    #[cfg(feature = "cookies")]
163    pub fn del_cookie(&mut self, name: &str) -> usize {
164        let headers = self.headers_mut();
165
166        let vals: Vec<HeaderValue> = headers
167            .get_all(header::SET_COOKIE)
168            .map(|v| v.to_owned())
169            .collect();
170
171        headers.remove(header::SET_COOKIE);
172
173        let mut count: usize = 0;
174
175        for v in vals {
176            if let Ok(s) = v.to_str() {
177                if let Ok(c) = Cookie::parse_encoded(s) {
178                    if c.name() == name {
179                        count += 1;
180                        continue;
181                    }
182                }
183            }
184
185            // put set-cookie header head back if it does not validate
186            headers.append(header::SET_COOKIE, v);
187        }
188
189        count
190    }
191
192    /// Connection upgrade status
193    #[inline]
194    pub fn upgrade(&self) -> bool {
195        self.res.upgrade()
196    }
197
198    /// Keep-alive status for this connection
199    pub fn keep_alive(&self) -> bool {
200        self.res.keep_alive()
201    }
202
203    /// Returns reference to the response-local data/extensions container.
204    #[inline]
205    pub fn extensions(&self) -> Ref<'_, Extensions> {
206        self.res.extensions()
207    }
208
209    /// Returns reference to the response-local data/extensions container.
210    #[inline]
211    pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
212        self.res.extensions_mut()
213    }
214
215    /// Returns a reference to this response's body.
216    #[inline]
217    pub fn body(&self) -> &B {
218        self.res.body()
219    }
220
221    /// Sets new body.
222    pub fn set_body<B2>(self, body: B2) -> HttpResponse<B2> {
223        HttpResponse {
224            res: self.res.set_body(body),
225            error: self.error,
226        }
227    }
228
229    /// Returns split head and body.
230    ///
231    /// # Implementation Notes
232    /// Due to internal performance optimizations, the first element of the returned tuple is an
233    /// `HttpResponse` as well but only contains the head of the response this was called on.
234    pub fn into_parts(self) -> (HttpResponse<()>, B) {
235        let (head, body) = self.res.into_parts();
236
237        (
238            HttpResponse {
239                res: head,
240                error: self.error,
241            },
242            body,
243        )
244    }
245
246    /// Drops body and returns new response.
247    pub fn drop_body(self) -> HttpResponse<()> {
248        HttpResponse {
249            res: self.res.drop_body(),
250            error: self.error,
251        }
252    }
253
254    /// Map the current body type to another using a closure, returning a new response.
255    ///
256    /// Closure receives the response head and the current body type.
257    pub fn map_body<F, B2>(self, f: F) -> HttpResponse<B2>
258    where
259        F: FnOnce(&mut ResponseHead, B) -> B2,
260    {
261        HttpResponse {
262            res: self.res.map_body(f),
263            error: self.error,
264        }
265    }
266
267    /// Map the current body type `B` to `EitherBody::Left(B)`.
268    ///
269    /// Useful for middleware which can generate their own responses.
270    #[inline]
271    pub fn map_into_left_body<R>(self) -> HttpResponse<EitherBody<B, R>> {
272        self.map_body(|_, body| EitherBody::left(body))
273    }
274
275    /// Map the current body type `B` to `EitherBody::Right(B)`.
276    ///
277    /// Useful for middleware which can generate their own responses.
278    #[inline]
279    pub fn map_into_right_body<L>(self) -> HttpResponse<EitherBody<L, B>> {
280        self.map_body(|_, body| EitherBody::right(body))
281    }
282
283    /// Map the current body to a type-erased `BoxBody`.
284    #[inline]
285    pub fn map_into_boxed_body(self) -> HttpResponse<BoxBody>
286    where
287        B: MessageBody + 'static,
288    {
289        self.map_body(|_, body| body.boxed())
290    }
291
292    /// Returns the response body, dropping all other parts.
293    pub fn into_body(self) -> B {
294        self.res.into_body()
295    }
296}
297
298impl<B> fmt::Debug for HttpResponse<B>
299where
300    B: MessageBody,
301{
302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303        f.debug_struct("HttpResponse")
304            .field("error", &self.error)
305            .field("res", &self.res)
306            .finish()
307    }
308}
309
310impl<B> From<Response<B>> for HttpResponse<B> {
311    fn from(res: Response<B>) -> Self {
312        HttpResponse { res, error: None }
313    }
314}
315
316impl From<Error> for HttpResponse {
317    fn from(err: Error) -> Self {
318        HttpResponse::from_error(err)
319    }
320}
321
322impl<B> From<HttpResponse<B>> for Response<B> {
323    fn from(res: HttpResponse<B>) -> Self {
324        // this impl will always be called as part of dispatcher
325        res.res
326    }
327}
328
329// Rationale for cfg(test): this impl causes false positives on a clippy lint (async_yields_async)
330// when returning an HttpResponse from an async function/closure and it's not very useful outside of
331// tests anyway.
332#[cfg(test)]
333mod response_fut_impl {
334    use std::{
335        future::Future,
336        mem,
337        pin::Pin,
338        task::{Context, Poll},
339    };
340
341    use super::*;
342
343    // Future is only implemented for BoxBody payload type because it's the most useful for making
344    // simple handlers without async blocks. Making it generic over all MessageBody types requires a
345    // future impl on Response which would cause its body field to be, undesirably, Option<B>.
346    //
347    // This impl is not particularly efficient due to the Response construction and should probably
348    // not be invoked if performance is important. Prefer an async fn/block in such cases.
349    impl Future for HttpResponse<BoxBody> {
350        type Output = Result<Response<BoxBody>, Error>;
351
352        fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
353            if let Some(err) = self.error.take() {
354                return Poll::Ready(Err(err));
355            }
356
357            Poll::Ready(Ok(mem::replace(
358                &mut self.res,
359                Response::new(StatusCode::default()),
360            )))
361        }
362    }
363}
364
365impl<B> Responder for HttpResponse<B>
366where
367    B: MessageBody + 'static,
368{
369    type Body = B;
370
371    #[inline]
372    fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
373        self
374    }
375}
376
377#[cfg(feature = "cookies")]
378pub struct CookieIter<'a> {
379    iter: std::slice::Iter<'a, HeaderValue>,
380}
381
382#[cfg(feature = "cookies")]
383impl<'a> Iterator for CookieIter<'a> {
384    type Item = Cookie<'a>;
385
386    #[inline]
387    fn next(&mut self) -> Option<Cookie<'a>> {
388        for v in self.iter.by_ref() {
389            if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) {
390                return Some(c);
391            }
392        }
393        None
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use static_assertions::assert_impl_all;
400
401    use super::*;
402    use crate::http::header::COOKIE;
403
404    assert_impl_all!(HttpResponse: Responder);
405    assert_impl_all!(HttpResponse<String>: Responder);
406    assert_impl_all!(HttpResponse<&'static str>: Responder);
407    assert_impl_all!(HttpResponse<crate::body::None>: Responder);
408
409    #[test]
410    fn test_debug() {
411        let resp = HttpResponse::Ok()
412            .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; ")))
413            .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; ")))
414            .finish();
415        let dbg = format!("{:?}", resp);
416        assert!(dbg.contains("HttpResponse"));
417    }
418}
419
420#[cfg(test)]
421#[cfg(feature = "cookies")]
422mod cookie_tests {
423    use super::*;
424
425    #[test]
426    fn removal_cookies() {
427        let mut res = HttpResponse::Ok().finish();
428        let cookie = Cookie::new("foo", "");
429        res.add_removal_cookie(&cookie).unwrap();
430        let set_cookie_hdr = res.headers().get(header::SET_COOKIE).unwrap();
431        assert_eq!(
432            &set_cookie_hdr.as_bytes()[..25],
433            &b"foo=; Max-Age=0; Expires="[..],
434            "unexpected set-cookie value: {:?}",
435            set_cookie_hdr.to_str()
436        );
437    }
438}