actix_http/responses/
head.rs

1//! Response head type and caching pool.
2
3use std::{cell::RefCell, ops};
4
5use crate::{header::HeaderMap, message::Flags, ConnectionType, StatusCode, Version};
6
7thread_local! {
8    static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create();
9}
10
11#[derive(Debug, Clone)]
12pub struct ResponseHead {
13    pub version: Version,
14    pub status: StatusCode,
15    pub headers: HeaderMap,
16    pub reason: Option<&'static str>,
17    pub(crate) flags: Flags,
18}
19
20impl ResponseHead {
21    /// Create new instance of `ResponseHead` type
22    #[inline]
23    pub fn new(status: StatusCode) -> ResponseHead {
24        ResponseHead {
25            status,
26            version: Version::HTTP_11,
27            headers: HeaderMap::with_capacity(12),
28            reason: None,
29            flags: Flags::empty(),
30        }
31    }
32
33    /// Read the message headers.
34    #[inline]
35    pub fn headers(&self) -> &HeaderMap {
36        &self.headers
37    }
38
39    /// Mutable reference to the message headers.
40    #[inline]
41    pub fn headers_mut(&mut self) -> &mut HeaderMap {
42        &mut self.headers
43    }
44
45    /// Sets the flag that controls whether to send headers formatted as Camel-Case.
46    ///
47    /// Only applicable to HTTP/1.x responses; HTTP/2 header names are always lowercase.
48    #[inline]
49    pub fn set_camel_case_headers(&mut self, camel_case: bool) {
50        if camel_case {
51            self.flags.insert(Flags::CAMEL_CASE);
52        } else {
53            self.flags.remove(Flags::CAMEL_CASE);
54        }
55    }
56
57    /// Set connection type of the message
58    #[inline]
59    pub fn set_connection_type(&mut self, ctype: ConnectionType) {
60        match ctype {
61            ConnectionType::Close => self.flags.insert(Flags::CLOSE),
62            ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE),
63            ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE),
64        }
65    }
66
67    #[inline]
68    pub fn connection_type(&self) -> ConnectionType {
69        if self.flags.contains(Flags::CLOSE) {
70            ConnectionType::Close
71        } else if self.flags.contains(Flags::KEEP_ALIVE) {
72            ConnectionType::KeepAlive
73        } else if self.flags.contains(Flags::UPGRADE) {
74            ConnectionType::Upgrade
75        } else if self.version < Version::HTTP_11 {
76            ConnectionType::Close
77        } else {
78            ConnectionType::KeepAlive
79        }
80    }
81
82    /// Check if keep-alive is enabled
83    #[inline]
84    pub fn keep_alive(&self) -> bool {
85        self.connection_type() == ConnectionType::KeepAlive
86    }
87
88    /// Check upgrade status of this message
89    #[inline]
90    pub fn upgrade(&self) -> bool {
91        self.connection_type() == ConnectionType::Upgrade
92    }
93
94    /// Get custom reason for the response
95    #[inline]
96    pub fn reason(&self) -> &str {
97        self.reason.unwrap_or_else(|| {
98            self.status
99                .canonical_reason()
100                .unwrap_or("<unknown status code>")
101        })
102    }
103
104    #[inline]
105    pub(crate) fn conn_type(&self) -> Option<ConnectionType> {
106        if self.flags.contains(Flags::CLOSE) {
107            Some(ConnectionType::Close)
108        } else if self.flags.contains(Flags::KEEP_ALIVE) {
109            Some(ConnectionType::KeepAlive)
110        } else if self.flags.contains(Flags::UPGRADE) {
111            Some(ConnectionType::Upgrade)
112        } else {
113            None
114        }
115    }
116
117    /// Get response body chunking state
118    #[inline]
119    pub fn chunked(&self) -> bool {
120        !self.flags.contains(Flags::NO_CHUNKING)
121    }
122
123    /// Set no chunking for payload
124    #[inline]
125    pub fn no_chunking(&mut self, val: bool) {
126        if val {
127            self.flags.insert(Flags::NO_CHUNKING);
128        } else {
129            self.flags.remove(Flags::NO_CHUNKING);
130        }
131    }
132}
133
134pub(crate) struct BoxedResponseHead {
135    head: Option<Box<ResponseHead>>,
136}
137
138impl BoxedResponseHead {
139    /// Get new message from the pool of objects
140    pub fn new(status: StatusCode) -> Self {
141        RESPONSE_POOL.with(|p| p.get_message(status))
142    }
143}
144
145impl ops::Deref for BoxedResponseHead {
146    type Target = ResponseHead;
147
148    fn deref(&self) -> &Self::Target {
149        self.head.as_ref().unwrap()
150    }
151}
152
153impl ops::DerefMut for BoxedResponseHead {
154    fn deref_mut(&mut self) -> &mut Self::Target {
155        self.head.as_mut().unwrap()
156    }
157}
158
159impl Drop for BoxedResponseHead {
160    fn drop(&mut self) {
161        if let Some(head) = self.head.take() {
162            RESPONSE_POOL.with(move |p| p.release(head))
163        }
164    }
165}
166
167/// Response head object pool.
168#[doc(hidden)]
169pub struct BoxedResponsePool(#[allow(clippy::vec_box)] RefCell<Vec<Box<ResponseHead>>>);
170
171impl BoxedResponsePool {
172    fn create() -> BoxedResponsePool {
173        BoxedResponsePool(RefCell::new(Vec::with_capacity(128)))
174    }
175
176    /// Get message from the pool.
177    #[inline]
178    fn get_message(&self, status: StatusCode) -> BoxedResponseHead {
179        if let Some(mut head) = self.0.borrow_mut().pop() {
180            head.reason = None;
181            head.status = status;
182            head.headers.clear();
183            head.flags = Flags::empty();
184            BoxedResponseHead { head: Some(head) }
185        } else {
186            BoxedResponseHead {
187                head: Some(Box::new(ResponseHead::new(status))),
188            }
189        }
190    }
191
192    /// Release request instance.
193    #[inline]
194    fn release(&self, msg: Box<ResponseHead>) {
195        let pool = &mut self.0.borrow_mut();
196
197        if pool.len() < 128 {
198            pool.push(msg);
199        }
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use std::{
206        io::{Read as _, Write as _},
207        net,
208    };
209
210    use memchr::memmem;
211
212    use crate::{
213        h1::H1Service,
214        header::{HeaderName, HeaderValue},
215        Error, Request, Response, ServiceConfig,
216    };
217
218    #[actix_rt::test]
219    async fn camel_case_headers() {
220        let mut srv = actix_http_test::test_server(|| {
221            H1Service::with_config(ServiceConfig::default(), |req: Request| async move {
222                let mut res = Response::ok();
223
224                if req.path().contains("camel") {
225                    res.head_mut().set_camel_case_headers(true);
226                }
227
228                res.headers_mut().insert(
229                    HeaderName::from_static("foo-bar"),
230                    HeaderValue::from_static("baz"),
231                );
232
233                Ok::<_, Error>(res)
234            })
235            .tcp()
236        })
237        .await;
238
239        let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
240        stream
241            .write_all(b"GET /camel HTTP/1.1\r\nConnection: Close\r\n\r\n")
242            .unwrap();
243        let mut data = vec![];
244        let _ = stream.read_to_end(&mut data).unwrap();
245        assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
246        assert!(memmem::find(&data, b"Foo-Bar").is_some());
247        assert!(memmem::find(&data, b"foo-bar").is_none());
248        assert!(memmem::find(&data, b"Date").is_some());
249        assert!(memmem::find(&data, b"date").is_none());
250        assert!(memmem::find(&data, b"Content-Length").is_some());
251        assert!(memmem::find(&data, b"content-length").is_none());
252
253        let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
254        stream
255            .write_all(b"GET /lower HTTP/1.1\r\nConnection: Close\r\n\r\n")
256            .unwrap();
257        let mut data = vec![];
258        let _ = stream.read_to_end(&mut data).unwrap();
259        assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
260        assert!(memmem::find(&data, b"Foo-Bar").is_none());
261        assert!(memmem::find(&data, b"foo-bar").is_some());
262        assert!(memmem::find(&data, b"Date").is_none());
263        assert!(memmem::find(&data, b"date").is_some());
264        assert!(memmem::find(&data, b"Content-Length").is_none());
265        assert!(memmem::find(&data, b"content-length").is_some());
266
267        srv.stop().await;
268    }
269}