Refactor web middleware registration and management procedure

This commit is contained in:
Nikolay Kim 2021-09-15 23:21:50 +06:00
parent fe13bf8281
commit 5ccd6ffca2
25 changed files with 235 additions and 483 deletions

View file

@ -1,5 +1,9 @@
# Changes
## [0.4.0] - 2021-09-15
* Refactor web middleware registration and management procedure.
## [0.4.0-b.13] - 2021-09-12
* Fix update timer wheel bucket calculation

View file

@ -1,6 +1,6 @@
[package]
name = "ntex"
version = "0.4.0-b.13"
version = "0.4.0"
authors = ["ntex contributors <team@ntex.rs>"]
description = "Framework for composable network services"
readme = "README.md"
@ -46,7 +46,7 @@ http-framework = ["h2", "http", "httparse",
ntex-codec = "0.5.1"
ntex-rt = "0.3.1"
ntex-router = "0.5.1"
ntex-service = "0.2.0-b.0"
ntex-service = "0.2.0"
ntex-macros = "0.1.3"
ntex-util = "0.1.1"
ntex-bytes = "0.1.4"

View file

@ -114,7 +114,7 @@ mod tests {
#[crate::rt_test]
async fn test_openssl_connect() {
let server = crate::server::test_server(|| {
crate::fn_service(|_| async { Ok::<_, ()>(()) })
crate::service::fn_service(|_| async { Ok::<_, ()>(()) })
});
let ssl = SslConnector::builder(SslMethod::tls()).unwrap();

View file

@ -109,7 +109,7 @@ mod tests {
#[crate::rt_test]
async fn test_rustls_connect() {
let server = crate::server::test_server(|| {
crate::fn_service(|_| async { Ok::<_, ()>(()) })
crate::service::fn_service(|_| async { Ok::<_, ()>(()) })
});
let mut config = ClientConfig::new();

View file

@ -227,7 +227,7 @@ mod tests {
#[crate::rt_test]
async fn test_connect() {
let server = crate::server::test_server(|| {
crate::fn_service(|_| async { Ok::<_, ()>(()) })
crate::service::fn_service(|_| async { Ok::<_, ()>(()) })
});
let srv = Connector::default();

View file

@ -580,7 +580,7 @@ mod tests {
let (disp, _) = Dispatcher::debug(
server,
BytesCodec,
crate::fn_service(|msg: DispatchItem<BytesCodec>| async move {
crate::service::fn_service(|msg: DispatchItem<BytesCodec>| async move {
sleep(Millis(50)).await;
if let DispatchItem::Item(msg) = msg {
Ok::<_, ()>(Some(msg.freeze()))
@ -609,7 +609,7 @@ mod tests {
let (disp, st) = Dispatcher::debug(
server,
BytesCodec,
crate::fn_service(|msg: DispatchItem<BytesCodec>| async move {
crate::service::fn_service(|msg: DispatchItem<BytesCodec>| async move {
if let DispatchItem::Item(msg) = msg {
Ok::<_, ()>(Some(msg.freeze()))
} else {
@ -645,7 +645,7 @@ mod tests {
let (disp, state) = Dispatcher::debug(
server,
BytesCodec,
crate::fn_service(|_: DispatchItem<BytesCodec>| async move {
crate::service::fn_service(|_: DispatchItem<BytesCodec>| async move {
Err::<Option<Bytes>, _>(())
}),
);
@ -686,7 +686,7 @@ mod tests {
let (disp, state) = Dispatcher::debug(
server,
BytesCodec,
crate::fn_service(move |msg: DispatchItem<BytesCodec>| {
crate::service::fn_service(move |msg: DispatchItem<BytesCodec>| {
let data = data2.clone();
async move {
match msg {
@ -754,7 +754,7 @@ mod tests {
let (disp, state) = Dispatcher::debug(
server,
BytesCodec,
crate::fn_service(move |msg: DispatchItem<BytesCodec>| {
crate::service::fn_service(move |msg: DispatchItem<BytesCodec>| {
let data = data2.clone();
async move {
match msg {
@ -805,7 +805,7 @@ mod tests {
let (disp, _) = Dispatcher::debug(
server,
BytesCodec,
crate::fn_service(move |msg: DispatchItem<BytesCodec>| {
crate::service::fn_service(move |msg: DispatchItem<BytesCodec>| {
handled2.store(true, Relaxed);
async move {
sleep(Millis(50)).await;

View file

@ -815,10 +815,12 @@ mod tests {
}),
ExpectHandler,
None,
Some(boxed::service(crate::into_service(move |(req, _)| {
data2.set(true);
Box::pin(async move { Ok(req) })
}))),
Some(boxed::service(crate::service::into_service(
move |(req, _)| {
data2.set(true);
Box::pin(async move { Ok(req) })
},
))),
)),
None,
None,

View file

@ -11,10 +11,8 @@ use crate::http::error::{DispatchError, ResponseError};
use crate::http::helpers::DataFactory;
use crate::http::request::Request;
use crate::http::response::Response;
use crate::rt::net::TcpStream;
use crate::{
pipeline_factory, time::Millis, IntoServiceFactory, Service, ServiceFactory,
};
use crate::service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
use crate::{rt::net::TcpStream, time::Millis};
use super::codec::Codec;
use super::dispatcher::Dispatcher;

View file

@ -12,12 +12,12 @@ use crate::http::helpers::DataFactory;
use crate::http::request::Request;
use crate::http::response::Response;
use crate::rt::net::TcpStream;
use crate::time::Millis;
use crate::util::Bytes;
use crate::{
use crate::service::{
fn_factory, fn_service, pipeline_factory, IntoServiceFactory, Service,
ServiceFactory,
};
use crate::time::Millis;
use crate::util::Bytes;
use super::dispatcher::Dispatcher;
@ -99,7 +99,7 @@ mod openssl {
use crate::server::SslError;
use super::*;
use crate::{fn_factory, fn_service};
use crate::service::{fn_factory, fn_service};
impl<S, B> H2Service<SslStream<TcpStream>, S, B>
where

View file

@ -46,7 +46,9 @@ pub mod util;
pub mod web;
pub mod ws;
pub use self::service::*;
pub use self::service::{
IntoService, IntoServiceFactory, Service, ServiceFactory, Transform,
};
pub use futures_core::stream::Stream;
pub use futures_sink::Sink;

View file

@ -626,6 +626,7 @@ impl Future for LowresTimerDriver {
#[cfg(test)]
mod tests {
use super::*;
use crate::time::Millis;
#[crate::rt_test]
async fn test_timer() {

View file

@ -46,9 +46,9 @@ impl<S, E> Transform<S> for Buffer<E>
where
S: Service<Error = E>,
{
type Transform = BufferService<S, E>;
type Service = BufferService<S, E>;
fn new_transform(&self, service: S) -> Self::Transform {
fn new_transform(&self, service: S) -> Self::Service {
BufferService {
size: self.buf_size,
inner: Rc::new(Inner {

View file

@ -28,9 +28,9 @@ impl<S> Transform<S> for InFlight
where
S: Service,
{
type Transform = InFlightService<S>;
type Service = InFlightService<S>;
fn new_transform(&self, service: S) -> Self::Transform {
fn new_transform(&self, service: S) -> Self::Service {
InFlightService::new(self.max_inflight, service)
}
}

View file

@ -180,7 +180,7 @@ mod tests {
let disp = Dispatcher::new(
decoder,
encoder,
crate::fn_service(move |_| {
crate::service::fn_service(move |_| {
counter2.set(counter2.get() + 1);
async { Ok(Some(ws::Message::Text(ByteString::from_static("test")))) }
}),

View file

@ -86,9 +86,9 @@ impl<S> Transform<S> for Timeout
where
S: Service,
{
type Transform = TimeoutService<S>;
type Service = TimeoutService<S>;
fn new_transform(&self, service: S) -> Self::Transform {
fn new_transform(&self, service: S) -> Self::Service {
TimeoutService {
service,
timeout: self.timeout,

View file

@ -3,8 +3,8 @@ use std::{cell::RefCell, fmt, future::Future, pin::Pin, rc::Rc};
use crate::http::Request;
use crate::router::ResourceDef;
use crate::service::boxed::{self, BoxServiceFactory};
use crate::service::{apply, apply_fn_factory, pipeline_factory};
use crate::service::{IntoServiceFactory, Service, ServiceFactory, Transform};
use crate::service::{map_config, pipeline_factory};
use crate::service::{Identity, IntoServiceFactory, Service, ServiceFactory, Transform};
use crate::util::{Either, Extensions, Ready};
use super::app_service::{AppEntry, AppFactory, AppRoutingFactory};
@ -24,8 +24,9 @@ type FnDataFactory =
/// Application builder - structure that follows the builder pattern
/// for building application instances.
pub struct App<T, Err: ErrorRenderer = DefaultError> {
endpoint: T,
pub struct App<M, S, Err: ErrorRenderer = DefaultError> {
middleware: M,
endpoint: S,
services: Vec<Box<dyn AppServiceFactory<Err>>>,
default: Option<Rc<HttpNewService<Err>>>,
factory_ref: Rc<RefCell<Option<AppRoutingFactory<Err>>>>,
@ -37,11 +38,12 @@ pub struct App<T, Err: ErrorRenderer = DefaultError> {
case_insensitive: bool,
}
impl App<AppEntry<DefaultError>, DefaultError> {
impl App<Identity, AppEntry<DefaultError>, DefaultError> {
/// Create application builder. Application can be configured with a builder-like pattern.
pub fn new() -> Self {
let fref = Rc::new(RefCell::new(None));
App {
middleware: Identity,
endpoint: AppEntry::new(fref.clone()),
data: Vec::new(),
data_factories: Vec::new(),
@ -56,11 +58,12 @@ impl App<AppEntry<DefaultError>, DefaultError> {
}
}
impl<Err: ErrorRenderer> App<AppEntry<Err>, Err> {
impl<Err: ErrorRenderer> App<Identity, AppEntry<Err>, Err> {
/// Create application builder with custom error renderer.
pub fn with(err: Err) -> Self {
let fref = Rc::new(RefCell::new(None));
App {
middleware: Identity,
endpoint: AppEntry::new(fref.clone()),
data: Vec::new(),
data_factories: Vec::new(),
@ -75,16 +78,16 @@ impl<Err: ErrorRenderer> App<AppEntry<Err>, Err> {
}
}
impl<T, Err> App<T, Err>
impl<M, S, Err> App<M, S, Err>
where
T: ServiceFactory<
S: ServiceFactory<
Config = (),
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
InitError = (),
>,
T::Future: 'static,
S::Future: 'static,
Err: ErrorRenderer,
{
/// Set application data. Application data could be accessed
@ -359,6 +362,7 @@ where
self,
filter: F,
) -> App<
M,
impl ServiceFactory<
Config = (),
Request = WebRequest<Err>,
@ -388,6 +392,7 @@ where
App {
endpoint,
middleware: self.middleware,
data: self.data,
data_factories: self.data_factories,
services: self.services,
@ -410,10 +415,7 @@ where
///
/// Notice that the keyword for registering middleware is `wrap`. As you
/// register middleware using `wrap` in the App builder, imagine wrapping
/// layers around an inner App. The first middleware layer exposed to a
/// Request is the outermost layer-- the *last* registered in
/// the builder chain. Consequently, the *first* middleware registered
/// in the builder chain is the *last* to execute during request processing.
/// layers around an inner App.
///
/// ```rust
/// use ntex::http::header::{CONTENT_TYPE, HeaderValue};
@ -429,90 +431,10 @@ where
/// .route("/index.html", web::get().to(index));
/// }
/// ```
pub fn wrap<M>(
self,
mw: M,
) -> App<
impl ServiceFactory<
Config = (),
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
InitError = (),
>,
Err,
>
where
M: Transform<T::Service>,
M::Transform: Service<
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
>,
{
pub fn wrap<U>(self, mw: U) -> App<Stack<M, U>, S, Err> {
App {
endpoint: apply(mw, self.endpoint),
data: self.data,
data_factories: self.data_factories,
services: self.services,
default: self.default,
factory_ref: self.factory_ref,
external: self.external,
extensions: self.extensions,
error_renderer: self.error_renderer,
case_insensitive: self.case_insensitive,
}
}
/// Registers middleware, in the form of a closure, that runs during inbound
/// and/or outbound processing in the request lifecycle (request -> response),
/// modifying request/response as necessary, across all requests managed by
/// the *Application*.
///
/// Use middleware when you need to read or modify *every* request or response in some way.
///
/// ```rust
/// use ntex::{web, Service};
/// use ntex::http::header::{CONTENT_TYPE, HeaderValue};
///
/// async fn index() -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = web::App::new()
/// .wrap_fn(|req, srv| {
/// let fut = srv.call(req);
/// async {
/// let mut res = fut.await?;
/// res.headers_mut().insert(
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
/// );
/// Ok(res)
/// }
/// })
/// .route("/index.html", web::get().to(index));
/// }
/// ```
pub fn wrap_fn<F, R>(
self,
mw: F,
) -> App<
impl ServiceFactory<
Config = (),
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
InitError = (),
>,
Err,
>
where
F: Fn(WebRequest<Err>, &T::Service) -> R + Clone,
R: Future<Output = Result<WebResponse, Err::Container>>,
{
App {
endpoint: apply_fn_factory(self.endpoint, mw),
middleware: Stack::new(self.middleware, mw),
endpoint: self.endpoint,
data: self.data,
data_factories: self.data_factories,
services: self.services,
@ -532,7 +454,26 @@ where
self.case_insensitive = true;
self
}
}
impl<M, S, Err> App<M, S, Err>
where
M: Transform<S::Service> + 'static,
M::Service: Service<
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
>,
S: ServiceFactory<
Config = (),
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
InitError = (),
>,
S::Future: 'static,
Err: ErrorRenderer,
{
/// Construct service factory with default `AppConfig`, suitable for `http::HttpService`.
///
/// ```rust,no_run
@ -557,10 +498,10 @@ where
Config = (),
Request = Request,
Response = WebResponse,
Error = T::Error,
InitError = T::InitError,
Error = S::Error,
InitError = S::InitError,
> {
crate::map_config(self.into_factory(), move |_| Default::default())
map_config(self.into_factory(), move |_| Default::default())
}
/// Construct service factory suitable for `http::HttpService`.
@ -588,27 +529,34 @@ where
Config = (),
Request = Request,
Response = WebResponse,
Error = T::Error,
InitError = T::InitError,
Error = S::Error,
InitError = S::InitError,
> {
crate::map_config(self.into_factory(), move |_| cfg.clone())
map_config(self.into_factory(), move |_| cfg.clone())
}
}
impl<T, Err> IntoServiceFactory<AppFactory<T, Err>> for App<T, Err>
impl<M, S, Err> IntoServiceFactory<AppFactory<M, S, Err>> for App<M, S, Err>
where
T: ServiceFactory<
M: Transform<S::Service> + 'static,
M::Service: Service<
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
>,
S: ServiceFactory<
Config = (),
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
InitError = (),
>,
T::Future: 'static,
S::Future: 'static,
Err: ErrorRenderer,
{
fn into_factory(self) -> AppFactory<T, Err> {
fn into_factory(self) -> AppFactory<M, S, Err> {
AppFactory {
middleware: Rc::new(self.middleware),
data: Rc::new(self.data),
data_factories: Rc::new(self.data_factories),
endpoint: self.endpoint,
@ -622,16 +570,41 @@ where
}
}
pub struct Stack<Inner, Outer> {
inner: Inner,
outer: Outer,
}
impl<Inner, Outer> Stack<Inner, Outer> {
pub(super) fn new(inner: Inner, outer: Outer) -> Self {
Stack { inner, outer }
}
}
impl<S, Inner, Outer> Transform<S> for Stack<Inner, Outer>
where
Inner: Transform<S>,
Outer: Transform<Inner::Service>,
{
type Service = Outer::Service;
fn new_transform(&self, service: S) -> Self::Service {
self.outer.new_transform(self.inner.new_transform(service))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::http::header::{self, HeaderValue};
use crate::http::{Method, StatusCode};
use crate::web::middleware::DefaultHeaders;
use crate::web::request::WebRequest;
use crate::service::{fn_service, Service};
use crate::util::Bytes;
use crate::web::test::{call_service, init_service, read_body, TestRequest};
use crate::web::{self, DefaultError, HttpRequest, HttpResponse};
use crate::{fn_service, util::Bytes, Service};
use crate::web::{
self, middleware::DefaultHeaders, request::WebRequest, DefaultError,
HttpRequest, HttpResponse,
};
#[crate::rt_test]
async fn test_default_resource() {
@ -777,60 +750,6 @@ mod tests {
);
}
#[crate::rt_test]
async fn test_wrap_fn() {
let srv = init_service(
App::new()
.wrap_fn(|req, srv| {
let fut = srv.call(req);
async move {
let mut res = fut.await?;
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
);
Ok(res)
}
})
.service(web::resource("/test").to(|| async { HttpResponse::Ok() })),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
}
#[crate::rt_test]
async fn test_router_wrap_fn() {
let srv = init_service(
App::new()
.route("/test", web::get().to(|| async { HttpResponse::Ok() }))
.wrap_fn(|req, srv| {
let fut = srv.call(req);
async {
let mut res = fut.await?;
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
);
Ok(res)
}
}),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
}
#[crate::rt_test]
async fn test_case_insensitive_router() {
let srv = init_service(

View file

@ -4,8 +4,8 @@ use std::{cell::RefCell, future::Future, marker::PhantomData, pin::Pin, rc::Rc};
use crate::http::{Request, Response};
use crate::router::{Path, ResourceDef, ResourceInfo, Router};
use crate::service::boxed::{self, BoxService, BoxServiceFactory};
use crate::service::{fn_service, Service, ServiceFactory, Transform};
use crate::util::Extensions;
use crate::{fn_service, Service, ServiceFactory};
use super::config::AppConfig;
use super::error::ErrorRenderer;
@ -29,19 +29,20 @@ type FnDataFactory =
/// Service factory to convert `Request` to a `WebRequest<S>`.
/// It also executes data factories.
pub struct AppFactory<T, Err: ErrorRenderer>
pub struct AppFactory<T, S, Err: ErrorRenderer>
where
T: ServiceFactory<
S: ServiceFactory<
Config = (),
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
InitError = (),
>,
T::Future: 'static,
S::Future: 'static,
Err: ErrorRenderer,
{
pub(super) endpoint: T,
pub(super) middleware: Rc<T>,
pub(super) endpoint: S,
pub(super) extensions: RefCell<Option<Extensions>>,
pub(super) data: Rc<Vec<Box<dyn DataFactory>>>,
pub(super) data_factories: Rc<Vec<FnDataFactory>>,
@ -52,23 +53,29 @@ where
pub(super) case_insensitive: bool,
}
impl<T, Err> ServiceFactory for AppFactory<T, Err>
impl<T, S, Err> ServiceFactory for AppFactory<T, S, Err>
where
T: ServiceFactory<
T: Transform<S::Service> + 'static,
T::Service: Service<
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
>,
S: ServiceFactory<
Config = (),
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
InitError = (),
>,
T::Future: 'static,
S::Future: 'static,
Err: ErrorRenderer,
{
type Config = AppConfig;
type Request = Request;
type Response = WebResponse;
type Error = T::Error;
type InitError = T::InitError;
type Error = S::Error;
type InitError = S::InitError;
type Service = AppFactoryService<T::Service, Err>;
type Future = Pin<Box<dyn Future<Output = Result<Self::Service, Self::InitError>>>>;
@ -127,6 +134,7 @@ where
.borrow_mut()
.take()
.unwrap_or_else(Extensions::new);
let middleware = self.middleware.clone();
Box::pin(async move {
// main service
@ -145,9 +153,9 @@ where
}
Ok(AppFactoryService {
service,
rmap,
config,
service: middleware.new_transform(service),
data: Rc::new(extensions),
pool: HttpRequestPool::create(),
_t: PhantomData,

View file

@ -50,9 +50,9 @@ where
S: Service<Request = WebRequest<E>, Response = WebResponse>,
E: ErrorRenderer,
{
type Transform = CompressMiddleware<S, E>;
type Service = CompressMiddleware<S, E>;
fn new_transform(&self, service: S) -> Self::Transform {
fn new_transform(&self, service: S) -> Self::Service {
CompressMiddleware {
service,
encoding: self.enc,

View file

@ -91,9 +91,9 @@ where
S: Service<Request = WebRequest<E>, Response = WebResponse>,
S::Future: 'static,
{
type Transform = DefaultHeadersMiddleware<S, E>;
type Service = DefaultHeadersMiddleware<S, E>;
fn new_transform(&self, service: S) -> Self::Transform {
fn new_transform(&self, service: S) -> Self::Service {
DefaultHeadersMiddleware {
service,
inner: self.inner.clone(),

View file

@ -118,9 +118,9 @@ impl<S, Err> Transform<S> for Logger
where
S: Service<Request = WebRequest<Err>, Response = WebResponse>,
{
type Transform = LoggerMiddleware<S>;
type Service = LoggerMiddleware<S>;
fn new_transform(&self, service: S) -> Self::Transform {
fn new_transform(&self, service: S) -> Self::Service {
LoggerMiddleware {
service,
inner: self.inner.clone(),

View file

@ -5,8 +5,8 @@ use std::{
use crate::http::Response;
use crate::router::{IntoPattern, ResourceDef};
use crate::service::boxed::{self, BoxService, BoxServiceFactory};
use crate::service::{apply, apply_fn_factory, pipeline_factory};
use crate::service::{IntoServiceFactory, Service, ServiceFactory, Transform};
use crate::service::{apply, dev::ApplyTransform, pipeline_factory};
use crate::service::{Identity, IntoServiceFactory, Service, ServiceFactory, Transform};
use crate::util::{Either, Extensions, Ready};
use super::dev::{insert_slesh, WebServiceConfig, WebServiceFactory};
@ -18,7 +18,7 @@ use super::request::WebRequest;
use super::responder::Responder;
use super::response::WebResponse;
use super::route::{IntoRoutes, Route, RouteService};
use super::types::Data;
use super::{app::Stack, types::Data};
type HttpService<Err: ErrorRenderer> =
BoxService<WebRequest<Err>, WebResponse, Err::Container>;
@ -47,8 +47,9 @@ type HttpNewService<Err: ErrorRenderer> =
///
/// If no matching route could be found, *405* response code get returned.
/// Default behavior could be overriden with `default_resource()` method.
pub struct Resource<Err: ErrorRenderer, T = ResourceEndpoint<Err>> {
pub struct Resource<Err: ErrorRenderer, T = ResourceEndpoint<Err>, U = Identity> {
endpoint: T,
middleware: U,
rdef: Vec<String>,
name: Option<String>,
routes: Vec<Route<Err>>,
@ -67,6 +68,7 @@ impl<Err: ErrorRenderer> Resource<Err> {
rdef: path.patterns(),
name: None,
endpoint: ResourceEndpoint::new(fref.clone()),
middleware: Identity,
factory_ref: fref,
guards: Vec::new(),
data: None,
@ -75,7 +77,7 @@ impl<Err: ErrorRenderer> Resource<Err> {
}
}
impl<Err, T> Resource<Err, T>
impl<Err, T, U> Resource<Err, T, U>
where
T: ServiceFactory<
Config = (),
@ -163,9 +165,9 @@ where
/// # async fn post_handler() -> web::HttpResponseBuilder { web::HttpResponse::Ok() }
/// # async fn delete_handler() -> web::HttpResponseBuilder { web::HttpResponse::Ok() }
/// ```
pub fn route<U>(mut self, route: U) -> Self
pub fn route<R>(mut self, route: R) -> Self
where
U: IntoRoutes<Err>,
R: IntoRoutes<Err>,
{
for route in route.routes() {
self.routes.push(route);
@ -198,14 +200,14 @@ where
/// ));
/// }
/// ```
pub fn data<U: 'static>(self, data: U) -> Self {
pub fn data<D: 'static>(self, data: D) -> Self {
self.app_data(Data::new(data))
}
/// Set or override application data.
///
/// This method overrides data stored with [`App::app_data()`](#method.app_data)
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
pub fn app_data<D: 'static>(mut self, data: D) -> Self {
if self.data.is_none() {
self.data = Some(Extensions::new());
}
@ -258,6 +260,7 @@ where
Error = Err::Container,
InitError = (),
>,
U,
>
where
F: ServiceFactory<
@ -279,6 +282,7 @@ where
Resource {
endpoint,
middleware: self.middleware,
rdef: self.rdef,
name: self.name,
guards: self.guards,
@ -296,92 +300,10 @@ where
/// type (i.e modify response's body).
///
/// **Note**: middlewares get called in opposite order of middlewares registration.
pub fn wrap<M>(
self,
mw: M,
) -> Resource<
Err,
impl ServiceFactory<
Config = (),
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
InitError = (),
>,
>
where
M: Transform<T::Service>,
M::Transform: Service<
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
>,
{
pub fn wrap<M>(self, mw: M) -> Resource<Err, T, Stack<U, M>> {
Resource {
endpoint: apply(mw, self.endpoint),
rdef: self.rdef,
name: self.name,
guards: self.guards,
routes: self.routes,
default: self.default,
data: self.data,
factory_ref: self.factory_ref,
}
}
/// Register a resource middleware function.
///
/// This function accepts instance of `WebRequest` type and
/// mutable reference to the next middleware in chain.
///
/// This is similar to `App's` middlewares, but middleware get invoked on resource level.
/// Resource level middlewares are not allowed to change response
/// type (i.e modify response's body).
///
/// ```rust
/// use ntex::service::Service;
/// use ntex::web::{self, App};
/// use ntex::http::header::{CONTENT_TYPE, HeaderValue};
///
/// async fn index() -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html")
/// .wrap_fn(|req, srv| {
/// let fut = srv.call(req);
/// async {
/// let mut res = fut.await?;
/// res.headers_mut().insert(
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
/// );
/// Ok(res)
/// }
/// })
/// .route(web::get().to(index)));
/// }
/// ```
pub fn wrap_fn<F, R>(
self,
mw: F,
) -> Resource<
Err,
impl ServiceFactory<
Config = (),
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
InitError = (),
>,
>
where
F: Fn(WebRequest<Err>, &T::Service) -> R + Clone,
R: Future<Output = Result<WebResponse, Err::Container>>,
{
Resource {
endpoint: apply_fn_factory(self.endpoint, mw),
middleware: Stack::new(self.middleware, mw),
endpoint: self.endpoint,
rdef: self.rdef,
name: self.name,
guards: self.guards,
@ -395,16 +317,16 @@ where
/// Default service to be used if no matching route could be found.
/// By default *405* response get returned. Resource does not use
/// default handler from `App` or `Scope`.
pub fn default_service<F, U>(mut self, f: F) -> Self
pub fn default_service<F, S>(mut self, f: F) -> Self
where
F: IntoServiceFactory<U>,
U: ServiceFactory<
F: IntoServiceFactory<S>,
S: ServiceFactory<
Config = (),
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
> + 'static,
U::InitError: fmt::Debug,
S::InitError: fmt::Debug,
{
// create and configure default resource
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
@ -417,7 +339,7 @@ where
}
}
impl<Err, T> WebServiceFactory<Err> for Resource<Err, T>
impl<Err, T, U> WebServiceFactory<Err> for Resource<Err, T, U>
where
T: ServiceFactory<
Config = (),
@ -426,6 +348,12 @@ where
Error = Err::Container,
InitError = (),
> + 'static,
U: Transform<T::Service> + 'static,
U::Service: Service<
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
>,
Err: ErrorRenderer,
{
fn register(mut self, config: &mut WebServiceConfig<Err>) {
@ -446,11 +374,23 @@ where
if let Some(ref mut ext) = self.data {
config.set_service_data(ext);
}
config.register_service(rdef, guards, self, None)
*self.factory_ref.borrow_mut() = Some(ResourceFactory {
routes: self.routes,
data: self.data.map(Rc::new),
default: self.default,
});
config.register_service(
rdef,
guards,
apply(self.middleware, self.endpoint),
None,
)
}
}
impl<Err, T> IntoServiceFactory<T> for Resource<Err, T>
impl<Err, T, U> IntoServiceFactory<ApplyTransform<U, T>> for Resource<Err, T, U>
where
T: ServiceFactory<
Config = (),
@ -459,16 +399,22 @@ where
Error = Err::Container,
InitError = (),
>,
U: Transform<T::Service> + 'static,
U::Service: Service<
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
>,
Err: ErrorRenderer,
{
fn into_factory(self) -> T {
fn into_factory(self) -> ApplyTransform<U, T> {
*self.factory_ref.borrow_mut() = Some(ResourceFactory {
routes: self.routes,
data: self.data.map(Rc::new),
default: self.default,
});
self.endpoint
apply(self.middleware, self.endpoint)
}
}
@ -579,10 +525,11 @@ mod tests {
use crate::http::{Method, StatusCode};
use crate::time::{sleep, Millis};
use crate::web::middleware::DefaultHeaders;
use crate::web::request::WebRequest;
use crate::web::test::{call_service, init_service, TestRequest};
use crate::web::{self, guard, App, DefaultError, HttpResponse};
use crate::{fn_service, util::Either, Service};
use crate::web::{
self, guard, request::WebRequest, App, DefaultError, HttpResponse,
};
use crate::{service::fn_service, util::Either};
#[crate::rt_test]
async fn test_filter() {
@ -625,36 +572,6 @@ mod tests {
);
}
#[crate::rt_test]
async fn test_middleware_fn() {
let srv = init_service(
App::new().service(
web::resource("/test")
.wrap_fn(|req, srv| {
let fut = srv.call(req);
async {
fut.await.map(|mut res| {
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
);
res
})
}
})
.route(web::get().to(|| async { HttpResponse::Ok() })),
),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
}
#[crate::rt_test]
async fn test_to() {
let srv =

View file

@ -5,10 +5,11 @@ use std::{
use crate::http::Response;
use crate::router::{IntoPattern, ResourceDef, ResourceInfo, Router};
use crate::service::boxed::{self, BoxService, BoxServiceFactory};
use crate::service::{apply, apply_fn_factory, pipeline_factory};
use crate::service::{IntoServiceFactory, Service, ServiceFactory, Transform};
use crate::service::{apply, pipeline_factory};
use crate::service::{Identity, IntoServiceFactory, Service, ServiceFactory, Transform};
use crate::util::{Either, Extensions, Ready};
use super::app::Stack;
use super::config::ServiceConfig;
use super::dev::{WebServiceConfig, WebServiceFactory};
use super::error::ErrorRenderer;
@ -58,8 +59,9 @@ type BoxedResponse<Err: ErrorRenderer> =
/// * /{project_id}/path2 - `GET` requests
/// * /{project_id}/path3 - `HEAD` requests
///
pub struct Scope<Err: ErrorRenderer, T = ScopeEndpoint<Err>> {
pub struct Scope<Err: ErrorRenderer, T = ScopeEndpoint<Err>, U = Identity> {
endpoint: T,
middleware: U,
rdef: Vec<String>,
data: Option<Extensions>,
services: Vec<Box<dyn AppServiceFactory<Err>>>,
@ -76,6 +78,7 @@ impl<Err: ErrorRenderer> Scope<Err> {
let fref = Rc::new(RefCell::new(None));
Scope {
endpoint: ScopeEndpoint::new(fref.clone()),
middleware: Identity,
rdef: path.patterns(),
data: None,
guards: Vec::new(),
@ -88,7 +91,7 @@ impl<Err: ErrorRenderer> Scope<Err> {
}
}
impl<Err, T> Scope<Err, T>
impl<Err, T, U> Scope<Err, T, U>
where
T: ServiceFactory<
Config = (),
@ -150,14 +153,14 @@ where
/// );
/// }
/// ```
pub fn data<U: 'static>(self, data: U) -> Self {
pub fn data<D: 'static>(self, data: D) -> Self {
self.app_data(Data::new(data))
}
/// Set or override application data.
///
/// This method overrides data stored with [`App::app_data()`](#method.app_data)
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
pub fn app_data<D: 'static>(mut self, data: D) -> Self {
if self.data.is_none() {
self.data = Some(Extensions::new());
}
@ -290,16 +293,16 @@ where
/// Default service to be used if no matching route could be found.
///
/// If default resource is not registered, app's default resource is being used.
pub fn default_service<F, U>(mut self, f: F) -> Self
pub fn default_service<F, S>(mut self, f: F) -> Self
where
F: IntoServiceFactory<U>,
U: ServiceFactory<
F: IntoServiceFactory<S>,
S: ServiceFactory<
Config = (),
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
> + 'static,
U::InitError: fmt::Debug,
S::InitError: fmt::Debug,
{
// create and configure default resource
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
@ -330,6 +333,7 @@ where
Error = Err::Container,
InitError = (),
>,
U,
>
where
F: ServiceFactory<
@ -351,6 +355,7 @@ where
Scope {
endpoint,
middleware: self.middleware,
rdef: self.rdef,
data: self.data,
guards: self.guards,
@ -366,98 +371,16 @@ where
///
/// That runs during inbound processing in the request
/// lifecycle (request -> response), modifying request as
/// necessary, across all requests managed by the *Scope*. Scope-level
/// necessary, across all requests managed by the *Scope*. Scope-level
/// middleware is more limited in what it can modify, relative to Route or
/// Application level middleware, in that Scope-level middleware can not modify
/// WebResponse.
///
/// Use middleware when you need to read or modify *every* request in some way.
pub fn wrap<M>(
self,
mw: M,
) -> Scope<
Err,
impl ServiceFactory<
Config = (),
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
InitError = (),
>,
>
where
M: Transform<T::Service>,
M::Transform: Service<
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
>,
{
pub fn wrap<M>(self, mw: M) -> Scope<Err, T, Stack<U, M>> {
Scope {
endpoint: apply(mw, self.endpoint),
rdef: self.rdef,
data: self.data,
guards: self.guards,
services: self.services,
default: self.default,
external: self.external,
factory_ref: self.factory_ref,
case_insensitive: self.case_insensitive,
}
}
/// Registers middleware, in the form of a closure.
///
/// That runs during inbound processing in the request lifecycle (request -> response),
/// modifying request as necessary, across all requests managed by the *Scope*.
/// Scope-level middleware is more limited in what it can modify, relative
/// to Route or Application level middleware, in that Scope-level middleware
/// can not modify WebResponse.
///
/// ```rust
/// use ntex::service::Service;
/// use ntex::web;
/// use ntex::http::header::{CONTENT_TYPE, HeaderValue};
///
/// async fn index() -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = web::App::new().service(
/// web::scope("/app")
/// .wrap_fn(|req, srv| {
/// let fut = srv.call(req);
/// async {
/// let mut res = fut.await?;
/// res.headers_mut().insert(
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
/// );
/// Ok(res)
/// }
/// })
/// .route("/index.html", web::get().to(index)));
/// }
/// ```
pub fn wrap_fn<F, R>(
self,
mw: F,
) -> Scope<
Err,
impl ServiceFactory<
Config = (),
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
InitError = (),
>,
>
where
F: Fn(WebRequest<Err>, &T::Service) -> R + Clone,
R: Future<Output = Result<WebResponse, Err::Container>>,
{
Scope {
endpoint: apply_fn_factory(self.endpoint, mw),
endpoint: self.endpoint,
middleware: Stack::new(self.middleware, mw),
rdef: self.rdef,
data: self.data,
guards: self.guards,
@ -470,7 +393,7 @@ where
}
}
impl<Err, T> WebServiceFactory<Err> for Scope<Err, T>
impl<Err, T, U> WebServiceFactory<Err> for Scope<Err, T, U>
where
T: ServiceFactory<
Config = (),
@ -479,6 +402,12 @@ where
Error = Err::Container,
InitError = (),
> + 'static,
U: Transform<T::Service> + 'static,
U::Service: Service<
Request = WebRequest<Err>,
Response = WebResponse,
Error = Err::Container,
>,
Err: ErrorRenderer,
{
fn register(mut self, config: &mut WebServiceConfig<Err>) {
@ -541,7 +470,7 @@ where
config.register_service(
ResourceDef::root_prefix(self.rdef),
guards,
self.endpoint,
apply(self.middleware, self.endpoint),
Some(Rc::new(rmap)),
)
}
@ -1208,34 +1137,6 @@ mod tests {
);
}
#[crate::rt_test]
async fn test_middleware_fn() {
let srv = init_service(
App::new().service(
web::scope("app")
.wrap_fn(|req, srv| {
let fut = srv.call(req);
async move {
let mut res = fut.await?;
res.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("0001"));
Ok(res)
}
})
.route("/test", web::get().to(|| async { HttpResponse::Ok() })),
),
)
.await;
let req = TestRequest::with_uri("/app/test").to_request();
let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
}
#[crate::rt_test]
async fn test_override_data() {
let srv = init_service(App::new().data(1usize).service(

View file

@ -10,11 +10,11 @@ use crate::http::Protocol;
use crate::http::{
body::MessageBody, HttpService, KeepAlive, Request, Response, ResponseError,
};
#[cfg(unix)]
use crate::pipeline_factory;
use crate::server::{Server, ServerBuilder};
#[cfg(unix)]
use crate::service::pipeline_factory;
use crate::time::Seconds;
use crate::{map_config, IntoServiceFactory, Service, ServiceFactory};
use crate::{service::map_config, IntoServiceFactory, Service, ServiceFactory};
use super::config::AppConfig;

View file

@ -18,12 +18,12 @@ use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
use crate::http::test::TestRequest as HttpTestRequest;
use crate::http::{HttpService, Method, Payload, Request, StatusCode, Uri, Version};
use crate::router::{Path, ResourceDef};
use crate::service::{
map_config, IntoService, IntoServiceFactory, Service, ServiceFactory,
};
use crate::time::{sleep, Millis, Seconds};
use crate::util::{next, Bytes, BytesMut, Extensions, Ready};
use crate::{
map_config, rt::System, server::Server, IntoService, IntoServiceFactory, Service,
ServiceFactory, Stream,
};
use crate::{rt::System, server::Server, Stream};
use crate::web::config::AppConfig;
use crate::web::dev::{WebRequest, WebResponse};

View file

@ -238,7 +238,7 @@ async fn test_bind_uds() {
let client = client::Client::build()
.connector(
client::Connector::default()
.connector(ntex::fn_service(|_| async {
.connector(ntex::service::fn_service(|_| async {
let stream =
ntex::rt::net::UnixStream::connect("/tmp/uds-test").await?;
Ok((stream, ntex::http::Protocol::Http1))
@ -292,7 +292,7 @@ async fn test_listen_uds() {
let client = client::Client::build()
.connector(
client::Connector::default()
.connector(ntex::fn_service(|_| async {
.connector(ntex::service::fn_service(|_| async {
let stream =
ntex::rt::net::UnixStream::connect("/tmp/uds-test2").await?;
Ok((stream, ntex::http::Protocol::Http1))