diff --git a/.github/workflows/cov.yml b/.github/workflows/cov.yml index 9853ebde..e7f3a7a7 100644 --- a/.github/workflows/cov.yml +++ b/.github/workflows/cov.yml @@ -30,7 +30,7 @@ jobs: run: cargo +nightly llvm-cov --no-report --all --no-default-features --features="glommio,cookie,url,compress,openssl,rustls,ws,brotli" - name: Code coverage - run: cargo +nightly llvm-cov --no-report --all --doctests --no-default-features --features="tokio,cookie,url,compress,openssl,rustls,ws,brotli" + run: RUST_LOG=trace cargo +nightly llvm-cov --no-report --all --doctests --no-default-features --features="tokio,cookie,url,compress,openssl,rustls,ws,brotli" - name: Generate coverage report run: cargo +nightly llvm-cov report --lcov --output-path lcov.info --ignore-filename-regex="ntex-tokio|ntex-glommio|ntex-async-std" diff --git a/ntex/CHANGES.md b/ntex/CHANGES.md index 2980b2a9..8f1edff8 100644 --- a/ntex/CHANGES.md +++ b/ntex/CHANGES.md @@ -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 diff --git a/ntex/Cargo.toml b/ntex/Cargo.toml index fb73093b..1509c852 100644 --- a/ntex/Cargo.toml +++ b/ntex/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ntex" -version = "2.2.0" +version = "2.3.0" authors = ["ntex contributors "] description = "Framework for composable network services" readme = "README.md" diff --git a/ntex/examples/basic.rs b/ntex/examples/basic.rs index d3e84fca..75b798e3 100644 --- a/ntex/examples/basic.rs +++ b/ntex/examples/basic.rs @@ -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() }), diff --git a/ntex/src/web/app.rs b/ntex/src/web/app.rs index d99852d3..a2a3aa59 100644 --- a/ntex/src/web/app.rs +++ b/ntex/src/web/app.rs @@ -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 = @@ -396,9 +397,9 @@ where /// .route("/index.html", web::get().to(index)); /// } /// ``` - pub fn wrap(self, mw: U) -> App, T, Err> { + pub fn wrap(self, mw: U) -> App, 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, diff --git a/ntex/src/web/error.rs b/ntex/src/web/error.rs index 03bac2a3..d1631ec8 100644 --- a/ntex/src/web/error.rs +++ b/ntex/src/web/error.rs @@ -707,16 +707,14 @@ mod tests { let req = TestRequest::default().to_http_request(); use crate::util::timeout::TimeoutError; - let resp = WebResponseError::::error_response( - &TimeoutError::::Timeout, - &req, - ); + let resp = + Error::from(TimeoutError::::Timeout).error_response(&req); assert_eq!(resp.status(), StatusCode::GATEWAY_TIMEOUT); - let resp = WebResponseError::::error_response( - &TimeoutError::::Service(UrlencodedError::Chunked), - &req, - ); + let resp = Error::from(TimeoutError::::Service( + UrlencodedError::Chunked, + )) + .error_response(&req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let resp = WebResponseError::::error_response( diff --git a/ntex/src/web/error_default.rs b/ntex/src/web/error_default.rs index dfe53783..4a3dd818 100644 --- a/ntex/src/web/error_default.rs +++ b/ntex/src/web/error_default.rs @@ -83,11 +83,14 @@ impl fmt::Debug for Error { } /// Return `GATEWAY_TIMEOUT` for `TimeoutError` -impl> WebResponseError for TimeoutError { - fn status_code(&self) -> StatusCode { - match self { - TimeoutError::Service(e) => e.status_code(), - TimeoutError::Timeout => StatusCode::GATEWAY_TIMEOUT, +impl From> for Error +where + Error: From, +{ + fn from(err: TimeoutError) -> Error { + match err { + TimeoutError::Service(e) => e.into(), + TimeoutError::Timeout => super::error::ErrorGatewayTimeout("").into(), } } } diff --git a/ntex/src/web/mod.rs b/ntex/src/web/mod.rs index 1f885b49..cf1686e0 100644 --- a/ntex/src/web/mod.rs +++ b/ntex/src/web/mod.rs @@ -82,6 +82,7 @@ mod route; mod scope; mod server; mod service; +mod stack; pub mod test; pub mod types; mod util; diff --git a/ntex/src/web/resource.rs b/ntex/src/web/resource.rs index a1c77995..2cfa8419 100644 --- a/ntex/src/web/resource.rs +++ b/ntex/src/web/resource.rs @@ -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 = BoxService, 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(self, mw: U) -> Resource, T> { + pub fn wrap(self, mw: U) -> Resource, T> { Resource { - middleware: Stack::new(self.middleware, mw), + middleware: WebStack::new(self.middleware, mw), filter: self.filter, rdef: self.rdef, name: self.name, diff --git a/ntex/src/web/scope.rs b/ntex/src/web/scope.rs index e501493b..dfe06c33 100644 --- a/ntex/src/web/scope.rs +++ b/ntex/src/web/scope.rs @@ -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>; type HttpService = @@ -342,9 +343,9 @@ where /// WebResponse. /// /// Use middleware when you need to read or modify *every* request in some way. - pub fn wrap(self, mw: U) -> Scope, T> { + pub fn wrap(self, mw: U) -> Scope, T> { Scope { - middleware: Stack::new(self.middleware, mw), + middleware: WebStack::new(self.middleware, mw), filter: self.filter, rdef: self.rdef, state: self.state, diff --git a/ntex/src/web/stack.rs b/ntex/src/web/stack.rs new file mode 100644 index 00000000..e708102b --- /dev/null +++ b/ntex/src/web/stack.rs @@ -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: Inner, + outer: Outer, + err: PhantomData, +} + +impl WebStack { + pub fn new(inner: Inner, outer: Outer) -> Self { + WebStack { + inner, + outer, + err: PhantomData, + } + } +} + +impl Middleware for WebStack +where + Inner: Middleware, + Outer: Middleware, + Outer::Service: Service, Response = WebResponse>, +{ + type Service = WebMiddleware; + + fn create(&self, service: S) -> Self::Service { + WebMiddleware { + svc: self.outer.create(self.inner.create(service)), + err: PhantomData, + } + } +} + +#[derive(Debug)] +pub struct WebMiddleware { + svc: S, + err: PhantomData, +} + +impl Clone for WebMiddleware +where + S: Clone, +{ + fn clone(&self) -> Self { + Self { + svc: self.svc.clone(), + err: PhantomData, + } + } +} + +impl Service> for WebMiddleware +where + S: Service, Response = WebResponse>, + Err: ErrorRenderer, + Err::Container: From, +{ + type Response = WebResponse; + type Error = Err::Container; + + #[inline] + async fn call( + &self, + req: WebRequest, + ctx: ServiceCtx<'_, Self>, + ) -> Result { + ctx.call(&self.svc, req).await.map_err(Into::into) + } + + crate::forward_ready!(svc); + crate::forward_shutdown!(svc); +} diff --git a/ntex/src/web/types/json.rs b/ntex/src/web/types/json.rs index 9e9ca8e9..580b1db8 100644 --- a/ntex/src/web/types/json.rs +++ b/ntex/src/web/types/json.rs @@ -413,6 +413,11 @@ mod tests { assert_eq!(j.name, "test"); assert!(format!("{:?}", j).contains("Json")); assert!(format!("{}", j).contains("test")); + + let cfg = JsonConfig::default().content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + }); + assert!(format!("{:?}", cfg).contains("JsonConfig")); } #[crate::rt_test] diff --git a/ntex/tests/web_server.rs b/ntex/tests/web_server.rs index aefe6682..cbb7956d 100644 --- a/ntex/tests/web_server.rs +++ b/ntex/tests/web_server.rs @@ -1119,6 +1119,7 @@ async fn test_web_server() { ) }) .headers_read_rate(Seconds(1), Seconds(5), 128) + .payload_read_rate(Seconds(1), Seconds(5), 128) .disconnect_timeout(Seconds(1)) .memory_pool(ntex_bytes::PoolId::P1) .listen(tcp)