1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-27 17:52:56 +01: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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 1498 additions and 901 deletions

View File

@ -1,7 +1,8 @@
[alias] [alias]
chk = "hack check --workspace --all-features --tests --examples" chk = "check --workspace --all-features --tests --examples --bins"
lint = "hack --clean-per-run clippy --workspace --tests --examples" lint = "clippy --workspace --tests --examples"
ci-min = "hack check --workspace --no-default-features" ci-min = "hack check --workspace --no-default-features"
ci-min-test = "hack check --workspace --no-default-features --tests --examples" ci-min-test = "hack check --workspace --no-default-features --tests --examples"
ci-default = "hack check --workspace" ci-default = "hack check --workspace"
ci-full = "check --workspace --bins --examples --tests" ci-full = "check --workspace --bins --examples --tests"
ci-test = "test --workspace --all-features --no-fail-fast"

View File

@ -13,6 +13,8 @@
* Update `language-tags` to `0.3`. * Update `language-tags` to `0.3`.
* `ServiceResponse::take_body`. [#2201] * `ServiceResponse::take_body`. [#2201]
* `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody<B>` types. [#2201] * `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody<B>` types. [#2201]
* All error trait bounds in server service builders have changed from `Into<Error>` to `Into<Response<AnyBody>>`. [#2253]
* All error trait bounds in message body and stream impls changed from `Into<Error>` to `Into<Box<dyn std::error::Error>>`. [#2253]
* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] * `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226]
* `middleware::normalize` now will not try to normalize URIs with no valid path [#2246] * `middleware::normalize` now will not try to normalize URIs with no valid path [#2246]
@ -21,6 +23,7 @@
[#2200]: https://github.com/actix/actix-web/pull/2200 [#2200]: https://github.com/actix/actix-web/pull/2200
[#2201]: https://github.com/actix/actix-web/pull/2201 [#2201]: https://github.com/actix/actix-web/pull/2201
[#2253]: https://github.com/actix/actix-web/pull/2253
[#2246]: https://github.com/actix/actix-web/pull/2246 [#2246]: https://github.com/actix/actix-web/pull/2246

View File

@ -18,6 +18,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = { version = "4.0.0-beta.6", default-features = false } actix-web = { version = "4.0.0-beta.6", default-features = false }
actix-http = "3.0.0-beta.6"
actix-service = "2.0.0" actix-service = "2.0.0"
actix-utils = "3.0.0" actix-utils = "3.0.0"

View File

@ -13,12 +13,15 @@
### Changed ### Changed
* The `MessageBody` trait now has an associated `Error` type. [#2183] * The `MessageBody` trait now has an associated `Error` type. [#2183]
* All error trait bounds in server service builders have changed from `Into<Error>` to `Into<Response<AnyBody>>`. [#2253]
* All error trait bounds in message body and stream impls changed from `Into<Error>` to `Into<Box<dyn std::error::Error>>`. [#2253]
* Places in `Response` where `ResponseBody<B>` was received or returned now simply use `B`. [#2201] * Places in `Response` where `ResponseBody<B>` was received or returned now simply use `B`. [#2201]
* `header` mod is now public. [#2171] * `header` mod is now public. [#2171]
* `uri` mod is now public. [#2171] * `uri` mod is now public. [#2171]
* Update `language-tags` to `0.3`. * Update `language-tags` to `0.3`.
* Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] * Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201]
* `ResponseBuilder::message_body` now returns a `Result`. [#2201] * `ResponseBuilder::message_body` now returns a `Result`. [#2201]
* Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253]
* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226] * `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226]
### Removed ### Removed
@ -37,6 +40,7 @@
[#2201]: https://github.com/actix/actix-web/pull/2201 [#2201]: https://github.com/actix/actix-web/pull/2201
[#2205]: https://github.com/actix/actix-web/pull/2205 [#2205]: https://github.com/actix/actix-web/pull/2205
[#2215]: https://github.com/actix/actix-web/pull/2215 [#2215]: https://github.com/actix/actix-web/pull/2215
[#2253]: https://github.com/actix/actix-web/pull/2253
[#2244]: https://github.com/actix/actix-web/pull/2244 [#2244]: https://github.com/actix/actix-web/pull/2244

View File

@ -84,7 +84,8 @@ trust-dns-resolver = { version = "0.20.0", optional = true }
actix-server = "2.0.0-beta.3" actix-server = "2.0.0-beta.3"
actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] }
actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] }
criterion = "0.3" async-stream = "0.3"
criterion = { version = "0.3", features = ["html_reports"] }
env_logger = "0.8" env_logger = "0.8"
rcgen = "0.8" rcgen = "0.8"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View File

@ -5,14 +5,13 @@ use actix_server::Server;
use bytes::BytesMut; use bytes::BytesMut;
use futures_util::StreamExt as _; use futures_util::StreamExt as _;
use http::header::HeaderValue; use http::header::HeaderValue;
use log::info;
#[actix_rt::main] #[actix_rt::main]
async fn main() -> io::Result<()> { async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
Server::build() Server::build()
.bind("echo", "127.0.0.1:8080", || { .bind("echo", ("127.0.0.1", 8080), || {
HttpService::build() HttpService::build()
.client_timeout(1000) .client_timeout(1000)
.client_disconnect(1000) .client_disconnect(1000)
@ -22,7 +21,8 @@ async fn main() -> io::Result<()> {
body.extend_from_slice(&item?); body.extend_from_slice(&item?);
} }
info!("request body: {:?}", body); log::info!("request body: {:?}", body);
Ok::<_, Error>( Ok::<_, Error>(
Response::build(StatusCode::OK) Response::build(StatusCode::OK)
.insert_header(( .insert_header((

View File

@ -5,7 +5,6 @@ use actix_http::{Error, 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 _;
use log::info;
async fn handle_request(mut req: Request) -> Result<Response<Body>, Error> { async fn handle_request(mut req: Request) -> Result<Response<Body>, Error> {
let mut body = BytesMut::new(); let mut body = BytesMut::new();
@ -13,7 +12,8 @@ async fn handle_request(mut req: Request) -> Result<Response<Body>, Error> {
body.extend_from_slice(&item?) body.extend_from_slice(&item?)
} }
info!("request body: {:?}", body); log::info!("request body: {:?}", body);
Ok(Response::build(StatusCode::OK) Ok(Response::build(StatusCode::OK)
.insert_header(("x-head", HeaderValue::from_static("dummy value!"))) .insert_header(("x-head", HeaderValue::from_static("dummy value!")))
.body(body)) .body(body))
@ -24,7 +24,7 @@ async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
Server::build() Server::build()
.bind("echo", "127.0.0.1:8080", || { .bind("echo", ("127.0.0.1", 8080), || {
HttpService::build().finish(handle_request).tcp() HttpService::build().finish(handle_request).tcp()
})? })?
.run() .run()

View File

@ -1,28 +1,28 @@
use std::io; use std::{convert::Infallible, io};
use actix_http::{http::StatusCode, HttpService, Response}; use actix_http::{http::StatusCode, HttpService, Response};
use actix_server::Server; use actix_server::Server;
use actix_utils::future;
use http::header::HeaderValue; use http::header::HeaderValue;
use log::info;
#[actix_rt::main] #[actix_rt::main]
async fn main() -> io::Result<()> { async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
Server::build() Server::build()
.bind("hello-world", "127.0.0.1:8080", || { .bind("hello-world", ("127.0.0.1", 8080), || {
HttpService::build() HttpService::build()
.client_timeout(1000) .client_timeout(1000)
.client_disconnect(1000) .client_disconnect(1000)
.finish(|_req| { .finish(|req| async move {
info!("{:?}", _req); log::info!("{:?}", req);
let mut res = Response::build(StatusCode::OK); let mut res = Response::build(StatusCode::OK);
res.insert_header(( res.insert_header((
"x-head", "x-head",
HeaderValue::from_static("dummy value!"), HeaderValue::from_static("dummy value!"),
)); ));
future::ok::<_, ()>(res.body("Hello world!"))
Ok::<_, Infallible>(res.body("Hello world!"))
}) })
.tcp() .tcp()
})? })?

View File

@ -0,0 +1,40 @@
//! Example showing response body (chunked) stream erroring.
//!
//! Test using `nc` or `curl`.
//! ```sh
//! $ curl -vN 127.0.0.1:8080
//! $ echo 'GET / HTTP/1.1\n\n' | nc 127.0.0.1 8080
//! ```
use std::{convert::Infallible, io, time::Duration};
use actix_http::{body::BodyStream, HttpService, Response};
use actix_server::Server;
use async_stream::stream;
use bytes::Bytes;
#[actix_rt::main]
async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
Server::build()
.bind("streaming-error", ("127.0.0.1", 8080), || {
HttpService::build()
.finish(|req| async move {
log::info!("{:?}", req);
let res = Response::ok();
Ok::<_, Infallible>(res.set_body(BodyStream::new(stream! {
yield Ok(Bytes::from("123"));
yield Ok(Bytes::from("456"));
actix_rt::time::sleep(Duration::from_millis(1000)).await;
yield Err(io::Error::new(io::ErrorKind::Other, ""));
})))
})
.tcp()
})?
.run()
.await
}

View File

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

View File

@ -1,4 +1,5 @@
use std::{ use std::{
error::Error as StdError,
pin::Pin, pin::Pin,
task::{Context, Poll}, task::{Context, Poll},
}; };
@ -7,8 +8,6 @@ use bytes::Bytes;
use futures_core::{ready, Stream}; use futures_core::{ready, Stream};
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use crate::error::Error;
use super::{BodySize, MessageBody}; use super::{BodySize, MessageBody};
pin_project! { pin_project! {
@ -24,7 +23,7 @@ pin_project! {
impl<S, E> BodyStream<S> impl<S, E> BodyStream<S>
where where
S: Stream<Item = Result<Bytes, E>>, S: Stream<Item = Result<Bytes, E>>,
E: Into<Error>, E: Into<Box<dyn StdError>> + 'static,
{ {
pub fn new(stream: S) -> Self { pub fn new(stream: S) -> Self {
BodyStream { stream } BodyStream { stream }
@ -34,9 +33,9 @@ where
impl<S, E> MessageBody for BodyStream<S> impl<S, E> MessageBody for BodyStream<S>
where where
S: Stream<Item = Result<Bytes, E>>, 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 { fn size(&self) -> BodySize {
BodySize::Stream BodySize::Stream
@ -56,7 +55,7 @@ where
let chunk = match ready!(stream.poll_next(cx)) { let chunk = match ready!(stream.poll_next(cx)) {
Some(Ok(ref bytes)) if bytes.is_empty() => continue, Some(Ok(ref bytes)) if bytes.is_empty() => continue,
opt => opt.map(|res| res.map_err(Into::into)), opt => opt,
}; };
return Poll::Ready(chunk); return Poll::Ready(chunk);
@ -66,9 +65,16 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { 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 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 super::*;
use crate::body::to_bytes; use crate::body::to_bytes;
@ -78,7 +84,7 @@ mod tests {
let body = BodyStream::new(stream::iter( let body = BodyStream::new(stream::iter(
["1", "", "2"] ["1", "", "2"]
.iter() .iter()
.map(|&v| Ok(Bytes::from(v)) as Result<Bytes, ()>), .map(|&v| Ok::<_, Infallible>(Bytes::from(v))),
)); ));
pin!(body); pin!(body);
@ -103,9 +109,63 @@ mod tests {
let body = BodyStream::new(stream::iter( let body = BodyStream::new(stream::iter(
["1", "", "2"] ["1", "", "2"]
.iter() .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"))); 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] #[actix_rt::test]
async fn test_box() { async fn test_box_and_pin() {
let val = Box::new(()); let val = Box::new(());
pin!(val); pin!(val);
assert_eq!(val.size(), BodySize::Empty); assert_eq!(val.size(), BodySize::Empty);
assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none()); 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] #[actix_rt::test]

View File

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

View File

@ -1,19 +1,16 @@
use std::marker::PhantomData; use std::{error::Error as StdError, fmt, marker::PhantomData, net, rc::Rc};
use std::rc::Rc;
use std::{fmt, net};
use actix_codec::Framed; use actix_codec::Framed;
use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use actix_service::{IntoServiceFactory, Service, ServiceFactory};
use crate::body::MessageBody; use crate::{
use crate::config::{KeepAlive, ServiceConfig}; body::{AnyBody, MessageBody},
use crate::error::Error; config::{KeepAlive, ServiceConfig},
use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler}; h1::{self, ExpectHandler, H1Service, UpgradeHandler},
use crate::h2::H2Service; h2::H2Service,
use crate::request::Request; service::HttpService,
use crate::response::Response; ConnectCallback, Extensions, Request, Response,
use crate::service::HttpService; };
use crate::{ConnectCallback, Extensions};
/// A HTTP service builder /// 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> impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static, S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
{ {
@ -57,13 +54,13 @@ where
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U> impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static, S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
X: ServiceFactory<Request, Config = (), Response = Request>, X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Error>, X::Error: Into<Response<AnyBody>>,
X::InitError: fmt::Debug, 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::Error: fmt::Display,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
@ -123,7 +120,7 @@ where
where where
F: IntoServiceFactory<X1, Request>, F: IntoServiceFactory<X1, Request>,
X1: ServiceFactory<Request, Config = (), Response = Request>, X1: ServiceFactory<Request, Config = (), Response = Request>,
X1::Error: Into<Error>, X1::Error: Into<Response<AnyBody>>,
X1::InitError: fmt::Debug, X1::InitError: fmt::Debug,
{ {
HttpServiceBuilder { HttpServiceBuilder {
@ -145,8 +142,8 @@ where
/// and this service get called with original request and framed object. /// and this service get called with original request and framed object.
pub fn upgrade<F, U1>(self, upgrade: F) -> HttpServiceBuilder<T, S, X, U1> pub fn upgrade<F, U1>(self, upgrade: F) -> HttpServiceBuilder<T, S, X, U1>
where where
F: IntoServiceFactory<U1, (Request, Framed<T, Codec>)>, F: IntoServiceFactory<U1, (Request, Framed<T, h1::Codec>)>,
U1: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>, U1: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
U1::Error: fmt::Display, U1::Error: fmt::Display,
U1::InitError: fmt::Debug, U1::InitError: fmt::Debug,
{ {
@ -181,7 +178,7 @@ where
where where
B: MessageBody, B: MessageBody,
F: IntoServiceFactory<S, Request>, F: IntoServiceFactory<S, Request>,
S::Error: Into<Error>, S::Error: Into<Response<AnyBody>>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
{ {
@ -203,12 +200,12 @@ where
pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B> pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
where where
F: IntoServiceFactory<S, Request>, F: IntoServiceFactory<S, Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
let cfg = ServiceConfig::new( let cfg = ServiceConfig::new(
self.keep_alive, self.keep_alive,
@ -226,12 +223,12 @@ where
pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U> pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U>
where where
F: IntoServiceFactory<S, Request>, F: IntoServiceFactory<S, Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Response<AnyBody>> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
let cfg = ServiceConfig::new( let cfg = ServiceConfig::new(
self.keep_alive, 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}; use derive_more::{Display, From};
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
use actix_tls::accept::openssl::SslError; use actix_tls::accept::openssl::SslError;
use crate::error::{Error, ParseError, ResponseError}; use crate::error::{Error, ParseError};
use crate::http::{Error as HttpError, StatusCode}; use crate::http::Error as HttpError;
/// A set of errors that can occur while connecting to an HTTP host /// A set of errors that can occur while connecting to an HTTP host
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum ConnectError { pub enum ConnectError {
/// SSL feature is not enabled /// SSL feature is not enabled
#[display(fmt = "SSL is not supported")] #[display(fmt = "SSL is not supported")]
@ -64,6 +65,7 @@ impl From<actix_tls::connect::ConnectError> for ConnectError {
} }
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum InvalidUrl { pub enum InvalidUrl {
#[display(fmt = "Missing URL scheme")] #[display(fmt = "Missing URL scheme")]
MissingScheme, MissingScheme,
@ -82,6 +84,7 @@ impl std::error::Error for InvalidUrl {}
/// A set of errors that can occur during request sending and response reading /// A set of errors that can occur during request sending and response reading
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum SendRequestError { pub enum SendRequestError {
/// Invalid URL /// Invalid URL
#[display(fmt = "Invalid URL: {}", _0)] #[display(fmt = "Invalid URL: {}", _0)]
@ -115,25 +118,17 @@ pub enum SendRequestError {
/// Error sending request body /// Error sending request body
Body(Error), 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 {} 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 /// A set of errors that can occur during freezing a request
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum FreezeRequestError { pub enum FreezeRequestError {
/// Invalid URL /// Invalid URL
#[display(fmt = "Invalid URL: {}", _0)] #[display(fmt = "Invalid URL: {}", _0)]
@ -142,15 +137,20 @@ pub enum FreezeRequestError {
/// HTTP error /// HTTP error
#[display(fmt = "{}", _0)] #[display(fmt = "{}", _0)]
Http(HttpError), 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 std::error::Error for FreezeRequestError {}
impl From<FreezeRequestError> for SendRequestError { impl From<FreezeRequestError> for SendRequestError {
fn from(e: FreezeRequestError) -> Self { fn from(err: FreezeRequestError) -> Self {
match e { match err {
FreezeRequestError::Url(e) => e.into(), FreezeRequestError::Url(err) => err.into(),
FreezeRequestError::Http(e) => e.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) => { EncoderBodyProj::BoxedStream(ref mut b) => {
match ready!(b.as_pin_mut().poll_next(cx)) { match ready!(b.as_pin_mut().poll_next(cx)) {
Some(Err(err)) => { Some(Err(err)) => Poll::Ready(Some(Err(EncoderError::Boxed(err)))),
Poll::Ready(Some(Err(EncoderError::Boxed(err.into()))))
}
Some(Ok(val)) => Poll::Ready(Some(Ok(val))), Some(Ok(val)) => Poll::Ready(Some(Ok(val))),
None => Poll::Ready(None), None => Poll::Ready(None),
} }
@ -337,7 +335,7 @@ pub enum EncoderError<E> {
Body(E), Body(E),
#[display(fmt = "boxed")] #[display(fmt = "boxed")]
Boxed(Error), Boxed(Box<dyn StdError>),
#[display(fmt = "blocking")] #[display(fmt = "blocking")]
Blocking(BlockingError), Blocking(BlockingError),
@ -346,19 +344,19 @@ pub enum EncoderError<E> {
Io(io::Error), 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)> { fn source(&self) -> Option<&(dyn StdError + 'static)> {
None match self {
} EncoderError::Body(err) => Some(err),
} EncoderError::Boxed(err) => Some(&**err),
EncoderError::Blocking(err) => Some(err),
impl<E: Into<Error>> From<EncoderError<E>> for Error { EncoderError::Io(err) => Some(err),
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(),
} }
} }
} }
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 //! Error and Result module
use std::{ use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};
error::Error as StdError,
fmt,
io::{self, Write as _},
str::Utf8Error,
string::FromUtf8Error,
};
use bytes::BytesMut;
use derive_more::{Display, Error, From}; use derive_more::{Display, Error, From};
use http::{header, uri::InvalidUri, StatusCode}; use http::{uri::InvalidUri, StatusCode};
use serde::de::value::Error as DeError;
use crate::{body::Body, helpers::Writer, Response}; use crate::{
body::{AnyBody, Body},
ws, Response,
};
pub use http::Error as HttpError; 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 { 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 { impl Error {
/// Returns the reference to the underlying `ResponseError`. fn new(kind: Kind) -> Self {
pub fn as_response_error(&self) -> &dyn ResponseError { Self {
self.cause.as_ref() inner: Box::new(ErrorInner { kind, cause: None }),
}
} }
/// Similar to `as_response_error` but downcasts. pub(crate) fn new_http() -> Self {
pub fn as_error<T: ResponseError + 'static>(&self) -> Option<&T> { Self::new(Kind::Http)
<dyn ResponseError>::downcast_ref(self.cause.as_ref()) }
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. impl From<Error> for Response<AnyBody> {
pub trait ResponseError: fmt::Debug + fmt::Display { fn from(err: Error) -> Self {
/// Returns appropriate status code for error. let status_code = match err.inner.kind {
/// Kind::Parse => StatusCode::BAD_REQUEST,
/// A 500 Internal Server Error is used by default. If [error_response](Self::error_response) is _ => StatusCode::INTERNAL_SERVER_ERROR,
/// also implemented and does not call `self.status_code()`, then this will not be used. };
fn status_code(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}
/// Creates full response for error. Response::new(status_code).set_body(Body::from(err.to_string()))
///
/// 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))
} }
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 { #[display(fmt = "error parsing HTTP message")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Parse,
fmt::Display::fmt(&self.cause, f)
} #[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 { impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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 { impl fmt::Display for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
None match self.inner.cause.as_ref() {
Some(err) => write!(f, "{}: {}", &self.inner.kind, err),
None => write!(f, "{}", &self.inner.kind),
}
} }
} }
impl From<()> for Error { impl StdError for Error {
fn from(_: ()) -> Self { fn source(&self) -> Option<&(dyn StdError + 'static)> {
Error::from(UnitError) self.inner.cause.as_ref().map(|err| err.as_ref())
} }
} }
impl From<std::convert::Infallible> for Error { impl From<std::convert::Infallible> for Error {
fn from(_: std::convert::Infallible) -> Self { fn from(err: std::convert::Infallible) -> Self {
// hint that an error that will never happen match err {}
unreachable!()
} }
} }
/// Convert `Error` to a `Response` instance impl From<ws::ProtocolError> for Error {
impl From<Error> for Response<Body> { fn from(err: ws::ProtocolError) -> Self {
fn from(err: Error) -> Self { Self::new_ws().with_cause(err)
Response::from_error(err)
} }
} }
/// `Error` for any error that implements `ResponseError` impl From<HttpError> for Error {
impl<T: ResponseError + 'static> From<T> for Error { fn from(err: HttpError) -> Self {
fn from(err: T) -> Error { Self::new_http().with_cause(err)
Error {
cause: Box::new(err),
}
} }
} }
#[derive(Debug, Display, Error)] impl From<ws::HandshakeError> for Error {
#[display(fmt = "Unknown Error")] fn from(err: ws::HandshakeError) -> Self {
struct UnitError; Self::new_ws().with_cause(err)
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
} }
} }
@ -218,13 +199,6 @@ pub enum ParseError {
Utf8(Utf8Error), Utf8(Utf8Error),
} }
/// Return `BadRequest` for `ParseError`
impl ResponseError for ParseError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
}
impl From<io::Error> for ParseError { impl From<io::Error> for ParseError {
fn from(err: io::Error) -> ParseError { fn from(err: io::Error) -> ParseError {
ParseError::Io(err) 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. /// A set of errors that can occur running blocking tasks in thread pool.
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "Blocking thread pool is gone")] #[display(fmt = "Blocking thread pool is gone")]
pub struct BlockingError; pub struct BlockingError;
/// `InternalServerError` for `BlockingError`
impl ResponseError for BlockingError {}
/// A set of errors that can occur during payload parsing. /// A set of errors that can occur during payload parsing.
#[derive(Debug, Display)] #[derive(Debug, Display)]
#[non_exhaustive] #[non_exhaustive]
@ -344,16 +327,9 @@ impl From<BlockingError> for PayloadError {
} }
} }
/// `PayloadError` returns two possible results: impl From<PayloadError> for Error {
/// fn from(err: PayloadError) -> Self {
/// - `Overflow` returns `PayloadTooLarge` Self::new_payload().with_cause(err)
/// - Other errors returns `BadRequest`
impl ResponseError for PayloadError {
fn status_code(&self) -> StatusCode {
match *self {
PayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE,
_ => StatusCode::BAD_REQUEST,
}
} }
} }
@ -362,13 +338,19 @@ impl ResponseError for PayloadError {
#[non_exhaustive] #[non_exhaustive]
pub enum DispatchError { pub enum DispatchError {
/// Service error /// 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 service error
Upgrade, Upgrade,
/// An `io::Error` that occurred while trying to read or write to a network /// An `io::Error` that occurred while trying to read or write to a network stream.
/// stream.
#[display(fmt = "IO error: {}", _0)] #[display(fmt = "IO error: {}", _0)]
Io(io::Error), 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -448,42 +424,36 @@ mod tests {
#[test] #[test]
fn test_into_response() { 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); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); 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); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
} }
#[test] #[test]
fn test_as_response() { fn test_as_response() {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let e: Error = ParseError::Io(orig).into(); let err: Error = ParseError::Io(orig).into();
assert_eq!(format!("{}", e.as_response_error()), "IO error: other"); assert_eq!(
} format!("{}", err),
"error parsing HTTP message: 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);
} }
#[test] #[test]
fn test_error_display() { fn test_error_display() {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.to_string(); let err = Error::new_io().with_cause(orig);
let e = Error::from(orig); assert_eq!("connection error: other", err.to_string());
assert_eq!(format!("{}", e), desc);
} }
#[test] #[test]
fn test_error_http_response() { fn test_error_http_response() {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let e = Error::from(orig); let err = Error::new_io().with_cause(orig);
let resp: Response<Body> = e.into(); let resp: Response<AnyBody> = err.into();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
} }
@ -535,14 +505,4 @@ mod tests {
from!(httparse::Error::TooManyHeaders => ParseError::TooLarge); from!(httparse::Error::TooManyHeaders => ParseError::TooLarge);
from!(httparse::Error::Version => ParseError::Version); 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::{ use std::{
collections::VecDeque, collections::VecDeque,
error::Error as StdError,
fmt, fmt,
future::Future, future::Future,
io, mem, net, io, mem, net,
@ -17,19 +18,19 @@ use futures_core::ready;
use log::{error, trace}; use log::{error, trace};
use pin_project::pin_project; use pin_project::pin_project;
use crate::body::{Body, BodySize, MessageBody}; use crate::{
use crate::config::ServiceConfig; body::{AnyBody, BodySize, MessageBody},
use crate::error::{DispatchError, Error}; config::ServiceConfig,
use crate::error::{ParseError, PayloadError}; error::{DispatchError, ParseError, PayloadError},
use crate::http::StatusCode; service::HttpFlow,
use crate::request::Request; OnConnectData, Request, Response, StatusCode,
use crate::response::Response; };
use crate::service::HttpFlow;
use crate::OnConnectData;
use super::codec::Codec; use super::{
use super::payload::{Payload, PayloadSender, PayloadStatus}; codec::Codec,
use super::{Message, MessageType}; payload::{Payload, PayloadSender, PayloadStatus},
Message, MessageType,
};
const LW_BUFFER_SIZE: usize = 1024; const LW_BUFFER_SIZE: usize = 1024;
const HW_BUFFER_SIZE: usize = 1024 * 8; const HW_BUFFER_SIZE: usize = 1024 * 8;
@ -50,13 +51,13 @@ bitflags! {
pub struct Dispatcher<T, S, B, X, U> pub struct Dispatcher<T, S, B, X, U>
where where
S: Service<Request>, S: Service<Request>,
S::Error: Into<Error>, S::Error: Into<Response<AnyBody>>,
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>, X: Service<Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>, U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
@ -72,13 +73,13 @@ where
enum DispatcherState<T, S, B, X, U> enum DispatcherState<T, S, B, X, U>
where where
S: Service<Request>, S: Service<Request>,
S::Error: Into<Error>, S::Error: Into<Response<AnyBody>>,
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>, X: Service<Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>, U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
@ -91,13 +92,13 @@ where
struct InnerDispatcher<T, S, B, X, U> struct InnerDispatcher<T, S, B, X, U>
where where
S: Service<Request>, S: Service<Request>,
S::Error: Into<Error>, S::Error: Into<Response<AnyBody>>,
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>, X: Service<Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>, U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
@ -136,13 +137,13 @@ where
X: Service<Request, Response = Request>, X: Service<Request, Response = Request>,
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
None, None,
ExpectCall(#[pin] X::Future), ExpectCall(#[pin] X::Future),
ServiceCall(#[pin] S::Future), ServiceCall(#[pin] S::Future),
SendPayload(#[pin] B), SendPayload(#[pin] B),
SendErrorPayload(#[pin] Body), SendErrorPayload(#[pin] AnyBody),
} }
impl<S, B, X> State<S, B, X> impl<S, B, X> State<S, B, X>
@ -152,7 +153,7 @@ where
X: Service<Request, Response = Request>, X: Service<Request, Response = Request>,
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
matches!(self, State::None) matches!(self, State::None)
@ -170,14 +171,14 @@ where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>, S: Service<Request>,
S::Error: Into<Error>, S::Error: Into<Response<AnyBody>>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>, X: Service<Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>, U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
@ -231,14 +232,14 @@ where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>, S: Service<Request>,
S::Error: Into<Error>, S::Error: Into<Response<AnyBody>>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>, X: Service<Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>, U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
@ -334,7 +335,7 @@ where
fn send_error_response( fn send_error_response(
mut self: Pin<&mut Self>, mut self: Pin<&mut Self>,
message: Response<()>, message: Response<()>,
body: Body, body: AnyBody,
) -> Result<(), DispatchError> { ) -> Result<(), DispatchError> {
let size = self.as_mut().send_response_inner(message, &body)?; let size = self.as_mut().send_response_inner(message, &body)?;
let state = match size { let state = match size {
@ -379,7 +380,7 @@ where
// send_response would update InnerDispatcher state to SendPayload or // send_response would update InnerDispatcher state to SendPayload or
// None(If response body is empty). // None(If response body is empty).
// continue loop to poll it. // 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. // return with upgrade request and poll it exclusively.
@ -399,7 +400,7 @@ where
// send service call error as response // send service call error as response
Poll::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(()); let (res, body) = res.replace_body(());
self.as_mut().send_error_response(res, body)?; self.as_mut().send_error_response(res, body)?;
} }
@ -438,7 +439,7 @@ where
} }
Poll::Ready(Some(Err(err))) => { Poll::Ready(Some(Err(err))) => {
return Err(DispatchError::Service(err.into())) return Err(DispatchError::Body(err.into()))
} }
Poll::Pending => return Ok(PollResponse::DoNothing), Poll::Pending => return Ok(PollResponse::DoNothing),
@ -473,7 +474,7 @@ where
} }
Poll::Ready(Some(Err(err))) => { Poll::Ready(Some(Err(err))) => {
return Err(DispatchError::Service(err)) return Err(DispatchError::Service(err.into()))
} }
Poll::Pending => return Ok(PollResponse::DoNothing), Poll::Pending => return Ok(PollResponse::DoNothing),
@ -496,7 +497,7 @@ where
// send expect error as response // send expect error as response
Poll::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(()); let (res, body) = res.replace_body(());
self.as_mut().send_error_response(res, 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 // to notify the dispatcher a new state is set and the outer loop
// should be continue. // should be continue.
Poll::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(()); let (res, body) = res.replace_body(());
return self.send_error_response(res, body); return self.send_error_response(res, body);
} }
@ -566,7 +567,7 @@ where
Poll::Pending => Ok(()), Poll::Pending => Ok(()),
// see the comment on ExpectCall state branch's Ready(Err(err)). // see the comment on ExpectCall state branch's Ready(Err(err)).
Poll::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(()); let (res, body) = res.replace_body(());
self.send_error_response(res, body) self.send_error_response(res, body)
} }
@ -772,7 +773,7 @@ where
trace!("Slow request timeout"); trace!("Slow request timeout");
let _ = self.as_mut().send_error_response( let _ = self.as_mut().send_error_response(
Response::with_body(StatusCode::REQUEST_TIMEOUT, ()), Response::with_body(StatusCode::REQUEST_TIMEOUT, ()),
Body::Empty, AnyBody::Empty,
); );
this = self.project(); this = self.project();
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
@ -909,14 +910,14 @@ where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>, S: Service<Request>,
S::Error: Into<Error>, S::Error: Into<Response<AnyBody>>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>, X: Service<Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Response<AnyBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>, U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, 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_service(|_req: Request| ready(Ok::<_, Error>(Response::ok())))
} }
fn echo_path_service( fn echo_path_service(
) -> impl Service<Request, Response = Response<Body>, Error = Error> { ) -> impl Service<Request, Response = Response<AnyBody>, Error = Error> {
fn_service(|req: Request| { fn_service(|req: Request| {
let path = req.path().as_bytes(); let path = req.path().as_bytes();
ready(Ok::<_, Error>( 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 bytes::{BufMut, BytesMut};
use crate::body::BodySize; use crate::{
use crate::config::ServiceConfig; body::BodySize,
use crate::header::{map::Value, HeaderName}; config::ServiceConfig,
use crate::helpers; header::{map::Value, HeaderMap, HeaderName},
use crate::http::header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
use crate::http::{HeaderMap, StatusCode, Version}; helpers,
use crate::message::{ConnectionType, RequestHeadType}; message::{ConnectionType, RequestHeadType},
use crate::response::Response; Response, StatusCode, Version,
};
const AVERAGE_HEADER_SIZE: usize = 30; const AVERAGE_HEADER_SIZE: usize = 30;
@ -287,7 +288,7 @@ impl MessageType for RequestHeadType {
let head = self.as_ref(); let head = self.as_ref();
dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE); dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE);
write!( write!(
helpers::Writer(dst), helpers::MutWriter(dst),
"{} {} {}", "{} {} {}",
head.method, head.method,
head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"),
@ -420,7 +421,7 @@ impl TransferEncoding {
*eof = true; *eof = true;
buf.extend_from_slice(b"0\r\n\r\n"); buf.extend_from_slice(b"0\r\n\r\n");
} else { } 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))?; .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
buf.reserve(msg.len() + 2); buf.reserve(msg.len() + 2);

View File

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

View File

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

View File

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

View File

@ -1,8 +1,12 @@
use std::future::Future; use std::{
use std::marker::PhantomData; error::Error as StdError,
use std::pin::Pin; future::Future,
use std::task::{Context, Poll}; marker::PhantomData,
use std::{net, rc::Rc}; net,
pin::Pin,
rc::Rc,
task::{Context, Poll},
};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
@ -13,16 +17,16 @@ use actix_service::{
use actix_utils::future::ready; use actix_utils::future::ready;
use bytes::Bytes; use bytes::Bytes;
use futures_core::{future::LocalBoxFuture, ready}; 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 log::error;
use crate::body::MessageBody; use crate::{
use crate::config::ServiceConfig; body::{AnyBody, MessageBody},
use crate::error::{DispatchError, Error}; config::ServiceConfig,
use crate::request::Request; error::DispatchError,
use crate::response::Response; service::HttpFlow,
use crate::service::HttpFlow; ConnectCallback, OnConnectData, Request, Response,
use crate::{ConnectCallback, OnConnectData}; };
use super::dispatcher::Dispatcher; use super::dispatcher::Dispatcher;
@ -37,12 +41,12 @@ pub struct H2Service<T, S, B> {
impl<T, S, B> H2Service<T, S, B> impl<T, S, B> H2Service<T, S, B>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static, S::Error: Into<Response<AnyBody>> + 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
/// Create new `H2Service` instance with config. /// Create new `H2Service` instance with config.
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>( pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
@ -68,12 +72,12 @@ impl<S, B> H2Service<TcpStream, S, B>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static, S::Future: 'static,
S::Error: Into<Error> + 'static, S::Error: Into<Response<AnyBody>> + 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
/// Create plain TCP based service /// Create plain TCP based service
pub fn tcp( pub fn tcp(
@ -107,12 +111,12 @@ mod openssl {
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static, S::Future: 'static,
S::Error: Into<Error> + 'static, S::Error: Into<Response<AnyBody>> + 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
/// Create OpenSSL based service /// Create OpenSSL based service
pub fn openssl( pub fn openssl(
@ -153,12 +157,12 @@ mod rustls {
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static, S::Future: 'static,
S::Error: Into<Error> + 'static, S::Error: Into<Response<AnyBody>> + 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
/// Create Rustls based service /// Create Rustls based service
pub fn rustls( pub fn rustls(
@ -197,12 +201,12 @@ where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static, S::Future: 'static,
S::Error: Into<Error> + 'static, S::Error: Into<Response<AnyBody>> + 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
@ -237,7 +241,7 @@ where
impl<T, S, B> H2ServiceHandler<T, S, B> impl<T, S, B> H2ServiceHandler<T, S, B>
where where
S: Service<Request>, S: Service<Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Response<AnyBody>> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
@ -260,11 +264,11 @@ impl<T, S, B> Service<(T, Option<net::SocketAddr>)> for H2ServiceHandler<T, S, B
where where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>, S: Service<Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Response<AnyBody>> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
@ -288,7 +292,7 @@ where
Some(self.cfg.clone()), Some(self.cfg.clone()),
addr, addr,
on_connect_data, on_connect_data,
handshake(io), h2_handshake(io),
), ),
} }
} }
@ -305,7 +309,7 @@ where
Option<ServiceConfig>, Option<ServiceConfig>,
Option<net::SocketAddr>, Option<net::SocketAddr>,
OnConnectData, OnConnectData,
Handshake<T, Bytes>, H2Handshake<T, Bytes>,
), ),
} }
@ -313,7 +317,7 @@ pub struct H2ServiceHandlerResponse<T, S, B>
where where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>, S: Service<Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Response<AnyBody>> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
@ -325,11 +329,11 @@ impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
where where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>, S: Service<Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Response<AnyBody>> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
type Output = Result<(), DispatchError>; 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' '); 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) { pub fn write_content_length<B: BufMut>(n: u64, buf: &mut B) {
if n == 0 { if n == 0 {
buf.put_slice(b"\r\ncontent-length: 0\r\n"); 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"); buf.put_slice(b"\r\n");
} }
// TODO: bench why this is needed vs Buf::writer /// An `io::Write`r that only requires mutable reference and assumes that there is space available
/// An `io` writer for a `BufMut` that should only be used once and on an empty buffer. /// in the buffer for every write operation or that it can be extended implicitly (like
pub(crate) struct Writer<'a, B>(pub &'a mut B); /// `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 where
B: BufMut, B: BufMut,
{ {

View File

@ -54,7 +54,7 @@ pub mod ws;
pub use self::builder::HttpServiceBuilder; pub use self::builder::HttpServiceBuilder;
pub use self::config::{KeepAlive, ServiceConfig}; 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::extensions::Extensions;
pub use self::header::ContentEncoding; pub use self::header::ContentEncoding;
pub use self::http_message::HttpMessage; pub use self::http_message::HttpMessage;

View File

@ -8,7 +8,7 @@ use std::{
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use crate::{ use crate::{
body::{Body, MessageBody}, body::{AnyBody, MessageBody},
error::Error, error::Error,
extensions::Extensions, extensions::Extensions,
http::{HeaderMap, StatusCode}, http::{HeaderMap, StatusCode},
@ -22,13 +22,13 @@ pub struct Response<B> {
pub(crate) body: B, pub(crate) body: B,
} }
impl Response<Body> { impl Response<AnyBody> {
/// Constructs a new response with default body. /// Constructs a new response with default body.
#[inline] #[inline]
pub fn new(status: StatusCode) -> Response<Body> { pub fn new(status: StatusCode) -> Self {
Response { Response {
head: BoxedResponseHead::new(status), 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. /// Constructs a new response with status 200 OK.
#[inline] #[inline]
pub fn ok() -> Response<Body> { pub fn ok() -> Self {
Response::new(StatusCode::OK) Response::new(StatusCode::OK)
} }
/// Constructs a new response with status 400 Bad Request. /// Constructs a new response with status 400 Bad Request.
#[inline] #[inline]
pub fn bad_request() -> Response<Body> { pub fn bad_request() -> Self {
Response::new(StatusCode::BAD_REQUEST) Response::new(StatusCode::BAD_REQUEST)
} }
/// Constructs a new response with status 404 Not Found. /// Constructs a new response with status 404 Not Found.
#[inline] #[inline]
pub fn not_found() -> Response<Body> { pub fn not_found() -> Self {
Response::new(StatusCode::NOT_FOUND) Response::new(StatusCode::NOT_FOUND)
} }
/// Constructs a new response with status 500 Internal Server Error. /// Constructs a new response with status 500 Internal Server Error.
#[inline] #[inline]
pub fn internal_server_error() -> Response<Body> { pub fn internal_server_error() -> Self {
Response::new(StatusCode::INTERNAL_SERVER_ERROR) Response::new(StatusCode::INTERNAL_SERVER_ERROR)
} }
// end shortcuts // 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> { impl<B> Response<B> {
@ -209,7 +198,6 @@ impl<B> Response<B> {
impl<B> fmt::Debug for Response<B> impl<B> fmt::Debug for Response<B>
where where
B: MessageBody, B: MessageBody,
B::Error: Into<Error>,
{ {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let res = writeln!( 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 { fn from(res: Result<I, E>) -> Self {
match res { match res {
Ok(val) => val.into(), 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 { fn from(mut builder: ResponseBuilder) -> Self {
builder.finish() 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 { fn from(val: &'static str) -> Self {
Response::build(StatusCode::OK) Response::build(StatusCode::OK)
.content_type(mime::TEXT_PLAIN_UTF_8) .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 { fn from(val: &'static [u8]) -> Self {
Response::build(StatusCode::OK) Response::build(StatusCode::OK)
.content_type(mime::APPLICATION_OCTET_STREAM) .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 { fn from(val: String) -> Self {
Response::build(StatusCode::OK) Response::build(StatusCode::OK)
.content_type(mime::TEXT_PLAIN_UTF_8) .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 { fn from(val: &'a String) -> Self {
Response::build(StatusCode::OK) Response::build(StatusCode::OK)
.content_type(mime::TEXT_PLAIN_UTF_8) .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 { fn from(val: Bytes) -> Self {
Response::build(StatusCode::OK) Response::build(StatusCode::OK)
.content_type(mime::APPLICATION_OCTET_STREAM) .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 { fn from(val: BytesMut) -> Self {
Response::build(StatusCode::OK) Response::build(StatusCode::OK)
.content_type(mime::APPLICATION_OCTET_STREAM) .content_type(mime::APPLICATION_OCTET_STREAM)
@ -301,7 +297,6 @@ impl From<BytesMut> for Response<Body> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::body::Body;
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE};
#[test] #[test]
@ -316,7 +311,7 @@ mod tests {
#[test] #[test]
fn test_into_response() { 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.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
@ -325,7 +320,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<AnyBody> = 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(),
@ -334,7 +329,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<AnyBody> = "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(),
@ -343,7 +338,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<AnyBody> = (&"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(),
@ -353,7 +348,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<AnyBody> = 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(),
@ -363,7 +358,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<AnyBody> = 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(),
@ -373,7 +368,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<AnyBody> = 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::{ use std::{
cell::{Ref, RefMut}, cell::{Ref, RefMut},
error::Error as StdError,
fmt, fmt,
future::Future, future::Future,
pin::Pin, pin::Pin,
@ -13,7 +14,7 @@ use bytes::Bytes;
use futures_core::Stream; use futures_core::Stream;
use crate::{ use crate::{
body::{Body, BodyStream}, body::{AnyBody, BodyStream},
error::{Error, HttpError}, error::{Error, HttpError},
header::{self, IntoHeaderPair, IntoHeaderValue}, header::{self, IntoHeaderPair, IntoHeaderValue},
message::{BoxedResponseHead, ConnectionType, ResponseHead}, message::{BoxedResponseHead, ConnectionType, ResponseHead},
@ -235,9 +236,9 @@ impl ResponseBuilder {
/// ///
/// This `ResponseBuilder` will be left in a useless state. /// This `ResponseBuilder` will be left in a useless state.
#[inline] #[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()) self.message_body(body.into())
.unwrap_or_else(Response::from_error) .unwrap_or_else(Response::from)
} }
/// Generate response with a body. /// Generate response with a body.
@ -245,7 +246,7 @@ impl ResponseBuilder {
/// This `ResponseBuilder` will be left in a useless state. /// This `ResponseBuilder` will be left in a useless state.
pub fn message_body<B>(&mut self, body: B) -> Result<Response<B>, Error> { pub fn message_body<B>(&mut self, body: B) -> Result<Response<B>, Error> {
if let Some(err) = self.err.take() { 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"); 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. /// This `ResponseBuilder` will be left in a useless state.
#[inline] #[inline]
pub fn streaming<S, E>(&mut self, stream: S) -> Response<Body> pub fn streaming<S, E>(&mut self, stream: S) -> Response<AnyBody>
where where
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static, S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<Error> + '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. /// Generate response with an empty body.
/// ///
/// This `ResponseBuilder` will be left in a useless state. /// This `ResponseBuilder` will be left in a useless state.
#[inline] #[inline]
pub fn finish(&mut self) -> Response<Body> { pub fn finish(&mut self) -> Response<AnyBody> {
self.body(Body::Empty) self.body(AnyBody::Empty)
} }
/// Create an owned `ResponseBuilder`, leaving the original in a useless state. /// 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 { 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> { fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(Ok(self.finish())) Poll::Ready(Ok(self.finish()))

View File

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

View File

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

View File

@ -1,5 +1,7 @@
use std::convert::Infallible;
use actix_http::{ use actix_http::{
http, http::StatusCode, HttpMessage, HttpService, Request, Response, ResponseError, body::AnyBody, http, http::StatusCode, HttpMessage, HttpService, Request, Response,
}; };
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::ServiceFactoryExt; use actix_service::ServiceFactoryExt;
@ -34,7 +36,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
async fn test_h1_v2() { async fn test_h1_v2() {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.finish(|_| future::ok::<_, ()>(Response::ok().set_body(STR))) .finish(|_| future::ok::<_, Infallible>(Response::ok().set_body(STR)))
.tcp() .tcp()
}) })
.await; .await;
@ -62,7 +64,7 @@ async fn test_h1_v2() {
async fn test_connection_close() { async fn test_connection_close() {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.finish(|_| future::ok::<_, ()>(Response::ok().set_body(STR))) .finish(|_| future::ok::<_, Infallible>(Response::ok().set_body(STR)))
.tcp() .tcp()
.map(|_| ()) .map(|_| ())
}) })
@ -76,11 +78,11 @@ async fn test_connection_close() {
async fn test_with_query_parameter() { async fn test_with_query_parameter() {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.finish(|req: Request| { .finish(|req: Request| async move {
if req.uri().query().unwrap().contains("qp=") { if req.uri().query().unwrap().contains("qp=") {
future::ok::<_, ()>(Response::ok()) Ok::<_, Infallible>(Response::ok())
} else { } else {
future::ok::<_, ()>(Response::bad_request()) Ok(Response::bad_request())
} }
}) })
.tcp() .tcp()
@ -97,9 +99,9 @@ async fn test_with_query_parameter() {
#[display(fmt = "expect failed")] #[display(fmt = "expect failed")]
struct ExpectFailed; struct ExpectFailed;
impl ResponseError for ExpectFailed { impl From<ExpectFailed> for Response<AnyBody> {
fn status_code(&self) -> StatusCode { fn from(_: ExpectFailed) -> Self {
StatusCode::EXPECTATION_FAILED Response::new(StatusCode::EXPECTATION_FAILED)
} }
} }
@ -123,7 +125,7 @@ async fn test_h1_expect() {
let str = std::str::from_utf8(&buf).unwrap(); let str = std::str::from_utf8(&buf).unwrap();
assert_eq!(str, "expect body"); assert_eq!(str, "expect body");
Ok::<_, ()>(Response::ok()) Ok::<_, Infallible>(Response::ok())
}) })
.tcp() .tcp()
}) })

View File

@ -2,16 +2,16 @@
extern crate tls_openssl as openssl; extern crate tls_openssl as openssl;
use std::io; use std::{convert::Infallible, io};
use actix_http::{ use actix_http::{
body::{Body, SizedStream}, body::{AnyBody, Body, SizedStream},
error::PayloadError, error::PayloadError,
http::{ http::{
header::{self, HeaderName, HeaderValue}, header::{self, HeaderName, HeaderValue},
Method, StatusCode, Version, Method, StatusCode, Version,
}, },
Error, HttpMessage, HttpService, Request, Response, ResponseError, Error, HttpMessage, HttpService, Request, Response,
}; };
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::{fn_service, ServiceFactoryExt}; use actix_service::{fn_service, ServiceFactoryExt};
@ -136,7 +136,7 @@ async fn test_h2_content_length() {
StatusCode::OK, StatusCode::OK,
StatusCode::NOT_FOUND, StatusCode::NOT_FOUND,
]; ];
ok::<_, ()>(Response::new(statuses[idx])) ok::<_, Infallible>(Response::new(statuses[idx]))
}) })
.openssl(tls_config()) .openssl(tls_config())
.map_err(|_| ()) .map_err(|_| ())
@ -206,7 +206,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())) ok::<_, Infallible>(builder.body(data.clone()))
}) })
.openssl(tls_config()) .openssl(tls_config())
.map_err(|_| ()) .map_err(|_| ())
@ -246,7 +246,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
async fn test_h2_body2() { async fn test_h2_body2() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.openssl(tls_config()) .openssl(tls_config())
.map_err(|_| ()) .map_err(|_| ())
}) })
@ -264,7 +264,7 @@ async fn test_h2_body2() {
async fn test_h2_head_empty() { async fn test_h2_head_empty() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.finish(|_| ok::<_, ()>(Response::ok().set_body(STR))) .finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.openssl(tls_config()) .openssl(tls_config())
.map_err(|_| ()) .map_err(|_| ())
}) })
@ -288,7 +288,7 @@ async fn test_h2_head_empty() {
async fn test_h2_head_binary() { async fn test_h2_head_binary() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.openssl(tls_config()) .openssl(tls_config())
.map_err(|_| ()) .map_err(|_| ())
}) })
@ -311,7 +311,7 @@ async fn test_h2_head_binary() {
async fn test_h2_head_binary2() { async fn test_h2_head_binary2() {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.openssl(tls_config()) .openssl(tls_config())
.map_err(|_| ()) .map_err(|_| ())
}) })
@ -330,9 +330,12 @@ async fn test_h2_head_binary2() {
async fn test_h2_body_length() { async fn test_h2_body_length() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| { .h2(|_| async {
let body = once(ok(Bytes::from_static(STR.as_ref()))); let body = once(async {
ok::<_, ()>( Ok::<_, Infallible>(Bytes::from_static(STR.as_ref()))
});
Ok::<_, Infallible>(
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
) )
}) })
@ -355,7 +358,7 @@ 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::<_, ()>( ok::<_, Infallible>(
Response::build(StatusCode::OK) Response::build(StatusCode::OK)
.insert_header((header::TRANSFER_ENCODING, "chunked")) .insert_header((header::TRANSFER_ENCODING, "chunked"))
.streaming(body), .streaming(body),
@ -383,7 +386,7 @@ 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::<_, ()>( ok::<_, Infallible>(
Response::build(StatusCode::OK) Response::build(StatusCode::OK)
.insert_header((header::CONTENT_TYPE, broken_header)) .insert_header((header::CONTENT_TYPE, broken_header))
.body(STR), .body(STR),
@ -399,16 +402,19 @@ async fn test_h2_response_http_error_handling() {
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); assert_eq!(
bytes,
Bytes::from_static(b"error processing HTTP: failed to parse header value")
);
} }
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "error")] #[display(fmt = "error")]
struct BadRequest; struct BadRequest;
impl ResponseError for BadRequest { impl From<BadRequest> for Response<AnyBody> {
fn status_code(&self) -> StatusCode { fn from(err: BadRequest) -> Self {
StatusCode::BAD_REQUEST Response::build(StatusCode::BAD_REQUEST).body(err.to_string())
} }
} }
@ -439,7 +445,7 @@ async fn test_h2_on_connect() {
}) })
.h2(|req: Request| { .h2(|req: Request| {
assert!(req.extensions().contains::<isize>()); assert!(req.extensions().contains::<isize>());
ok::<_, ()>(Response::ok()) ok::<_, Infallible>(Response::ok())
}) })
.openssl(tls_config()) .openssl(tls_config())
.map_err(|_| ()) .map_err(|_| ())

View File

@ -2,14 +2,21 @@
extern crate tls_rustls as rustls; extern crate tls_rustls as rustls;
use std::{
convert::Infallible,
io::{self, BufReader, Write},
net::{SocketAddr, TcpStream as StdTcpStream},
sync::Arc,
};
use actix_http::{ use actix_http::{
body::{Body, SizedStream}, body::{AnyBody, Body, SizedStream},
error::PayloadError, error::PayloadError,
http::{ http::{
header::{self, HeaderName, HeaderValue}, header::{self, HeaderName, HeaderValue},
Method, StatusCode, Version, Method, StatusCode, Version,
}, },
Error, HttpService, Request, Response, ResponseError, Error, HttpService, Request, Response,
}; };
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};
@ -24,12 +31,6 @@ use rustls::{
}; };
use webpki::DNSNameRef; use webpki::DNSNameRef;
use std::{
io::{self, BufReader, Write},
net::{SocketAddr, TcpStream as StdTcpStream},
sync::Arc,
};
async fn load_body<S>(mut stream: S) -> Result<BytesMut, PayloadError> async fn load_body<S>(mut stream: S) -> Result<BytesMut, PayloadError>
where where
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin, S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
@ -173,7 +174,7 @@ async fn test_h2_content_length() {
StatusCode::OK, StatusCode::OK,
StatusCode::NOT_FOUND, StatusCode::NOT_FOUND,
]; ];
ok::<_, ()>(Response::new(statuses[indx])) ok::<_, Infallible>(Response::new(statuses[indx]))
}) })
.rustls(tls_config()) .rustls(tls_config())
}) })
@ -242,7 +243,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::<_, ()>(config.body(data.clone())) ok::<_, Infallible>(config.body(data.clone()))
}) })
.rustls(tls_config()) .rustls(tls_config())
}).await; }).await;
@ -281,7 +282,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
async fn test_h2_body2() { async fn test_h2_body2() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls(tls_config()) .rustls(tls_config())
}) })
.await; .await;
@ -298,7 +299,7 @@ async fn test_h2_body2() {
async fn test_h2_head_empty() { async fn test_h2_head_empty() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.finish(|_| ok::<_, ()>(Response::ok().set_body(STR))) .finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls(tls_config()) .rustls(tls_config())
}) })
.await; .await;
@ -324,7 +325,7 @@ async fn test_h2_head_empty() {
async fn test_h2_head_binary() { async fn test_h2_head_binary() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls(tls_config()) .rustls(tls_config())
}) })
.await; .await;
@ -349,7 +350,7 @@ async fn test_h2_head_binary() {
async fn test_h2_head_binary2() { async fn test_h2_head_binary2() {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, ()>(Response::ok().set_body(STR))) .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls(tls_config()) .rustls(tls_config())
}) })
.await; .await;
@ -371,8 +372,8 @@ async fn test_h2_body_length() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| { .h2(|_| {
let body = once(ok(Bytes::from_static(STR.as_ref()))); let body = once(ok::<_, Infallible>(Bytes::from_static(STR.as_ref())));
ok::<_, ()>( ok::<_, Infallible>(
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
) )
}) })
@ -394,7 +395,7 @@ 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::<_, ()>( ok::<_, Infallible>(
Response::build(StatusCode::OK) Response::build(StatusCode::OK)
.insert_header((header::TRANSFER_ENCODING, "chunked")) .insert_header((header::TRANSFER_ENCODING, "chunked"))
.streaming(body), .streaming(body),
@ -420,9 +421,9 @@ async fn test_h2_response_http_error_handling() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(fn_factory_with_config(|_: ()| { .h2(fn_factory_with_config(|_: ()| {
ok::<_, ()>(fn_service(|_| { ok::<_, Infallible>(fn_service(|_| {
let broken_header = Bytes::from_static(b"\0\0\0"); let broken_header = Bytes::from_static(b"\0\0\0");
ok::<_, ()>( ok::<_, Infallible>(
Response::build(StatusCode::OK) Response::build(StatusCode::OK)
.insert_header((http::header::CONTENT_TYPE, broken_header)) .insert_header((http::header::CONTENT_TYPE, broken_header))
.body(STR), .body(STR),
@ -438,16 +439,19 @@ async fn test_h2_response_http_error_handling() {
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); assert_eq!(
bytes,
Bytes::from_static(b"error processing HTTP: failed to parse header value")
);
} }
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "error")] #[display(fmt = "error")]
struct BadRequest; struct BadRequest;
impl ResponseError for BadRequest { impl From<BadRequest> for Response<AnyBody> {
fn status_code(&self) -> StatusCode { fn from(_: BadRequest) -> Self {
StatusCode::BAD_REQUEST Response::bad_request().set_body(AnyBody::from("error"))
} }
} }

View File

@ -1,21 +1,25 @@
use std::io::{Read, Write}; use std::{
use std::time::Duration; convert::Infallible,
use std::{net, thread}; io::{Read, Write},
net, thread,
time::Duration,
};
use actix_http::{ use actix_http::{
body::{Body, SizedStream}, body::{AnyBody, Body, SizedStream},
http::{self, header, StatusCode}, header, http, Error, HttpMessage, HttpService, KeepAlive, Request, Response,
Error, HttpService, KeepAlive, Request, Response, StatusCode,
}; };
use actix_http::{HttpMessage, ResponseError};
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_rt::time::sleep; use actix_rt::time::sleep;
use actix_service::fn_service; use actix_service::fn_service;
use actix_utils::future::{err, ok, ready}; use actix_utils::future::{err, ok, ready};
use bytes::Bytes; use bytes::Bytes;
use derive_more::{Display, Error}; use derive_more::{Display, Error};
use futures_util::stream::{once, StreamExt as _}; use futures_util::{
use futures_util::FutureExt as _; stream::{once, StreamExt as _},
FutureExt as _,
};
use regex::Regex; use regex::Regex;
#[actix_rt::test] #[actix_rt::test]
@ -27,7 +31,7 @@ async fn test_h1() {
.client_disconnect(1000) .client_disconnect(1000)
.h1(|req: Request| { .h1(|req: Request| {
assert!(req.peer_addr().is_some()); assert!(req.peer_addr().is_some());
ok::<_, ()>(Response::ok()) ok::<_, Infallible>(Response::ok())
}) })
.tcp() .tcp()
}) })
@ -47,7 +51,7 @@ async fn test_h1_2() {
.finish(|req: Request| { .finish(|req: Request| {
assert!(req.peer_addr().is_some()); assert!(req.peer_addr().is_some());
assert_eq!(req.version(), http::Version::HTTP_11); assert_eq!(req.version(), http::Version::HTTP_11);
ok::<_, ()>(Response::ok()) ok::<_, Infallible>(Response::ok())
}) })
.tcp() .tcp()
}) })
@ -61,9 +65,9 @@ async fn test_h1_2() {
#[display(fmt = "expect failed")] #[display(fmt = "expect failed")]
struct ExpectFailed; struct ExpectFailed;
impl ResponseError for ExpectFailed { impl From<ExpectFailed> for Response<AnyBody> {
fn status_code(&self) -> StatusCode { fn from(_: ExpectFailed) -> Self {
StatusCode::PRECONDITION_FAILED Response::new(StatusCode::EXPECTATION_FAILED)
} }
} }
@ -78,7 +82,7 @@ async fn test_expect_continue() {
err(ExpectFailed) err(ExpectFailed)
} }
})) }))
.finish(|_| ok::<_, ()>(Response::ok())) .finish(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
}) })
.await; .await;
@ -87,7 +91,7 @@ async fn test_expect_continue() {
let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n");
let mut data = String::new(); let mut data = String::new();
let _ = stream.read_to_string(&mut data); let _ = stream.read_to_string(&mut data);
assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); assert!(data.starts_with("HTTP/1.1 417 Expectation Failed\r\ncontent-length"));
let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n");
@ -109,7 +113,7 @@ async fn test_expect_continue_h1() {
} }
}) })
})) }))
.h1(fn_service(|_| ok::<_, ()>(Response::ok()))) .h1(fn_service(|_| ok::<_, Infallible>(Response::ok())))
.tcp() .tcp()
}) })
.await; .await;
@ -118,7 +122,7 @@ async fn test_expect_continue_h1() {
let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n");
let mut data = String::new(); let mut data = String::new();
let _ = stream.read_to_string(&mut data); let _ = stream.read_to_string(&mut data);
assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); assert!(data.starts_with("HTTP/1.1 417 Expectation Failed\r\ncontent-length"));
let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n");
@ -190,7 +194,7 @@ async fn test_slow_request() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.client_timeout(100) .client_timeout(100)
.finish(|_| ok::<_, ()>(Response::ok())) .finish(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
}) })
.await; .await;
@ -206,7 +210,7 @@ async fn test_slow_request() {
async fn test_http1_malformed_request() { async fn test_http1_malformed_request() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, ()>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
}) })
.await; .await;
@ -222,7 +226,7 @@ async fn test_http1_malformed_request() {
async fn test_http1_keepalive() { async fn test_http1_keepalive() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, ()>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
}) })
.await; .await;
@ -244,7 +248,7 @@ async fn test_http1_keepalive_timeout() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.keep_alive(1) .keep_alive(1)
.h1(|_| ok::<_, ()>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
}) })
.await; .await;
@ -265,7 +269,7 @@ async fn test_http1_keepalive_timeout() {
async fn test_http1_keepalive_close() { async fn test_http1_keepalive_close() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, ()>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
}) })
.await; .await;
@ -286,7 +290,7 @@ async fn test_http1_keepalive_close() {
async fn test_http10_keepalive_default_close() { async fn test_http10_keepalive_default_close() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, ()>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
}) })
.await; .await;
@ -306,7 +310,7 @@ async fn test_http10_keepalive_default_close() {
async fn test_http10_keepalive() { async fn test_http10_keepalive() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, ()>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
}) })
.await; .await;
@ -334,7 +338,7 @@ async fn test_http1_keepalive_disabled() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.keep_alive(KeepAlive::Disabled) .keep_alive(KeepAlive::Disabled)
.h1(|_| ok::<_, ()>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
}) })
.await; .await;
@ -369,7 +373,7 @@ async fn test_content_length() {
StatusCode::OK, StatusCode::OK,
StatusCode::NOT_FOUND, StatusCode::NOT_FOUND,
]; ];
ok::<_, ()>(Response::new(statuses[indx])) ok::<_, Infallible>(Response::new(statuses[indx]))
}) })
.tcp() .tcp()
}) })
@ -424,7 +428,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::<_, Infallible>(builder.body(data.clone()))
}).tcp() }).tcp()
}).await; }).await;
@ -462,7 +466,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
async fn test_h1_body() { async fn test_h1_body() {
let mut srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.tcp() .tcp()
}) })
.await; .await;
@ -479,7 +483,7 @@ async fn test_h1_body() {
async fn test_h1_head_empty() { async fn test_h1_head_empty() {
let mut srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.tcp() .tcp()
}) })
.await; .await;
@ -504,7 +508,7 @@ async fn test_h1_head_empty() {
async fn test_h1_head_binary() { async fn test_h1_head_binary() {
let mut srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.tcp() .tcp()
}) })
.await; .await;
@ -529,7 +533,7 @@ async fn test_h1_head_binary() {
async fn test_h1_head_binary2() { async fn test_h1_head_binary2() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, ()>(Response::ok().set_body(STR))) .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.tcp() .tcp()
}) })
.await; .await;
@ -551,8 +555,8 @@ async fn test_h1_body_length() {
let mut srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| { .h1(|_| {
let body = once(ok(Bytes::from_static(STR.as_ref()))); let body = once(ok::<_, Infallible>(Bytes::from_static(STR.as_ref())));
ok::<_, ()>( ok::<_, Infallible>(
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
) )
}) })
@ -574,7 +578,7 @@ 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::<_, ()>( ok::<_, Infallible>(
Response::build(StatusCode::OK) Response::build(StatusCode::OK)
.insert_header((header::TRANSFER_ENCODING, "chunked")) .insert_header((header::TRANSFER_ENCODING, "chunked"))
.streaming(body), .streaming(body),
@ -609,7 +613,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::build(StatusCode::OK).streaming(body)) ok::<_, Infallible>(Response::build(StatusCode::OK).streaming(body))
}) })
.tcp() .tcp()
}) })
@ -638,7 +642,7 @@ 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::<_, ()>( ok::<_, Infallible>(
Response::build(StatusCode::OK) Response::build(StatusCode::OK)
.insert_header((http::header::CONTENT_TYPE, broken_header)) .insert_header((http::header::CONTENT_TYPE, broken_header))
.body(STR), .body(STR),
@ -653,16 +657,19 @@ async fn test_h1_response_http_error_handling() {
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); assert_eq!(
bytes,
Bytes::from_static(b"error processing HTTP: failed to parse header value")
);
} }
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "error")] #[display(fmt = "error")]
struct BadRequest; struct BadRequest;
impl ResponseError for BadRequest { impl From<BadRequest> for Response<AnyBody> {
fn status_code(&self) -> StatusCode { fn from(_: BadRequest) -> Self {
StatusCode::BAD_REQUEST Response::bad_request().set_body(AnyBody::from("error"))
} }
} }
@ -692,7 +699,7 @@ async fn test_h1_on_connect() {
}) })
.h1(|req: Request| { .h1(|req: Request| {
assert!(req.extensions().contains::<isize>()); assert!(req.extensions().contains::<isize>());
ok::<_, ()>(Response::ok()) ok::<_, Infallible>(Response::ok())
}) })
.tcp() .tcp()
}) })

View File

@ -1,11 +1,12 @@
use std::{ use std::{
cell::Cell, cell::Cell,
convert::Infallible,
task::{Context, Poll}, task::{Context, Poll},
}; };
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::{ use actix_http::{
body::BodySize, body::{AnyBody, BodySize},
h1, h1,
ws::{self, CloseCode, Frame, Item, Message}, ws::{self, CloseCode, Frame, Item, Message},
Error, HttpService, Request, Response, Error, HttpService, Request, Response,
@ -13,6 +14,7 @@ use actix_http::{
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::{fn_factory, Service}; use actix_service::{fn_factory, Service};
use bytes::Bytes; use bytes::Bytes;
use derive_more::{Display, Error, From};
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use futures_util::{SinkExt as _, StreamExt as _}; use futures_util::{SinkExt as _, StreamExt as _};
@ -33,12 +35,39 @@ impl WsService {
} }
} }
#[derive(Debug, Display, Error, From)]
enum WsServiceError {
#[display(fmt = "http error")]
Http(actix_http::Error),
#[display(fmt = "ws handshake error")]
Ws(actix_http::ws::HandshakeError),
#[display(fmt = "io error")]
Io(std::io::Error),
#[display(fmt = "dispatcher error")]
Dispatcher,
}
impl From<WsServiceError> for Response<AnyBody> {
fn from(err: WsServiceError) -> Self {
match err {
WsServiceError::Http(err) => err.into(),
WsServiceError::Ws(err) => err.into(),
WsServiceError::Io(_err) => unreachable!(),
WsServiceError::Dispatcher => Response::internal_server_error()
.set_body(AnyBody::from(format!("{}", err))),
}
}
}
impl<T> Service<(Request, Framed<T, h1::Codec>)> for WsService impl<T> Service<(Request, Framed<T, h1::Codec>)> for WsService
where where
T: AsyncRead + AsyncWrite + Unpin + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
type Response = (); type Response = ();
type Error = Error; type Error = WsServiceError;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
@ -56,7 +85,9 @@ where
let framed = framed.replace_codec(ws::Codec::new()); let framed = framed.replace_codec(ws::Codec::new());
ws::Dispatcher::with(framed, service).await?; ws::Dispatcher::with(framed, service)
.await
.map_err(|_| WsServiceError::Dispatcher)?;
Ok(()) Ok(())
}) })
@ -72,7 +103,7 @@ async fn service(msg: Frame) -> Result<Message, Error> {
Frame::Binary(bin) => Message::Binary(bin), Frame::Binary(bin) => Message::Binary(bin),
Frame::Continuation(item) => Message::Continuation(item), Frame::Continuation(item) => Message::Continuation(item),
Frame::Close(reason) => Message::Close(reason), Frame::Close(reason) => Message::Close(reason),
_ => return Err(Error::from(ws::ProtocolError::BadOpCode)), _ => return Err(ws::ProtocolError::BadOpCode.into()),
}; };
Ok(msg) Ok(msg)
@ -82,8 +113,10 @@ async fn service(msg: Frame) -> Result<Message, Error> {
async fn test_simple() { async fn test_simple() {
let mut srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.upgrade(fn_factory(|| async { Ok::<_, ()>(WsService::new()) })) .upgrade(fn_factory(|| async {
.finish(|_| async { Ok::<_, ()>(Response::not_found()) }) Ok::<_, Infallible>(WsService::new())
}))
.finish(|_| async { Ok::<_, Infallible>(Response::not_found()) })
.tcp() .tcp()
}) })
.await; .await;

View File

@ -31,7 +31,7 @@ extern crate tls_openssl as openssl;
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
extern crate tls_rustls as rustls; extern crate tls_rustls as rustls;
use std::{fmt, net, sync::mpsc, thread, time}; use std::{error::Error as StdError, fmt, net, sync::mpsc, thread, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
pub use actix_http::test::TestBuffer; pub use actix_http::test::TestBuffer;
@ -39,7 +39,7 @@ use actix_http::{
http::{HeaderMap, Method}, http::{HeaderMap, Method},
ws, HttpService, Request, Response, ws, HttpService, Request, Response,
}; };
use actix_service::{map_config, IntoServiceFactory, ServiceFactory}; use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
use actix_web::{ use actix_web::{
dev::{AppConfig, MessageBody, Server, Service}, dev::{AppConfig, MessageBody, Server, Service},
rt, web, Error, rt, web, Error,
@ -86,7 +86,7 @@ where
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
start_with(TestServerConfig::default(), factory) start_with(TestServerConfig::default(), factory)
} }
@ -126,7 +126,7 @@ where
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
@ -153,25 +153,40 @@ where
HttpVer::Http1 => builder.listen("test", tcp, move || { HttpVer::Http1 => builder.listen("test", tcp, move || {
let app_cfg = let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build() HttpService::build()
.client_timeout(timeout) .client_timeout(timeout)
.h1(map_config(factory(), move |_| app_cfg.clone())) .h1(map_config(fac, move |_| app_cfg.clone()))
.tcp() .tcp()
}), }),
HttpVer::Http2 => builder.listen("test", tcp, move || { HttpVer::Http2 => builder.listen("test", tcp, move || {
let app_cfg = let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build() HttpService::build()
.client_timeout(timeout) .client_timeout(timeout)
.h2(map_config(factory(), move |_| app_cfg.clone())) .h2(map_config(fac, move |_| app_cfg.clone()))
.tcp() .tcp()
}), }),
HttpVer::Both => builder.listen("test", tcp, move || { HttpVer::Both => builder.listen("test", tcp, move || {
let app_cfg = let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build() HttpService::build()
.client_timeout(timeout) .client_timeout(timeout)
.finish(map_config(factory(), move |_| app_cfg.clone())) .finish(map_config(fac, move |_| app_cfg.clone()))
.tcp() .tcp()
}), }),
}, },
@ -180,25 +195,40 @@ where
HttpVer::Http1 => builder.listen("test", tcp, move || { HttpVer::Http1 => builder.listen("test", tcp, move || {
let app_cfg = let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build() HttpService::build()
.client_timeout(timeout) .client_timeout(timeout)
.h1(map_config(factory(), move |_| app_cfg.clone())) .h1(map_config(fac, move |_| app_cfg.clone()))
.openssl(acceptor.clone()) .openssl(acceptor.clone())
}), }),
HttpVer::Http2 => builder.listen("test", tcp, move || { HttpVer::Http2 => builder.listen("test", tcp, move || {
let app_cfg = let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build() HttpService::build()
.client_timeout(timeout) .client_timeout(timeout)
.h2(map_config(factory(), move |_| app_cfg.clone())) .h2(map_config(fac, move |_| app_cfg.clone()))
.openssl(acceptor.clone()) .openssl(acceptor.clone())
}), }),
HttpVer::Both => builder.listen("test", tcp, move || { HttpVer::Both => builder.listen("test", tcp, move || {
let app_cfg = let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build() HttpService::build()
.client_timeout(timeout) .client_timeout(timeout)
.finish(map_config(factory(), move |_| app_cfg.clone())) .finish(map_config(fac, move |_| app_cfg.clone()))
.openssl(acceptor.clone()) .openssl(acceptor.clone())
}), }),
}, },
@ -207,25 +237,40 @@ where
HttpVer::Http1 => builder.listen("test", tcp, move || { HttpVer::Http1 => builder.listen("test", tcp, move || {
let app_cfg = let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build() HttpService::build()
.client_timeout(timeout) .client_timeout(timeout)
.h1(map_config(factory(), move |_| app_cfg.clone())) .h1(map_config(fac, move |_| app_cfg.clone()))
.rustls(config.clone()) .rustls(config.clone())
}), }),
HttpVer::Http2 => builder.listen("test", tcp, move || { HttpVer::Http2 => builder.listen("test", tcp, move || {
let app_cfg = let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build() HttpService::build()
.client_timeout(timeout) .client_timeout(timeout)
.h2(map_config(factory(), move |_| app_cfg.clone())) .h2(map_config(fac, move |_| app_cfg.clone()))
.rustls(config.clone()) .rustls(config.clone())
}), }),
HttpVer::Both => builder.listen("test", tcp, move || { HttpVer::Both => builder.listen("test", tcp, move || {
let app_cfg = let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build() HttpService::build()
.client_timeout(timeout) .client_timeout(timeout)
.finish(map_config(factory(), move |_| app_cfg.clone())) .finish(map_config(fac, move |_| app_cfg.clone()))
.rustls(config.clone()) .rustls(config.clone())
}), }),
}, },

View File

@ -22,10 +22,11 @@ use actix_http::{
http::HeaderValue, http::HeaderValue,
ws::{hash_key, Codec}, ws::{hash_key, Codec},
}; };
use actix_web::error::{Error, PayloadError}; use actix_web::{
use actix_web::http::{header, Method, StatusCode}; error::{Error, PayloadError},
use actix_web::HttpResponseBuilder; http::{header, Method, StatusCode},
use actix_web::{HttpRequest, HttpResponse}; HttpRequest, HttpResponse, HttpResponseBuilder,
};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use bytestring::ByteString; use bytestring::ByteString;
use futures_core::Stream; use futures_core::Stream;

View File

@ -1,7 +1,7 @@
use actix_http::Error; use std::error::Error as StdError;
#[actix_web::main] #[actix_web::main]
async fn main() -> Result<(), Error> { async fn main() -> Result<(), Box<dyn StdError>> {
std::env::set_var("RUST_LOG", "actix_http=trace"); std::env::set_var("RUST_LOG", "actix_http=trace");
env_logger::init(); env_logger::init();

View File

@ -6,7 +6,6 @@ pub use actix_http::http::Error as HttpError;
pub use actix_http::ws::HandshakeError as WsHandshakeError; pub use actix_http::ws::HandshakeError as WsHandshakeError;
pub use actix_http::ws::ProtocolError as WsProtocolError; pub use actix_http::ws::ProtocolError as WsProtocolError;
use actix_http::ResponseError;
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use actix_http::http::{header::HeaderValue, StatusCode}; use actix_http::http::{header::HeaderValue, StatusCode};
@ -77,6 +76,3 @@ pub enum JsonPayloadError {
} }
impl std::error::Error for JsonPayloadError {} impl std::error::Error for JsonPayloadError {}
/// Return `InternalServerError` for `JsonPayloadError`
impl ResponseError for JsonPayloadError {}

View File

@ -1,21 +1,21 @@
use std::convert::TryFrom; use std::{convert::TryFrom, error::Error as StdError, net, rc::Rc, time::Duration};
use std::net;
use std::rc::Rc;
use std::time::Duration;
use bytes::Bytes; use bytes::Bytes;
use futures_core::Stream; use futures_core::Stream;
use serde::Serialize; use serde::Serialize;
use actix_http::body::Body; use actix_http::{
use actix_http::http::header::IntoHeaderValue; body::Body,
use actix_http::http::{Error as HttpError, HeaderMap, HeaderName, Method, Uri}; http::{header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, Method, Uri},
use actix_http::{Error, RequestHead}; RequestHead,
};
use crate::sender::{RequestSender, SendClientRequest}; use crate::{
use crate::ClientConfig; sender::{RequestSender, SendClientRequest},
ClientConfig,
};
/// `FrozenClientRequest` struct represents clonable client request. /// `FrozenClientRequest` struct represents cloneable client request.
/// It could be used to send same request multiple times. /// It could be used to send same request multiple times.
#[derive(Clone)] #[derive(Clone)]
pub struct FrozenClientRequest { pub struct FrozenClientRequest {
@ -82,7 +82,7 @@ impl FrozenClientRequest {
pub fn send_stream<S, E>(&self, stream: S) -> SendClientRequest pub fn send_stream<S, E>(&self, stream: S) -> SendClientRequest
where where
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static, S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
E: Into<Error> + 'static, E: Into<Box<dyn StdError>> + 'static,
{ {
RequestSender::Rc(self.head.clone(), None).send_stream( RequestSender::Rc(self.head.clone(), None).send_stream(
self.addr, self.addr,
@ -207,7 +207,7 @@ impl FrozenSendBuilder {
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
where where
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static, S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
E: Into<Error> + 'static, E: Into<Box<dyn StdError>> + 'static,
{ {
if let Some(e) = self.err { if let Some(e) = self.err {
return e.into(); return e.into();

View File

@ -128,8 +128,7 @@ pub use self::sender::SendClientRequest;
/// An asynchronous HTTP and WebSocket client. /// An asynchronous HTTP and WebSocket client.
/// ///
/// ## Examples /// # Examples
///
/// ``` /// ```
/// use awc::Client; /// use awc::Client;
/// ///

View File

@ -1,25 +1,26 @@
use std::convert::TryFrom; use std::{convert::TryFrom, error::Error as StdError, fmt, net, rc::Rc, time::Duration};
use std::rc::Rc;
use std::time::Duration;
use std::{fmt, net};
use bytes::Bytes; use bytes::Bytes;
use futures_core::Stream; use futures_core::Stream;
use serde::Serialize; use serde::Serialize;
use actix_http::body::Body; use actix_http::{
use actix_http::http::header::{self, IntoHeaderPair}; body::Body,
use actix_http::http::{ http::{
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, header::{self, IntoHeaderPair},
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version,
},
RequestHead,
}; };
use actix_http::{Error, RequestHead};
#[cfg(feature = "cookies")] #[cfg(feature = "cookies")]
use crate::cookie::{Cookie, CookieJar}; use crate::cookie::{Cookie, CookieJar};
use crate::error::{FreezeRequestError, InvalidUrl}; use crate::{
use crate::frozen::FrozenClientRequest; error::{FreezeRequestError, InvalidUrl},
use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest}; frozen::FrozenClientRequest,
use crate::ClientConfig; sender::{PrepForSendingError, RequestSender, SendClientRequest},
ClientConfig,
};
#[cfg(feature = "compress")] #[cfg(feature = "compress")]
const HTTPS_ENCODING: &str = "br, gzip, deflate"; const HTTPS_ENCODING: &str = "br, gzip, deflate";
@ -408,7 +409,7 @@ impl ClientRequest {
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
where where
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static, S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
E: Into<Error> + 'static, E: Into<Box<dyn StdError>> + 'static,
{ {
let slf = match self.prep_for_sending() { let slf = match self.prep_for_sending() {
Ok(slf) => slf, Ok(slf) => slf,

View File

@ -1,6 +1,7 @@
use std::{ use std::{
error::Error as StdError,
future::Future, future::Future,
io, net, net,
pin::Pin, pin::Pin,
rc::Rc, rc::Rc,
task::{Context, Poll}, task::{Context, Poll},
@ -24,22 +25,30 @@ use serde::Serialize;
#[cfg(feature = "compress")] #[cfg(feature = "compress")]
use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream}; use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream};
use crate::connect::{ConnectRequest, ConnectResponse}; use crate::{
use crate::error::{FreezeRequestError, InvalidUrl, SendRequestError}; error::{FreezeRequestError, InvalidUrl, SendRequestError},
use crate::response::ClientResponse; ClientConfig, ClientResponse, ConnectRequest, ConnectResponse,
use crate::ClientConfig; };
#[derive(Debug, From)] #[derive(Debug, From)]
pub(crate) enum PrepForSendingError { pub(crate) enum PrepForSendingError {
Url(InvalidUrl), Url(InvalidUrl),
Http(HttpError), Http(HttpError),
Json(serde_json::Error),
Form(serde_urlencoded::ser::Error),
} }
impl From<PrepForSendingError> for FreezeRequestError { impl From<PrepForSendingError> for FreezeRequestError {
fn from(err: PrepForSendingError) -> FreezeRequestError { fn from(err: PrepForSendingError) -> FreezeRequestError {
match err { match err {
PrepForSendingError::Url(e) => FreezeRequestError::Url(e), PrepForSendingError::Url(err) => FreezeRequestError::Url(err),
PrepForSendingError::Http(e) => FreezeRequestError::Http(e), PrepForSendingError::Http(err) => FreezeRequestError::Http(err),
PrepForSendingError::Json(err) => {
FreezeRequestError::Custom(Box::new(err), Box::new("json serialization error"))
}
PrepForSendingError::Form(err) => {
FreezeRequestError::Custom(Box::new(err), Box::new("form serialization error"))
}
} }
} }
} }
@ -49,6 +58,12 @@ impl From<PrepForSendingError> for SendRequestError {
match err { match err {
PrepForSendingError::Url(e) => SendRequestError::Url(e), PrepForSendingError::Url(e) => SendRequestError::Url(e),
PrepForSendingError::Http(e) => SendRequestError::Http(e), PrepForSendingError::Http(e) => SendRequestError::Http(e),
PrepForSendingError::Json(err) => {
SendRequestError::Custom(Box::new(err), Box::new("json serialization error"))
}
PrepForSendingError::Form(err) => {
SendRequestError::Custom(Box::new(err), Box::new("form serialization error"))
}
} }
} }
} }
@ -209,8 +224,7 @@ impl RequestSender {
) -> SendClientRequest { ) -> SendClientRequest {
let body = match serde_json::to_string(value) { let body = match serde_json::to_string(value) {
Ok(body) => body, Ok(body) => body,
// TODO: own error type Err(err) => return PrepForSendingError::Json(err).into(),
Err(e) => return Error::from(io::Error::new(io::ErrorKind::Other, e)).into(),
}; };
if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") { if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") {
@ -236,8 +250,7 @@ impl RequestSender {
) -> SendClientRequest { ) -> SendClientRequest {
let body = match serde_urlencoded::to_string(value) { let body = match serde_urlencoded::to_string(value) {
Ok(body) => body, Ok(body) => body,
// TODO: own error type Err(err) => return PrepForSendingError::Form(err).into(),
Err(e) => return Error::from(io::Error::new(io::ErrorKind::Other, e)).into(),
}; };
// set content-type // set content-type
@ -266,7 +279,7 @@ impl RequestSender {
) -> SendClientRequest ) -> SendClientRequest
where where
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static, S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
E: Into<Error> + 'static, E: Into<Box<dyn StdError>> + 'static,
{ {
self.send_body( self.send_body(
addr, addr,

View File

@ -1,12 +1,13 @@
use std::{any::type_name, ops::Deref, sync::Arc}; use std::{any::type_name, ops::Deref, sync::Arc};
use actix_http::{error::Error, Extensions}; use actix_http::Extensions;
use actix_utils::future::{err, ok, Ready}; use actix_utils::future::{err, ok, Ready};
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use serde::Serialize; use serde::Serialize;
use crate::{ use crate::{
dev::Payload, error::ErrorInternalServerError, extract::FromRequest, request::HttpRequest, dev::Payload, error::ErrorInternalServerError, extract::FromRequest, request::HttpRequest,
Error,
}; };
/// Data factory. /// Data factory.

76
src/error/error.rs Normal file
View File

@ -0,0 +1,76 @@
use std::{error::Error as StdError, fmt};
use actix_http::{body::AnyBody, Response};
use crate::{HttpResponse, ResponseError};
/// 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>,
}
impl Error {
/// Returns the reference to the underlying `ResponseError`.
pub fn as_response_error(&self) -> &dyn ResponseError {
self.cause.as_ref()
}
/// 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())
}
/// Shortcut for creating an `HttpResponse`.
pub fn error_response(&self) -> HttpResponse {
self.cause.error_response()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.cause, f)
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", &self.cause)
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
// TODO: populate if replacement for Box<dyn Error> is found
None
}
}
impl From<std::convert::Infallible> for Error {
fn from(val: std::convert::Infallible) -> Self {
match val {}
}
}
/// `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<Error> for Response<AnyBody> {
fn from(err: Error) -> Response<AnyBody> {
err.error_response().into()
}
}

View File

@ -1,9 +1,9 @@
use std::{cell::RefCell, fmt, io::Write as _}; use std::{cell::RefCell, fmt, io::Write as _};
use actix_http::{body::Body, header, Response, StatusCode}; use actix_http::{body::Body, header, StatusCode};
use bytes::{BufMut as _, BytesMut}; use bytes::{BufMut as _, BytesMut};
use crate::{Error, HttpResponse, ResponseError}; use crate::{Error, HttpRequest, HttpResponse, Responder, ResponseError};
/// Wraps errors to alter the generated response status code. /// Wraps errors to alter the generated response status code.
/// ///
@ -77,10 +77,10 @@ where
} }
} }
fn error_response(&self) -> Response<Body> { fn error_response(&self) -> HttpResponse {
match self.status { match self.status {
InternalErrorType::Status(status) => { InternalErrorType::Status(status) => {
let mut res = Response::new(status); let mut res = HttpResponse::new(status);
let mut buf = BytesMut::new().writer(); let mut buf = BytesMut::new().writer();
let _ = write!(buf, "{}", self); let _ = write!(buf, "{}", self);
@ -88,20 +88,29 @@ where
header::CONTENT_TYPE, header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain; charset=utf-8"), header::HeaderValue::from_static("text/plain; charset=utf-8"),
); );
res.set_body(Body::from(buf.into_inner())).into() res.set_body(Body::from(buf.into_inner()))
} }
InternalErrorType::Response(ref resp) => { InternalErrorType::Response(ref resp) => {
if let Some(resp) = resp.borrow_mut().take() { if let Some(resp) = resp.borrow_mut().take() {
resp.into() resp
} else { } else {
Response::new(StatusCode::INTERNAL_SERVER_ERROR) HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
} }
} }
} }
} }
} }
impl<T> Responder for InternalError<T>
where
T: fmt::Debug + fmt::Display + 'static,
{
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
HttpResponse::from_error(self)
}
}
macro_rules! error_helper { macro_rules! error_helper {
($name:ident, $status:ident) => { ($name:ident, $status:ident) => {
paste::paste! { paste::paste! {
@ -171,134 +180,134 @@ error_helper!(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_http::{error::ParseError, Response}; use actix_http::error::ParseError;
use super::*; use super::*;
#[test] #[test]
fn test_internal_error() { fn test_internal_error() {
let err = InternalError::from_response(ParseError::Method, HttpResponse::Ok().finish()); let err = InternalError::from_response(ParseError::Method, HttpResponse::Ok().finish());
let resp: Response<Body> = err.error_response(); let resp: HttpResponse = err.error_response();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test] #[test]
fn test_error_helpers() { fn test_error_helpers() {
let res: Response<Body> = ErrorBadRequest("err").into(); let res: HttpResponse = ErrorBadRequest("err").into();
assert_eq!(res.status(), StatusCode::BAD_REQUEST); assert_eq!(res.status(), StatusCode::BAD_REQUEST);
let res: Response<Body> = ErrorUnauthorized("err").into(); let res: HttpResponse = ErrorUnauthorized("err").into();
assert_eq!(res.status(), StatusCode::UNAUTHORIZED); assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
let res: Response<Body> = ErrorPaymentRequired("err").into(); let res: HttpResponse = ErrorPaymentRequired("err").into();
assert_eq!(res.status(), StatusCode::PAYMENT_REQUIRED); assert_eq!(res.status(), StatusCode::PAYMENT_REQUIRED);
let res: Response<Body> = ErrorForbidden("err").into(); let res: HttpResponse = ErrorForbidden("err").into();
assert_eq!(res.status(), StatusCode::FORBIDDEN); assert_eq!(res.status(), StatusCode::FORBIDDEN);
let res: Response<Body> = ErrorNotFound("err").into(); let res: HttpResponse = ErrorNotFound("err").into();
assert_eq!(res.status(), StatusCode::NOT_FOUND); assert_eq!(res.status(), StatusCode::NOT_FOUND);
let res: Response<Body> = ErrorMethodNotAllowed("err").into(); let res: HttpResponse = ErrorMethodNotAllowed("err").into();
assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED);
let res: Response<Body> = ErrorNotAcceptable("err").into(); let res: HttpResponse = ErrorNotAcceptable("err").into();
assert_eq!(res.status(), StatusCode::NOT_ACCEPTABLE); assert_eq!(res.status(), StatusCode::NOT_ACCEPTABLE);
let res: Response<Body> = ErrorProxyAuthenticationRequired("err").into(); let res: HttpResponse = ErrorProxyAuthenticationRequired("err").into();
assert_eq!(res.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED); assert_eq!(res.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED);
let res: Response<Body> = ErrorRequestTimeout("err").into(); let res: HttpResponse = ErrorRequestTimeout("err").into();
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT); assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
let res: Response<Body> = ErrorConflict("err").into(); let res: HttpResponse = ErrorConflict("err").into();
assert_eq!(res.status(), StatusCode::CONFLICT); assert_eq!(res.status(), StatusCode::CONFLICT);
let res: Response<Body> = ErrorGone("err").into(); let res: HttpResponse = ErrorGone("err").into();
assert_eq!(res.status(), StatusCode::GONE); assert_eq!(res.status(), StatusCode::GONE);
let res: Response<Body> = ErrorLengthRequired("err").into(); let res: HttpResponse = ErrorLengthRequired("err").into();
assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED); assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED);
let res: Response<Body> = ErrorPreconditionFailed("err").into(); let res: HttpResponse = ErrorPreconditionFailed("err").into();
assert_eq!(res.status(), StatusCode::PRECONDITION_FAILED); assert_eq!(res.status(), StatusCode::PRECONDITION_FAILED);
let res: Response<Body> = ErrorPayloadTooLarge("err").into(); let res: HttpResponse = ErrorPayloadTooLarge("err").into();
assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE); assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
let res: Response<Body> = ErrorUriTooLong("err").into(); let res: HttpResponse = ErrorUriTooLong("err").into();
assert_eq!(res.status(), StatusCode::URI_TOO_LONG); assert_eq!(res.status(), StatusCode::URI_TOO_LONG);
let res: Response<Body> = ErrorUnsupportedMediaType("err").into(); let res: HttpResponse = ErrorUnsupportedMediaType("err").into();
assert_eq!(res.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); assert_eq!(res.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
let res: Response<Body> = ErrorRangeNotSatisfiable("err").into(); let res: HttpResponse = ErrorRangeNotSatisfiable("err").into();
assert_eq!(res.status(), StatusCode::RANGE_NOT_SATISFIABLE); assert_eq!(res.status(), StatusCode::RANGE_NOT_SATISFIABLE);
let res: Response<Body> = ErrorExpectationFailed("err").into(); let res: HttpResponse = ErrorExpectationFailed("err").into();
assert_eq!(res.status(), StatusCode::EXPECTATION_FAILED); assert_eq!(res.status(), StatusCode::EXPECTATION_FAILED);
let res: Response<Body> = ErrorImATeapot("err").into(); let res: HttpResponse = ErrorImATeapot("err").into();
assert_eq!(res.status(), StatusCode::IM_A_TEAPOT); assert_eq!(res.status(), StatusCode::IM_A_TEAPOT);
let res: Response<Body> = ErrorMisdirectedRequest("err").into(); let res: HttpResponse = ErrorMisdirectedRequest("err").into();
assert_eq!(res.status(), StatusCode::MISDIRECTED_REQUEST); assert_eq!(res.status(), StatusCode::MISDIRECTED_REQUEST);
let res: Response<Body> = ErrorUnprocessableEntity("err").into(); let res: HttpResponse = ErrorUnprocessableEntity("err").into();
assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY); assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY);
let res: Response<Body> = ErrorLocked("err").into(); let res: HttpResponse = ErrorLocked("err").into();
assert_eq!(res.status(), StatusCode::LOCKED); assert_eq!(res.status(), StatusCode::LOCKED);
let res: Response<Body> = ErrorFailedDependency("err").into(); let res: HttpResponse = ErrorFailedDependency("err").into();
assert_eq!(res.status(), StatusCode::FAILED_DEPENDENCY); assert_eq!(res.status(), StatusCode::FAILED_DEPENDENCY);
let res: Response<Body> = ErrorUpgradeRequired("err").into(); let res: HttpResponse = ErrorUpgradeRequired("err").into();
assert_eq!(res.status(), StatusCode::UPGRADE_REQUIRED); assert_eq!(res.status(), StatusCode::UPGRADE_REQUIRED);
let res: Response<Body> = ErrorPreconditionRequired("err").into(); let res: HttpResponse = ErrorPreconditionRequired("err").into();
assert_eq!(res.status(), StatusCode::PRECONDITION_REQUIRED); assert_eq!(res.status(), StatusCode::PRECONDITION_REQUIRED);
let res: Response<Body> = ErrorTooManyRequests("err").into(); let res: HttpResponse = ErrorTooManyRequests("err").into();
assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS); assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS);
let res: Response<Body> = ErrorRequestHeaderFieldsTooLarge("err").into(); let res: HttpResponse = ErrorRequestHeaderFieldsTooLarge("err").into();
assert_eq!(res.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); assert_eq!(res.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE);
let res: Response<Body> = ErrorUnavailableForLegalReasons("err").into(); let res: HttpResponse = ErrorUnavailableForLegalReasons("err").into();
assert_eq!(res.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); assert_eq!(res.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS);
let res: Response<Body> = ErrorInternalServerError("err").into(); let res: HttpResponse = ErrorInternalServerError("err").into();
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
let res: Response<Body> = ErrorNotImplemented("err").into(); let res: HttpResponse = ErrorNotImplemented("err").into();
assert_eq!(res.status(), StatusCode::NOT_IMPLEMENTED); assert_eq!(res.status(), StatusCode::NOT_IMPLEMENTED);
let res: Response<Body> = ErrorBadGateway("err").into(); let res: HttpResponse = ErrorBadGateway("err").into();
assert_eq!(res.status(), StatusCode::BAD_GATEWAY); assert_eq!(res.status(), StatusCode::BAD_GATEWAY);
let res: Response<Body> = ErrorServiceUnavailable("err").into(); let res: HttpResponse = ErrorServiceUnavailable("err").into();
assert_eq!(res.status(), StatusCode::SERVICE_UNAVAILABLE); assert_eq!(res.status(), StatusCode::SERVICE_UNAVAILABLE);
let res: Response<Body> = ErrorGatewayTimeout("err").into(); let res: HttpResponse = ErrorGatewayTimeout("err").into();
assert_eq!(res.status(), StatusCode::GATEWAY_TIMEOUT); assert_eq!(res.status(), StatusCode::GATEWAY_TIMEOUT);
let res: Response<Body> = ErrorHttpVersionNotSupported("err").into(); let res: HttpResponse = ErrorHttpVersionNotSupported("err").into();
assert_eq!(res.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED); assert_eq!(res.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED);
let res: Response<Body> = ErrorVariantAlsoNegotiates("err").into(); let res: HttpResponse = ErrorVariantAlsoNegotiates("err").into();
assert_eq!(res.status(), StatusCode::VARIANT_ALSO_NEGOTIATES); assert_eq!(res.status(), StatusCode::VARIANT_ALSO_NEGOTIATES);
let res: Response<Body> = ErrorInsufficientStorage("err").into(); let res: HttpResponse = ErrorInsufficientStorage("err").into();
assert_eq!(res.status(), StatusCode::INSUFFICIENT_STORAGE); assert_eq!(res.status(), StatusCode::INSUFFICIENT_STORAGE);
let res: Response<Body> = ErrorLoopDetected("err").into(); let res: HttpResponse = ErrorLoopDetected("err").into();
assert_eq!(res.status(), StatusCode::LOOP_DETECTED); assert_eq!(res.status(), StatusCode::LOOP_DETECTED);
let res: Response<Body> = ErrorNotExtended("err").into(); let res: HttpResponse = ErrorNotExtended("err").into();
assert_eq!(res.status(), StatusCode::NOT_EXTENDED); assert_eq!(res.status(), StatusCode::NOT_EXTENDED);
let res: Response<Body> = ErrorNetworkAuthenticationRequired("err").into(); let res: HttpResponse = ErrorNetworkAuthenticationRequired("err").into();
assert_eq!(res.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED); assert_eq!(res.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED);
} }
} }

109
src/error/macros.rs Normal file
View File

@ -0,0 +1,109 @@
#[macro_export]
#[doc(hidden)]
macro_rules! __downcast_get_type_id {
() => {
/// A helper method to get the type ID of the type
/// this trait is implemented on.
/// This method is unsafe to *implement*, since `downcast_ref` relies
/// on the returned `TypeId` to perform a cast.
///
/// Unfortunately, Rust has no notion of a trait method that is
/// unsafe to implement (marking it as `unsafe` makes it unsafe
/// to *call*). As a workaround, we require this method
/// to return a private type along with the `TypeId`. This
/// private type (`PrivateHelper`) has a private constructor,
/// making it impossible for safe code to construct outside of
/// this module. This ensures that safe code cannot violate
/// type-safety by implementing this method.
///
/// We also take `PrivateHelper` as a parameter, to ensure that
/// safe code cannot obtain a `PrivateHelper` instance by
/// delegating to an existing implementation of `__private_get_type_id__`
#[doc(hidden)]
#[allow(dead_code)]
fn __private_get_type_id__(&self, _: PrivateHelper) -> (std::any::TypeId, PrivateHelper)
where
Self: 'static,
{
(std::any::TypeId::of::<Self>(), PrivateHelper(()))
}
};
}
//Generate implementation for dyn $name
#[doc(hidden)]
#[macro_export]
macro_rules! __downcast_dyn {
($name:ident) => {
/// A struct with a private constructor, for use with
/// `__private_get_type_id__`. Its single field is private,
/// ensuring that it can only be constructed from this module
#[doc(hidden)]
#[allow(dead_code)]
pub struct PrivateHelper(());
impl dyn $name + 'static {
/// Downcasts generic body to a specific type.
#[allow(dead_code)]
pub fn downcast_ref<T: $name + 'static>(&self) -> Option<&T> {
if self.__private_get_type_id__(PrivateHelper(())).0
== std::any::TypeId::of::<T>()
{
// SAFETY: external crates cannot override the default
// implementation of `__private_get_type_id__`, since
// it requires returning a private type. We can therefore
// rely on the returned `TypeId`, which ensures that this
// case is correct.
unsafe { Some(&*(self as *const dyn $name as *const T)) }
} else {
None
}
}
/// Downcasts a generic body to a mutable specific type.
#[allow(dead_code)]
pub fn downcast_mut<T: $name + 'static>(&mut self) -> Option<&mut T> {
if self.__private_get_type_id__(PrivateHelper(())).0
== std::any::TypeId::of::<T>()
{
// SAFETY: external crates cannot override the default
// implementation of `__private_get_type_id__`, since
// it requires returning a private type. We can therefore
// rely on the returned `TypeId`, which ensures that this
// case is correct.
unsafe { Some(&mut *(self as *const dyn $name as *const T as *mut T)) }
} else {
None
}
}
}
};
}
#[cfg(test)]
mod tests {
#![allow(clippy::upper_case_acronyms)]
trait MB {
__downcast_get_type_id!();
}
__downcast_dyn!(MB);
impl MB for String {}
impl MB for () {}
#[actix_rt::test]
async fn test_any_casting() {
let mut body = String::from("hello cast");
let resp_body: &mut dyn MB = &mut body;
let body = resp_body.downcast_ref::<String>().unwrap();
assert_eq!(body, "hello cast");
let body = &mut resp_body.downcast_mut::<String>().unwrap();
body.push('!');
let body = resp_body.downcast_ref::<String>().unwrap();
assert_eq!(body, "hello cast!");
let not_body = resp_body.downcast_ref::<()>();
assert!(not_body.is_none());
}
}

View File

@ -9,14 +9,20 @@ use url::ParseError as UrlParseError;
use crate::http::StatusCode; use crate::http::StatusCode;
#[allow(clippy::module_inception)]
mod error;
mod internal; mod internal;
mod macros;
mod response_error;
pub use self::error::Error;
pub use self::internal::*; pub use self::internal::*;
pub use self::response_error::ResponseError;
/// A convenience [`Result`](std::result::Result) for Actix Web operations. /// A convenience [`Result`](std::result::Result) for Actix Web operations.
/// ///
/// This type alias is generally used to avoid writing out `actix_http::Error` directly. /// This type alias is generally used to avoid writing out `actix_http::Error` directly.
pub type Result<T, E = actix_http::Error> = std::result::Result<T, E>; pub type Result<T, E = Error> = std::result::Result<T, E>;
/// Errors which can occur when attempting to generate resource uri. /// Errors which can occur when attempting to generate resource uri.
#[derive(Debug, PartialEq, Display, Error, From)] #[derive(Debug, PartialEq, Display, Error, From)]

144
src/error/response_error.rs Normal file
View File

@ -0,0 +1,144 @@
//! `ResponseError` trait and foreign impls.
use std::{
error::Error as StdError,
fmt,
io::{self, Write as _},
};
use actix_http::{body::AnyBody, header, Response, StatusCode};
use bytes::BytesMut;
use crate::{__downcast_dyn, __downcast_get_type_id};
use crate::{helpers, HttpResponse};
/// Errors that can generate responses.
// TODO: add std::error::Error bound when replacement for Box<dyn Error> is found
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
}
/// 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) -> HttpResponse {
let mut res = HttpResponse::new(self.status_code());
let mut buf = BytesMut::new();
let _ = write!(helpers::MutWriter(&mut buf), "{}", self);
res.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain; charset=utf-8"),
);
res.set_body(AnyBody::from(buf))
}
__downcast_get_type_id!();
}
__downcast_dyn!(ResponseError);
impl ResponseError for Box<dyn StdError + 'static> {}
#[cfg(feature = "openssl")]
impl ResponseError for actix_tls::accept::openssl::SslError {}
impl ResponseError for serde::de::value::Error {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
}
impl ResponseError for std::str::Utf8Error {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
}
impl ResponseError for std::io::Error {
fn status_code(&self) -> StatusCode {
// TODO: decide if these errors should consider not found or permission errors
match self.kind() {
io::ErrorKind::NotFound => StatusCode::NOT_FOUND,
io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN,
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
impl ResponseError for actix_http::error::HttpError {}
impl ResponseError for actix_http::Error {
fn status_code(&self) -> StatusCode {
// TODO: map error kinds to status code better
StatusCode::INTERNAL_SERVER_ERROR
}
fn error_response(&self) -> HttpResponse {
HttpResponse::new(self.status_code()).set_body(self.to_string().into())
}
}
impl ResponseError for actix_http::header::InvalidHeaderValue {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
}
impl ResponseError for actix_http::error::ParseError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
}
impl ResponseError for actix_http::error::BlockingError {}
impl ResponseError for actix_http::error::PayloadError {
fn status_code(&self) -> StatusCode {
match *self {
actix_http::error::PayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE,
_ => StatusCode::BAD_REQUEST,
}
}
}
impl ResponseError for actix_http::ws::ProtocolError {}
impl ResponseError for actix_http::error::ContentTypeError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
}
impl ResponseError for actix_http::ws::HandshakeError {
fn error_response(&self) -> HttpResponse {
Response::from(self).into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_casting() {
use actix_http::error::{ContentTypeError, PayloadError};
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

@ -47,8 +47,7 @@ pub trait FromRequest: Sized {
/// ///
/// If the FromRequest for T fails, return None rather than returning an error response /// If the FromRequest for T fails, return None rather than returning an error response
/// ///
/// ## Example /// # Examples
///
/// ``` /// ```
/// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest}; /// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest};
/// use actix_web::error::ErrorBadRequest; /// use actix_web::error::ErrorBadRequest;
@ -139,8 +138,7 @@ where
/// ///
/// If the `FromRequest` for T fails, inject Err into handler rather than returning an error response /// If the `FromRequest` for T fails, inject Err into handler rather than returning an error response
/// ///
/// ## Example /// # Examples
///
/// ``` /// ```
/// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest}; /// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest};
/// use actix_web::error::ErrorBadRequest; /// use actix_web::error::ErrorBadRequest;

View File

@ -3,18 +3,14 @@ use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_http::Error;
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use actix_utils::future::{ready, Ready}; use actix_utils::future::{ready, Ready};
use futures_core::ready; use futures_core::ready;
use pin_project::pin_project; use pin_project::pin_project;
use crate::{ use crate::{
extract::FromRequest,
request::HttpRequest,
responder::Responder,
response::HttpResponse,
service::{ServiceRequest, ServiceResponse}, service::{ServiceRequest, ServiceResponse},
Error, FromRequest, HttpRequest, HttpResponse, Responder,
}; };
/// A request handler is an async function that accepts zero or more parameters that can be /// A request handler is an async function that accepts zero or more parameters that can be

25
src/helpers.rs Normal file
View File

@ -0,0 +1,25 @@
use std::io;
use bytes::BufMut;
/// 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 MutWriter<'a, B>
where
B: BufMut,
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.put_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

View File

@ -1,7 +1,6 @@
//! Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust. //! Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.
//! //!
//! ## Example //! # Examples
//!
//! ```no_run //! ```no_run
//! use actix_web::{get, web, App, HttpServer, Responder}; //! use actix_web::{get, web, App, HttpServer, Responder};
//! //!
@ -20,8 +19,7 @@
//! } //! }
//! ``` //! ```
//! //!
//! ## Documentation & Community Resources //! # Documentation & Community Resources
//!
//! In addition to this API documentation, several other resources are available: //! In addition to this API documentation, several other resources are available:
//! //!
//! * [Website & User Guide](https://actix.rs/) //! * [Website & User Guide](https://actix.rs/)
@ -44,8 +42,7 @@
//! structs represent HTTP requests and responses and expose methods for creating, inspecting, //! structs represent HTTP requests and responses and expose methods for creating, inspecting,
//! and otherwise utilizing them. //! and otherwise utilizing them.
//! //!
//! ## Features //! # Features
//!
//! * Supports *HTTP/1.x* and *HTTP/2* //! * Supports *HTTP/1.x* and *HTTP/2*
//! * Streaming and pipelining //! * Streaming and pipelining
//! * Keep-alive and slow requests handling //! * Keep-alive and slow requests handling
@ -59,8 +56,7 @@
//! * Includes an async [HTTP client](https://docs.rs/awc/) //! * Includes an async [HTTP client](https://docs.rs/awc/)
//! * Runs on stable Rust 1.46+ //! * Runs on stable Rust 1.46+
//! //!
//! ## Crate Features //! # Crate Features
//!
//! * `compress` - content encoding compression support (enabled by default) //! * `compress` - content encoding compression support (enabled by default)
//! * `cookies` - cookies support (enabled by default) //! * `cookies` - cookies support (enabled by default)
//! * `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2` //! * `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2`
@ -80,6 +76,7 @@ pub mod error;
mod extract; mod extract;
pub mod guard; pub mod guard;
mod handler; mod handler;
mod helpers;
pub mod http; pub mod http;
mod info; mod info;
pub mod middleware; pub mod middleware;
@ -98,7 +95,7 @@ pub(crate) mod types;
pub mod web; pub mod web;
pub use actix_http::Response as BaseHttpResponse; pub use actix_http::Response as BaseHttpResponse;
pub use actix_http::{body, Error, HttpMessage, ResponseError}; pub use actix_http::{body, HttpMessage};
#[doc(inline)] #[doc(inline)]
pub use actix_rt as rt; pub use actix_rt as rt;
pub use actix_web_codegen::*; pub use actix_web_codegen::*;
@ -106,7 +103,7 @@ pub use actix_web_codegen::*;
pub use cookie; pub use cookie;
pub use crate::app::App; pub use crate::app::App;
pub use crate::error::Result; pub use crate::error::{Error, ResponseError, Result};
pub use crate::extract::FromRequest; pub use crate::extract::FromRequest;
pub use crate::request::HttpRequest; pub use crate::request::HttpRequest;
pub use crate::resource::Resource; pub use crate::resource::Resource;
@ -140,7 +137,9 @@ pub mod dev {
pub use crate::types::json::JsonBody; pub use crate::types::json::JsonBody;
pub use crate::types::readlines::Readlines; pub use crate::types::readlines::Readlines;
pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody, SizedStream}; pub use actix_http::body::{
AnyBody, Body, BodySize, MessageBody, ResponseBody, SizedStream,
};
#[cfg(feature = "compress")] #[cfg(feature = "compress")]
pub use actix_http::encoding::Decoder as Decompress; pub use actix_http::encoding::Decoder as Decompress;
pub use actix_http::ResponseBuilder as BaseHttpResponseBuilder; pub use actix_http::ResponseBuilder as BaseHttpResponseBuilder;

View File

@ -50,7 +50,7 @@ where
T: Transform<S, Req>, T: Transform<S, Req>,
T::Future: 'static, T::Future: 'static,
T::Response: MapServiceResponseBody, T::Response: MapServiceResponseBody,
Error: From<T::Error>, T::Error: Into<Error>,
{ {
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
@ -75,7 +75,7 @@ impl<S, Req> Service<Req> for CompatMiddleware<S>
where where
S: Service<Req>, S: Service<Req>,
S::Response: MapServiceResponseBody, S::Response: MapServiceResponseBody,
Error: From<S::Error>, S::Error: Into<Error>,
{ {
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
@ -99,12 +99,16 @@ impl<Fut, T, E> Future for CompatMiddlewareFuture<Fut>
where where
Fut: Future<Output = Result<T, E>>, Fut: Future<Output = Result<T, E>>,
T: MapServiceResponseBody, T: MapServiceResponseBody,
Error: From<E>, E: Into<Error>,
{ {
type Output = Result<ServiceResponse, Error>; type Output = Result<ServiceResponse, Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let res = ready!(self.project().fut.poll(cx))?; let res = match ready!(self.project().fut.poll(cx)) {
Ok(res) => res,
Err(err) => return Poll::Ready(Err(err.into())),
};
Poll::Ready(Ok(res.map_body())) Poll::Ready(Ok(res.map_body()))
} }
} }

View File

@ -13,7 +13,6 @@ use actix_http::{
body::{MessageBody, ResponseBody}, body::{MessageBody, ResponseBody},
encoding::Encoder, encoding::Encoder,
http::header::{ContentEncoding, ACCEPT_ENCODING}, http::header::{ContentEncoding, ACCEPT_ENCODING},
Error,
}; };
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use actix_utils::future::{ok, Ready}; use actix_utils::future::{ok, Ready};
@ -23,6 +22,7 @@ use pin_project::pin_project;
use crate::{ use crate::{
dev::BodyEncoding, dev::BodyEncoding,
service::{ServiceRequest, ServiceResponse}, service::{ServiceRequest, ServiceResponse},
Error,
}; };
/// Middleware for compressing response payloads. /// Middleware for compressing response payloads.

View File

@ -13,8 +13,8 @@ use futures_core::{future::LocalBoxFuture, ready};
use crate::{ use crate::{
dev::{ServiceRequest, ServiceResponse}, dev::{ServiceRequest, ServiceResponse},
error::{Error, Result},
http::StatusCode, http::StatusCode,
Error, Result,
}; };
/// Return type for [`ErrorHandlers`] custom handlers. /// Return type for [`ErrorHandlers`] custom handlers.

View File

@ -7,7 +7,7 @@ use std::{
use actix_http::{ use actix_http::{
http::{HeaderMap, Method, Uri, Version}, http::{HeaderMap, Method, Uri, Version},
Error, Extensions, HttpMessage, Message, Payload, RequestHead, Extensions, HttpMessage, Message, Payload, RequestHead,
}; };
use actix_router::{Path, Url}; use actix_router::{Path, Url};
use actix_utils::future::{ok, Ready}; use actix_utils::future::{ok, Ready};
@ -17,7 +17,7 @@ use smallvec::SmallVec;
use crate::{ use crate::{
app_service::AppInitServiceState, config::AppConfig, error::UrlGenerationError, app_service::AppInitServiceState, config::AppConfig, error::UrlGenerationError,
extract::FromRequest, info::ConnectionInfo, rmap::ResourceMap, info::ConnectionInfo, rmap::ResourceMap, Error, FromRequest,
}; };
#[cfg(feature = "cookies")] #[cfg(feature = "cookies")]
@ -356,8 +356,7 @@ impl Drop for HttpRequest {
/// It is possible to get `HttpRequest` as an extractor handler parameter /// It is possible to get `HttpRequest` as an extractor handler parameter
/// ///
/// ## Example /// # Examples
///
/// ``` /// ```
/// use actix_web::{web, App, HttpRequest}; /// use actix_web::{web, App, HttpRequest};
/// use serde_derive::Deserialize; /// use serde_derive::Deserialize;

View File

@ -1,9 +1,8 @@
use std::{any::type_name, ops::Deref}; use std::{any::type_name, ops::Deref};
use actix_http::error::Error;
use actix_utils::future::{err, ok, Ready}; use actix_utils::future::{err, ok, Ready};
use crate::{dev::Payload, error::ErrorInternalServerError, FromRequest, HttpRequest}; use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, HttpRequest};
/// Request-local data extractor. /// Request-local data extractor.
/// ///

View File

@ -3,7 +3,7 @@ use std::fmt;
use std::future::Future; use std::future::Future;
use std::rc::Rc; use std::rc::Rc;
use actix_http::{Error, Extensions}; use actix_http::Extensions;
use actix_router::IntoPattern; use actix_router::IntoPattern;
use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::boxed::{self, BoxService, BoxServiceFactory};
use actix_service::{ use actix_service::{
@ -13,14 +13,16 @@ use actix_service::{
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use futures_util::future::join_all; use futures_util::future::join_all;
use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; use crate::{
use crate::extract::FromRequest; data::Data,
use crate::guard::Guard; dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef},
use crate::handler::Handler; guard::Guard,
use crate::responder::Responder; handler::Handler,
use crate::route::{Route, RouteService}; responder::Responder,
use crate::service::{ServiceRequest, ServiceResponse}; route::{Route, RouteService},
use crate::{data::Data, HttpResponse}; service::{ServiceRequest, ServiceResponse},
Error, FromRequest, HttpResponse,
};
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>; type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;

View File

@ -1,4 +1,4 @@
use std::{borrow::Cow, fmt}; use std::borrow::Cow;
use actix_http::{ use actix_http::{
body::Body, body::Body,
@ -6,7 +6,7 @@ use actix_http::{
}; };
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use crate::{error::InternalError, Error, HttpRequest, HttpResponse, HttpResponseBuilder}; use crate::{Error, HttpRequest, HttpResponse, HttpResponseBuilder};
/// Trait implemented by types that can be converted to an HTTP response. /// Trait implemented by types that can be converted to an HTTP response.
/// ///
@ -226,15 +226,6 @@ impl<T: Responder> Responder for CustomResponder<T> {
} }
} }
impl<T> Responder for InternalError<T>
where
T: fmt::Debug + fmt::Display + 'static,
{
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
HttpResponse::from_error(self.into())
}
}
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
use actix_service::Service; use actix_service::Service;

View File

@ -1,13 +1,14 @@
use std::{ use std::{
cell::{Ref, RefMut}, cell::{Ref, RefMut},
convert::TryInto, convert::TryInto,
error::Error as StdError,
future::Future, future::Future,
pin::Pin, pin::Pin,
task::{Context, Poll}, task::{Context, Poll},
}; };
use actix_http::{ use actix_http::{
body::{Body, BodyStream}, body::{AnyBody, BodyStream},
http::{ http::{
header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue}, header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue},
ConnectionType, Error as HttpError, StatusCode, ConnectionType, Error as HttpError, StatusCode,
@ -32,7 +33,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 HttpResponseBuilder { pub struct HttpResponseBuilder {
res: Option<Response<Body>>, res: Option<Response<AnyBody>>,
err: Option<HttpError>, err: Option<HttpError>,
#[cfg(feature = "cookies")] #[cfg(feature = "cookies")]
cookies: Option<CookieJar>, cookies: Option<CookieJar>,
@ -310,7 +311,7 @@ impl HttpResponseBuilder {
/// ///
/// `HttpResponseBuilder` can not be used after this call. /// `HttpResponseBuilder` can not be used after this call.
#[inline] #[inline]
pub fn body<B: Into<Body>>(&mut self, body: B) -> HttpResponse<Body> { pub fn body<B: Into<AnyBody>>(&mut self, body: B) -> HttpResponse<AnyBody> {
match self.message_body(body.into()) { match self.message_body(body.into()) {
Ok(res) => res, Ok(res) => res,
Err(err) => HttpResponse::from_error(err), Err(err) => HttpResponse::from_error(err),
@ -354,9 +355,9 @@ impl HttpResponseBuilder {
pub fn streaming<S, E>(&mut self, stream: S) -> HttpResponse pub fn streaming<S, E>(&mut self, stream: S) -> HttpResponse
where where
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static, S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
E: Into<Error> + 'static, E: Into<Box<dyn StdError>> + 'static,
{ {
self.body(Body::from_message(BodyStream::new(stream))) self.body(AnyBody::from_message(BodyStream::new(stream)))
} }
/// Set a json body and generate `Response` /// Set a json body and generate `Response`
@ -375,9 +376,9 @@ impl HttpResponseBuilder {
self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
} }
self.body(Body::from(body)) self.body(AnyBody::from(body))
} }
Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err).into()), Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)),
} }
} }
@ -386,7 +387,7 @@ impl HttpResponseBuilder {
/// `HttpResponseBuilder` can not be used after this call. /// `HttpResponseBuilder` can not be used after this call.
#[inline] #[inline]
pub fn finish(&mut self) -> HttpResponse { pub fn finish(&mut self) -> HttpResponse {
self.body(Body::Empty) self.body(AnyBody::Empty)
} }
/// This method construct new `HttpResponseBuilder` /// This method construct new `HttpResponseBuilder`
@ -415,7 +416,7 @@ impl From<HttpResponseBuilder> for HttpResponse {
} }
} }
impl From<HttpResponseBuilder> for Response<Body> { impl From<HttpResponseBuilder> for Response<AnyBody> {
fn from(mut builder: HttpResponseBuilder) -> Self { fn from(mut builder: HttpResponseBuilder) -> Self {
builder.finish().into() builder.finish().into()
} }

View File

@ -8,7 +8,7 @@ use std::{
}; };
use actix_http::{ use actix_http::{
body::{Body, MessageBody}, body::{AnyBody, Body, MessageBody},
http::{header::HeaderMap, StatusCode}, http::{header::HeaderMap, StatusCode},
Extensions, Response, ResponseHead, Extensions, Response, ResponseHead,
}; };
@ -25,12 +25,12 @@ use {
use crate::{error::Error, HttpResponseBuilder}; use crate::{error::Error, HttpResponseBuilder};
/// An HTTP Response /// An HTTP Response
pub struct HttpResponse<B = Body> { pub struct HttpResponse<B = AnyBody> {
res: Response<B>, res: Response<B>,
pub(crate) error: Option<Error>, pub(crate) error: Option<Error>,
} }
impl HttpResponse<Body> { impl HttpResponse<AnyBody> {
/// Create HTTP response builder with specific status. /// Create HTTP response builder with specific status.
#[inline] #[inline]
pub fn build(status: StatusCode) -> HttpResponseBuilder { pub fn build(status: StatusCode) -> HttpResponseBuilder {
@ -48,13 +48,8 @@ impl HttpResponse<Body> {
/// Create an error response. /// Create an error response.
#[inline] #[inline]
pub fn from_error(error: Error) -> Self { pub fn from_error(error: impl Into<Error>) -> Self {
let res = error.as_response_error().error_response(); error.into().as_response_error().error_response()
Self {
res,
error: Some(error),
}
} }
} }
@ -238,7 +233,6 @@ impl<B> HttpResponse<B> {
impl<B> fmt::Debug for HttpResponse<B> impl<B> fmt::Debug for HttpResponse<B>
where where
B: MessageBody, B: MessageBody,
B::Error: Into<Error>,
{ {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HttpResponse") f.debug_struct("HttpResponse")

View File

@ -2,19 +2,19 @@
use std::{future::Future, rc::Rc}; use std::{future::Future, rc::Rc};
use actix_http::{http::Method, Error}; use actix_http::http::Method;
use actix_service::{ use actix_service::{
boxed::{self, BoxService, BoxServiceFactory}, boxed::{self, BoxService, BoxServiceFactory},
Service, ServiceFactory, Service, ServiceFactory,
}; };
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use crate::extract::FromRequest; use crate::{
use crate::guard::{self, Guard}; guard::{self, Guard},
use crate::handler::{Handler, HandlerService}; handler::{Handler, HandlerService},
use crate::responder::Responder; service::{ServiceRequest, ServiceResponse},
use crate::service::{ServiceRequest, ServiceResponse}; Error, FromRequest, HttpResponse, Responder,
use crate::HttpResponse; };
/// Resource route definition /// Resource route definition
/// ///
@ -188,7 +188,7 @@ impl Route {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::time::Duration; use std::{convert::Infallible, time::Duration};
use actix_rt::time::sleep; use actix_rt::time::sleep;
use bytes::Bytes; use bytes::Bytes;
@ -215,7 +215,7 @@ mod tests {
})) }))
.route(web::post().to(|| async { .route(web::post().to(|| async {
sleep(Duration::from_millis(100)).await; sleep(Duration::from_millis(100)).await;
Ok::<_, ()>(HttpResponse::Created()) Ok::<_, Infallible>(HttpResponse::Created())
})) }))
.route(web::delete().to(|| async { .route(web::delete().to(|| async {
sleep(Duration::from_millis(100)).await; sleep(Duration::from_millis(100)).await;

View File

@ -1,23 +1,25 @@
use std::{ use std::{
any::Any, any::Any,
cmp, fmt, io, cmp,
error::Error as StdError,
fmt, io,
marker::PhantomData, marker::PhantomData,
net, net,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use actix_http::{ use actix_http::{body::MessageBody, Extensions, HttpService, KeepAlive, Request, Response};
body::MessageBody, Error, Extensions, HttpService, KeepAlive, Request, Response,
};
use actix_server::{Server, ServerBuilder}; use actix_server::{Server, ServerBuilder};
use actix_service::{map_config, IntoServiceFactory, Service, ServiceFactory}; use actix_service::{
map_config, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _,
};
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
use actix_tls::accept::openssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; use actix_tls::accept::openssl::{AlpnError, SslAcceptor, SslAcceptorBuilder};
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
use actix_tls::accept::rustls::ServerConfig as RustlsServerConfig; use actix_tls::accept::rustls::ServerConfig as RustlsServerConfig;
use crate::config::AppConfig; use crate::{config::AppConfig, Error};
struct Socket { struct Socket {
scheme: &'static str, scheme: &'static str,
@ -81,7 +83,7 @@ where
S::Service: 'static, S::Service: 'static,
// S::Service: 'static, // S::Service: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
B::Error: Into<Error>, B::Error: Into<Box<dyn StdError>>,
{ {
/// Create new HTTP server with application factory /// Create new HTTP server with application factory
pub fn new(factory: F) -> Self { pub fn new(factory: F) -> Self {
@ -301,7 +303,11 @@ where
svc svc
}; };
svc.finish(map_config(factory(), move |_| { let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
svc.finish(map_config(fac, move |_| {
AppConfig::new(false, host.clone(), addr) AppConfig::new(false, host.clone(), addr)
})) }))
.tcp() .tcp()
@ -356,7 +362,11 @@ where
svc svc
}; };
svc.finish(map_config(factory(), move |_| { let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
svc.finish(map_config(fac, move |_| {
AppConfig::new(true, host.clone(), addr) AppConfig::new(true, host.clone(), addr)
})) }))
.openssl(acceptor.clone()) .openssl(acceptor.clone())
@ -410,7 +420,11 @@ where
svc svc
}; };
svc.finish(map_config(factory(), move |_| { let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
svc.finish(map_config(fac, move |_| {
AppConfig::new(true, host.clone(), addr) AppConfig::new(true, host.clone(), addr)
})) }))
.rustls(config.clone()) .rustls(config.clone())
@ -533,7 +547,11 @@ where
svc svc
}; };
svc.finish(map_config(factory(), move |_| config.clone())) let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
svc.finish(map_config(fac, move |_| config.clone()))
}) })
})?; })?;
Ok(self) Ok(self)
@ -568,14 +586,20 @@ where
c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)),
socket_addr, socket_addr,
); );
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then( fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then(
HttpService::build() HttpService::build()
.keep_alive(c.keep_alive) .keep_alive(c.keep_alive)
.client_timeout(c.client_timeout) .client_timeout(c.client_timeout)
.finish(map_config(factory(), move |_| config.clone())), .finish(map_config(fac, move |_| config.clone())),
) )
}, },
)?; )?;
Ok(self) Ok(self)
} }
} }

View File

@ -2,24 +2,24 @@ use std::cell::{Ref, RefMut};
use std::rc::Rc; use std::rc::Rc;
use std::{fmt, net}; use std::{fmt, net};
use actix_http::body::{Body, MessageBody};
use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version};
use actix_http::{ use actix_http::{
Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead, body::{AnyBody, MessageBody},
http::{HeaderMap, Method, StatusCode, Uri, Version},
Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead,
}; };
use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url}; use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url};
use actix_service::{IntoServiceFactory, ServiceFactory}; use actix_service::{IntoServiceFactory, ServiceFactory};
#[cfg(feature = "cookies")] #[cfg(feature = "cookies")]
use cookie::{Cookie, ParseError as CookieParseError}; use cookie::{Cookie, ParseError as CookieParseError};
use crate::dev::insert_slash;
use crate::guard::Guard;
use crate::info::ConnectionInfo;
use crate::request::HttpRequest;
use crate::rmap::ResourceMap;
use crate::{ use crate::{
config::{AppConfig, AppService}, config::{AppConfig, AppService},
HttpResponse, dev::insert_slash,
guard::Guard,
info::ConnectionInfo,
request::HttpRequest,
rmap::ResourceMap,
Error, HttpResponse,
}; };
pub trait HttpServiceFactory { pub trait HttpServiceFactory {
@ -330,15 +330,15 @@ impl fmt::Debug for ServiceRequest {
} }
} }
pub struct ServiceResponse<B = Body> { pub struct ServiceResponse<B = AnyBody> {
request: HttpRequest, request: HttpRequest,
response: HttpResponse<B>, response: HttpResponse<B>,
} }
impl ServiceResponse<Body> { impl ServiceResponse<AnyBody> {
/// Create service response from the error /// Create service response from the error
pub fn from_err<E: Into<Error>>(err: E, request: HttpRequest) -> Self { pub fn from_err<E: Into<Error>>(err: E, request: HttpRequest) -> Self {
let response = HttpResponse::from_error(err.into()); let response = HttpResponse::from_error(err);
ServiceResponse { request, response } ServiceResponse { request, response }
} }
} }

