mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-27 17:52:56 +01:00
major cleanup of middleware module (#1875)
* major cleanup of middleware module * update changelog
This commit is contained in:
parent
4f5971d79e
commit
68117543ea
20
CHANGES.md
20
CHANGES.md
@ -2,25 +2,31 @@
|
|||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
### Added
|
### Added
|
||||||
* `Compat` middleware enabling generic response body/error type of middlewares
|
* `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and
|
||||||
like `Logger` and `Compress` to be used in `middleware::Condition`
|
`Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865]
|
||||||
and `Resource`, `Scope` services. [#1865]
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
|
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
|
||||||
* Bumped `rand` to `0.8`.
|
* Bumped `rand` to `0.8`.
|
||||||
* Update `rust-tls` to `0.19`. [#1813]
|
* Update `rust-tls` to `0.19`. [#1813]
|
||||||
* Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852]
|
* Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852]
|
||||||
|
* The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration
|
||||||
|
guide for implications. [#1875]
|
||||||
|
* Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875]
|
||||||
* MSRV is now 1.46.0.
|
* MSRV is now 1.46.0.
|
||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
|
||||||
[#1865]: https://github.com/actix/actix-web/pull/1865
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* added the actual parsing error to `test::read_body_json` [#1812]
|
* Added the underlying parse error to `test::read_body_json`'s panic message. [#1812]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now
|
||||||
|
exposed directly by the `middleware` module.
|
||||||
|
|
||||||
[#1812]: https://github.com/actix/actix-web/pull/1812
|
[#1812]: https://github.com/actix/actix-web/pull/1812
|
||||||
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
[#1852]: https://github.com/actix/actix-web/pull/1852
|
[#1852]: https://github.com/actix/actix-web/pull/1852
|
||||||
|
[#1865]: https://github.com/actix/actix-web/pull/1865
|
||||||
|
[#1875]: https://github.com/actix/actix-web/pull/1875
|
||||||
|
|
||||||
|
|
||||||
## 3.3.2 - 2020-12-01
|
## 3.3.2 - 2020-12-01
|
||||||
|
10
MIGRATION.md
10
MIGRATION.md
@ -1,5 +1,15 @@
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
* The default `NormalizePath` behavior now strips trailing slashes by default. This was
|
||||||
|
previously documented to be the case in v3 but the behavior now matches. The effect is that
|
||||||
|
routes defined with trailing slashes will become inaccessible when
|
||||||
|
using `NormalizePath::default()`.
|
||||||
|
|
||||||
|
Before: `#[get("/test/")`
|
||||||
|
After: `#[get("/test")`
|
||||||
|
|
||||||
|
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0
|
## 3.0.0
|
||||||
|
|
||||||
|
@ -18,8 +18,7 @@ async fn no_params() -> &'static str {
|
|||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
HttpServer::new(|| {
|
HttpServer::new(|| {
|
||||||
App::new()
|
App::new()
|
||||||
|
@ -1,41 +1,45 @@
|
|||||||
//! `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`.
|
//! For middleware documentation, see [`Compat`].
|
||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
|
|
||||||
use actix_http::body::Body;
|
use std::{
|
||||||
use actix_http::body::{MessageBody, ResponseBody};
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use actix_http::body::{Body, MessageBody, ResponseBody};
|
||||||
use actix_service::{Service, Transform};
|
use actix_service::{Service, Transform};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::{future::LocalBoxFuture, ready};
|
||||||
use futures_core::ready;
|
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::{error::Error, service::ServiceResponse};
|
||||||
use crate::service::ServiceResponse;
|
|
||||||
|
|
||||||
/// `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`.
|
/// Middleware for enabling any middleware to be used in [`Resource::wrap`](crate::Resource::wrap),
|
||||||
///
|
/// [`Scope::wrap`](crate::Scope::wrap) and [`Condition`](super::Condition).
|
||||||
///
|
|
||||||
/// ## Usage
|
|
||||||
///
|
///
|
||||||
|
/// # Usage
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_web::middleware::{Logger, Compat};
|
/// use actix_web::middleware::{Logger, Compat};
|
||||||
/// use actix_web::{App, web};
|
/// use actix_web::{App, web};
|
||||||
///
|
///
|
||||||
/// let logger = Logger::default();
|
/// let logger = Logger::default();
|
||||||
///
|
///
|
||||||
/// // this would not compile
|
/// // this would not compile because of incompatible body types
|
||||||
/// // let app = App::new().service(web::scope("scoped").wrap(logger));
|
/// // let app = App::new()
|
||||||
|
/// // .service(web::scope("scoped").wrap(logger));
|
||||||
///
|
///
|
||||||
/// // by using scoped middleware we can use logger in scope.
|
/// // by using this middleware we can use the logger on a scope
|
||||||
/// let app = App::new().service(web::scope("scoped").wrap(Compat::new(logger)));
|
/// let app = App::new()
|
||||||
|
/// .service(web::scope("scoped").wrap(Compat::new(logger)));
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Compat<T> {
|
pub struct Compat<T> {
|
||||||
transform: T,
|
transform: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Compat<T> {
|
impl<T> Compat<T> {
|
||||||
pub fn new(transform: T) -> Self {
|
/// Wrap a middleware to give it broader compatibility.
|
||||||
Self { transform }
|
pub fn new(middleware: T) -> Self {
|
||||||
|
Self {
|
||||||
|
transform: middleware,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,45 +1,47 @@
|
|||||||
//! `Middleware` for compressing response body.
|
//! For middleware documentation, see [`Compress`].
|
||||||
use std::cmp;
|
|
||||||
use std::future::Future;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
|
|
||||||
use actix_http::body::MessageBody;
|
use std::{
|
||||||
use actix_http::encoding::Encoder;
|
cmp,
|
||||||
use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING};
|
future::Future,
|
||||||
use actix_http::Error;
|
marker::PhantomData,
|
||||||
|
pin::Pin,
|
||||||
|
str::FromStr,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use actix_http::{
|
||||||
|
body::MessageBody,
|
||||||
|
encoding::Encoder,
|
||||||
|
http::header::{ContentEncoding, ACCEPT_ENCODING},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
use actix_service::{Service, Transform};
|
use actix_service::{Service, Transform};
|
||||||
use futures_util::future::{ok, Ready};
|
use futures_util::future::{ok, Ready};
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
use crate::dev::BodyEncoding;
|
use crate::{
|
||||||
use crate::service::{ServiceRequest, ServiceResponse};
|
dev::BodyEncoding,
|
||||||
|
service::{ServiceRequest, ServiceResponse},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
/// Middleware for compressing response payloads.
|
||||||
/// `Middleware` for compressing response body.
|
|
||||||
///
|
///
|
||||||
/// Use `BodyEncoding` trait for overriding response compression.
|
/// Use `BodyEncoding` trait for overriding response compression. To disable compression set
|
||||||
/// To disable compression set encoding to `ContentEncoding::Identity` value.
|
/// encoding to `ContentEncoding::Identity`.
|
||||||
///
|
///
|
||||||
|
/// # Usage
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_web::{web, middleware, App, HttpResponse};
|
/// use actix_web::{web, middleware, App, HttpResponse};
|
||||||
///
|
///
|
||||||
/// fn main() {
|
|
||||||
/// let app = App::new()
|
/// let app = App::new()
|
||||||
/// .wrap(middleware::Compress::default())
|
/// .wrap(middleware::Compress::default())
|
||||||
/// .service(
|
/// .default_service(web::to(|| HttpResponse::NotFound()));
|
||||||
/// web::resource("/test")
|
|
||||||
/// .route(web::get().to(|| HttpResponse::Ok()))
|
|
||||||
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
|
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Compress(ContentEncoding);
|
pub struct Compress(ContentEncoding);
|
||||||
|
|
||||||
impl Compress {
|
impl Compress {
|
||||||
/// Create new `Compress` middleware with default encoding.
|
/// Create new `Compress` middleware with the specified encoding.
|
||||||
pub fn new(encoding: ContentEncoding) -> Self {
|
pub fn new(encoding: ContentEncoding) -> Self {
|
||||||
Compress(encoding)
|
Compress(encoding)
|
||||||
}
|
}
|
||||||
@ -84,9 +86,7 @@ where
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = CompressResponse<S, B>;
|
type Future = CompressResponse<S, B>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
actix_service::forward_ready!(service);
|
||||||
self.service.poll_ready(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::borrow_interior_mutable_const)]
|
#[allow(clippy::borrow_interior_mutable_const)]
|
||||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||||
@ -109,7 +109,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[pin_project]
|
#[pin_project]
|
||||||
pub struct CompressResponse<S, B>
|
pub struct CompressResponse<S, B>
|
||||||
where
|
where
|
||||||
|
@ -1,35 +1,36 @@
|
|||||||
//! `Middleware` for conditionally enables another middleware.
|
//! For middleware documentation, see [`Condition`].
|
||||||
|
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use actix_service::{Service, Transform};
|
use actix_service::{Service, Transform};
|
||||||
use futures_util::future::{ok, Either, FutureExt, LocalBoxFuture};
|
use futures_util::future::{Either, FutureExt, LocalBoxFuture};
|
||||||
|
|
||||||
/// `Middleware` for conditionally enables another middleware.
|
/// Middleware for conditionally enabling other middleware.
|
||||||
/// The controlled middleware must not change the `Service` interfaces.
|
|
||||||
///
|
///
|
||||||
/// This means you cannot control such middlewares like `Logger` or `Compress` directly.
|
/// The controlled middleware must not change the `Service` interfaces. This means you cannot
|
||||||
/// *. See `Compat` middleware for alternative.
|
/// control such middlewares like `Logger` or `Compress` directly. See the [`Compat`](super::Compat)
|
||||||
///
|
/// middleware for a workaround.
|
||||||
/// ## Usage
|
|
||||||
///
|
///
|
||||||
|
/// # Usage
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_web::middleware::{Condition, NormalizePath};
|
/// use actix_web::middleware::{Condition, NormalizePath};
|
||||||
/// use actix_web::App;
|
/// use actix_web::App;
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
/// let enable_normalize = std::env::var("NORMALIZE_PATH").is_ok();
|
||||||
/// let enable_normalize = std::env::var("NORMALIZE_PATH") == Ok("true".into());
|
|
||||||
/// let app = App::new()
|
/// let app = App::new()
|
||||||
/// .wrap(Condition::new(enable_normalize, NormalizePath::default()));
|
/// .wrap(Condition::new(enable_normalize, NormalizePath::default()));
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Condition<T> {
|
pub struct Condition<T> {
|
||||||
trans: T,
|
transformer: T,
|
||||||
enable: bool,
|
enable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Condition<T> {
|
impl<T> Condition<T> {
|
||||||
pub fn new(enable: bool, trans: T) -> Self {
|
pub fn new(enable: bool, transformer: T) -> Self {
|
||||||
Self { trans, enable }
|
Self {
|
||||||
|
transformer,
|
||||||
|
enable,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,16 +50,15 @@ where
|
|||||||
|
|
||||||
fn new_transform(&self, service: S) -> Self::Future {
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
if self.enable {
|
if self.enable {
|
||||||
let f = self.trans.new_transform(service).map(|res| {
|
let fut = self.transformer.new_transform(service);
|
||||||
res.map(
|
async move {
|
||||||
ConditionMiddleware::Enable as fn(T::Transform) -> Self::Transform,
|
let wrapped_svc = fut.await?;
|
||||||
)
|
Ok(ConditionMiddleware::Enable(wrapped_svc))
|
||||||
});
|
|
||||||
Either::Left(f)
|
|
||||||
} else {
|
|
||||||
Either::Right(ok(ConditionMiddleware::Disable(service)))
|
|
||||||
}
|
}
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
|
} else {
|
||||||
|
async move { Ok(ConditionMiddleware::Disable(service)) }.boxed_local()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,18 +77,16 @@ where
|
|||||||
type Future = Either<E::Future, D::Future>;
|
type Future = Either<E::Future, D::Future>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
use ConditionMiddleware::*;
|
|
||||||
match self {
|
match self {
|
||||||
Enable(service) => service.poll_ready(cx),
|
ConditionMiddleware::Enable(service) => service.poll_ready(cx),
|
||||||
Disable(service) => service.poll_ready(cx),
|
ConditionMiddleware::Disable(service) => service.poll_ready(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: Req) -> Self::Future {
|
fn call(&mut self, req: Req) -> Self::Future {
|
||||||
use ConditionMiddleware::*;
|
|
||||||
match self {
|
match self {
|
||||||
Enable(service) => Either::Left(service.call(req)),
|
ConditionMiddleware::Enable(service) => Either::Left(service.call(req)),
|
||||||
Disable(service) => Either::Right(service.call(req)),
|
ConditionMiddleware::Disable(service) => Either::Right(service.call(req)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,14 +94,17 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use actix_service::IntoService;
|
use actix_service::IntoService;
|
||||||
|
use futures_util::future::ok;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::dev::{ServiceRequest, ServiceResponse};
|
use crate::{
|
||||||
use crate::error::Result;
|
dev::{ServiceRequest, ServiceResponse},
|
||||||
use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
|
error::Result,
|
||||||
use crate::middleware::errhandlers::*;
|
http::{header::CONTENT_TYPE, HeaderValue, StatusCode},
|
||||||
use crate::test::{self, TestRequest};
|
middleware::err_handlers::*,
|
||||||
use crate::HttpResponse;
|
test::{self, TestRequest},
|
||||||
|
HttpResponse,
|
||||||
|
};
|
||||||
|
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
fn render_500<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
fn render_500<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
||||||
|
@ -1,24 +1,34 @@
|
|||||||
//! Middleware for setting default response headers
|
//! For middleware documentation, see [`DefaultHeaders`].
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::future::Future;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
|
|
||||||
use actix_service::{Service, Transform};
|
use std::{
|
||||||
use futures_util::future::{ready, Ready};
|
convert::TryFrom,
|
||||||
use futures_util::ready;
|
future::Future,
|
||||||
|
marker::PhantomData,
|
||||||
|
pin::Pin,
|
||||||
|
rc::Rc,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
|
use futures_util::{
|
||||||
use crate::http::{Error as HttpError, HeaderMap};
|
future::{ready, Ready},
|
||||||
use crate::service::{ServiceRequest, ServiceResponse};
|
ready,
|
||||||
use crate::Error;
|
};
|
||||||
|
|
||||||
/// `Middleware` for setting default response headers.
|
use crate::{
|
||||||
|
dev::{Service, Transform},
|
||||||
|
http::{
|
||||||
|
header::{HeaderName, HeaderValue, CONTENT_TYPE},
|
||||||
|
Error as HttpError, HeaderMap,
|
||||||
|
},
|
||||||
|
service::{ServiceRequest, ServiceResponse},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Middleware for setting default response headers.
|
||||||
///
|
///
|
||||||
/// This middleware does not set header if response headers already contains it.
|
/// Headers with the same key that are already set in a response will *not* be overwritten.
|
||||||
///
|
///
|
||||||
|
/// # Usage
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_web::{web, http, middleware, App, HttpResponse};
|
/// use actix_web::{web, http, middleware, App, HttpResponse};
|
||||||
///
|
///
|
||||||
@ -38,7 +48,6 @@ pub struct DefaultHeaders {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Inner {
|
struct Inner {
|
||||||
ct: bool,
|
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +55,6 @@ impl Default for DefaultHeaders {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
DefaultHeaders {
|
DefaultHeaders {
|
||||||
inner: Rc::new(Inner {
|
inner: Rc::new(Inner {
|
||||||
ct: false,
|
|
||||||
headers: HeaderMap::new(),
|
headers: HeaderMap::new(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -54,12 +62,12 @@ impl Default for DefaultHeaders {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DefaultHeaders {
|
impl DefaultHeaders {
|
||||||
/// Construct `DefaultHeaders` middleware.
|
/// Constructs an empty `DefaultHeaders` middleware.
|
||||||
pub fn new() -> DefaultHeaders {
|
pub fn new() -> DefaultHeaders {
|
||||||
DefaultHeaders::default()
|
DefaultHeaders::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a header.
|
/// Adds a header to the default set.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
||||||
where
|
where
|
||||||
@ -84,11 +92,18 @@ impl DefaultHeaders {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set *CONTENT-TYPE* header if response does not contain this header.
|
/// Adds a default *Content-Type* header if response does not contain one.
|
||||||
pub fn content_type(mut self) -> Self {
|
///
|
||||||
|
/// Default is `application/octet-stream`.
|
||||||
|
pub fn add_content_type(mut self) -> Self {
|
||||||
Rc::get_mut(&mut self.inner)
|
Rc::get_mut(&mut self.inner)
|
||||||
.expect("Multiple copies exist")
|
.expect("Multiple `Inner` copies exist.")
|
||||||
.ct = true;
|
.headers
|
||||||
|
.insert(
|
||||||
|
CONTENT_TYPE,
|
||||||
|
HeaderValue::from_static("application/octet-stream"),
|
||||||
|
);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,9 +141,7 @@ where
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = DefaultHeaderFuture<S, B>;
|
type Future = DefaultHeaderFuture<S, B>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
actix_service::forward_ready!(service);
|
||||||
self.service.poll_ready(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||||
let inner = self.inner.clone();
|
let inner = self.inner.clone();
|
||||||
@ -160,19 +173,14 @@ where
|
|||||||
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 this = self.project();
|
let this = self.project();
|
||||||
let mut res = ready!(this.fut.poll(cx))?;
|
let mut res = ready!(this.fut.poll(cx))?;
|
||||||
|
|
||||||
// set response headers
|
// set response headers
|
||||||
for (key, value) in this.inner.headers.iter() {
|
for (key, value) in this.inner.headers.iter() {
|
||||||
if !res.headers().contains_key(key) {
|
if !res.headers().contains_key(key) {
|
||||||
res.headers_mut().insert(key.clone(), value.clone());
|
res.headers_mut().insert(key.clone(), value.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// default content-type
|
|
||||||
if this.inner.ct && !res.headers().contains_key(&CONTENT_TYPE) {
|
|
||||||
res.headers_mut().insert(
|
|
||||||
CONTENT_TYPE,
|
|
||||||
HeaderValue::from_static("application/octet-stream"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Poll::Ready(Ok(res))
|
Poll::Ready(Ok(res))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,10 +191,12 @@ mod tests {
|
|||||||
use futures_util::future::ok;
|
use futures_util::future::ok;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::dev::ServiceRequest;
|
use crate::{
|
||||||
use crate::http::header::CONTENT_TYPE;
|
dev::ServiceRequest,
|
||||||
use crate::test::{ok_service, TestRequest};
|
http::header::CONTENT_TYPE,
|
||||||
use crate::HttpResponse;
|
test::{ok_service, TestRequest},
|
||||||
|
HttpResponse,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_default_headers() {
|
async fn test_default_headers() {
|
||||||
@ -219,7 +229,7 @@ mod tests {
|
|||||||
let srv =
|
let srv =
|
||||||
|req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish()));
|
|req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish()));
|
||||||
let mut mw = DefaultHeaders::new()
|
let mut mw = DefaultHeaders::new()
|
||||||
.content_type()
|
.add_content_type()
|
||||||
.new_transform(srv.into_service())
|
.new_transform(srv.into_service())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
@ -1,35 +1,36 @@
|
|||||||
//! Custom handlers service for responses.
|
//! For middleware documentation, see [`ErrorHandlers`].
|
||||||
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::task::{Context, Poll};
|
|
||||||
|
|
||||||
use actix_service::{Service, Transform};
|
use actix_service::{Service, Transform};
|
||||||
use ahash::AHashMap;
|
use ahash::AHashMap;
|
||||||
use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready};
|
use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready};
|
||||||
|
|
||||||
use crate::dev::{ServiceRequest, ServiceResponse};
|
use crate::{
|
||||||
use crate::error::{Error, Result};
|
dev::{ServiceRequest, ServiceResponse},
|
||||||
use crate::http::StatusCode;
|
error::{Error, Result},
|
||||||
|
http::StatusCode,
|
||||||
|
};
|
||||||
|
|
||||||
/// Error handler response
|
/// Return type for [`ErrorHandlers`] custom handlers.
|
||||||
pub enum ErrorHandlerResponse<B> {
|
pub enum ErrorHandlerResponse<B> {
|
||||||
/// New http response got generated
|
/// Immediate HTTP response.
|
||||||
Response(ServiceResponse<B>),
|
Response(ServiceResponse<B>),
|
||||||
/// Result is a future that resolves to a new http response
|
|
||||||
|
/// A future that resolves to an HTTP response.
|
||||||
Future(LocalBoxFuture<'static, Result<ServiceResponse<B>, Error>>),
|
Future(LocalBoxFuture<'static, Result<ServiceResponse<B>, Error>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrorHandler<B> = dyn Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>>;
|
type ErrorHandler<B> = dyn Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>>;
|
||||||
|
|
||||||
/// `Middleware` for allowing custom handlers for responses.
|
/// Middleware for registering custom status code based error handlers.
|
||||||
///
|
///
|
||||||
/// You can use `ErrorHandlers::handler()` method to register a custom error
|
/// Register handlers with the `ErrorHandlers::handler()` method to register a custom error handler
|
||||||
/// handler for specific status code. You can modify existing response or
|
/// for a given status code. Handlers can modify existing responses or create completely new ones.
|
||||||
/// create completely new one.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
///
|
||||||
|
/// # Usage
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_web::middleware::errhandlers::{ErrorHandlers, ErrorHandlerResponse};
|
/// use actix_web::middleware::{ErrorHandlers, ErrorHandlerResponse};
|
||||||
/// use actix_web::{web, http, dev, App, HttpRequest, HttpResponse, Result};
|
/// use actix_web::{web, http, dev, App, HttpRequest, HttpResponse, Result};
|
||||||
///
|
///
|
||||||
/// fn render_500<B>(mut res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
/// fn render_500<B>(mut res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
||||||
@ -39,7 +40,6 @@ type ErrorHandler<B> = dyn Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse
|
|||||||
/// Ok(ErrorHandlerResponse::Response(res))
|
/// Ok(ErrorHandlerResponse::Response(res))
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let app = App::new()
|
/// let app = App::new()
|
||||||
/// .wrap(
|
/// .wrap(
|
||||||
/// ErrorHandlers::new()
|
/// ErrorHandlers::new()
|
||||||
@ -49,7 +49,6 @@ type ErrorHandler<B> = dyn Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse
|
|||||||
/// .route(web::get().to(|| HttpResponse::Ok()))
|
/// .route(web::get().to(|| HttpResponse::Ok()))
|
||||||
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())
|
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())
|
||||||
/// ));
|
/// ));
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub struct ErrorHandlers<B> {
|
pub struct ErrorHandlers<B> {
|
||||||
handlers: Rc<AHashMap<StatusCode, Box<ErrorHandler<B>>>>,
|
handlers: Rc<AHashMap<StatusCode, Box<ErrorHandler<B>>>>,
|
||||||
@ -64,12 +63,12 @@ impl<B> Default for ErrorHandlers<B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<B> ErrorHandlers<B> {
|
impl<B> ErrorHandlers<B> {
|
||||||
/// Construct new `ErrorHandlers` instance
|
/// Construct new `ErrorHandlers` instance.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
ErrorHandlers::default()
|
ErrorHandlers::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register error handler for specified status code
|
/// Register error handler for specified status code.
|
||||||
pub fn handler<F>(mut self, status: StatusCode, handler: F) -> Self
|
pub fn handler<F>(mut self, status: StatusCode, handler: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> + 'static,
|
F: Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> + 'static,
|
||||||
@ -117,9 +116,7 @@ where
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
actix_service::forward_ready!(service);
|
||||||
self.service.poll_ready(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||||
let handlers = self.handlers.clone();
|
let handlers = self.handlers.clone();
|
@ -1,13 +1,16 @@
|
|||||||
//! Request logging middleware
|
//! For middleware documentation, see [`Logger`].
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::convert::TryFrom;
|
use std::{
|
||||||
use std::env;
|
collections::HashSet,
|
||||||
use std::fmt::{self, Display, Formatter};
|
convert::TryFrom,
|
||||||
use std::future::Future;
|
env,
|
||||||
use std::marker::PhantomData;
|
fmt::{self, Display as _},
|
||||||
use std::pin::Pin;
|
future::Future,
|
||||||
use std::rc::Rc;
|
marker::PhantomData,
|
||||||
use std::task::{Context, Poll};
|
pin::Pin,
|
||||||
|
rc::Rc,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_service::{Service, Transform};
|
use actix_service::{Service, Transform};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
@ -16,78 +19,69 @@ use log::debug;
|
|||||||
use regex::{Regex, RegexSet};
|
use regex::{Regex, RegexSet};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::dev::{BodySize, MessageBody, ResponseBody};
|
use crate::{
|
||||||
use crate::error::{Error, Result};
|
dev::{BodySize, MessageBody, ResponseBody},
|
||||||
use crate::http::{HeaderName, StatusCode};
|
error::{Error, Result},
|
||||||
use crate::service::{ServiceRequest, ServiceResponse};
|
http::{HeaderName, StatusCode},
|
||||||
use crate::HttpResponse;
|
service::{ServiceRequest, ServiceResponse},
|
||||||
|
HttpResponse,
|
||||||
|
};
|
||||||
|
|
||||||
/// `Middleware` for logging request and response info to the terminal.
|
/// Middleware for logging request and response summaries to the terminal.
|
||||||
///
|
///
|
||||||
/// `Logger` middleware uses standard log crate to log information. You should
|
/// This middleware uses the `log` crate to output information. Enable `log`'s output for the
|
||||||
/// enable logger for `actix_web` package to see access log.
|
/// "actix_web" scope using [`env_logger`](https://docs.rs/env_logger) or similar crate.
|
||||||
/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar)
|
|
||||||
///
|
///
|
||||||
/// ## Usage
|
/// # Default Format
|
||||||
///
|
/// The [`default`](Logger::default) Logger uses the following format:
|
||||||
/// Create `Logger` middleware with the specified `format`.
|
|
||||||
/// Default `Logger` could be created with `default` method, it uses the
|
|
||||||
/// default format:
|
|
||||||
///
|
///
|
||||||
/// ```plain
|
/// ```plain
|
||||||
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||||
|
///
|
||||||
|
/// Example Output:
|
||||||
|
/// 127.0.0.1:54278 "GET /test HTTP/1.1" 404 20 "-" "HTTPie/2.2.0" 0.001074
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// # Usage
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_web::{middleware::Logger, App};
|
/// use actix_web::{middleware::Logger, App};
|
||||||
///
|
///
|
||||||
/// std::env::set_var("RUST_LOG", "actix_web=info");
|
/// // access logs are printed with the INFO level so ensure it is enabled by default
|
||||||
/// env_logger::init();
|
/// env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
///
|
///
|
||||||
/// let app = App::new()
|
/// let app = App::new()
|
||||||
/// .wrap(Logger::default())
|
/// // .wrap(Logger::default())
|
||||||
/// .wrap(Logger::new("%a %{User-Agent}i"));
|
/// .wrap(Logger::new("%a %{User-Agent}i"));
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Format
|
/// # Format
|
||||||
///
|
/// Variable | Description
|
||||||
/// `%%` The percent sign
|
/// -------- | -----------
|
||||||
///
|
/// `%%` | The percent sign
|
||||||
/// `%a` Remote IP-address (IP-address of proxy if using reverse proxy)
|
/// `%a` | Peer IP address (or IP address of reverse proxy if used)
|
||||||
///
|
/// `%t` | Time when the request started processing (in RFC 3339 format)
|
||||||
/// `%t` Time when the request was started to process (in rfc3339 format)
|
/// `%r` | First line of request (Example: `GET /test HTTP/1.1`)
|
||||||
///
|
/// `%s` | Response status code
|
||||||
/// `%r` First line of request
|
/// `%b` | Size of response in bytes, including HTTP headers
|
||||||
///
|
/// `%T` | Time taken to serve the request, in seconds to 6 decimal places
|
||||||
/// `%s` Response status code
|
/// `%D` | Time taken to serve the request, in milliseconds
|
||||||
///
|
/// `%U` | Request URL
|
||||||
/// `%b` Size of response in bytes, including HTTP headers
|
/// `%{r}a` | "Real IP" remote address **\***
|
||||||
///
|
/// `%{FOO}i` | `request.headers["FOO"]`
|
||||||
/// `%T` Time taken to serve the request, in seconds with floating fraction in
|
/// `%{FOO}o` | `response.headers["FOO"]`
|
||||||
/// .06f format
|
/// `%{FOO}e` | `env_var["FOO"]`
|
||||||
///
|
/// `%{FOO}xi` | [Custom request replacement](Logger::custom_request_replace) labelled "FOO"
|
||||||
/// `%D` Time taken to serve the request, in milliseconds
|
|
||||||
///
|
|
||||||
/// `%U` Request URL
|
|
||||||
///
|
|
||||||
/// `%{r}a` Real IP remote address **\***
|
|
||||||
///
|
|
||||||
/// `%{FOO}i` request.headers['FOO']
|
|
||||||
///
|
|
||||||
/// `%{FOO}o` response.headers['FOO']
|
|
||||||
///
|
|
||||||
/// `%{FOO}e` os.environ['FOO']
|
|
||||||
///
|
|
||||||
/// `%{FOO}xi` [custom request replacement](Logger::custom_request_replace) labelled "FOO"
|
|
||||||
///
|
///
|
||||||
/// # Security
|
/// # Security
|
||||||
/// **\*** It is calculated using
|
/// **\*** "Real IP" remote address is calculated using
|
||||||
/// [`ConnectionInfo::realip_remote_addr()`](crate::dev::ConnectionInfo::realip_remote_addr())
|
/// [`ConnectionInfo::realip_remote_addr()`](crate::dev::ConnectionInfo::realip_remote_addr())
|
||||||
///
|
///
|
||||||
/// If you use this value ensure that all requests come from trusted hosts, since it is trivial
|
/// If you use this value, ensure that all requests come from trusted hosts. Otherwise, it is
|
||||||
/// for the remote client to simulate being another client.
|
/// trivial for the remote client to falsify their source IP address.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Logger(Rc<Inner>);
|
pub struct Logger(Rc<Inner>);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
struct Inner {
|
struct Inner {
|
||||||
format: Format,
|
format: Format,
|
||||||
exclude: HashSet<String>,
|
exclude: HashSet<String>,
|
||||||
@ -113,7 +107,7 @@ impl Logger {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ignore and do not log access info for paths that match regex
|
/// Ignore and do not log access info for paths that match regex.
|
||||||
pub fn exclude_regex<T: Into<String>>(mut self, path: T) -> Self {
|
pub fn exclude_regex<T: Into<String>>(mut self, path: T) -> Self {
|
||||||
let inner = Rc::get_mut(&mut self.0).unwrap();
|
let inner = Rc::get_mut(&mut self.0).unwrap();
|
||||||
let mut patterns = inner.exclude_regex.patterns().to_vec();
|
let mut patterns = inner.exclude_regex.patterns().to_vec();
|
||||||
@ -209,7 +203,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Logger middleware
|
/// Logger middleware service.
|
||||||
pub struct LoggerMiddleware<S> {
|
pub struct LoggerMiddleware<S> {
|
||||||
inner: Rc<Inner>,
|
inner: Rc<Inner>,
|
||||||
service: S,
|
service: S,
|
||||||
@ -224,9 +218,7 @@ where
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = LoggerResponse<S, B>;
|
type Future = LoggerResponse<S, B>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
actix_service::forward_ready!(service);
|
||||||
self.service.poll_ready(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||||
if self.inner.exclude.contains(req.path())
|
if self.inner.exclude.contains(req.path())
|
||||||
@ -255,7 +247,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[pin_project::pin_project]
|
#[pin_project::pin_project]
|
||||||
pub struct LoggerResponse<S, B>
|
pub struct LoggerResponse<S, B>
|
||||||
where
|
where
|
||||||
@ -325,7 +316,7 @@ pub struct StreamLog<B> {
|
|||||||
impl<B> PinnedDrop for StreamLog<B> {
|
impl<B> PinnedDrop for StreamLog<B> {
|
||||||
fn drop(self: Pin<&mut Self>) {
|
fn drop(self: Pin<&mut Self>) {
|
||||||
if let Some(ref format) = self.format {
|
if let Some(ref format) = self.format {
|
||||||
let render = |fmt: &mut Formatter<'_>| {
|
let render = |fmt: &mut fmt::Formatter<'_>| {
|
||||||
for unit in &format.0 {
|
for unit in &format.0 {
|
||||||
unit.render(fmt, self.size, self.time)?;
|
unit.render(fmt, self.size, self.time)?;
|
||||||
}
|
}
|
||||||
@ -356,9 +347,8 @@ impl<B: MessageBody> MessageBody for StreamLog<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A formatting style for the `Logger`, consisting of multiple
|
/// A formatting style for the `Logger` consisting of multiple concatenated `FormatText` items.
|
||||||
/// `FormatText`s concatenated into one line.
|
#[derive(Debug, Clone)]
|
||||||
#[derive(Clone)]
|
|
||||||
struct Format(Vec<FormatText>);
|
struct Format(Vec<FormatText>);
|
||||||
|
|
||||||
impl Default for Format {
|
impl Default for Format {
|
||||||
@ -430,13 +420,12 @@ impl Format {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A string of text to be logged. This is either one of the data
|
/// A string of text to be logged.
|
||||||
/// fields supported by the `Logger`, or a custom `String`.
|
///
|
||||||
#[doc(hidden)]
|
/// This is either one of the data fields supported by the `Logger`, or a custom `String`.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
// TODO: remove pub on next breaking change
|
enum FormatText {
|
||||||
pub enum FormatText {
|
|
||||||
Str(String),
|
Str(String),
|
||||||
Percent,
|
Percent,
|
||||||
RequestLine,
|
RequestLine,
|
||||||
@ -454,10 +443,8 @@ pub enum FormatText {
|
|||||||
CustomRequest(String, Option<CustomRequestFn>),
|
CustomRequest(String, Option<CustomRequestFn>),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove pub on next breaking change
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CustomRequestFn {
|
struct CustomRequestFn {
|
||||||
inner_fn: Rc<dyn Fn(&ServiceRequest) -> String>,
|
inner_fn: Rc<dyn Fn(&ServiceRequest) -> String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -476,11 +463,11 @@ impl fmt::Debug for CustomRequestFn {
|
|||||||
impl FormatText {
|
impl FormatText {
|
||||||
fn render(
|
fn render(
|
||||||
&self,
|
&self,
|
||||||
fmt: &mut Formatter<'_>,
|
fmt: &mut fmt::Formatter<'_>,
|
||||||
size: usize,
|
size: usize,
|
||||||
entry_time: OffsetDateTime,
|
entry_time: OffsetDateTime,
|
||||||
) -> Result<(), fmt::Error> {
|
) -> Result<(), fmt::Error> {
|
||||||
match *self {
|
match self {
|
||||||
FormatText::Str(ref string) => fmt.write_str(string),
|
FormatText::Str(ref string) => fmt.write_str(string),
|
||||||
FormatText::Percent => "%".fmt(fmt),
|
FormatText::Percent => "%".fmt(fmt),
|
||||||
FormatText::ResponseSize => size.fmt(fmt),
|
FormatText::ResponseSize => size.fmt(fmt),
|
||||||
@ -506,7 +493,7 @@ impl FormatText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_response<B>(&mut self, res: &HttpResponse<B>) {
|
fn render_response<B>(&mut self, res: &HttpResponse<B>) {
|
||||||
match *self {
|
match self {
|
||||||
FormatText::ResponseStatus => {
|
FormatText::ResponseStatus => {
|
||||||
*self = FormatText::Str(format!("{}", res.status().as_u16()))
|
*self = FormatText::Str(format!("{}", res.status().as_u16()))
|
||||||
}
|
}
|
||||||
@ -527,7 +514,7 @@ impl FormatText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) {
|
fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) {
|
||||||
match &*self {
|
match self {
|
||||||
FormatText::RequestLine => {
|
FormatText::RequestLine => {
|
||||||
*self = if req.query_string().is_empty() {
|
*self = if req.query_string().is_empty() {
|
||||||
FormatText::Str(format!(
|
FormatText::Str(format!(
|
||||||
@ -594,11 +581,11 @@ impl FormatText {
|
|||||||
|
|
||||||
/// Converter to get a String from something that writes to a Formatter.
|
/// Converter to get a String from something that writes to a Formatter.
|
||||||
pub(crate) struct FormatDisplay<'a>(
|
pub(crate) struct FormatDisplay<'a>(
|
||||||
&'a dyn Fn(&mut Formatter<'_>) -> Result<(), fmt::Error>,
|
&'a dyn Fn(&mut fmt::Formatter<'_>) -> Result<(), fmt::Error>,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl<'a> fmt::Display for FormatDisplay<'a> {
|
impl<'a> fmt::Display for FormatDisplay<'a> {
|
||||||
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
(self.0)(fmt)
|
(self.0)(fmt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -675,7 +662,7 @@ mod tests {
|
|||||||
unit.render_response(&resp);
|
unit.render_response(&resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
let render = |fmt: &mut Formatter<'_>| {
|
let render = |fmt: &mut fmt::Formatter<'_>| {
|
||||||
for unit in &format.0 {
|
for unit in &format.0 {
|
||||||
unit.render(fmt, 1024, now)?;
|
unit.render(fmt, 1024, now)?;
|
||||||
}
|
}
|
||||||
@ -708,7 +695,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let entry_time = OffsetDateTime::now_utc();
|
let entry_time = OffsetDateTime::now_utc();
|
||||||
let render = |fmt: &mut Formatter<'_>| {
|
let render = |fmt: &mut fmt::Formatter<'_>| {
|
||||||
for unit in &format.0 {
|
for unit in &format.0 {
|
||||||
unit.render(fmt, 1024, entry_time)?;
|
unit.render(fmt, 1024, entry_time)?;
|
||||||
}
|
}
|
||||||
@ -736,7 +723,7 @@ mod tests {
|
|||||||
unit.render_response(&resp);
|
unit.render_response(&resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
let render = |fmt: &mut Formatter<'_>| {
|
let render = |fmt: &mut fmt::Formatter<'_>| {
|
||||||
for unit in &format.0 {
|
for unit in &format.0 {
|
||||||
unit.render(fmt, 1024, now)?;
|
unit.render(fmt, 1024, now)?;
|
||||||
}
|
}
|
||||||
@ -769,7 +756,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let entry_time = OffsetDateTime::now_utc();
|
let entry_time = OffsetDateTime::now_utc();
|
||||||
let render = |fmt: &mut Formatter<'_>| {
|
let render = |fmt: &mut fmt::Formatter<'_>| {
|
||||||
for unit in &format.0 {
|
for unit in &format.0 {
|
||||||
unit.render(fmt, 1024, entry_time)?;
|
unit.render(fmt, 1024, entry_time)?;
|
||||||
}
|
}
|
||||||
@ -800,7 +787,7 @@ mod tests {
|
|||||||
|
|
||||||
unit.render_request(now, &req);
|
unit.render_request(now, &req);
|
||||||
|
|
||||||
let render = |fmt: &mut Formatter<'_>| unit.render(fmt, 1024, now);
|
let render = |fmt: &mut fmt::Formatter<'_>| unit.render(fmt, 1024, now);
|
||||||
|
|
||||||
let log_output = FormatDisplay(&render).to_string();
|
let log_output = FormatDisplay(&render).to_string();
|
||||||
assert_eq!(log_output, "custom_log");
|
assert_eq!(log_output, "custom_log");
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
//! Middlewares
|
//! Commonly used middleware.
|
||||||
|
|
||||||
|
mod compat;
|
||||||
|
mod condition;
|
||||||
|
mod default_headers;
|
||||||
|
mod err_handlers;
|
||||||
|
mod logger;
|
||||||
|
mod normalize;
|
||||||
|
|
||||||
|
pub use self::compat::Compat;
|
||||||
|
pub use self::condition::Condition;
|
||||||
|
pub use self::default_headers::DefaultHeaders;
|
||||||
|
pub use self::err_handlers::{ErrorHandlerResponse, ErrorHandlers};
|
||||||
|
pub use self::logger::Logger;
|
||||||
|
pub use self::normalize::{NormalizePath, TrailingSlash};
|
||||||
|
|
||||||
#[cfg(feature = "compress")]
|
#[cfg(feature = "compress")]
|
||||||
mod compress;
|
mod compress;
|
||||||
#[cfg(feature = "compress")]
|
#[cfg(feature = "compress")]
|
||||||
pub use self::compress::Compress;
|
pub use self::compress::Compress;
|
||||||
|
|
||||||
mod compat;
|
|
||||||
mod condition;
|
|
||||||
mod defaultheaders;
|
|
||||||
pub mod errhandlers;
|
|
||||||
mod logger;
|
|
||||||
pub mod normalize;
|
|
||||||
|
|
||||||
pub use self::compat::Compat;
|
|
||||||
pub use self::condition::Condition;
|
|
||||||
pub use self::defaultheaders::DefaultHeaders;
|
|
||||||
pub use self::logger::Logger;
|
|
||||||
pub use self::normalize::NormalizePath;
|
|
||||||
|
@ -1,60 +1,63 @@
|
|||||||
//! For middleware documentation, see [`NormalizePath`].
|
//! For middleware documentation, see [`NormalizePath`].
|
||||||
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
|
|
||||||
use actix_http::http::{PathAndQuery, Uri};
|
use actix_http::http::{PathAndQuery, Uri};
|
||||||
use actix_service::{Service, Transform};
|
use actix_service::{Service, Transform};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::future::{ready, Ready};
|
use futures_util::future::{ready, Ready};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::service::{ServiceRequest, ServiceResponse};
|
use crate::{
|
||||||
use crate::Error;
|
service::{ServiceRequest, ServiceResponse},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
|
||||||
/// To be used when constructing `NormalizePath` to define it's behavior.
|
/// Determines the behavior of the [`NormalizePath`] middleware.
|
||||||
|
///
|
||||||
|
/// The default is `TrailingSlash::Trim`.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum TrailingSlash {
|
pub enum TrailingSlash {
|
||||||
/// Always add a trailing slash to the end of the path.
|
/// Trim trailing slashes from the end of the path.
|
||||||
/// This will require all routes to end in a trailing slash for them to be accessible.
|
///
|
||||||
Always,
|
/// Using this will require all routes to omit trailing slashes for them to be accessible.
|
||||||
|
Trim,
|
||||||
|
|
||||||
/// Only merge any present multiple trailing slashes.
|
/// Only merge any present multiple trailing slashes.
|
||||||
///
|
///
|
||||||
/// Note: This option provides the best compatibility with the v2 version of this middleware.
|
/// This option provides the best compatibility with behavior in actix-web v2.0.
|
||||||
MergeOnly,
|
MergeOnly,
|
||||||
|
|
||||||
/// Trim trailing slashes from the end of the path.
|
/// Always add a trailing slash to the end of the path.
|
||||||
Trim,
|
///
|
||||||
|
/// Using this will require all routes have a trailing slash for them to be accessible.
|
||||||
|
Always,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TrailingSlash {
|
impl Default for TrailingSlash {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
TrailingSlash::Always
|
TrailingSlash::Trim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy)]
|
/// Middleware for normalizing a request's path so that routes can be matched more flexibly.
|
||||||
/// Middleware to normalize a request's path so that routes can be matched less strictly.
|
|
||||||
///
|
///
|
||||||
/// # Normalization Steps
|
/// # Normalization Steps
|
||||||
/// - Merges multiple consecutive slashes into one. (For example, `/path//one` always
|
/// - Merges consecutive slashes into one. (For example, `/path//one` always becomes `/path/one`.)
|
||||||
/// becomes `/path/one`.)
|
|
||||||
/// - Appends a trailing slash if one is not present, removes one if present, or keeps trailing
|
/// - Appends a trailing slash if one is not present, removes one if present, or keeps trailing
|
||||||
/// slashes as-is, depending on which [`TrailingSlash`] variant is supplied
|
/// slashes as-is, depending on which [`TrailingSlash`] variant is supplied
|
||||||
/// to [`new`](NormalizePath::new()).
|
/// to [`new`](NormalizePath::new()).
|
||||||
///
|
///
|
||||||
/// # Default Behavior
|
/// # Default Behavior
|
||||||
/// The default constructor chooses to strip trailing slashes from the end
|
/// The default constructor chooses to strip trailing slashes from the end of paths with them
|
||||||
/// ([`TrailingSlash::Trim`]), the effect is that route definitions should be defined without
|
/// ([`TrailingSlash::Trim`]). The implication is that route definitions should be defined without
|
||||||
/// trailing slashes or else they will be inaccessible.
|
/// trailing slashes or else they will be inaccessible (or vice versa when using the
|
||||||
|
/// `TrailingSlash::Always` behavior), as shown in the example tests below.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Usage
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_web::{web, middleware, App};
|
/// use actix_web::{web, middleware, App};
|
||||||
///
|
///
|
||||||
/// # #[actix_rt::test]
|
/// # actix_web::rt::System::new("doctest").block_on(async {
|
||||||
/// # async fn normalize() {
|
|
||||||
/// let app = App::new()
|
/// let app = App::new()
|
||||||
/// .wrap(middleware::NormalizePath::default())
|
/// .wrap(middleware::NormalizePath::default())
|
||||||
/// .route("/test", web::get().to(|| async { "test" }))
|
/// .route("/test", web::get().to(|| async { "test" }))
|
||||||
@ -80,8 +83,9 @@ impl Default for TrailingSlash {
|
|||||||
/// let req = TestRequest::with_uri("/unmatchable/").to_request();
|
/// let req = TestRequest::with_uri("/unmatchable/").to_request();
|
||||||
/// let res = call_service(&mut app, req).await;
|
/// let res = call_service(&mut app, req).await;
|
||||||
/// assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
/// assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
/// # }
|
/// # })
|
||||||
/// ```
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct NormalizePath(TrailingSlash);
|
pub struct NormalizePath(TrailingSlash);
|
||||||
|
|
||||||
impl NormalizePath {
|
impl NormalizePath {
|
||||||
@ -111,7 +115,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub struct NormalizePathNormalization<S> {
|
pub struct NormalizePathNormalization<S> {
|
||||||
service: S,
|
service: S,
|
||||||
merge_slash: Regex,
|
merge_slash: Regex,
|
||||||
@ -127,9 +130,7 @@ where
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = S::Future;
|
type Future = S::Future;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
actix_service::forward_ready!(service);
|
||||||
self.service.poll_ready(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
||||||
let head = req.head_mut();
|
let head = req.head_mut();
|
||||||
@ -198,7 +199,7 @@ mod tests {
|
|||||||
App::new()
|
App::new()
|
||||||
.wrap(NormalizePath::default())
|
.wrap(NormalizePath::default())
|
||||||
.service(web::resource("/").to(HttpResponse::Ok))
|
.service(web::resource("/").to(HttpResponse::Ok))
|
||||||
.service(web::resource("/v1/something/").to(HttpResponse::Ok)),
|
.service(web::resource("/v1/something").to(HttpResponse::Ok)),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@ -306,7 +307,7 @@ mod tests {
|
|||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_in_place_normalization() {
|
async fn test_in_place_normalization() {
|
||||||
let srv = |req: ServiceRequest| {
|
let srv = |req: ServiceRequest| {
|
||||||
assert_eq!("/v1/something/", req.path());
|
assert_eq!("/v1/something", req.path());
|
||||||
ready(Ok(req.into_response(HttpResponse::Ok().finish())))
|
ready(Ok(req.into_response(HttpResponse::Ok().finish())))
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -334,7 +335,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn should_normalize_nothing() {
|
async fn should_normalize_nothing() {
|
||||||
const URI: &str = "/v1/something/";
|
const URI: &str = "/v1/something";
|
||||||
|
|
||||||
let srv = |req: ServiceRequest| {
|
let srv = |req: ServiceRequest| {
|
||||||
assert_eq!(URI, req.path());
|
assert_eq!(URI, req.path());
|
||||||
@ -353,10 +354,8 @@ mod tests {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn should_normalize_no_trail() {
|
async fn should_normalize_no_trail() {
|
||||||
const URI: &str = "/v1/something";
|
|
||||||
|
|
||||||
let srv = |req: ServiceRequest| {
|
let srv = |req: ServiceRequest| {
|
||||||
assert_eq!(URI.to_string() + "/", req.path());
|
assert_eq!("/v1/something", req.path());
|
||||||
ready(Ok(req.into_response(HttpResponse::Ok().finish())))
|
ready(Ok(req.into_response(HttpResponse::Ok().finish())))
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -365,7 +364,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let req = TestRequest::with_uri(URI).to_srv_request();
|
let req = TestRequest::with_uri("/v1/something/").to_srv_request();
|
||||||
let res = normalize.call(req).await.unwrap();
|
let res = normalize.call(req).await.unwrap();
|
||||||
assert!(res.status().is_success());
|
assert!(res.status().is_success());
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use std::future::Future;
|
use std::{
|
||||||
use std::io::{Read, Write};
|
future::Future,
|
||||||
use std::pin::Pin;
|
io::{Read, Write},
|
||||||
use std::task::{Context, Poll};
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_http::http::header::{
|
use actix_http::http::header::{
|
||||||
ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
|
ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
|
||||||
@ -9,15 +11,16 @@ use actix_http::http::header::{
|
|||||||
};
|
};
|
||||||
use brotli2::write::{BrotliDecoder, BrotliEncoder};
|
use brotli2::write::{BrotliDecoder, BrotliEncoder};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flate2::read::GzDecoder;
|
use flate2::{
|
||||||
use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder};
|
read::GzDecoder,
|
||||||
use flate2::Compression;
|
write::{GzEncoder, ZlibDecoder, ZlibEncoder},
|
||||||
|
Compression,
|
||||||
|
};
|
||||||
use futures_util::ready;
|
use futures_util::ready;
|
||||||
use rand::{distributions::Alphanumeric, Rng};
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
|
|
||||||
use actix_web::dev::BodyEncoding;
|
use actix_web::dev::BodyEncoding;
|
||||||
use actix_web::middleware::normalize::TrailingSlash;
|
use actix_web::middleware::{Compress, NormalizePath, TrailingSlash};
|
||||||
use actix_web::middleware::{Compress, NormalizePath};
|
|
||||||
use actix_web::{dev, test, web, App, Error, HttpResponse};
|
use actix_web::{dev, test, web, App, Error, HttpResponse};
|
||||||
|
|
||||||
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
|
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Loading…
Reference in New Issue
Block a user