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, SizedStream},
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
24pub struct HttpResponseBuilder {
28 res: Option<Response<BoxBody>>,
29 error: Option<HttpError>,
30}
31
32impl HttpResponseBuilder {
33 #[inline]
34 pub fn new(status: StatusCode) -> Self {
36 Self {
37 res: Some(Response::with_body(status, BoxBody::new(()))),
38 error: None,
39 }
40 }
41
42 #[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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 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 #[inline]
326 pub fn streaming<S, E>(&mut self, stream: S) -> HttpResponse
327 where
328 S: Stream<Item = Result<Bytes, E>> + 'static,
329 E: Into<BoxError> + 'static,
330 {
331 if let Some(parts) = self.inner() {
333 if !parts.headers.contains_key(header::CONTENT_TYPE) {
334 self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
335 }
336 }
337
338 let content_length = self
339 .inner()
340 .and_then(|parts| parts.headers.get(header::CONTENT_LENGTH))
341 .and_then(|value| value.to_str().ok())
342 .and_then(|value| value.parse::<u64>().ok());
343
344 if let Some(len) = content_length {
345 self.no_chunking(len);
346 self.body(SizedStream::new(len, stream))
347 } else {
348 self.body(BodyStream::new(stream))
349 }
350 }
351
352 pub fn json(&mut self, value: impl Serialize) -> HttpResponse {
356 match serde_json::to_string(&value) {
357 Ok(body) => {
358 let contains = if let Some(parts) = self.inner() {
359 parts.headers.contains_key(header::CONTENT_TYPE)
360 } else {
361 true
362 };
363
364 if !contains {
365 self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
366 }
367
368 self.body(body)
369 }
370 Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)),
371 }
372 }
373
374 #[inline]
378 pub fn finish(&mut self) -> HttpResponse {
379 self.body(())
380 }
381
382 pub fn take(&mut self) -> Self {
384 Self {
385 res: self.res.take(),
386 error: self.error.take(),
387 }
388 }
389
390 fn inner(&mut self) -> Option<&mut ResponseHead> {
391 if self.error.is_some() {
392 return None;
393 }
394
395 self.res.as_mut().map(Response::head_mut)
396 }
397}
398
399impl From<HttpResponseBuilder> for HttpResponse {
400 fn from(mut builder: HttpResponseBuilder) -> Self {
401 builder.finish()
402 }
403}
404
405impl From<HttpResponseBuilder> for Response<BoxBody> {
406 fn from(mut builder: HttpResponseBuilder) -> Self {
407 builder.finish().into()
408 }
409}
410
411impl Future for HttpResponseBuilder {
412 type Output = Result<HttpResponse, Error>;
413
414 fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
415 Poll::Ready(Ok(self.finish()))
416 }
417}
418
419impl Responder for HttpResponseBuilder {
420 type Body = BoxBody;
421
422 #[inline]
423 fn respond_to(mut self, _: &HttpRequest) -> HttpResponse<Self::Body> {
424 self.finish()
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431 use crate::{
432 body,
433 http::header::{HeaderValue, CONTENT_TYPE},
434 test::assert_body_eq,
435 };
436
437 #[test]
438 fn test_basic_builder() {
439 let resp = HttpResponse::Ok()
440 .insert_header(("X-TEST", "value"))
441 .finish();
442 assert_eq!(resp.status(), StatusCode::OK);
443 }
444
445 #[test]
446 fn test_upgrade() {
447 let resp = HttpResponseBuilder::new(StatusCode::OK)
448 .upgrade("websocket")
449 .finish();
450 assert!(resp.upgrade());
451 assert_eq!(
452 resp.headers().get(header::UPGRADE).unwrap(),
453 HeaderValue::from_static("websocket")
454 );
455 }
456
457 #[test]
458 fn test_force_close() {
459 let resp = HttpResponseBuilder::new(StatusCode::OK)
460 .force_close()
461 .finish();
462 assert!(!resp.keep_alive())
463 }
464
465 #[test]
466 fn test_content_type() {
467 let resp = HttpResponseBuilder::new(StatusCode::OK)
468 .content_type("text/plain")
469 .body(Bytes::new());
470 assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
471 }
472
473 #[actix_rt::test]
474 async fn test_json() {
475 let res = HttpResponse::Ok().json(vec!["v1", "v2", "v3"]);
476 let ct = res.headers().get(CONTENT_TYPE).unwrap();
477 assert_eq!(ct, HeaderValue::from_static("application/json"));
478 assert_body_eq!(res, br#"["v1","v2","v3"]"#);
479
480 let res = HttpResponse::Ok().json(["v1", "v2", "v3"]);
481 let ct = res.headers().get(CONTENT_TYPE).unwrap();
482 assert_eq!(ct, HeaderValue::from_static("application/json"));
483 assert_body_eq!(res, br#"["v1","v2","v3"]"#);
484
485 let res = HttpResponse::Ok()
487 .insert_header((CONTENT_TYPE, "text/json"))
488 .json(["v1", "v2", "v3"]);
489 let ct = res.headers().get(CONTENT_TYPE).unwrap();
490 assert_eq!(ct, HeaderValue::from_static("text/json"));
491 assert_body_eq!(res, br#"["v1","v2","v3"]"#);
492 }
493
494 #[actix_rt::test]
495 async fn test_serde_json_in_body() {
496 let resp = HttpResponse::Ok()
497 .body(serde_json::to_vec(&serde_json::json!({ "test-key": "test-value" })).unwrap());
498
499 assert_eq!(
500 body::to_bytes(resp.into_body()).await.unwrap().as_ref(),
501 br#"{"test-key":"test-value"}"#
502 );
503 }
504
505 #[test]
506 fn response_builder_header_insert_kv() {
507 let mut res = HttpResponse::Ok();
508 res.insert_header(("Content-Type", "application/octet-stream"));
509 let res = res.finish();
510
511 assert_eq!(
512 res.headers().get("Content-Type"),
513 Some(&HeaderValue::from_static("application/octet-stream"))
514 );
515 }
516
517 #[test]
518 fn response_builder_header_insert_typed() {
519 let mut res = HttpResponse::Ok();
520 res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
521 let res = res.finish();
522
523 assert_eq!(
524 res.headers().get("Content-Type"),
525 Some(&HeaderValue::from_static("application/octet-stream"))
526 );
527 }
528
529 #[test]
530 fn response_builder_header_append_kv() {
531 let mut res = HttpResponse::Ok();
532 res.append_header(("Content-Type", "application/octet-stream"));
533 res.append_header(("Content-Type", "application/json"));
534 let res = res.finish();
535
536 let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
537 assert_eq!(headers.len(), 2);
538 assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
539 assert!(headers.contains(&HeaderValue::from_static("application/json")));
540 }
541
542 #[test]
543 fn response_builder_header_append_typed() {
544 let mut res = HttpResponse::Ok();
545 res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
546 res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
547 let res = res.finish();
548
549 let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
550 assert_eq!(headers.len(), 2);
551 assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
552 assert!(headers.contains(&HeaderValue::from_static("application/json")));
553 }
554}