1
0
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:
Rob Ede 2021-01-05 09:51:58 +00:00 committed by GitHub
parent 4f5971d79e
commit 68117543ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 319 additions and 303 deletions

View File

@ -2,25 +2,31 @@
## Unreleased - 2021-xx-xx
### Added
* `Compat` middleware enabling generic response body/error type of middlewares
like `Logger` and `Compress` to be used in `middleware::Condition`
and `Resource`, `Scope` services. [#1865]
* `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and
`Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865]
### Changed
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
* Bumped `rand` to `0.8`.
* Update `rust-tls` to `0.19`. [#1813]
* 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.
[#1813]: https://github.com/actix/actix-web/pull/1813
[#1865]: https://github.com/actix/actix-web/pull/1865
### 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
[#1813]: https://github.com/actix/actix-web/pull/1813
[#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

View File

@ -1,5 +1,15 @@
## 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

View File

@ -18,8 +18,7 @@ async fn no_params() -> &'static str {
#[actix_web::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
env_logger::init();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
HttpServer::new(|| {
App::new()

View File

@ -1,41 +1,45 @@
//! `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`.
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
//! For middleware documentation, see [`Compat`].
use actix_http::body::Body;
use actix_http::body::{MessageBody, ResponseBody};
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use actix_http::body::{Body, MessageBody, ResponseBody};
use actix_service::{Service, Transform};
use futures_core::future::LocalBoxFuture;
use futures_core::ready;
use futures_core::{future::LocalBoxFuture, ready};
use crate::error::Error;
use crate::service::ServiceResponse;
use crate::{error::Error, service::ServiceResponse};
/// `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`.
///
///
/// ## Usage
/// Middleware for enabling any middleware to be used in [`Resource::wrap`](crate::Resource::wrap),
/// [`Scope::wrap`](crate::Scope::wrap) and [`Condition`](super::Condition).
///
/// # Usage
/// ```rust
/// use actix_web::middleware::{Logger, Compat};
/// use actix_web::{App, web};
///
/// let logger = Logger::default();
///
/// // this would not compile
/// // let app = App::new().service(web::scope("scoped").wrap(logger));
/// // this would not compile because of incompatible body types
/// // let app = App::new()
/// // .service(web::scope("scoped").wrap(logger));
///
/// // by using scoped middleware we can use logger in scope.
/// let app = App::new().service(web::scope("scoped").wrap(Compat::new(logger)));
/// // by using this middleware we can use the logger on a scope
/// let app = App::new()
/// .service(web::scope("scoped").wrap(Compat::new(logger)));
/// ```
pub struct Compat<T> {
transform: T,
}
impl<T> Compat<T> {
pub fn new(transform: T) -> Self {
Self { transform }
/// Wrap a middleware to give it broader compatibility.
pub fn new(middleware: T) -> Self {
Self {
transform: middleware,
}
}
}

View File

@ -1,45 +1,47 @@
//! `Middleware` for compressing response body.
use std::cmp;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::str::FromStr;
use std::task::{Context, Poll};
//! For middleware documentation, see [`Compress`].
use actix_http::body::MessageBody;
use actix_http::encoding::Encoder;
use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING};
use actix_http::Error;
use std::{
cmp,
future::Future,
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 futures_util::future::{ok, Ready};
use pin_project::pin_project;
use crate::dev::BodyEncoding;
use crate::service::{ServiceRequest, ServiceResponse};
use crate::{
dev::BodyEncoding,
service::{ServiceRequest, ServiceResponse},
};
#[derive(Debug, Clone)]
/// `Middleware` for compressing response body.
/// Middleware for compressing response payloads.
///
/// Use `BodyEncoding` trait for overriding response compression.
/// To disable compression set encoding to `ContentEncoding::Identity` value.
/// Use `BodyEncoding` trait for overriding response compression. To disable compression set
/// encoding to `ContentEncoding::Identity`.
///
/// # Usage
/// ```rust
/// use actix_web::{web, middleware, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .wrap(middleware::Compress::default())
/// .service(
/// web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// .default_service(web::to(|| HttpResponse::NotFound()));
/// ```
#[derive(Debug, Clone)]
pub struct Compress(ContentEncoding);
impl Compress {
/// Create new `Compress` middleware with default encoding.
/// Create new `Compress` middleware with the specified encoding.
pub fn new(encoding: ContentEncoding) -> Self {
Compress(encoding)
}
@ -84,9 +86,7 @@ where
type Error = Error;
type Future = CompressResponse<S, B>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
actix_service::forward_ready!(service);
#[allow(clippy::borrow_interior_mutable_const)]
fn call(&mut self, req: ServiceRequest) -> Self::Future {
@ -109,7 +109,6 @@ where
}
}
#[doc(hidden)]
#[pin_project]
pub struct CompressResponse<S, B>
where

View File

@ -1,35 +1,36 @@
//! `Middleware` for conditionally enables another middleware.
//! For middleware documentation, see [`Condition`].
use std::task::{Context, Poll};
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.
/// The controlled middleware must not change the `Service` interfaces.
/// Middleware for conditionally enabling other middleware.
///
/// This means you cannot control such middlewares like `Logger` or `Compress` directly.
/// *. See `Compat` middleware for alternative.
///
/// ## Usage
/// The controlled middleware must not change the `Service` interfaces. This means you cannot
/// control such middlewares like `Logger` or `Compress` directly. See the [`Compat`](super::Compat)
/// middleware for a workaround.
///
/// # Usage
/// ```rust
/// use actix_web::middleware::{Condition, NormalizePath};
/// use actix_web::App;
///
/// # fn main() {
/// let enable_normalize = std::env::var("NORMALIZE_PATH") == Ok("true".into());
/// let enable_normalize = std::env::var("NORMALIZE_PATH").is_ok();
/// let app = App::new()
/// .wrap(Condition::new(enable_normalize, NormalizePath::default()));
/// # }
/// ```
pub struct Condition<T> {
trans: T,
transformer: T,
enable: bool,
}
impl<T> Condition<T> {
pub fn new(enable: bool, trans: T) -> Self {
Self { trans, enable }
pub fn new(enable: bool, transformer: T) -> Self {
Self {
transformer,
enable,
}
}
}
@ -49,16 +50,15 @@ where
fn new_transform(&self, service: S) -> Self::Future {
if self.enable {
let f = self.trans.new_transform(service).map(|res| {
res.map(
ConditionMiddleware::Enable as fn(T::Transform) -> Self::Transform,
)
});
Either::Left(f)
} else {
Either::Right(ok(ConditionMiddleware::Disable(service)))
let fut = self.transformer.new_transform(service);
async move {
let wrapped_svc = fut.await?;
Ok(ConditionMiddleware::Enable(wrapped_svc))
}
.boxed_local()
} else {
async move { Ok(ConditionMiddleware::Disable(service)) }.boxed_local()
}
}
}
@ -77,18 +77,16 @@ where
type Future = Either<E::Future, D::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
use ConditionMiddleware::*;
match self {
Enable(service) => service.poll_ready(cx),
Disable(service) => service.poll_ready(cx),
ConditionMiddleware::Enable(service) => service.poll_ready(cx),
ConditionMiddleware::Disable(service) => service.poll_ready(cx),
}
}
fn call(&mut self, req: Req) -> Self::Future {
use ConditionMiddleware::*;
match self {
Enable(service) => Either::Left(service.call(req)),
Disable(service) => Either::Right(service.call(req)),
ConditionMiddleware::Enable(service) => Either::Left(service.call(req)),
ConditionMiddleware::Disable(service) => Either::Right(service.call(req)),
}
}
}
@ -96,14 +94,17 @@ where
#[cfg(test)]
mod tests {
use actix_service::IntoService;
use futures_util::future::ok;
use super::*;
use crate::dev::{ServiceRequest, ServiceResponse};
use crate::error::Result;
use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
use crate::middleware::errhandlers::*;
use crate::test::{self, TestRequest};
use crate::HttpResponse;
use crate::{
dev::{ServiceRequest, ServiceResponse},
error::Result,
http::{header::CONTENT_TYPE, HeaderValue, StatusCode},
middleware::err_handlers::*,
test::{self, TestRequest},
HttpResponse,
};
#[allow(clippy::unnecessary_wraps)]
fn render_500<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {

View File

@ -1,24 +1,34 @@
//! Middleware for setting default response headers
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};
//! For middleware documentation, see [`DefaultHeaders`].
use actix_service::{Service, Transform};
use futures_util::future::{ready, Ready};
use futures_util::ready;
use std::{
convert::TryFrom,
future::Future,
marker::PhantomData,
pin::Pin,
rc::Rc,
task::{Context, Poll},
};
use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
use crate::http::{Error as HttpError, HeaderMap};
use crate::service::{ServiceRequest, ServiceResponse};
use crate::Error;
use futures_util::{
future::{ready, Ready},
ready,
};
/// `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
/// use actix_web::{web, http, middleware, App, HttpResponse};
///
@ -38,7 +48,6 @@ pub struct DefaultHeaders {
}
struct Inner {
ct: bool,
headers: HeaderMap,
}
@ -46,7 +55,6 @@ impl Default for DefaultHeaders {
fn default() -> Self {
DefaultHeaders {
inner: Rc::new(Inner {
ct: false,
headers: HeaderMap::new(),
}),
}
@ -54,12 +62,12 @@ impl Default for DefaultHeaders {
}
impl DefaultHeaders {
/// Construct `DefaultHeaders` middleware.
/// Constructs an empty `DefaultHeaders` middleware.
pub fn new() -> DefaultHeaders {
DefaultHeaders::default()
}
/// Set a header.
/// Adds a header to the default set.
#[inline]
pub fn header<K, V>(mut self, key: K, value: V) -> Self
where
@ -84,11 +92,18 @@ impl DefaultHeaders {
self
}
/// Set *CONTENT-TYPE* header if response does not contain this header.
pub fn content_type(mut self) -> Self {
/// Adds a default *Content-Type* header if response does not contain one.
///
/// Default is `application/octet-stream`.
pub fn add_content_type(mut self) -> Self {
Rc::get_mut(&mut self.inner)
.expect("Multiple copies exist")
.ct = true;
.expect("Multiple `Inner` copies exist.")
.headers
.insert(
CONTENT_TYPE,
HeaderValue::from_static("application/octet-stream"),
);
self
}
}
@ -126,9 +141,7 @@ where
type Error = Error;
type Future = DefaultHeaderFuture<S, B>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
actix_service::forward_ready!(service);
fn call(&mut self, req: ServiceRequest) -> Self::Future {
let inner = self.inner.clone();
@ -160,19 +173,14 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let mut res = ready!(this.fut.poll(cx))?;
// set response headers
for (key, value) in this.inner.headers.iter() {
if !res.headers().contains_key(key) {
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))
}
}
@ -183,10 +191,12 @@ mod tests {
use futures_util::future::ok;
use super::*;
use crate::dev::ServiceRequest;
use crate::http::header::CONTENT_TYPE;
use crate::test::{ok_service, TestRequest};
use crate::HttpResponse;
use crate::{
dev::ServiceRequest,
http::header::CONTENT_TYPE,
test::{ok_service, TestRequest},
HttpResponse,
};
#[actix_rt::test]
async fn test_default_headers() {
@ -219,7 +229,7 @@ mod tests {
let srv =
|req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish()));
let mut mw = DefaultHeaders::new()
.content_type()
.add_content_type()
.new_transform(srv.into_service())
.await
.unwrap();

View File

@ -1,35 +1,36 @@
//! Custom handlers service for responses.
//! For middleware documentation, see [`ErrorHandlers`].
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_service::{Service, Transform};
use ahash::AHashMap;
use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready};
use crate::dev::{ServiceRequest, ServiceResponse};
use crate::error::{Error, Result};
use crate::http::StatusCode;
use crate::{
dev::{ServiceRequest, ServiceResponse},
error::{Error, Result},
http::StatusCode,
};
/// Error handler response
/// Return type for [`ErrorHandlers`] custom handlers.
pub enum ErrorHandlerResponse<B> {
/// New http response got generated
/// Immediate HTTP response.
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>>),
}
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
/// handler for specific status code. You can modify existing response or
/// create completely new one.
///
/// ## Example
/// Register handlers with the `ErrorHandlers::handler()` method to register a custom error handler
/// for a given status code. Handlers can modify existing responses or create completely new ones.
///
/// # Usage
/// ```rust
/// use actix_web::middleware::errhandlers::{ErrorHandlers, ErrorHandlerResponse};
/// use actix_web::middleware::{ErrorHandlers, ErrorHandlerResponse};
/// use actix_web::{web, http, dev, App, HttpRequest, HttpResponse, Result};
///
/// 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))
/// }
///
/// # fn main() {
/// let app = App::new()
/// .wrap(
/// ErrorHandlers::new()
@ -49,7 +49,6 @@ type ErrorHandler<B> = dyn Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())
/// ));
/// # }
/// ```
pub struct ErrorHandlers<B> {
handlers: Rc<AHashMap<StatusCode, Box<ErrorHandler<B>>>>,
@ -64,12 +63,12 @@ impl<B> Default for ErrorHandlers<B> {
}
impl<B> ErrorHandlers<B> {
/// Construct new `ErrorHandlers` instance
/// Construct new `ErrorHandlers` instance.
pub fn new() -> Self {
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
where
F: Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> + 'static,
@ -117,9 +116,7 @@ where
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
actix_service::forward_ready!(service);
fn call(&mut self, req: ServiceRequest) -> Self::Future {
let handlers = self.handlers.clone();

View File

@ -1,13 +1,16 @@
//! Request logging middleware
use std::collections::HashSet;
use std::convert::TryFrom;
use std::env;
use std::fmt::{self, Display, Formatter};
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
//! For middleware documentation, see [`Logger`].
use std::{
collections::HashSet,
convert::TryFrom,
env,
fmt::{self, Display as _},
future::Future,
marker::PhantomData,
pin::Pin,
rc::Rc,
task::{Context, Poll},
};
use actix_service::{Service, Transform};
use bytes::Bytes;
@ -16,78 +19,69 @@ use log::debug;
use regex::{Regex, RegexSet};
use time::OffsetDateTime;
use crate::dev::{BodySize, MessageBody, ResponseBody};
use crate::error::{Error, Result};
use crate::http::{HeaderName, StatusCode};
use crate::service::{ServiceRequest, ServiceResponse};
use crate::HttpResponse;
use crate::{
dev::{BodySize, MessageBody, ResponseBody},
error::{Error, Result},
http::{HeaderName, StatusCode},
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
/// enable logger for `actix_web` package to see access log.
/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar)
/// This middleware uses the `log` crate to output information. Enable `log`'s output for the
/// "actix_web" scope using [`env_logger`](https://docs.rs/env_logger) or similar crate.
///
/// ## Usage
///
/// Create `Logger` middleware with the specified `format`.
/// Default `Logger` could be created with `default` method, it uses the
/// default format:
/// # Default Format
/// The [`default`](Logger::default) Logger uses the following format:
///
/// ```plain
/// %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
/// use actix_web::{middleware::Logger, App};
///
/// std::env::set_var("RUST_LOG", "actix_web=info");
/// env_logger::init();
/// // access logs are printed with the INFO level so ensure it is enabled by default
/// env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
///
/// let app = App::new()
/// .wrap(Logger::default())
/// // .wrap(Logger::default())
/// .wrap(Logger::new("%a %{User-Agent}i"));
/// ```
///
/// ## Format
///
/// `%%` The percent sign
///
/// `%a` Remote IP-address (IP-address of proxy if using reverse proxy)
///
/// `%t` Time when the request was started to process (in rfc3339 format)
///
/// `%r` First line of request
///
/// `%s` Response status code
///
/// `%b` Size of response in bytes, including HTTP headers
///
/// `%T` Time taken to serve the request, in seconds with floating fraction in
/// .06f format
///
/// `%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"
/// # Format
/// Variable | Description
/// -------- | -----------
/// `%%` | The percent sign
/// `%a` | Peer IP address (or IP address of reverse proxy if used)
/// `%t` | Time when the request started processing (in RFC 3339 format)
/// `%r` | First line of request (Example: `GET /test HTTP/1.1`)
/// `%s` | Response status code
/// `%b` | Size of response in bytes, including HTTP headers
/// `%T` | Time taken to serve the request, in seconds to 6 decimal places
/// `%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` | `env_var["FOO"]`
/// `%{FOO}xi` | [Custom request replacement](Logger::custom_request_replace) labelled "FOO"
///
/// # Security
/// **\*** It is calculated using
/// **\*** "Real IP" remote address is calculated using
/// [`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
/// for the remote client to simulate being another client.
/// If you use this value, ensure that all requests come from trusted hosts. Otherwise, it is
/// trivial for the remote client to falsify their source IP address.
#[derive(Debug)]
pub struct Logger(Rc<Inner>);
#[derive(Debug, Clone)]
struct Inner {
format: Format,
exclude: HashSet<String>,
@ -113,7 +107,7 @@ impl Logger {
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 {
let inner = Rc::get_mut(&mut self.0).unwrap();
let mut patterns = inner.exclude_regex.patterns().to_vec();
@ -209,7 +203,7 @@ where
}
}
/// Logger middleware
/// Logger middleware service.
pub struct LoggerMiddleware<S> {
inner: Rc<Inner>,
service: S,
@ -224,9 +218,7 @@ where
type Error = Error;
type Future = LoggerResponse<S, B>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
actix_service::forward_ready!(service);
fn call(&mut self, req: ServiceRequest) -> Self::Future {
if self.inner.exclude.contains(req.path())
@ -255,7 +247,6 @@ where
}
}
#[doc(hidden)]
#[pin_project::pin_project]
pub struct LoggerResponse<S, B>
where
@ -325,7 +316,7 @@ pub struct StreamLog<B> {
impl<B> PinnedDrop for StreamLog<B> {
fn drop(self: Pin<&mut Self>) {
if let Some(ref format) = self.format {
let render = |fmt: &mut Formatter<'_>| {
let render = |fmt: &mut fmt::Formatter<'_>| {
for unit in &format.0 {
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
/// `FormatText`s concatenated into one line.
#[derive(Clone)]
/// A formatting style for the `Logger` consisting of multiple concatenated `FormatText` items.
#[derive(Debug, Clone)]
struct Format(Vec<FormatText>);
impl Default for Format {
@ -430,13 +420,12 @@ impl Format {
}
}
/// A string of text to be logged. This is either one of the data
/// fields supported by the `Logger`, or a custom `String`.
#[doc(hidden)]
/// A string of text to be logged.
///
/// This is either one of the data fields supported by the `Logger`, or a custom `String`.
#[non_exhaustive]
#[derive(Debug, Clone)]
// TODO: remove pub on next breaking change
pub enum FormatText {
enum FormatText {
Str(String),
Percent,
RequestLine,
@ -454,10 +443,8 @@ pub enum FormatText {
CustomRequest(String, Option<CustomRequestFn>),
}
// TODO: remove pub on next breaking change
#[doc(hidden)]
#[derive(Clone)]
pub struct CustomRequestFn {
struct CustomRequestFn {
inner_fn: Rc<dyn Fn(&ServiceRequest) -> String>,
}
@ -476,11 +463,11 @@ impl fmt::Debug for CustomRequestFn {
impl FormatText {
fn render(
&self,
fmt: &mut Formatter<'_>,
fmt: &mut fmt::Formatter<'_>,
size: usize,
entry_time: OffsetDateTime,
) -> Result<(), fmt::Error> {
match *self {
match self {
FormatText::Str(ref string) => fmt.write_str(string),
FormatText::Percent => "%".fmt(fmt),
FormatText::ResponseSize => size.fmt(fmt),
@ -506,7 +493,7 @@ impl FormatText {
}
fn render_response<B>(&mut self, res: &HttpResponse<B>) {
match *self {
match self {
FormatText::ResponseStatus => {
*self = FormatText::Str(format!("{}", res.status().as_u16()))
}
@ -527,7 +514,7 @@ impl FormatText {
}
fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) {
match &*self {
match self {
FormatText::RequestLine => {
*self = if req.query_string().is_empty() {
FormatText::Str(format!(
@ -594,11 +581,11 @@ impl FormatText {
/// Converter to get a String from something that writes to a Formatter.
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> {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
(self.0)(fmt)
}
}
@ -675,7 +662,7 @@ mod tests {
unit.render_response(&resp);
}
let render = |fmt: &mut Formatter<'_>| {
let render = |fmt: &mut fmt::Formatter<'_>| {
for unit in &format.0 {
unit.render(fmt, 1024, now)?;
}
@ -708,7 +695,7 @@ mod tests {
}
let entry_time = OffsetDateTime::now_utc();
let render = |fmt: &mut Formatter<'_>| {
let render = |fmt: &mut fmt::Formatter<'_>| {
for unit in &format.0 {
unit.render(fmt, 1024, entry_time)?;
}
@ -736,7 +723,7 @@ mod tests {
unit.render_response(&resp);
}
let render = |fmt: &mut Formatter<'_>| {
let render = |fmt: &mut fmt::Formatter<'_>| {
for unit in &format.0 {
unit.render(fmt, 1024, now)?;
}
@ -769,7 +756,7 @@ mod tests {
}
let entry_time = OffsetDateTime::now_utc();
let render = |fmt: &mut Formatter<'_>| {
let render = |fmt: &mut fmt::Formatter<'_>| {
for unit in &format.0 {
unit.render(fmt, 1024, entry_time)?;
}
@ -800,7 +787,7 @@ mod tests {
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();
assert_eq!(log_output, "custom_log");

View File

@ -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")]
mod compress;
#[cfg(feature = "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;

View File

@ -1,60 +1,63 @@
//! For middleware documentation, see [`NormalizePath`].
use std::task::{Context, Poll};
use actix_http::http::{PathAndQuery, Uri};
use actix_service::{Service, Transform};
use bytes::Bytes;
use futures_util::future::{ready, Ready};
use regex::Regex;
use crate::service::{ServiceRequest, ServiceResponse};
use crate::Error;
use crate::{
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]
#[derive(Clone, Copy)]
#[derive(Debug, Clone, Copy)]
pub enum TrailingSlash {
/// Always add a trailing slash to the end of the path.
/// This will require all routes to end in a trailing slash for them to be accessible.
Always,
/// Trim trailing slashes from the end of the path.
///
/// Using this will require all routes to omit trailing slashes for them to be accessible.
Trim,
/// 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,
/// Trim trailing slashes from the end of the path.
Trim,
/// Always add a trailing slash to the end of the path.
///
/// Using this will require all routes have a trailing slash for them to be accessible.
Always,
}
impl Default for TrailingSlash {
fn default() -> Self {
TrailingSlash::Always
TrailingSlash::Trim
}
}
#[derive(Default, Clone, Copy)]
/// Middleware to normalize a request's path so that routes can be matched less strictly.
/// Middleware for normalizing a request's path so that routes can be matched more flexibly.
///
/// # Normalization Steps
/// - Merges multiple consecutive slashes into one. (For example, `/path//one` always
/// becomes `/path/one`.)
/// - Merges consecutive slashes into one. (For example, `/path//one` always becomes `/path/one`.)
/// - 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
/// to [`new`](NormalizePath::new()).
///
/// # Default Behavior
/// The default constructor chooses to strip trailing slashes from the end
/// ([`TrailingSlash::Trim`]), the effect is that route definitions should be defined without
/// trailing slashes or else they will be inaccessible.
/// The default constructor chooses to strip trailing slashes from the end of paths with them
/// ([`TrailingSlash::Trim`]). The implication is that route definitions should be defined without
/// 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
/// use actix_web::{web, middleware, App};
///
/// # #[actix_rt::test]
/// # async fn normalize() {
/// # actix_web::rt::System::new("doctest").block_on(async {
/// let app = App::new()
/// .wrap(middleware::NormalizePath::default())
/// .route("/test", web::get().to(|| async { "test" }))
@ -80,8 +83,9 @@ impl Default for TrailingSlash {
/// let req = TestRequest::with_uri("/unmatchable/").to_request();
/// let res = call_service(&mut app, req).await;
/// assert_eq!(res.status(), StatusCode::NOT_FOUND);
/// # }
/// # })
/// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct NormalizePath(TrailingSlash);
impl NormalizePath {
@ -111,7 +115,6 @@ where
}
}
#[doc(hidden)]
pub struct NormalizePathNormalization<S> {
service: S,
merge_slash: Regex,
@ -127,9 +130,7 @@ where
type Error = Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
actix_service::forward_ready!(service);
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let head = req.head_mut();
@ -198,7 +199,7 @@ mod tests {
App::new()
.wrap(NormalizePath::default())
.service(web::resource("/").to(HttpResponse::Ok))
.service(web::resource("/v1/something/").to(HttpResponse::Ok)),
.service(web::resource("/v1/something").to(HttpResponse::Ok)),
)
.await;
@ -306,7 +307,7 @@ mod tests {
#[actix_rt::test]
async fn test_in_place_normalization() {
let srv = |req: ServiceRequest| {
assert_eq!("/v1/something/", req.path());
assert_eq!("/v1/something", req.path());
ready(Ok(req.into_response(HttpResponse::Ok().finish())))
};
@ -334,7 +335,7 @@ mod tests {
#[actix_rt::test]
async fn should_normalize_nothing() {
const URI: &str = "/v1/something/";
const URI: &str = "/v1/something";
let srv = |req: ServiceRequest| {
assert_eq!(URI, req.path());
@ -353,10 +354,8 @@ mod tests {
#[actix_rt::test]
async fn should_normalize_no_trail() {
const URI: &str = "/v1/something";
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())))
};
@ -365,7 +364,7 @@ mod tests {
.await
.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();
assert!(res.status().is_success());
}

View File

@ -1,7 +1,9 @@
use std::future::Future;
use std::io::{Read, Write};
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{
future::Future,
io::{Read, Write},
pin::Pin,
task::{Context, Poll},
};
use actix_http::http::header::{
ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
@ -9,15 +11,16 @@ use actix_http::http::header::{
};
use brotli2::write::{BrotliDecoder, BrotliEncoder};
use bytes::Bytes;
use flate2::read::GzDecoder;
use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder};
use flate2::Compression;
use flate2::{
read::GzDecoder,
write::{GzEncoder, ZlibDecoder, ZlibEncoder},
Compression,
};
use futures_util::ready;
use rand::{distributions::Alphanumeric, Rng};
use actix_web::dev::BodyEncoding;
use actix_web::middleware::normalize::TrailingSlash;
use actix_web::middleware::{Compress, NormalizePath};
use actix_web::middleware::{Compress, NormalizePath, TrailingSlash};
use actix_web::{dev, test, web, App, Error, HttpResponse};
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \