diff --git a/CHANGES.md b/CHANGES.md index db2c8e8f5..97b7ca63a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,12 +4,21 @@ ### Changed +* Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486] + +## [3.0.0-alpha.2] - 2020-05-08 + +### Changed + * `{Resource,Scope}::default_service(f)` handlers now support app data extraction. [#1452] * Implement `std::error::Error` for our custom errors [#1422] -* NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. +* NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. [#1433] +* Remove the `failure` feature and support. [#1422]: https://github.com/actix/actix-web/pull/1422 +[#1433]: https://github.com/actix/actix-web/pull/1433 [#1452]: https://github.com/actix/actix-web/pull/1452 +[#1486]: https://github.com/actix/actix-web/pull/1486 ## [3.0.0-alpha.1] - 2020-03-11 diff --git a/Cargo.toml b/Cargo.toml index 7a0bef858..b24cc89d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "3.0.0-alpha.1" +version = "3.0.0-alpha.2" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -81,7 +81,7 @@ actix-threadpool = "0.3.1" actix-tls = "2.0.0-alpha.1" actix-web-codegen = "0.2.0" -actix-http = "2.0.0-alpha.2" +actix-http = "2.0.0-alpha.3" awc = { version = "2.0.0-alpha.1", default-features = false } bytes = "0.5.3" @@ -101,6 +101,7 @@ time = { version = "0.2.7", default-features = false, features = ["std"] } url = "2.1" open-ssl = { version="0.10", package = "openssl", optional = true } rust-tls = { version = "0.17.0", package = "rustls", optional = true } +tinyvec = { version = "0.3", features = ["alloc"] } [dev-dependencies] actix = "0.10.0-alpha.1" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index ed887e323..60f92c5d9 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,8 +18,8 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "3.0.0-alpha.1", default-features = false } -actix-http = "2.0.0-alpha.2" +actix-web = { version = "3.0.0-alpha.2", default-features = false } +actix-http = "2.0.0-alpha.3" actix-service = "1.0.1" bitflags = "1" bytes = "0.5.3" @@ -33,4 +33,4 @@ v_htmlescape = "0.4" [dev-dependencies] actix-rt = "1.0.0" -actix-web = { version = "3.0.0-alpha.1", features=["openssl"] } +actix-web = { version = "3.0.0-alpha.2", features = ["openssl"] } diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index adcda1422..94009db6f 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -23,7 +23,7 @@ actix-codec = "0.2.0" actix-service = "1.0.1" actix-router = "0.2.1" actix-rt = "1.0.0" -actix-http = "2.0.0-alpha.2" +actix-http = "2.0.0-alpha.3" bytes = "0.5.3" futures = "0.3.1" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 43f189afc..92302a666 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,16 +1,21 @@ # Changes -## [Unreleased] +## [2.0.0-alpha.3] - 2020-05-08 + +### Fixed + +* Correct spelling of ConnectError::Unresolved [#1487] +* Fix a mistake in the encoding of websocket continuation messages wherein + Item::FirstText and Item::FirstBinary are each encoded as the other. ### Changed * Implement `std::error::Error` for our custom errors [#1422] * Remove `failure` support for `ResponseError` since that crate will be deprecated in the near future. -* Fix a mistake in the encoding of websocket continuation messages wherein - Item::FirstText and Item::FirstBinary are each encoded as the other. [#1422]: https://github.com/actix/actix-web/pull/1422 +[#1487]: https://github.com/actix/actix-web/pull/1487 ## [2.0.0-alpha.2] - 2020-03-07 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b2c6b8e0a..7e398f6fa 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "2.0.0-alpha.2" +version = "2.0.0-alpha.3" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" @@ -42,7 +42,7 @@ actors = ["actix"] [dependencies] actix-service = "1.0.5" actix-codec = "0.2.0" -actix-connect = "2.0.0-alpha.2" +actix-connect = "2.0.0-alpha.3" actix-utils = "1.0.6" actix-rt = "1.0.0" actix-threadpool = "0.3.1" diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index 0f0a86cd1..e4653a31a 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -48,7 +48,7 @@ pub enum ConnectError { /// Unresolved host name #[display(fmt = "Connector received `Connect` method with unresolved host")] - Unresolverd, + Unresolved, /// Connection io error #[display(fmt = "{}", _0)] @@ -63,7 +63,7 @@ impl From for ConnectError { actix_connect::ConnectError::Resolver(e) => ConnectError::Resolver(e), actix_connect::ConnectError::NoRecords => ConnectError::NoRecords, actix_connect::ConnectError::InvalidInput => panic!(), - actix_connect::ConnectError::Unresolverd => ConnectError::Unresolverd, + actix_connect::ConnectError::Unresolved => ConnectError::Unresolved, actix_connect::ConnectError::Io(e) => ConnectError::Io(e), } } diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 983396f92..5a10725b0 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -105,7 +105,7 @@ where let key = if let Some(authority) = req.uri.authority() { authority.clone().into() } else { - return Err(ConnectError::Unresolverd); + return Err(ConnectError::Unresolved); }; // acquire connection @@ -195,7 +195,7 @@ where if let Some(i) = self.inner.take() { let mut inner = i.as_ref().borrow_mut(); inner.release_waiter(&self.key, self.token); - inner.check_availibility(); + inner.check_availability(); } } } @@ -232,7 +232,7 @@ where if let Some(i) = self.inner.take() { let mut inner = i.as_ref().borrow_mut(); inner.release(); - inner.check_availibility(); + inner.check_availability(); } } } @@ -359,7 +359,7 @@ where created, used: Instant::now(), }); - self.check_availibility(); + self.check_availability(); } fn release_close(&mut self, io: ConnectionType) { @@ -369,10 +369,10 @@ where actix_rt::spawn(CloseConnection::new(io, timeout)) } } - self.check_availibility(); + self.check_availability(); } - fn check_availibility(&self) { + fn check_availability(&self) { if !self.waiters_queue.is_empty() && self.acquired < self.config.limit { self.waker.wake(); } @@ -534,7 +534,7 @@ where if let Some(inner) = self.project().inner.take() { let mut inner = inner.as_ref().borrow_mut(); inner.release(); - inner.check_availibility(); + inner.check_availability(); } } } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index e81b07cb7..d8421d0eb 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -16,7 +16,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "3.0.0-alpha.1", default-features = false } +actix-web = { version = "3.0.0-alpha.2", default-features = false } actix-service = "1.0.1" actix-utils = "1.0.3" bytes = "0.5.3" @@ -29,4 +29,4 @@ twoway = "0.2" [dev-dependencies] actix-rt = "1.0.0" -actix-http = "2.0.0-alpha.2" +actix-http = "2.0.0-alpha.3" diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 66ff7ed6c..68947569a 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,5 +1,11 @@ # Changes +# [3.0.0-alpha.1] - 2020-05-08 + +* Update the actix-web dependency to 3.0.0-alpha.1 +* Update the actix dependency to 0.10.0-alpha.2 +* Update the actix-http dependency to 2.0.0-alpha.3 + ## [2.0.0] - 2019-12-20 * Release diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 7e24d8c99..f9bcc65de 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "2.0.0" +version = "3.0.0-alpha.1" authors = ["Nikolay Kim "] description = "Actix actors support for actix web framework." readme = "README.md" @@ -16,9 +16,9 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = "0.10.0-alpha.1" -actix-web = "3.0.0-alpha.1" -actix-http = "2.0.0-alpha.2" +actix = "0.10.0-alpha.2" +actix-web = "3.0.0-alpha.2" +actix-http = "2.0.0-alpha.3" actix-codec = "0.2.0" bytes = "0.5.2" futures = "0.3.1" @@ -26,4 +26,4 @@ pin-project = "0.4.6" [dev-dependencies] actix-rt = "1.0.0" -env_logger = "0.6" +env_logger = "0.7" diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index 6a403de12..c889092d2 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -174,7 +174,7 @@ where // frames if let Some(data) = self.fut.ctx().stream.pop_front() { - Poll::Ready(data.map(|b| Ok(b))) + Poll::Ready(data.map(Ok)) } else if self.fut.alive() { Poll::Pending } else { diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 42befc0dd..835c9fcc9 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -18,5 +18,5 @@ proc-macro2 = "^1" [dev-dependencies] actix-rt = "1.0.0" -actix-web = "3.0.0-alpha.1" +actix-web = "3.0.0-alpha.2" futures = "0.3.1" diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index b724eb797..39a8a6464 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -21,6 +21,7 @@ //! //! - `"path"` - Raw literal string with path for which to register handle. Mandatory. //! - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard` +//! - `wrap="Middleware"` - Registers a resource middleware. //! //! ## Notes //! @@ -54,6 +55,7 @@ use proc_macro::TokenStream; /// /// - `"path"` - Raw literal string with path for which to register handler. Mandatory. /// - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard` +/// - `wrap="Middleware"` - Registers a resource middleware. #[proc_macro_attribute] pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { route::generate(args, input, route::GuardType::Get) diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index 3e6f9c979..7e3d43f1d 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -56,12 +56,14 @@ impl ToTokens for GuardType { struct Args { path: syn::LitStr, guards: Vec, + wrappers: Vec, } impl Args { fn new(args: AttributeArgs) -> syn::Result { let mut path = None; let mut guards = Vec::new(); + let mut wrappers = Vec::new(); for arg in args { match arg { NestedMeta::Lit(syn::Lit::Str(lit)) => match path { @@ -85,10 +87,19 @@ impl Args { "Attribute guard expects literal string!", )); } + } else if nv.path.is_ident("wrap") { + if let syn::Lit::Str(lit) = nv.lit { + wrappers.push(lit.parse()?); + } else { + return Err(syn::Error::new_spanned( + nv.lit, + "Attribute wrap expects type", + )); + } } else { return Err(syn::Error::new_spanned( nv.path, - "Unknown attribute key is specified. Allowed: guard.", + "Unknown attribute key is specified. Allowed: guard and wrap", )); } } @@ -100,6 +111,7 @@ impl Args { Ok(Args { path: path.unwrap(), guards, + wrappers, }) } } @@ -184,7 +196,7 @@ impl ToTokens for Route { name, guard, ast, - args: Args { path, guards }, + args: Args { path, guards, wrappers }, resource_type, } = self; let resource_name = name.to_string(); @@ -199,6 +211,7 @@ impl ToTokens for Route { .name(#resource_name) .guard(actix_web::guard::#guard()) #(.guard(actix_web::guard::fn_guard(#guards)))* + #(.wrap(#wrappers))* .#resource_type(#name); actix_web::dev::HttpServiceFactory::register(__resource, __config) diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index ffb50c11e..8264a7fd7 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,6 +1,11 @@ -use actix_web::{http, test, web::Path, App, HttpResponse, Responder}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use actix_web::{http, test, web::Path, App, HttpResponse, Responder, Error}; +use actix_web::dev::{Service, Transform, ServiceRequest, ServiceResponse}; use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, trace}; use futures::{future, Future}; +use actix_web::http::header::{HeaderName, HeaderValue}; // Make sure that we can name function as 'config' #[get("/config")] @@ -73,6 +78,65 @@ async fn get_param_test(_: Path) -> impl Responder { HttpResponse::Ok() } +pub struct ChangeStatusCode; + +impl Transform for ChangeStatusCode +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = ChangeStatusCodeMiddleware; + type Future = future::Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + future::ok(ChangeStatusCodeMiddleware { service }) + } +} + +pub struct ChangeStatusCodeMiddleware { + service: S, +} + +impl Service for ChangeStatusCodeMiddleware +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type Future = Pin>>>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.service.poll_ready(cx) + } + + fn call(&mut self, req: ServiceRequest) -> Self::Future { + + let fut = self.service.call(req); + + Box::pin(async move { + let mut res = fut.await?; + let headers = res.headers_mut(); + let header_name = HeaderName::from_lowercase(b"custom-header").unwrap(); + let header_value = HeaderValue::from_str("hello").unwrap(); + headers.insert(header_name, header_value); + Ok(res) + }) + } +} + +#[get("/test/wrap", wrap = "ChangeStatusCode")] +async fn get_wrap(_: Path) -> impl Responder { + HttpResponse::Ok() +} + #[actix_rt::test] async fn test_params() { let srv = test::start(|| { @@ -155,3 +219,15 @@ async fn test_auto_async() { let response = request.send().await.unwrap(); assert!(response.status().is_success()); } + +#[actix_rt::test] +async fn test_wrap() { + let srv = test::start(|| { + App::new() + .service(get_wrap) + }); + + let request = srv.request(http::Method::GET, srv.url("/test/wrap")); + let response = request.send().await.unwrap(); + assert!(response.headers().contains_key("custom-header")); +} diff --git a/awc/Cargo.toml b/awc/Cargo.toml index fde136eb0..5ccf31654 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -36,7 +36,7 @@ compress = ["actix-http/compress"] [dependencies] actix-codec = "0.2.0" actix-service = "1.0.1" -actix-http = "2.0.0-alpha.2" +actix-http = "2.0.0-alpha.3" actix-rt = "1.0.0" base64 = "0.11" @@ -50,17 +50,17 @@ rand = "0.7" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.6.1" -open-ssl = { version="0.10", package="openssl", optional = true } -rust-tls = { version = "0.17.0", package="rustls", optional = true, features = ["dangerous_configuration"] } +open-ssl = { version = "0.10", package = "openssl", optional = true } +rust-tls = { version = "0.17.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] -actix-connect = { version = "2.0.0-alpha.2", features=["openssl"] } -actix-web = { version = "3.0.0-alpha.1", features=["openssl"] } -actix-http = { version = "2.0.0-alpha.2", features=["openssl"] } -actix-http-test = { version = "1.0.0", features=["openssl"] } +actix-connect = { version = "2.0.0-alpha.2", features = ["openssl"] } +actix-web = { version = "3.0.0-alpha.2", features = ["openssl"] } +actix-http = { version = "2.0.0-alpha.3", features = ["openssl"] } +actix-http-test = { version = "1.0.0", features = ["openssl"] } actix-utils = "1.0.3" actix-server = "1.0.0" -actix-tls = { version = "2.0.0-alpha.1", features=["openssl", "rustls"] } +actix-tls = { version = "2.0.0-alpha.1", features = ["openssl", "rustls"] } brotli2 = "0.3.2" flate2 = "1.0.13" futures = "0.3.1" diff --git a/src/app_service.rs b/src/app_service.rs index 808592e58..2d64bed3e 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -245,7 +245,7 @@ where inner.path.reset(); inner.head = head; inner.payload = payload; - inner.app_data = self.data.clone(); + inner.app_data.push(self.data.clone()); req } else { HttpRequest::new( diff --git a/src/request.rs b/src/request.rs index 51a1c54ff..72fea1faf 100644 --- a/src/request.rs +++ b/src/request.rs @@ -6,6 +6,7 @@ use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_router::{Path, Url}; use futures::future::{ok, Ready}; +use tinyvec::TinyVec; use crate::config::AppConfig; use crate::error::UrlGenerationError; @@ -21,7 +22,7 @@ pub(crate) struct HttpRequestInner { pub(crate) head: Message, pub(crate) path: Path, pub(crate) payload: Payload, - pub(crate) app_data: Rc, + pub(crate) app_data: TinyVec<[Rc; 4]>, rmap: Rc, config: AppConfig, pool: &'static HttpRequestPool, @@ -38,13 +39,16 @@ impl HttpRequest { app_data: Rc, pool: &'static HttpRequestPool, ) -> HttpRequest { + let mut data = TinyVec::<[Rc; 4]>::new(); + data.push(app_data); + HttpRequest(Rc::new(HttpRequestInner { head, path, payload, rmap, config, - app_data, + app_data: data, pool, })) } @@ -215,11 +219,13 @@ impl HttpRequest { /// let opt_t = req.app_data::>(); /// ``` pub fn app_data(&self) -> Option<&T> { - if let Some(st) = self.0.app_data.get::() { - Some(&st) - } else { - None + for container in self.0.app_data.iter().rev() { + if let Some(data) = container.get::() { + return Some(data); + } } + + None } } @@ -342,10 +348,13 @@ impl HttpRequestPool { #[cfg(test)] mod tests { + use actix_service::Service; + use bytes::Bytes; + use super::*; use crate::dev::{ResourceDef, ResourceMap}; use crate::http::{header, StatusCode}; - use crate::test::{call_service, init_service, TestRequest}; + use crate::test::{call_service, init_service, read_body, TestRequest}; use crate::{web, App, HttpResponse}; #[test] @@ -494,6 +503,68 @@ mod tests { assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } + #[actix_rt::test] + async fn test_cascading_data() { + #[allow(dead_code)] + fn echo_usize(req: HttpRequest) -> HttpResponse { + let num = req.app_data::().unwrap(); + HttpResponse::Ok().body(num.to_string()) + } + + let mut srv = init_service( + App::new() + .app_data(88usize) + .service(web::resource("/").route(web::get().to(echo_usize))) + .service( + web::resource("/one") + .app_data(1u32) + .route(web::get().to(echo_usize)), + ), + ) + .await; + + let req = TestRequest::get().uri("/").to_request(); + let resp = srv.call(req).await.unwrap(); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"88")); + + let req = TestRequest::get().uri("/one").to_request(); + let resp = srv.call(req).await.unwrap(); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"88")); + } + + #[actix_rt::test] + async fn test_overwrite_data() { + #[allow(dead_code)] + fn echo_usize(req: HttpRequest) -> HttpResponse { + let num = req.app_data::().unwrap(); + HttpResponse::Ok().body(num.to_string()) + } + + let mut srv = init_service( + App::new() + .app_data(88usize) + .service(web::resource("/").route(web::get().to(echo_usize))) + .service( + web::resource("/one") + .app_data(1usize) + .route(web::get().to(echo_usize)), + ), + ) + .await; + + let req = TestRequest::get().uri("/").to_request(); + let resp = srv.call(req).await.unwrap(); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"88")); + + let req = TestRequest::get().uri("/one").to_request(); + let resp = srv.call(req).await.unwrap(); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"1")); + } + #[actix_rt::test] async fn test_extensions_dropped() { struct Tracker { diff --git a/src/resource.rs b/src/resource.rs index 634294cc2..477f0bfba 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -198,9 +198,7 @@ where /// Add resource data. /// - /// If used, this method will create a new data context used for extracting - /// from requests. Data added here is *not* merged with data added on App - /// or containing scopes. + /// Data of different types from parent contexts will still be accessible. pub fn app_data(mut self, data: U) -> Self { if self.data.is_none() { self.data = Some(Extensions::new()); @@ -539,14 +537,14 @@ impl Service for ResourceService { for route in self.routes.iter_mut() { if route.check(&mut req) { if let Some(ref data) = self.data { - req.set_data_container(data.clone()); + req.add_data_container(data.clone()); } return Either::Right(route.call(req)); } } if let Some(ref mut default) = self.default { if let Some(ref data) = self.data { - req.set_data_container(data.clone()); + req.add_data_container(data.clone()); } Either::Right(default.call(req)) } else { @@ -590,14 +588,13 @@ mod tests { use actix_rt::time::delay_for; use actix_service::Service; - use bytes::Bytes; use futures::future::ok; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::middleware::DefaultHeaders; use crate::service::ServiceRequest; - use crate::test::{call_service, init_service, read_body, TestRequest}; - use crate::{guard, web, App, Error, HttpRequest, HttpResponse}; + use crate::test::{call_service, init_service, TestRequest}; + use crate::{guard, web, App, Error, HttpResponse}; #[actix_rt::test] async fn test_middleware() { @@ -623,79 +620,6 @@ mod tests { ); } - #[actix_rt::test] - async fn test_overwritten_data() { - #[allow(dead_code)] - fn echo_usize(req: HttpRequest) -> HttpResponse { - let num = req.app_data::().unwrap(); - HttpResponse::Ok().body(format!("{}", num)) - } - - #[allow(dead_code)] - fn echo_u32(req: HttpRequest) -> HttpResponse { - let num = req.app_data::().unwrap(); - HttpResponse::Ok().body(format!("{}", num)) - } - - #[allow(dead_code)] - fn echo_both(req: HttpRequest) -> HttpResponse { - let num = req.app_data::().unwrap(); - let num2 = req.app_data::().unwrap(); - HttpResponse::Ok().body(format!("{}-{}", num, num2)) - } - - let mut srv = init_service( - App::new() - .app_data(88usize) - .service(web::resource("/").route(web::get().to(echo_usize))) - .service( - web::resource("/one") - .app_data(1usize) - .route(web::get().to(echo_usize)), - ) - .service( - web::resource("/two") - .app_data(2usize) - .route(web::get().to(echo_usize)), - ) - .service( - web::resource("/three") - .app_data(3u32) - // this doesnt work because app_data "overrides" the - // entire data field potentially passed down - // .route(web::get().to(echo_both)), - .route(web::get().to(echo_u32)), - ) - .service(web::resource("/eight").route(web::get().to(echo_usize))), - ) - .await; - - let req = TestRequest::get().uri("/").to_request(); - let resp = srv.call(req).await.unwrap(); - let body = read_body(resp).await; - assert_eq!(body, Bytes::from_static(b"88")); - - let req = TestRequest::get().uri("/one").to_request(); - let resp = srv.call(req).await.unwrap(); - let body = read_body(resp).await; - assert_eq!(body, Bytes::from_static(b"1")); - - let req = TestRequest::get().uri("/two").to_request(); - let resp = srv.call(req).await.unwrap(); - let body = read_body(resp).await; - assert_eq!(body, Bytes::from_static(b"2")); - - // let req = TestRequest::get().uri("/three").to_request(); - // let resp = srv.call(req).await.unwrap(); - // let body = read_body(resp).await; - // assert_eq!(body, Bytes::from_static(b"88-3")); - - let req = TestRequest::get().uri("/eight").to_request(); - let resp = srv.call(req).await.unwrap(); - let body = read_body(resp).await; - assert_eq!(body, Bytes::from_static(b"88")); - } - #[actix_rt::test] async fn test_middleware_fn() { let mut srv = init_service( diff --git a/src/scope.rs b/src/scope.rs index 2016c6f1c..407d4946d 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -153,9 +153,7 @@ where /// Add scope data. /// - /// If used, this method will create a new data context used for extracting - /// from requests. Data added here is *not* merged with data added on App - /// or containing scopes. + /// Data of different types from parent contexts will still be accessible. pub fn app_data(mut self, data: U) -> Self { if self.data.is_none() { self.data = Some(Extensions::new()); @@ -624,12 +622,12 @@ impl Service for ScopeService { if let Some((srv, _info)) = res { if let Some(ref data) = self.data { - req.set_data_container(data.clone()); + req.add_data_container(data.clone()); } Either::Left(srv.call(req)) } else if let Some(ref mut default) = self.default { if let Some(ref data) = self.data { - req.set_data_container(data.clone()); + req.add_data_container(data.clone()); } Either::Left(default.call(req)) } else { diff --git a/src/service.rs b/src/service.rs index b783fd720..8dc9fa93d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -217,11 +217,13 @@ impl ServiceRequest { /// Get an application data stored with `App::data()` method during /// application configuration. pub fn app_data(&self) -> Option> { - if let Some(st) = (self.0).0.app_data.get::>() { - Some(st.clone()) - } else { - None + for container in (self.0).0.app_data.iter().rev() { + if let Some(data) = container.get::>() { + return Some(Data::clone(&data)); + } } + + None } /// Set request payload. @@ -230,9 +232,12 @@ impl ServiceRequest { } #[doc(hidden)] - /// Set new app data container - pub fn set_data_container(&mut self, extensions: Rc) { - Rc::get_mut(&mut (self.0).0).unwrap().app_data = extensions; + /// Add app data container to request's resolution set. + pub fn add_data_container(&mut self, extensions: Rc) { + Rc::get_mut(&mut (self.0).0) + .unwrap() + .app_data + .push(extensions); } } @@ -578,7 +583,6 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), http::StatusCode::NOT_FOUND); } - #[test] fn test_fmt_debug() { let req = TestRequest::get() diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 97581585d..f44ecc5fd 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -52,8 +52,8 @@ sha1 = "0.6" slab = "0.4" serde_urlencoded = "0.6.1" time = { version = "0.2.7", default-features = false, features = ["std"] } -open-ssl = { version="0.10", package = "openssl", optional = true } +open-ssl = { version = "0.10", package = "openssl", optional = true } [dev-dependencies] -actix-web = "3.0.0-alpha.1" -actix-http = "2.0.0-alpha.2" +actix-web = "3.0.0-alpha.2" +actix-http = "2.0.0-alpha.3"