View File

@ -188,7 +188,7 @@ impl<T: Serialize> Responder for Form<T> {
Ok(body) => HttpResponse::Ok() Ok(body) => HttpResponse::Ok()
.content_type(mime::APPLICATION_WWW_FORM_URLENCODED) .content_type(mime::APPLICATION_WWW_FORM_URLENCODED)
.body(body), .body(body),
Err(err) => HttpResponse::from_error(UrlencodedError::Serialize(err).into()), Err(err) => HttpResponse::from_error(UrlencodedError::Serialize(err)),
} }
} }
} }

View File

@ -127,7 +127,7 @@ impl<T: Serialize> Responder for Json<T> {
Ok(body) => HttpResponse::Ok() Ok(body) => HttpResponse::Ok()
.content_type(mime::APPLICATION_JSON) .content_type(mime::APPLICATION_JSON)
.body(body), .body(body),
Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err).into()), Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)),
} }
} }
} }
@ -500,7 +500,7 @@ mod tests {
}; };
let resp = let resp =
HttpResponse::BadRequest().body(serde_json::to_string(&msg).unwrap()); HttpResponse::BadRequest().body(serde_json::to_string(&msg).unwrap());
InternalError::from_response(err, resp.into()).into() InternalError::from_response(err, resp).into()
})) }))
.to_http_parts(); .to_http_parts();

View File

@ -2,14 +2,13 @@
use std::{fmt, ops, sync::Arc}; use std::{fmt, ops, sync::Arc};
use actix_http::error::Error;
use actix_router::PathDeserializer; use actix_router::PathDeserializer;
use actix_utils::future::{ready, Ready}; use actix_utils::future::{ready, Ready};
use serde::de; use serde::de;
use crate::{ use crate::{
dev::Payload, dev::Payload,
error::{ErrorNotFound, PathError}, error::{Error, ErrorNotFound, PathError},
FromRequest, HttpRequest, FromRequest, HttpRequest,
}; };
@ -296,11 +295,8 @@ mod tests {
async fn test_custom_err_handler() { async fn test_custom_err_handler() {
let (req, mut pl) = TestRequest::with_uri("/name/user1/") let (req, mut pl) = TestRequest::with_uri("/name/user1/")
.app_data(PathConfig::default().error_handler(|err, _| { .app_data(PathConfig::default().error_handler(|err, _| {
error::InternalError::from_response( error::InternalError::from_response(err, HttpResponse::Conflict().finish())
err, .into()
HttpResponse::Conflict().finish().into(),
)
.into()
})) }))
.to_http_parts(); .to_http_parts();

View File

@ -267,7 +267,7 @@ mod tests {
let req = TestRequest::with_uri("/name/user1/") let req = TestRequest::with_uri("/name/user1/")
.app_data(QueryConfig::default().error_handler(|e, _| { .app_data(QueryConfig::default().error_handler(|e, _| {
let resp = HttpResponse::UnprocessableEntity().finish(); let resp = HttpResponse::UnprocessableEntity().finish();
InternalError::from_response(e, resp.into()).into() InternalError::from_response(e, resp).into()
})) }))
.to_srv_request(); .to_srv_request();