actix_http/responses/
head.rs1use 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 #[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 #[inline]
35 pub fn headers(&self) -> &HeaderMap {
36 &self.headers
37 }
38
39 #[inline]
41 pub fn headers_mut(&mut self) -> &mut HeaderMap {
42 &mut self.headers
43 }
44
45 #[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 #[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 #[inline]
84 pub fn keep_alive(&self) -> bool {
85 self.connection_type() == ConnectionType::KeepAlive
86 }
87
88 #[inline]
90 pub fn upgrade(&self) -> bool {
91 self.connection_type() == ConnectionType::Upgrade
92 }
93
94 #[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 #[inline]
119 pub fn chunked(&self) -> bool {
120 !self.flags.contains(Flags::NO_CHUNKING)
121 }
122
123 #[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 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#[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 #[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 #[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}