mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-24 07:53:00 +01:00
handle response errors
This commit is contained in:
parent
ab3e12f2b4
commit
1a322966ff
40
src/body.rs
40
src/body.rs
@ -37,6 +37,37 @@ impl MessageBody for () {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ResponseBody<B> {
|
||||
Body(B),
|
||||
Other(Body),
|
||||
}
|
||||
|
||||
impl<B: MessageBody> ResponseBody<B> {
|
||||
pub fn as_ref(&self) -> Option<&B> {
|
||||
if let ResponseBody::Body(ref b) = self {
|
||||
Some(b)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: MessageBody> MessageBody for ResponseBody<B> {
|
||||
fn length(&self) -> BodyLength {
|
||||
match self {
|
||||
ResponseBody::Body(ref body) => body.length(),
|
||||
ResponseBody::Other(ref body) => body.length(),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
||||
match self {
|
||||
ResponseBody::Body(ref mut body) => body.poll_next(),
|
||||
ResponseBody::Other(ref mut body) => body.poll_next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents various types of http message body.
|
||||
pub enum Body {
|
||||
/// Empty response. `Content-Length` header is not set.
|
||||
@ -332,6 +363,15 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseBody<Body> {
|
||||
pub(crate) fn get_ref(&self) -> &[u8] {
|
||||
match *self {
|
||||
ResponseBody::Body(ref b) => b.get_ref(),
|
||||
ResponseBody::Other(ref b) => b.get_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_str() {
|
||||
assert_eq!(Body::from("").length(), BodyLength::Sized(0));
|
||||
|
@ -13,7 +13,7 @@ use tokio_timer::Delay;
|
||||
use error::{ParseError, PayloadError};
|
||||
use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter};
|
||||
|
||||
use body::{BodyLength, MessageBody};
|
||||
use body::{Body, BodyLength, MessageBody, ResponseBody};
|
||||
use config::ServiceConfig;
|
||||
use error::DispatchError;
|
||||
use request::Request;
|
||||
@ -70,7 +70,7 @@ enum DispatcherMessage {
|
||||
enum State<S: Service, B: MessageBody> {
|
||||
None,
|
||||
ServiceCall(S::Future),
|
||||
SendPayload(B),
|
||||
SendPayload(ResponseBody<B>),
|
||||
}
|
||||
|
||||
impl<S: Service, B: MessageBody> State<S, B> {
|
||||
@ -186,11 +186,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn send_response<B1: MessageBody>(
|
||||
fn send_response(
|
||||
&mut self,
|
||||
message: Response<()>,
|
||||
body: B1,
|
||||
) -> Result<State<S, B1>, DispatchError<S::Error>> {
|
||||
body: ResponseBody<B>,
|
||||
) -> Result<State<S, B>, DispatchError<S::Error>> {
|
||||
self.framed
|
||||
.force_send(Message::Item((message, body.length())))
|
||||
.map_err(|err| {
|
||||
@ -217,7 +217,7 @@ where
|
||||
Some(self.handle_request(req)?)
|
||||
}
|
||||
Some(DispatcherMessage::Error(res)) => {
|
||||
self.send_response(res, ())?;
|
||||
self.send_response(res, ResponseBody::Other(Body::Empty))?;
|
||||
None
|
||||
}
|
||||
None => None,
|
||||
@ -431,7 +431,7 @@ where
|
||||
trace!("Slow request timeout");
|
||||
let _ = self.send_response(
|
||||
Response::RequestTimeout().finish().drop_body(),
|
||||
(),
|
||||
ResponseBody::Other(Body::Empty),
|
||||
);
|
||||
} else {
|
||||
trace!("Keep-alive connection timeout");
|
||||
|
@ -12,7 +12,7 @@ use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version};
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
|
||||
use body::{Body, BodyStream, MessageBody};
|
||||
use body::{Body, BodyStream, MessageBody, ResponseBody};
|
||||
use error::Error;
|
||||
use header::{Header, IntoHeaderValue};
|
||||
use message::{ConnectionType, Head, ResponseHead};
|
||||
@ -21,7 +21,7 @@ use message::{ConnectionType, Head, ResponseHead};
|
||||
pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536;
|
||||
|
||||
/// An HTTP Response
|
||||
pub struct Response<B: MessageBody = Body>(Box<InnerResponse>, B);
|
||||
pub struct Response<B: MessageBody = Body>(Box<InnerResponse>, ResponseBody<B>);
|
||||
|
||||
impl Response<Body> {
|
||||
/// Create http response builder with specific status.
|
||||
@ -71,6 +71,15 @@ impl Response<Body> {
|
||||
cookies: jar,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert response to response with body
|
||||
pub fn into_body<B: MessageBody>(self) -> Response<B> {
|
||||
let b = match self.1 {
|
||||
ResponseBody::Body(b) => b,
|
||||
ResponseBody::Other(b) => b,
|
||||
};
|
||||
Response(self.0, ResponseBody::Other(b))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: MessageBody> Response<B> {
|
||||
@ -195,23 +204,26 @@ impl<B: MessageBody> Response<B> {
|
||||
|
||||
/// Get body os this response
|
||||
#[inline]
|
||||
pub fn body(&self) -> &B {
|
||||
pub(crate) fn body(&self) -> &ResponseBody<B> {
|
||||
&self.1
|
||||
}
|
||||
|
||||
/// Set a body
|
||||
pub fn set_body<B2: MessageBody>(self, body: B2) -> Response<B2> {
|
||||
Response(self.0, body)
|
||||
pub(crate) fn set_body<B2: MessageBody>(self, body: B2) -> Response<B2> {
|
||||
Response(self.0, ResponseBody::Body(body))
|
||||
}
|
||||
|
||||
/// Drop request's body
|
||||
pub fn drop_body(self) -> Response<()> {
|
||||
Response(self.0, ())
|
||||
pub(crate) fn drop_body(self) -> Response<()> {
|
||||
Response(self.0, ResponseBody::Body(()))
|
||||
}
|
||||
|
||||
/// Set a body and return previous body value
|
||||
pub fn replace_body<B2: MessageBody>(self, body: B2) -> (Response<B2>, B) {
|
||||
(Response(self.0, body), self.1)
|
||||
pub(crate) fn replace_body<B2: MessageBody>(
|
||||
self,
|
||||
body: B2,
|
||||
) -> (Response<B2>, ResponseBody<B>) {
|
||||
(Response(self.0, ResponseBody::Body(body)), self.1)
|
||||
}
|
||||
|
||||
/// Size of response in bytes, excluding HTTP headers
|
||||
@ -233,7 +245,10 @@ impl<B: MessageBody> Response<B> {
|
||||
}
|
||||
|
||||
pub(crate) fn from_parts(parts: ResponseParts) -> Response {
|
||||
Response(Box::new(InnerResponse::from_parts(parts)), Body::Empty)
|
||||
Response(
|
||||
Box::new(InnerResponse::from_parts(parts)),
|
||||
ResponseBody::Body(Body::Empty),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,7 +265,7 @@ impl<B: MessageBody> fmt::Debug for Response<B> {
|
||||
for (key, val) in self.get_ref().head.headers.iter() {
|
||||
let _ = writeln!(f, " {:?}: {:?}", key, val);
|
||||
}
|
||||
let _ = writeln!(f, " body: {:?}", self.body().length());
|
||||
let _ = writeln!(f, " body: {:?}", self.1.length());
|
||||
res
|
||||
}
|
||||
}
|
||||
@ -559,11 +574,9 @@ impl ResponseBuilder {
|
||||
///
|
||||
/// `ResponseBuilder` can not be used after this call.
|
||||
pub fn message_body<B: MessageBody>(&mut self, body: B) -> Response<B> {
|
||||
let mut error = if let Some(e) = self.err.take() {
|
||||
Some(Error::from(e))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(e) = self.err.take() {
|
||||
return Response::from(Error::from(e)).into_body();
|
||||
}
|
||||
|
||||
let mut response = self.response.take().expect("cannot reuse response builder");
|
||||
if let Some(ref jar) = self.cookies {
|
||||
@ -572,17 +585,12 @@ impl ResponseBuilder {
|
||||
Ok(val) => {
|
||||
let _ = response.head.headers.append(header::SET_COOKIE, val);
|
||||
}
|
||||
Err(e) => if error.is_none() {
|
||||
error = Some(Error::from(e));
|
||||
},
|
||||
Err(e) => return Response::from(Error::from(e)).into_body(),
|
||||
};
|
||||
}
|
||||
}
|
||||
if let Some(error) = error {
|
||||
response.error = Some(error);
|
||||
}
|
||||
|
||||
Response(response, body)
|
||||
Response(response, ResponseBody::Body(body))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -812,9 +820,12 @@ impl ResponsePool {
|
||||
) -> Response<B> {
|
||||
if let Some(mut msg) = pool.0.borrow_mut().pop_front() {
|
||||
msg.head.status = status;
|
||||
Response(msg, body)
|
||||
Response(msg, ResponseBody::Body(body))
|
||||
} else {
|
||||
Response(Box::new(InnerResponse::new(status, pool)), body)
|
||||
Response(
|
||||
Box::new(InnerResponse::new(status, pool)),
|
||||
ResponseBody::Body(body),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -971,10 +982,7 @@ mod tests {
|
||||
let resp = Response::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]);
|
||||
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
||||
assert_eq!(ct, HeaderValue::from_static("application/json"));
|
||||
assert_eq!(
|
||||
*resp.body(),
|
||||
Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))
|
||||
);
|
||||
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -984,10 +992,7 @@ mod tests {
|
||||
.json(vec!["v1", "v2", "v3"]);
|
||||
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
||||
assert_eq!(ct, HeaderValue::from_static("text/json"));
|
||||
assert_eq!(
|
||||
*resp.body(),
|
||||
Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))
|
||||
);
|
||||
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -995,10 +1000,7 @@ mod tests {
|
||||
let resp = Response::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]);
|
||||
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
||||
assert_eq!(ct, HeaderValue::from_static("application/json"));
|
||||
assert_eq!(
|
||||
*resp.body(),
|
||||
Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))
|
||||
);
|
||||
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1008,10 +1010,7 @@ mod tests {
|
||||
.json2(&vec!["v1", "v2", "v3"]);
|
||||
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
||||
assert_eq!(ct, HeaderValue::from_static("text/json"));
|
||||
assert_eq!(
|
||||
*resp.body(),
|
||||
Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))
|
||||
);
|
||||
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -6,7 +6,7 @@ use futures::future::{ok, Either, FutureResult};
|
||||
use futures::{Async, Future, Poll, Sink};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use body::{BodyLength, MessageBody};
|
||||
use body::{BodyLength, MessageBody, ResponseBody};
|
||||
use error::{Error, ResponseError};
|
||||
use h1::{Codec, Message};
|
||||
use response::Response;
|
||||
@ -174,7 +174,7 @@ where
|
||||
|
||||
pub struct SendResponseFut<T, B> {
|
||||
res: Option<Message<(Response<()>, BodyLength)>>,
|
||||
body: Option<B>,
|
||||
body: Option<ResponseBody<B>>,
|
||||
framed: Option<Framed<T, Codec>>,
|
||||
}
|
||||
|
||||
|
@ -445,3 +445,25 @@ fn test_body_chunked_implicit() {
|
||||
let bytes = srv.block_on(response.body()).unwrap();
|
||||
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_http_error_handling() {
|
||||
let mut srv = test::TestServer::with_factory(|| {
|
||||
h1::H1Service::new(|_| {
|
||||
let broken_header = Bytes::from_static(b"\0\0\0");
|
||||
ok::<_, ()>(
|
||||
Response::Ok()
|
||||
.header(http::header::CONTENT_TYPE, broken_header)
|
||||
.body(STR),
|
||||
)
|
||||
}).map(|_| ())
|
||||
});
|
||||
|
||||
let req = srv.get().finish().unwrap();
|
||||
let response = srv.send_request(req).unwrap();
|
||||
assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
|
||||
|
||||
// read response
|
||||
let bytes = srv.block_on(response.body()).unwrap();
|
||||
assert!(bytes.is_empty());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user