Merge pull request #102 from actix/master

-
This commit is contained in:
云上于天 2020-10-27 08:36:46 +08:00 committed by GitHub
commit ca0ba091d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 281 additions and 37 deletions

View File

@ -1,11 +1,21 @@
# Changes # Changes
## Unreleased - 2020-xx-xx ## Unreleased - 2020-xx-xx
* Implement Logger middleware regex exclude pattern [#1723] ### Added
* Print unconfigured `Data<T>` type when attempting extraction. [#1743] * Implement `exclude_regex` for Logger middleware. [#1723]
* Add request-local data extractor `web::ReqData`. [#1748]
* Add `app_data` to `ServiceConfig`. [#1757]
### Changed
* Print non-configured `Data<T>` type when attempting extraction. [#1743]
* Re-export bytes::Buf{Mut} in web module. [#1750]
* Upgrade `pin-project` to `1.0`.
[#1723]: https://github.com/actix/actix-web/pull/1723 [#1723]: https://github.com/actix/actix-web/pull/1723
[#1743]: https://github.com/actix/actix-web/pull/1743 [#1743]: https://github.com/actix/actix-web/pull/1743
[#1748]: https://github.com/actix/actix-web/pull/1748
[#1750]: https://github.com/actix/actix-web/pull/1750
## 3.1.0 - 2020-09-29 ## 3.1.0 - 2020-09-29
### Changed ### Changed

View File

@ -90,7 +90,7 @@ fxhash = "0.2.1"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
socket2 = "0.3" socket2 = "0.3"
pin-project = "0.4.17" pin-project = "1.0.0"
regex = "1.4" regex = "1.4"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View File

@ -2,6 +2,7 @@
## Unreleased - 2020-xx-xx ## Unreleased - 2020-xx-xx
* Upgrade `base64` to `0.13`. * Upgrade `base64` to `0.13`.
* Upgrade `pin-project` to `1.0`.
## 2.0.0 - 2020-09-11 ## 2.0.0 - 2020-09-11
* No significant changes from `2.0.0-beta.4`. * No significant changes from `2.0.0-beta.4`.

View File

@ -71,7 +71,7 @@ language-tags = "0.2"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
percent-encoding = "2.1" percent-encoding = "2.1"
pin-project = "0.4.17" pin-project = "1.0.0"
rand = "0.7" rand = "0.7"
regex = "1.3" regex = "1.3"
serde = "1.0" serde = "1.0"

View File

@ -61,6 +61,11 @@ impl Extensions {
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.map.clear(); self.map.clear();
} }
/// Extends self with the items from another `Extensions`.
pub fn extend(&mut self, other: Extensions) {
self.map.extend(other.map);
}
} }
impl fmt::Debug for Extensions { impl fmt::Debug for Extensions {
@ -178,4 +183,34 @@ mod tests {
assert_eq!(extensions.get::<bool>(), None); assert_eq!(extensions.get::<bool>(), None);
assert_eq!(extensions.get(), Some(&MyType(10))); assert_eq!(extensions.get(), Some(&MyType(10)));
} }
#[test]
fn test_extend() {
#[derive(Debug, PartialEq)]
struct MyType(i32);
let mut extensions = Extensions::new();
extensions.insert(5i32);
extensions.insert(MyType(10));
let mut other = Extensions::new();
other.insert(15i32);
other.insert(20u8);
extensions.extend(other);
assert_eq!(extensions.get(), Some(&15i32));
assert_eq!(extensions.get_mut(), Some(&mut 15i32));
assert_eq!(extensions.remove::<i32>(), Some(15i32));
assert!(extensions.get::<i32>().is_none());
assert_eq!(extensions.get::<bool>(), None);
assert_eq!(extensions.get(), Some(&MyType(10)));
assert_eq!(extensions.get(), Some(&20u8));
assert_eq!(extensions.get_mut(), Some(&mut 20u8));
}
} }

View File

@ -1,7 +1,7 @@
# Changes # Changes
## Unreleased - 2020-xx-xx ## Unreleased - 2020-xx-xx
* Upgrade `pin-project` to `1.0`.
## 3.0.0 - 2020-09-11 ## 3.0.0 - 2020-09-11
* No significant changes from `3.0.0-beta.2`. * No significant changes from `3.0.0-beta.2`.

View File

@ -23,7 +23,7 @@ actix-codec = "0.3.0"
bytes = "0.5.2" bytes = "0.5.2"
futures-channel = { version = "0.3.5", default-features = false } futures-channel = { version = "0.3.5", default-features = false }
futures-core = { version = "0.3.5", default-features = false } futures-core = { version = "0.3.5", default-features = false }
pin-project = "0.4.17" pin-project = "1.0.0"
[dev-dependencies] [dev-dependencies]
actix-rt = "1.1.1" actix-rt = "1.1.1"

View File

@ -1,8 +1,15 @@
# Changes # Changes
## Unreleased - 2020-xx-xx ## Unreleased - 2020-xx-xx
### Changed
* Upgrade `base64` to `0.13`. * Upgrade `base64` to `0.13`.
### Fixed
* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature is enabled [#1737]
[#1737]: https://github.com/actix/actix-web/pull/1737
## 2.0.0 - 2020-09-11 ## 2.0.0 - 2020-09-11
### Changed ### Changed
* `Client::build` was renamed to `Client::builder`. * `Client::build` was renamed to `Client::builder`.

View File

@ -44,6 +44,7 @@ actix-rt = "1.0.0"
base64 = "0.13" base64 = "0.13"
bytes = "0.5.3" bytes = "0.5.3"
cfg-if = "1.0"
derive_more = "0.99.2" derive_more = "0.99.2"
futures-core = { version = "0.3.5", default-features = false } futures-core = { version = "0.3.5", default-features = false }
log =" 0.4" log =" 0.4"

View File

@ -21,10 +21,15 @@ use crate::frozen::FrozenClientRequest;
use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest}; use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest};
use crate::ClientConfig; use crate::ClientConfig;
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] cfg_if::cfg_if! {
const HTTPS_ENCODING: &str = "br, gzip, deflate"; if #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] {
#[cfg(not(any(feature = "flate2-zlib", feature = "flate2-rust")))] const HTTPS_ENCODING: &str = "br, gzip, deflate";
const HTTPS_ENCODING: &str = "br"; } else if #[cfg(feature = "compress")] {
const HTTPS_ENCODING: &str = "br";
} else {
const HTTPS_ENCODING: &str = "identity";
}
}
/// An HTTP Client request builder /// An HTTP Client request builder
/// ///

View File

@ -183,6 +183,7 @@ where
self.data.extend(cfg.data); self.data.extend(cfg.data);
self.services.extend(cfg.services); self.services.extend(cfg.services);
self.external.extend(cfg.external); self.external.extend(cfg.external);
self.extensions.extend(cfg.extensions);
self self
} }

View File

@ -178,6 +178,7 @@ pub struct ServiceConfig {
pub(crate) services: Vec<Box<dyn AppServiceFactory>>, pub(crate) services: Vec<Box<dyn AppServiceFactory>>,
pub(crate) data: Vec<Box<dyn DataFactory>>, pub(crate) data: Vec<Box<dyn DataFactory>>,
pub(crate) external: Vec<ResourceDef>, pub(crate) external: Vec<ResourceDef>,
pub(crate) extensions: Extensions,
} }
impl ServiceConfig { impl ServiceConfig {
@ -186,6 +187,7 @@ impl ServiceConfig {
services: Vec::new(), services: Vec::new(),
data: Vec::new(), data: Vec::new(),
external: Vec::new(), external: Vec::new(),
extensions: Extensions::new(),
} }
} }
@ -198,6 +200,14 @@ impl ServiceConfig {
self self
} }
/// Set arbitrary data item.
///
/// This is same as `App::data()` method.
pub fn app_data<U: 'static>(&mut self, ext: U) -> &mut Self {
self.extensions.insert(ext);
self
}
/// Configure route for a specific path. /// Configure route for a specific path.
/// ///
/// This is same as `App::route()` method. /// This is same as `App::route()` method.
@ -254,11 +264,14 @@ mod tests {
async fn test_data() { async fn test_data() {
let cfg = |cfg: &mut ServiceConfig| { let cfg = |cfg: &mut ServiceConfig| {
cfg.data(10usize); cfg.data(10usize);
cfg.app_data(15u8);
}; };
let mut srv = let mut srv = init_service(App::new().configure(cfg).service(
init_service(App::new().configure(cfg).service( web::resource("/").to(|_: web::Data<usize>, req: HttpRequest| {
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()), assert_eq!(*req.app_data::<u8>().unwrap(), 15u8);
HttpResponse::Ok()
}),
)) ))
.await; .await;
let req = TestRequest::default().to_request(); let req = TestRequest::default().to_request();

View File

@ -20,25 +20,20 @@ pub(crate) type FnDataFactory =
/// Application data. /// Application data.
/// ///
/// Application data is an arbitrary data attached to the app. /// Application level data is a piece of arbitrary data attached to the app, scope, or resource.
/// Application data is available to all routes and could be added /// Application data is available to all routes and can be added during the application
/// during application configuration process /// configuration process via `App::data()`.
/// with `App::data()` method.
/// ///
/// Application data could be accessed by using `Data<T>` /// Application data can be accessed by using `Data<T>` extractor where `T` is data type.
/// extractor where `T` is data type.
/// ///
/// **Note**: http server accepts an application factory rather than /// **Note**: http server accepts an application factory rather than an application instance. HTTP
/// an application instance. Http server constructs an application /// server constructs an application instance for each thread, thus application data must be
/// instance for each thread, thus application data must be constructed /// constructed multiple times. If you want to share data between different threads, a shareable
/// multiple times. If you want to share data between different /// object should be used, e.g. `Send + Sync`. Application data does not need to be `Send`
/// threads, a shareable object should be used, e.g. `Send + Sync`. Application /// or `Sync`. Internally `Data` uses `Arc`.
/// data does not need to be `Send` or `Sync`. Internally `Data` type
/// uses `Arc`. if your data implements `Send` + `Sync` traits you can
/// use `web::Data::new()` and avoid double `Arc`.
/// ///
/// If route data is not set for a handler, using `Data<T>` extractor would /// If route data is not set for a handler, using `Data<T>` extractor would cause *Internal
/// cause *Internal Server Error* response. /// Server Error* response.
/// ///
/// ```rust /// ```rust
/// use std::sync::Mutex; /// use std::sync::Mutex;
@ -48,7 +43,7 @@ pub(crate) type FnDataFactory =
/// counter: usize, /// counter: usize,
/// } /// }
/// ///
/// /// Use `Data<T>` extractor to access data in handler. /// /// Use the `Data<T>` extractor to access data in a handler.
/// async fn index(data: web::Data<Mutex<MyData>>) -> impl Responder { /// async fn index(data: web::Data<Mutex<MyData>>) -> impl Responder {
/// let mut data = data.lock().unwrap(); /// let mut data = data.lock().unwrap();
/// data.counter += 1; /// data.counter += 1;
@ -71,10 +66,6 @@ pub struct Data<T: ?Sized>(Arc<T>);
impl<T> Data<T> { impl<T> Data<T> {
/// Create new `Data` instance. /// Create new `Data` instance.
///
/// Internally `Data` type uses `Arc`. if your data implements
/// `Send` + `Sync` traits you can use `web::Data::new()` and
/// avoid double `Arc`.
pub fn new(state: T) -> Data<T> { pub fn new(state: T) -> Data<T> {
Data(Arc::new(state)) Data(Arc::new(state))
} }

View File

@ -81,6 +81,7 @@ mod handler;
mod info; mod info;
pub mod middleware; pub mod middleware;
mod request; mod request;
mod request_data;
mod resource; mod resource;
mod responder; mod responder;
mod rmap; mod rmap;

175
src/request_data.rs Normal file
View File

@ -0,0 +1,175 @@
use std::{any::type_name, ops::Deref};
use actix_http::error::{Error, ErrorInternalServerError};
use futures_util::future;
use crate::{dev::Payload, FromRequest, HttpRequest};
/// Request-local data extractor.
///
/// Request-local data is arbitrary data attached to an individual request, usually
/// by middleware. It can be set via `extensions_mut` on [`HttpRequest`][htr_ext_mut]
/// or [`ServiceRequest`][srv_ext_mut].
///
/// Unlike app data, request data is dropped when the request has finished processing. This makes it
/// useful as a kind of messaging system between middleware and request handlers. It uses the same
/// types-as-keys storage system as app data.
///
/// # Mutating Request Data
/// Note that since extractors must output owned data, only types that `impl Clone` can use this
/// extractor. A clone is taken of the required request data and can, therefore, not be directly
/// mutated in-place. To mutate request data, continue to use [`HttpRequest::extensions_mut`] or
/// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not
/// provided to make this potential foot-gun more obvious.
///
/// # Example
/// ```rust,no_run
/// # use actix_web::{web, HttpResponse, HttpRequest, Responder};
///
/// #[derive(Debug, Clone, PartialEq)]
/// struct FlagFromMiddleware(String);
///
/// /// Use the `ReqData<T>` extractor to access request data in a handler.
/// async fn handler(
/// req: HttpRequest,
/// opt_flag: Option<web::ReqData<FlagFromMiddleware>>,
/// ) -> impl Responder {
/// // use an optional extractor if the middleware is
/// // not guaranteed to add this type of requests data
/// if let Some(flag) = opt_flag {
/// assert_eq!(&flag.into_inner(), req.extensions().get::<FlagFromMiddleware>().unwrap());
/// }
///
/// HttpResponse::Ok()
/// }
/// ```
///
/// [htr_ext_mut]: crate::HttpRequest::extensions_mut
/// [srv_ext_mut]: crate::dev::ServiceRequest::extensions_mut
#[derive(Debug, Clone)]
pub struct ReqData<T: Clone + 'static>(T);
impl<T: Clone + 'static> ReqData<T> {
/// Consumes the `ReqData`, returning it's wrapped data.
pub fn into_inner(self) -> T {
self.0
}
}
impl<T: Clone + 'static> Deref for ReqData<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T: Clone + 'static> FromRequest for ReqData<T> {
type Config = ();
type Error = Error;
type Future = future::Ready<Result<Self, Error>>;
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
if let Some(st) = req.extensions().get::<T>() {
future::ok(ReqData(st.clone()))
} else {
log::debug!(
"Failed to construct App-level ReqData extractor. \
Request path: {:?} (type: {})",
req.path(),
type_name::<T>(),
);
future::err(ErrorInternalServerError(
"Missing expected request extension data",
))
}
}
}
#[cfg(test)]
mod tests {
use std::{cell::RefCell, rc::Rc};
use futures_util::TryFutureExt as _;
use super::*;
use crate::{
dev::Service,
http::{Method, StatusCode},
test::{init_service, TestRequest},
web, App, HttpMessage, HttpResponse,
};
#[actix_rt::test]
async fn req_data_extractor() {
let mut srv = init_service(
App::new()
.wrap_fn(|req, srv| {
if req.method() == Method::POST {
req.extensions_mut().insert(42u32);
}
srv.call(req)
})
.service(web::resource("/test").to(
|req: HttpRequest, data: Option<ReqData<u32>>| {
if req.method() != Method::POST {
assert!(data.is_none());
}
if let Some(data) = data {
assert_eq!(*data, 42);
assert_eq!(
Some(data.into_inner()),
req.extensions().get::<u32>().copied()
);
}
HttpResponse::Ok()
},
)),
)
.await;
let req = TestRequest::get().uri("/test").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::post().uri("/test").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn req_data_internal_mutability() {
let mut srv = init_service(
App::new()
.wrap_fn(|req, srv| {
let data_before = Rc::new(RefCell::new(42u32));
req.extensions_mut().insert(data_before);
srv.call(req).map_ok(|res| {
{
let ext = res.request().extensions();
let data_after = ext.get::<Rc<RefCell<u32>>>().unwrap();
assert_eq!(*data_after.borrow(), 53u32);
}
res
})
})
.default_service(web::to(|data: ReqData<Rc<RefCell<u32>>>| {
assert_eq!(*data.borrow(), 42);
*data.borrow_mut() += 11;
assert_eq!(*data.borrow(), 53);
HttpResponse::Ok()
})),
)
.await;
let req = TestRequest::get().uri("/test").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
}

View File

@ -209,6 +209,9 @@ where
self.data = Some(data); self.data = Some(data);
} }
self.data
.get_or_insert_with(Extensions::new)
.extend(cfg.extensions);
self self
} }

View File

@ -4,7 +4,7 @@ use actix_router::IntoPattern;
use std::future::Future; use std::future::Future;
pub use actix_http::Response as HttpResponse; pub use actix_http::Response as HttpResponse;
pub use bytes::{Bytes, BytesMut}; pub use bytes::{Buf, BufMut, Bytes, BytesMut};
pub use futures_channel::oneshot::Canceled; pub use futures_channel::oneshot::Canceled;
use crate::error::BlockingError; use crate::error::BlockingError;
@ -19,6 +19,7 @@ use crate::service::WebService;
pub use crate::config::ServiceConfig; pub use crate::config::ServiceConfig;
pub use crate::data::Data; pub use crate::data::Data;
pub use crate::request::HttpRequest; pub use crate::request::HttpRequest;
pub use crate::request_data::ReqData;
pub use crate::types::*; pub use crate::types::*;
/// Create resource for a specific path. /// Create resource for a specific path.