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
|
||||
### 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
|
||||
|
10
MIGRATION.md
10
MIGRATION.md
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>> {
|
||||
|
@ -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();
|
@ -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();
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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 \
|
||||
|
Loading…
Reference in New Issue
Block a user