1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-06-25 06:39:22 +02:00

refined error model (#2253)

This commit is contained in:
Rob Ede
2021-06-17 17:57:58 +01:00
committed by GitHub
parent bb0331ae28
commit 532f7b9923
69 changed files with 1498 additions and 901 deletions

View File

@ -76,7 +76,9 @@ impl MessageBody for AnyBody {
// TODO: MSRV 1.51: poll_map_err
AnyBody::Message(body) => match ready!(body.as_pin_mut().poll_next(cx)) {
Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
Some(Err(err)) => {
Poll::Ready(Some(Err(Error::new_body().with_cause(err))))
}
Some(Ok(val)) => Poll::Ready(Some(Ok(val))),
None => Poll::Ready(None),
},
@ -162,9 +164,10 @@ impl From<BytesMut> for AnyBody {
}
}
impl<S> From<SizedStream<S>> for AnyBody
impl<S, E> From<SizedStream<S>> for AnyBody
where
S: Stream<Item = Result<Bytes, Error>> + 'static,
S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<Box<dyn StdError>> + 'static,
{
fn from(s: SizedStream<S>) -> Body {
AnyBody::from_message(s)
@ -174,7 +177,7 @@ where
impl<S, E> From<BodyStream<S>> for AnyBody
where
S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<Error> + 'static,
E: Into<Box<dyn StdError>> + 'static,
{
fn from(s: BodyStream<S>) -> Body {
AnyBody::from_message(s)
@ -222,7 +225,7 @@ impl MessageBody for BoxAnyBody {
) -> Poll<Option<Result<Bytes, Self::Error>>> {
// TODO: MSRV 1.51: poll_map_err
match ready!(self.0.as_mut().poll_next(cx)) {
Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
Some(Err(err)) => Poll::Ready(Some(Err(Error::new_body().with_cause(err)))),
Some(Ok(val)) => Poll::Ready(Some(Ok(val))),
None => Poll::Ready(None),
}

View File

@ -1,4 +1,5 @@
use std::{
error::Error as StdError,
pin::Pin,
task::{Context, Poll},
};
@ -7,8 +8,6 @@ use bytes::Bytes;
use futures_core::{ready, Stream};
use pin_project_lite::pin_project;
use crate::error::Error;
use super::{BodySize, MessageBody};
pin_project! {
@ -24,7 +23,7 @@ pin_project! {
impl<S, E> BodyStream<S>
where
S: Stream<Item = Result<Bytes, E>>,
E: Into<Error>,
E: Into<Box<dyn StdError>> + 'static,
{
pub fn new(stream: S) -> Self {
BodyStream { stream }
@ -34,9 +33,9 @@ where
impl<S, E> MessageBody for BodyStream<S>
where
S: Stream<Item = Result<Bytes, E>>,
E: Into<Error>,
E: Into<Box<dyn StdError>> + 'static,
{
type Error = Error;
type Error = E;
fn size(&self) -> BodySize {
BodySize::Stream
@ -56,7 +55,7 @@ where
let chunk = match ready!(stream.poll_next(cx)) {
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
opt => opt.map(|res| res.map_err(Into::into)),
opt => opt,
};
return Poll::Ready(chunk);
@ -66,9 +65,16 @@ where
#[cfg(test)]
mod tests {
use actix_rt::pin;
use std::{convert::Infallible, time::Duration};
use actix_rt::{
pin,
time::{sleep, Sleep},
};
use actix_utils::future::poll_fn;
use futures_util::stream;
use derive_more::{Display, Error};
use futures_core::ready;
use futures_util::{stream, FutureExt as _};
use super::*;
use crate::body::to_bytes;
@ -78,7 +84,7 @@ mod tests {
let body = BodyStream::new(stream::iter(
["1", "", "2"]
.iter()
.map(|&v| Ok(Bytes::from(v)) as Result<Bytes, ()>),
.map(|&v| Ok::<_, Infallible>(Bytes::from(v))),
));
pin!(body);
@ -103,9 +109,63 @@ mod tests {
let body = BodyStream::new(stream::iter(
["1", "", "2"]
.iter()
.map(|&v| Ok(Bytes::from(v)) as Result<Bytes, ()>),
.map(|&v| Ok::<_, Infallible>(Bytes::from(v))),
));
assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12")));
}
#[derive(Debug, Display, Error)]
#[display(fmt = "stream error")]
struct StreamErr;
#[actix_rt::test]
async fn stream_immediate_error() {
let body = BodyStream::new(stream::once(async { Err(StreamErr) }));
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
}
#[actix_rt::test]
async fn stream_delayed_error() {
let body =
BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)]));
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
#[pin_project::pin_project(project = TimeDelayStreamProj)]
#[derive(Debug)]
enum TimeDelayStream {
Start,
Sleep(Pin<Box<Sleep>>),
Done,
}
impl Stream for TimeDelayStream {
type Item = Result<Bytes, StreamErr>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
match self.as_mut().get_mut() {
TimeDelayStream::Start => {
let sleep = sleep(Duration::from_millis(1));
self.as_mut().set(TimeDelayStream::Sleep(Box::pin(sleep)));
cx.waker().wake_by_ref();
Poll::Pending
}
TimeDelayStream::Sleep(ref mut delay) => {
ready!(delay.poll_unpin(cx));
self.set(TimeDelayStream::Done);
cx.waker().wake_by_ref();
Poll::Pending
}
TimeDelayStream::Done => Poll::Ready(Some(Err(StreamErr))),
}
}
}
let body = BodyStream::new(TimeDelayStream::Start);
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
}
}

View File

@ -191,11 +191,15 @@ mod tests {
}
#[actix_rt::test]
async fn test_box() {
async fn test_box_and_pin() {
let val = Box::new(());
pin!(val);
assert_eq!(val.size(), BodySize::Empty);
assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none());
let mut val = Box::pin(());
assert_eq!(val.size(), BodySize::Empty);
assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none());
}
#[actix_rt::test]

View File

@ -1,4 +1,5 @@
use std::{
error::Error as StdError,
pin::Pin,
task::{Context, Poll},
};
@ -7,15 +8,13 @@ use bytes::Bytes;
use futures_core::{ready, Stream};
use pin_project_lite::pin_project;
use crate::error::Error;
use super::{BodySize, MessageBody};
pin_project! {
/// Known sized streaming response wrapper.
///
/// This body implementation should be used if total size of stream is known. Data get sent as is
/// without using transfer encoding.
/// This body implementation should be used if total size of stream is known. Data is sent as-is
/// without using chunked transfer encoding.
pub struct SizedStream<S> {
size: u64,
#[pin]
@ -23,20 +22,22 @@ pin_project! {
}
}
impl<S> SizedStream<S>
impl<S, E> SizedStream<S>
where
S: Stream<Item = Result<Bytes, Error>>,
S: Stream<Item = Result<Bytes, E>>,
E: Into<Box<dyn StdError>> + 'static,
{
pub fn new(size: u64, stream: S) -> Self {
SizedStream { size, stream }
}
}
impl<S> MessageBody for SizedStream<S>
impl<S, E> MessageBody for SizedStream<S>
where
S: Stream<Item = Result<Bytes, Error>>,
S: Stream<Item = Result<Bytes, E>>,
E: Into<Box<dyn StdError>> + 'static,
{
type Error = Error;
type Error = E;
fn size(&self) -> BodySize {
BodySize::Sized(self.size as u64)
@ -66,6 +67,8 @@ where
#[cfg(test)]
mod tests {
use std::convert::Infallible;
use actix_rt::pin;
use actix_utils::future::poll_fn;
use futures_util::stream;
@ -77,7 +80,11 @@ mod tests {
async fn skips_empty_chunks() {
let body = SizedStream::new(
2,
stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))),
stream::iter(
["1", "", "2"]
.iter()
.map(|&v| Ok::<_, Infallible>(Bytes::from(v))),
),
);
pin!(body);
@ -103,7 +110,11 @@ mod tests {
async fn read_to_bytes() {
let body = SizedStream::new(
2,
stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))),
stream::iter(
["1", "", "2"]
.iter()
.map(|&v| Ok::<_, Infallible>(Bytes::from(v))),
),
);
assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12")));

View File

@ -1,19 +1,16 @@
use std::marker::PhantomData;
use std::rc::Rc;
use std::{fmt, net};
use std::{error::Error as StdError, fmt, marker::PhantomData, net, rc::Rc};
use actix_codec::Framed;
use actix_service::{IntoServiceFactory, Service, ServiceFactory};
use crate::body::MessageBody;
use crate::config::{KeepAlive, ServiceConfig};
use crate::error::Error;
use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler};
use crate::h2::H2Service;
use crate::request::Request;
use crate::response::Response;
use crate::service::HttpService;
use crate::{ConnectCallback, Extensions};
use crate::{
body::{AnyBody, MessageBody},
config::{KeepAlive, ServiceConfig},
h1::{self, ExpectHandler, H1Service, UpgradeHandler},
h2::H2Service,
service::HttpService,
ConnectCallback, Extensions, Request, Response,
};
/// A HTTP service builder
///
@ -34,7 +31,7 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug,
<S::Service as Service<Request>>::Future: 'static,
{
@ -57,13 +54,13 @@ where
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug,
<S::Service as Service<Request>>::Future: 'static,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
{
@ -123,7 +120,7 @@ where
where
F: IntoServiceFactory<X1, Request>,
X1: ServiceFactory<Request, Config = (), Response = Request>,
X1::Error: Into<Error>,
X1::Error: Into<Response<AnyBody>>,
X1::InitError: fmt::Debug,
{
HttpServiceBuilder {
@ -145,8 +142,8 @@ where
/// and this service get called with original request and framed object.
pub fn upgrade<F, U1>(self, upgrade: F) -> HttpServiceBuilder<T, S, X, U1>
where
F: IntoServiceFactory<U1, (Request, Framed<T, Codec>)>,
U1: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
F: IntoServiceFactory<U1, (Request, Framed<T, h1::Codec>)>,
U1: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
U1::Error: fmt::Display,
U1::InitError: fmt::Debug,
{
@ -181,7 +178,7 @@ where
where
B: MessageBody,
F: IntoServiceFactory<S, Request>,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
{
@ -203,12 +200,12 @@ where
pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
where
F: IntoServiceFactory<S, Request>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
let cfg = ServiceConfig::new(
self.keep_alive,
@ -226,12 +223,12 @@ where
pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U>
where
F: IntoServiceFactory<S, Request>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
let cfg = ServiceConfig::new(
self.keep_alive,

View File

@ -1,15 +1,16 @@
use std::io;
use std::{error::Error as StdError, fmt, io};
use derive_more::{Display, From};
#[cfg(feature = "openssl")]
use actix_tls::accept::openssl::SslError;
use crate::error::{Error, ParseError, ResponseError};
use crate::http::{Error as HttpError, StatusCode};
use crate::error::{Error, ParseError};
use crate::http::Error as HttpError;
/// A set of errors that can occur while connecting to an HTTP host
#[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum ConnectError {
/// SSL feature is not enabled
#[display(fmt = "SSL is not supported")]
@ -64,6 +65,7 @@ impl From<actix_tls::connect::ConnectError> for ConnectError {
}
#[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum InvalidUrl {
#[display(fmt = "Missing URL scheme")]
MissingScheme,
@ -82,6 +84,7 @@ impl std::error::Error for InvalidUrl {}
/// A set of errors that can occur during request sending and response reading
#[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum SendRequestError {
/// Invalid URL
#[display(fmt = "Invalid URL: {}", _0)]
@ -115,25 +118,17 @@ pub enum SendRequestError {
/// Error sending request body
Body(Error),
/// Other errors that can occur after submitting a request.
#[display(fmt = "{:?}: {}", _1, _0)]
Custom(Box<dyn StdError>, Box<dyn fmt::Debug>),
}
impl std::error::Error for SendRequestError {}
/// Convert `SendRequestError` to a server `Response`
impl ResponseError for SendRequestError {
fn status_code(&self) -> StatusCode {
match *self {
SendRequestError::Connect(ConnectError::Timeout) => {
StatusCode::GATEWAY_TIMEOUT
}
SendRequestError::Connect(_) => StatusCode::BAD_REQUEST,
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
/// A set of errors that can occur during freezing a request
#[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum FreezeRequestError {
/// Invalid URL
#[display(fmt = "Invalid URL: {}", _0)]
@ -142,15 +137,20 @@ pub enum FreezeRequestError {
/// HTTP error
#[display(fmt = "{}", _0)]
Http(HttpError),
/// Other errors that can occur after submitting a request.
#[display(fmt = "{:?}: {}", _1, _0)]
Custom(Box<dyn StdError>, Box<dyn fmt::Debug>),
}
impl std::error::Error for FreezeRequestError {}
impl From<FreezeRequestError> for SendRequestError {
fn from(e: FreezeRequestError) -> Self {
match e {
FreezeRequestError::Url(e) => e.into(),
FreezeRequestError::Http(e) => e.into(),
fn from(err: FreezeRequestError) -> Self {
match err {
FreezeRequestError::Url(err) => err.into(),
FreezeRequestError::Http(err) => err.into(),
FreezeRequestError::Custom(err, msg) => SendRequestError::Custom(err, msg),
}
}
}

View File

@ -133,9 +133,7 @@ where
},
EncoderBodyProj::BoxedStream(ref mut b) => {
match ready!(b.as_pin_mut().poll_next(cx)) {
Some(Err(err)) => {
Poll::Ready(Some(Err(EncoderError::Boxed(err.into()))))
}
Some(Err(err)) => Poll::Ready(Some(Err(EncoderError::Boxed(err)))),
Some(Ok(val)) => Poll::Ready(Some(Ok(val))),
None => Poll::Ready(None),
}
@ -337,7 +335,7 @@ pub enum EncoderError<E> {
Body(E),
#[display(fmt = "boxed")]
Boxed(Error),
Boxed(Box<dyn StdError>),
#[display(fmt = "blocking")]
Blocking(BlockingError),
@ -346,19 +344,19 @@ pub enum EncoderError<E> {
Io(io::Error),
}
impl<E: StdError> StdError for EncoderError<E> {
impl<E: StdError + 'static> StdError for EncoderError<E> {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
None
}
}
impl<E: Into<Error>> From<EncoderError<E>> for Error {
fn from(err: EncoderError<E>) -> Self {
match err {
EncoderError::Body(err) => err.into(),
EncoderError::Boxed(err) => err,
EncoderError::Blocking(err) => err.into(),
EncoderError::Io(err) => err.into(),
match self {
EncoderError::Body(err) => Some(err),
EncoderError::Boxed(err) => Some(&**err),
EncoderError::Blocking(err) => Some(err),
EncoderError::Io(err) => Some(err),
}
}
}
impl<E: StdError + 'static> From<EncoderError<E>> for crate::Error {
fn from(err: EncoderError<E>) -> Self {
crate::Error::new_encoder().with_cause(err)
}
}

View File

@ -1,174 +1,155 @@
//! Error and Result module
use std::{
error::Error as StdError,
fmt,
io::{self, Write as _},
str::Utf8Error,
string::FromUtf8Error,
};
use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};
use bytes::BytesMut;
use derive_more::{Display, Error, From};
use http::{header, uri::InvalidUri, StatusCode};
use serde::de::value::Error as DeError;
use http::{uri::InvalidUri, StatusCode};
use crate::{body::Body, helpers::Writer, Response};
use crate::{
body::{AnyBody, Body},
ws, Response,
};
pub use http::Error as HttpError;
/// General purpose actix web error.
///
/// An actix web error is used to carry errors from `std::error`
/// through actix in a convenient way. It can be created through
/// converting errors with `into()`.
///
/// Whenever it is created from an external object a response error is created
/// for it that can be used to create an HTTP response from it this means that
/// if you have access to an actix `Error` you can always get a
/// `ResponseError` reference from it.
pub struct Error {
cause: Box<dyn ResponseError>,
inner: Box<ErrorInner>,
}
pub(crate) struct ErrorInner {
#[allow(dead_code)]
kind: Kind,
cause: Option<Box<dyn StdError>>,
}
impl Error {
/// Returns the reference to the underlying `ResponseError`.
pub fn as_response_error(&self) -> &dyn ResponseError {
self.cause.as_ref()
fn new(kind: Kind) -> Self {
Self {
inner: Box::new(ErrorInner { kind, cause: None }),
}
}
/// Similar to `as_response_error` but downcasts.
pub fn as_error<T: ResponseError + 'static>(&self) -> Option<&T> {
<dyn ResponseError>::downcast_ref(self.cause.as_ref())
pub(crate) fn new_http() -> Self {
Self::new(Kind::Http)
}
pub(crate) fn new_parse() -> Self {
Self::new(Kind::Parse)
}
pub(crate) fn new_payload() -> Self {
Self::new(Kind::Payload)
}
pub(crate) fn new_body() -> Self {
Self::new(Kind::Body)
}
pub(crate) fn new_send_response() -> Self {
Self::new(Kind::SendResponse)
}
// TODO: remove allow
#[allow(dead_code)]
pub(crate) fn new_io() -> Self {
Self::new(Kind::Io)
}
pub(crate) fn new_encoder() -> Self {
Self::new(Kind::Encoder)
}
pub(crate) fn new_ws() -> Self {
Self::new(Kind::Ws)
}
pub(crate) fn with_cause(mut self, cause: impl Into<Box<dyn StdError>>) -> Self {
self.inner.cause = Some(cause.into());
self
}
}
/// Errors that can generate responses.
pub trait ResponseError: fmt::Debug + fmt::Display {
/// Returns appropriate status code for error.
///
/// A 500 Internal Server Error is used by default. If [error_response](Self::error_response) is
/// also implemented and does not call `self.status_code()`, then this will not be used.
fn status_code(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}
impl From<Error> for Response<AnyBody> {
fn from(err: Error) -> Self {
let status_code = match err.inner.kind {
Kind::Parse => StatusCode::BAD_REQUEST,
_ => StatusCode::INTERNAL_SERVER_ERROR,
};
/// Creates full response for error.
///
/// By default, the generated response uses a 500 Internal Server Error status code, a
/// `Content-Type` of `text/plain`, and the body is set to `Self`'s `Display` impl.
fn error_response(&self) -> Response<Body> {
let mut resp = Response::new(self.status_code());
let mut buf = BytesMut::new();
let _ = write!(Writer(&mut buf), "{}", self);
resp.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain; charset=utf-8"),
);
resp.set_body(Body::from(buf))
Response::new(status_code).set_body(Body::from(err.to_string()))
}
downcast_get_type_id!();
}
downcast!(ResponseError);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
pub enum Kind {
#[display(fmt = "error processing HTTP")]
Http,
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.cause, f)
}
#[display(fmt = "error parsing HTTP message")]
Parse,
#[display(fmt = "request payload read error")]
Payload,
#[display(fmt = "response body write error")]
Body,
#[display(fmt = "send response error")]
SendResponse,
#[display(fmt = "error in WebSocket process")]
Ws,
#[display(fmt = "connection error")]
Io,
#[display(fmt = "encoder error")]
Encoder,
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", &self.cause)
// TODO: more detail
f.write_str("actix_http::Error")
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.inner.cause.as_ref() {
Some(err) => write!(f, "{}: {}", &self.inner.kind, err),
None => write!(f, "{}", &self.inner.kind),
}
}
}
impl From<()> for Error {
fn from(_: ()) -> Self {
Error::from(UnitError)
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.inner.cause.as_ref().map(|err| err.as_ref())
}
}
impl From<std::convert::Infallible> for Error {
fn from(_: std::convert::Infallible) -> Self {
// hint that an error that will never happen
unreachable!()
fn from(err: std::convert::Infallible) -> Self {
match err {}
}
}
/// Convert `Error` to a `Response` instance
impl From<Error> for Response<Body> {
fn from(err: Error) -> Self {
Response::from_error(err)
impl From<ws::ProtocolError> for Error {
fn from(err: ws::ProtocolError) -> Self {
Self::new_ws().with_cause(err)
}
}
/// `Error` for any error that implements `ResponseError`
impl<T: ResponseError + 'static> From<T> for Error {
fn from(err: T) -> Error {
Error {
cause: Box::new(err),
}
impl From<HttpError> for Error {
fn from(err: HttpError) -> Self {
Self::new_http().with_cause(err)
}
}
#[derive(Debug, Display, Error)]
#[display(fmt = "Unknown Error")]
struct UnitError;
impl ResponseError for Box<dyn StdError + 'static> {}
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`UnitError`].
impl ResponseError for UnitError {}
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix_tls::accept::openssl::SslError`].
#[cfg(feature = "openssl")]
impl ResponseError for actix_tls::accept::openssl::SslError {}
/// Returns [`StatusCode::BAD_REQUEST`] for [`DeError`].
impl ResponseError for DeError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
}
/// Returns [`StatusCode::BAD_REQUEST`] for [`Utf8Error`].
impl ResponseError for Utf8Error {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
}
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`HttpError`].
impl ResponseError for HttpError {}
/// Inspects the underlying [`io::ErrorKind`] and returns an appropriate status code.
///
/// If the error is [`io::ErrorKind::NotFound`], [`StatusCode::NOT_FOUND`] is returned. If the
/// error is [`io::ErrorKind::PermissionDenied`], [`StatusCode::FORBIDDEN`] is returned. Otherwise,
/// [`StatusCode::INTERNAL_SERVER_ERROR`] is returned.
impl ResponseError for io::Error {
fn status_code(&self) -> StatusCode {
match self.kind() {
io::ErrorKind::NotFound => StatusCode::NOT_FOUND,
io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN,
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
/// Returns [`StatusCode::BAD_REQUEST`] for [`header::InvalidHeaderValue`].
impl ResponseError for header::InvalidHeaderValue {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
impl From<ws::HandshakeError> for Error {
fn from(err: ws::HandshakeError) -> Self {
Self::new_ws().with_cause(err)
}
}
@ -218,13 +199,6 @@ pub enum ParseError {
Utf8(Utf8Error),
}
/// Return `BadRequest` for `ParseError`
impl ResponseError for ParseError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
}
impl From<io::Error> for ParseError {
fn from(err: io::Error) -> ParseError {
ParseError::Io(err)
@ -263,14 +237,23 @@ impl From<httparse::Error> for ParseError {
}
}
impl From<ParseError> for Error {
fn from(err: ParseError) -> Self {
Self::new_parse().with_cause(err)
}
}
impl From<ParseError> for Response<AnyBody> {
fn from(err: ParseError) -> Self {
Error::from(err).into()
}
}
/// A set of errors that can occur running blocking tasks in thread pool.
#[derive(Debug, Display, Error)]
#[display(fmt = "Blocking thread pool is gone")]
pub struct BlockingError;
/// `InternalServerError` for `BlockingError`
impl ResponseError for BlockingError {}
/// A set of errors that can occur during payload parsing.
#[derive(Debug, Display)]
#[non_exhaustive]
@ -344,16 +327,9 @@ impl From<BlockingError> for PayloadError {
}
}
/// `PayloadError` returns two possible results:
///
/// - `Overflow` returns `PayloadTooLarge`
/// - Other errors returns `BadRequest`
impl ResponseError for PayloadError {
fn status_code(&self) -> StatusCode {
match *self {
PayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE,
_ => StatusCode::BAD_REQUEST,
}
impl From<PayloadError> for Error {
fn from(err: PayloadError) -> Self {
Self::new_payload().with_cause(err)
}
}
@ -362,13 +338,19 @@ impl ResponseError for PayloadError {
#[non_exhaustive]
pub enum DispatchError {
/// Service error
Service(Error),
// FIXME: display and error type
#[display(fmt = "Service Error")]
Service(#[error(not(source))] Response<AnyBody>),
/// Body error
// FIXME: display and error type
#[display(fmt = "Body Error")]
Body(#[error(not(source))] Box<dyn StdError>),
/// Upgrade service error
Upgrade,
/// An `io::Error` that occurred while trying to read or write to a network
/// stream.
/// An `io::Error` that occurred while trying to read or write to a network stream.
#[display(fmt = "IO error: {}", _0)]
Io(io::Error),
@ -434,12 +416,6 @@ mod content_type_test_impls {
}
}
impl ResponseError for ContentTypeError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -448,42 +424,36 @@ mod tests {
#[test]
fn test_into_response() {
let resp: Response<Body> = ParseError::Incomplete.error_response();
let resp: Response<AnyBody> = ParseError::Incomplete.into();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into();
let resp: Response<Body> = err.error_response();
let resp: Response<AnyBody> = Error::new_http().with_cause(err).into();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn test_as_response() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let e: Error = ParseError::Io(orig).into();
assert_eq!(format!("{}", e.as_response_error()), "IO error: other");
}
#[test]
fn test_error_cause() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.to_string();
let e = Error::from(orig);
assert_eq!(format!("{}", e.as_response_error()), desc);
let err: Error = ParseError::Io(orig).into();
assert_eq!(
format!("{}", err),
"error parsing HTTP message: IO error: other"
);
}
#[test]
fn test_error_display() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.to_string();
let e = Error::from(orig);
assert_eq!(format!("{}", e), desc);
let err = Error::new_io().with_cause(orig);
assert_eq!("connection error: other", err.to_string());
}
#[test]
fn test_error_http_response() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let e = Error::from(orig);
let resp: Response<Body> = e.into();
let err = Error::new_io().with_cause(orig);
let resp: Response<AnyBody> = err.into();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
@ -535,14 +505,4 @@ mod tests {
from!(httparse::Error::TooManyHeaders => ParseError::TooLarge);
from!(httparse::Error::Version => ParseError::Version);
}
#[test]
fn test_error_casting() {
let err = PayloadError::Overflow;
let resp_err: &dyn ResponseError = &err;
let err = resp_err.downcast_ref::<PayloadError>().unwrap();
assert_eq!(err.to_string(), "Payload reached size limit.");
let not_err = resp_err.downcast_ref::<ContentTypeError>();
assert!(not_err.is_none());
}
}

View File

@ -1,5 +1,6 @@
use std::{
collections::VecDeque,
error::Error as StdError,
fmt,
future::Future,
io, mem, net,
@ -17,19 +18,19 @@ use futures_core::ready;
use log::{error, trace};
use pin_project::pin_project;
use crate::body::{Body, BodySize, MessageBody};
use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error};
use crate::error::{ParseError, PayloadError};
use crate::http::StatusCode;
use crate::request::Request;
use crate::response::Response;
use crate::service::HttpFlow;
use crate::OnConnectData;
use crate::{
body::{AnyBody, BodySize, MessageBody},
config::ServiceConfig,
error::{DispatchError, ParseError, PayloadError},
service::HttpFlow,
OnConnectData, Request, Response, StatusCode,
};
use super::codec::Codec;
use super::payload::{Payload, PayloadSender, PayloadStatus};
use super::{Message, MessageType};
use super::{
codec::Codec,
payload::{Payload, PayloadSender, PayloadStatus},
Message, MessageType,
};
const LW_BUFFER_SIZE: usize = 1024;
const HW_BUFFER_SIZE: usize = 1024 * 8;
@ -50,13 +51,13 @@ bitflags! {
pub struct Dispatcher<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
@ -72,13 +73,13 @@ where
enum DispatcherState<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
@ -91,13 +92,13 @@ where
struct InnerDispatcher<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
@ -136,13 +137,13 @@ where
X: Service<Request, Response = Request>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
None,
ExpectCall(#[pin] X::Future),
ServiceCall(#[pin] S::Future),
SendPayload(#[pin] B),
SendErrorPayload(#[pin] Body),
SendErrorPayload(#[pin] AnyBody),
}
impl<S, B, X> State<S, B, X>
@ -152,7 +153,7 @@ where
X: Service<Request, Response = Request>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
fn is_empty(&self) -> bool {
matches!(self, State::None)
@ -170,14 +171,14 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
@ -231,14 +232,14 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
@ -334,7 +335,7 @@ where
fn send_error_response(
mut self: Pin<&mut Self>,
message: Response<()>,
body: Body,
body: AnyBody,
) -> Result<(), DispatchError> {
let size = self.as_mut().send_response_inner(message, &body)?;
let state = match size {
@ -379,7 +380,7 @@ where
// send_response would update InnerDispatcher state to SendPayload or
// None(If response body is empty).
// continue loop to poll it.
self.as_mut().send_error_response(res, Body::Empty)?;
self.as_mut().send_error_response(res, AnyBody::Empty)?;
}
// return with upgrade request and poll it exclusively.
@ -399,7 +400,7 @@ where
// send service call error as response
Poll::Ready(Err(err)) => {
let res = Response::from_error(err);
let res: Response<AnyBody> = err.into();
let (res, body) = res.replace_body(());
self.as_mut().send_error_response(res, body)?;
}
@ -438,7 +439,7 @@ where
}
Poll::Ready(Some(Err(err))) => {
return Err(DispatchError::Service(err.into()))
return Err(DispatchError::Body(err.into()))
}
Poll::Pending => return Ok(PollResponse::DoNothing),
@ -473,7 +474,7 @@ where
}
Poll::Ready(Some(Err(err))) => {
return Err(DispatchError::Service(err))
return Err(DispatchError::Service(err.into()))
}
Poll::Pending => return Ok(PollResponse::DoNothing),
@ -496,7 +497,7 @@ where
// send expect error as response
Poll::Ready(Err(err)) => {
let res = Response::from_error(err);
let res: Response<AnyBody> = err.into();
let (res, body) = res.replace_body(());
self.as_mut().send_error_response(res, body)?;
}
@ -546,7 +547,7 @@ where
// to notify the dispatcher a new state is set and the outer loop
// should be continue.
Poll::Ready(Err(err)) => {
let res = Response::from_error(err);
let res: Response<AnyBody> = err.into();
let (res, body) = res.replace_body(());
return self.send_error_response(res, body);
}
@ -566,7 +567,7 @@ where
Poll::Pending => Ok(()),
// see the comment on ExpectCall state branch's Ready(Err(err)).
Poll::Ready(Err(err)) => {
let res = Response::from_error(err);
let res: Response<AnyBody> = err.into();
let (res, body) = res.replace_body(());
self.send_error_response(res, body)
}
@ -772,7 +773,7 @@ where
trace!("Slow request timeout");
let _ = self.as_mut().send_error_response(
Response::with_body(StatusCode::REQUEST_TIMEOUT, ()),
Body::Empty,
AnyBody::Empty,
);
this = self.project();
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
@ -909,14 +910,14 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
@ -1067,16 +1068,17 @@ mod tests {
}
}
fn ok_service() -> impl Service<Request, Response = Response<Body>, Error = Error> {
fn ok_service() -> impl Service<Request, Response = Response<AnyBody>, Error = Error>
{
fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok())))
}
fn echo_path_service(
) -> impl Service<Request, Response = Response<Body>, Error = Error> {
) -> impl Service<Request, Response = Response<AnyBody>, Error = Error> {
fn_service(|req: Request| {
let path = req.path().as_bytes();
ready(Ok::<_, Error>(
Response::ok().set_body(Body::from_slice(path)),
Response::ok().set_body(AnyBody::from_slice(path)),
))
})
}

View File

@ -6,14 +6,15 @@ use std::{cmp, io};
use bytes::{BufMut, BytesMut};
use crate::body::BodySize;
use crate::config::ServiceConfig;
use crate::header::{map::Value, HeaderName};
use crate::helpers;
use crate::http::header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
use crate::http::{HeaderMap, StatusCode, Version};
use crate::message::{ConnectionType, RequestHeadType};
use crate::response::Response;
use crate::{
body::BodySize,
config::ServiceConfig,
header::{map::Value, HeaderMap, HeaderName},
header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
helpers,
message::{ConnectionType, RequestHeadType},
Response, StatusCode, Version,
};
const AVERAGE_HEADER_SIZE: usize = 30;
@ -287,7 +288,7 @@ impl MessageType for RequestHeadType {
let head = self.as_ref();
dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE);
write!(
helpers::Writer(dst),
helpers::MutWriter(dst),
"{} {} {}",
head.method,
head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"),
@ -420,7 +421,7 @@ impl TransferEncoding {
*eof = true;
buf.extend_from_slice(b"0\r\n\r\n");
} else {
writeln!(helpers::Writer(buf), "{:X}\r", msg.len())
writeln!(helpers::MutWriter(buf), "{:X}\r", msg.len())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
buf.reserve(msg.len() + 2);

View File

@ -1,7 +1,11 @@
use std::marker::PhantomData;
use std::rc::Rc;
use std::task::{Context, Poll};
use std::{fmt, net};
use std::{
error::Error as StdError,
fmt,
marker::PhantomData,
net,
rc::Rc,
task::{Context, Poll},
};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_rt::net::TcpStream;
@ -11,17 +15,15 @@ use actix_service::{
use actix_utils::future::ready;
use futures_core::future::LocalBoxFuture;
use crate::body::MessageBody;
use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error};
use crate::request::Request;
use crate::response::Response;
use crate::service::HttpServiceHandler;
use crate::{ConnectCallback, OnConnectData};
use crate::{
body::{AnyBody, MessageBody},
config::ServiceConfig,
error::DispatchError,
service::HttpServiceHandler,
ConnectCallback, OnConnectData, Request, Response,
};
use super::codec::Codec;
use super::dispatcher::Dispatcher;
use super::{ExpectHandler, UpgradeHandler};
use super::{codec::Codec, dispatcher::Dispatcher, ExpectHandler, UpgradeHandler};
/// `ServiceFactory` implementation for HTTP1 transport
pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> {
@ -36,7 +38,7 @@ pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> {
impl<T, S, B> H1Service<T, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
@ -61,21 +63,21 @@ impl<S, B, X, U> H1Service<TcpStream, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<TcpStream, Codec>), Config = (), Response = ()>,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::InitError: fmt::Debug,
{
/// Create simple tcp stream service
@ -110,16 +112,16 @@ mod openssl {
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
@ -128,7 +130,7 @@ mod openssl {
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::InitError: fmt::Debug,
{
/// Create openssl based service
@ -170,16 +172,16 @@ mod rustls {
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
@ -188,7 +190,7 @@ mod rustls {
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::InitError: fmt::Debug,
{
/// Create rustls based service
@ -217,7 +219,7 @@ mod rustls {
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
S::Response: Into<Response<B>>,
S::InitError: fmt::Debug,
B: MessageBody,
@ -225,7 +227,7 @@ where
pub fn expect<X1>(self, expect: X1) -> H1Service<T, S, B, X1, U>
where
X1: ServiceFactory<Request, Response = Request>,
X1::Error: Into<Error>,
X1::Error: Into<Response<AnyBody>>,
X1::InitError: fmt::Debug,
{
H1Service {
@ -268,21 +270,21 @@ where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
S::Response: Into<Response<B>>,
S::InitError: fmt::Debug,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::InitError: fmt::Debug,
{
type Response = ();
@ -338,17 +340,17 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display + Into<Error>,
U::Error: fmt::Display + Into<Response<AnyBody>>,
{
type Response = ();
type Error = DispatchError;

View File

@ -81,7 +81,9 @@ where
let _ = this.body.take();
}
let framed = this.framed.as_mut().as_pin_mut().unwrap();
framed.write(Message::Chunk(item))?;
framed.write(Message::Chunk(item)).map_err(|err| {
Error::new_send_response().with_cause(err)
})?;
}
Poll::Pending => body_ready = false,
}
@ -92,7 +94,10 @@ where
// flush write buffer
if !framed.is_write_buf_empty() {
match framed.flush(cx)? {
match framed
.flush(cx)
.map_err(|err| Error::new_send_response().with_cause(err))?
{
Poll::Ready(_) => {
if body_ready {
continue;
@ -106,7 +111,9 @@ where
// send response
if let Some(res) = this.res.take() {
framed.write(res)?;
framed
.write(res)
.map_err(|err| Error::new_send_response().with_cause(err))?;
continue;
}

View File

@ -1,5 +1,6 @@
use std::{
cmp,
error::Error as StdError,
future::Future,
marker::PhantomData,
net,
@ -18,15 +19,12 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD
use log::{error, trace};
use pin_project_lite::pin_project;
use crate::body::{BodySize, MessageBody};
use crate::config::ServiceConfig;
use crate::error::Error;
use crate::message::ResponseHead;
use crate::payload::Payload;
use crate::request::Request;
use crate::response::Response;
use crate::service::HttpFlow;
use crate::OnConnectData;
use crate::{
body::{AnyBody, BodySize, MessageBody},
config::ServiceConfig,
service::HttpFlow,
OnConnectData, Payload, Request, Response, ResponseHead,
};
const CHUNK_SIZE: usize = 16_384;
@ -66,12 +64,12 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
type Output = Result<(), crate::error::DispatchError>;
@ -106,7 +104,7 @@ where
let res = match fut.await {
Ok(res) => handle_response(res.into(), tx, config).await,
Err(err) => {
let res = Response::from_error(err.into());
let res: Response<AnyBody> = err.into();
handle_response(res, tx, config).await
}
};
@ -133,7 +131,7 @@ where
enum DispatchError {
SendResponse(h2::Error),
SendData(h2::Error),
ResponseBody(Error),
ResponseBody(Box<dyn StdError>),
}
async fn handle_response<B>(
@ -143,7 +141,7 @@ async fn handle_response<B>(
) -> Result<(), DispatchError>
where
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
let (res, body) = res.replace_body(());

View File

@ -1,8 +1,12 @@
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{net, rc::Rc};
use std::{
error::Error as StdError,
future::Future,
marker::PhantomData,
net,
pin::Pin,
rc::Rc,
task::{Context, Poll},
};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream;
@ -13,16 +17,16 @@ use actix_service::{
use actix_utils::future::ready;
use bytes::Bytes;
use futures_core::{future::LocalBoxFuture, ready};
use h2::server::{handshake, Handshake};
use h2::server::{handshake as h2_handshake, Handshake as H2Handshake};
use log::error;
use crate::body::MessageBody;
use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error};
use crate::request::Request;
use crate::response::Response;
use crate::service::HttpFlow;
use crate::{ConnectCallback, OnConnectData};
use crate::{
body::{AnyBody, MessageBody},
config::ServiceConfig,
error::DispatchError,
service::HttpFlow,
ConnectCallback, OnConnectData, Request, Response,
};
use super::dispatcher::Dispatcher;
@ -37,12 +41,12 @@ pub struct H2Service<T, S, B> {
impl<T, S, B> H2Service<T, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
/// Create new `H2Service` instance with config.
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
@ -68,12 +72,12 @@ impl<S, B> H2Service<TcpStream, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
/// Create plain TCP based service
pub fn tcp(
@ -107,12 +111,12 @@ mod openssl {
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
/// Create OpenSSL based service
pub fn openssl(
@ -153,12 +157,12 @@ mod rustls {
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
/// Create Rustls based service
pub fn rustls(
@ -197,12 +201,12 @@ where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
type Response = ();
type Error = DispatchError;
@ -237,7 +241,7 @@ where
impl<T, S, B> H2ServiceHandler<T, S, B>
where
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
@ -260,11 +264,11 @@ impl<T, S, B> Service<(T, Option<net::SocketAddr>)> for H2ServiceHandler<T, S, B
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
type Response = ();
type Error = DispatchError;
@ -288,7 +292,7 @@ where
Some(self.cfg.clone()),
addr,
on_connect_data,
handshake(io),
h2_handshake(io),
),
}
}
@ -305,7 +309,7 @@ where
Option<ServiceConfig>,
Option<net::SocketAddr>,
OnConnectData,
Handshake<T, Bytes>,
H2Handshake<T, Bytes>,
),
}
@ -313,7 +317,7 @@ pub struct H2ServiceHandlerResponse<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
@ -325,11 +329,11 @@ impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
type Output = Result<(), DispatchError>;

View File

@ -27,7 +27,9 @@ pub(crate) fn write_status_line<B: BufMut>(version: Version, n: u16, buf: &mut B
buf.put_u8(b' ');
}
/// NOTE: bytes object has to contain enough space
/// Write out content length header.
///
/// Buffer must to contain enough space or be implicitly extendable.
pub fn write_content_length<B: BufMut>(n: u64, buf: &mut B) {
if n == 0 {
buf.put_slice(b"\r\ncontent-length: 0\r\n");
@ -41,11 +43,15 @@ pub fn write_content_length<B: BufMut>(n: u64, buf: &mut B) {
buf.put_slice(b"\r\n");
}
// TODO: bench why this is needed vs Buf::writer
/// An `io` writer for a `BufMut` that should only be used once and on an empty buffer.
pub(crate) struct Writer<'a, B>(pub &'a mut B);
/// An `io::Write`r that only requires mutable reference and assumes that there is space available
/// in the buffer for every write operation or that it can be extended implicitly (like
/// `bytes::BytesMut`, for example).
///
/// This is slightly faster (~10%) than `bytes::buf::Writer` in such cases because it does not
/// perform a remaining length check before writing.
pub(crate) struct MutWriter<'a, B>(pub(crate) &'a mut B);
impl<'a, B> io::Write for Writer<'a, B>
impl<'a, B> io::Write for MutWriter<'a, B>
where
B: BufMut,
{

View File

@ -54,7 +54,7 @@ pub mod ws;
pub use self::builder::HttpServiceBuilder;
pub use self::config::{KeepAlive, ServiceConfig};
pub use self::error::{Error, ResponseError};
pub use self::error::Error;
pub use self::extensions::Extensions;
pub use self::header::ContentEncoding;
pub use self::http_message::HttpMessage;

View File

@ -8,7 +8,7 @@ use std::{
use bytes::{Bytes, BytesMut};
use crate::{
body::{Body, MessageBody},
body::{AnyBody, MessageBody},
error::Error,
extensions::Extensions,
http::{HeaderMap, StatusCode},
@ -22,13 +22,13 @@ pub struct Response<B> {
pub(crate) body: B,
}
impl Response<Body> {
impl Response<AnyBody> {
/// Constructs a new response with default body.
#[inline]
pub fn new(status: StatusCode) -> Response<Body> {
pub fn new(status: StatusCode) -> Self {
Response {
head: BoxedResponseHead::new(status),
body: Body::Empty,
body: AnyBody::Empty,
}
}
@ -43,40 +43,29 @@ impl Response<Body> {
/// Constructs a new response with status 200 OK.
#[inline]
pub fn ok() -> Response<Body> {
pub fn ok() -> Self {
Response::new(StatusCode::OK)
}
/// Constructs a new response with status 400 Bad Request.
#[inline]
pub fn bad_request() -> Response<Body> {
pub fn bad_request() -> Self {
Response::new(StatusCode::BAD_REQUEST)
}
/// Constructs a new response with status 404 Not Found.
#[inline]
pub fn not_found() -> Response<Body> {
pub fn not_found() -> Self {
Response::new(StatusCode::NOT_FOUND)
}
/// Constructs a new response with status 500 Internal Server Error.
#[inline]
pub fn internal_server_error() -> Response<Body> {
pub fn internal_server_error() -> Self {
Response::new(StatusCode::INTERNAL_SERVER_ERROR)
}
// end shortcuts
/// Constructs a new response from an error.
#[inline]
pub fn from_error(error: impl Into<Error>) -> Response<Body> {
let error = error.into();
let resp = error.as_response_error().error_response();
if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR {
debug!("Internal Server Error: {:?}", error);
}
resp
}
}
impl<B> Response<B> {
@ -209,7 +198,6 @@ impl<B> Response<B> {
impl<B> fmt::Debug for Response<B>
where
B: MessageBody,
B::Error: Into<Error>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let res = writeln!(
@ -235,7 +223,9 @@ impl<B: Default> Default for Response<B> {
}
}
impl<I: Into<Response<Body>>, E: Into<Error>> From<Result<I, E>> for Response<Body> {
impl<I: Into<Response<AnyBody>>, E: Into<Error>> From<Result<I, E>>
for Response<AnyBody>
{
fn from(res: Result<I, E>) -> Self {
match res {
Ok(val) => val.into(),
@ -244,13 +234,19 @@ impl<I: Into<Response<Body>>, E: Into<Error>> From<Result<I, E>> for Response<Bo
}
}
impl From<ResponseBuilder> for Response<Body> {
impl From<ResponseBuilder> for Response<AnyBody> {
fn from(mut builder: ResponseBuilder) -> Self {
builder.finish()
}
}
impl From<&'static str> for Response<Body> {
impl From<std::convert::Infallible> for Response<AnyBody> {
fn from(val: std::convert::Infallible) -> Self {
match val {}
}
}
impl From<&'static str> for Response<AnyBody> {
fn from(val: &'static str) -> Self {
Response::build(StatusCode::OK)
.content_type(mime::TEXT_PLAIN_UTF_8)
@ -258,7 +254,7 @@ impl From<&'static str> for Response<Body> {
}
}
impl From<&'static [u8]> for Response<Body> {
impl From<&'static [u8]> for Response<AnyBody> {
fn from(val: &'static [u8]) -> Self {
Response::build(StatusCode::OK)
.content_type(mime::APPLICATION_OCTET_STREAM)
@ -266,7 +262,7 @@ impl From<&'static [u8]> for Response<Body> {
}
}
impl From<String> for Response<Body> {
impl From<String> for Response<AnyBody> {
fn from(val: String) -> Self {
Response::build(StatusCode::OK)
.content_type(mime::TEXT_PLAIN_UTF_8)
@ -274,7 +270,7 @@ impl From<String> for Response<Body> {
}
}
impl<'a> From<&'a String> for Response<Body> {
impl<'a> From<&'a String> for Response<AnyBody> {
fn from(val: &'a String) -> Self {
Response::build(StatusCode::OK)
.content_type(mime::TEXT_PLAIN_UTF_8)
@ -282,7 +278,7 @@ impl<'a> From<&'a String> for Response<Body> {
}
}
impl From<Bytes> for Response<Body> {
impl From<Bytes> for Response<AnyBody> {
fn from(val: Bytes) -> Self {
Response::build(StatusCode::OK)
.content_type(mime::APPLICATION_OCTET_STREAM)
@ -290,7 +286,7 @@ impl From<Bytes> for Response<Body> {
}
}
impl From<BytesMut> for Response<Body> {
impl From<BytesMut> for Response<AnyBody> {
fn from(val: BytesMut) -> Self {
Response::build(StatusCode::OK)
.content_type(mime::APPLICATION_OCTET_STREAM)
@ -301,7 +297,6 @@ impl From<BytesMut> for Response<Body> {
#[cfg(test)]
mod tests {
use super::*;
use crate::body::Body;
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE};
#[test]
@ -316,7 +311,7 @@ mod tests {
#[test]
fn test_into_response() {
let resp: Response<Body> = "test".into();
let resp: Response<AnyBody> = "test".into();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
@ -325,7 +320,7 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test");
let resp: Response<Body> = b"test".as_ref().into();
let resp: Response<AnyBody> = b"test".as_ref().into();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
@ -334,7 +329,7 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test");
let resp: Response<Body> = "test".to_owned().into();
let resp: Response<AnyBody> = "test".to_owned().into();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
@ -343,7 +338,7 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test");
let resp: Response<Body> = (&"test".to_owned()).into();
let resp: Response<AnyBody> = (&"test".to_owned()).into();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
@ -353,7 +348,7 @@ mod tests {
assert_eq!(resp.body().get_ref(), b"test");
let b = Bytes::from_static(b"test");
let resp: Response<Body> = b.into();
let resp: Response<AnyBody> = b.into();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
@ -363,7 +358,7 @@ mod tests {
assert_eq!(resp.body().get_ref(), b"test");
let b = Bytes::from_static(b"test");
let resp: Response<Body> = b.into();
let resp: Response<AnyBody> = b.into();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
@ -373,7 +368,7 @@ mod tests {
assert_eq!(resp.body().get_ref(), b"test");
let b = BytesMut::from("test");
let resp: Response<Body> = b.into();
let resp: Response<AnyBody> = b.into();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),

View File

@ -2,6 +2,7 @@
use std::{
cell::{Ref, RefMut},
error::Error as StdError,
fmt,
future::Future,
pin::Pin,
@ -13,7 +14,7 @@ use bytes::Bytes;
use futures_core::Stream;
use crate::{
body::{Body, BodyStream},
body::{AnyBody, BodyStream},
error::{Error, HttpError},
header::{self, IntoHeaderPair, IntoHeaderValue},
message::{BoxedResponseHead, ConnectionType, ResponseHead},
@ -235,9 +236,9 @@ impl ResponseBuilder {
///
/// This `ResponseBuilder` will be left in a useless state.
#[inline]
pub fn body<B: Into<Body>>(&mut self, body: B) -> Response<Body> {
pub fn body<B: Into<AnyBody>>(&mut self, body: B) -> Response<AnyBody> {
self.message_body(body.into())
.unwrap_or_else(Response::from_error)
.unwrap_or_else(Response::from)
}
/// Generate response with a body.
@ -245,7 +246,7 @@ impl ResponseBuilder {
/// This `ResponseBuilder` will be left in a useless state.
pub fn message_body<B>(&mut self, body: B) -> Result<Response<B>, Error> {
if let Some(err) = self.err.take() {
return Err(err.into());
return Err(Error::new_http().with_cause(err));
}
let head = self.head.take().expect("cannot reuse response builder");
@ -256,20 +257,20 @@ impl ResponseBuilder {
///
/// This `ResponseBuilder` will be left in a useless state.
#[inline]
pub fn streaming<S, E>(&mut self, stream: S) -> Response<Body>
pub fn streaming<S, E>(&mut self, stream: S) -> Response<AnyBody>
where
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
E: Into<Error> + 'static,
S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<Box<dyn StdError>> + 'static,
{
self.body(Body::from_message(BodyStream::new(stream)))
self.body(AnyBody::from_message(BodyStream::new(stream)))
}
/// Generate response with an empty body.
///
/// This `ResponseBuilder` will be left in a useless state.
#[inline]
pub fn finish(&mut self) -> Response<Body> {
self.body(Body::Empty)
pub fn finish(&mut self) -> Response<AnyBody> {
self.body(AnyBody::Empty)
}
/// Create an owned `ResponseBuilder`, leaving the original in a useless state.
@ -327,7 +328,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
}
impl Future for ResponseBuilder {
type Output = Result<Response<Body>, Error>;
type Output = Result<Response<AnyBody>, Error>;
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(Ok(self.finish()))

View File

@ -1,4 +1,5 @@
use std::{
error::Error as StdError,
fmt,
future::Future,
marker::PhantomData,
@ -8,6 +9,7 @@ use std::{
task::{Context, Poll},
};
use ::h2::server::{handshake as h2_handshake, Handshake as H2Handshake};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_rt::net::TcpStream;
use actix_service::{
@ -15,16 +17,15 @@ use actix_service::{
};
use bytes::Bytes;
use futures_core::{future::LocalBoxFuture, ready};
use h2::server::{handshake, Handshake};
use pin_project::pin_project;
use crate::body::MessageBody;
use crate::builder::HttpServiceBuilder;
use crate::config::{KeepAlive, ServiceConfig};
use crate::error::{DispatchError, Error};
use crate::request::Request;
use crate::response::Response;
use crate::{h1, h2::Dispatcher, ConnectCallback, OnConnectData, Protocol};
use crate::{
body::{AnyBody, MessageBody},
builder::HttpServiceBuilder,
config::{KeepAlive, ServiceConfig},
error::DispatchError,
h1, h2, ConnectCallback, OnConnectData, Protocol, Request, Response,
};
/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol.
pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler> {
@ -39,7 +40,7 @@ pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler> {
impl<T, S, B> HttpService<T, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
@ -54,12 +55,12 @@ where
impl<T, S, B> HttpService<T, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
{
/// Create new `HttpService` instance.
pub fn new<F: IntoServiceFactory<S, Request>>(service: F) -> Self {
@ -94,7 +95,7 @@ where
impl<T, S, B, X, U> HttpService<T, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
@ -108,7 +109,7 @@ where
pub fn expect<X1>(self, expect: X1) -> HttpService<T, S, B, X1, U>
where
X1: ServiceFactory<Request, Config = (), Response = Request>,
X1::Error: Into<Error>,
X1::Error: Into<Response<AnyBody>>,
X1::InitError: fmt::Debug,
{
HttpService {
@ -152,17 +153,17 @@ impl<S, B, X, U> HttpService<TcpStream, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
@ -171,7 +172,7 @@ where
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::InitError: fmt::Debug,
{
/// Create simple tcp stream service
@ -204,17 +205,17 @@ mod openssl {
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
@ -223,7 +224,7 @@ mod openssl {
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::InitError: fmt::Debug,
{
/// Create openssl based service
@ -272,17 +273,17 @@ mod rustls {
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
@ -291,7 +292,7 @@ mod rustls {
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::InitError: fmt::Debug,
{
/// Create rustls based service
@ -338,22 +339,22 @@ where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::InitError: fmt::Debug,
{
type Response = ();
@ -416,11 +417,11 @@ where
impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
X: Service<Request>,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, h1::Codec>)>,
U::Error: Into<Error>,
U::Error: Into<Response<AnyBody>>,
{
pub(super) fn new(
cfg: ServiceConfig,
@ -437,7 +438,10 @@ where
}
}
pub(super) fn _poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
pub(super) fn _poll_ready(
&self,
cx: &mut Context<'_>,
) -> Poll<Result<(), Response<AnyBody>>> {
ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?;
ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?;
@ -473,18 +477,18 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display + Into<Error>,
U::Error: fmt::Display + Into<Response<AnyBody>>,
{
type Response = ();
type Error = DispatchError;
@ -507,7 +511,7 @@ where
match proto {
Protocol::Http2 => HttpServiceHandlerResponse {
state: State::H2Handshake(Some((
handshake(io),
h2_handshake(io),
self.cfg.clone(),
self.flow.clone(),
on_connect_data,
@ -537,22 +541,22 @@ where
S: Service<Request>,
S::Future: 'static,
S::Error: Into<Error>,
S::Error: Into<Response<AnyBody>>,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{
H1(#[pin] h1::Dispatcher<T, S, B, X, U>),
H2(#[pin] Dispatcher<T, S, B, X, U>),
H2(#[pin] h2::Dispatcher<T, S, B, X, U>),
H2Handshake(
Option<(
Handshake<T, Bytes>,
H2Handshake<T, Bytes>,
ServiceConfig,
Rc<HttpFlow<S, X, U>>,
OnConnectData,
@ -567,15 +571,15 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
@ -589,15 +593,15 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
B::Error: Into<Error>,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
@ -613,13 +617,15 @@ where
Ok(conn) => {
let (_, cfg, srv, on_connect_data, peer_addr) =
data.take().unwrap();
self.as_mut().project().state.set(State::H2(Dispatcher::new(
srv,
conn,
on_connect_data,
cfg,
peer_addr,
)));
self.as_mut().project().state.set(State::H2(
h2::Dispatcher::new(
srv,
conn,
on_connect_data,
cfg,
peer_addr,
),
));
self.poll(cx)
}
Err(err) => {

View File

@ -72,7 +72,7 @@ mod inner {
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
use crate::ResponseError;
use crate::{body::AnyBody, Response};
/// Framed transport errors
pub enum DispatcherError<E, U, I>
@ -136,13 +136,16 @@ mod inner {
}
}
impl<E, U, I> ResponseError for DispatcherError<E, U, I>
impl<E, U, I> From<DispatcherError<E, U, I>> for Response<AnyBody>
where
E: fmt::Debug + fmt::Display,
U: Encoder<I> + Decoder,
<U as Encoder<I>>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
fn from(err: DispatcherError<E, U, I>) -> Self {
Response::internal_server_error().set_body(AnyBody::from(err.to_string()))
}
}
/// Message type wrapper for signalling end of message stream.

View File

@ -9,8 +9,8 @@ use derive_more::{Display, Error, From};
use http::{header, Method, StatusCode};
use crate::{
body::Body, error::ResponseError, header::HeaderValue, message::RequestHead,
response::Response, ResponseBuilder,
body::AnyBody, header::HeaderValue, message::RequestHead, response::Response,
ResponseBuilder,
};
mod codec;
@ -25,7 +25,7 @@ pub use self::frame::Parser;
pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode};
/// WebSocket protocol errors.
#[derive(Debug, Display, From, Error)]
#[derive(Debug, Display, Error, From)]
pub enum ProtocolError {
/// Received an unmasked frame from client.
#[display(fmt = "Received an unmasked frame from client.")]
@ -68,10 +68,8 @@ pub enum ProtocolError {
Io(io::Error),
}
impl ResponseError for ProtocolError {}
/// WebSocket handshake errors
#[derive(PartialEq, Debug, Display)]
#[derive(Debug, PartialEq, Display, Error)]
pub enum HandshakeError {
/// Only get method is allowed.
#[display(fmt = "Method not allowed.")]
@ -98,44 +96,55 @@ pub enum HandshakeError {
BadWebsocketKey,
}
impl ResponseError for HandshakeError {
fn error_response(&self) -> Response<Body> {
match self {
impl From<&HandshakeError> for Response<AnyBody> {
fn from(err: &HandshakeError) -> Self {
match err {
HandshakeError::GetMethodRequired => {
Response::build(StatusCode::METHOD_NOT_ALLOWED)
.insert_header((header::ALLOW, "GET"))
.finish()
let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED);
res.headers_mut()
.insert(header::ALLOW, HeaderValue::from_static("GET"));
res
}
HandshakeError::NoWebsocketUpgrade => {
Response::build(StatusCode::BAD_REQUEST)
.reason("No WebSocket Upgrade header found")
.finish()
let mut res = Response::bad_request();
res.head_mut().reason = Some("No WebSocket Upgrade header found");
res
}
HandshakeError::NoConnectionUpgrade => {
Response::build(StatusCode::BAD_REQUEST)
.reason("No Connection upgrade")
.finish()
let mut res = Response::bad_request();
res.head_mut().reason = Some("No Connection upgrade");
res
}
HandshakeError::NoVersionHeader => Response::build(StatusCode::BAD_REQUEST)
.reason("WebSocket version header is required")
.finish(),
HandshakeError::NoVersionHeader => {
let mut res = Response::bad_request();
res.head_mut().reason = Some("WebSocket version header is required");
res
}
HandshakeError::UnsupportedVersion => {
Response::build(StatusCode::BAD_REQUEST)
.reason("Unsupported WebSocket version")
.finish()
let mut res = Response::bad_request();
res.head_mut().reason = Some("Unsupported WebSocket version");
res
}
HandshakeError::BadWebsocketKey => Response::build(StatusCode::BAD_REQUEST)
.reason("Handshake error")
.finish(),
HandshakeError::BadWebsocketKey => {
let mut res = Response::bad_request();
res.head_mut().reason = Some("Handshake error");
res
}
}
}
}
impl From<HandshakeError> for Response<AnyBody> {
fn from(err: HandshakeError) -> Self {
(&err).into()
}
}
/// Verify WebSocket handshake request and create handshake response.
pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
verify_handshake(req)?;
@ -213,7 +222,7 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
#[cfg(test)]
mod tests {
use super::*;
use crate::test::TestRequest;
use crate::{body::AnyBody, test::TestRequest};
use http::{header, Method};
#[test]
@ -327,18 +336,18 @@ mod tests {
}
#[test]
fn test_wserror_http_response() {
let resp = HandshakeError::GetMethodRequired.error_response();
fn test_ws_error_http_response() {
let resp: Response<AnyBody> = HandshakeError::GetMethodRequired.into();
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let resp = HandshakeError::NoWebsocketUpgrade.error_response();
let resp: Response<AnyBody> = HandshakeError::NoWebsocketUpgrade.into();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp = HandshakeError::NoConnectionUpgrade.error_response();
let resp: Response<AnyBody> = HandshakeError::NoConnectionUpgrade.into();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp = HandshakeError::NoVersionHeader.error_response();
let resp: Response<AnyBody> = HandshakeError::NoVersionHeader.into();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp = HandshakeError::UnsupportedVersion.error_response();
let resp: Response<AnyBody> = HandshakeError::UnsupportedVersion.into();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp = HandshakeError::BadWebsocketKey.error_response();
let resp: Response<AnyBody> = HandshakeError::BadWebsocketKey.into();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
}