mirror of
https://github.com/ntex-rs/ntex.git
synced 2025-04-03 21:07:39 +03:00
Change state management behaviour
This commit is contained in:
parent
d947f7f08c
commit
c8530d65a5
17 changed files with 348 additions and 443 deletions
12
.github/workflows/linux.yml
vendored
12
.github/workflows/linux.yml
vendored
|
@ -8,7 +8,7 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- 1.59.0 # MSRV
|
||||
- 1.60.0 # MSRV
|
||||
- stable
|
||||
- nightly
|
||||
|
||||
|
@ -43,7 +43,7 @@ jobs:
|
|||
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Cache cargo tarpaulin
|
||||
if: matrix.version == '1.59.0' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
if: matrix.version == '1.60.0' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cargo/bin
|
||||
|
@ -64,26 +64,26 @@ jobs:
|
|||
cargo test --no-default-features --no-fail-fast --features="async-std,cookie,url,compress,openssl,rustls" --lib -- --test-threads 1
|
||||
|
||||
- name: Install tarpaulin
|
||||
if: matrix.version == '1.59.0' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
if: matrix.version == '1.60.0' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cargo install cargo-tarpaulin
|
||||
|
||||
- name: Generate coverage report
|
||||
if: matrix.version == '1.59.0' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
if: matrix.version == '1.60.0' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cargo tarpaulin --out Xml --all --all-features
|
||||
|
||||
- name: Generate coverage report (glommio)
|
||||
if: matrix.version == '1.59.0' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
if: matrix.version == '1.60.0' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cd ntex
|
||||
sudo -E env PATH="$PATH" bash -c "ulimit -l 512 && ulimit -a && cargo tarpaulin --out Xml --no-default-features --features=\"glommio,cookie,url,compress,openssl,rustls\""
|
||||
|
||||
- name: Upload to Codecov
|
||||
if: matrix.version == '1.59.0' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
if: matrix.version == '1.60.0' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
continue-on-error: true
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
## [0.5.30] - 2022-11-25
|
||||
|
||||
* Change `App::state()` behaviour
|
||||
|
||||
* Remove `App::app_state()` method
|
||||
|
||||
## [0.5.29] - 2022-11-03
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ ntex-http = "0.1.7"
|
|||
ntex-router = "0.5.1"
|
||||
ntex-service = "0.3.2"
|
||||
ntex-macros = "0.1.3"
|
||||
ntex-util = "0.1.17"
|
||||
ntex-util = "0.1.18"
|
||||
ntex-bytes = "0.1.16"
|
||||
ntex-h2 = "0.1.5"
|
||||
ntex-rt = "0.4.6"
|
||||
|
|
|
@ -16,13 +16,12 @@ use super::resource::Resource;
|
|||
use super::response::WebResponse;
|
||||
use super::route::Route;
|
||||
use super::service::{AppServiceFactory, ServiceFactoryWrapper, WebServiceFactory};
|
||||
use super::types::state::{State, StateFactory};
|
||||
use super::{DefaultError, ErrorRenderer};
|
||||
|
||||
type HttpNewService<Err: ErrorRenderer> =
|
||||
BoxServiceFactory<(), WebRequest<Err>, WebResponse, Err::Container, ()>;
|
||||
type FnStateFactory =
|
||||
Box<dyn Fn() -> Pin<Box<dyn Future<Output = Result<Box<dyn StateFactory>, ()>>>>>;
|
||||
Box<dyn Fn(Extensions) -> Pin<Box<dyn Future<Output = Result<Extensions, ()>>>>>;
|
||||
|
||||
/// Application builder - structure that follows the builder pattern
|
||||
/// for building application instances.
|
||||
|
@ -31,10 +30,9 @@ pub struct App<M, F, Err: ErrorRenderer = DefaultError> {
|
|||
filter: PipelineFactory<F, WebRequest<Err>>,
|
||||
services: Vec<Box<dyn AppServiceFactory<Err>>>,
|
||||
default: Option<Rc<HttpNewService<Err>>>,
|
||||
state: Vec<Box<dyn StateFactory>>,
|
||||
state_factories: Vec<FnStateFactory>,
|
||||
external: Vec<ResourceDef>,
|
||||
extensions: Extensions,
|
||||
state_factories: Vec<FnStateFactory>,
|
||||
error_renderer: Err,
|
||||
case_insensitive: bool,
|
||||
}
|
||||
|
@ -45,7 +43,6 @@ impl App<Identity, Filter<DefaultError>, DefaultError> {
|
|||
App {
|
||||
middleware: Identity,
|
||||
filter: pipeline_factory(Filter::new()),
|
||||
state: Vec::new(),
|
||||
state_factories: Vec::new(),
|
||||
services: Vec::new(),
|
||||
default: None,
|
||||
|
@ -63,7 +60,6 @@ impl<Err: ErrorRenderer> App<Identity, Filter<Err>, Err> {
|
|||
App {
|
||||
middleware: Identity,
|
||||
filter: pipeline_factory(Filter::new()),
|
||||
state: Vec::new(),
|
||||
state_factories: Vec::new(),
|
||||
services: Vec::new(),
|
||||
default: None,
|
||||
|
@ -86,16 +82,19 @@ where
|
|||
T::Future: 'static,
|
||||
Err: ErrorRenderer,
|
||||
{
|
||||
/// Set application state. Application state could be accessed
|
||||
/// by using `State<T>` extractor where `T` is state type.
|
||||
/// Set application level arbitrary state item.
|
||||
///
|
||||
/// Application state stored with `App::app_state()` method is available
|
||||
/// via `HttpRequest::app_state()` method at runtime.
|
||||
///
|
||||
/// This method could be used for storing `State<T>` as well, in that case
|
||||
/// state could be accessed by using `State<T>` extractor.
|
||||
///
|
||||
/// **Note**: http server accepts an application factory rather than
|
||||
/// an application instance. Http server constructs an application
|
||||
/// instance for each thread, thus application state must be constructed
|
||||
/// multiple times. If you want to share state between different
|
||||
/// threads, a shared object should be used, e.g. `Arc`. Internally `State` type
|
||||
/// uses `Arc` so statw could be created outside of app factory and clones could
|
||||
/// be stored via `App::app_state()` method.
|
||||
/// threads, a shared object should be used, e.g. `Arc`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::cell::Cell;
|
||||
|
@ -117,7 +116,7 @@ where
|
|||
/// );
|
||||
/// ```
|
||||
pub fn state<U: 'static>(mut self, state: U) -> Self {
|
||||
self.state.push(Box::new(State::new(state)));
|
||||
self.extensions.insert(state);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -131,7 +130,7 @@ where
|
|||
D: 'static,
|
||||
E: fmt::Debug,
|
||||
{
|
||||
self.state_factories.push(Box::new(move || {
|
||||
self.state_factories.push(Box::new(move |mut ext| {
|
||||
let fut = state();
|
||||
Box::pin(async move {
|
||||
match fut.await {
|
||||
|
@ -140,8 +139,8 @@ where
|
|||
Err(())
|
||||
}
|
||||
Ok(st) => {
|
||||
let st: Box<dyn StateFactory> = Box::new(State::new(st));
|
||||
Ok(st)
|
||||
ext.insert(st);
|
||||
Ok(ext)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -149,18 +148,6 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Set application level arbitrary state item.
|
||||
///
|
||||
/// Application state stored with `App::app_state()` method is available
|
||||
/// via `HttpRequest::app_state()` method at runtime.
|
||||
///
|
||||
/// This method could be used for storing `State<T>` as well, in that case
|
||||
/// state could be accessed by using `State<T>` extractor.
|
||||
pub fn app_state<U: 'static>(mut self, ext: U) -> Self {
|
||||
self.extensions.insert(ext);
|
||||
self
|
||||
}
|
||||
|
||||
/// Run external configuration as part of the application building
|
||||
/// process
|
||||
///
|
||||
|
@ -192,9 +179,9 @@ where
|
|||
{
|
||||
let mut cfg = ServiceConfig::new();
|
||||
f(&mut cfg);
|
||||
self.state.extend(cfg.state);
|
||||
self.services.extend(cfg.services);
|
||||
self.external.extend(cfg.external);
|
||||
self.extensions.extend(cfg.state);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -375,7 +362,6 @@ where
|
|||
App {
|
||||
filter: self.filter.and_then(filter.into_factory()),
|
||||
middleware: self.middleware,
|
||||
state: self.state,
|
||||
state_factories: self.state_factories,
|
||||
services: self.services,
|
||||
default: self.default,
|
||||
|
@ -416,7 +402,6 @@ where
|
|||
App {
|
||||
middleware: Stack::new(self.middleware, mw),
|
||||
filter: self.filter,
|
||||
state: self.state,
|
||||
state_factories: self.state_factories,
|
||||
services: self.services,
|
||||
default: self.default,
|
||||
|
@ -508,7 +493,6 @@ where
|
|||
let app = AppFactory {
|
||||
filter: self.filter,
|
||||
middleware: Rc::new(self.middleware),
|
||||
state: Rc::new(self.state),
|
||||
state_factories: Rc::new(self.state_factories),
|
||||
services: Rc::new(RefCell::new(self.services)),
|
||||
external: RefCell::new(self.external),
|
||||
|
@ -538,7 +522,6 @@ where
|
|||
AppFactory {
|
||||
filter: self.filter,
|
||||
middleware: Rc::new(self.middleware),
|
||||
state: Rc::new(self.state),
|
||||
state_factories: Rc::new(self.state_factories),
|
||||
services: Rc::new(RefCell::new(self.services)),
|
||||
external: RefCell::new(self.external),
|
||||
|
@ -566,7 +549,6 @@ where
|
|||
AppFactory {
|
||||
filter: self.filter,
|
||||
middleware: Rc::new(self.middleware),
|
||||
state: Rc::new(self.state),
|
||||
state_factories: Rc::new(self.state_factories),
|
||||
services: Rc::new(RefCell::new(self.services)),
|
||||
external: RefCell::new(self.external),
|
||||
|
@ -729,12 +711,12 @@ mod tests {
|
|||
|
||||
#[crate::rt_test]
|
||||
async fn test_extension() {
|
||||
let srv = init_service(App::new().app_state(10usize).service(
|
||||
web::resource("/").to(|req: HttpRequest| async move {
|
||||
let srv = init_service(App::new().state(10usize).service(web::resource("/").to(
|
||||
|req: HttpRequest| async move {
|
||||
assert_eq!(*req.app_state::<usize>().unwrap(), 10);
|
||||
HttpResponse::Ok()
|
||||
}),
|
||||
))
|
||||
},
|
||||
)))
|
||||
.await;
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
|
|
|
@ -14,8 +14,7 @@ use super::httprequest::{HttpRequest, HttpRequestPool};
|
|||
use super::request::WebRequest;
|
||||
use super::response::WebResponse;
|
||||
use super::rmap::ResourceMap;
|
||||
use super::service::{AppServiceFactory, WebServiceConfig};
|
||||
use super::types::state::StateFactory;
|
||||
use super::service::{AppServiceFactory, AppState, WebServiceConfig};
|
||||
|
||||
type Guards = Vec<Box<dyn Guard>>;
|
||||
type HttpService<Err: ErrorRenderer> =
|
||||
|
@ -25,7 +24,7 @@ type HttpNewService<Err: ErrorRenderer> =
|
|||
type BoxResponse<Err: ErrorRenderer> =
|
||||
Pin<Box<dyn Future<Output = Result<WebResponse, Err::Container>>>>;
|
||||
type FnStateFactory =
|
||||
Box<dyn Fn() -> Pin<Box<dyn Future<Output = Result<Box<dyn StateFactory>, ()>>>>>;
|
||||
Box<dyn Fn(Extensions) -> Pin<Box<dyn Future<Output = Result<Extensions, ()>>>>>;
|
||||
|
||||
/// Service factory to convert `Request` to a `WebRequest<S>`.
|
||||
/// It also executes state factories.
|
||||
|
@ -43,7 +42,6 @@ where
|
|||
pub(super) middleware: Rc<T>,
|
||||
pub(super) filter: PipelineFactory<F, WebRequest<Err>>,
|
||||
pub(super) extensions: RefCell<Option<Extensions>>,
|
||||
pub(super) state: Rc<Vec<Box<dyn StateFactory>>>,
|
||||
pub(super) state_factories: Rc<Vec<FnStateFactory>>,
|
||||
pub(super) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory<Err>>>>>,
|
||||
pub(super) default: Option<Rc<HttpNewService<Err>>>,
|
||||
|
@ -95,6 +93,8 @@ where
|
|||
type Future = Pin<Box<dyn Future<Output = Result<Self::Service, Self::InitError>>>>;
|
||||
|
||||
fn new_service(&self, config: AppConfig) -> Self::Future {
|
||||
let services = std::mem::take(&mut *self.services.borrow_mut());
|
||||
|
||||
// update resource default service
|
||||
let default = self.default.clone().unwrap_or_else(|| {
|
||||
Rc::new(boxed::factory(fn_service(
|
||||
|
@ -104,42 +104,7 @@ where
|
|||
)))
|
||||
});
|
||||
|
||||
// App config
|
||||
let mut config = WebServiceConfig::new(config, default.clone(), self.state.clone());
|
||||
|
||||
// register services
|
||||
std::mem::take(&mut *self.services.borrow_mut())
|
||||
.into_iter()
|
||||
.for_each(|mut srv| srv.register(&mut config));
|
||||
let (config, services) = config.into_services();
|
||||
|
||||
// resource map
|
||||
let mut rmap = ResourceMap::new(ResourceDef::new(""));
|
||||
for mut rdef in std::mem::take(&mut *self.external.borrow_mut()) {
|
||||
rmap.add(&mut rdef, None);
|
||||
}
|
||||
|
||||
// complete pipeline creation
|
||||
let services: Vec<_> = services
|
||||
.into_iter()
|
||||
.map(|(mut rdef, srv, guards, nested)| {
|
||||
rmap.add(&mut rdef, nested);
|
||||
(rdef, srv, RefCell::new(guards))
|
||||
})
|
||||
.collect();
|
||||
let default_fut = default.new_service(());
|
||||
|
||||
let mut router = Router::build();
|
||||
if self.case_insensitive {
|
||||
router.case_insensitive();
|
||||
}
|
||||
|
||||
// complete ResourceMap tree creation
|
||||
let rmap = Rc::new(rmap);
|
||||
rmap.finish(rmap.clone());
|
||||
|
||||
let filter_fut = self.filter.new_service(());
|
||||
let state = self.state.clone();
|
||||
let state_factories = self.state_factories.clone();
|
||||
let mut extensions = self
|
||||
.extensions
|
||||
|
@ -147,8 +112,48 @@ where
|
|||
.take()
|
||||
.unwrap_or_else(Extensions::new);
|
||||
let middleware = self.middleware.clone();
|
||||
let external = std::mem::take(&mut *self.external.borrow_mut());
|
||||
|
||||
let mut router = Router::build();
|
||||
if self.case_insensitive {
|
||||
router.case_insensitive();
|
||||
}
|
||||
|
||||
Box::pin(async move {
|
||||
// app state factories
|
||||
for fut in state_factories.iter() {
|
||||
extensions = fut(extensions).await?;
|
||||
}
|
||||
let state = AppState::new(extensions, None, config);
|
||||
|
||||
// App config
|
||||
let mut config = WebServiceConfig::new(state.clone(), default.clone());
|
||||
|
||||
// register services
|
||||
services
|
||||
.into_iter()
|
||||
.for_each(|mut srv| srv.register(&mut config));
|
||||
let services = config.into_services();
|
||||
|
||||
// resource map
|
||||
let mut rmap = ResourceMap::new(ResourceDef::new(""));
|
||||
for mut rdef in external {
|
||||
rmap.add(&mut rdef, None);
|
||||
}
|
||||
|
||||
// complete pipeline creation
|
||||
let services: Vec<_> = services
|
||||
.into_iter()
|
||||
.map(|(mut rdef, srv, guards, nested)| {
|
||||
rmap.add(&mut rdef, nested);
|
||||
(rdef, srv, RefCell::new(guards))
|
||||
})
|
||||
.collect();
|
||||
|
||||
// complete ResourceMap tree creation
|
||||
let rmap = Rc::new(rmap);
|
||||
rmap.finish(rmap.clone());
|
||||
|
||||
// create http services
|
||||
for (path, factory, guards) in &mut services.iter() {
|
||||
let service = factory.new_service(()).await?;
|
||||
|
@ -157,7 +162,7 @@ where
|
|||
|
||||
let routing = AppRouting {
|
||||
router: router.finish(),
|
||||
default: Some(default_fut.await?),
|
||||
default: Some(default.new_service(()).await?),
|
||||
};
|
||||
|
||||
// main service
|
||||
|
@ -166,23 +171,10 @@ where
|
|||
routing: Rc::new(routing),
|
||||
};
|
||||
|
||||
// create app state container
|
||||
for f in state.iter() {
|
||||
f.create(&mut extensions);
|
||||
}
|
||||
|
||||
// async state factories
|
||||
for fut in state_factories.iter() {
|
||||
if let Ok(f) = fut().await {
|
||||
f.create(&mut extensions);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(AppFactoryService {
|
||||
rmap,
|
||||
config,
|
||||
state,
|
||||
service: middleware.new_transform(service),
|
||||
state: Rc::new(extensions),
|
||||
pool: HttpRequestPool::create(),
|
||||
_t: PhantomData,
|
||||
})
|
||||
|
@ -198,8 +190,7 @@ where
|
|||
{
|
||||
service: T,
|
||||
rmap: Rc<ResourceMap>,
|
||||
config: AppConfig,
|
||||
state: Rc<Extensions>,
|
||||
state: AppState,
|
||||
pool: &'static HttpRequestPool,
|
||||
_t: PhantomData<Err>,
|
||||
}
|
||||
|
@ -239,7 +230,6 @@ where
|
|||
head,
|
||||
payload,
|
||||
self.rmap.clone(),
|
||||
self.config.clone(),
|
||||
self.state.clone(),
|
||||
self.pool,
|
||||
)
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
use std::{net::SocketAddr, rc::Rc};
|
||||
|
||||
use crate::router::ResourceDef;
|
||||
use crate::{router::ResourceDef, util::Extensions};
|
||||
|
||||
use super::resource::Resource;
|
||||
use super::route::Route;
|
||||
use super::service::{AppServiceFactory, ServiceFactoryWrapper, WebServiceFactory};
|
||||
use super::types::state::{State, StateFactory};
|
||||
use super::{DefaultError, ErrorRenderer};
|
||||
|
||||
/// Application configuration
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppConfig(Rc<AppConfigInner>);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AppConfigInner {
|
||||
secure: bool,
|
||||
host: String,
|
||||
|
@ -61,7 +61,7 @@ impl Default for AppConfig {
|
|||
/// modularization of big application configuration.
|
||||
pub struct ServiceConfig<Err = DefaultError> {
|
||||
pub(super) services: Vec<Box<dyn AppServiceFactory<Err>>>,
|
||||
pub(super) state: Vec<Box<dyn StateFactory>>,
|
||||
pub(super) state: Extensions,
|
||||
pub(super) external: Vec<ResourceDef>,
|
||||
}
|
||||
|
||||
|
@ -69,17 +69,16 @@ impl<Err: ErrorRenderer> ServiceConfig<Err> {
|
|||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
services: Vec::new(),
|
||||
state: Vec::new(),
|
||||
state: Extensions::new(),
|
||||
external: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set application state. Application state could be accessed
|
||||
/// by using `State<T>` extractor where `T` is state type.
|
||||
/// Set application state.
|
||||
///
|
||||
/// This is same as `App::state()` method.
|
||||
pub fn state<S: 'static>(&mut self, st: S) -> &mut Self {
|
||||
self.state.push(Box::new(State::new(st)));
|
||||
self.state.insert(st);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ use super::error::ErrorRenderer;
|
|||
use super::extract::FromRequest;
|
||||
use super::info::ConnectionInfo;
|
||||
use super::rmap::ResourceMap;
|
||||
use super::service::AppState;
|
||||
|
||||
#[derive(Clone)]
|
||||
/// An HTTP Request
|
||||
|
@ -21,9 +22,8 @@ pub(crate) struct HttpRequestInner {
|
|||
pub(crate) head: Message<RequestHead>,
|
||||
pub(crate) path: Path<Uri>,
|
||||
pub(crate) payload: Payload,
|
||||
pub(crate) app_state: Rc<Extensions>,
|
||||
pub(crate) app_state: AppState,
|
||||
rmap: Rc<ResourceMap>,
|
||||
config: AppConfig,
|
||||
pool: &'static HttpRequestPool,
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,7 @@ impl HttpRequest {
|
|||
head: Message<RequestHead>,
|
||||
payload: Payload,
|
||||
rmap: Rc<ResourceMap>,
|
||||
config: AppConfig,
|
||||
app_state: Rc<Extensions>,
|
||||
app_state: AppState,
|
||||
pool: &'static HttpRequestPool,
|
||||
) -> HttpRequest {
|
||||
HttpRequest(Rc::new(HttpRequestInner {
|
||||
|
@ -44,7 +43,6 @@ impl HttpRequest {
|
|||
payload,
|
||||
app_state,
|
||||
rmap,
|
||||
config,
|
||||
pool,
|
||||
}))
|
||||
}
|
||||
|
@ -213,7 +211,7 @@ impl HttpRequest {
|
|||
/// App config
|
||||
#[inline]
|
||||
pub fn app_config(&self) -> &AppConfig {
|
||||
&self.0.config
|
||||
self.0.app_state.config()
|
||||
}
|
||||
|
||||
/// Get an application state object stored with `App::state()` or `App::app_state()`
|
||||
|
@ -467,22 +465,22 @@ mod tests {
|
|||
|
||||
#[crate::rt_test]
|
||||
async fn test_state() {
|
||||
let srv = init_service(App::new().app_state(10usize).service(
|
||||
web::resource("/").to(|req: HttpRequest| async move {
|
||||
let srv = init_service(App::new().state(10usize).service(web::resource("/").to(
|
||||
|req: HttpRequest| async move {
|
||||
if req.app_state::<usize>().is_some() {
|
||||
HttpResponse::Ok()
|
||||
} else {
|
||||
HttpResponse::BadRequest()
|
||||
}
|
||||
}),
|
||||
))
|
||||
},
|
||||
)))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = call_service(&srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let srv = init_service(App::new().app_state(10u32).service(web::resource("/").to(
|
||||
let srv = init_service(App::new().state(10u32).service(web::resource("/").to(
|
||||
|req: HttpRequest| async move {
|
||||
if req.app_state::<usize>().is_some() {
|
||||
HttpResponse::Ok()
|
||||
|
|
|
@ -13,6 +13,7 @@ use super::httprequest::HttpRequest;
|
|||
use super::info::ConnectionInfo;
|
||||
use super::response::WebResponse;
|
||||
use super::rmap::ResourceMap;
|
||||
use super::service::AppState;
|
||||
|
||||
/// An service http request
|
||||
///
|
||||
|
@ -224,8 +225,8 @@ impl<Err> WebRequest<Err> {
|
|||
|
||||
#[doc(hidden)]
|
||||
/// Set new app state container
|
||||
pub fn set_state_container(&mut self, extensions: Rc<Extensions>) {
|
||||
Rc::get_mut(&mut (self.req).0).unwrap().app_state = extensions;
|
||||
pub(super) fn set_state_container(&mut self, state: AppState) {
|
||||
Rc::get_mut(&mut (self.req).0).unwrap().app_state = state;
|
||||
}
|
||||
|
||||
/// Request extensions
|
||||
|
|
|
@ -17,7 +17,7 @@ use super::request::WebRequest;
|
|||
use super::responder::Responder;
|
||||
use super::response::WebResponse;
|
||||
use super::route::{IntoRoutes, Route, RouteService};
|
||||
use super::{app::Filter, app::Stack, guard::Guard, types::State};
|
||||
use super::{app::Filter, app::Stack, guard::Guard, service::AppState};
|
||||
|
||||
type HttpService<Err: ErrorRenderer> =
|
||||
BoxService<WebRequest<Err>, WebResponse, Err::Container>;
|
||||
|
@ -63,10 +63,10 @@ impl<Err: ErrorRenderer> Resource<Err> {
|
|||
routes: Vec::new(),
|
||||
rdef: path.patterns(),
|
||||
name: None,
|
||||
state: None,
|
||||
middleware: Identity,
|
||||
filter: pipeline_factory(Filter::new()),
|
||||
guards: Vec::new(),
|
||||
state: None,
|
||||
default: Rc::new(RefCell::new(None)),
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +123,38 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Provide resource specific state. This method allows to add extractor
|
||||
/// configuration or specific state available via `State<T>` extractor.
|
||||
/// Provided state is available for all routes registered for the current resource.
|
||||
/// Resource state overrides state registered by `App::state()` method.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ntex::web::{self, App, FromRequest};
|
||||
///
|
||||
/// /// extract text data from request
|
||||
/// async fn index(body: String) -> String {
|
||||
/// format!("Body {}!", body)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html")
|
||||
/// // limit size of the payload
|
||||
/// .state(web::types::PayloadConfig::new(4096))
|
||||
/// .route(
|
||||
/// // register handler
|
||||
/// web::get().to(index)
|
||||
/// ));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn state<D: 'static>(mut self, st: D) -> Self {
|
||||
if self.state.is_none() {
|
||||
self.state = Some(Extensions::new());
|
||||
}
|
||||
self.state.as_mut().unwrap().insert(st);
|
||||
self
|
||||
}
|
||||
|
||||
/// Register a new route.
|
||||
///
|
||||
/// ```rust
|
||||
|
@ -169,46 +201,6 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Provide resource specific state. This method allows to add extractor
|
||||
/// configuration or specific state available via `State<T>` extractor.
|
||||
/// Provided state is available for all routes registered for the current resource.
|
||||
/// Resource state overrides state registered by `App::state()` method.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ntex::web::{self, App, FromRequest};
|
||||
///
|
||||
/// /// extract text data from request
|
||||
/// async fn index(body: String) -> String {
|
||||
/// format!("Body {}!", body)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html")
|
||||
/// // limit size of the payload
|
||||
/// .app_state(web::types::PayloadConfig::new(4096))
|
||||
/// .route(
|
||||
/// web::get()
|
||||
/// // register handler
|
||||
/// .to(index)
|
||||
/// ));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn state<D: 'static>(self, st: D) -> Self {
|
||||
self.app_state(State::new(st))
|
||||
}
|
||||
|
||||
/// Set or override application state.
|
||||
///
|
||||
/// This method overrides state stored with [`App::app_state()`](#method.app_state)
|
||||
pub fn app_state<D: 'static>(mut self, st: D) -> Self {
|
||||
if self.state.is_none() {
|
||||
self.state = Some(Extensions::new());
|
||||
}
|
||||
self.state.as_mut().unwrap().insert(st);
|
||||
self
|
||||
}
|
||||
|
||||
/// Register a new route and add handler. This route matches all requests.
|
||||
///
|
||||
/// ```rust
|
||||
|
@ -269,10 +261,10 @@ where
|
|||
middleware: self.middleware,
|
||||
rdef: self.rdef,
|
||||
name: self.name,
|
||||
state: self.state,
|
||||
guards: self.guards,
|
||||
routes: self.routes,
|
||||
default: self.default,
|
||||
state: self.state,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,10 +281,10 @@ where
|
|||
filter: self.filter,
|
||||
rdef: self.rdef,
|
||||
name: self.name,
|
||||
state: self.state,
|
||||
guards: self.guards,
|
||||
routes: self.routes,
|
||||
default: self.default,
|
||||
state: self.state,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -342,14 +334,18 @@ where
|
|||
if let Some(ref name) = self.name {
|
||||
*rdef.name_mut() = name.clone();
|
||||
}
|
||||
// custom app data storage
|
||||
if let Some(ref mut ext) = self.state {
|
||||
config.set_service_state(ext);
|
||||
}
|
||||
|
||||
let state = self.state.take().map(|state| {
|
||||
AppState::new(
|
||||
state,
|
||||
Some(config.state().clone()),
|
||||
config.state().config().clone(),
|
||||
)
|
||||
});
|
||||
|
||||
let router_factory = ResourceRouterFactory {
|
||||
state,
|
||||
routes: self.routes,
|
||||
state: self.state.map(Rc::new),
|
||||
default: self.default,
|
||||
};
|
||||
|
||||
|
@ -386,8 +382,8 @@ where
|
|||
self,
|
||||
) -> ResourceServiceFactory<Err, M, PipelineFactory<T, WebRequest<Err>>> {
|
||||
let router_factory = ResourceRouterFactory {
|
||||
state: None,
|
||||
routes: self.routes,
|
||||
state: self.state.map(Rc::new),
|
||||
default: self.default,
|
||||
};
|
||||
|
||||
|
@ -506,8 +502,8 @@ where
|
|||
|
||||
struct ResourceRouterFactory<Err: ErrorRenderer> {
|
||||
routes: Vec<Route<Err>>,
|
||||
state: Option<Rc<Extensions>>,
|
||||
default: Rc<RefCell<Option<Rc<HttpNewService<Err>>>>>,
|
||||
state: Option<AppState>,
|
||||
}
|
||||
|
||||
impl<Err: ErrorRenderer> ServiceFactory<WebRequest<Err>> for ResourceRouterFactory<Err> {
|
||||
|
@ -530,8 +526,8 @@ impl<Err: ErrorRenderer> ServiceFactory<WebRequest<Err>> for ResourceRouterFacto
|
|||
};
|
||||
|
||||
Ok(ResourceRouter {
|
||||
routes,
|
||||
state,
|
||||
routes,
|
||||
default,
|
||||
})
|
||||
})
|
||||
|
@ -539,8 +535,8 @@ impl<Err: ErrorRenderer> ServiceFactory<WebRequest<Err>> for ResourceRouterFacto
|
|||
}
|
||||
|
||||
struct ResourceRouter<Err: ErrorRenderer> {
|
||||
state: Option<AppState>,
|
||||
routes: Vec<RouteService<Err>>,
|
||||
state: Option<Rc<Extensions>>,
|
||||
default: Option<HttpService<Err>>,
|
||||
}
|
||||
|
||||
|
@ -746,26 +742,22 @@ mod tests {
|
|||
#[crate::rt_test]
|
||||
async fn test_state() {
|
||||
let srv = init_service(
|
||||
App::new()
|
||||
.state(1i32)
|
||||
.state(1usize)
|
||||
.app_state(web::types::State::new('-'))
|
||||
.service(
|
||||
web::resource("/test")
|
||||
.state(10usize)
|
||||
.app_state(web::types::State::new('*'))
|
||||
.guard(guard::Get())
|
||||
.to(
|
||||
|data1: web::types::State<usize>,
|
||||
data2: web::types::State<char>,
|
||||
data3: web::types::State<i32>| {
|
||||
assert_eq!(**data1, 10);
|
||||
assert_eq!(**data2, '*');
|
||||
assert_eq!(**data3, 1);
|
||||
async { HttpResponse::Ok() }
|
||||
},
|
||||
),
|
||||
),
|
||||
App::new().state(1i32).state(1usize).state('-').service(
|
||||
web::resource("/test")
|
||||
.state(10usize)
|
||||
.state('*')
|
||||
.guard(guard::Get())
|
||||
.to(
|
||||
|data1: web::types::State<usize>,
|
||||
data2: web::types::State<char>,
|
||||
data3: web::types::State<i32>| {
|
||||
assert_eq!(*data1, 10);
|
||||
assert_eq!(*data2, '*');
|
||||
assert_eq!(*data3, 1);
|
||||
async { HttpResponse::Ok() }
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
|
|
@ -19,8 +19,7 @@ use super::resource::Resource;
|
|||
use super::response::WebResponse;
|
||||
use super::rmap::ResourceMap;
|
||||
use super::route::Route;
|
||||
use super::service::{AppServiceFactory, ServiceFactoryWrapper};
|
||||
use super::types::State;
|
||||
use super::service::{AppServiceFactory, AppState, ServiceFactoryWrapper};
|
||||
|
||||
type Guards = Vec<Box<dyn Guard>>;
|
||||
type HttpService<Err: ErrorRenderer> =
|
||||
|
@ -149,14 +148,7 @@ where
|
|||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub fn state<D: 'static>(self, st: D) -> Self {
|
||||
self.app_state(State::new(st))
|
||||
}
|
||||
|
||||
/// Set or override application state.
|
||||
///
|
||||
/// This method overrides state stored with [`App::app_state()`](#method.app_state)
|
||||
pub fn app_state<D: 'static>(mut self, st: D) -> Self {
|
||||
pub fn state<D: 'static>(mut self, st: D) -> Self {
|
||||
if self.state.is_none() {
|
||||
self.state = Some(Extensions::new());
|
||||
}
|
||||
|
@ -211,11 +203,7 @@ where
|
|||
|
||||
if !cfg.state.is_empty() {
|
||||
let mut state = self.state.unwrap_or_else(Extensions::new);
|
||||
|
||||
for value in cfg.state.iter() {
|
||||
value.create(&mut state);
|
||||
}
|
||||
|
||||
state.extend(cfg.state);
|
||||
self.state = Some(state);
|
||||
}
|
||||
self
|
||||
|
@ -390,8 +378,16 @@ where
|
|||
*self.default.borrow_mut() = Some(config.default_service());
|
||||
}
|
||||
|
||||
let state = self.state.take().map(|state| {
|
||||
AppState::new(
|
||||
state,
|
||||
Some(config.state().clone()),
|
||||
config.state().config().clone(),
|
||||
)
|
||||
});
|
||||
|
||||
// register nested services
|
||||
let mut cfg = config.clone_config();
|
||||
let mut cfg = config.clone_config(state.clone());
|
||||
self.services
|
||||
.into_iter()
|
||||
.for_each(|mut srv| srv.register(&mut cfg));
|
||||
|
@ -404,19 +400,13 @@ where
|
|||
rmap.add(&mut rdef, None);
|
||||
}
|
||||
|
||||
// custom app data storage
|
||||
if let Some(ref mut ext) = self.state {
|
||||
config.set_service_state(ext);
|
||||
}
|
||||
|
||||
// complete scope pipeline creation
|
||||
let router_factory = ScopeRouterFactory {
|
||||
state: self.state.take().map(Rc::new),
|
||||
state,
|
||||
default: self.default.clone(),
|
||||
case_insensitive: self.case_insensitive,
|
||||
services: Rc::new(
|
||||
cfg.into_services()
|
||||
.1
|
||||
.into_iter()
|
||||
.map(|(rdef, srv, guards, nested)| {
|
||||
// case for scope prefix ends with '/' and
|
||||
|
@ -560,7 +550,7 @@ where
|
|||
}
|
||||
|
||||
struct ScopeRouterFactory<Err: ErrorRenderer> {
|
||||
state: Option<Rc<Extensions>>,
|
||||
state: Option<AppState>,
|
||||
services: Rc<Vec<(ResourceDef, HttpNewService<Err>, RefCell<Option<Guards>>)>>,
|
||||
default: Rc<RefCell<Option<Rc<HttpNewService<Err>>>>>,
|
||||
case_insensitive: bool,
|
||||
|
@ -575,8 +565,8 @@ impl<Err: ErrorRenderer> ServiceFactory<WebRequest<Err>> for ScopeRouterFactory<
|
|||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let services = self.services.clone();
|
||||
let case_insensitive = self.case_insensitive;
|
||||
let state = self.state.clone();
|
||||
let case_insensitive = self.case_insensitive;
|
||||
let default_fut = self
|
||||
.default
|
||||
.borrow()
|
||||
|
@ -610,7 +600,7 @@ impl<Err: ErrorRenderer> ServiceFactory<WebRequest<Err>> for ScopeRouterFactory<
|
|||
}
|
||||
|
||||
struct ScopeRouter<Err: ErrorRenderer> {
|
||||
state: Option<Rc<Extensions>>,
|
||||
state: Option<AppState>,
|
||||
router: Router<HttpService<Err>, Vec<Box<dyn Guard>>>,
|
||||
default: Option<HttpService<Err>>,
|
||||
}
|
||||
|
@ -1205,48 +1195,6 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[crate::rt_test]
|
||||
async fn test_override_data() {
|
||||
let srv = init_service(App::new().state(1usize).service(
|
||||
web::scope("app").state(10usize).route(
|
||||
"/t",
|
||||
web::get().to(|data: web::types::State<usize>| {
|
||||
assert_eq!(**data, 10);
|
||||
async { HttpResponse::Ok() }
|
||||
}),
|
||||
),
|
||||
))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/app/t").to_request();
|
||||
let resp = call_service(&srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[crate::rt_test]
|
||||
async fn test_override_app_data() {
|
||||
let srv = init_service(
|
||||
App::new()
|
||||
.app_state(web::types::State::new(1usize))
|
||||
.service(
|
||||
web::scope("app")
|
||||
.app_state(web::types::State::new(10usize))
|
||||
.route(
|
||||
"/t",
|
||||
web::get().to(|data: web::types::State<usize>| {
|
||||
assert_eq!(**data, 10);
|
||||
async { HttpResponse::Ok() }
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/app/t").to_request();
|
||||
let resp = call_service(&srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[crate::rt_test]
|
||||
async fn test_scope_config() {
|
||||
let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
|
||||
|
@ -1260,6 +1208,24 @@ mod tests {
|
|||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[crate::rt_test]
|
||||
async fn test_override_state() {
|
||||
let srv = init_service(App::new().state(1usize).service(
|
||||
web::scope("app").state(10usize).route(
|
||||
"/t",
|
||||
web::get().to(|data: web::types::State<usize>| {
|
||||
assert_eq!(*data, 10);
|
||||
async { HttpResponse::Ok() }
|
||||
}),
|
||||
),
|
||||
))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/app/t").to_request();
|
||||
let resp = call_service(&srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[crate::rt_test]
|
||||
async fn test_scope_config_2() {
|
||||
let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
|
||||
|
|
|
@ -8,10 +8,7 @@ use super::config::AppConfig;
|
|||
use super::dev::insert_slesh;
|
||||
use super::error::ErrorRenderer;
|
||||
use super::guard::Guard;
|
||||
use super::request::WebRequest;
|
||||
use super::response::WebResponse;
|
||||
use super::rmap::ResourceMap;
|
||||
use super::types::state::StateFactory;
|
||||
use super::{request::WebRequest, response::WebResponse, rmap::ResourceMap};
|
||||
|
||||
pub trait WebServiceFactory<Err: ErrorRenderer> {
|
||||
fn register(self, config: &mut WebServiceConfig<Err>);
|
||||
|
@ -49,9 +46,58 @@ type Guards = Vec<Box<dyn Guard>>;
|
|||
type HttpServiceFactory<Err: ErrorRenderer> =
|
||||
boxed::BoxServiceFactory<(), WebRequest<Err>, WebResponse, Err::Container, ()>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct AppState(Rc<AppStateInner>);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AppStateInner {
|
||||
ext: Extensions,
|
||||
parent: Option<AppState>,
|
||||
config: AppConfig,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub(crate) fn new(
|
||||
ext: Extensions,
|
||||
parent: Option<AppState>,
|
||||
config: AppConfig,
|
||||
) -> Self {
|
||||
AppState(Rc::new(AppStateInner {
|
||||
ext,
|
||||
parent,
|
||||
config,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn config(&self) -> &AppConfig {
|
||||
&self.0.config
|
||||
}
|
||||
|
||||
pub(crate) fn get<T: 'static>(&self) -> Option<&T> {
|
||||
let result = self.0.ext.get::<T>();
|
||||
if result.is_some() {
|
||||
result
|
||||
} else if let Some(parent) = self.0.parent.as_ref() {
|
||||
parent.get::<T>()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn contains<T: 'static>(&self) -> bool {
|
||||
if self.0.ext.contains::<T>() {
|
||||
true
|
||||
} else if let Some(parent) = self.0.parent.as_ref() {
|
||||
parent.contains::<T>()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Application service configuration
|
||||
pub struct WebServiceConfig<Err: ErrorRenderer> {
|
||||
config: AppConfig,
|
||||
state: AppState,
|
||||
root: bool,
|
||||
default: Rc<HttpServiceFactory<Err>>,
|
||||
services: Vec<(
|
||||
|
@ -60,20 +106,14 @@ pub struct WebServiceConfig<Err: ErrorRenderer> {
|
|||
Option<Guards>,
|
||||
Option<Rc<ResourceMap>>,
|
||||
)>,
|
||||
service_state: Rc<Vec<Box<dyn StateFactory>>>,
|
||||
}
|
||||
|
||||
impl<Err: ErrorRenderer> WebServiceConfig<Err> {
|
||||
/// Crate server settings instance
|
||||
pub(crate) fn new(
|
||||
config: AppConfig,
|
||||
default: Rc<HttpServiceFactory<Err>>,
|
||||
service_state: Rc<Vec<Box<dyn StateFactory>>>,
|
||||
) -> Self {
|
||||
pub(crate) fn new(state: AppState, default: Rc<HttpServiceFactory<Err>>) -> Self {
|
||||
WebServiceConfig {
|
||||
config,
|
||||
state,
|
||||
default,
|
||||
service_state,
|
||||
root: true,
|
||||
services: Vec::new(),
|
||||
}
|
||||
|
@ -84,33 +124,33 @@ impl<Err: ErrorRenderer> WebServiceConfig<Err> {
|
|||
self.root
|
||||
}
|
||||
|
||||
pub(crate) fn into_services(
|
||||
self,
|
||||
) -> (
|
||||
AppConfig,
|
||||
Vec<(
|
||||
ResourceDef,
|
||||
HttpServiceFactory<Err>,
|
||||
Option<Guards>,
|
||||
Option<Rc<ResourceMap>>,
|
||||
)>,
|
||||
) {
|
||||
(self.config, self.services)
|
||||
pub(super) fn state(&self) -> &AppState {
|
||||
&self.state
|
||||
}
|
||||
|
||||
pub(crate) fn clone_config(&self) -> Self {
|
||||
pub(crate) fn into_services(
|
||||
self,
|
||||
) -> Vec<(
|
||||
ResourceDef,
|
||||
HttpServiceFactory<Err>,
|
||||
Option<Guards>,
|
||||
Option<Rc<ResourceMap>>,
|
||||
)> {
|
||||
self.services
|
||||
}
|
||||
|
||||
pub(crate) fn clone_config(&self, state: Option<AppState>) -> Self {
|
||||
WebServiceConfig {
|
||||
config: self.config.clone(),
|
||||
state: state.unwrap_or_else(|| self.state.clone()),
|
||||
default: self.default.clone(),
|
||||
services: Vec::new(),
|
||||
root: false,
|
||||
service_state: self.service_state.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Service configuration
|
||||
pub fn config(&self) -> &AppConfig {
|
||||
&self.config
|
||||
self.state.config()
|
||||
}
|
||||
|
||||
/// Default resource
|
||||
|
@ -118,14 +158,6 @@ impl<Err: ErrorRenderer> WebServiceConfig<Err> {
|
|||
self.default.clone()
|
||||
}
|
||||
|
||||
/// Set global route state
|
||||
pub fn set_service_state(&self, extensions: &mut Extensions) -> bool {
|
||||
for f in self.service_state.iter() {
|
||||
f.create(extensions);
|
||||
}
|
||||
!self.service_state.is_empty()
|
||||
}
|
||||
|
||||
/// Register http service
|
||||
pub fn register_service<F, S>(
|
||||
&mut self,
|
||||
|
|
|
@ -23,10 +23,10 @@ use crate::util::{stream_recv, Bytes, BytesMut, Extensions, Ready, Stream};
|
|||
use crate::ws::{error::WsClientError, WsClient, WsConnection};
|
||||
use crate::{io::Sealed, rt::System, server::Server};
|
||||
|
||||
use crate::web::config::AppConfig;
|
||||
use crate::web::error::{DefaultError, ErrorRenderer};
|
||||
use crate::web::httprequest::{HttpRequest, HttpRequestPool};
|
||||
use crate::web::rmap::ResourceMap;
|
||||
use crate::web::{config::AppConfig, service::AppState};
|
||||
use crate::web::{FromRequest, HttpResponse, Responder, WebRequest, WebResponse};
|
||||
|
||||
/// Create service that always responds with `HttpResponse::Ok()`
|
||||
|
@ -460,14 +460,14 @@ impl TestRequest {
|
|||
pub fn to_srv_request(mut self) -> WebRequest<DefaultError> {
|
||||
let (head, payload) = self.req.finish().into_parts();
|
||||
*self.path.get_mut() = head.uri.clone();
|
||||
let app_state = AppState::new(self.app_state, None, self.config);
|
||||
|
||||
WebRequest::new(HttpRequest::new(
|
||||
self.path,
|
||||
head,
|
||||
payload,
|
||||
Rc::new(self.rmap),
|
||||
self.config.clone(),
|
||||
Rc::new(self.app_state),
|
||||
app_state,
|
||||
HttpRequestPool::create(),
|
||||
))
|
||||
}
|
||||
|
@ -481,14 +481,14 @@ impl TestRequest {
|
|||
pub fn to_http_request(mut self) -> HttpRequest {
|
||||
let (head, payload) = self.req.finish().into_parts();
|
||||
*self.path.get_mut() = head.uri.clone();
|
||||
let app_state = AppState::new(self.app_state, None, self.config);
|
||||
|
||||
HttpRequest::new(
|
||||
self.path,
|
||||
head,
|
||||
payload,
|
||||
Rc::new(self.rmap),
|
||||
self.config.clone(),
|
||||
Rc::new(self.app_state),
|
||||
app_state,
|
||||
HttpRequestPool::create(),
|
||||
)
|
||||
}
|
||||
|
@ -497,14 +497,14 @@ impl TestRequest {
|
|||
pub fn to_http_parts(mut self) -> (HttpRequest, Payload) {
|
||||
let (head, payload) = self.req.finish().into_parts();
|
||||
*self.path.get_mut() = head.uri.clone();
|
||||
let app_state = AppState::new(self.app_state, None, self.config);
|
||||
|
||||
let req = HttpRequest::new(
|
||||
self.path,
|
||||
head,
|
||||
Payload::None,
|
||||
Rc::new(self.rmap),
|
||||
self.config.clone(),
|
||||
Rc::new(self.app_state),
|
||||
app_state,
|
||||
HttpRequestPool::create(),
|
||||
);
|
||||
|
||||
|
@ -980,7 +980,7 @@ mod tests {
|
|||
.version(Version::HTTP_2)
|
||||
.header(header::DATE, "some date")
|
||||
.param("test", "123")
|
||||
.state(web::types::State::new(20u64))
|
||||
.state(20u64)
|
||||
.peer_addr("127.0.0.1:8081".parse().unwrap())
|
||||
.to_http_request();
|
||||
assert!(req.headers().contains_key(header::CONTENT_TYPE));
|
||||
|
@ -988,8 +988,8 @@ mod tests {
|
|||
// assert_eq!(req.peer_addr(), Some("127.0.0.1:8081".parse().unwrap()));
|
||||
assert_eq!(&req.match_info()["test"], "123");
|
||||
assert_eq!(req.version(), Version::HTTP_2);
|
||||
let data = req.app_state::<web::types::State<u64>>().unwrap();
|
||||
assert_eq!(*data.get_ref(), 20);
|
||||
let data = req.app_state::<u64>().unwrap();
|
||||
assert_eq!(*data, 20);
|
||||
|
||||
assert_eq!(format!("{:?}", StreamType::Tcp), "StreamType::Tcp");
|
||||
}
|
||||
|
@ -1154,7 +1154,7 @@ mod tests {
|
|||
#[crate::rt_test]
|
||||
async fn test_server_state() {
|
||||
async fn handler(data: web::types::State<usize>) -> crate::http::ResponseBuilder {
|
||||
assert_eq!(**data, 10);
|
||||
assert_eq!(*data, 10);
|
||||
HttpResponse::Ok()
|
||||
}
|
||||
|
||||
|
|
|
@ -172,7 +172,7 @@ where
|
|||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html")
|
||||
/// // change `Form` extractor configuration
|
||||
/// .app_state(
|
||||
/// .state(
|
||||
/// web::types::FormConfig::default().limit(4097)
|
||||
/// )
|
||||
/// .route(web::get().to(index))
|
||||
|
|
|
@ -210,7 +210,7 @@ where
|
|||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html")
|
||||
/// .app_state(
|
||||
/// .state(
|
||||
/// // change json extractor configuration
|
||||
/// web::types::JsonConfig::default()
|
||||
/// .limit(4096)
|
||||
|
|
|
@ -186,7 +186,7 @@ impl<Err: ErrorRenderer> FromRequest<Err> for Bytes {
|
|||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html")
|
||||
/// .app_state(
|
||||
/// .state(
|
||||
/// web::types::PayloadConfig::new(4096) // <- limit size of the payload
|
||||
/// )
|
||||
/// .route(web::get().to(index)) // <- register handler with extractor params
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
use std::{marker::PhantomData, ops::Deref};
|
||||
|
||||
use crate::http::Payload;
|
||||
use crate::util::{Extensions, Ready};
|
||||
use crate::web::error::{ErrorRenderer, StateExtractorError};
|
||||
use crate::web::extract::FromRequest;
|
||||
use crate::web::httprequest::HttpRequest;
|
||||
|
||||
/// Application data factory
|
||||
pub(crate) trait StateFactory {
|
||||
fn create(&self, extensions: &mut Extensions) -> bool;
|
||||
}
|
||||
use crate::web::service::AppState;
|
||||
use crate::{http::Payload, util::Ready};
|
||||
|
||||
/// Application state.
|
||||
///
|
||||
|
@ -26,15 +21,13 @@ pub(crate) trait StateFactory {
|
|||
/// instance for each thread, thus application data must be constructed
|
||||
/// multiple times. If you want to share state between different
|
||||
/// threads, a shareable object should be used, e.g. `Send + Sync`. Application
|
||||
/// state does not need to be `Send` or `Sync`. Internally `State` type
|
||||
/// uses `Arc`. if your state implements `Send` + `Sync` traits you can
|
||||
/// use `web::types::State::new()` and avoid double `Arc`.
|
||||
/// state does not need to be `Send` or `Sync`.
|
||||
///
|
||||
/// If state is not set for a handler, using `State<T>` extractor would
|
||||
/// cause *Internal Server Error* response.
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::sync::Mutex;
|
||||
/// use std::sync::{Arc, Mutex};
|
||||
/// use ntex::web::{self, App, HttpResponse};
|
||||
///
|
||||
/// struct MyState {
|
||||
|
@ -42,58 +35,44 @@ pub(crate) trait StateFactory {
|
|||
/// }
|
||||
///
|
||||
/// /// Use `State<T>` extractor to access data in handler.
|
||||
/// async fn index(st: web::types::State<Mutex<MyState>>) -> HttpResponse {
|
||||
/// async fn index(st: web::types::State<Arc<Mutex<MyState>>>) -> HttpResponse {
|
||||
/// let mut data = st.lock().unwrap();
|
||||
/// data.counter += 1;
|
||||
/// HttpResponse::Ok().into()
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let st = web::types::State::new(Mutex::new(MyState{ counter: 0 }));
|
||||
/// let st = Arc::new(Mutex::new(MyState{ counter: 0 }));
|
||||
///
|
||||
/// let app = App::new()
|
||||
/// // Store `MyState` in application storage.
|
||||
/// .app_state(st.clone())
|
||||
/// .state(st.clone())
|
||||
/// .service(
|
||||
/// web::resource("/index.html").route(
|
||||
/// web::get().to(index)));
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct State<T>(Arc<T>);
|
||||
|
||||
impl<T> State<T> {
|
||||
/// Create new `State` instance.
|
||||
///
|
||||
/// Internally `State` type uses `Arc`. if your state implements
|
||||
/// `Send` + `Sync` traits you can use `web::types::State::new()` and
|
||||
/// avoid double `Arc`.
|
||||
pub fn new(state: T) -> State<T> {
|
||||
State(Arc::new(state))
|
||||
}
|
||||
pub struct State<T>(AppState, PhantomData<T>);
|
||||
|
||||
impl<T: 'static> State<T> {
|
||||
/// Get reference to inner app data.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
/// Convert to the internal Arc<T>
|
||||
pub fn into_inner(self) -> Arc<T> {
|
||||
self.0
|
||||
self.0.get::<T>().expect("Unexpected state")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for State<T> {
|
||||
type Target = Arc<T>;
|
||||
impl<T: 'static> Deref for State<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Arc<T> {
|
||||
&self.0
|
||||
fn deref(&self) -> &T {
|
||||
self.get_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for State<T> {
|
||||
fn clone(&self) -> State<T> {
|
||||
State(self.0.clone())
|
||||
State(self.0.clone(), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,8 +82,8 @@ impl<T: 'static, E: ErrorRenderer> FromRequest<E> for State<T> {
|
|||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
if let Some(st) = req.app_state::<State<T>>() {
|
||||
Ready::Ok(st.clone())
|
||||
if req.0.app_state.contains::<T>() {
|
||||
Ready::Ok(Self(req.0.app_state.clone(), PhantomData))
|
||||
} else {
|
||||
log::debug!(
|
||||
"Failed to construct App-level State extractor. \
|
||||
|
@ -116,20 +95,9 @@ impl<T: 'static, E: ErrorRenderer> FromRequest<E> for State<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> StateFactory for State<T> {
|
||||
fn create(&self, extensions: &mut Extensions) -> bool {
|
||||
if !extensions.contains::<State<T>>() {
|
||||
extensions.insert(State(self.0.clone()));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{atomic::AtomicUsize, atomic::Ordering, Arc};
|
||||
|
||||
use super::*;
|
||||
use crate::http::StatusCode;
|
||||
|
@ -138,13 +106,13 @@ mod tests {
|
|||
use crate::web::{self, App, HttpResponse};
|
||||
|
||||
#[crate::rt_test]
|
||||
async fn test_data_extractor() {
|
||||
let srv = init_service(App::new().state("TEST".to_string()).service(
|
||||
web::resource("/").to(|data: web::types::State<String>| async move {
|
||||
assert_eq!(data.to_lowercase(), "test");
|
||||
HttpResponse::Ok()
|
||||
}),
|
||||
))
|
||||
async fn test_state_extractor() {
|
||||
let srv = init_service(
|
||||
App::new().state(10usize).service(
|
||||
web::resource("/")
|
||||
.to(|_: web::types::State<usize>| async { HttpResponse::Ok() }),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
|
@ -163,78 +131,9 @@ mod tests {
|
|||
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
#[crate::rt_test]
|
||||
async fn test_app_data_extractor() {
|
||||
let srv = init_service(
|
||||
App::new().app_state(State::new(10usize)).service(
|
||||
web::resource("/")
|
||||
.to(|_: web::types::State<usize>| async { HttpResponse::Ok() }),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let srv = init_service(
|
||||
App::new().app_state(State::new(10u32)).service(
|
||||
web::resource("/")
|
||||
.to(|_: web::types::State<usize>| async { HttpResponse::Ok() }),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::default().to_request();
|
||||
let res = srv.call(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
#[crate::rt_test]
|
||||
async fn test_route_data_extractor() {
|
||||
let srv =
|
||||
init_service(App::new().service(web::resource("/").state(10usize).route(
|
||||
web::get().to(|data: web::types::State<usize>| async move {
|
||||
let _ = data.clone();
|
||||
HttpResponse::Ok()
|
||||
}),
|
||||
)))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
// different type
|
||||
let srv = init_service(App::new().service(web::resource("/").state(10u32).route(
|
||||
web::get().to(|_: web::types::State<usize>| async { HttpResponse::Ok() }),
|
||||
)))
|
||||
.await;
|
||||
let req = TestRequest::default().to_request();
|
||||
let res = srv.call(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
#[crate::rt_test]
|
||||
async fn test_override_data() {
|
||||
let srv = init_service(App::new().state(1usize).service(
|
||||
web::resource("/").state(10usize).route(web::get().to(
|
||||
|data: web::types::State<usize>| async move {
|
||||
assert_eq!(**data, 10);
|
||||
let _ = data.clone();
|
||||
HttpResponse::Ok()
|
||||
},
|
||||
)),
|
||||
))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[cfg(feature = "tokio")]
|
||||
#[crate::rt_test]
|
||||
async fn test_data_drop() {
|
||||
async fn test_state_drop() {
|
||||
struct TestData(Arc<AtomicUsize>);
|
||||
|
||||
impl TestData {
|
||||
|
@ -275,4 +174,47 @@ mod tests {
|
|||
|
||||
assert_eq!(num.load(Ordering::SeqCst), 0);
|
||||
}
|
||||
|
||||
#[crate::rt_test]
|
||||
async fn test_route_state_extractor() {
|
||||
let srv =
|
||||
init_service(App::new().service(web::resource("/").state(10usize).route(
|
||||
web::get().to(|data: web::types::State<usize>| async move {
|
||||
let _ = data.clone();
|
||||
HttpResponse::Ok()
|
||||
}),
|
||||
)))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
// different type
|
||||
let srv = init_service(App::new().service(web::resource("/").state(10u32).route(
|
||||
web::get().to(|_: web::types::State<usize>| async { HttpResponse::Ok() }),
|
||||
)))
|
||||
.await;
|
||||
let req = TestRequest::default().to_request();
|
||||
let res = srv.call(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
#[crate::rt_test]
|
||||
async fn test_override_state() {
|
||||
let srv = init_service(App::new().state(1usize).service(
|
||||
web::resource("/").state(10usize).route(web::get().to(
|
||||
|data: web::types::State<usize>| async move {
|
||||
assert_eq!(*data, 10);
|
||||
let _ = data.clone();
|
||||
HttpResponse::Ok()
|
||||
},
|
||||
)),
|
||||
))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -680,7 +680,7 @@ async fn test_brotli_encoding_large() {
|
|||
let srv = test::server_with(test::config().h1(), || {
|
||||
App::new().service(
|
||||
web::resource("/")
|
||||
.app_state(web::types::PayloadConfig::new(320_000))
|
||||
.state(web::types::PayloadConfig::new(320_000))
|
||||
.route(web::to(move |body: Bytes| async {
|
||||
HttpResponse::Ok().streaming(TestBody::new(body, 10240))
|
||||
})),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue