mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into data-doc
This commit is contained in:
commit
2a1ba5c620
14
CHANGES.md
14
CHANGES.md
|
@ -1,6 +1,20 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
### Added
|
||||||
|
* 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
|
||||||
|
[#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
|
||||||
|
|
|
@ -90,8 +90,8 @@ 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.3"
|
regex = "1.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_urlencoded = "0.6.1"
|
serde_urlencoded = "0.6.1"
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
* 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`.
|
||||||
|
|
|
@ -49,7 +49,7 @@ actix-threadpool = "0.3.1"
|
||||||
actix-tls = { version = "2.0.0", optional = true }
|
actix-tls = { version = "2.0.0", optional = true }
|
||||||
actix = { version = "0.10.0", optional = true }
|
actix = { version = "0.10.0", optional = true }
|
||||||
|
|
||||||
base64 = "0.12"
|
base64 = "0.13"
|
||||||
bitflags = "1.2"
|
bitflags = "1.2"
|
||||||
bytes = "0.5.3"
|
bytes = "0.5.3"
|
||||||
cookie = { version = "0.14.1", features = ["percent-encode"] }
|
cookie = { version = "0.14.1", features = ["percent-encode"] }
|
||||||
|
@ -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"
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,9 @@ use std::ptr::copy_nonoverlapping;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
|
||||||
// Holds a slice guaranteed to be shorter than 8 bytes
|
// Holds a slice guaranteed to be shorter than 8 bytes
|
||||||
struct ShortSlice<'a>(&'a mut [u8]);
|
struct ShortSlice<'a> {
|
||||||
|
inner: &'a mut [u8],
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> ShortSlice<'a> {
|
impl<'a> ShortSlice<'a> {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -12,10 +14,11 @@ impl<'a> ShortSlice<'a> {
|
||||||
unsafe fn new(slice: &'a mut [u8]) -> Self {
|
unsafe fn new(slice: &'a mut [u8]) -> Self {
|
||||||
// Sanity check for debug builds
|
// Sanity check for debug builds
|
||||||
debug_assert!(slice.len() < 8);
|
debug_assert!(slice.len() < 8);
|
||||||
ShortSlice(slice)
|
ShortSlice { inner: slice }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
self.0.len()
|
self.inner.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +59,7 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
|
||||||
fn xor_short(buf: ShortSlice<'_>, mask: u64) {
|
fn xor_short(buf: ShortSlice<'_>, mask: u64) {
|
||||||
// SAFETY: we know that a `ShortSlice` fits in a u64
|
// SAFETY: we know that a `ShortSlice` fits in a u64
|
||||||
unsafe {
|
unsafe {
|
||||||
let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len());
|
let (ptr, len) = (buf.inner.as_mut_ptr(), buf.len());
|
||||||
let mut b: u64 = 0;
|
let mut b: u64 = 0;
|
||||||
#[allow(trivial_casts)]
|
#[allow(trivial_casts)]
|
||||||
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
|
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
|
||||||
|
@ -96,7 +99,13 @@ fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) {
|
||||||
|
|
||||||
// SAFETY: we know the middle section is correctly aligned, and the outer
|
// SAFETY: we know the middle section is correctly aligned, and the outer
|
||||||
// sections are smaller than 8 bytes
|
// sections are smaller than 8 bytes
|
||||||
unsafe { (ShortSlice::new(head), cast_slice(mid), ShortSlice(tail)) }
|
unsafe {
|
||||||
|
(
|
||||||
|
ShortSlice::new(head),
|
||||||
|
cast_slice(mid),
|
||||||
|
ShortSlice::new(tail),
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// We didn't cross even one aligned boundary!
|
// We didn't cross even one aligned boundary!
|
||||||
|
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -6,9 +6,8 @@ fn compile_macros() {
|
||||||
t.compile_fail("tests/trybuild/simple-fail.rs");
|
t.compile_fail("tests/trybuild/simple-fail.rs");
|
||||||
|
|
||||||
t.pass("tests/trybuild/route-ok.rs");
|
t.pass("tests/trybuild/route-ok.rs");
|
||||||
t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs");
|
|
||||||
t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs");
|
|
||||||
|
|
||||||
|
test_route_duplicate_unexpected_method(&t);
|
||||||
test_route_missing_method(&t)
|
test_route_missing_method(&t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,3 +24,13 @@ fn test_route_missing_method(t: &trybuild::TestCases) {
|
||||||
|
|
||||||
#[rustversion::nightly]
|
#[rustversion::nightly]
|
||||||
fn test_route_missing_method(_t: &trybuild::TestCases) {}
|
fn test_route_missing_method(_t: &trybuild::TestCases) {}
|
||||||
|
|
||||||
|
// FIXME: Re-test them on nightly once rust-lang/rust#77993 is fixed.
|
||||||
|
#[rustversion::not(nightly)]
|
||||||
|
fn test_route_duplicate_unexpected_method(t: &trybuild::TestCases) {
|
||||||
|
t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs");
|
||||||
|
t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustversion::nightly]
|
||||||
|
fn test_route_duplicate_unexpected_method(_t: &trybuild::TestCases) {}
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
### Changed
|
||||||
|
* 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
|
||||||
|
|
|
@ -42,8 +42,9 @@ actix-service = "1.0.6"
|
||||||
actix-http = "2.0.0"
|
actix-http = "2.0.0"
|
||||||
actix-rt = "1.0.0"
|
actix-rt = "1.0.0"
|
||||||
|
|
||||||
base64 = "0.12"
|
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"
|
||||||
|
|
|
@ -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
|
||||||
///
|
///
|
||||||
|
|
|
@ -191,6 +191,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,13 +264,16 @@ 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();
|
||||||
let resp = srv.call(req).await.unwrap();
|
let resp = srv.call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
39
src/data.rs
39
src/data.rs
|
@ -1,3 +1,4 @@
|
||||||
|
use std::any::type_name;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -19,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;
|
||||||
|
@ -47,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;
|
||||||
|
@ -70,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))
|
||||||
}
|
}
|
||||||
|
@ -121,8 +113,9 @@ impl<T: ?Sized + 'static> FromRequest for Data<T> {
|
||||||
} else {
|
} else {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Failed to construct App-level Data extractor. \
|
"Failed to construct App-level Data extractor. \
|
||||||
Request path: {:?}",
|
Request path: {:?} (type: {})",
|
||||||
req.path()
|
req.path(),
|
||||||
|
type_name::<T>(),
|
||||||
);
|
);
|
||||||
err(ErrorInternalServerError(
|
err(ErrorInternalServerError(
|
||||||
"App data is not configured, to configure use App::data()",
|
"App data is not configured, to configure use App::data()",
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -13,7 +13,7 @@ use actix_service::{Service, Transform};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::future::{ok, Ready};
|
use futures_util::future::{ok, Ready};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use regex::Regex;
|
use regex::{Regex, RegexSet};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::dev::{BodySize, MessageBody, ResponseBody};
|
use crate::dev::{BodySize, MessageBody, ResponseBody};
|
||||||
|
@ -92,6 +92,7 @@ pub struct Logger(Rc<Inner>);
|
||||||
struct Inner {
|
struct Inner {
|
||||||
format: Format,
|
format: Format,
|
||||||
exclude: HashSet<String>,
|
exclude: HashSet<String>,
|
||||||
|
exclude_regex: RegexSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Logger {
|
impl Logger {
|
||||||
|
@ -100,6 +101,7 @@ impl Logger {
|
||||||
Logger(Rc::new(Inner {
|
Logger(Rc::new(Inner {
|
||||||
format: Format::new(format),
|
format: Format::new(format),
|
||||||
exclude: HashSet::new(),
|
exclude: HashSet::new(),
|
||||||
|
exclude_regex: RegexSet::empty(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +113,16 @@ impl Logger {
|
||||||
.insert(path.into());
|
.insert(path.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ignore and do not log access info for paths that match regex
|
||||||
|
pub fn exclude_regex<T: Into<String>>(mut self, path: T) -> Self {
|
||||||
|
let inner = Rc::get_mut(&mut self.0).unwrap();
|
||||||
|
let mut patterns = inner.exclude_regex.patterns().to_vec();
|
||||||
|
patterns.push(path.into());
|
||||||
|
let regex_set = RegexSet::new(patterns).unwrap();
|
||||||
|
inner.exclude_regex = regex_set;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Logger {
|
impl Default for Logger {
|
||||||
|
@ -123,6 +135,7 @@ impl Default for Logger {
|
||||||
Logger(Rc::new(Inner {
|
Logger(Rc::new(Inner {
|
||||||
format: Format::default(),
|
format: Format::default(),
|
||||||
exclude: HashSet::new(),
|
exclude: HashSet::new(),
|
||||||
|
exclude_regex: RegexSet::empty(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,7 +181,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||||
if self.inner.exclude.contains(req.path()) {
|
if self.inner.exclude.contains(req.path())
|
||||||
|
|| self.inner.exclude_regex.is_match(req.path())
|
||||||
|
{
|
||||||
LoggerResponse {
|
LoggerResponse {
|
||||||
fut: self.service.call(req),
|
fut: self.service.call(req),
|
||||||
format: None,
|
format: None,
|
||||||
|
@ -538,6 +553,28 @@ mod tests {
|
||||||
let _res = srv.call(req).await;
|
let _res = srv.call(req).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_logger_exclude_regex() {
|
||||||
|
let srv = |req: ServiceRequest| {
|
||||||
|
ok(req.into_response(
|
||||||
|
HttpResponse::build(StatusCode::OK)
|
||||||
|
.header("X-Test", "ttt")
|
||||||
|
.finish(),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test")
|
||||||
|
.exclude_regex("\\w");
|
||||||
|
|
||||||
|
let mut srv = logger.new_transform(srv.into_service()).await.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::with_header(
|
||||||
|
header::USER_AGENT,
|
||||||
|
header::HeaderValue::from_static("ACTIX-WEB"),
|
||||||
|
)
|
||||||
|
.to_srv_request();
|
||||||
|
let _res = srv.call(req).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_url_path() {
|
async fn test_url_path() {
|
||||||
let mut format = Format::new("%T %U");
|
let mut format = Format::new("%T %U");
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
* add ability to set address for `TestServer` [#1645]
|
* add ability to set address for `TestServer` [#1645]
|
||||||
|
* Upgrade `base64` to `0.13`.
|
||||||
|
|
||||||
[#1645]: https://github.com/actix/actix-web/pull/1645
|
[#1645]: https://github.com/actix/actix-web/pull/1645
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ actix-server = "1.0.0"
|
||||||
actix-testing = "1.0.0"
|
actix-testing = "1.0.0"
|
||||||
awc = "2.0.0"
|
awc = "2.0.0"
|
||||||
|
|
||||||
base64 = "0.12"
|
base64 = "0.13"
|
||||||
bytes = "0.5.3"
|
bytes = "0.5.3"
|
||||||
futures-core = { version = "0.3.5", default-features = false }
|
futures-core = { version = "0.3.5", default-features = false }
|
||||||
http = "0.2.0"
|
http = "0.2.0"
|
||||||
|
|
Loading…
Reference in New Issue