1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-27 09:42:57 +01:00

add Compat middleware (#1865)

This commit is contained in:
fakeshadow 2021-01-05 08:22:57 +08:00 committed by GitHub
parent 93161df141
commit 4f5971d79e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 203 additions and 2 deletions

View File

@ -1,6 +1,11 @@
# Changes
## 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]
### Changed
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
* Bumped `rand` to `0.8`.
@ -9,7 +14,7 @@
* 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]

192
src/middleware/compat.rs Normal file
View File

@ -0,0 +1,192 @@
//! `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};
use actix_http::body::Body;
use actix_http::body::{MessageBody, ResponseBody};
use actix_service::{Service, Transform};
use futures_core::future::LocalBoxFuture;
use futures_core::ready;
use crate::error::Error;
use crate::service::ServiceResponse;
/// `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `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));
///
/// // by using scoped middleware we can use logger in 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 }
}
}
impl<S, T, Req> Transform<S, Req> for Compat<T>
where
S: Service<Req>,
T: Transform<S, Req>,
T::Future: 'static,
T::Response: MapServiceResponseBody,
Error: From<T::Error>,
{
type Response = ServiceResponse;
type Error = Error;
type Transform = CompatMiddleware<T::Transform>;
type InitError = T::InitError;
type Future = LocalBoxFuture<'static, Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
let fut = self.transform.new_transform(service);
Box::pin(async move {
let service = fut.await?;
Ok(CompatMiddleware { service })
})
}
}
pub struct CompatMiddleware<S> {
service: S,
}
impl<S, Req> Service<Req> for CompatMiddleware<S>
where
S: Service<Req>,
S::Response: MapServiceResponseBody,
Error: From<S::Error>,
{
type Response = ServiceResponse;
type Error = Error;
type Future = CompatMiddlewareFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx).map_err(From::from)
}
fn call(&mut self, req: Req) -> Self::Future {
let fut = self.service.call(req);
CompatMiddlewareFuture { fut }
}
}
#[pin_project::pin_project]
pub struct CompatMiddlewareFuture<Fut> {
#[pin]
fut: Fut,
}
impl<Fut, T, E> Future for CompatMiddlewareFuture<Fut>
where
Fut: Future<Output = Result<T, E>>,
T: MapServiceResponseBody,
Error: From<E>,
{
type Output = Result<ServiceResponse, Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let res = ready!(self.project().fut.poll(cx))?;
Poll::Ready(Ok(res.map_body()))
}
}
// trait for convert ServiceResponse's ResponseBody<B> generic type
// to ResponseBody<Body>
pub trait MapServiceResponseBody {
fn map_body(self) -> ServiceResponse;
}
impl<B: MessageBody + Unpin + 'static> MapServiceResponseBody for ServiceResponse<B> {
fn map_body(self) -> ServiceResponse {
self.map_body(|_, body| ResponseBody::Other(Body::from_message(body)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use actix_service::IntoService;
use crate::dev::ServiceRequest;
use crate::http::StatusCode;
use crate::middleware::{Compress, Condition, Logger};
use crate::test::{call_service, init_service, TestRequest};
use crate::{web, App, HttpResponse};
#[actix_rt::test]
async fn test_scope_middleware() {
let logger = Logger::default();
let compress = Compress::default();
let mut srv = init_service(
App::new().service(
web::scope("app")
.wrap(Compat::new(logger))
.wrap(Compat::new(compress))
.service(
web::resource("/test").route(web::get().to(HttpResponse::Ok)),
),
),
)
.await;
let req = TestRequest::with_uri("/app/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn test_resource_scope_middleware() {
let logger = Logger::default();
let compress = Compress::default();
let mut srv = init_service(
App::new().service(
web::resource("app/test")
.wrap(Compat::new(logger))
.wrap(Compat::new(compress))
.route(web::get().to(HttpResponse::Ok)),
),
)
.await;
let req = TestRequest::with_uri("/app/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn test_condition_scope_middleware() {
let srv = |req: ServiceRequest| {
Box::pin(async move {
Ok(req.into_response(HttpResponse::InternalServerError().finish()))
})
};
let logger = Logger::default();
let mut mw = Condition::new(true, Compat::new(logger))
.new_transform(srv.into_service())
.await
.unwrap();
let resp = call_service(&mut mw, TestRequest::default().to_srv_request()).await;
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}

View File

@ -6,7 +6,9 @@ use futures_util::future::{ok, Either, FutureExt, LocalBoxFuture};
/// `Middleware` for conditionally enables another middleware.
/// The controlled middleware must not change the `Service` interfaces.
/// This means you cannot control such middlewares like `Logger` or `Compress`.
///
/// This means you cannot control such middlewares like `Logger` or `Compress` directly.
/// *. See `Compat` middleware for alternative.
///
/// ## Usage
///

View File

@ -5,12 +5,14 @@ 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;