From 387c229f281271e7d701a0ff5d09e68a282e9988 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Apr 2021 02:12:47 +0100 Subject: [PATCH] move response builder code to own file --- actix-http/src/body/mod.rs | 2 +- actix-http/src/lib.rs | 4 +- actix-http/src/response.rs | 412 +---------------------- actix-http/src/response_builder.rs | 503 +++++++++++++++++++++++++++++ 4 files changed, 517 insertions(+), 404 deletions(-) create mode 100644 actix-http/src/response_builder.rs diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index c298dda11..f26d6a8cf 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -56,7 +56,7 @@ pub async fn to_bytes(body: impl MessageBody) -> Result { let body = body.as_mut(); match ready!(body.poll_next(cx)) { - Some(Ok(bytes)) => buf.extend(bytes), + Some(Ok(bytes)) => buf.extend_from_slice(&*bytes), None => return Poll::Ready(Ok(())), Some(Err(err)) => return Poll::Ready(Err(err)), } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 3125e3874..8674834e0 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -42,6 +42,7 @@ mod message; mod payload; mod request; mod response; +mod response_builder; mod service; mod time_parser; @@ -59,7 +60,8 @@ pub use self::http_message::HttpMessage; pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; -pub use self::response::{Response, ResponseBuilder}; +pub use self::response::{Response}; +pub use self::response_builder::{ResponseBuilder}; pub use self::service::HttpService; pub mod http { diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 7f73538fc..a3ab1175c 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -1,4 +1,4 @@ -//! HTTP responses. +//! HTTP response. use std::{ cell::{Ref, RefMut}, @@ -10,22 +10,21 @@ use std::{ }; use bytes::{Bytes, BytesMut}; -use futures_core::Stream; use crate::{ - body::{Body, BodyStream, MessageBody, ResponseBody}, + body::{Body, MessageBody, ResponseBody}, error::Error, extensions::Extensions, - header::{IntoHeaderPair, IntoHeaderValue}, - http::{header, Error as HttpError, HeaderMap, StatusCode}, - message::{BoxedResponseHead, ConnectionType, ResponseHead}, + http::{HeaderMap, StatusCode}, + message::{BoxedResponseHead, ResponseHead}, + ResponseBuilder, }; -/// An HTTP Response +/// An HTTP response. pub struct Response { - head: BoxedResponseHead, - body: ResponseBody, - error: Option, + pub(crate) head: BoxedResponseHead, + pub(crate) body: ResponseBody, + pub(crate) error: Option, } impl Response { @@ -273,295 +272,6 @@ impl Future for Response { } } -/// An HTTP response builder. -/// -/// This type can be used to construct an instance of `Response` through a builder-like pattern. -pub struct ResponseBuilder { - head: Option, - err: Option, -} - -impl ResponseBuilder { - #[inline] - /// Create response builder - pub fn new(status: StatusCode) -> Self { - ResponseBuilder { - head: Some(BoxedResponseHead::new(status)), - err: None, - } - } - - /// Set HTTP status code of this response. - #[inline] - pub fn status(&mut self, status: StatusCode) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.status = status; - } - self - } - - /// Insert a header, replacing any that were set with an equivalent field name. - /// - /// ``` - /// # use actix_http::Response; - /// use actix_http::http::{header, StatusCode}; - /// - /// Response::build(StatusCode::OK) - /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) - /// .insert_header(("X-TEST", "value")) - /// .finish(); - /// ``` - pub fn insert_header(&mut self, header: H) -> &mut Self - where - H: IntoHeaderPair, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match header.try_into_header_pair() { - Ok((key, value)) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }; - } - - self - } - - /// Append a header, keeping any that were set with an equivalent field name. - /// - /// ``` - /// # use actix_http::Response; - /// use actix_http::http::{header, StatusCode}; - /// - /// Response::build(StatusCode::OK) - /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) - /// .append_header(("X-TEST", "value1")) - /// .append_header(("X-TEST", "value2")) - /// .finish(); - /// ``` - pub fn append_header(&mut self, header: H) -> &mut Self - where - H: IntoHeaderPair, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match header.try_into_header_pair() { - Ok((key, value)) => parts.headers.append(key, value), - Err(e) => self.err = Some(e.into()), - }; - } - - self - } - - /// Set the custom reason for the response. - #[inline] - pub fn reason(&mut self, reason: &'static str) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.reason = Some(reason); - } - self - } - - /// Set connection type to KeepAlive - #[inline] - pub fn keep_alive(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::KeepAlive); - } - self - } - - /// Set connection type to Upgrade - #[inline] - pub fn upgrade(&mut self, value: V) -> &mut Self - where - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::Upgrade); - } - - if let Ok(value) = value.try_into_value() { - self.insert_header((header::UPGRADE, value)); - } - - self - } - - /// Force close connection, even if it is marked as keep-alive - #[inline] - pub fn force_close(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::Close); - } - self - } - - /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. - #[inline] - pub fn no_chunking(&mut self, len: u64) -> &mut Self { - let mut buf = itoa::Buffer::new(); - self.insert_header((header::CONTENT_LENGTH, buf.format(len))); - - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.no_chunking(true); - } - self - } - - /// Set response content type. - #[inline] - pub fn content_type(&mut self, value: V) -> &mut Self - where - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match value.try_into_value() { - Ok(value) => { - parts.headers.insert(header::CONTENT_TYPE, value); - } - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Responses extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - let head = self.head.as_ref().expect("cannot reuse response builder"); - head.extensions.borrow() - } - - /// Mutable reference to a the response's extensions - #[inline] - pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { - let head = self.head.as_ref().expect("cannot reuse response builder"); - head.extensions.borrow_mut() - } - - /// Set a body and generate `Response`. - /// - /// `ResponseBuilder` can not be used after this call. - #[inline] - pub fn body>(&mut self, body: B) -> Response { - self.message_body(body.into()) - } - - /// Set a body and generate `Response`. - /// - /// `ResponseBuilder` can not be used after this call. - pub fn message_body(&mut self, body: B) -> Response { - if let Some(e) = self.err.take() { - return Response::from(Error::from(e)).into_body(); - } - - let response = self.head.take().expect("cannot reuse response builder"); - - Response { - head: response, - body: ResponseBody::Body(body), - error: None, - } - } - - /// Set a streaming body and generate `Response`. - /// - /// `ResponseBuilder` can not be used after this call. - #[inline] - pub fn streaming(&mut self, stream: S) -> Response - where - S: Stream> + Unpin + 'static, - E: Into + 'static, - { - self.body(Body::from_message(BodyStream::new(stream))) - } - - /// Set an empty body and generate `Response` - /// - /// `ResponseBuilder` can not be used after this call. - #[inline] - pub fn finish(&mut self) -> Response { - self.body(Body::Empty) - } - - /// This method construct new `ResponseBuilder` - pub fn take(&mut self) -> ResponseBuilder { - ResponseBuilder { - head: self.head.take(), - err: self.err.take(), - } - } -} - -#[inline] -fn parts<'a>( - parts: &'a mut Option, - err: &Option, -) -> Option<&'a mut ResponseHead> { - if err.is_some() { - return None; - } - parts.as_mut().map(|r| &mut **r) -} - -/// Convert `Response` to a `ResponseBuilder`. Body get dropped. -impl From> for ResponseBuilder { - fn from(res: Response) -> ResponseBuilder { - ResponseBuilder { - head: Some(res.head), - err: None, - } - } -} - -/// Convert `ResponseHead` to a `ResponseBuilder` -impl<'a> From<&'a ResponseHead> for ResponseBuilder { - fn from(head: &'a ResponseHead) -> ResponseBuilder { - let mut msg = BoxedResponseHead::new(head.status); - msg.version = head.version; - msg.reason = head.reason; - - for (k, v) in head.headers.iter() { - msg.headers.append(k.clone(), v.clone()); - } - - msg.no_chunking(!head.chunked()); - - ResponseBuilder { - head: Some(msg), - err: None, - } - } -} - -impl Future for ResponseBuilder { - type Output = Result, Error>; - - fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - Poll::Ready(Ok(self.finish())) - } -} - -impl fmt::Debug for ResponseBuilder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let head = self.head.as_ref().unwrap(); - - let res = writeln!( - f, - "\nResponseBuilder {:?} {}{}", - head.version, - head.status, - head.reason.unwrap_or(""), - ); - let _ = writeln!(f, " headers:"); - for (key, val) in head.headers.iter() { - let _ = writeln!(f, " {:?}: {:?}", key, val); - } - res - } -} - /// Helper converters impl>, E: Into> From> for Response { fn from(res: Result) -> Self { @@ -630,7 +340,7 @@ impl From for Response { mod tests { use super::*; use crate::body::Body; - use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE, COOKIE}; + use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; #[test] fn test_debug() { @@ -642,40 +352,6 @@ mod tests { assert!(dbg.contains("Response")); } - #[test] - fn test_basic_builder() { - let resp = Response::build(StatusCode::OK) - .insert_header(("X-TEST", "value")) - .finish(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[test] - fn test_upgrade() { - let resp = Response::build(StatusCode::OK) - .upgrade("websocket") - .finish(); - assert!(resp.upgrade()); - assert_eq!( - resp.headers().get(header::UPGRADE).unwrap(), - HeaderValue::from_static("websocket") - ); - } - - #[test] - fn test_force_close() { - let resp = Response::build(StatusCode::OK).force_close().finish(); - assert!(!resp.keep_alive()) - } - - #[test] - fn test_content_type() { - let resp = Response::build(StatusCode::OK) - .content_type("text/plain") - .body(Body::Empty); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") - } - #[test] fn test_into_response() { let resp: Response = "test".into(); @@ -745,72 +421,4 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().get_ref(), b"test"); } - - #[test] - fn test_into_builder() { - let mut resp: Response = "test".into(); - assert_eq!(resp.status(), StatusCode::OK); - - resp.headers_mut().insert( - HeaderName::from_static("cookie"), - HeaderValue::from_static("cookie1=val100"), - ); - - let mut builder: ResponseBuilder = resp.into(); - let resp = builder.status(StatusCode::BAD_REQUEST).finish(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let cookie = resp.headers().get_all("Cookie").next().unwrap(); - assert_eq!(cookie.to_str().unwrap(), "cookie1=val100"); - } - - #[test] - fn response_builder_header_insert_kv() { - let mut res = Response::build(StatusCode::OK); - res.insert_header(("Content-Type", "application/octet-stream")); - let res = res.finish(); - - assert_eq!( - res.headers().get("Content-Type"), - Some(&HeaderValue::from_static("application/octet-stream")) - ); - } - - #[test] - fn response_builder_header_insert_typed() { - let mut res = Response::build(StatusCode::OK); - res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); - let res = res.finish(); - - assert_eq!( - res.headers().get("Content-Type"), - Some(&HeaderValue::from_static("application/octet-stream")) - ); - } - - #[test] - fn response_builder_header_append_kv() { - let mut res = Response::build(StatusCode::OK); - res.append_header(("Content-Type", "application/octet-stream")); - res.append_header(("Content-Type", "application/json")); - let res = res.finish(); - - let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); - assert_eq!(headers.len(), 2); - assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); - assert!(headers.contains(&HeaderValue::from_static("application/json"))); - } - - #[test] - fn response_builder_header_append_typed() { - let mut res = Response::build(StatusCode::OK); - res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); - res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); - let res = res.finish(); - - let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); - assert_eq!(headers.len(), 2); - assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); - assert!(headers.contains(&HeaderValue::from_static("application/json"))); - } } diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs new file mode 100644 index 000000000..eab96b03d --- /dev/null +++ b/actix-http/src/response_builder.rs @@ -0,0 +1,503 @@ +//! HTTP response builder. + +use std::{ + cell::{Ref, RefMut}, + fmt, + future::Future, + pin::Pin, + str, + task::{Context, Poll}, +}; + +use bytes::Bytes; +use futures_core::Stream; + +use crate::{ + body::{Body, BodyStream, ResponseBody}, + error::Error, + extensions::Extensions, + header::{IntoHeaderPair, IntoHeaderValue}, + http::{header, Error as HttpError, StatusCode}, + message::{BoxedResponseHead, ConnectionType, ResponseHead}, + Response, +}; + +/// An HTTP response builder. +/// +/// This type can be used to construct an instance of `Response` using a builder pattern. +pub struct ResponseBuilder { + head: Option, + err: Option, +} + +impl ResponseBuilder { + #[inline] + /// Create response builder + pub fn new(status: StatusCode) -> Self { + ResponseBuilder { + head: Some(BoxedResponseHead::new(status)), + err: None, + } + } + + /// Set HTTP status code of this response. + #[inline] + pub fn status(&mut self, status: StatusCode) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.status = status; + } + self + } + + /// Insert a header, replacing any that were set with an equivalent field name. + /// + /// ``` + /// # use actix_http::Response; + /// use actix_http::http::{header, StatusCode}; + /// + /// Response::build(StatusCode::OK) + /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) + /// .insert_header(("X-TEST", "value")) + /// .finish(); + /// ``` + pub fn insert_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + match header.try_into_header_pair() { + Ok((key, value)) => { + parts.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }; + } + + self + } + + /// Append a header, keeping any that were set with an equivalent field name. + /// + /// ``` + /// # use actix_http::Response; + /// use actix_http::http::{header, StatusCode}; + /// + /// Response::build(StatusCode::OK) + /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) + /// .append_header(("X-TEST", "value1")) + /// .append_header(("X-TEST", "value2")) + /// .finish(); + /// ``` + pub fn append_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + match header.try_into_header_pair() { + Ok((key, value)) => parts.headers.append(key, value), + Err(e) => self.err = Some(e.into()), + }; + } + + self + } + + /// Set the custom reason for the response. + #[inline] + pub fn reason(&mut self, reason: &'static str) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.reason = Some(reason); + } + self + } + + /// Set connection type to KeepAlive + #[inline] + pub fn keep_alive(&mut self) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::KeepAlive); + } + self + } + + /// Set connection type to Upgrade + #[inline] + pub fn upgrade(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::Upgrade); + } + + if let Ok(value) = value.try_into_value() { + self.insert_header((header::UPGRADE, value)); + } + + self + } + + /// Force close connection, even if it is marked as keep-alive + #[inline] + pub fn force_close(&mut self) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::Close); + } + self + } + + /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. + #[inline] + pub fn no_chunking(&mut self, len: u64) -> &mut Self { + let mut buf = itoa::Buffer::new(); + self.insert_header((header::CONTENT_LENGTH, buf.format(len))); + + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.no_chunking(true); + } + self + } + + /// Set response content type. + #[inline] + pub fn content_type(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + match value.try_into_value() { + Ok(value) => { + parts.headers.insert(header::CONTENT_TYPE, value); + } + Err(e) => self.err = Some(e.into()), + }; + } + self + } + + /// Responses extensions + #[inline] + pub fn extensions(&self) -> Ref<'_, Extensions> { + let head = self.head.as_ref().expect("cannot reuse response builder"); + head.extensions.borrow() + } + + /// Mutable reference to a the response's extensions + #[inline] + pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { + let head = self.head.as_ref().expect("cannot reuse response builder"); + head.extensions.borrow_mut() + } + + /// Set a body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn body>(&mut self, body: B) -> Response { + self.message_body(body.into()) + } + + /// Set a body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + pub fn message_body(&mut self, body: B) -> Response { + if let Some(e) = self.err.take() { + return Response::from(Error::from(e)).into_body(); + } + + let response = self.head.take().expect("cannot reuse response builder"); + + Response { + head: response, + body: ResponseBody::Body(body), + error: None, + } + } + + /// Set a streaming body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn streaming(&mut self, stream: S) -> Response + where + S: Stream> + Unpin + 'static, + E: Into + 'static, + { + self.body(Body::from_message(BodyStream::new(stream))) + } + + /// Set an empty body and generate `Response` + /// + /// `ResponseBuilder` can not be used after this call. + #[inline] + pub fn finish(&mut self) -> Response { + self.body(Body::Empty) + } + + /// This method construct new `ResponseBuilder` + pub fn take(&mut self) -> ResponseBuilder { + ResponseBuilder { + head: self.head.take(), + err: self.err.take(), + } + } +} + +#[inline] +fn parts<'a>( + parts: &'a mut Option, + err: &Option, +) -> Option<&'a mut ResponseHead> { + if err.is_some() { + return None; + } + parts.as_mut().map(|r| &mut **r) +} + +/// Convert `Response` to a `ResponseBuilder`. Body get dropped. +impl From> for ResponseBuilder { + fn from(res: Response) -> ResponseBuilder { + ResponseBuilder { + head: Some(res.head), + err: None, + } + } +} + +/// Convert `ResponseHead` to a `ResponseBuilder` +impl<'a> From<&'a ResponseHead> for ResponseBuilder { + fn from(head: &'a ResponseHead) -> ResponseBuilder { + let mut msg = BoxedResponseHead::new(head.status); + msg.version = head.version; + msg.reason = head.reason; + + for (k, v) in head.headers.iter() { + msg.headers.append(k.clone(), v.clone()); + } + + msg.no_chunking(!head.chunked()); + + ResponseBuilder { + head: Some(msg), + err: None, + } + } +} + +impl Future for ResponseBuilder { + type Output = Result, Error>; + + fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { + Poll::Ready(Ok(self.finish())) + } +} + +impl fmt::Debug for ResponseBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let head = self.head.as_ref().unwrap(); + + let res = writeln!( + f, + "\nResponseBuilder {:?} {}{}", + head.version, + head.status, + head.reason.unwrap_or(""), + ); + let _ = writeln!(f, " headers:"); + for (key, val) in head.headers.iter() { + let _ = writeln!(f, " {:?}: {:?}", key, val); + } + res + } +} + +#[cfg(test)] +mod tests { + use bytes::BytesMut; + + use super::*; + use crate::body::Body; + use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE, COOKIE}; + + #[test] + fn test_debug() { + let resp = Response::build(StatusCode::OK) + .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) + .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) + .finish(); + let dbg = format!("{:?}", resp); + assert!(dbg.contains("Response")); + } + + #[test] + fn test_basic_builder() { + let resp = Response::build(StatusCode::OK) + .insert_header(("X-TEST", "value")) + .finish(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[test] + fn test_upgrade() { + let resp = Response::build(StatusCode::OK) + .upgrade("websocket") + .finish(); + assert!(resp.upgrade()); + assert_eq!( + resp.headers().get(header::UPGRADE).unwrap(), + HeaderValue::from_static("websocket") + ); + } + + #[test] + fn test_force_close() { + let resp = Response::build(StatusCode::OK).force_close().finish(); + assert!(!resp.keep_alive()) + } + + #[test] + fn test_content_type() { + let resp = Response::build(StatusCode::OK) + .content_type("text/plain") + .body(Body::Empty); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") + } + + #[test] + fn test_into_response() { + let resp: Response = "test".into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + + let resp: Response = b"test".as_ref().into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + + let resp: Response = "test".to_owned().into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + + let resp: Response = (&"test".to_owned()).into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + + let b = Bytes::from_static(b"test"); + let resp: Response = b.into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + + let b = Bytes::from_static(b"test"); + let resp: Response = b.into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + + let b = BytesMut::from("test"); + let resp: Response = b.into(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); + + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().get_ref(), b"test"); + } + + #[test] + fn test_into_builder() { + let mut resp: Response = "test".into(); + assert_eq!(resp.status(), StatusCode::OK); + + resp.headers_mut().insert( + HeaderName::from_static("cookie"), + HeaderValue::from_static("cookie1=val100"), + ); + + let mut builder: ResponseBuilder = resp.into(); + let resp = builder.status(StatusCode::BAD_REQUEST).finish(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + + let cookie = resp.headers().get_all("Cookie").next().unwrap(); + assert_eq!(cookie.to_str().unwrap(), "cookie1=val100"); + } + + #[test] + fn response_builder_header_insert_kv() { + let mut res = Response::build(StatusCode::OK); + res.insert_header(("Content-Type", "application/octet-stream")); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_insert_typed() { + let mut res = Response::build(StatusCode::OK); + res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_append_kv() { + let mut res = Response::build(StatusCode::OK); + res.append_header(("Content-Type", "application/octet-stream")); + res.append_header(("Content-Type", "application/json")); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } + + #[test] + fn response_builder_header_append_typed() { + let mut res = Response::build(StatusCode::OK); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } +}