This commit is contained in:
Rob Ede 2021-04-13 11:12:40 +01:00
parent d29f208cba
commit fa9691a2f8
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
14 changed files with 270 additions and 150 deletions

View File

@ -1,6 +1,6 @@
use std::{env, io}; use std::{env, io};
use actix_http::{http::StatusCode, Error, HttpService, Request, Response}; use actix_http::{http::StatusCode, HttpService, Request, Response};
use actix_server::Server; use actix_server::Server;
use bytes::BytesMut; use bytes::BytesMut;
use futures_util::StreamExt as _; use futures_util::StreamExt as _;
@ -24,14 +24,14 @@ async fn main() -> io::Result<()> {
} }
info!("request body: {:?}", body); info!("request body: {:?}", body);
Ok::<_, Error>(
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.insert_header(( .insert_header((
"x-head", "x-head",
HeaderValue::from_static("dummy value!"), HeaderValue::from_static("dummy value!"),
)) ))
.body(body), .take()
) .body(body)
}) })
.tcp() .tcp()
})? })?

View File

@ -17,9 +17,10 @@ async fn handle_request(mut req: Request) -> Result<Response<Body>, Error> {
} }
info!("request body: {:?}", body); info!("request body: {:?}", body);
Ok(Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.insert_header(("x-head", HeaderValue::from_static("dummy value!"))) .insert_header(("x-head", HeaderValue::from_static("dummy value!")))
.body(body)) .take()
.body(body)
} }
#[actix_rt::main] #[actix_rt::main]

View File

@ -23,7 +23,7 @@ async fn main() -> io::Result<()> {
"x-head", "x-head",
HeaderValue::from_static("dummy value!"), HeaderValue::from_static("dummy value!"),
)); ));
future::ok::<_, ()>(res.body("Hello world!")) future::ready(res.body("Hello world!"))
}) })
.tcp() .tcp()
})? })?

View File

@ -36,12 +36,12 @@ async fn main() -> io::Result<()> {
async fn handler(req: Request) -> Result<Response<BodyStream<Heartbeat>>, Error> { async fn handler(req: Request) -> Result<Response<BodyStream<Heartbeat>>, Error> {
log::info!("handshaking"); log::info!("handshaking");
let mut res = ws::handshake(req.head())?; let res = ws::handshake(req.head())?;
// handshake will always fail under HTTP/2 // handshake will always fail under HTTP/2
log::info!("responding"); log::info!("responding");
Ok(res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))) res.streaming(Heartbeat::new(ws::Codec::new()))
} }
struct Heartbeat { struct Heartbeat {

View File

@ -1,5 +1,12 @@
//! Traits and structures to aid consuming and writing HTTP payloads. //! Traits and structures to aid consuming and writing HTTP payloads.
use std::task::Poll;
use actix_rt::pin;
use actix_utils::future::poll_fn;
use bytes::{Bytes, BytesMut};
use futures_core::ready;
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]
mod body; mod body;
mod body_stream; mod body_stream;
@ -15,6 +22,31 @@ pub use self::response_body::ResponseBody;
pub use self::size::BodySize; pub use self::size::BodySize;
pub use self::sized_stream::SizedStream; pub use self::sized_stream::SizedStream;
pub async fn to_bytes(body: impl MessageBody) -> Result<Bytes, crate::Error> {
let cap = match body.size() {
BodySize::None | BodySize::Empty | BodySize::Sized(0) => return Ok(Bytes::new()),
BodySize::Sized(size) => size as usize,
BodySize::Stream => 32_768,
};
let mut buf = BytesMut::with_capacity(cap);
pin!(body);
poll_fn(|cx| loop {
let body = body.as_mut();
match ready!(body.poll_next(cx)) {
Some(Ok(bytes)) => buf.extend(bytes),
None => return Poll::Ready(Ok(())),
Some(Err(err)) => return Poll::Ready(Err(err)),
}
})
.await?;
Ok(buf.freeze())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::pin::Pin; use std::pin::Pin;
@ -44,6 +76,42 @@ mod tests {
} }
} }
impl ResponseBody<Bytes> {
pub(crate) fn get_ref(&self) -> &[u8] {
match *self {
ResponseBody::Body(ref b) => b.as_ref(),
ResponseBody::Other(ref b) => b.get_ref(),
}
}
}
impl ResponseBody<String> {
pub(crate) fn get_ref(&self) -> &[u8] {
match *self {
ResponseBody::Body(ref b) => b.as_ref(),
ResponseBody::Other(ref b) => b.get_ref(),
}
}
}
impl ResponseBody<&str> {
pub(crate) fn get_ref(&self) -> &[u8] {
match *self {
ResponseBody::Body(ref b) => b.as_ref(),
ResponseBody::Other(ref b) => b.get_ref(),
}
}
}
impl ResponseBody<&[u8]> {
pub(crate) fn get_ref(&self) -> &[u8] {
match *self {
ResponseBody::Body(ref b) => b.as_ref(),
ResponseBody::Other(ref b) => b.get_ref(),
}
}
}
#[actix_rt::test] #[actix_rt::test]
async fn test_static_str() { async fn test_static_str() {
assert_eq!(Body::from("").size(), BodySize::Sized(0)); assert_eq!(Body::from("").size(), BodySize::Sized(0));

View File

@ -135,8 +135,14 @@ impl From<Response<Body>> for Error {
/// Convert ResponseBuilder to a Error /// Convert ResponseBuilder to a Error
impl From<ResponseBuilder> for Error { impl From<ResponseBuilder> for Error {
fn from(mut res: ResponseBuilder) -> Error { fn from(res: ResponseBuilder) -> Error {
InternalError::from_response("", res.finish()).into() match res.finish() {
Ok(res) => InternalError::from_response("", res).into(),
Err(err) => {
let res = err.as_response_error().error_response();
InternalError::from_response(err, res).into()
}
}
} }
} }

View File

@ -64,6 +64,12 @@ impl Response<()> {
pub fn not_found() -> Response<()> { pub fn not_found() -> Response<()> {
Response::new(StatusCode::NOT_FOUND) Response::new(StatusCode::NOT_FOUND)
} }
/// Creates a new response with status 500 Internal Server Error.
#[inline]
pub fn internal_server_error() -> Response<()> {
Response::new(StatusCode::INTERNAL_SERVER_ERROR)
}
} }
impl Response<Body> { impl Response<Body> {
@ -204,7 +210,7 @@ impl<B> Response<B> {
} }
} }
/// Set a body and return previous body value /// Set a body and return previous body value.
pub(crate) fn replace_body<B2>(self, body: B2) -> (Response<B2>, ResponseBody<B>) { pub(crate) fn replace_body<B2>(self, body: B2) -> (Response<B2>, ResponseBody<B>) {
( (
Response { Response {
@ -216,7 +222,9 @@ impl<B> Response<B> {
) )
} }
/// Set a body and return previous body value /// Consumes the response returning a new response with the body mapped by a function.
///
/// Given function also has mutable access to response head.
pub fn map_body<F, B2>(mut self, f: F) -> Response<B2> pub fn map_body<F, B2>(mut self, f: F) -> Response<B2>
where where
F: FnOnce(&mut ResponseHead, ResponseBody<B>) -> ResponseBody<B2>, F: FnOnce(&mut ResponseHead, ResponseBody<B>) -> ResponseBody<B2>,
@ -271,38 +279,44 @@ impl<I: Into<Response<Body>>, E: Into<Error>> From<Result<I, E>> for Response<Bo
fn from(res: Result<I, E>) -> Self { fn from(res: Result<I, E>) -> Self {
match res { match res {
Ok(val) => val.into(), Ok(val) => val.into(),
Err(err) => err.into().into(), Err(err) => Response::from_error(err.into()),
} }
} }
} }
impl From<ResponseBuilder> for Response<Body> { impl From<ResponseBuilder> for Response<Body> {
fn from(mut builder: ResponseBuilder) -> Self { fn from(builder: ResponseBuilder) -> Self {
builder.finish() builder.finish().into()
} }
} }
impl From<&'static str> for Response<Body> { impl From<&'static str> for Response<&'static str> {
fn from(val: &'static str) -> Self { fn from(val: &'static str) -> Self {
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.content_type(mime::TEXT_PLAIN_UTF_8) .content_type(mime::TEXT_PLAIN_UTF_8)
.take()
.body(val) .body(val)
.unwrap()
} }
} }
impl From<&'static [u8]> for Response<Body> { impl From<&'static [u8]> for Response<&'static [u8]> {
fn from(val: &'static [u8]) -> Self { fn from(val: &'static [u8]) -> Self {
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.content_type(mime::APPLICATION_OCTET_STREAM) .content_type(mime::APPLICATION_OCTET_STREAM)
.take()
.body(val) .body(val)
.unwrap()
} }
} }
impl From<String> for Response<Body> { impl From<String> for Response<String> {
fn from(val: String) -> Self { fn from(val: String) -> Self {
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.content_type(mime::TEXT_PLAIN_UTF_8) .content_type(mime::TEXT_PLAIN_UTF_8)
.take()
.body(val) .body(val)
.unwrap()
} }
} }
@ -310,23 +324,29 @@ impl<'a> From<&'a String> for Response<Body> {
fn from(val: &'a String) -> Self { fn from(val: &'a String) -> Self {
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.content_type(mime::TEXT_PLAIN_UTF_8) .content_type(mime::TEXT_PLAIN_UTF_8)
.body(val) .take()
.body(Body::from_slice(val.as_bytes()))
.unwrap()
} }
} }
impl From<Bytes> for Response<Body> { impl From<Bytes> for Response<Bytes> {
fn from(val: Bytes) -> Self { fn from(val: Bytes) -> Self {
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.content_type(mime::APPLICATION_OCTET_STREAM) .content_type(mime::APPLICATION_OCTET_STREAM)
.take()
.body(val) .body(val)
.unwrap()
} }
} }
impl From<BytesMut> for Response<Body> { impl From<BytesMut> for Response<Bytes> {
fn from(val: BytesMut) -> Self { fn from(val: BytesMut) -> Self {
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.content_type(mime::APPLICATION_OCTET_STREAM) .content_type(mime::APPLICATION_OCTET_STREAM)
.body(val) .take()
.body(val.freeze())
.unwrap()
} }
} }
@ -348,7 +368,7 @@ mod tests {
#[test] #[test]
fn test_into_response() { fn test_into_response() {
let resp: Response<Body> = "test".into(); let resp: Response<_> = "test".into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
@ -357,7 +377,7 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test"); assert_eq!(resp.body().get_ref(), b"test");
let resp: Response<Body> = b"test".as_ref().into(); let resp: Response<_> = b"test".as_ref().into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
@ -366,7 +386,7 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test"); assert_eq!(resp.body().get_ref(), b"test");
let resp: Response<Body> = "test".to_owned().into(); let resp: Response<_> = "test".to_owned().into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
@ -385,7 +405,7 @@ mod tests {
assert_eq!(resp.body().get_ref(), b"test"); assert_eq!(resp.body().get_ref(), b"test");
let b = Bytes::from_static(b"test"); let b = Bytes::from_static(b"test");
let resp: Response<Body> = b.into(); let resp: Response<_> = b.into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
@ -394,8 +414,7 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test"); assert_eq!(resp.body().get_ref(), b"test");
let b = Bytes::from_static(b"test"); let resp: Response<_> = Bytes::from_static(b"test").into();
let resp: Response<Body> = b.into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
@ -405,7 +424,7 @@ mod tests {
assert_eq!(resp.body().get_ref(), b"test"); assert_eq!(resp.body().get_ref(), b"test");
let b = BytesMut::from("test"); let b = BytesMut::from("test");
let resp: Response<Body> = b.into(); let resp: Response<_> = b.into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),

View File

@ -2,6 +2,7 @@ use std::{
cell::{Ref, RefMut}, cell::{Ref, RefMut},
fmt, fmt,
future::Future, future::Future,
mem,
pin::Pin, pin::Pin,
str, str,
task::{Context, Poll}, task::{Context, Poll},
@ -24,8 +25,7 @@ use crate::{
/// ///
/// This type can be used to construct an instance of `Response` through a builder-like pattern. /// This type can be used to construct an instance of `Response` through a builder-like pattern.
pub struct ResponseBuilder { pub struct ResponseBuilder {
head: Option<BoxedResponseHead>, inner: Result<BoxedResponseHead, HttpError>,
err: Option<HttpError>,
} }
impl ResponseBuilder { impl ResponseBuilder {
@ -33,14 +33,13 @@ impl ResponseBuilder {
/// Create response builder /// Create response builder
pub fn new(status: StatusCode) -> Self { pub fn new(status: StatusCode) -> Self {
ResponseBuilder { ResponseBuilder {
head: Some(BoxedResponseHead::new(status)), inner: Ok(BoxedResponseHead::new(status)),
err: None,
} }
} }
/// Set HTTP status code of this response. /// Set HTTP status code of this response.
#[inline] #[inline]
pub fn status(&mut self, status: StatusCode) -> &mut Self { pub fn status(mut self, status: StatusCode) -> Self {
if let Some(parts) = self.inner() { if let Some(parts) = self.inner() {
parts.status = status; parts.status = status;
} }
@ -58,7 +57,7 @@ impl ResponseBuilder {
/// .insert_header(("X-TEST", "value")) /// .insert_header(("X-TEST", "value"))
/// .finish(); /// .finish();
/// ``` /// ```
pub fn insert_header<H>(&mut self, header: H) -> &mut Self pub fn insert_header<H>(mut self, header: H) -> Self
where where
H: IntoHeaderPair, H: IntoHeaderPair,
{ {
@ -67,7 +66,7 @@ impl ResponseBuilder {
Ok((key, value)) => { Ok((key, value)) => {
parts.headers.insert(key, value); parts.headers.insert(key, value);
} }
Err(e) => self.err = Some(e.into()), Err(err) => self.inner = Err(err.into()),
}; };
} }
@ -86,14 +85,14 @@ impl ResponseBuilder {
/// .append_header(("X-TEST", "value2")) /// .append_header(("X-TEST", "value2"))
/// .finish(); /// .finish();
/// ``` /// ```
pub fn append_header<H>(&mut self, header: H) -> &mut Self pub fn append_header<H>(mut self, header: H) -> Self
where where
H: IntoHeaderPair, H: IntoHeaderPair,
{ {
if let Some(parts) = self.inner() { if let Some(parts) = self.inner() {
match header.try_into_header_pair() { match header.try_into_header_pair() {
Ok((key, value)) => parts.headers.append(key, value), Ok((key, value)) => parts.headers.append(key, value),
Err(e) => self.err = Some(e.into()), Err(err) => self.inner = Err(err.into()),
}; };
} }
@ -102,7 +101,7 @@ impl ResponseBuilder {
/// Set the custom reason for the response. /// Set the custom reason for the response.
#[inline] #[inline]
pub fn reason(&mut self, reason: &'static str) -> &mut Self { pub fn reason(mut self, reason: &'static str) -> Self {
if let Some(parts) = self.inner() { if let Some(parts) = self.inner() {
parts.reason = Some(reason); parts.reason = Some(reason);
} }
@ -111,7 +110,7 @@ impl ResponseBuilder {
/// Set connection type to KeepAlive /// Set connection type to KeepAlive
#[inline] #[inline]
pub fn keep_alive(&mut self) -> &mut Self { pub fn keep_alive(self) -> Self {
if let Some(parts) = self.inner() { if let Some(parts) = self.inner() {
parts.set_connection_type(ConnectionType::KeepAlive); parts.set_connection_type(ConnectionType::KeepAlive);
} }
@ -120,7 +119,7 @@ impl ResponseBuilder {
/// Set connection type to Upgrade /// Set connection type to Upgrade
#[inline] #[inline]
pub fn upgrade<V>(&mut self, value: V) -> &mut Self pub fn upgrade<V>(mut self, value: V) -> Self
where where
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
@ -132,12 +131,12 @@ impl ResponseBuilder {
self.insert_header((header::UPGRADE, value)); self.insert_header((header::UPGRADE, value));
} }
self res
} }
/// Force close connection, even if it is marked as keep-alive /// Force close connection, even if it is marked as keep-alive
#[inline] #[inline]
pub fn force_close(&mut self) -> &mut Self { pub fn force_close(mut self) -> Self {
if let Some(parts) = self.inner() { if let Some(parts) = self.inner() {
parts.set_connection_type(ConnectionType::Close); parts.set_connection_type(ConnectionType::Close);
} }
@ -146,28 +145,29 @@ impl ResponseBuilder {
/// Disable chunked transfer encoding for HTTP/1.1 streaming responses. /// Disable chunked transfer encoding for HTTP/1.1 streaming responses.
#[inline] #[inline]
pub fn no_chunking(&mut self, len: u64) -> &mut Self { pub fn no_chunking(self, len: u64) -> Self {
let mut buf = itoa::Buffer::new(); let mut buf = itoa::Buffer::new();
self.insert_header((header::CONTENT_LENGTH, buf.format(len))); let mut res = self.insert_header((header::CONTENT_LENGTH, buf.format(len)));
if let Some(parts) = self.inner() { if let Some(head) = res.inner() {
parts.no_chunking(true); head.no_chunking(true);
} }
self
res
} }
/// Set response content type. /// Set response content type.
#[inline] #[inline]
pub fn content_type<V>(&mut self, value: V) -> &mut Self pub fn content_type<V>(mut self, value: V) -> Self
where where
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
if let Some(parts) = self.inner() { if let Some(head) = self.inner() {
match value.try_into_value() { match value.try_into_value() {
Ok(value) => { Ok(value) => {
parts.headers.insert(header::CONTENT_TYPE, value); head.headers.insert(header::CONTENT_TYPE, value);
} }
Err(e) => self.err = Some(e.into()), Err(err) => self.inner = Err(err.into()),
}; };
} }
self self
@ -176,77 +176,74 @@ impl ResponseBuilder {
/// Responses extensions /// Responses extensions
#[inline] #[inline]
pub fn extensions(&self) -> Ref<'_, Extensions> { pub fn extensions(&self) -> Ref<'_, Extensions> {
let head = self.head.as_ref().expect("cannot reuse response builder"); let head = self.inner.as_ref().expect("cannot reuse response builder");
head.extensions.borrow() head.extensions.borrow()
} }
/// Mutable reference to a the response's extensions /// Mutable reference to a the response's extensions
#[inline] #[inline]
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
let head = self.head.as_ref().expect("cannot reuse response builder"); let head = self.inner.as_ref().expect("cannot reuse response builder");
head.extensions.borrow_mut() head.extensions.borrow_mut()
} }
/// Set a body and generate `Response`. /// Creates an owned response builder, leaving a default-ish builder in it's place.
/// ///
/// `ResponseBuilder` can not be used after this call. /// Useful under the assumption the original builder will be dropped immediately.
#[inline] ///
pub fn body<B: Into<Body>>(&mut self, body: B) -> Response<Body> { /// If the builder contains an error, it will be passed to the new, owned builder.
self.message_body(body.into()) pub fn take(&mut self) -> ResponseBuilder {
let res = BoxedResponseHead::new(StatusCode::INTERNAL_SERVER_ERROR);
let inner = mem::replace(&mut self.inner, Ok(res));
ResponseBuilder { inner }
} }
/// Set a body and generate `Response`. /// Set a body and generate `Response`.
/// ///
/// `ResponseBuilder` can not be used after this call. /// `ResponseBuilder` can not be used after this call.
pub fn message_body<B>(&mut self, body: B) -> Response<B> { fn with_body<B>(self, body: B) -> Result<Response<B>, Error> {
if let Some(e) = self.err.take() { match self.inner {
return Response::from(Error::from(e)).into_body(); Ok(head) => Ok(Response {
} head,
body: ResponseBody::Body(body),
let response = self.head.take().expect("cannot reuse response builder"); error: None,
}),
Response { Err(err) => Err(Error::from(err)),
head: response,
body: ResponseBody::Body(body),
error: None,
} }
} }
/// Set a streaming body and generate `Response`. /// Consume builder and generate response with given body.
///
/// `ResponseBuilder` can not be used after this call.
#[inline] #[inline]
pub fn streaming<S, E>(&mut self, stream: S) -> Response<Body> pub fn body<B>(self, body: B) -> Result<Response<B>, Error> {
self.with_body(body.into())
}
/// Consume builder and generate response with given stream as body.
#[inline]
pub fn streaming<S, E>(self, stream: S) -> Result<Response<BodyStream<S>>, Error>
where where
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static, S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<Error> + 'static, E: Into<Error> + 'static,
{ {
self.body(Body::from_message(BodyStream::new(stream))) self.body(BodyStream::new(stream))
} }
/// Set an empty body and generate `Response` /// Consume builder and generate response with empty body.
///
/// `ResponseBuilder` can not be used after this call.
#[inline] #[inline]
pub fn finish(&mut self) -> Response<Body> { pub fn finish(self) -> Result<Response<Body>, Error> {
self.body(Body::Empty) self.body(Body::Empty)
} }
/// This method construct new `ResponseBuilder` /// Consume builder and generate response with empty body, converting errors into responses.
pub fn take(&mut self) -> ResponseBuilder { #[inline]
ResponseBuilder { pub fn complete(self) -> Response<Body> {
head: self.head.take(), self.body(Body::Empty).into()
err: self.err.take(),
}
} }
/// Access to contained response when there is no error. /// Access to contained response when there is no error.
fn inner(&mut self) -> Option<&mut ResponseHead> { fn inner(&mut self) -> Option<&mut ResponseHead> {
if self.err.is_some() { self.inner.as_mut().ok().map(|head| &mut **head)
return None;
}
self.head.as_mut().map(|r| &mut **r)
} }
} }
@ -254,8 +251,7 @@ impl ResponseBuilder {
impl<B> From<Response<B>> for ResponseBuilder { impl<B> From<Response<B>> for ResponseBuilder {
fn from(res: Response<B>) -> ResponseBuilder { fn from(res: Response<B>) -> ResponseBuilder {
ResponseBuilder { ResponseBuilder {
head: Some(res.head), inner: Ok(res.head),
err: None,
} }
} }
} }
@ -273,10 +269,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
msg.no_chunking(!head.chunked()); msg.no_chunking(!head.chunked());
ResponseBuilder { ResponseBuilder { inner: Ok(msg) }
head: Some(msg),
err: None,
}
} }
} }
@ -284,13 +277,13 @@ impl Future for ResponseBuilder {
type Output = Result<Response<Body>, Error>; type Output = Result<Response<Body>, Error>;
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(Ok(self.finish())) Poll::Ready(self.take().finish())
} }
} }
impl fmt::Debug for ResponseBuilder { impl fmt::Debug for ResponseBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let head = self.head.as_ref().unwrap(); let head = self.inner.as_ref().unwrap();
let res = writeln!( let res = writeln!(
f, f,
@ -317,7 +310,7 @@ mod tests {
fn test_basic_builder() { fn test_basic_builder() {
let resp = Response::builder(StatusCode::OK) let resp = Response::builder(StatusCode::OK)
.insert_header(("X-TEST", "value")) .insert_header(("X-TEST", "value"))
.finish(); .complete();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
@ -325,7 +318,7 @@ mod tests {
fn test_upgrade() { fn test_upgrade() {
let resp = Response::builder(StatusCode::OK) let resp = Response::builder(StatusCode::OK)
.upgrade("websocket") .upgrade("websocket")
.finish(); .complete();
assert!(resp.upgrade()); assert!(resp.upgrade());
assert_eq!( assert_eq!(
resp.headers().get(header::UPGRADE).unwrap(), resp.headers().get(header::UPGRADE).unwrap(),
@ -335,7 +328,10 @@ mod tests {
#[test] #[test]
fn test_force_close() { fn test_force_close() {
let resp = Response::builder(StatusCode::OK).force_close().finish(); let resp = Response::builder(StatusCode::OK)
.force_close()
.finish()
.unwrap();
assert!(!resp.keep_alive()) assert!(!resp.keep_alive())
} }
@ -343,13 +339,14 @@ mod tests {
fn test_content_type() { fn test_content_type() {
let resp = Response::builder(StatusCode::OK) let resp = Response::builder(StatusCode::OK)
.content_type("text/plain") .content_type("text/plain")
.body(Body::Empty); .body(Body::Empty)
.unwrap();
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
} }
#[test] #[test]
fn test_into_builder() { fn test_into_builder() {
let mut resp: Response<Body> = "test".into(); let mut resp: Response<_> = "test".into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
resp.headers_mut().insert( resp.headers_mut().insert(
@ -358,7 +355,7 @@ mod tests {
); );
let mut builder: ResponseBuilder = resp.into(); let mut builder: ResponseBuilder = resp.into();
let resp = builder.status(StatusCode::BAD_REQUEST).finish(); let resp = builder.status(StatusCode::BAD_REQUEST).finish().unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let cookie = resp.headers().get_all("Cookie").next().unwrap(); let cookie = resp.headers().get_all("Cookie").next().unwrap();
@ -367,9 +364,11 @@ mod tests {
#[test] #[test]
fn response_builder_header_insert_kv() { fn response_builder_header_insert_kv() {
let mut res = Response::builder(StatusCode::OK); let res = Response::builder(StatusCode::OK)
res.insert_header(("Content-Type", "application/octet-stream")); .insert_header(("Content-Type", "application/octet-stream"))
let res = res.finish(); .take()
.finish()
.unwrap();
assert_eq!( assert_eq!(
res.headers().get("Content-Type"), res.headers().get("Content-Type"),
@ -379,9 +378,11 @@ mod tests {
#[test] #[test]
fn response_builder_header_insert_typed() { fn response_builder_header_insert_typed() {
let mut res = Response::builder(StatusCode::OK); let res = Response::builder(StatusCode::OK)
res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); .insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM))
let res = res.finish(); .take()
.finish()
.unwrap();
assert_eq!( assert_eq!(
res.headers().get("Content-Type"), res.headers().get("Content-Type"),
@ -391,10 +392,12 @@ mod tests {
#[test] #[test]
fn response_builder_header_append_kv() { fn response_builder_header_append_kv() {
let mut res = Response::builder(StatusCode::OK); let mut res = Response::builder(StatusCode::OK)
res.append_header(("Content-Type", "application/octet-stream")); .append_header(("Content-Type", "application/octet-stream"))
res.append_header(("Content-Type", "application/json")); .append_header(("Content-Type", "application/json"))
let res = res.finish(); .take()
.finish()
.unwrap();
let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
assert_eq!(headers.len(), 2); assert_eq!(headers.len(), 2);
@ -407,7 +410,7 @@ mod tests {
let mut res = Response::builder(StatusCode::OK); let mut res = Response::builder(StatusCode::OK);
res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
let res = res.finish(); let res = res.finish().unwrap();
let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
assert_eq!(headers.len(), 2); assert_eq!(headers.len(), 2);

View File

@ -103,38 +103,50 @@ impl ResponseError for HandshakeError {
match self { match self {
HandshakeError::GetMethodRequired => { HandshakeError::GetMethodRequired => {
Response::builder(StatusCode::METHOD_NOT_ALLOWED) Response::builder(StatusCode::METHOD_NOT_ALLOWED)
.insert_header((header::ALLOW, "GET")) .insert_header((header::ALLOW, "GET"))
.finish() .take()
.body(Body::Empty)
.unwrap()
} }
HandshakeError::NoWebsocketUpgrade => { HandshakeError::NoWebsocketUpgrade => {
Response::builder(StatusCode::BAD_REQUEST) Response::builder(StatusCode::BAD_REQUEST)
.reason("No WebSocket Upgrade header found") .reason("No WebSocket Upgrade header found")
.finish() .take()
.body(Body::Empty)
.unwrap()
} }
HandshakeError::NoConnectionUpgrade => { HandshakeError::NoConnectionUpgrade => {
Response::builder(StatusCode::BAD_REQUEST) Response::builder(StatusCode::BAD_REQUEST)
.reason("No Connection upgrade") .reason("No Connection upgrade")
.finish() .take()
.body(Body::Empty)
.unwrap()
} }
HandshakeError::NoVersionHeader => { HandshakeError::NoVersionHeader => {
Response::builder(StatusCode::BAD_REQUEST) Response::builder(StatusCode::BAD_REQUEST)
.reason("WebSocket version header is required") .reason("WebSocket version header is required")
.finish() .take()
.body(Body::Empty)
.unwrap()
} }
HandshakeError::UnsupportedVersion => { HandshakeError::UnsupportedVersion => {
Response::builder(StatusCode::BAD_REQUEST) Response::builder(StatusCode::BAD_REQUEST)
.reason("Unsupported WebSocket version") .reason("Unsupported WebSocket version")
.finish() .take()
.body(Body::Empty)
.unwrap()
} }
HandshakeError::BadWebsocketKey => { HandshakeError::BadWebsocketKey => {
Response::builder(StatusCode::BAD_REQUEST) Response::builder(StatusCode::BAD_REQUEST)
.reason("Handshake error") .reason("Handshake error")
.finish() .take()
.body(Body::Empty)
.unwrap()
} }
} }
} }
@ -326,7 +338,7 @@ mod tests {
.finish(); .finish();
assert_eq!( assert_eq!(
StatusCode::SWITCHING_PROTOCOLS, StatusCode::SWITCHING_PROTOCOLS,
handshake_response(req.head()).finish().status() handshake_response(req.head()).finish().unwrap().status()
); );
} }

View File

@ -205,7 +205,7 @@ async fn test_h2_headers() {
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
)); ));
} }
ok::<_, ()>(builder.body(data.clone())) ready(builder.body(data.clone()))
}) })
.openssl(tls_config()) .openssl(tls_config())
.map_err(|_| ()) .map_err(|_| ())
@ -331,8 +331,9 @@ async fn test_h2_body_length() {
HttpService::build() HttpService::build()
.h2(|_| { .h2(|_| {
let body = once(ok(Bytes::from_static(STR.as_ref()))); let body = once(ok(Bytes::from_static(STR.as_ref())));
ok::<_, ()>( ready(
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.take()
.body(SizedStream::new(STR.len() as u64, body)), .body(SizedStream::new(STR.len() as u64, body)),
) )
}) })
@ -355,9 +356,10 @@ async fn test_h2_body_chunked_explicit() {
HttpService::build() HttpService::build()
.h2(|_| { .h2(|_| {
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
ok::<_, ()>( ready(
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.insert_header((header::TRANSFER_ENCODING, "chunked")) .insert_header((header::TRANSFER_ENCODING, "chunked"))
.take()
.streaming(body), .streaming(body),
) )
}) })
@ -383,9 +385,10 @@ async fn test_h2_response_http_error_handling() {
HttpService::build() HttpService::build()
.h2(fn_service(|_| { .h2(fn_service(|_| {
let broken_header = Bytes::from_static(b"\0\0\0"); let broken_header = Bytes::from_static(b"\0\0\0");
ok::<_, ()>( ready(
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.insert_header((header::CONTENT_TYPE, broken_header)) .insert_header((header::CONTENT_TYPE, broken_header))
.take()
.body(STR), .body(STR),
) )
})) }))

View File

@ -13,7 +13,7 @@ use actix_http::{
}; };
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::{fn_factory_with_config, fn_service}; use actix_service::{fn_factory_with_config, fn_service};
use actix_utils::future::{err, ok}; use actix_utils::future::{err, ok, ready};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures_core::Stream; use futures_core::Stream;
@ -218,9 +218,9 @@ async fn test_h2_headers() {
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
)); ));
} }
ok::<_, ()>(config.body(data.clone())) ready(config.body(data.clone()))
}) })
.rustls(tls_config()) .rustls(tls_config())
}).await; }).await;
let response = srv.sget("/").send().await.unwrap(); let response = srv.sget("/").send().await.unwrap();
@ -370,9 +370,10 @@ async fn test_h2_body_chunked_explicit() {
HttpService::build() HttpService::build()
.h2(|_| { .h2(|_| {
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
ok::<_, ()>( ready(
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.insert_header((header::TRANSFER_ENCODING, "chunked")) .insert_header((header::TRANSFER_ENCODING, "chunked"))
.take()
.streaming(body), .streaming(body),
) )
}) })
@ -398,9 +399,10 @@ async fn test_h2_response_http_error_handling() {
.h2(fn_factory_with_config(|_: ()| { .h2(fn_factory_with_config(|_: ()| {
ok::<_, ()>(fn_service(|_| { ok::<_, ()>(fn_service(|_| {
let broken_header = Bytes::from_static(b"\0\0\0"); let broken_header = Bytes::from_static(b"\0\0\0");
ok::<_, ()>( ready(
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.insert_header((http::header::CONTENT_TYPE, broken_header)) .insert_header((http::header::CONTENT_TYPE, broken_header))
.take()
.body(STR), .body(STR),
) )
})) }))

View File

@ -416,7 +416,7 @@ async fn test_h1_headers() {
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
)); ));
} }
ok::<_, ()>(builder.body(data.clone())) ok::<_, ()>(builder.body(data.clone()).unwrap())
}).tcp() }).tcp()
}).await; }).await;
@ -566,9 +566,10 @@ async fn test_h1_body_chunked_explicit() {
HttpService::build() HttpService::build()
.h1(|_| { .h1(|_| {
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
ok::<_, ()>( ready(
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.insert_header((header::TRANSFER_ENCODING, "chunked")) .insert_header((header::TRANSFER_ENCODING, "chunked"))
.take()
.streaming(body), .streaming(body),
) )
}) })
@ -601,7 +602,7 @@ async fn test_h1_body_chunked_implicit() {
HttpService::build() HttpService::build()
.h1(|_| { .h1(|_| {
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
ok::<_, ()>(Response::builder(StatusCode::OK).streaming(body)) ready(Response::builder(StatusCode::OK).streaming(body))
}) })
.tcp() .tcp()
}) })
@ -630,9 +631,10 @@ async fn test_h1_response_http_error_handling() {
HttpService::build() HttpService::build()
.h1(fn_service(|_| { .h1(fn_service(|_| {
let broken_header = Bytes::from_static(b"\0\0\0"); let broken_header = Bytes::from_static(b"\0\0\0");
ok::<_, ()>( ready(
Response::builder(StatusCode::OK) Response::builder(StatusCode::OK)
.insert_header((http::header::CONTENT_TYPE, broken_header)) .insert_header((http::header::CONTENT_TYPE, broken_header))
.take()
.body(STR), .body(STR),
) )
})) }))

View File

@ -52,7 +52,11 @@ where
fn call(&self, (req, mut framed): (Request, Framed<T, h1::Codec>)) -> Self::Future { fn call(&self, (req, mut framed): (Request, Framed<T, h1::Codec>)) -> Self::Future {
let fut = async move { let fut = async move {
let res = ws::handshake(req.head()).unwrap().message_body(()); let res = ws::handshake(req.head())
.unwrap()
.finish()
.unwrap()
.set_body(());
framed framed
.send((res, body::BodySize::None).into()) .send((res, body::BodySize::None).into())

View File

@ -82,8 +82,8 @@ impl Responder for HttpResponseBuilder {
impl Responder for actix_http::ResponseBuilder { impl Responder for actix_http::ResponseBuilder {
#[inline] #[inline]
fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { fn respond_to(self, _: &HttpRequest) -> HttpResponse {
HttpResponse::from(self.finish()) HttpResponse::from(self.complete())
} }
} }