mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-10 04:46:00 +02:00
Compare commits
8 Commits
files-v0.6
...
web-v4.0.0
Author | SHA1 | Date | |
---|---|---|---|
9779010a5a | |||
11d50d792b | |||
798e9911e9 | |||
2b2de29800 | |||
0f5c876c6b | |||
96a4dc9dec | |||
4616ca8ee6 | |||
36193b0a50 |
20
CHANGES.md
20
CHANGES.md
@ -3,6 +3,26 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0-beta.17 - 2021-12-29
|
||||||
|
### Added
|
||||||
|
- `guard::GuardContext` for use with the `Guard` trait. [#2552]
|
||||||
|
- `ServiceRequest::guard_ctx` for obtaining a guard context. [#2552]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `Guard` trait now receives a `&GuardContext`. [#2552]
|
||||||
|
- `guard::fn_guard` functions now receives a `&GuardContext`. [#2552]
|
||||||
|
- Some guards now return `impl Guard` and their concrete types are made private: `guard::Header` and all the method guards. [#2552]
|
||||||
|
- The `Not` guard is now generic over the type of guard it wraps. [#2552]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Rename `ConnectionInfo::{remote_addr => peer_addr}`, deprecating the old name. [#2554]
|
||||||
|
- `ConnectionInfo::peer_addr` will not return the port number. [#2554]
|
||||||
|
- `ConnectionInfo::realip_remote_addr` will not return the port number if sourcing the IP from the peer's socket address. [#2554]
|
||||||
|
|
||||||
|
[#2552]: https://github.com/actix/actix-web/pull/2552
|
||||||
|
[#2554]: https://github.com/actix/actix-web/pull/2554
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.16 - 2021-12-27
|
## 4.0.0-beta.16 - 2021-12-27
|
||||||
### Changed
|
### Changed
|
||||||
- No longer require `Scope` service body type to be boxed. [#2523]
|
- No longer require `Scope` service body type to be boxed. [#2523]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "4.0.0-beta.16"
|
version = "4.0.0-beta.17"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
||||||
keywords = ["actix", "http", "web", "framework", "async"]
|
keywords = ["actix", "http", "web", "framework", "async"]
|
||||||
@ -107,7 +107,7 @@ url = "2.1"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] }
|
actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] }
|
||||||
awc = { version = "3.0.0-beta.15", features = ["openssl"] }
|
awc = { version = "3.0.0-beta.16", features = ["openssl"] }
|
||||||
|
|
||||||
brotli2 = "0.3.2"
|
brotli2 = "0.3.2"
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
<p>
|
<p>
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web)
|
[](https://crates.io/crates/actix-web)
|
||||||
[](https://docs.rs/actix-web/4.0.0-beta.16)
|
[](https://docs.rs/actix-web/4.0.0-beta.17)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-web/4.0.0-beta.16)
|
[](https://deps.rs/crate/actix-web/4.0.0-beta.17)
|
||||||
<br />
|
<br />
|
||||||
[](https://github.com/actix/actix-web/actions)
|
[](https://github.com/actix/actix-web/actions)
|
||||||
[](https://codecov.io/gh/actix/actix-web)
|
[](https://codecov.io/gh/actix/actix-web)
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0-beta.12 - 2021-12-29
|
||||||
|
* No significant changes since `0.6.0-beta.11`.
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.11 - 2021-12-27
|
## 0.6.0-beta.11 - 2021-12-27
|
||||||
* No significant changes since `0.6.0-beta.10`.
|
* No significant changes since `0.6.0-beta.10`.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.6.0-beta.11"
|
version = "0.6.0-beta.12"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"fakeshadow <24548779@qq.com>",
|
"fakeshadow <24548779@qq.com>",
|
||||||
@ -25,7 +25,7 @@ experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
|
|||||||
actix-http = "3.0.0-beta.17"
|
actix-http = "3.0.0-beta.17"
|
||||||
actix-service = "2"
|
actix-service = "2"
|
||||||
actix-utils = "3"
|
actix-utils = "3"
|
||||||
actix-web = { version = "4.0.0-beta.16", default-features = false }
|
actix-web = { version = "4.0.0-beta.17", default-features = false }
|
||||||
|
|
||||||
askama_escape = "0.10"
|
askama_escape = "0.10"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
@ -44,4 +44,4 @@ tokio-uring = { version = "0.1", optional = true }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.10"
|
actix-test = "0.1.0-beta.10"
|
||||||
actix-web = "4.0.0-beta.16"
|
actix-web = "4.0.0-beta.17"
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
> Static file serving for Actix Web
|
> Static file serving for Actix Web
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://docs.rs/actix-files/0.6.0-beta.11)
|
[](https://docs.rs/actix-files/0.6.0-beta.12)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-files/0.6.0-beta.11)
|
[](https://deps.rs/crate/actix-files/0.6.0-beta.12)
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use std::{fmt, io, ops::Deref, path::PathBuf, rc::Rc};
|
use std::{fmt, io, ops::Deref, path::PathBuf, rc::Rc};
|
||||||
|
|
||||||
use actix_service::Service;
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{ServiceRequest, ServiceResponse},
|
body::BoxBody,
|
||||||
|
dev::{Service, ServiceRequest, ServiceResponse},
|
||||||
error::Error,
|
error::Error,
|
||||||
guard::Guard,
|
guard::Guard,
|
||||||
http::{header, Method},
|
http::{header, Method},
|
||||||
@ -94,7 +94,7 @@ impl fmt::Debug for FilesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Service<ServiceRequest> for FilesService {
|
impl Service<ServiceRequest> for FilesService {
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse<BoxBody>;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ impl Service<ServiceRequest> for FilesService {
|
|||||||
fn call(&self, req: ServiceRequest) -> Self::Future {
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
let is_method_valid = if let Some(guard) = &self.guards {
|
let is_method_valid = if let Some(guard) = &self.guards {
|
||||||
// execute user defined guards
|
// execute user defined guards
|
||||||
(**guard).check(req.head())
|
(**guard).check(&req.guard_ctx())
|
||||||
} else {
|
} else {
|
||||||
// default behavior
|
// default behavior
|
||||||
matches!(*req.method(), Method::HEAD | Method::GET)
|
matches!(*req.method(), Method::HEAD | Method::GET)
|
||||||
|
@ -35,7 +35,7 @@ actix-tls = "3.0.0"
|
|||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-server = "2.0.0-rc.2"
|
actix-server = "2.0.0-rc.2"
|
||||||
awc = { version = "3.0.0-beta.15", default-features = false }
|
awc = { version = "3.0.0-beta.16", default-features = false }
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
@ -48,8 +48,8 @@ serde_json = "1.0"
|
|||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||||
tokio = { version = "1.8", features = ["sync"] }
|
tokio = { version = "1.8.4", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.16", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4.0.0-beta.17", default-features = false, features = ["cookies"] }
|
||||||
actix-http = "3.0.0-beta.17"
|
actix-http = "3.0.0-beta.17"
|
||||||
|
@ -82,7 +82,7 @@ zstd = { version = "0.9", optional = true }
|
|||||||
actix-http-test = { version = "3.0.0-beta.10", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.10", features = ["openssl"] }
|
||||||
actix-server = "2.0.0-rc.2"
|
actix-server = "2.0.0-rc.2"
|
||||||
actix-tls = { version = "3.0.0", features = ["openssl"] }
|
actix-tls = { version = "3.0.0", features = ["openssl"] }
|
||||||
actix-web = "4.0.0-beta.16"
|
actix-web = "4.0.0-beta.17"
|
||||||
|
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
@ -96,7 +96,7 @@ serde_json = "1.0"
|
|||||||
static_assertions = "1"
|
static_assertions = "1"
|
||||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
tls-openssl = { package = "openssl", version = "0.10.9" }
|
||||||
tls-rustls = { package = "rustls", version = "0.20.0" }
|
tls-rustls = { package = "rustls", version = "0.20.0" }
|
||||||
tokio = { version = "1.8", features = ["net", "rt", "macros"] }
|
tokio = { version = "1.8.4", features = ["net", "rt", "macros"] }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "ws"
|
name = "ws"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, ops, rc::Rc};
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ impl<T: Head> Message<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Head> std::ops::Deref for Message<T> {
|
impl<T: Head> ops::Deref for Message<T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
@ -57,7 +57,7 @@ impl<T: Head> std::ops::Deref for Message<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Head> std::ops::DerefMut for Message<T> {
|
impl<T: Head> ops::DerefMut for Message<T> {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
Rc::get_mut(&mut self.head).expect("Multiple copies exist")
|
Rc::get_mut(&mut self.head).expect("Multiple copies exist")
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = { version = "4.0.0-beta.16", default-features = false }
|
actix-web = { version = "4.0.0-beta.17", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
@ -30,5 +30,5 @@ twoway = "0.2"
|
|||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-http = "3.0.0-beta.17"
|
actix-http = "3.0.0-beta.17"
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
tokio = { version = "1.8", features = ["sync"] }
|
tokio = { version = "1.8.4", features = ["sync"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
|
@ -34,8 +34,8 @@ actix-http-test = "3.0.0-beta.10"
|
|||||||
actix-rt = "2.1"
|
actix-rt = "2.1"
|
||||||
actix-service = "2.0.0"
|
actix-service = "2.0.0"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = { version = "4.0.0-beta.16", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4.0.0-beta.17", default-features = false, features = ["cookies"] }
|
||||||
awc = { version = "3.0.0-beta.15", default-features = false, features = ["cookies"] }
|
awc = { version = "3.0.0-beta.16", default-features = false, features = ["cookies"] }
|
||||||
|
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = [] }
|
futures-util = { version = "0.3.7", default-features = false, features = [] }
|
||||||
@ -45,4 +45,4 @@ serde_json = "1"
|
|||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
|
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
|
||||||
tls-rustls = { package = "rustls", version = "0.20.0", optional = true }
|
tls-rustls = { package = "rustls", version = "0.20.0", optional = true }
|
||||||
tokio = { version = "1.8", features = ["sync"] }
|
tokio = { version = "1.8.4", features = ["sync"] }
|
||||||
|
@ -17,18 +17,18 @@ path = "src/lib.rs"
|
|||||||
actix = { version = "0.12.0", default-features = false }
|
actix = { version = "0.12.0", default-features = false }
|
||||||
actix-codec = "0.4.1"
|
actix-codec = "0.4.1"
|
||||||
actix-http = "3.0.0-beta.17"
|
actix-http = "3.0.0-beta.17"
|
||||||
actix-web = { version = "4.0.0-beta.16", default-features = false }
|
actix-web = { version = "4.0.0-beta.17", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
pin-project-lite = "0.2"
|
pin-project-lite = "0.2"
|
||||||
tokio = { version = "1.8", features = ["sync"] }
|
tokio = { version = "1.8.4", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.10"
|
actix-test = "0.1.0-beta.10"
|
||||||
awc = { version = "3.0.0-beta.15", default-features = false }
|
awc = { version = "3.0.0-beta.16", default-features = false }
|
||||||
|
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
|
@ -25,7 +25,7 @@ actix-macros = "0.2.3"
|
|||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.10"
|
actix-test = "0.1.0-beta.10"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = "4.0.0-beta.16"
|
actix-web = "4.0.0-beta.17"
|
||||||
|
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
trybuild = "1"
|
trybuild = "1"
|
||||||
|
@ -3,6 +3,14 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.16 - 2021-12-29
|
||||||
|
- `*::send_json` and `*::send_form` methods now receive `impl Serialize`. [#2553]
|
||||||
|
- `FrozenClientRequest::extra_header` now uses receives an `impl TryIntoHeaderPair`. [#2553]
|
||||||
|
- Remove unnecessary `Unpin` bounds on `*::send_stream`. [#2553]
|
||||||
|
|
||||||
|
[#2553]: https://github.com/actix/actix-web/pull/2553
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.15 - 2021-12-27
|
## 3.0.0-beta.15 - 2021-12-27
|
||||||
- Rename `Connector::{ssl => openssl}`. [#2503]
|
- Rename `Connector::{ssl => openssl}`. [#2503]
|
||||||
- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503]
|
- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "awc"
|
name = "awc"
|
||||||
version = "3.0.0-beta.15"
|
version = "3.0.0-beta.16"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"fakeshadow <24548779@qq.com>",
|
"fakeshadow <24548779@qq.com>",
|
||||||
@ -83,7 +83,7 @@ rand = "0.8"
|
|||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
tokio = { version = "1.8", features = ["sync"] }
|
tokio = { version = "1.8.4", features = ["sync"] }
|
||||||
|
|
||||||
cookie = { version = "0.15", features = ["percent-encode"], optional = true }
|
cookie = { version = "0.15", features = ["percent-encode"], optional = true }
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ actix-server = "2.0.0-rc.2"
|
|||||||
actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] }
|
actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] }
|
||||||
actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] }
|
actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] }
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = { version = "4.0.0-beta.16", features = ["openssl"] }
|
actix-web = { version = "4.0.0-beta.17", features = ["openssl"] }
|
||||||
|
|
||||||
brotli2 = "0.3.2"
|
brotli2 = "0.3.2"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
> Async HTTP and WebSocket client library.
|
> Async HTTP and WebSocket client library.
|
||||||
|
|
||||||
[](https://crates.io/crates/awc)
|
[](https://crates.io/crates/awc)
|
||||||
[](https://docs.rs/awc/3.0.0-beta.15)
|
[](https://docs.rs/awc/3.0.0-beta.16)
|
||||||

|

|
||||||
[](https://deps.rs/crate/awc/3.0.0-beta.15)
|
[](https://deps.rs/crate/awc/3.0.0-beta.16)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
|
||||||
fmt, mem,
|
fmt, mem,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::Bytes;
|
||||||
use futures_core::Stream;
|
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use actix_http::body::{BodySize, BodyStream, BoxBody, MessageBody, SizedStream};
|
use actix_http::body::{BodySize, BoxBody, MessageBody};
|
||||||
|
|
||||||
use crate::BoxError;
|
|
||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
/// Represents various types of HTTP message body.
|
/// Represents various types of HTTP message body.
|
||||||
@ -160,91 +156,6 @@ impl<S: fmt::Debug> fmt::Debug for AnyBody<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B> From<&'static str> for AnyBody<B> {
|
|
||||||
fn from(string: &'static str) -> Self {
|
|
||||||
Self::Bytes {
|
|
||||||
body: Bytes::from_static(string.as_ref()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<&'static [u8]> for AnyBody<B> {
|
|
||||||
fn from(bytes: &'static [u8]) -> Self {
|
|
||||||
Self::Bytes {
|
|
||||||
body: Bytes::from_static(bytes),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<Vec<u8>> for AnyBody<B> {
|
|
||||||
fn from(vec: Vec<u8>) -> Self {
|
|
||||||
Self::Bytes {
|
|
||||||
body: Bytes::from(vec),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<String> for AnyBody<B> {
|
|
||||||
fn from(string: String) -> Self {
|
|
||||||
Self::Bytes {
|
|
||||||
body: Bytes::from(string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<&'_ String> for AnyBody<B> {
|
|
||||||
fn from(string: &String) -> Self {
|
|
||||||
Self::Bytes {
|
|
||||||
body: Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<Cow<'_, str>> for AnyBody<B> {
|
|
||||||
fn from(string: Cow<'_, str>) -> Self {
|
|
||||||
match string {
|
|
||||||
Cow::Owned(s) => Self::from(s),
|
|
||||||
Cow::Borrowed(s) => Self::Bytes {
|
|
||||||
body: Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<Bytes> for AnyBody<B> {
|
|
||||||
fn from(bytes: Bytes) -> Self {
|
|
||||||
Self::Bytes { body: bytes }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<BytesMut> for AnyBody<B> {
|
|
||||||
fn from(bytes: BytesMut) -> Self {
|
|
||||||
Self::Bytes {
|
|
||||||
body: bytes.freeze(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, E> From<SizedStream<S>> for AnyBody
|
|
||||||
where
|
|
||||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
|
||||||
E: Into<BoxError> + 'static,
|
|
||||||
{
|
|
||||||
fn from(stream: SizedStream<S>) -> Self {
|
|
||||||
AnyBody::new_boxed(stream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, E> From<BodyStream<S>> for AnyBody
|
|
||||||
where
|
|
||||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
|
||||||
E: Into<BoxError> + 'static,
|
|
||||||
{
|
|
||||||
fn from(stream: BodyStream<S>) -> Self {
|
|
||||||
AnyBody::new_boxed(stream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::marker::PhantomPinned;
|
use std::marker::PhantomPinned;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{convert::TryFrom, net, rc::Rc, time::Duration};
|
use std::{net, rc::Rc, time::Duration};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
@ -7,7 +7,7 @@ use serde::Serialize;
|
|||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::MessageBody,
|
body::MessageBody,
|
||||||
error::HttpError,
|
error::HttpError,
|
||||||
header::{HeaderMap, HeaderName, TryIntoHeaderValue},
|
header::{HeaderMap, TryIntoHeaderPair},
|
||||||
Method, RequestHead, Uri,
|
Method, RequestHead, Uri,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// `FrozenClientRequest` struct represents cloneable client request.
|
/// `FrozenClientRequest` struct represents cloneable client request.
|
||||||
|
///
|
||||||
/// It could be used to send same request multiple times.
|
/// It could be used to send same request multiple times.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FrozenClientRequest {
|
pub struct FrozenClientRequest {
|
||||||
@ -83,7 +84,7 @@ impl FrozenClientRequest {
|
|||||||
/// Send a streaming body.
|
/// Send a streaming body.
|
||||||
pub fn send_stream<S, E>(&self, stream: S) -> SendClientRequest
|
pub fn send_stream<S, E>(&self, stream: S) -> SendClientRequest
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||||
E: Into<BoxError> + 'static,
|
E: Into<BoxError> + 'static,
|
||||||
{
|
{
|
||||||
RequestSender::Rc(self.head.clone(), None).send_stream(
|
RequestSender::Rc(self.head.clone(), None).send_stream(
|
||||||
@ -105,20 +106,14 @@ impl FrozenClientRequest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a `FrozenSendBuilder` with extra headers
|
/// Clones this `FrozenClientRequest`, returning a new one with extra headers added.
|
||||||
pub fn extra_headers(&self, extra_headers: HeaderMap) -> FrozenSendBuilder {
|
pub fn extra_headers(&self, extra_headers: HeaderMap) -> FrozenSendBuilder {
|
||||||
FrozenSendBuilder::new(self.clone(), extra_headers)
|
FrozenSendBuilder::new(self.clone(), extra_headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a `FrozenSendBuilder` with an extra header
|
/// Clones this `FrozenClientRequest`, returning a new one with the extra header added.
|
||||||
pub fn extra_header<K, V>(&self, key: K, value: V) -> FrozenSendBuilder
|
pub fn extra_header(&self, header: impl TryIntoHeaderPair) -> FrozenSendBuilder {
|
||||||
where
|
self.extra_headers(HeaderMap::new()).extra_header(header)
|
||||||
HeaderName: TryFrom<K>,
|
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: TryIntoHeaderValue,
|
|
||||||
{
|
|
||||||
self.extra_headers(HeaderMap::new())
|
|
||||||
.extra_header(key, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,29 +134,20 @@ impl FrozenSendBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a header, it overrides existing header in `FrozenClientRequest`.
|
/// Insert a header, it overrides existing header in `FrozenClientRequest`.
|
||||||
pub fn extra_header<K, V>(mut self, key: K, value: V) -> Self
|
pub fn extra_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||||
where
|
match header.try_into_pair() {
|
||||||
HeaderName: TryFrom<K>,
|
Ok((key, value)) => {
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
self.extra_headers.insert(key, value);
|
||||||
V: TryIntoHeaderValue,
|
}
|
||||||
{
|
|
||||||
match HeaderName::try_from(key) {
|
Err(err) => self.err = Some(err.into()),
|
||||||
Ok(key) => match value.try_into_value() {
|
|
||||||
Ok(value) => {
|
|
||||||
self.extra_headers.insert(key, value);
|
|
||||||
}
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
},
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Complete request construction and send a body.
|
/// Complete request construction and send a body.
|
||||||
pub fn send_body<B>(self, body: B) -> SendClientRequest
|
pub fn send_body(self, body: impl MessageBody + 'static) -> SendClientRequest {
|
||||||
where
|
|
||||||
B: MessageBody + 'static,
|
|
||||||
{
|
|
||||||
if let Some(e) = self.err {
|
if let Some(e) = self.err {
|
||||||
return e.into();
|
return e.into();
|
||||||
}
|
}
|
||||||
@ -176,9 +162,9 @@ impl FrozenSendBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Complete request construction and send a json body.
|
/// Complete request construction and send a json body.
|
||||||
pub fn send_json<T: Serialize>(self, value: &T) -> SendClientRequest {
|
pub fn send_json(self, value: impl Serialize) -> SendClientRequest {
|
||||||
if let Some(e) = self.err {
|
if let Some(err) = self.err {
|
||||||
return e.into();
|
return err.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_json(
|
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_json(
|
||||||
@ -191,7 +177,7 @@ impl FrozenSendBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Complete request construction and send an urlencoded body.
|
/// Complete request construction and send an urlencoded body.
|
||||||
pub fn send_form<T: Serialize>(self, value: &T) -> SendClientRequest {
|
pub fn send_form(self, value: impl Serialize) -> SendClientRequest {
|
||||||
if let Some(e) = self.err {
|
if let Some(e) = self.err {
|
||||||
return e.into();
|
return e.into();
|
||||||
}
|
}
|
||||||
@ -208,7 +194,7 @@ impl FrozenSendBuilder {
|
|||||||
/// Complete request construction and send a streaming body.
|
/// Complete request construction and send a streaming body.
|
||||||
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
|
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||||
E: Into<BoxError> + 'static,
|
E: Into<BoxError> + 'static,
|
||||||
{
|
{
|
||||||
if let Some(e) = self.err {
|
if let Some(e) = self.err {
|
||||||
|
@ -190,7 +190,9 @@ where
|
|||||||
let body_new = if is_redirect {
|
let body_new = if is_redirect {
|
||||||
// try to reuse body
|
// try to reuse body
|
||||||
match body {
|
match body {
|
||||||
Some(ref bytes) => AnyBody::from(bytes.clone()),
|
Some(ref bytes) => AnyBody::Bytes {
|
||||||
|
body: bytes.clone(),
|
||||||
|
},
|
||||||
// TODO: should this be AnyBody::Empty or AnyBody::None.
|
// TODO: should this be AnyBody::Empty or AnyBody::None.
|
||||||
_ => AnyBody::empty(),
|
_ => AnyBody::empty(),
|
||||||
}
|
}
|
||||||
|
@ -385,7 +385,7 @@ impl ClientRequest {
|
|||||||
/// Set an streaming body and generate `ClientRequest`.
|
/// Set an streaming body and generate `ClientRequest`.
|
||||||
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
|
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||||
E: Into<BoxError> + 'static,
|
E: Into<BoxError> + 'static,
|
||||||
{
|
{
|
||||||
let slf = match self.prep_for_sending() {
|
let slf = match self.prep_for_sending() {
|
||||||
|
@ -7,8 +7,8 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
error::PayloadError, header, header::HeaderMap, BoxedPayloadStream, Extensions,
|
error::PayloadError, header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage,
|
||||||
HttpMessage, Payload, ResponseHead, StatusCode, Version,
|
Payload, ResponseHead, StatusCode, Version,
|
||||||
};
|
};
|
||||||
use actix_rt::time::{sleep, Sleep};
|
use actix_rt::time::{sleep, Sleep};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
@ -119,12 +119,13 @@ impl<S> ClientResponse<S> {
|
|||||||
|
|
||||||
if self.extensions().get::<Cookies>().is_none() {
|
if self.extensions().get::<Cookies>().is_none() {
|
||||||
let mut cookies = Vec::new();
|
let mut cookies = Vec::new();
|
||||||
for hdr in self.headers().get_all(&header::SET_COOKIE) {
|
for hdr in self.headers().get_all(&actix_http::header::SET_COOKIE) {
|
||||||
let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
|
let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
|
||||||
cookies.push(Cookie::parse_encoded(s)?.into_owned());
|
cookies.push(Cookie::parse_encoded(s)?.into_owned());
|
||||||
}
|
}
|
||||||
self.extensions_mut().insert(Cookies(cookies));
|
self.extensions_mut().insert(Cookies(cookies));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Ref::map(self.extensions(), |ext| {
|
Ok(Ref::map(self.extensions(), |ext| {
|
||||||
&ext.get::<Cookies>().unwrap().0
|
&ext.get::<Cookies>().unwrap().0
|
||||||
}))
|
}))
|
||||||
|
@ -181,17 +181,14 @@ pub(crate) enum RequestSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RequestSender {
|
impl RequestSender {
|
||||||
pub(crate) fn send_body<B>(
|
pub(crate) fn send_body(
|
||||||
self,
|
self,
|
||||||
addr: Option<net::SocketAddr>,
|
addr: Option<net::SocketAddr>,
|
||||||
response_decompress: bool,
|
response_decompress: bool,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
config: &ClientConfig,
|
config: &ClientConfig,
|
||||||
body: B,
|
body: impl MessageBody + 'static,
|
||||||
) -> SendClientRequest
|
) -> SendClientRequest {
|
||||||
where
|
|
||||||
B: MessageBody + 'static,
|
|
||||||
{
|
|
||||||
let req = match self {
|
let req = match self {
|
||||||
RequestSender::Owned(head) => ConnectRequest::Client(
|
RequestSender::Owned(head) => ConnectRequest::Client(
|
||||||
RequestHeadType::Owned(head),
|
RequestHeadType::Owned(head),
|
||||||
@ -210,15 +207,15 @@ impl RequestSender {
|
|||||||
SendClientRequest::new(fut, response_decompress, timeout.or(config.timeout))
|
SendClientRequest::new(fut, response_decompress, timeout.or(config.timeout))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn send_json<T: Serialize>(
|
pub(crate) fn send_json(
|
||||||
mut self,
|
mut self,
|
||||||
addr: Option<net::SocketAddr>,
|
addr: Option<net::SocketAddr>,
|
||||||
response_decompress: bool,
|
response_decompress: bool,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
config: &ClientConfig,
|
config: &ClientConfig,
|
||||||
value: &T,
|
value: impl Serialize,
|
||||||
) -> SendClientRequest {
|
) -> SendClientRequest {
|
||||||
let body = match serde_json::to_string(value) {
|
let body = match serde_json::to_string(&value) {
|
||||||
Ok(body) => body,
|
Ok(body) => body,
|
||||||
Err(err) => return PrepForSendingError::Json(err).into(),
|
Err(err) => return PrepForSendingError::Json(err).into(),
|
||||||
};
|
};
|
||||||
@ -227,22 +224,16 @@ impl RequestSender {
|
|||||||
return e.into();
|
return e.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.send_body(
|
self.send_body(addr, response_decompress, timeout, config, body)
|
||||||
addr,
|
|
||||||
response_decompress,
|
|
||||||
timeout,
|
|
||||||
config,
|
|
||||||
AnyBody::from_message_body(body.into_bytes()),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn send_form<T: Serialize>(
|
pub(crate) fn send_form(
|
||||||
mut self,
|
mut self,
|
||||||
addr: Option<net::SocketAddr>,
|
addr: Option<net::SocketAddr>,
|
||||||
response_decompress: bool,
|
response_decompress: bool,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
config: &ClientConfig,
|
config: &ClientConfig,
|
||||||
value: &T,
|
value: impl Serialize,
|
||||||
) -> SendClientRequest {
|
) -> SendClientRequest {
|
||||||
let body = match serde_urlencoded::to_string(value) {
|
let body = match serde_urlencoded::to_string(value) {
|
||||||
Ok(body) => body,
|
Ok(body) => body,
|
||||||
@ -250,19 +241,13 @@ impl RequestSender {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// set content-type
|
// set content-type
|
||||||
if let Err(e) =
|
if let Err(err) =
|
||||||
self.set_header_if_none(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
|
self.set_header_if_none(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||||
{
|
{
|
||||||
return e.into();
|
return err.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.send_body(
|
self.send_body(addr, response_decompress, timeout, config, body)
|
||||||
addr,
|
|
||||||
response_decompress,
|
|
||||||
timeout,
|
|
||||||
config,
|
|
||||||
AnyBody::from_message_body(body.into_bytes()),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn send_stream<S, E>(
|
pub(crate) fn send_stream<S, E>(
|
||||||
@ -274,7 +259,7 @@ impl RequestSender {
|
|||||||
stream: S,
|
stream: S,
|
||||||
) -> SendClientRequest
|
) -> SendClientRequest
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||||
E: Into<BoxError> + 'static,
|
E: Into<BoxError> + 'static,
|
||||||
{
|
{
|
||||||
self.send_body(
|
self.send_body(
|
||||||
@ -282,7 +267,7 @@ impl RequestSender {
|
|||||||
response_decompress,
|
response_decompress,
|
||||||
timeout,
|
timeout,
|
||||||
config,
|
config,
|
||||||
AnyBody::new_boxed(BodyStream::new(stream)),
|
BodyStream::new(stream),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,7 +278,7 @@ impl RequestSender {
|
|||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
config: &ClientConfig,
|
config: &ClientConfig,
|
||||||
) -> SendClientRequest {
|
) -> SendClientRequest {
|
||||||
self.send_body(addr, response_decompress, timeout, config, AnyBody::empty())
|
self.send_body(addr, response_decompress, timeout, config, ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_header_if_none<V>(&mut self, key: HeaderName, value: V) -> Result<(), HttpError>
|
fn set_header_if_none<V>(&mut self, key: HeaderName, value: V) -> Result<(), HttpError>
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
use std::{cell::RefCell, mem, rc::Rc};
|
use std::{cell::RefCell, mem, rc::Rc};
|
||||||
|
|
||||||
use actix_http::{Extensions, Request};
|
use actix_http::Request;
|
||||||
use actix_router::{Path, ResourceDef, Router, Url};
|
use actix_router::{Path, ResourceDef, Router, Url};
|
||||||
use actix_service::{boxed, fn_service, Service, ServiceFactory};
|
use actix_service::{boxed, fn_service, Service, ServiceFactory};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use futures_util::future::join_all;
|
use futures_util::future::join_all;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
body::BoxBody,
|
||||||
config::{AppConfig, AppService},
|
config::{AppConfig, AppService},
|
||||||
data::FnDataFactory,
|
data::FnDataFactory,
|
||||||
|
dev::Extensions,
|
||||||
guard::Guard,
|
guard::Guard,
|
||||||
request::{HttpRequest, HttpRequestPool},
|
request::{HttpRequest, HttpRequestPool},
|
||||||
rmap::ResourceMap,
|
rmap::ResourceMap,
|
||||||
@ -297,7 +299,7 @@ pub struct AppRouting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Service<ServiceRequest> for AppRouting {
|
impl Service<ServiceRequest> for AppRouting {
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse<BoxBody>;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
@ -306,12 +308,15 @@ impl Service<ServiceRequest> for AppRouting {
|
|||||||
fn call(&self, mut req: ServiceRequest) -> Self::Future {
|
fn call(&self, mut req: ServiceRequest) -> Self::Future {
|
||||||
let res = self.router.recognize_fn(&mut req, |req, guards| {
|
let res = self.router.recognize_fn(&mut req, |req, guards| {
|
||||||
if let Some(ref guards) = guards {
|
if let Some(ref guards) = guards {
|
||||||
for f in guards {
|
let guard_ctx = req.guard_ctx();
|
||||||
if !f.check(req.head()) {
|
|
||||||
|
for guard in guards {
|
||||||
|
if !guard.check(&guard_ctx) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ impl AppConfig {
|
|||||||
|
|
||||||
/// Server host name.
|
/// Server host name.
|
||||||
///
|
///
|
||||||
/// Host name is used by application router as a hostname for url generation.
|
/// Host name is used by application router as a hostname for URL generation.
|
||||||
/// Check [ConnectionInfo](super::dev::ConnectionInfo::host())
|
/// Check [ConnectionInfo](super::dev::ConnectionInfo::host())
|
||||||
/// documentation for more information.
|
/// documentation for more information.
|
||||||
///
|
///
|
||||||
@ -137,7 +137,7 @@ impl AppConfig {
|
|||||||
&self.host
|
&self.host
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if connection is secure(https)
|
/// Returns true if connection is secure (i.e., running over `https:`).
|
||||||
pub fn secure(&self) -> bool {
|
pub fn secure(&self) -> bool {
|
||||||
self.secure
|
self.secure
|
||||||
}
|
}
|
||||||
|
23
src/dev.rs
23
src/dev.rs
@ -3,6 +3,16 @@
|
|||||||
//! Most users will not have to interact with the types in this module, but it is useful for those
|
//! Most users will not have to interact with the types in this module, but it is useful for those
|
||||||
//! writing extractors, middleware, libraries, or interacting with the service API directly.
|
//! writing extractors, middleware, libraries, or interacting with the service API directly.
|
||||||
|
|
||||||
|
pub use actix_http::{Extensions, Payload, RequestHead, Response, ResponseHead};
|
||||||
|
pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
|
||||||
|
pub use actix_server::{Server, ServerHandle};
|
||||||
|
pub use actix_service::{
|
||||||
|
always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "__compress")]
|
||||||
|
pub use actix_http::encoding::Decoder as Decompress;
|
||||||
|
|
||||||
pub use crate::config::{AppConfig, AppService};
|
pub use crate::config::{AppConfig, AppService};
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use crate::handler::Handler;
|
pub use crate::handler::Handler;
|
||||||
@ -14,16 +24,6 @@ pub use crate::types::form::UrlEncoded;
|
|||||||
pub use crate::types::json::JsonBody;
|
pub use crate::types::json::JsonBody;
|
||||||
pub use crate::types::readlines::Readlines;
|
pub use crate::types::readlines::Readlines;
|
||||||
|
|
||||||
pub use actix_http::{Extensions, Payload, RequestHead, Response, ResponseHead};
|
|
||||||
pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
|
|
||||||
pub use actix_server::{Server, ServerHandle};
|
|
||||||
pub use actix_service::{
|
|
||||||
always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "__compress")]
|
|
||||||
pub use actix_http::encoding::Decoder as Decompress;
|
|
||||||
|
|
||||||
use crate::http::header::ContentEncoding;
|
use crate::http::header::ContentEncoding;
|
||||||
|
|
||||||
use actix_router::Patterns;
|
use actix_router::Patterns;
|
||||||
@ -46,7 +46,6 @@ pub(crate) fn ensure_leading_slash(mut patterns: Patterns) -> Patterns {
|
|||||||
|
|
||||||
patterns
|
patterns
|
||||||
}
|
}
|
||||||
struct Enc(ContentEncoding);
|
|
||||||
|
|
||||||
/// Helper trait that allows to set specific encoding for response.
|
/// Helper trait that allows to set specific encoding for response.
|
||||||
pub trait BodyEncoding {
|
pub trait BodyEncoding {
|
||||||
@ -70,6 +69,8 @@ impl BodyEncoding for actix_http::ResponseBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Enc(ContentEncoding);
|
||||||
|
|
||||||
impl<B> BodyEncoding for actix_http::Response<B> {
|
impl<B> BodyEncoding for actix_http::Response<B> {
|
||||||
fn get_encoding(&self) -> Option<ContentEncoding> {
|
fn get_encoding(&self) -> Option<ContentEncoding> {
|
||||||
self.extensions().get::<Enc>().map(|enc| enc.0)
|
self.extensions().get::<Enc>().map(|enc| enc.0)
|
||||||
|
762
src/guard.rs
762
src/guard.rs
@ -1,160 +1,232 @@
|
|||||||
//! Route match guards.
|
//! Route guards.
|
||||||
//!
|
//!
|
||||||
//! Guards are one of the ways how actix-web router chooses a
|
//! Guards are used during routing to help select a matching service or handler using some aspect of
|
||||||
//! handler service. In essence it is just a function that accepts a
|
//! the request; though guards should not be used for path matching since it is a built-in function
|
||||||
//! reference to a `RequestHead` instance and returns a boolean.
|
//! of the Actix Web router.
|
||||||
//! It is possible to add guards to *scopes*, *resources*
|
|
||||||
//! and *routes*. Actix provide several guards by default, like various
|
|
||||||
//! http methods, header, etc. To become a guard, type must implement `Guard`
|
|
||||||
//! trait. Simple functions could be guards as well.
|
|
||||||
//!
|
//!
|
||||||
//! Guards can not modify the request object. But it is possible
|
//! Guards can be used on [`Scope`]s, [`Resource`]s, [`Route`]s, and other custom services.
|
||||||
//! to store extra attributes on a request by using the `Extensions` container.
|
|
||||||
//! Extensions containers are available via the `RequestHead::extensions()` method.
|
|
||||||
//!
|
//!
|
||||||
|
//! Fundamentally, a guard is a predicate function that receives a reference to a request context
|
||||||
|
//! object and returns a boolean; true if the request _should_ be handled by the guarded service
|
||||||
|
//! or handler. This interface is defined by the [`Guard`] trait.
|
||||||
|
//!
|
||||||
|
//! Commonly-used guards are provided in this module as well as a way of creating a guard from a
|
||||||
|
//! closure ([`fn_guard`]). The [`Not`], [`Any`], and [`All`] guards are noteworthy, as they can be
|
||||||
|
//! used to compose other guards in a more flexible and semantic way than calling `.guard(...)` on
|
||||||
|
//! services multiple times (which might have different combining behavior than you want).
|
||||||
|
//!
|
||||||
|
//! There are shortcuts for routes with method guards in the [`web`](crate::web) module:
|
||||||
|
//! [`web::get()`](crate::web::get), [`web::post()`](crate::web::post), etc. The routes created by
|
||||||
|
//! the following calls are equivalent:
|
||||||
|
//! - `web::get()` (recommended form)
|
||||||
|
//! - `web::route().guard(guard::Get())`
|
||||||
|
//!
|
||||||
|
//! Guards can not modify anything about the request. However, it is possible to store extra
|
||||||
|
//! attributes in the request-local data container obtained with [`GuardContext::req_data_mut`].
|
||||||
|
//!
|
||||||
|
//! Guards can prevent resource definitions from overlapping which, when only considering paths,
|
||||||
|
//! would result in inaccessible routes. See the [`Host`] guard for an example of virtual hosting.
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//! In the following code, the `/guarded` resource has one defined route whose handler will only be
|
||||||
|
//! called if the request method is `POST` and there is a request header with name and value equal
|
||||||
|
//! to `x-guarded` and `secret`, respectively.
|
||||||
//! ```
|
//! ```
|
||||||
//! use actix_web::{web, http, dev, guard, App, HttpResponse};
|
//! use actix_web::{web, http::Method, guard, HttpResponse};
|
||||||
//!
|
//!
|
||||||
//! App::new().service(web::resource("/index.html").route(
|
//! web::resource("/guarded").route(
|
||||||
//! web::route()
|
//! web::route()
|
||||||
//! .guard(guard::Post())
|
//! .guard(guard::Any(guard::Get()).or(guard::Post()))
|
||||||
//! .guard(guard::fn_guard(|head| head.method == http::Method::GET))
|
//! .guard(guard::Header("x-guarded", "secret"))
|
||||||
//! .to(|| HttpResponse::MethodNotAllowed()))
|
//! .to(|| HttpResponse::Ok())
|
||||||
//! );
|
//! );
|
||||||
//! ```
|
//! ```
|
||||||
|
//!
|
||||||
|
//! [`Scope`]: crate::Scope::guard()
|
||||||
|
//! [`Resource`]: crate::Resource::guard()
|
||||||
|
//! [`Route`]: crate::Route::guard()
|
||||||
|
|
||||||
#![allow(non_snake_case)]
|
use std::{
|
||||||
|
cell::{Ref, RefMut},
|
||||||
|
convert::TryFrom,
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
use std::rc::Rc;
|
use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead};
|
||||||
use std::{convert::TryFrom, ops::Deref};
|
|
||||||
|
|
||||||
use actix_http::{header, uri::Uri, Method as HttpMethod, RequestHead};
|
use crate::service::ServiceRequest;
|
||||||
|
|
||||||
/// Trait defines resource guards. Guards are used for route selection.
|
/// Provides access to request parts that are useful during routing.
|
||||||
///
|
#[derive(Debug)]
|
||||||
/// Guards can not modify the request object. But it is possible
|
pub struct GuardContext<'a> {
|
||||||
/// to store extra attributes on a request by using the `Extensions` container.
|
pub(crate) req: &'a ServiceRequest,
|
||||||
/// Extensions containers are available via the `RequestHead::extensions()` method.
|
|
||||||
pub trait Guard {
|
|
||||||
/// Check if request matches predicate
|
|
||||||
fn check(&self, request: &RequestHead) -> bool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Guard for Rc<dyn Guard> {
|
impl<'a> GuardContext<'a> {
|
||||||
fn check(&self, request: &RequestHead) -> bool {
|
/// Returns reference to the request head.
|
||||||
self.deref().check(request)
|
#[inline]
|
||||||
|
pub fn head(&self) -> &RequestHead {
|
||||||
|
self.req.head()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns reference to the request-local data container.
|
||||||
|
#[inline]
|
||||||
|
pub fn req_data(&self) -> Ref<'a, Extensions> {
|
||||||
|
self.req.req_data()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns mutable reference to the request-local data container.
|
||||||
|
#[inline]
|
||||||
|
pub fn req_data_mut(&self) -> RefMut<'a, Extensions> {
|
||||||
|
self.req.req_data_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create guard object for supplied function.
|
/// Interface for routing guards.
|
||||||
///
|
///
|
||||||
|
/// See [module level documentation](self) for more.
|
||||||
|
pub trait Guard {
|
||||||
|
/// Returns true if predicate condition is met for a given request.
|
||||||
|
fn check(&self, ctx: &GuardContext<'_>) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Guard for Rc<dyn Guard> {
|
||||||
|
fn check(&self, ctx: &GuardContext<'_>) -> bool {
|
||||||
|
(**self).check(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a guard using the given function.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{guard, web, App, HttpResponse};
|
/// use actix_web::{guard, web, HttpResponse};
|
||||||
///
|
///
|
||||||
/// App::new().service(web::resource("/index.html").route(
|
/// web::route()
|
||||||
/// web::route()
|
/// .guard(guard::fn_guard(|ctx| {
|
||||||
/// .guard(
|
/// ctx.head().headers().contains_key("content-type")
|
||||||
/// guard::fn_guard(
|
/// }))
|
||||||
/// |req| req.headers()
|
/// .to(|| HttpResponse::Ok());
|
||||||
/// .contains_key("content-type")))
|
|
||||||
/// .to(|| HttpResponse::MethodNotAllowed()))
|
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn fn_guard<F>(f: F) -> impl Guard
|
pub fn fn_guard<F>(f: F) -> impl Guard
|
||||||
where
|
where
|
||||||
F: Fn(&RequestHead) -> bool,
|
F: Fn(&GuardContext<'_>) -> bool,
|
||||||
{
|
{
|
||||||
FnGuard(f)
|
FnGuard(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FnGuard<F: Fn(&RequestHead) -> bool>(F);
|
struct FnGuard<F: Fn(&GuardContext<'_>) -> bool>(F);
|
||||||
|
|
||||||
impl<F> Guard for FnGuard<F>
|
impl<F> Guard for FnGuard<F>
|
||||||
where
|
where
|
||||||
F: Fn(&RequestHead) -> bool,
|
F: Fn(&GuardContext<'_>) -> bool,
|
||||||
{
|
{
|
||||||
fn check(&self, head: &RequestHead) -> bool {
|
fn check(&self, ctx: &GuardContext<'_>) -> bool {
|
||||||
(self.0)(head)
|
(self.0)(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F> Guard for F
|
impl<F> Guard for F
|
||||||
where
|
where
|
||||||
F: Fn(&RequestHead) -> bool,
|
F: Fn(&GuardContext<'_>) -> bool,
|
||||||
{
|
{
|
||||||
fn check(&self, head: &RequestHead) -> bool {
|
fn check(&self, ctx: &GuardContext<'_>) -> bool {
|
||||||
(self)(head)
|
(self)(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return guard that matches if any of supplied guards.
|
/// Creates a guard that matches if any added guards match.
|
||||||
///
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// The handler below will be called for either request method `GET` or `POST`.
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{web, guard, App, HttpResponse};
|
/// use actix_web::{web, guard, HttpResponse};
|
||||||
///
|
///
|
||||||
/// App::new().service(web::resource("/index.html").route(
|
/// web::route()
|
||||||
/// web::route()
|
/// .guard(
|
||||||
/// .guard(guard::Any(guard::Get()).or(guard::Post()))
|
/// guard::Any(guard::Get())
|
||||||
/// .to(|| HttpResponse::MethodNotAllowed()))
|
/// .or(guard::Post()))
|
||||||
/// );
|
/// .to(|| HttpResponse::Ok());
|
||||||
/// ```
|
/// ```
|
||||||
|
#[allow(non_snake_case)]
|
||||||
pub fn Any<F: Guard + 'static>(guard: F) -> AnyGuard {
|
pub fn Any<F: Guard + 'static>(guard: F) -> AnyGuard {
|
||||||
AnyGuard(vec![Box::new(guard)])
|
AnyGuard {
|
||||||
|
guards: vec![Box::new(guard)],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Matches any of supplied guards.
|
/// A collection of guards that match if the disjunction of their `check` outcomes is true.
|
||||||
pub struct AnyGuard(Vec<Box<dyn Guard>>);
|
///
|
||||||
|
/// That is, only one contained guard needs to match in order for the aggregate guard to match.
|
||||||
|
///
|
||||||
|
/// Construct an `AnyGuard` using [`Any`].
|
||||||
|
pub struct AnyGuard {
|
||||||
|
guards: Vec<Box<dyn Guard>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl AnyGuard {
|
impl AnyGuard {
|
||||||
/// Add guard to a list of guards to check
|
/// Adds new guard to the collection of guards to check.
|
||||||
pub fn or<F: Guard + 'static>(mut self, guard: F) -> Self {
|
pub fn or<F: Guard + 'static>(mut self, guard: F) -> Self {
|
||||||
self.0.push(Box::new(guard));
|
self.guards.push(Box::new(guard));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Guard for AnyGuard {
|
impl Guard for AnyGuard {
|
||||||
fn check(&self, req: &RequestHead) -> bool {
|
fn check(&self, ctx: &GuardContext<'_>) -> bool {
|
||||||
for p in &self.0 {
|
for guard in &self.guards {
|
||||||
if p.check(req) {
|
if guard.check(ctx) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return guard that matches if all of the supplied guards.
|
/// Creates a guard that matches if all added guards match.
|
||||||
///
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// The handler below will only be called if the request method is `GET` **and** the specified
|
||||||
|
/// header name and value match exactly.
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{guard, web, App, HttpResponse};
|
/// use actix_web::{guard, web, HttpResponse};
|
||||||
///
|
///
|
||||||
/// App::new().service(web::resource("/index.html").route(
|
/// web::route()
|
||||||
/// web::route()
|
/// .guard(
|
||||||
/// .guard(
|
/// guard::All(guard::Get())
|
||||||
/// guard::All(guard::Get()).and(guard::Header("content-type", "text/plain")))
|
/// .and(guard::Header("accept", "text/plain"))
|
||||||
/// .to(|| HttpResponse::MethodNotAllowed()))
|
/// )
|
||||||
/// );
|
/// .to(|| HttpResponse::Ok());
|
||||||
/// ```
|
/// ```
|
||||||
|
#[allow(non_snake_case)]
|
||||||
pub fn All<F: Guard + 'static>(guard: F) -> AllGuard {
|
pub fn All<F: Guard + 'static>(guard: F) -> AllGuard {
|
||||||
AllGuard(vec![Box::new(guard)])
|
AllGuard {
|
||||||
|
guards: vec![Box::new(guard)],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Matches if all of supplied guards.
|
/// A collection of guards that match if the conjunction of their `check` outcomes is true.
|
||||||
pub struct AllGuard(Vec<Box<dyn Guard>>);
|
///
|
||||||
|
/// That is, **all** contained guard needs to match in order for the aggregate guard to match.
|
||||||
|
///
|
||||||
|
/// Construct an `AllGuard` using [`All`].
|
||||||
|
pub struct AllGuard {
|
||||||
|
guards: Vec<Box<dyn Guard>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl AllGuard {
|
impl AllGuard {
|
||||||
/// Add new guard to the list of guards to check
|
/// Adds new guard to the collection of guards to check.
|
||||||
pub fn and<F: Guard + 'static>(mut self, guard: F) -> Self {
|
pub fn and<F: Guard + 'static>(mut self, guard: F) -> Self {
|
||||||
self.0.push(Box::new(guard));
|
self.guards.push(Box::new(guard));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Guard for AllGuard {
|
impl Guard for AllGuard {
|
||||||
fn check(&self, request: &RequestHead) -> bool {
|
fn check(&self, ctx: &GuardContext<'_>) -> bool {
|
||||||
for p in &self.0 {
|
for guard in &self.guards {
|
||||||
if !p.check(request) {
|
if !guard.check(ctx) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,159 +234,212 @@ impl Guard for AllGuard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return guard that matches if supplied guard does not match.
|
/// Wraps a guard and inverts the outcome of it's `Guard` implementation.
|
||||||
pub fn Not<F: Guard + 'static>(guard: F) -> NotGuard {
|
///
|
||||||
NotGuard(Box::new(guard))
|
/// # Examples
|
||||||
}
|
/// The handler below will be called for any request method apart from `GET`.
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{guard, web, HttpResponse};
|
||||||
|
///
|
||||||
|
/// web::route()
|
||||||
|
/// .guard(guard::Not(guard::Get()))
|
||||||
|
/// .to(|| HttpResponse::Ok());
|
||||||
|
/// ```
|
||||||
|
pub struct Not<G>(pub G);
|
||||||
|
|
||||||
#[doc(hidden)]
|
impl<G: Guard> Guard for Not<G> {
|
||||||
pub struct NotGuard(Box<dyn Guard>);
|
fn check(&self, ctx: &GuardContext<'_>) -> bool {
|
||||||
|
!self.0.check(ctx)
|
||||||
impl Guard for NotGuard {
|
|
||||||
fn check(&self, request: &RequestHead) -> bool {
|
|
||||||
!self.0.check(request)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// HTTP method guard.
|
/// Creates a guard that matches a specified HTTP method.
|
||||||
#[doc(hidden)]
|
#[allow(non_snake_case)]
|
||||||
pub struct MethodGuard(HttpMethod);
|
pub fn Method(method: HttpMethod) -> impl Guard {
|
||||||
|
|
||||||
impl Guard for MethodGuard {
|
|
||||||
fn check(&self, request: &RequestHead) -> bool {
|
|
||||||
request.method == self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Guard to match *GET* HTTP method.
|
|
||||||
pub fn Get() -> MethodGuard {
|
|
||||||
MethodGuard(HttpMethod::GET)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Predicate to match *POST* HTTP method.
|
|
||||||
pub fn Post() -> MethodGuard {
|
|
||||||
MethodGuard(HttpMethod::POST)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Predicate to match *PUT* HTTP method.
|
|
||||||
pub fn Put() -> MethodGuard {
|
|
||||||
MethodGuard(HttpMethod::PUT)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Predicate to match *DELETE* HTTP method.
|
|
||||||
pub fn Delete() -> MethodGuard {
|
|
||||||
MethodGuard(HttpMethod::DELETE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Predicate to match *HEAD* HTTP method.
|
|
||||||
pub fn Head() -> MethodGuard {
|
|
||||||
MethodGuard(HttpMethod::HEAD)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Predicate to match *OPTIONS* HTTP method.
|
|
||||||
pub fn Options() -> MethodGuard {
|
|
||||||
MethodGuard(HttpMethod::OPTIONS)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Predicate to match *CONNECT* HTTP method.
|
|
||||||
pub fn Connect() -> MethodGuard {
|
|
||||||
MethodGuard(HttpMethod::CONNECT)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Predicate to match *PATCH* HTTP method.
|
|
||||||
pub fn Patch() -> MethodGuard {
|
|
||||||
MethodGuard(HttpMethod::PATCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Predicate to match *TRACE* HTTP method.
|
|
||||||
pub fn Trace() -> MethodGuard {
|
|
||||||
MethodGuard(HttpMethod::TRACE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Predicate to match specified HTTP method.
|
|
||||||
pub fn Method(method: HttpMethod) -> MethodGuard {
|
|
||||||
MethodGuard(method)
|
MethodGuard(method)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return predicate that matches if request contains specified header and
|
/// HTTP method guard.
|
||||||
/// value.
|
struct MethodGuard(HttpMethod);
|
||||||
pub fn Header(name: &'static str, value: &'static str) -> HeaderGuard {
|
|
||||||
|
impl Guard for MethodGuard {
|
||||||
|
fn check(&self, ctx: &GuardContext<'_>) -> bool {
|
||||||
|
ctx.head().method == self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! method_guard {
|
||||||
|
($method_fn:ident, $method_const:ident) => {
|
||||||
|
paste::paste! {
|
||||||
|
#[doc = " Creates a guard that matches the `" $method_const "` request method."]
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
#[doc = " The route in this example will only respond to `" $method_const "` requests."]
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{guard, web, HttpResponse};
|
||||||
|
///
|
||||||
|
/// web::route()
|
||||||
|
#[doc = " .guard(guard::" $method_fn "())"]
|
||||||
|
/// .to(|| HttpResponse::Ok());
|
||||||
|
/// ```
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn $method_fn() -> impl Guard {
|
||||||
|
MethodGuard(HttpMethod::$method_const)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
method_guard!(Get, GET);
|
||||||
|
method_guard!(Post, POST);
|
||||||
|
method_guard!(Put, PUT);
|
||||||
|
method_guard!(Delete, DELETE);
|
||||||
|
method_guard!(Head, HEAD);
|
||||||
|
method_guard!(Options, OPTIONS);
|
||||||
|
method_guard!(Connect, CONNECT);
|
||||||
|
method_guard!(Patch, PATCH);
|
||||||
|
method_guard!(Trace, TRACE);
|
||||||
|
|
||||||
|
/// Creates a guard that matches if request contains given header name and value.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// The handler below will be called when the request contains an `x-guarded` header with value
|
||||||
|
/// equal to `secret`.
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{guard, web, HttpResponse};
|
||||||
|
///
|
||||||
|
/// web::route()
|
||||||
|
/// .guard(guard::Header("x-guarded", "secret"))
|
||||||
|
/// .to(|| HttpResponse::Ok());
|
||||||
|
/// ```
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn Header(name: &'static str, value: &'static str) -> impl Guard {
|
||||||
HeaderGuard(
|
HeaderGuard(
|
||||||
header::HeaderName::try_from(name).unwrap(),
|
header::HeaderName::try_from(name).unwrap(),
|
||||||
header::HeaderValue::from_static(value),
|
header::HeaderValue::from_static(value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
struct HeaderGuard(header::HeaderName, header::HeaderValue);
|
||||||
pub struct HeaderGuard(header::HeaderName, header::HeaderValue);
|
|
||||||
|
|
||||||
impl Guard for HeaderGuard {
|
impl Guard for HeaderGuard {
|
||||||
fn check(&self, req: &RequestHead) -> bool {
|
fn check(&self, ctx: &GuardContext<'_>) -> bool {
|
||||||
if let Some(val) = req.headers.get(&self.0) {
|
if let Some(val) = ctx.head().headers.get(&self.0) {
|
||||||
return val == self.1;
|
return val == self.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return predicate that matches if request contains specified Host name.
|
/// Creates a guard that matches requests targetting a specific host.
|
||||||
///
|
///
|
||||||
/// ```
|
/// # Matching Host
|
||||||
/// use actix_web::{web, guard::Host, App, HttpResponse};
|
/// This guard will:
|
||||||
|
/// - match against the `Host` header, if present;
|
||||||
|
/// - fall-back to matching against the request target's host, if present;
|
||||||
|
/// - return false if host cannot be determined;
|
||||||
///
|
///
|
||||||
/// App::new().service(
|
/// # Matching Scheme
|
||||||
/// web::resource("/index.html")
|
/// Optionally, this guard can match against the host's scheme. Set the scheme for matching using
|
||||||
/// .guard(Host("www.rust-lang.org"))
|
/// `Host(host).scheme(protocol)`. If the request's scheme cannot be determined, it will not prevent
|
||||||
/// .to(|| HttpResponse::MethodNotAllowed())
|
/// the guard from matching successfully.
|
||||||
/// );
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// The [module-level documentation](self) has an example of virtual hosting using `Host` guards.
|
||||||
|
///
|
||||||
|
/// The example below additionally guards on the host URI's scheme. This could allow routing to
|
||||||
|
/// different handlers for `http:` vs `https:` visitors; to redirect, for example.
|
||||||
/// ```
|
/// ```
|
||||||
pub fn Host<H: AsRef<str>>(host: H) -> HostGuard {
|
/// use actix_web::{web, guard::Host, HttpResponse};
|
||||||
HostGuard(host.as_ref().to_string(), None)
|
///
|
||||||
|
/// web::scope("/admin")
|
||||||
|
/// .guard(Host("admin.rust-lang.org").scheme("https"))
|
||||||
|
/// .default_service(web::to(|| HttpResponse::Ok().body("admin connection is secure")));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The `Host` guard can be used to set up some form of [virtual hosting] within a single app.
|
||||||
|
/// Overlapping scope prefixes are usually discouraged, but when combined with non-overlapping guard
|
||||||
|
/// definitions they become safe to use in this way. Without these host guards, only routes under
|
||||||
|
/// the first-to-be-defined scope would be accessible. You can test this locally using `127.0.0.1`
|
||||||
|
/// and `localhost` as the `Host` guards.
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{web, http::Method, guard, App, HttpResponse};
|
||||||
|
///
|
||||||
|
/// App::new()
|
||||||
|
/// .service(
|
||||||
|
/// web::scope("")
|
||||||
|
/// .guard(guard::Host("www.rust-lang.org"))
|
||||||
|
/// .default_service(web::to(|| HttpResponse::Ok().body("marketing site"))),
|
||||||
|
/// )
|
||||||
|
/// .service(
|
||||||
|
/// web::scope("")
|
||||||
|
/// .guard(guard::Host("play.rust-lang.org"))
|
||||||
|
/// .default_service(web::to(|| HttpResponse::Ok().body("playground frontend"))),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [virtual hosting]: https://en.wikipedia.org/wiki/Virtual_hosting
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn Host(host: impl AsRef<str>) -> HostGuard {
|
||||||
|
HostGuard {
|
||||||
|
host: host.as_ref().to_string(),
|
||||||
|
scheme: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_host_uri(req: &RequestHead) -> Option<Uri> {
|
fn get_host_uri(req: &RequestHead) -> Option<Uri> {
|
||||||
use core::str::FromStr;
|
|
||||||
req.headers
|
req.headers
|
||||||
.get(header::HOST)
|
.get(header::HOST)
|
||||||
.and_then(|host_value| host_value.to_str().ok())
|
.and_then(|host_value| host_value.to_str().ok())
|
||||||
.or_else(|| req.uri.host())
|
.or_else(|| req.uri.host())
|
||||||
.map(|host: &str| Uri::from_str(host).ok())
|
.and_then(|host| host.parse().ok())
|
||||||
.and_then(|host_success| host_success)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct HostGuard(String, Option<String>);
|
pub struct HostGuard {
|
||||||
|
host: String,
|
||||||
|
scheme: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
impl HostGuard {
|
impl HostGuard {
|
||||||
/// Set request scheme to match
|
/// Set request scheme to match
|
||||||
pub fn scheme<H: AsRef<str>>(mut self, scheme: H) -> HostGuard {
|
pub fn scheme<H: AsRef<str>>(mut self, scheme: H) -> HostGuard {
|
||||||
self.1 = Some(scheme.as_ref().to_string());
|
self.scheme = Some(scheme.as_ref().to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Guard for HostGuard {
|
impl Guard for HostGuard {
|
||||||
fn check(&self, req: &RequestHead) -> bool {
|
fn check(&self, ctx: &GuardContext<'_>) -> bool {
|
||||||
let req_host_uri = if let Some(uri) = get_host_uri(req) {
|
// parse host URI from header or request target
|
||||||
uri
|
let req_host_uri = match get_host_uri(ctx.head()) {
|
||||||
} else {
|
Some(uri) => uri,
|
||||||
return false;
|
|
||||||
|
// no match if host cannot be determined
|
||||||
|
None => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(uri_host) = req_host_uri.host() {
|
match req_host_uri.host() {
|
||||||
if self.0 != uri_host {
|
// fall through to scheme checks
|
||||||
return false;
|
Some(uri_host) if self.host == uri_host => {}
|
||||||
}
|
|
||||||
} else {
|
// Either:
|
||||||
return false;
|
// - request's host does not match guard's host;
|
||||||
|
// - It was possible that the parsed URI from request target did not contain a host.
|
||||||
|
_ => return false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref scheme) = self.1 {
|
if let Some(ref scheme) = self.scheme {
|
||||||
if let Some(ref req_host_uri_scheme) = req_host_uri.scheme_str() {
|
if let Some(ref req_host_uri_scheme) = req_host_uri.scheme_str() {
|
||||||
return scheme == req_host_uri_scheme;
|
return scheme == req_host_uri_scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: is the the correct behavior?
|
||||||
|
// falls through if scheme cannot be determined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// all conditions passed
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -327,171 +452,214 @@ mod tests {
|
|||||||
use crate::test::TestRequest;
|
use crate::test::TestRequest;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_header() {
|
fn header_match() {
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
.to_http_request();
|
.to_srv_request();
|
||||||
|
|
||||||
let pred = Header("transfer-encoding", "chunked");
|
let hdr = Header("transfer-encoding", "chunked");
|
||||||
assert!(pred.check(req.head()));
|
assert!(hdr.check(&req.guard_ctx()));
|
||||||
|
|
||||||
let pred = Header("transfer-encoding", "other");
|
let hdr = Header("transfer-encoding", "other");
|
||||||
assert!(!pred.check(req.head()));
|
assert!(!hdr.check(&req.guard_ctx()));
|
||||||
|
|
||||||
let pred = Header("content-type", "other");
|
let hdr = Header("content-type", "chunked");
|
||||||
assert!(!pred.check(req.head()));
|
assert!(!hdr.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let hdr = Header("content-type", "other");
|
||||||
|
assert!(!hdr.check(&req.guard_ctx()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_host() {
|
fn host_from_header() {
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.insert_header((
|
.insert_header((
|
||||||
header::HOST,
|
header::HOST,
|
||||||
header::HeaderValue::from_static("www.rust-lang.org"),
|
header::HeaderValue::from_static("www.rust-lang.org"),
|
||||||
))
|
))
|
||||||
.to_http_request();
|
.to_srv_request();
|
||||||
|
|
||||||
let pred = Host("www.rust-lang.org");
|
let host = Host("www.rust-lang.org");
|
||||||
assert!(pred.check(req.head()));
|
assert!(host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
let pred = Host("www.rust-lang.org").scheme("https");
|
let host = Host("www.rust-lang.org").scheme("https");
|
||||||
assert!(pred.check(req.head()));
|
assert!(host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
let pred = Host("blog.rust-lang.org");
|
let host = Host("blog.rust-lang.org");
|
||||||
assert!(!pred.check(req.head()));
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
let pred = Host("blog.rust-lang.org").scheme("https");
|
let host = Host("blog.rust-lang.org").scheme("https");
|
||||||
assert!(!pred.check(req.head()));
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
let pred = Host("crates.io");
|
let host = Host("crates.io");
|
||||||
assert!(!pred.check(req.head()));
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
let pred = Host("localhost");
|
let host = Host("localhost");
|
||||||
assert!(!pred.check(req.head()));
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_host_scheme() {
|
fn host_without_header() {
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.uri("www.rust-lang.org")
|
||||||
|
.to_srv_request();
|
||||||
|
|
||||||
|
let host = Host("www.rust-lang.org");
|
||||||
|
assert!(host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("www.rust-lang.org").scheme("https");
|
||||||
|
assert!(host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("blog.rust-lang.org");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("blog.rust-lang.org").scheme("https");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("crates.io");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("localhost");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn host_scheme() {
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.insert_header((
|
.insert_header((
|
||||||
header::HOST,
|
header::HOST,
|
||||||
header::HeaderValue::from_static("https://www.rust-lang.org"),
|
header::HeaderValue::from_static("https://www.rust-lang.org"),
|
||||||
))
|
))
|
||||||
.to_http_request();
|
.to_srv_request();
|
||||||
|
|
||||||
let pred = Host("www.rust-lang.org").scheme("https");
|
let host = Host("www.rust-lang.org").scheme("https");
|
||||||
assert!(pred.check(req.head()));
|
assert!(host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
let pred = Host("www.rust-lang.org");
|
let host = Host("www.rust-lang.org");
|
||||||
assert!(pred.check(req.head()));
|
assert!(host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
let pred = Host("www.rust-lang.org").scheme("http");
|
let host = Host("www.rust-lang.org").scheme("http");
|
||||||
assert!(!pred.check(req.head()));
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
let pred = Host("blog.rust-lang.org");
|
let host = Host("blog.rust-lang.org");
|
||||||
assert!(!pred.check(req.head()));
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
let pred = Host("blog.rust-lang.org").scheme("https");
|
let host = Host("blog.rust-lang.org").scheme("https");
|
||||||
assert!(!pred.check(req.head()));
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
let pred = Host("crates.io").scheme("https");
|
let host = Host("crates.io").scheme("https");
|
||||||
assert!(!pred.check(req.head()));
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
let pred = Host("localhost");
|
let host = Host("localhost");
|
||||||
assert!(!pred.check(req.head()));
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_host_without_header() {
|
fn method_guards() {
|
||||||
|
let get_req = TestRequest::get().to_srv_request();
|
||||||
|
let post_req = TestRequest::post().to_srv_request();
|
||||||
|
|
||||||
|
assert!(Get().check(&get_req.guard_ctx()));
|
||||||
|
assert!(!Get().check(&post_req.guard_ctx()));
|
||||||
|
|
||||||
|
assert!(Post().check(&post_req.guard_ctx()));
|
||||||
|
assert!(!Post().check(&get_req.guard_ctx()));
|
||||||
|
|
||||||
|
let req = TestRequest::put().to_srv_request();
|
||||||
|
assert!(Put().check(&req.guard_ctx()));
|
||||||
|
assert!(!Put().check(&get_req.guard_ctx()));
|
||||||
|
|
||||||
|
let req = TestRequest::patch().to_srv_request();
|
||||||
|
assert!(Patch().check(&req.guard_ctx()));
|
||||||
|
assert!(!Patch().check(&get_req.guard_ctx()));
|
||||||
|
|
||||||
|
let r = TestRequest::delete().to_srv_request();
|
||||||
|
assert!(Delete().check(&r.guard_ctx()));
|
||||||
|
assert!(!Delete().check(&get_req.guard_ctx()));
|
||||||
|
|
||||||
|
let req = TestRequest::default().method(Method::HEAD).to_srv_request();
|
||||||
|
assert!(Head().check(&req.guard_ctx()));
|
||||||
|
assert!(!Head().check(&get_req.guard_ctx()));
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.uri("www.rust-lang.org")
|
|
||||||
.to_http_request();
|
|
||||||
|
|
||||||
let pred = Host("www.rust-lang.org");
|
|
||||||
assert!(pred.check(req.head()));
|
|
||||||
|
|
||||||
let pred = Host("www.rust-lang.org").scheme("https");
|
|
||||||
assert!(pred.check(req.head()));
|
|
||||||
|
|
||||||
let pred = Host("blog.rust-lang.org");
|
|
||||||
assert!(!pred.check(req.head()));
|
|
||||||
|
|
||||||
let pred = Host("blog.rust-lang.org").scheme("https");
|
|
||||||
assert!(!pred.check(req.head()));
|
|
||||||
|
|
||||||
let pred = Host("crates.io");
|
|
||||||
assert!(!pred.check(req.head()));
|
|
||||||
|
|
||||||
let pred = Host("localhost");
|
|
||||||
assert!(!pred.check(req.head()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_methods() {
|
|
||||||
let req = TestRequest::default().to_http_request();
|
|
||||||
let req2 = TestRequest::default()
|
|
||||||
.method(Method::POST)
|
|
||||||
.to_http_request();
|
|
||||||
|
|
||||||
assert!(Get().check(req.head()));
|
|
||||||
assert!(!Get().check(req2.head()));
|
|
||||||
assert!(Post().check(req2.head()));
|
|
||||||
assert!(!Post().check(req.head()));
|
|
||||||
|
|
||||||
let r = TestRequest::default().method(Method::PUT).to_http_request();
|
|
||||||
assert!(Put().check(r.head()));
|
|
||||||
assert!(!Put().check(req.head()));
|
|
||||||
|
|
||||||
let r = TestRequest::default()
|
|
||||||
.method(Method::DELETE)
|
|
||||||
.to_http_request();
|
|
||||||
assert!(Delete().check(r.head()));
|
|
||||||
assert!(!Delete().check(req.head()));
|
|
||||||
|
|
||||||
let r = TestRequest::default()
|
|
||||||
.method(Method::HEAD)
|
|
||||||
.to_http_request();
|
|
||||||
assert!(Head().check(r.head()));
|
|
||||||
assert!(!Head().check(req.head()));
|
|
||||||
|
|
||||||
let r = TestRequest::default()
|
|
||||||
.method(Method::OPTIONS)
|
.method(Method::OPTIONS)
|
||||||
.to_http_request();
|
.to_srv_request();
|
||||||
assert!(Options().check(r.head()));
|
assert!(Options().check(&req.guard_ctx()));
|
||||||
assert!(!Options().check(req.head()));
|
assert!(!Options().check(&get_req.guard_ctx()));
|
||||||
|
|
||||||
let r = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.method(Method::CONNECT)
|
.method(Method::CONNECT)
|
||||||
.to_http_request();
|
.to_srv_request();
|
||||||
assert!(Connect().check(r.head()));
|
assert!(Connect().check(&req.guard_ctx()));
|
||||||
assert!(!Connect().check(req.head()));
|
assert!(!Connect().check(&get_req.guard_ctx()));
|
||||||
|
|
||||||
let r = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.method(Method::PATCH)
|
|
||||||
.to_http_request();
|
|
||||||
assert!(Patch().check(r.head()));
|
|
||||||
assert!(!Patch().check(req.head()));
|
|
||||||
|
|
||||||
let r = TestRequest::default()
|
|
||||||
.method(Method::TRACE)
|
.method(Method::TRACE)
|
||||||
.to_http_request();
|
.to_srv_request();
|
||||||
assert!(Trace().check(r.head()));
|
assert!(Trace().check(&req.guard_ctx()));
|
||||||
assert!(!Trace().check(req.head()));
|
assert!(!Trace().check(&get_req.guard_ctx()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_preds() {
|
fn aggregate_any() {
|
||||||
let r = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.method(Method::TRACE)
|
.method(Method::TRACE)
|
||||||
.to_http_request();
|
.to_srv_request();
|
||||||
|
|
||||||
assert!(Not(Get()).check(r.head()));
|
assert!(Any(Trace()).check(&req.guard_ctx()));
|
||||||
assert!(!Not(Trace()).check(r.head()));
|
assert!(Any(Trace()).or(Get()).check(&req.guard_ctx()));
|
||||||
|
assert!(!Any(Get()).or(Get()).check(&req.guard_ctx()));
|
||||||
|
}
|
||||||
|
|
||||||
assert!(All(Trace()).and(Trace()).check(r.head()));
|
#[test]
|
||||||
assert!(!All(Get()).and(Trace()).check(r.head()));
|
fn aggregate_all() {
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.method(Method::TRACE)
|
||||||
|
.to_srv_request();
|
||||||
|
|
||||||
assert!(Any(Get()).or(Trace()).check(r.head()));
|
assert!(All(Trace()).check(&req.guard_ctx()));
|
||||||
assert!(!Any(Get()).or(Get()).check(r.head()));
|
assert!(All(Trace()).and(Trace()).check(&req.guard_ctx()));
|
||||||
|
assert!(!All(Trace()).and(Get()).check(&req.guard_ctx()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_not() {
|
||||||
|
let req = TestRequest::default().to_srv_request();
|
||||||
|
|
||||||
|
let get = Get();
|
||||||
|
assert!(get.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let not_get = Not(get);
|
||||||
|
assert!(!not_get.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let not_not_get = Not(not_get);
|
||||||
|
assert!(not_not_get.check(&req.guard_ctx()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn function_guard() {
|
||||||
|
let domain = "rust-lang.org".to_owned();
|
||||||
|
let guard = fn_guard(|ctx| ctx.head().uri.host().unwrap().ends_with(&domain));
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.uri("blog.rust-lang.org")
|
||||||
|
.to_srv_request();
|
||||||
|
assert!(guard.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let req = TestRequest::default().uri("crates.io").to_srv_request();
|
||||||
|
assert!(!guard.check(&req.guard_ctx()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mega_nesting() {
|
||||||
|
let guard = fn_guard(|ctx| All(Not(Any(Not(Trace())))).check(ctx));
|
||||||
|
|
||||||
|
let req = TestRequest::default().to_srv_request();
|
||||||
|
assert!(!guard.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.method(Method::TRACE)
|
||||||
|
.to_srv_request();
|
||||||
|
assert!(guard.check(&req.guard_ctx()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
125
src/info.rs
125
src/info.rs
@ -67,7 +67,7 @@ fn first_header_value<'a>(req: &'a RequestHead, name: &'_ HeaderName) -> Option<
|
|||||||
pub struct ConnectionInfo {
|
pub struct ConnectionInfo {
|
||||||
host: String,
|
host: String,
|
||||||
scheme: String,
|
scheme: String,
|
||||||
remote_addr: Option<String>,
|
peer_addr: Option<String>,
|
||||||
realip_remote_addr: Option<String>,
|
realip_remote_addr: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,67 +134,70 @@ impl ConnectionInfo {
|
|||||||
.or_else(|| first_header_value(req, &*X_FORWARDED_FOR))
|
.or_else(|| first_header_value(req, &*X_FORWARDED_FOR))
|
||||||
.map(str::to_owned);
|
.map(str::to_owned);
|
||||||
|
|
||||||
let remote_addr = req.peer_addr.map(|addr| addr.to_string());
|
let peer_addr = req.peer_addr.map(|addr| addr.ip().to_string());
|
||||||
|
|
||||||
ConnectionInfo {
|
ConnectionInfo {
|
||||||
host,
|
host,
|
||||||
scheme,
|
scheme,
|
||||||
remote_addr,
|
peer_addr,
|
||||||
realip_remote_addr,
|
realip_remote_addr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Real IP (remote address) of client that initiated request.
|
||||||
|
///
|
||||||
|
/// The address is resolved through the following, in order:
|
||||||
|
/// - `Forwarded` header
|
||||||
|
/// - `X-Forwarded-For` header
|
||||||
|
/// - peer address of opened socket (same as [`remote_addr`](Self::remote_addr))
|
||||||
|
///
|
||||||
|
/// # Security
|
||||||
|
/// Do not use this function for security purposes unless you can be sure that the `Forwarded`
|
||||||
|
/// and `X-Forwarded-For` headers cannot be spoofed by the client. If you are running without a
|
||||||
|
/// proxy then [obtaining the peer address](Self::peer_addr) would be more appropriate.
|
||||||
|
#[inline]
|
||||||
|
pub fn realip_remote_addr(&self) -> Option<&str> {
|
||||||
|
self.realip_remote_addr
|
||||||
|
.as_deref()
|
||||||
|
.or_else(|| self.peer_addr.as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns serialized IP address of the peer connection.
|
||||||
|
///
|
||||||
|
/// See [`HttpRequest::peer_addr`] for more details.
|
||||||
|
#[inline]
|
||||||
|
pub fn peer_addr(&self) -> Option<&str> {
|
||||||
|
self.peer_addr.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hostname of the request.
|
||||||
|
///
|
||||||
|
/// Hostname is resolved through the following, in order:
|
||||||
|
/// - `Forwarded` header
|
||||||
|
/// - `X-Forwarded-Host` header
|
||||||
|
/// - `Host` header
|
||||||
|
/// - request target / URI
|
||||||
|
/// - configured server hostname
|
||||||
|
#[inline]
|
||||||
|
pub fn host(&self) -> &str {
|
||||||
|
&self.host
|
||||||
|
}
|
||||||
|
|
||||||
/// Scheme of the request.
|
/// Scheme of the request.
|
||||||
///
|
///
|
||||||
/// Scheme is resolved through the following headers, in this order:
|
/// Scheme is resolved through the following, in order:
|
||||||
///
|
/// - `Forwarded` header
|
||||||
/// - Forwarded
|
/// - `X-Forwarded-Proto` header
|
||||||
/// - X-Forwarded-Proto
|
/// - request target / URI
|
||||||
/// - Uri
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn scheme(&self) -> &str {
|
pub fn scheme(&self) -> &str {
|
||||||
&self.scheme
|
&self.scheme
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hostname of the request.
|
#[doc(hidden)]
|
||||||
///
|
#[deprecated(since = "4.0.0", note = "Renamed to `peer_addr`.")]
|
||||||
/// Hostname is resolved through the following headers, in this order:
|
|
||||||
///
|
|
||||||
/// - Forwarded
|
|
||||||
/// - X-Forwarded-Host
|
|
||||||
/// - Host
|
|
||||||
/// - Uri
|
|
||||||
/// - Server hostname
|
|
||||||
pub fn host(&self) -> &str {
|
|
||||||
&self.host
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remote address of the connection.
|
|
||||||
///
|
|
||||||
/// Get remote_addr address from socket address.
|
|
||||||
pub fn remote_addr(&self) -> Option<&str> {
|
pub fn remote_addr(&self) -> Option<&str> {
|
||||||
self.remote_addr.as_deref()
|
self.peer_addr()
|
||||||
}
|
|
||||||
|
|
||||||
/// Real IP (remote address) of client that initiated request.
|
|
||||||
///
|
|
||||||
/// The address is resolved through the following headers, in this order:
|
|
||||||
///
|
|
||||||
/// - Forwarded
|
|
||||||
/// - X-Forwarded-For
|
|
||||||
/// - remote_addr name of opened socket
|
|
||||||
///
|
|
||||||
/// # Security
|
|
||||||
/// Do not use this function for security purposes, unless you can ensure the Forwarded and
|
|
||||||
/// X-Forwarded-For headers cannot be spoofed by the client. If you want the client's socket
|
|
||||||
/// address explicitly, use [`HttpRequest::peer_addr()`][peer_addr] instead.
|
|
||||||
///
|
|
||||||
/// [peer_addr]: crate::web::HttpRequest::peer_addr()
|
|
||||||
#[inline]
|
|
||||||
pub fn realip_remote_addr(&self) -> Option<&str> {
|
|
||||||
self.realip_remote_addr
|
|
||||||
.as_deref()
|
|
||||||
.or_else(|| self.remote_addr.as_deref())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +212,7 @@ impl FromRequest for ConnectionInfo {
|
|||||||
|
|
||||||
/// Extractor for peer's socket address.
|
/// Extractor for peer's socket address.
|
||||||
///
|
///
|
||||||
/// Also see [`HttpRequest::peer_addr`].
|
/// Also see [`HttpRequest::peer_addr`] and [`ConnectionInfo::peer_addr`].
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
@ -432,13 +435,37 @@ mod tests {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn peer_addr_extract() {
|
async fn peer_addr_extract() {
|
||||||
|
let req = TestRequest::default().to_http_request();
|
||||||
|
let res = PeerAddr::extract(&req).await;
|
||||||
|
assert!(res.is_err());
|
||||||
|
|
||||||
let addr = "127.0.0.1:8080".parse().unwrap();
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
let req = TestRequest::default().peer_addr(addr).to_http_request();
|
let req = TestRequest::default().peer_addr(addr).to_http_request();
|
||||||
let peer_addr = PeerAddr::extract(&req).await.unwrap();
|
let peer_addr = PeerAddr::extract(&req).await.unwrap();
|
||||||
assert_eq!(peer_addr, PeerAddr(addr));
|
assert_eq!(peer_addr, PeerAddr(addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn remote_address() {
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let res = PeerAddr::extract(&req).await;
|
let res = ConnectionInfo::extract(&req).await.unwrap();
|
||||||
assert!(res.is_err());
|
assert!(res.peer_addr().is_none());
|
||||||
|
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
|
let req = TestRequest::default().peer_addr(addr).to_http_request();
|
||||||
|
let conn_info = ConnectionInfo::extract(&req).await.unwrap();
|
||||||
|
assert_eq!(conn_info.peer_addr().unwrap(), "127.0.0.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn real_ip_from_socket_addr() {
|
||||||
|
let req = TestRequest::default().to_http_request();
|
||||||
|
let res = ConnectionInfo::extract(&req).await.unwrap();
|
||||||
|
assert!(res.realip_remote_addr().is_none());
|
||||||
|
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
|
let req = TestRequest::default().peer_addr(addr).to_http_request();
|
||||||
|
let conn_info = ConnectionInfo::extract(&req).await.unwrap();
|
||||||
|
assert_eq!(conn_info.realip_remote_addr().unwrap(), "127.0.0.1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -547,7 +547,7 @@ impl FormatText {
|
|||||||
*self = FormatText::Str(s.to_string());
|
*self = FormatText::Str(s.to_string());
|
||||||
}
|
}
|
||||||
FormatText::RemoteAddr => {
|
FormatText::RemoteAddr => {
|
||||||
let s = if let Some(peer) = req.connection_info().remote_addr() {
|
let s = if let Some(peer) = req.connection_info().peer_addr() {
|
||||||
FormatText::Str((*peer).to_string())
|
FormatText::Str((*peer).to_string())
|
||||||
} else {
|
} else {
|
||||||
FormatText::Str("-".to_string())
|
FormatText::Str("-".to_string())
|
||||||
|
@ -225,7 +225,7 @@ mod tests {
|
|||||||
.service(web::resource("/v1/something").to(HttpResponse::Ok))
|
.service(web::resource("/v1/something").to(HttpResponse::Ok))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/v2/something")
|
web::resource("/v2/something")
|
||||||
.guard(fn_guard(|req| req.uri.query() == Some("query=test")))
|
.guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test")))
|
||||||
.to(HttpResponse::Ok),
|
.to(HttpResponse::Ok),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -261,7 +261,7 @@ mod tests {
|
|||||||
.service(web::resource("/v1/something").to(HttpResponse::Ok))
|
.service(web::resource("/v1/something").to(HttpResponse::Ok))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/v2/something")
|
web::resource("/v2/something")
|
||||||
.guard(fn_guard(|req| req.uri.query() == Some("query=test")))
|
.guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test")))
|
||||||
.to(HttpResponse::Ok),
|
.to(HttpResponse::Ok),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -294,7 +294,7 @@ mod tests {
|
|||||||
let app = init_service(
|
let app = init_service(
|
||||||
App::new().wrap(NormalizePath(TrailingSlash::Trim)).service(
|
App::new().wrap(NormalizePath(TrailingSlash::Trim)).service(
|
||||||
web::resource("/")
|
web::resource("/")
|
||||||
.guard(fn_guard(|req| req.uri.query() == Some("query=test")))
|
.guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test")))
|
||||||
.to(HttpResponse::Ok),
|
.to(HttpResponse::Ok),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -318,7 +318,7 @@ mod tests {
|
|||||||
.service(web::resource("/v1/something/").to(HttpResponse::Ok))
|
.service(web::resource("/v1/something/").to(HttpResponse::Ok))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/v2/something/")
|
web::resource("/v2/something/")
|
||||||
.guard(fn_guard(|req| req.uri.query() == Some("query=test")))
|
.guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test")))
|
||||||
.to(HttpResponse::Ok),
|
.to(HttpResponse::Ok),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -353,7 +353,7 @@ mod tests {
|
|||||||
.wrap(NormalizePath(TrailingSlash::Always))
|
.wrap(NormalizePath(TrailingSlash::Always))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/")
|
web::resource("/")
|
||||||
.guard(fn_guard(|req| req.uri.query() == Some("query=test")))
|
.guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test")))
|
||||||
.to(HttpResponse::Ok),
|
.to(HttpResponse::Ok),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -378,7 +378,7 @@ mod tests {
|
|||||||
.service(web::resource("/v1/").to(HttpResponse::Ok))
|
.service(web::resource("/v1/").to(HttpResponse::Ok))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/v2/something")
|
web::resource("/v2/something")
|
||||||
.guard(fn_guard(|req| req.uri.query() == Some("query=test")))
|
.guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test")))
|
||||||
.to(HttpResponse::Ok),
|
.to(HttpResponse::Ok),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -228,23 +228,28 @@ impl HttpRequest {
|
|||||||
self.app_state().rmap()
|
self.app_state().rmap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peer socket address.
|
/// Returns peer socket address.
|
||||||
///
|
///
|
||||||
/// Peer address is the directly connected peer's socket address. If a proxy is used in front of
|
/// Peer address is the directly connected peer's socket address. If a proxy is used in front of
|
||||||
/// the Actix Web server, then it would be address of this proxy.
|
/// the Actix Web server, then it would be address of this proxy.
|
||||||
///
|
///
|
||||||
/// To get client connection information `.connection_info()` should be used.
|
/// For expanded client connection information, use [`connection_info`] instead.
|
||||||
///
|
///
|
||||||
/// Will only return None when called in unit tests.
|
/// Will only return None when called in unit tests unless [`TestRequest::peer_addr`] is used.
|
||||||
|
///
|
||||||
|
/// [`TestRequest::peer_addr`]: crate::test::TestRequest::peer_addr
|
||||||
|
/// [`connection_info`]: Self::connection_info
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
|
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||||
self.head().peer_addr
|
self.head().peer_addr
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get *ConnectionInfo* for the current request.
|
/// Returns connection info for the current request.
|
||||||
///
|
///
|
||||||
/// This method panics if request's extensions container is already
|
/// The return type, [`ConnectionInfo`], can also be used as an extractor.
|
||||||
/// borrowed.
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if request's extensions container is already borrowed.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
|
pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
|
||||||
if !self.extensions().contains::<ConnectionInfo>() {
|
if !self.extensions().contains::<ConnectionInfo>() {
|
||||||
@ -252,7 +257,7 @@ impl HttpRequest {
|
|||||||
self.extensions_mut().insert(info);
|
self.extensions_mut().insert(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref::map(self.extensions(), |e| e.get().unwrap())
|
Ref::map(self.extensions(), |data| data.get().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// App config
|
/// App config
|
||||||
|
38
src/route.rs
38
src/route.rs
@ -65,9 +65,12 @@ pub struct RouteService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RouteService {
|
impl RouteService {
|
||||||
|
// TODO: does this need to take &mut ?
|
||||||
pub fn check(&self, req: &mut ServiceRequest) -> bool {
|
pub fn check(&self, req: &mut ServiceRequest) -> bool {
|
||||||
for f in self.guards.iter() {
|
let guard_ctx = req.guard_ctx();
|
||||||
if !f.check(req.head()) {
|
|
||||||
|
for guard in self.guards.iter() {
|
||||||
|
if !guard.check(&guard_ctx) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,6 +93,7 @@ impl Service<ServiceRequest> for RouteService {
|
|||||||
impl Route {
|
impl Route {
|
||||||
/// Add method guard to the route.
|
/// Add method guard to the route.
|
||||||
///
|
///
|
||||||
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// # use actix_web::*;
|
/// # use actix_web::*;
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
@ -110,6 +114,7 @@ impl Route {
|
|||||||
|
|
||||||
/// Add guard to the route.
|
/// Add guard to the route.
|
||||||
///
|
///
|
||||||
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// # use actix_web::*;
|
/// # use actix_web::*;
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
@ -143,16 +148,13 @@ impl Route {
|
|||||||
/// format!("Welcome {}!", info.username)
|
/// format!("Welcome {}!", info.username)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// let app = App::new().service(
|
||||||
/// let app = App::new().service(
|
/// web::resource("/{username}/index.html") // <- define path parameters
|
||||||
/// web::resource("/{username}/index.html") // <- define path parameters
|
/// .route(web::get().to(index)) // <- register handler
|
||||||
/// .route(web::get().to(index)) // <- register handler
|
/// );
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// It is possible to use multiple extractors for one handler function.
|
/// It is possible to use multiple extractors for one handler function.
|
||||||
///
|
|
||||||
/// ```
|
/// ```
|
||||||
/// # use std::collections::HashMap;
|
/// # use std::collections::HashMap;
|
||||||
/// # use serde::Deserialize;
|
/// # use serde::Deserialize;
|
||||||
@ -164,16 +166,18 @@ impl Route {
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// /// extract path info using serde
|
/// /// extract path info using serde
|
||||||
/// async fn index(path: web::Path<Info>, query: web::Query<HashMap<String, String>>, body: web::Json<Info>) -> String {
|
/// async fn index(
|
||||||
|
/// path: web::Path<Info>,
|
||||||
|
/// query: web::Query<HashMap<String, String>>,
|
||||||
|
/// body: web::Json<Info>
|
||||||
|
/// ) -> String {
|
||||||
/// format!("Welcome {}!", path.username)
|
/// format!("Welcome {}!", path.username)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// let app = App::new().service(
|
||||||
/// let app = App::new().service(
|
/// web::resource("/{username}/index.html") // <- define path parameters
|
||||||
/// web::resource("/{username}/index.html") // <- define path parameters
|
/// .route(web::get().to(index))
|
||||||
/// .route(web::get().to(index))
|
/// );
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn to<F, Args>(mut self, handler: F) -> Self
|
pub fn to<F, Args>(mut self, handler: F) -> Self
|
||||||
where
|
where
|
||||||
@ -199,7 +203,7 @@ impl Route {
|
|||||||
/// type Error = Infallible;
|
/// type Error = Infallible;
|
||||||
/// type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
/// type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
///
|
///
|
||||||
/// always_ready!();
|
/// dev::always_ready!();
|
||||||
///
|
///
|
||||||
/// fn call(&self, req: ServiceRequest) -> Self::Future {
|
/// fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
/// let (req, _) = req.into_parts();
|
/// let (req, _) = req.into_parts();
|
||||||
|
@ -538,12 +538,15 @@ impl Service<ServiceRequest> for ScopeService {
|
|||||||
fn call(&self, mut req: ServiceRequest) -> Self::Future {
|
fn call(&self, mut req: ServiceRequest) -> Self::Future {
|
||||||
let res = self.router.recognize_fn(&mut req, |req, guards| {
|
let res = self.router.recognize_fn(&mut req, |req, guards| {
|
||||||
if let Some(ref guards) = guards {
|
if let Some(ref guards) = guards {
|
||||||
for f in guards {
|
let guard_ctx = req.guard_ctx();
|
||||||
if !f.check(req.head()) {
|
|
||||||
|
for guard in guards {
|
||||||
|
if !guard.check(&guard_ctx) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ use cookie::{Cookie, ParseError as CookieParseError};
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::{AppConfig, AppService},
|
config::{AppConfig, AppService},
|
||||||
dev::ensure_leading_slash,
|
dev::ensure_leading_slash,
|
||||||
guard::Guard,
|
guard::{Guard, GuardContext},
|
||||||
info::ConnectionInfo,
|
info::ConnectionInfo,
|
||||||
rmap::ResourceMap,
|
rmap::ResourceMap,
|
||||||
Error, HttpRequest, HttpResponse,
|
Error, HttpRequest, HttpResponse,
|
||||||
@ -172,7 +172,7 @@ impl ServiceRequest {
|
|||||||
self.head().uri.path()
|
self.head().uri.path()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Counterpart to [`HttpRequest::query_string`](super::HttpRequest::query_string()).
|
/// Counterpart to [`HttpRequest::query_string`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn query_string(&self) -> &str {
|
pub fn query_string(&self) -> &str {
|
||||||
self.req.query_string()
|
self.req.query_string()
|
||||||
@ -208,13 +208,13 @@ impl ServiceRequest {
|
|||||||
self.req.match_info()
|
self.req.match_info()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Counterpart to [`HttpRequest::match_name`](super::HttpRequest::match_name()).
|
/// Counterpart to [`HttpRequest::match_name`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn match_name(&self) -> Option<&str> {
|
pub fn match_name(&self) -> Option<&str> {
|
||||||
self.req.match_name()
|
self.req.match_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Counterpart to [`HttpRequest::match_pattern`](super::HttpRequest::match_pattern()).
|
/// Counterpart to [`HttpRequest::match_pattern`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn match_pattern(&self) -> Option<String> {
|
pub fn match_pattern(&self) -> Option<String> {
|
||||||
self.req.match_pattern()
|
self.req.match_pattern()
|
||||||
@ -238,7 +238,7 @@ impl ServiceRequest {
|
|||||||
self.req.app_config()
|
self.req.app_config()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Counterpart to [`HttpRequest::app_data`](super::HttpRequest::app_data()).
|
/// Counterpart to [`HttpRequest::app_data`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn app_data<T: 'static>(&self) -> Option<&T> {
|
pub fn app_data<T: 'static>(&self) -> Option<&T> {
|
||||||
for container in self.req.inner.app_data.iter().rev() {
|
for container in self.req.inner.app_data.iter().rev() {
|
||||||
@ -250,19 +250,33 @@ impl ServiceRequest {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Counterpart to [`HttpRequest::conn_data`](super::HttpRequest::conn_data()).
|
/// Counterpart to [`HttpRequest::conn_data`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn conn_data<T: 'static>(&self) -> Option<&T> {
|
pub fn conn_data<T: 'static>(&self) -> Option<&T> {
|
||||||
self.req.conn_data()
|
self.req.conn_data()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Counterpart to [`HttpRequest::req_data`].
|
||||||
|
#[inline]
|
||||||
|
pub fn req_data(&self) -> Ref<'_, Extensions> {
|
||||||
|
self.req.req_data()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Counterpart to [`HttpRequest::req_data_mut`].
|
||||||
|
#[inline]
|
||||||
|
pub fn req_data_mut(&self) -> RefMut<'_, Extensions> {
|
||||||
|
self.req.req_data_mut()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
|
#[inline]
|
||||||
pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
|
pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
|
||||||
self.req.cookies()
|
self.req.cookies()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return request cookie.
|
/// Return request cookie.
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
|
#[inline]
|
||||||
pub fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
|
pub fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
|
||||||
self.req.cookie(name)
|
self.req.cookie(name)
|
||||||
}
|
}
|
||||||
@ -283,6 +297,14 @@ impl ServiceRequest {
|
|||||||
.app_data
|
.app_data
|
||||||
.push(extensions);
|
.push(extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a context object for use with a [guard](crate::guard).
|
||||||
|
///
|
||||||
|
/// Useful if you are implementing
|
||||||
|
#[inline]
|
||||||
|
pub fn guard_ctx(&self) -> GuardContext<'_> {
|
||||||
|
GuardContext { req: self }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resource<Url> for ServiceRequest {
|
impl Resource<Url> for ServiceRequest {
|
||||||
|
@ -124,7 +124,7 @@ impl TestRequest {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set HTTP Uri of this request
|
/// Set HTTP URI of this request
|
||||||
pub fn uri(mut self, path: &str) -> Self {
|
pub fn uri(mut self, path: &str) -> Self {
|
||||||
self.req.uri(path);
|
self.req.uri(path);
|
||||||
self
|
self
|
||||||
|
Reference in New Issue
Block a user