Allow to use generic middlewares #390

This commit is contained in:
Nikolay Kim 2024-08-13 15:56:10 +05:00
parent 5f20ee2be5
commit ee640ab335
10 changed files with 112 additions and 27 deletions

View file

@ -1,5 +1,9 @@
# Changes
## [2.3.0] - 2024-08-13
* web: Allow to use generic middlewares #390
## [2.2.0] - 2024-08-12
* Http server gracefull shutdown support

View file

@ -1,6 +1,6 @@
[package]
name = "ntex"
version = "2.2.0"
version = "2.3.0"
authors = ["ntex contributors <team@ntex.rs>"]
description = "Framework for composable network services"
readme = "README.md"

View file

@ -28,6 +28,7 @@ async fn main() -> std::io::Result<()> {
.service((index, no_params))
.service(
web::resource("/resource2/index.html")
.wrap(ntex::util::timeout::Timeout::new(ntex::time::Millis(5000)))
.wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"))
.default_service(
web::route().to(|| async { HttpResponse::MethodNotAllowed() }),

View file

@ -6,7 +6,7 @@ use crate::service::boxed::{self, BoxServiceFactory};
use crate::service::{
chain_factory, dev::ServiceChainFactory, map_config, IntoServiceFactory,
};
use crate::service::{Identity, Middleware, Service, ServiceCtx, ServiceFactory, Stack};
use crate::service::{Identity, Middleware, Service, ServiceCtx, ServiceFactory};
use crate::util::{BoxFuture, Extensions};
use super::app_service::{AppFactory, AppService};
@ -16,6 +16,7 @@ use super::resource::Resource;
use super::response::WebResponse;
use super::route::Route;
use super::service::{AppServiceFactory, ServiceFactoryWrapper, WebServiceFactory};
use super::stack::WebStack;
use super::{DefaultError, ErrorRenderer};
type HttpNewService<Err: ErrorRenderer> =
@ -396,9 +397,9 @@ where
/// .route("/index.html", web::get().to(index));
/// }
/// ```
pub fn wrap<U>(self, mw: U) -> App<Stack<M, U>, T, Err> {
pub fn wrap<U>(self, mw: U) -> App<WebStack<M, U, Err>, T, Err> {
App {
middleware: Stack::new(self.middleware, mw),
middleware: WebStack::new(self.middleware, mw),
filter: self.filter,
state_factories: self.state_factories,
services: self.services,

View file

@ -707,16 +707,14 @@ mod tests {
let req = TestRequest::default().to_http_request();
use crate::util::timeout::TimeoutError;
let resp = WebResponseError::<DefaultError>::error_response(
&TimeoutError::<UrlencodedError>::Timeout,
&req,
);
let resp =
Error::from(TimeoutError::<UrlencodedError>::Timeout).error_response(&req);
assert_eq!(resp.status(), StatusCode::GATEWAY_TIMEOUT);
let resp = WebResponseError::<DefaultError>::error_response(
&TimeoutError::<UrlencodedError>::Service(UrlencodedError::Chunked),
&req,
);
let resp = Error::from(TimeoutError::<UrlencodedError>::Service(
UrlencodedError::Chunked,
))
.error_response(&req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp = WebResponseError::<DefaultError>::error_response(

View file

@ -83,11 +83,14 @@ impl fmt::Debug for Error {
}
/// Return `GATEWAY_TIMEOUT` for `TimeoutError`
impl<E: WebResponseError<DefaultError>> WebResponseError<DefaultError> for TimeoutError<E> {
fn status_code(&self) -> StatusCode {
match self {
TimeoutError::Service(e) => e.status_code(),
TimeoutError::Timeout => StatusCode::GATEWAY_TIMEOUT,
impl<E> From<TimeoutError<E>> for Error
where
Error: From<E>,
{
fn from(err: TimeoutError<E>) -> Error {
match err {
TimeoutError::Service(e) => e.into(),
TimeoutError::Timeout => super::error::ErrorGatewayTimeout("").into(),
}
}
}

View file

@ -82,6 +82,7 @@ mod route;
mod scope;
mod server;
mod service;
mod stack;
pub mod test;
pub mod types;
mod util;

View file

@ -5,18 +5,16 @@ use crate::router::{IntoPattern, ResourceDef};
use crate::service::boxed::{self, BoxService, BoxServiceFactory};
use crate::service::dev::{AndThen, ServiceChain, ServiceChainFactory};
use crate::service::{chain, chain_factory, ServiceCtx};
use crate::service::{
Identity, IntoServiceFactory, Middleware, Service, ServiceFactory, Stack,
};
use crate::service::{Identity, IntoServiceFactory, Middleware, Service, ServiceFactory};
use crate::util::Extensions;
use super::dev::{insert_slash, WebServiceConfig, WebServiceFactory};
use super::extract::FromRequest;
use super::handler::Handler;
use super::request::WebRequest;
use super::response::WebResponse;
use super::route::{IntoRoutes, Route, RouteService};
use super::stack::WebStack;
use super::{app::Filter, error::ErrorRenderer, guard::Guard, service::AppState};
use super::{request::WebRequest, response::WebResponse};
type HttpService<Err: ErrorRenderer> =
BoxService<WebRequest<Err>, WebResponse, Err::Container>;
@ -276,9 +274,9 @@ where
/// type (i.e modify response's body).
///
/// **Note**: middlewares get called in opposite order of middlewares registration.
pub fn wrap<U>(self, mw: U) -> Resource<Err, Stack<M, U>, T> {
pub fn wrap<U>(self, mw: U) -> Resource<Err, WebStack<M, U, Err>, T> {
Resource {
middleware: Stack::new(self.middleware, mw),
middleware: WebStack::new(self.middleware, mw),
filter: self.filter,
rdef: self.rdef,
name: self.name,

View file

@ -4,7 +4,7 @@ use crate::http::Response;
use crate::router::{IntoPattern, ResourceDef, Router};
use crate::service::boxed::{self, BoxService, BoxServiceFactory};
use crate::service::{chain_factory, dev::ServiceChainFactory, IntoServiceFactory};
use crate::service::{Identity, Middleware, Service, ServiceCtx, ServiceFactory, Stack};
use crate::service::{Identity, Middleware, Service, ServiceCtx, ServiceFactory};
use crate::util::{join, Extensions};
use super::app::Filter;
@ -18,6 +18,7 @@ use super::response::WebResponse;
use super::rmap::ResourceMap;
use super::route::Route;
use super::service::{AppServiceFactory, AppState, ServiceFactoryWrapper};
use super::stack::WebStack;
type Guards = Vec<Box<dyn Guard>>;
type HttpService<Err: ErrorRenderer> =
@ -342,9 +343,9 @@ where
/// WebResponse.
///
/// Use middleware when you need to read or modify *every* request in some way.
pub fn wrap<U>(self, mw: U) -> Scope<Err, Stack<M, U>, T> {
pub fn wrap<U>(self, mw: U) -> Scope<Err, WebStack<M, U, Err>, T> {
Scope {
middleware: Stack::new(self.middleware, mw),
middleware: WebStack::new(self.middleware, mw),
filter: self.filter,
rdef: self.rdef,
state: self.state,

78
ntex/src/web/stack.rs Normal file
View file

@ -0,0 +1,78 @@
use std::marker::PhantomData;
use crate::service::{Middleware, Service, ServiceCtx};
use crate::web::{ErrorRenderer, WebRequest, WebResponse};
/// Stack of middlewares.
#[derive(Debug, Clone)]
pub struct WebStack<Inner, Outer, Err> {
inner: Inner,
outer: Outer,
err: PhantomData<Err>,
}
impl<Inner, Outer, Err> WebStack<Inner, Outer, Err> {
pub fn new(inner: Inner, outer: Outer) -> Self {
WebStack {
inner,
outer,
err: PhantomData,
}
}
}
impl<S, Inner, Outer, Err> Middleware<S> for WebStack<Inner, Outer, Err>
where
Inner: Middleware<S>,
Outer: Middleware<Inner::Service>,
Outer::Service: Service<WebRequest<Err>, Response = WebResponse>,
{
type Service = WebMiddleware<Outer::Service, Err>;
fn create(&self, service: S) -> Self::Service {
WebMiddleware {
svc: self.outer.create(self.inner.create(service)),
err: PhantomData,
}
}
}
#[derive(Debug)]
pub struct WebMiddleware<S, Err> {
svc: S,
err: PhantomData<Err>,
}
impl<S, Err> Clone for WebMiddleware<S, Err>
where
S: Clone,
{
fn clone(&self) -> Self {
Self {
svc: self.svc.clone(),
err: PhantomData,
}
}
}
impl<S, Err> Service<WebRequest<Err>> for WebMiddleware<S, Err>
where
S: Service<WebRequest<Err>, Response = WebResponse>,
Err: ErrorRenderer,
Err::Container: From<S::Error>,
{
type Response = WebResponse;
type Error = Err::Container;
#[inline]
async fn call(
&self,
req: WebRequest<Err>,
ctx: ServiceCtx<'_, Self>,
) -> Result<Self::Response, Self::Error> {
ctx.call(&self.svc, req).await.map_err(Into::into)
}
crate::forward_ready!(svc);
crate::forward_shutdown!(svc);
}