1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-01 16:55:08 +02:00

refined error model (#2253)

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

View File

@ -1,12 +1,13 @@
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 futures_core::future::LocalBoxFuture;
use serde::Serialize;
use crate::{
dev::Payload, error::ErrorInternalServerError, extract::FromRequest, request::HttpRequest,
Error,
};
/// 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 actix_http::{body::Body, header, Response, StatusCode};
use actix_http::{body::Body, header, StatusCode};
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.
///
@ -77,10 +77,10 @@ where
}
}
fn error_response(&self) -> Response<Body> {
fn error_response(&self) -> HttpResponse {
match self.status {
InternalErrorType::Status(status) => {
let mut res = Response::new(status);
let mut res = HttpResponse::new(status);
let mut buf = BytesMut::new().writer();
let _ = write!(buf, "{}", self);
@ -88,20 +88,29 @@ where
header::CONTENT_TYPE,
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) => {
if let Some(resp) = resp.borrow_mut().take() {
resp.into()
resp
} 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 {
($name:ident, $status:ident) => {
paste::paste! {
@ -171,134 +180,134 @@ error_helper!(
#[cfg(test)]
mod tests {
use actix_http::{error::ParseError, Response};
use actix_http::error::ParseError;
use super::*;
#[test]
fn test_internal_error() {
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);
}
#[test]
fn test_error_helpers() {
let res: Response<Body> = ErrorBadRequest("err").into();
let res: HttpResponse = ErrorBadRequest("err").into();
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);
let res: Response<Body> = ErrorPaymentRequired("err").into();
let res: HttpResponse = ErrorPaymentRequired("err").into();
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);
let res: Response<Body> = ErrorNotFound("err").into();
let res: HttpResponse = ErrorNotFound("err").into();
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);
let res: Response<Body> = ErrorNotAcceptable("err").into();
let res: HttpResponse = ErrorNotAcceptable("err").into();
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);
let res: Response<Body> = ErrorRequestTimeout("err").into();
let res: HttpResponse = ErrorRequestTimeout("err").into();
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);
let res: Response<Body> = ErrorGone("err").into();
let res: HttpResponse = ErrorGone("err").into();
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);
let res: Response<Body> = ErrorPreconditionFailed("err").into();
let res: HttpResponse = ErrorPreconditionFailed("err").into();
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);
let res: Response<Body> = ErrorUriTooLong("err").into();
let res: HttpResponse = ErrorUriTooLong("err").into();
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);
let res: Response<Body> = ErrorRangeNotSatisfiable("err").into();
let res: HttpResponse = ErrorRangeNotSatisfiable("err").into();
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);
let res: Response<Body> = ErrorImATeapot("err").into();
let res: HttpResponse = ErrorImATeapot("err").into();
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);
let res: Response<Body> = ErrorUnprocessableEntity("err").into();
let res: HttpResponse = ErrorUnprocessableEntity("err").into();
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);
let res: Response<Body> = ErrorFailedDependency("err").into();
let res: HttpResponse = ErrorFailedDependency("err").into();
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);
let res: Response<Body> = ErrorPreconditionRequired("err").into();
let res: HttpResponse = ErrorPreconditionRequired("err").into();
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);
let res: Response<Body> = ErrorRequestHeaderFieldsTooLarge("err").into();
let res: HttpResponse = ErrorRequestHeaderFieldsTooLarge("err").into();
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);
let res: Response<Body> = ErrorInternalServerError("err").into();
let res: HttpResponse = ErrorInternalServerError("err").into();
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);
let res: Response<Body> = ErrorBadGateway("err").into();
let res: HttpResponse = ErrorBadGateway("err").into();
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);
let res: Response<Body> = ErrorGatewayTimeout("err").into();
let res: HttpResponse = ErrorGatewayTimeout("err").into();
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);
let res: Response<Body> = ErrorVariantAlsoNegotiates("err").into();
let res: HttpResponse = ErrorVariantAlsoNegotiates("err").into();
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);
let res: Response<Body> = ErrorLoopDetected("err").into();
let res: HttpResponse = ErrorLoopDetected("err").into();
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);
let res: Response<Body> = ErrorNetworkAuthenticationRequired("err").into();
let res: HttpResponse = ErrorNetworkAuthenticationRequired("err").into();
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;
#[allow(clippy::module_inception)]
mod error;
mod internal;
mod macros;
mod response_error;
pub use self::error::Error;
pub use self::internal::*;
pub use self::response_error::ResponseError;
/// A convenience [`Result`](std::result::Result) for Actix Web operations.
///
/// 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.
#[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
///
/// ## Example
///
/// # Examples
/// ```
/// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest};
/// 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
///
/// ## Example
///
/// # Examples
/// ```
/// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest};
/// use actix_web::error::ErrorBadRequest;

View File

@ -3,18 +3,14 @@ use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_http::Error;
use actix_service::{Service, ServiceFactory};
use actix_utils::future::{ready, Ready};
use futures_core::ready;
use pin_project::pin_project;
use crate::{
extract::FromRequest,
request::HttpRequest,
responder::Responder,
response::HttpResponse,
service::{ServiceRequest, ServiceResponse},
Error, FromRequest, HttpRequest, HttpResponse, Responder,
};
/// 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.
//!
//! ## Example
//!
//! # Examples
//! ```no_run
//! 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:
//!
//! * [Website & User Guide](https://actix.rs/)
@ -44,8 +42,7 @@
//! structs represent HTTP requests and responses and expose methods for creating, inspecting,
//! and otherwise utilizing them.
//!
//! ## Features
//!
//! # Features
//! * Supports *HTTP/1.x* and *HTTP/2*
//! * Streaming and pipelining
//! * Keep-alive and slow requests handling
@ -59,8 +56,7 @@
//! * Includes an async [HTTP client](https://docs.rs/awc/)
//! * Runs on stable Rust 1.46+
//!
//! ## Crate Features
//!
//! # Crate Features
//! * `compress` - content encoding compression support (enabled by default)
//! * `cookies` - cookies support (enabled by default)
//! * `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2`
@ -80,6 +76,7 @@ pub mod error;
mod extract;
pub mod guard;
mod handler;
mod helpers;
pub mod http;
mod info;
pub mod middleware;
@ -98,7 +95,7 @@ pub(crate) mod types;
pub mod web;
pub use actix_http::Response as BaseHttpResponse;
pub use actix_http::{body, Error, HttpMessage, ResponseError};
pub use actix_http::{body, HttpMessage};
#[doc(inline)]
pub use actix_rt as rt;
pub use actix_web_codegen::*;
@ -106,7 +103,7 @@ pub use actix_web_codegen::*;
pub use cookie;
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::request::HttpRequest;
pub use crate::resource::Resource;
@ -140,7 +137,9 @@ pub mod dev {
pub use crate::types::json::JsonBody;
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")]
pub use actix_http::encoding::Decoder as Decompress;
pub use actix_http::ResponseBuilder as BaseHttpResponseBuilder;

View File

@ -50,7 +50,7 @@ where
T: Transform<S, Req>,
T::Future: 'static,
T::Response: MapServiceResponseBody,
Error: From<T::Error>,
T::Error: Into<Error>,
{
type Response = ServiceResponse;
type Error = Error;
@ -75,7 +75,7 @@ impl<S, Req> Service<Req> for CompatMiddleware<S>
where
S: Service<Req>,
S::Response: MapServiceResponseBody,
Error: From<S::Error>,
S::Error: Into<Error>,
{
type Response = ServiceResponse;
type Error = Error;
@ -99,12 +99,16 @@ impl<Fut, T, E> Future for CompatMiddlewareFuture<Fut>
where
Fut: Future<Output = Result<T, E>>,
T: MapServiceResponseBody,
Error: From<E>,
E: Into<Error>,
{
type Output = Result<ServiceResponse, Error>;
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()))
}
}

View File

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

View File

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

View File

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

View File

@ -1,9 +1,8 @@
use std::{any::type_name, ops::Deref};
use actix_http::error::Error;
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.
///

View File

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

View File

@ -1,13 +1,14 @@
use std::{
cell::{Ref, RefMut},
convert::TryInto,
error::Error as StdError,
future::Future,
pin::Pin,
task::{Context, Poll},
};
use actix_http::{
body::{Body, BodyStream},
body::{AnyBody, BodyStream},
http::{
header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue},
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.
pub struct HttpResponseBuilder {
res: Option<Response<Body>>,
res: Option<Response<AnyBody>>,
err: Option<HttpError>,
#[cfg(feature = "cookies")]
cookies: Option<CookieJar>,
@ -310,7 +311,7 @@ impl HttpResponseBuilder {
///
/// `HttpResponseBuilder` can not be used after this call.
#[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()) {
Ok(res) => res,
Err(err) => HttpResponse::from_error(err),
@ -354,9 +355,9 @@ impl HttpResponseBuilder {
pub fn streaming<S, E>(&mut self, stream: S) -> HttpResponse
where
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`
@ -375,9 +376,9 @@ impl HttpResponseBuilder {
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.
#[inline]
pub fn finish(&mut self) -> HttpResponse {
self.body(Body::Empty)
self.body(AnyBody::Empty)
}
/// 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 {
builder.finish().into()
}

View File

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

View File

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

View File

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

View File

@ -2,24 +2,24 @@ use std::cell::{Ref, RefMut};
use std::rc::Rc;
use std::{fmt, net};
use actix_http::body::{Body, MessageBody};
use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version};
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_service::{IntoServiceFactory, ServiceFactory};
#[cfg(feature = "cookies")]
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::{
config::{AppConfig, AppService},
HttpResponse,
dev::insert_slash,
guard::Guard,
info::ConnectionInfo,
request::HttpRequest,
rmap::ResourceMap,
Error, HttpResponse,
};
pub trait HttpServiceFactory {
@ -330,15 +330,15 @@ impl fmt::Debug for ServiceRequest {
}
}
pub struct ServiceResponse<B = Body> {
pub struct ServiceResponse<B = AnyBody> {
request: HttpRequest,
response: HttpResponse<B>,
}
impl ServiceResponse<Body> {
impl ServiceResponse<AnyBody> {
/// Create service response from the error
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 }
}
}

View File

@ -188,7 +188,7 @@ impl<T: Serialize> Responder for Form<T> {
Ok(body) => HttpResponse::Ok()
.content_type(mime::APPLICATION_WWW_FORM_URLENCODED)
.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()
.content_type(mime::APPLICATION_JSON)
.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 =
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();

View File

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

View File

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