mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into remove_checked_expr
This commit is contained in:
commit
4e22b98ae3
|
@ -6,4 +6,4 @@ ci-min-test = "hack check --workspace --no-default-features --tests --examples"
|
||||||
ci-default = "check --workspace --bins --tests --examples"
|
ci-default = "check --workspace --bins --tests --examples"
|
||||||
ci-full = "check --workspace --all-features --bins --tests --examples"
|
ci-full = "check --workspace --all-features --bins --tests --examples"
|
||||||
ci-test = "test --workspace --all-features --lib --tests --no-fail-fast -- --nocapture"
|
ci-test = "test --workspace --all-features --lib --tests --no-fail-fast -- --nocapture"
|
||||||
ci-doctest = "hack test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
||||||
|
|
|
@ -24,6 +24,8 @@ jobs:
|
||||||
runs-on: ${{ matrix.target.os }}
|
runs-on: ${{ matrix.target.os }}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
CI: 1
|
||||||
|
CARGO_INCREMENTAL: 0
|
||||||
VCPKGRS_DYNAMIC: 1
|
VCPKGRS_DYNAMIC: 1
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -80,13 +82,6 @@ jobs:
|
||||||
command: ci-test
|
command: ci-test
|
||||||
args: --skip=test_reading_deflate_encoding_large_random_rustls
|
args: --skip=test_reading_deflate_encoding_large_random_rustls
|
||||||
|
|
||||||
- name: doc tests
|
|
||||||
# due to unknown issue with running doc tests on macOS
|
|
||||||
if: matrix.target.os == 'ubuntu-latest'
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
timeout-minutes: 40
|
|
||||||
with: { command: ci-doctest }
|
|
||||||
|
|
||||||
- name: Generate coverage file
|
- name: Generate coverage file
|
||||||
if: >
|
if: >
|
||||||
matrix.target.os == 'ubuntu-latest'
|
matrix.target.os == 'ubuntu-latest'
|
||||||
|
@ -106,5 +101,36 @@ jobs:
|
||||||
|
|
||||||
- name: Clear the cargo caches
|
- name: Clear the cargo caches
|
||||||
run: |
|
run: |
|
||||||
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
|
cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean
|
||||||
cargo-cache
|
cargo-cache
|
||||||
|
|
||||||
|
rustdoc:
|
||||||
|
name: rustdoc
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Rust (nightly)
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly-x86_64-unknown-linux-gnu
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Generate Cargo.lock
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with: { command: generate-lockfile }
|
||||||
|
- name: Cache Dependencies
|
||||||
|
uses: Swatinem/rust-cache@v1.3.0
|
||||||
|
|
||||||
|
- name: Install cargo-hack
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: install
|
||||||
|
args: cargo-hack
|
||||||
|
|
||||||
|
- name: doc tests
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
timeout-minutes: 40
|
||||||
|
with: { command: ci-doctest }
|
||||||
|
|
34
CHANGES.md
34
CHANGES.md
|
@ -2,21 +2,41 @@
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
### Added
|
### Added
|
||||||
* Re-export actix-service `ServiceFactory` in `dev` module. [#2325]
|
* Option to allow `Json` extractor to work without a `Content-Type` header present. [#2362]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
* Associated type `FromRequest::Config` was removed. [#2233]
|
||||||
* Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344]
|
* Inner field made private on `web::Payload`. [#2384]
|
||||||
|
|
||||||
### Fixed
|
|
||||||
* Fix quality parse error in Accept-Encoding header. [#2344]
|
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* `ServiceResponse::checked_expr` was a legacy and just removed. [#2401]
|
* `ServiceResponse::checked_expr` was a legacy and just removed. [#2401]
|
||||||
|
|
||||||
|
[#2233]: https://github.com/actix/actix-web/pull/2233
|
||||||
|
[#2362]: https://github.com/actix/actix-web/pull/2362
|
||||||
|
[#2384]: https://github.com/actix/actix-web/pull/2384
|
||||||
|
[#2401]: https://github.com/actix/actix-web/pull/2401
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0-beta.9 - 2021-09-09
|
||||||
|
### Added
|
||||||
|
* Re-export actix-service `ServiceFactory` in `dev` module. [#2325]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Compress middleware will return 406 Not Acceptable when no content encoding is acceptable to the client. [#2344]
|
||||||
|
* Move `BaseHttpResponse` to `dev::Response`. [#2379]
|
||||||
|
* Enable `TestRequest::param` to accept more than just static strings. [#2172]
|
||||||
|
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Fix quality parse error in Accept-Encoding header. [#2344]
|
||||||
|
* Re-export correct type at `web::HttpResponse`. [#2379]
|
||||||
|
|
||||||
[#2325]: https://github.com/actix/actix-web/pull/2325
|
[#2325]: https://github.com/actix/actix-web/pull/2325
|
||||||
[#2344]: https://github.com/actix/actix-web/pull/2344
|
[#2344]: https://github.com/actix/actix-web/pull/2344
|
||||||
[#2401]: https://github.com/actix/actix-web/pull/2401
|
[#2172]: https://github.com/actix/actix-web/pull/2172
|
||||||
|
[#2325]: https://github.com/actix/actix-web/pull/2325
|
||||||
|
[#2344]: https://github.com/actix/actix-web/pull/2344
|
||||||
|
[#2379]: https://github.com/actix/actix-web/pull/2379
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.8 - 2021-06-26
|
## 4.0.0-beta.8 - 2021-06-26
|
||||||
|
|
17
Cargo.toml
17
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "4.0.0-beta.8"
|
version = "4.0.0-beta.9"
|
||||||
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"]
|
||||||
|
@ -18,6 +18,7 @@ edition = "2018"
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
# features that docs.rs will build with
|
# features that docs.rs will build with
|
||||||
features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"]
|
features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"]
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "actix_web"
|
name = "actix_web"
|
||||||
|
@ -69,15 +70,15 @@ __compress = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.4.0"
|
actix-codec = "0.4.0"
|
||||||
actix-macros = "0.2.1"
|
actix-macros = "0.2.1"
|
||||||
actix-router = "0.5.0-beta.1"
|
actix-router = "0.5.0-beta.2"
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-server = "2.0.0-beta.3"
|
actix-server = "2.0.0-beta.3"
|
||||||
actix-service = "2.0.0"
|
actix-service = "2.0.0"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true }
|
actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true }
|
||||||
|
|
||||||
actix-web-codegen = "0.5.0-beta.2"
|
actix-web-codegen = "0.5.0-beta.4"
|
||||||
actix-http = "3.0.0-beta.9"
|
actix-http = "3.0.0-beta.10"
|
||||||
|
|
||||||
ahash = "0.7"
|
ahash = "0.7"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
@ -101,12 +102,12 @@ serde_json = "1.0"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
smallvec = "1.6.1"
|
smallvec = "1.6.1"
|
||||||
socket2 = "0.4.0"
|
socket2 = "0.4.0"
|
||||||
time = { version = "0.2.23", default-features = false, features = ["std"] }
|
time = { version = "0.3", default-features = false, features = ["formatting"] }
|
||||||
url = "2.1"
|
url = "2.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] }
|
actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] }
|
||||||
awc = { version = "3.0.0-beta.7", features = ["openssl"] }
|
awc = { version = "3.0.0-beta.8", features = ["openssl"] }
|
||||||
|
|
||||||
brotli2 = "0.3.2"
|
brotli2 = "0.3.2"
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
|
@ -118,6 +119,10 @@ rcgen = "0.8"
|
||||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
tls-openssl = { package = "openssl", version = "0.10.9" }
|
||||||
tls-rustls = { package = "rustls", version = "0.19.0" }
|
tls-rustls = { package = "rustls", version = "0.19.0" }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much.
|
||||||
|
debug = 0
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
|
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
|
||||||
|
|
||||||
|
* The `type Config` of `FromRequest` was removed.
|
||||||
|
|
||||||
* Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd).
|
* Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd).
|
||||||
By default all compression algorithms are enabled.
|
By default all compression algorithms are enabled.
|
||||||
To select algorithm you want to include with `middleware::Compress` use following flags:
|
To select algorithm you want to include with `middleware::Compress` use following flags:
|
||||||
|
|
|
@ -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.8)
|
[](https://docs.rs/actix-web/4.0.0-beta.9)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-web/4.0.0-beta.8)
|
[](https://deps.rs/crate/actix-web/4.0.0-beta.9)
|
||||||
<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)
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0-beta.7 - 2021-09-09
|
||||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.6.0-beta.6"
|
version = "0.6.0-beta.7"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Static file serving for Actix Web"
|
description = "Static file serving for Actix Web"
|
||||||
keywords = ["actix", "http", "async", "futures"]
|
keywords = ["actix", "http", "async", "futures"]
|
||||||
|
@ -15,8 +15,8 @@ name = "actix_files"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.8", default-features = false }
|
actix-web = { version = "4.0.0-beta.9", default-features = false }
|
||||||
actix-http = "3.0.0-beta.8"
|
actix-http = "3.0.0-beta.10"
|
||||||
actix-service = "2.0.0"
|
actix-service = "2.0.0"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
|
|
||||||
|
@ -33,5 +33,5 @@ percent-encoding = "2.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-web = "4.0.0-beta.8"
|
actix-web = "4.0.0-beta.9"
|
||||||
actix-test = "0.1.0-beta.3"
|
actix-test = "0.1.0-beta.3"
|
||||||
|
|
|
@ -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.6)
|
[](https://docs.rs/actix-files/0.6.0-beta.7)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-files/0.6.0-beta.6)
|
[](https://deps.rs/crate/actix-files/0.6.0-beta.7)
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,6 @@ impl AsRef<Path> for PathBufWrap {
|
||||||
impl FromRequest for PathBufWrap {
|
impl FromRequest for PathBufWrap {
|
||||||
type Error = UriSegmentError;
|
type Error = UriSegmentError;
|
||||||
type Future = Ready<Result<Self, Self::Error>>;
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
type Config = ();
|
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
ready(req.match_info().path().parse())
|
ready(req.match_info().path().parse())
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.5 - 2021-09-09
|
||||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-http-test"
|
name = "actix-http-test"
|
||||||
version = "3.0.0-beta.4"
|
version = "3.0.0-beta.5"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Various helpers for Actix applications to use during testing"
|
description = "Various helpers for Actix applications to use during testing"
|
||||||
readme = "README.md"
|
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
repository = "https://github.com/actix/actix-web.git"
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
documentation = "https://docs.rs/actix-http-test/"
|
categories = [
|
||||||
categories = ["network-programming", "asynchronous",
|
"network-programming",
|
||||||
"web-programming::http-server",
|
"asynchronous",
|
||||||
"web-programming::websocket"]
|
"web-programming::http-server",
|
||||||
|
"web-programming::websocket",
|
||||||
|
]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
exclude = [".gitignore", ".cargo/config"]
|
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
|
@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.5"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-server = "2.0.0-beta.3"
|
actix-server = "2.0.0-beta.3"
|
||||||
awc = { version = "3.0.0-beta.7", default-features = false }
|
awc = { version = "3.0.0-beta.8", default-features = false }
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
@ -47,9 +47,8 @@ serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
time = { version = "0.2.23", default-features = false, features = ["std"] }
|
|
||||||
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.8", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["cookies"] }
|
||||||
actix-http = "3.0.0-beta.8"
|
actix-http = "3.0.0-beta.10"
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
> Various helpers for Actix applications to use during testing.
|
> Various helpers for Actix applications to use during testing.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http-test)
|
[](https://crates.io/crates/actix-http-test)
|
||||||
[](https://docs.rs/actix-http-test/3.0.0-beta.4)
|
[](https://docs.rs/actix-http-test/3.0.0-beta.5)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
<br>
|
<br>
|
||||||
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.4)
|
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.5)
|
||||||
[](https://crates.io/crates/actix-http-test)
|
[](https://crates.io/crates/actix-http-test)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
extern crate tls_openssl as openssl;
|
extern crate tls_openssl as openssl;
|
||||||
|
|
||||||
use std::sync::mpsc;
|
use std::{net, sync::mpsc, thread, time::Duration};
|
||||||
use std::{net, thread, time};
|
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
use actix_rt::{net::TcpStream, System};
|
use actix_rt::{net::TcpStream, System};
|
||||||
|
@ -95,15 +94,15 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
||||||
.set_alpn_protos(b"\x02h2\x08http/1.1")
|
.set_alpn_protos(b"\x02h2\x08http/1.1")
|
||||||
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
|
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
|
||||||
Connector::new()
|
Connector::new()
|
||||||
.conn_lifetime(time::Duration::from_secs(0))
|
.conn_lifetime(Duration::from_secs(0))
|
||||||
.timeout(time::Duration::from_millis(30000))
|
.timeout(Duration::from_millis(30000))
|
||||||
.ssl(builder.build())
|
.ssl(builder.build())
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "openssl"))]
|
#[cfg(not(feature = "openssl"))]
|
||||||
{
|
{
|
||||||
Connector::new()
|
Connector::new()
|
||||||
.conn_lifetime(time::Duration::from_secs(0))
|
.conn_lifetime(Duration::from_secs(0))
|
||||||
.timeout(time::Duration::from_millis(30000))
|
.timeout(Duration::from_millis(30000))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.10 - 2021-09-09
|
||||||
### Changed
|
### Changed
|
||||||
* `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377]
|
* `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377]
|
||||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
@ -16,7 +19,7 @@
|
||||||
[#2377]: https://github.com/actix/actix-web/pull/2377
|
[#2377]: https://github.com/actix/actix-web/pull/2377
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.8 - 2021-08-09
|
## 3.0.0-beta.9 - 2021-08-09
|
||||||
### Fixed
|
### Fixed
|
||||||
* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
|
* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "3.0.0-beta.9"
|
version = "3.0.0-beta.10"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "HTTP primitives for the Actix ecosystem"
|
description = "HTTP primitives for the Actix ecosystem"
|
||||||
keywords = ["actix", "http", "framework", "async", "futures"]
|
keywords = ["actix", "http", "framework", "async", "futures"]
|
||||||
|
@ -60,6 +60,7 @@ futures-util = { version = "0.3.7", default-features = false, features = ["alloc
|
||||||
h2 = "0.3.1"
|
h2 = "0.3.1"
|
||||||
http = "0.2.2"
|
http = "0.2.2"
|
||||||
httparse = "1.5.1"
|
httparse = "1.5.1"
|
||||||
|
httpdate = "1.0.1"
|
||||||
itoa = "0.4"
|
itoa = "0.4"
|
||||||
language-tags = "0.3"
|
language-tags = "0.3"
|
||||||
local-channel = "0.1"
|
local-channel = "0.1"
|
||||||
|
@ -70,11 +71,8 @@ percent-encoding = "2.1"
|
||||||
pin-project = "1.0.0"
|
pin-project = "1.0.0"
|
||||||
pin-project-lite = "0.2"
|
pin-project-lite = "0.2"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
regex = "1.3"
|
|
||||||
serde = "1.0"
|
|
||||||
sha-1 = "0.9"
|
sha-1 = "0.9"
|
||||||
smallvec = "1.6.1"
|
smallvec = "1.6.1"
|
||||||
time = { version = "0.2.23", default-features = false, features = ["std"] }
|
|
||||||
tokio = { version = "1.2", features = ["sync"] }
|
tokio = { version = "1.2", features = ["sync"] }
|
||||||
|
|
||||||
# compression
|
# compression
|
||||||
|
@ -86,17 +84,18 @@ trust-dns-resolver = { version = "0.20.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-server = "2.0.0-beta.3"
|
actix-server = "2.0.0-beta.3"
|
||||||
actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] }
|
||||||
actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] }
|
actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] }
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
rcgen = "0.8"
|
rcgen = "0.8"
|
||||||
|
regex = "1.3"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tls-openssl = { version = "0.10", package = "openssl" }
|
tls-openssl = { version = "0.10", package = "openssl" }
|
||||||
tls-rustls = { version = "0.19", package = "rustls" }
|
tls-rustls = { version = "0.19", package = "rustls" }
|
||||||
webpki = { version = "0.21.0" }
|
webpki = { version = "0.21" }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "ws"
|
name = "ws"
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
> HTTP primitives for the Actix ecosystem.
|
> HTTP primitives for the Actix ecosystem.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http)
|
[](https://crates.io/crates/actix-http)
|
||||||
[](https://docs.rs/actix-http/3.0.0-beta.9)
|
[](https://docs.rs/actix-http/3.0.0-beta.10)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-http/3.0.0-beta.9)
|
[](https://deps.rs/crate/actix-http/3.0.0-beta.10)
|
||||||
[](https://crates.io/crates/actix-http)
|
[](https://crates.io/crates/actix-http)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
use std::cell::Cell;
|
use std::{
|
||||||
use std::fmt::Write;
|
cell::Cell,
|
||||||
use std::rc::Rc;
|
fmt::{self, Write},
|
||||||
use std::time::Duration;
|
net,
|
||||||
use std::{fmt, net};
|
rc::Rc,
|
||||||
|
time::{Duration, SystemTime},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_rt::{
|
use actix_rt::{
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
time::{interval, sleep_until, Instant, Sleep},
|
time::{interval, sleep_until, Instant, Sleep},
|
||||||
};
|
};
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use time::OffsetDateTime;
|
|
||||||
|
|
||||||
/// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
/// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
||||||
const DATE_VALUE_LENGTH: usize = 29;
|
pub(crate) const DATE_VALUE_LENGTH: usize = 29;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
/// Server keep-alive setting
|
/// Server keep-alive setting
|
||||||
|
@ -206,12 +207,7 @@ impl Date {
|
||||||
|
|
||||||
fn update(&mut self) {
|
fn update(&mut self) {
|
||||||
self.pos = 0;
|
self.pos = 0;
|
||||||
write!(
|
write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap();
|
||||||
self,
|
|
||||||
"{}",
|
|
||||||
OffsetDateTime::now_utc().format("%a, %d %b %Y %H:%M:%S GMT")
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,11 +265,11 @@ impl DateService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to a util module for testing all spawn handle drop style tasks.
|
// TODO: move to a util module for testing all spawn handle drop style tasks.
|
||||||
#[cfg(test)]
|
|
||||||
/// Test Module for checking the drop state of certain async tasks that are spawned
|
/// Test Module for checking the drop state of certain async tasks that are spawned
|
||||||
/// with `actix_rt::spawn`
|
/// with `actix_rt::spawn`
|
||||||
///
|
///
|
||||||
/// The target task must explicitly generate `NotifyOnDrop` when spawn the task
|
/// The target task must explicitly generate `NotifyOnDrop` when spawn the task
|
||||||
|
#[cfg(test)]
|
||||||
mod notify_on_drop {
|
mod notify_on_drop {
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
@ -283,9 +279,8 @@ mod notify_on_drop {
|
||||||
|
|
||||||
/// Check if the spawned task is dropped.
|
/// Check if the spawned task is dropped.
|
||||||
///
|
///
|
||||||
/// # Panic:
|
/// # Panics
|
||||||
///
|
/// Panics when there was no `NotifyOnDrop` instance on current thread.
|
||||||
/// When there was no `NotifyOnDrop` instance on current thread
|
|
||||||
pub(crate) fn is_dropped() -> bool {
|
pub(crate) fn is_dropped() -> bool {
|
||||||
NOTIFY_DROPPED.with(|bool| {
|
NOTIFY_DROPPED.with(|bool| {
|
||||||
bool.borrow()
|
bool.borrow()
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
use std::{fmt, io::Write, str::FromStr, time::SystemTime};
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use http::header::{HeaderValue, InvalidHeaderValue};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue,
|
||||||
|
helpers::MutWriter,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A timestamp with HTTP formatting and parsing.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct HttpDate(SystemTime);
|
||||||
|
|
||||||
|
impl FromStr for HttpDate {
|
||||||
|
type Err = ParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<HttpDate, ParseError> {
|
||||||
|
match httpdate::parse_http_date(s) {
|
||||||
|
Ok(sys_time) => Ok(HttpDate(sys_time)),
|
||||||
|
Err(_) => Err(ParseError::Header),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for HttpDate {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let date_str = httpdate::fmt_http_date(self.0);
|
||||||
|
f.write_str(&date_str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for HttpDate {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH);
|
||||||
|
let mut wrt = MutWriter(&mut buf);
|
||||||
|
|
||||||
|
// unwrap: date output is known to be well formed and of known length
|
||||||
|
write!(wrt, "{}", httpdate::fmt_http_date(self.0)).unwrap();
|
||||||
|
|
||||||
|
HeaderValue::from_maybe_shared(buf.split().freeze())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SystemTime> for HttpDate {
|
||||||
|
fn from(sys_time: SystemTime) -> HttpDate {
|
||||||
|
HttpDate(sys_time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HttpDate> for SystemTime {
|
||||||
|
fn from(HttpDate(sys_time): HttpDate) -> SystemTime {
|
||||||
|
sys_time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn date_header() {
|
||||||
|
macro_rules! assert_parsed_date {
|
||||||
|
($case:expr, $exp:expr) => {
|
||||||
|
assert_eq!($case.parse::<HttpDate>().unwrap(), $exp);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 784198117 = SystemTime::from(datetime!(1994-11-07 08:48:37).assume_utc()).duration_since(SystemTime::UNIX_EPOCH));
|
||||||
|
let nov_07 = HttpDate(SystemTime::UNIX_EPOCH + Duration::from_secs(784198117));
|
||||||
|
|
||||||
|
assert_parsed_date!("Mon, 07 Nov 1994 08:48:37 GMT", nov_07);
|
||||||
|
assert_parsed_date!("Monday, 07-Nov-94 08:48:37 GMT", nov_07);
|
||||||
|
assert_parsed_date!("Mon Nov 7 08:48:37 1994", nov_07);
|
||||||
|
|
||||||
|
assert!("this-is-no-date".parse::<HttpDate>().is_err());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,97 +0,0 @@
|
||||||
use std::{
|
|
||||||
fmt,
|
|
||||||
io::Write,
|
|
||||||
str::FromStr,
|
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
|
||||||
};
|
|
||||||
|
|
||||||
use bytes::buf::BufMut;
|
|
||||||
use bytes::BytesMut;
|
|
||||||
use http::header::{HeaderValue, InvalidHeaderValue};
|
|
||||||
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
|
|
||||||
|
|
||||||
use crate::error::ParseError;
|
|
||||||
use crate::header::IntoHeaderValue;
|
|
||||||
use crate::time_parser;
|
|
||||||
|
|
||||||
/// A timestamp with HTTP formatting and parsing.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct HttpDate(OffsetDateTime);
|
|
||||||
|
|
||||||
impl FromStr for HttpDate {
|
|
||||||
type Err = ParseError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<HttpDate, ParseError> {
|
|
||||||
match time_parser::parse_http_date(s) {
|
|
||||||
Some(t) => Ok(HttpDate(t.assume_utc())),
|
|
||||||
None => Err(ParseError::Header),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for HttpDate {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
fmt::Display::fmt(&self.0.format("%a, %d %b %Y %H:%M:%S GMT"), f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SystemTime> for HttpDate {
|
|
||||||
fn from(sys: SystemTime) -> HttpDate {
|
|
||||||
HttpDate(PrimitiveDateTime::from(sys).assume_utc())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for HttpDate {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
let mut wrt = BytesMut::with_capacity(29).writer();
|
|
||||||
write!(
|
|
||||||
wrt,
|
|
||||||
"{}",
|
|
||||||
self.0
|
|
||||||
.to_offset(UtcOffset::UTC)
|
|
||||||
.format("%a, %d %b %Y %H:%M:%S GMT")
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
HeaderValue::from_maybe_shared(wrt.get_mut().split().freeze())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<HttpDate> for SystemTime {
|
|
||||||
fn from(date: HttpDate) -> SystemTime {
|
|
||||||
let dt = date.0;
|
|
||||||
let epoch = OffsetDateTime::unix_epoch();
|
|
||||||
|
|
||||||
UNIX_EPOCH + (dt - epoch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::HttpDate;
|
|
||||||
use time::{date, time, PrimitiveDateTime};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_date() {
|
|
||||||
let nov_07 = HttpDate(
|
|
||||||
PrimitiveDateTime::new(date!(1994 - 11 - 07), time!(8:48:37)).assume_utc(),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
"Sun, 07 Nov 1994 08:48:37 GMT".parse::<HttpDate>().unwrap(),
|
|
||||||
nov_07
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
"Sunday, 07-Nov-94 08:48:37 GMT"
|
|
||||||
.parse::<HttpDate>()
|
|
||||||
.unwrap(),
|
|
||||||
nov_07
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
"Sun Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(),
|
|
||||||
nov_07
|
|
||||||
);
|
|
||||||
assert!("this-is-no-date".parse::<HttpDate>().is_err());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,12 +3,12 @@
|
||||||
mod charset;
|
mod charset;
|
||||||
mod content_encoding;
|
mod content_encoding;
|
||||||
mod extended;
|
mod extended;
|
||||||
mod httpdate;
|
mod http_date;
|
||||||
mod quality_item;
|
mod quality_item;
|
||||||
|
|
||||||
pub use self::charset::Charset;
|
pub use self::charset::Charset;
|
||||||
pub use self::content_encoding::ContentEncoding;
|
pub use self::content_encoding::ContentEncoding;
|
||||||
pub use self::extended::{parse_extended_value, ExtendedValue};
|
pub use self::extended::{parse_extended_value, ExtendedValue};
|
||||||
pub use self::httpdate::HttpDate;
|
pub use self::http_date::HttpDate;
|
||||||
pub use self::quality_item::{q, qitem, Quality, QualityItem};
|
pub use self::quality_item::{q, qitem, Quality, QualityItem};
|
||||||
pub use language_tags::LanguageTag;
|
pub use language_tags::LanguageTag;
|
||||||
|
|
|
@ -44,7 +44,6 @@ mod request;
|
||||||
mod response;
|
mod response;
|
||||||
mod response_builder;
|
mod response_builder;
|
||||||
mod service;
|
mod service;
|
||||||
mod time_parser;
|
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod h1;
|
pub mod h1;
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
use time::{Date, OffsetDateTime, PrimitiveDateTime};
|
|
||||||
|
|
||||||
/// Attempt to parse a `time` string as one of either RFC 1123, RFC 850, or asctime.
|
|
||||||
pub(crate) fn parse_http_date(time: &str) -> Option<PrimitiveDateTime> {
|
|
||||||
try_parse_rfc_1123(time)
|
|
||||||
.or_else(|| try_parse_rfc_850(time))
|
|
||||||
.or_else(|| try_parse_asctime(time))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to parse a `time` string as a RFC 1123 formatted date time string.
|
|
||||||
///
|
|
||||||
/// Eg: `Fri, 12 Feb 2021 00:14:29 GMT`
|
|
||||||
fn try_parse_rfc_1123(time: &str) -> Option<PrimitiveDateTime> {
|
|
||||||
time::parse(time, "%a, %d %b %Y %H:%M:%S").ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to parse a `time` string as a RFC 850 formatted date time string.
|
|
||||||
///
|
|
||||||
/// Eg: `Wednesday, 11-Jan-21 13:37:41 UTC`
|
|
||||||
fn try_parse_rfc_850(time: &str) -> Option<PrimitiveDateTime> {
|
|
||||||
let dt = PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S").ok()?;
|
|
||||||
|
|
||||||
// If the `time` string contains a two-digit year, then as per RFC 2616 § 19.3,
|
|
||||||
// we consider the year as part of this century if it's within the next 50 years,
|
|
||||||
// otherwise we consider as part of the previous century.
|
|
||||||
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
let century_start_year = (now.year() / 100) * 100;
|
|
||||||
let mut expanded_year = century_start_year + dt.year();
|
|
||||||
|
|
||||||
if expanded_year > now.year() + 50 {
|
|
||||||
expanded_year -= 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
let date = Date::try_from_ymd(expanded_year, dt.month(), dt.day()).ok()?;
|
|
||||||
Some(PrimitiveDateTime::new(date, dt.time()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to parse a `time` string using ANSI C's `asctime` format.
|
|
||||||
///
|
|
||||||
/// Eg: `Wed Feb 13 15:46:11 2013`
|
|
||||||
fn try_parse_asctime(time: &str) -> Option<PrimitiveDateTime> {
|
|
||||||
time::parse(time, "%a %b %_d %H:%M:%S %Y").ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use time::{date, time};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rfc_850_year_shift() {
|
|
||||||
let date = try_parse_rfc_850("Friday, 19-Nov-82 16:14:55 EST").unwrap();
|
|
||||||
assert_eq!(date, date!(1982 - 11 - 19).with_time(time!(16:14:55)));
|
|
||||||
|
|
||||||
let date = try_parse_rfc_850("Wednesday, 11-Jan-62 13:37:41 EST").unwrap();
|
|
||||||
assert_eq!(date, date!(2062 - 01 - 11).with_time(time!(13:37:41)));
|
|
||||||
|
|
||||||
let date = try_parse_rfc_850("Wednesday, 11-Jan-21 13:37:41 EST").unwrap();
|
|
||||||
assert_eq!(date, date!(2021 - 01 - 11).with_time(time!(13:37:41)));
|
|
||||||
|
|
||||||
let date = try_parse_rfc_850("Wednesday, 11-Jan-23 13:37:41 EST").unwrap();
|
|
||||||
assert_eq!(date, date!(2023 - 01 - 11).with_time(time!(13:37:41)));
|
|
||||||
|
|
||||||
let date = try_parse_rfc_850("Wednesday, 11-Jan-99 13:37:41 EST").unwrap();
|
|
||||||
assert_eq!(date, date!(1999 - 01 - 11).with_time(time!(13:37:41)));
|
|
||||||
|
|
||||||
let date = try_parse_rfc_850("Wednesday, 11-Jan-00 13:37:41 EST").unwrap();
|
|
||||||
assert_eq!(date, date!(2000 - 01 - 11).with_time(time!(13:37:41)));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -183,6 +183,7 @@ async fn test_chunked_payload() {
|
||||||
Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(),
|
Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(),
|
||||||
None => panic!("Failed to find size in HTTP Response: {}", data),
|
None => panic!("Failed to find size in HTTP Response: {}", data),
|
||||||
};
|
};
|
||||||
|
|
||||||
size
|
size
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.0-beta.6 - 2021-09-09
|
||||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-multipart"
|
name = "actix-multipart"
|
||||||
version = "0.4.0-beta.5"
|
version = "0.4.0-beta.6"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Multipart form support for Actix Web"
|
description = "Multipart form support for Actix Web"
|
||||||
readme = "README.md"
|
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
repository = "https://github.com/actix/actix-web.git"
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
documentation = "https://docs.rs/actix-multipart"
|
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -16,7 +14,7 @@ name = "actix_multipart"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.8", default-features = false }
|
actix-web = { version = "4.0.0-beta.9", default-features = false }
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
@ -31,6 +29,6 @@ twoway = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-http = "3.0.0-beta.8"
|
actix-http = "3.0.0-beta.10"
|
||||||
tokio = { version = "1", features = ["sync"] }
|
tokio = { version = "1", features = ["sync"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
> Multipart form support for Actix Web.
|
> Multipart form support for Actix Web.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-multipart)
|
[](https://crates.io/crates/actix-multipart)
|
||||||
[](https://docs.rs/actix-multipart/0.4.0-beta.5)
|
[](https://docs.rs/actix-multipart/0.4.0-beta.6)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.5)
|
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.6)
|
||||||
[](https://crates.io/crates/actix-multipart)
|
[](https://crates.io/crates/actix-multipart)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,6 @@ use crate::server::Multipart;
|
||||||
impl FromRequest for Multipart {
|
impl FromRequest for Multipart {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Multipart, Error>>;
|
type Future = Ready<Result<Multipart, Error>>;
|
||||||
type Config = ();
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0-beta.2 - 2021-09-09
|
||||||
* Introduce `ResourceDef::join`. [#380]
|
* Introduce `ResourceDef::join`. [#380]
|
||||||
* Disallow prefix routes with tail segments. [#379]
|
* Disallow prefix routes with tail segments. [#379]
|
||||||
* Enforce path separators on dynamic prefixes. [#378]
|
* Enforce path separators on dynamic prefixes. [#378]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.5.0-beta.1"
|
version = "0.5.0-beta.2"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||||
|
@ -8,7 +8,7 @@ authors = [
|
||||||
]
|
]
|
||||||
description = "Resource path matching and router"
|
description = "Resource path matching and router"
|
||||||
keywords = ["actix", "router", "routing"]
|
keywords = ["actix", "router", "routing"]
|
||||||
repository = "https://github.com/actix/actix-net.git"
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.1.0-beta.4 - 2021-09-09
|
||||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-test"
|
name = "actix-test"
|
||||||
version = "0.1.0-beta.3"
|
version = "0.1.0-beta.4"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
|
@ -20,13 +20,13 @@ openssl = ["tls-openssl", "actix-http/openssl"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.4.0"
|
actix-codec = "0.4.0"
|
||||||
actix-http = "3.0.0-beta.8"
|
actix-http = "3.0.0-beta.10"
|
||||||
actix-http-test = { version = "3.0.0-beta.4", features = [] }
|
actix-http-test = "3.0.0-beta.5"
|
||||||
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.8", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["cookies"] }
|
||||||
actix-rt = "2.1"
|
actix-rt = "2.1"
|
||||||
awc = { version = "3.0.0-beta.7", default-features = false, features = ["cookies"] }
|
awc = { version = "3.0.0-beta.8", 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 = [] }
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0-beta.7 - 2021-09-09
|
||||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-web-actors"
|
name = "actix-web-actors"
|
||||||
version = "4.0.0-beta.6"
|
version = "4.0.0-beta.7"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix actors support for Actix Web"
|
description = "Actix actors support for Actix Web"
|
||||||
keywords = ["actix", "http", "web", "framework", "async"]
|
keywords = ["actix", "http", "web", "framework", "async"]
|
||||||
|
@ -16,8 +16,8 @@ path = "src/lib.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = { version = "0.12.0", default-features = false }
|
actix = { version = "0.12.0", default-features = false }
|
||||||
actix-codec = "0.4.0"
|
actix-codec = "0.4.0"
|
||||||
actix-http = "3.0.0-beta.8"
|
actix-http = "3.0.0-beta.10"
|
||||||
actix-web = { version = "4.0.0-beta.8", default-features = false }
|
actix-web = { version = "4.0.0-beta.9", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
|
@ -29,6 +29,6 @@ tokio = { version = "1", features = ["sync"] }
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.3"
|
actix-test = "0.1.0-beta.3"
|
||||||
|
|
||||||
awc = { version = "3.0.0-beta.7", default-features = false }
|
awc = { version = "3.0.0-beta.8", default-features = false }
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
> Actix actors support for Actix Web.
|
> Actix actors support for Actix Web.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web-actors)
|
[](https://crates.io/crates/actix-web-actors)
|
||||||
[](https://docs.rs/actix-web-actors/4.0.0-beta.6)
|
[](https://docs.rs/actix-web-actors/4.0.0-beta.7)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-web-actors/4.0.0-beta.6)
|
[](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7)
|
||||||
[](https://crates.io/crates/actix-web-actors)
|
[](https://crates.io/crates/actix-web-actors)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0-beta.4 - 2021-09-09
|
||||||
* In routing macros, paths are now validated at compile time. [#2350]
|
* In routing macros, paths are now validated at compile time. [#2350]
|
||||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-web-codegen"
|
name = "actix-web-codegen"
|
||||||
version = "0.5.0-beta.3"
|
version = "0.5.0-beta.4"
|
||||||
description = "Routing and runtime macros for Actix Web"
|
description = "Routing and runtime macros for Actix Web"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
|
@ -17,13 +17,13 @@ proc-macro = true
|
||||||
quote = "1"
|
quote = "1"
|
||||||
syn = { version = "1", features = ["full", "parsing"] }
|
syn = { version = "1", features = ["full", "parsing"] }
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
actix-router = "0.5.0-beta.1"
|
actix-router = "0.5.0-beta.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.3"
|
actix-test = "0.1.0-beta.3"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = "4.0.0-beta.8"
|
actix-web = "4.0.0-beta.9"
|
||||||
|
|
||||||
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,11 +3,11 @@
|
||||||
> Routing and runtime macros for Actix Web.
|
> Routing and runtime macros for Actix Web.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web-codegen)
|
[](https://crates.io/crates/actix-web-codegen)
|
||||||
[](https://docs.rs/actix-web-codegen/0.5.0-beta.3)
|
[](https://docs.rs/actix-web-codegen/0.5.0-beta.4)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3)
|
[](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.4)
|
||||||
[](https://crates.io/crates/actix-web-codegen)
|
[](https://crates.io/crates/actix-web-codegen)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,13 @@
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.8 - 2021-09-09
|
||||||
|
### Changed
|
||||||
|
* Send headers within the redirect requests. [#2310]
|
||||||
|
|
||||||
|
[#2310]: https://github.com/actix/actix-web/pull/2310
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.7 - 2021-06-26
|
## 3.0.0-beta.7 - 2021-06-26
|
||||||
### Changed
|
### Changed
|
||||||
* Change compression algorithm features flags. [#2250]
|
* Change compression algorithm features flags. [#2250]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "awc"
|
name = "awc"
|
||||||
version = "3.0.0-beta.7"
|
version = "3.0.0-beta.8"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"fakeshadow <24548779@qq.com>",
|
"fakeshadow <24548779@qq.com>",
|
||||||
|
@ -55,7 +55,7 @@ __compress = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.4.0"
|
actix-codec = "0.4.0"
|
||||||
actix-service = "2.0.0"
|
actix-service = "2.0.0"
|
||||||
actix-http = "3.0.0-beta.8"
|
actix-http = "3.0.0-beta.10"
|
||||||
actix-rt = { version = "2.1", default-features = false }
|
actix-rt = { version = "2.1", default-features = false }
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
|
@ -77,9 +77,9 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||||
tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
|
tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.8", features = ["openssl"] }
|
actix-web = { version = "4.0.0-beta.9", features = ["openssl"] }
|
||||||
actix-http = { version = "3.0.0-beta.8", features = ["openssl"] }
|
actix-http = { version = "3.0.0-beta.10", features = ["openssl"] }
|
||||||
actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] }
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-server = "2.0.0-beta.3"
|
actix-server = "2.0.0-beta.3"
|
||||||
actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] }
|
actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] }
|
||||||
|
|
|
@ -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.7)
|
[](https://docs.rs/awc/3.0.0-beta.8)
|
||||||

|

|
||||||
[](https://deps.rs/crate/awc/3.0.0-beta.7)
|
[](https://deps.rs/crate/awc/3.0.0-beta.8)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
|
@ -85,10 +85,12 @@ where
|
||||||
let max_redirect_times = self.max_redirect_times;
|
let max_redirect_times = self.max_redirect_times;
|
||||||
|
|
||||||
// backup the uri and method for reuse schema and authority.
|
// backup the uri and method for reuse schema and authority.
|
||||||
let (uri, method) = match head {
|
let (uri, method, headers) = match head {
|
||||||
RequestHeadType::Owned(ref head) => (head.uri.clone(), head.method.clone()),
|
RequestHeadType::Owned(ref head) => {
|
||||||
|
(head.uri.clone(), head.method.clone(), head.headers.clone())
|
||||||
|
}
|
||||||
RequestHeadType::Rc(ref head, ..) => {
|
RequestHeadType::Rc(ref head, ..) => {
|
||||||
(head.uri.clone(), head.method.clone())
|
(head.uri.clone(), head.method.clone(), head.headers.clone())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -104,6 +106,7 @@ where
|
||||||
max_redirect_times,
|
max_redirect_times,
|
||||||
uri: Some(uri),
|
uri: Some(uri),
|
||||||
method: Some(method),
|
method: Some(method),
|
||||||
|
headers: Some(headers),
|
||||||
body: body_opt,
|
body: body_opt,
|
||||||
addr,
|
addr,
|
||||||
connector: Some(connector),
|
connector: Some(connector),
|
||||||
|
@ -127,9 +130,10 @@ pin_project_lite::pin_project! {
|
||||||
max_redirect_times: u8,
|
max_redirect_times: u8,
|
||||||
uri: Option<Uri>,
|
uri: Option<Uri>,
|
||||||
method: Option<Method>,
|
method: Option<Method>,
|
||||||
|
headers: Option<header::HeaderMap>,
|
||||||
body: Option<Bytes>,
|
body: Option<Bytes>,
|
||||||
addr: Option<SocketAddr>,
|
addr: Option<SocketAddr>,
|
||||||
connector: Option<Rc<S>>
|
connector: Option<Rc<S>>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,6 +152,7 @@ where
|
||||||
max_redirect_times,
|
max_redirect_times,
|
||||||
uri,
|
uri,
|
||||||
method,
|
method,
|
||||||
|
headers,
|
||||||
body,
|
body,
|
||||||
addr,
|
addr,
|
||||||
connector,
|
connector,
|
||||||
|
@ -156,79 +161,60 @@ where
|
||||||
StatusCode::MOVED_PERMANENTLY
|
StatusCode::MOVED_PERMANENTLY
|
||||||
| StatusCode::FOUND
|
| StatusCode::FOUND
|
||||||
| StatusCode::SEE_OTHER
|
| StatusCode::SEE_OTHER
|
||||||
|
| StatusCode::TEMPORARY_REDIRECT
|
||||||
|
| StatusCode::PERMANENT_REDIRECT
|
||||||
if *max_redirect_times > 0 =>
|
if *max_redirect_times > 0 =>
|
||||||
{
|
{
|
||||||
let org_uri = uri.take().unwrap();
|
let is_redirect = res.head().status == StatusCode::TEMPORARY_REDIRECT
|
||||||
// rebuild uri from the location header value.
|
|| res.head().status == StatusCode::PERMANENT_REDIRECT;
|
||||||
let uri = rebuild_uri(&res, org_uri)?;
|
|
||||||
|
|
||||||
// reset method
|
let prev_uri = uri.take().unwrap();
|
||||||
let method = method.take().unwrap();
|
|
||||||
let method = match method {
|
// rebuild uri from the location header value.
|
||||||
Method::GET | Method::HEAD => method,
|
let next_uri = build_next_uri(&res, &prev_uri)?;
|
||||||
_ => Method::GET,
|
|
||||||
};
|
|
||||||
|
|
||||||
// take ownership of states that could be reused
|
// take ownership of states that could be reused
|
||||||
let addr = addr.take();
|
let addr = addr.take();
|
||||||
let connector = connector.take();
|
let connector = connector.take();
|
||||||
let mut max_redirect_times = *max_redirect_times;
|
|
||||||
|
|
||||||
// use a new request head.
|
// reset method
|
||||||
let mut head = RequestHead::default();
|
let method = if is_redirect {
|
||||||
head.uri = uri.clone();
|
method.take().unwrap()
|
||||||
head.method = method.clone();
|
} else {
|
||||||
|
let method = method.take().unwrap();
|
||||||
let head = RequestHeadType::Owned(head);
|
match method {
|
||||||
|
Method::GET | Method::HEAD => method,
|
||||||
max_redirect_times -= 1;
|
_ => Method::GET,
|
||||||
|
}
|
||||||
let fut = connector
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
// remove body
|
|
||||||
.call(ConnectRequest::Client(head, Body::None, addr));
|
|
||||||
|
|
||||||
self.set(RedirectServiceFuture::Client {
|
|
||||||
fut,
|
|
||||||
max_redirect_times,
|
|
||||||
uri: Some(uri),
|
|
||||||
method: Some(method),
|
|
||||||
// body is dropped on 301,302,303
|
|
||||||
body: None,
|
|
||||||
addr,
|
|
||||||
connector,
|
|
||||||
});
|
|
||||||
|
|
||||||
self.poll(cx)
|
|
||||||
}
|
|
||||||
StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT
|
|
||||||
if *max_redirect_times > 0 =>
|
|
||||||
{
|
|
||||||
let org_uri = uri.take().unwrap();
|
|
||||||
// rebuild uri from the location header value.
|
|
||||||
let uri = rebuild_uri(&res, org_uri)?;
|
|
||||||
|
|
||||||
// try to reuse body
|
|
||||||
let body = body.take();
|
|
||||||
let body_new = match body {
|
|
||||||
Some(ref bytes) => Body::Bytes(bytes.clone()),
|
|
||||||
// TODO: should this be Body::Empty or Body::None.
|
|
||||||
_ => Body::Empty,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let addr = addr.take();
|
let mut body = body.take();
|
||||||
let method = method.take().unwrap();
|
let body_new = if is_redirect {
|
||||||
let connector = connector.take();
|
// try to reuse body
|
||||||
let mut max_redirect_times = *max_redirect_times;
|
match body {
|
||||||
|
Some(ref bytes) => Body::Bytes(bytes.clone()),
|
||||||
|
// TODO: should this be Body::Empty or Body::None.
|
||||||
|
_ => Body::Empty,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
body = None;
|
||||||
|
// remove body
|
||||||
|
Body::None
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut headers = headers.take().unwrap();
|
||||||
|
|
||||||
|
remove_sensitive_headers(&mut headers, &prev_uri, &next_uri);
|
||||||
|
|
||||||
// use a new request head.
|
// use a new request head.
|
||||||
let mut head = RequestHead::default();
|
let mut head = RequestHead::default();
|
||||||
head.uri = uri.clone();
|
head.uri = next_uri.clone();
|
||||||
head.method = method.clone();
|
head.method = method.clone();
|
||||||
|
head.headers = headers.clone();
|
||||||
|
|
||||||
let head = RequestHeadType::Owned(head);
|
let head = RequestHeadType::Owned(head);
|
||||||
|
|
||||||
|
let mut max_redirect_times = *max_redirect_times;
|
||||||
max_redirect_times -= 1;
|
max_redirect_times -= 1;
|
||||||
|
|
||||||
let fut = connector
|
let fut = connector
|
||||||
|
@ -239,8 +225,9 @@ where
|
||||||
self.set(RedirectServiceFuture::Client {
|
self.set(RedirectServiceFuture::Client {
|
||||||
fut,
|
fut,
|
||||||
max_redirect_times,
|
max_redirect_times,
|
||||||
uri: Some(uri),
|
uri: Some(next_uri),
|
||||||
method: Some(method),
|
method: Some(method),
|
||||||
|
headers: Some(headers),
|
||||||
body,
|
body,
|
||||||
addr,
|
addr,
|
||||||
connector,
|
connector,
|
||||||
|
@ -256,7 +243,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rebuild_uri(res: &ClientResponse, org_uri: Uri) -> Result<Uri, SendRequestError> {
|
fn build_next_uri(res: &ClientResponse, prev_uri: &Uri) -> Result<Uri, SendRequestError> {
|
||||||
let uri = res
|
let uri = res
|
||||||
.headers()
|
.headers()
|
||||||
.get(header::LOCATION)
|
.get(header::LOCATION)
|
||||||
|
@ -266,8 +253,8 @@ fn rebuild_uri(res: &ClientResponse, org_uri: Uri) -> Result<Uri, SendRequestErr
|
||||||
.map_err(|e| SendRequestError::Url(InvalidUrl::HttpError(e.into())))?;
|
.map_err(|e| SendRequestError::Url(InvalidUrl::HttpError(e.into())))?;
|
||||||
if uri.scheme().is_none() || uri.authority().is_none() {
|
if uri.scheme().is_none() || uri.authority().is_none() {
|
||||||
let uri = Uri::builder()
|
let uri = Uri::builder()
|
||||||
.scheme(org_uri.scheme().cloned().unwrap())
|
.scheme(prev_uri.scheme().cloned().unwrap())
|
||||||
.authority(org_uri.authority().cloned().unwrap())
|
.authority(prev_uri.authority().cloned().unwrap())
|
||||||
.path_and_query(value.as_bytes())
|
.path_and_query(value.as_bytes())
|
||||||
.build()?;
|
.build()?;
|
||||||
Ok::<_, SendRequestError>(uri)
|
Ok::<_, SendRequestError>(uri)
|
||||||
|
@ -281,12 +268,25 @@ fn rebuild_uri(res: &ClientResponse, org_uri: Uri) -> Result<Uri, SendRequestErr
|
||||||
Ok(uri)
|
Ok(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_sensitive_headers(headers: &mut header::HeaderMap, prev_uri: &Uri, next_uri: &Uri) {
|
||||||
|
if next_uri.host() != prev_uri.host()
|
||||||
|
|| next_uri.port() != prev_uri.port()
|
||||||
|
|| next_uri.scheme() != prev_uri.scheme()
|
||||||
|
{
|
||||||
|
headers.remove(header::COOKIE);
|
||||||
|
headers.remove(header::AUTHORIZATION);
|
||||||
|
headers.remove(header::PROXY_AUTHORIZATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use actix_web::{web, App, Error, HttpResponse};
|
use actix_web::{web, App, Error, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::http::HeaderValue;
|
||||||
use crate::ClientBuilder;
|
use crate::ClientBuilder;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_basic_redirect() {
|
async fn test_basic_redirect() {
|
||||||
|
@ -347,4 +347,239 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(res.status().as_u16(), 302);
|
assert_eq!(res.status().as_u16(), 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_redirect_status_kind_307_308() {
|
||||||
|
let srv = actix_test::start(|| {
|
||||||
|
async fn root() -> HttpResponse {
|
||||||
|
HttpResponse::TemporaryRedirect()
|
||||||
|
.append_header(("location", "/test"))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test(req: HttpRequest, body: Bytes) -> HttpResponse {
|
||||||
|
if req.method() == Method::POST && !body.is_empty() {
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
} else {
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.service(web::resource("/").route(web::to(root)))
|
||||||
|
.service(web::resource("/test").route(web::to(test)))
|
||||||
|
});
|
||||||
|
|
||||||
|
let res = srv.post("/").send_body("Hello").await.unwrap();
|
||||||
|
assert_eq!(res.status().as_u16(), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_redirect_status_kind_301_302_303() {
|
||||||
|
let srv = actix_test::start(|| {
|
||||||
|
async fn root() -> HttpResponse {
|
||||||
|
HttpResponse::Found()
|
||||||
|
.append_header(("location", "/test"))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test(req: HttpRequest, body: Bytes) -> HttpResponse {
|
||||||
|
if (req.method() == Method::GET || req.method() == Method::HEAD)
|
||||||
|
&& body.is_empty()
|
||||||
|
{
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
} else {
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.service(web::resource("/").route(web::to(root)))
|
||||||
|
.service(web::resource("/test").route(web::to(test)))
|
||||||
|
});
|
||||||
|
|
||||||
|
let res = srv.post("/").send_body("Hello").await.unwrap();
|
||||||
|
assert_eq!(res.status().as_u16(), 200);
|
||||||
|
|
||||||
|
let res = srv.post("/").send().await.unwrap();
|
||||||
|
assert_eq!(res.status().as_u16(), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_redirect_headers() {
|
||||||
|
let srv = actix_test::start(|| {
|
||||||
|
async fn root(req: HttpRequest) -> HttpResponse {
|
||||||
|
if req
|
||||||
|
.headers()
|
||||||
|
.get("custom")
|
||||||
|
.unwrap_or(&HeaderValue::from_str("").unwrap())
|
||||||
|
== "value"
|
||||||
|
{
|
||||||
|
HttpResponse::Found()
|
||||||
|
.append_header(("location", "/test"))
|
||||||
|
.finish()
|
||||||
|
} else {
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test(req: HttpRequest) -> HttpResponse {
|
||||||
|
if req
|
||||||
|
.headers()
|
||||||
|
.get("custom")
|
||||||
|
.unwrap_or(&HeaderValue::from_str("").unwrap())
|
||||||
|
== "value"
|
||||||
|
{
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
} else {
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.service(web::resource("/").route(web::to(root)))
|
||||||
|
.service(web::resource("/test").route(web::to(test)))
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = ClientBuilder::new()
|
||||||
|
.header("custom", "value")
|
||||||
|
.disable_redirects()
|
||||||
|
.finish();
|
||||||
|
let res = client.get(srv.url("/")).send().await.unwrap();
|
||||||
|
assert_eq!(res.status().as_u16(), 302);
|
||||||
|
|
||||||
|
let client = ClientBuilder::new().header("custom", "value").finish();
|
||||||
|
let res = client.get(srv.url("/")).send().await.unwrap();
|
||||||
|
assert_eq!(res.status().as_u16(), 200);
|
||||||
|
|
||||||
|
let client = ClientBuilder::new().finish();
|
||||||
|
let res = client
|
||||||
|
.get(srv.url("/"))
|
||||||
|
.insert_header(("custom", "value"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(res.status().as_u16(), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_redirect_cross_origin_headers() {
|
||||||
|
// defining two services to have two different origins
|
||||||
|
let srv2 = actix_test::start(|| {
|
||||||
|
async fn root(req: HttpRequest) -> HttpResponse {
|
||||||
|
if req.headers().get(header::AUTHORIZATION).is_none() {
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
} else {
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App::new().service(web::resource("/").route(web::to(root)))
|
||||||
|
});
|
||||||
|
let srv2_port: u16 = srv2.addr().port();
|
||||||
|
|
||||||
|
let srv1 = actix_test::start(move || {
|
||||||
|
async fn root(req: HttpRequest) -> HttpResponse {
|
||||||
|
let port = *req.app_data::<u16>().unwrap();
|
||||||
|
if req.headers().get(header::AUTHORIZATION).is_some() {
|
||||||
|
HttpResponse::Found()
|
||||||
|
.append_header((
|
||||||
|
"location",
|
||||||
|
format!("http://localhost:{}/", port).as_str(),
|
||||||
|
))
|
||||||
|
.finish()
|
||||||
|
} else {
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test1(req: HttpRequest) -> HttpResponse {
|
||||||
|
if req.headers().get(header::AUTHORIZATION).is_some() {
|
||||||
|
HttpResponse::Found()
|
||||||
|
.append_header(("location", "/test2"))
|
||||||
|
.finish()
|
||||||
|
} else {
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test2(req: HttpRequest) -> HttpResponse {
|
||||||
|
if req.headers().get(header::AUTHORIZATION).is_some() {
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
} else {
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.app_data(srv2_port)
|
||||||
|
.service(web::resource("/").route(web::to(root)))
|
||||||
|
.service(web::resource("/test1").route(web::to(test1)))
|
||||||
|
.service(web::resource("/test2").route(web::to(test2)))
|
||||||
|
});
|
||||||
|
|
||||||
|
// send a request to different origins, http://srv1/ then http://srv2/. So it should remove the header
|
||||||
|
let client = ClientBuilder::new()
|
||||||
|
.header(header::AUTHORIZATION, "auth_key_value")
|
||||||
|
.finish();
|
||||||
|
let res = client.get(srv1.url("/")).send().await.unwrap();
|
||||||
|
assert_eq!(res.status().as_u16(), 200);
|
||||||
|
|
||||||
|
// send a request to same origin, http://srv1/test1 then http://srv1/test2. So it should NOT remove any header
|
||||||
|
let res = client.get(srv1.url("/test1")).send().await.unwrap();
|
||||||
|
assert_eq!(res.status().as_u16(), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_remove_sensitive_headers() {
|
||||||
|
fn gen_headers() -> header::HeaderMap {
|
||||||
|
let mut headers = header::HeaderMap::new();
|
||||||
|
headers.insert(header::USER_AGENT, HeaderValue::from_str("value").unwrap());
|
||||||
|
headers.insert(
|
||||||
|
header::AUTHORIZATION,
|
||||||
|
HeaderValue::from_str("value").unwrap(),
|
||||||
|
);
|
||||||
|
headers.insert(
|
||||||
|
header::PROXY_AUTHORIZATION,
|
||||||
|
HeaderValue::from_str("value").unwrap(),
|
||||||
|
);
|
||||||
|
headers.insert(header::COOKIE, HeaderValue::from_str("value").unwrap());
|
||||||
|
headers
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same origin
|
||||||
|
let prev_uri = Uri::from_str("https://host/path1").unwrap();
|
||||||
|
let next_uri = Uri::from_str("https://host/path2").unwrap();
|
||||||
|
let mut headers = gen_headers();
|
||||||
|
remove_sensitive_headers(&mut headers, &prev_uri, &next_uri);
|
||||||
|
assert_eq!(headers.len(), 4);
|
||||||
|
|
||||||
|
// different schema
|
||||||
|
let prev_uri = Uri::from_str("http://host/").unwrap();
|
||||||
|
let next_uri = Uri::from_str("https://host/").unwrap();
|
||||||
|
let mut headers = gen_headers();
|
||||||
|
remove_sensitive_headers(&mut headers, &prev_uri, &next_uri);
|
||||||
|
assert_eq!(headers.len(), 1);
|
||||||
|
|
||||||
|
// different host
|
||||||
|
let prev_uri = Uri::from_str("https://host1/").unwrap();
|
||||||
|
let next_uri = Uri::from_str("https://host2/").unwrap();
|
||||||
|
let mut headers = gen_headers();
|
||||||
|
remove_sensitive_headers(&mut headers, &prev_uri, &next_uri);
|
||||||
|
assert_eq!(headers.len(), 1);
|
||||||
|
|
||||||
|
// different port
|
||||||
|
let prev_uri = Uri::from_str("https://host:12/").unwrap();
|
||||||
|
let next_uri = Uri::from_str("https://host:23/").unwrap();
|
||||||
|
let mut headers = gen_headers();
|
||||||
|
remove_sensitive_headers(&mut headers, &prev_uri, &next_uri);
|
||||||
|
assert_eq!(headers.len(), 1);
|
||||||
|
|
||||||
|
// different everything!
|
||||||
|
let prev_uri = Uri::from_str("http://host1:12/path1").unwrap();
|
||||||
|
let next_uri = Uri::from_str("https://host2:23/path2").unwrap();
|
||||||
|
let mut headers = gen_headers();
|
||||||
|
remove_sensitive_headers(&mut headers, &prev_uri, &next_uri);
|
||||||
|
assert_eq!(headers.len(), 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,6 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ?Sized + 'static> FromRequest for Data<T> {
|
impl<T: ?Sized + 'static> FromRequest for Data<T> {
|
||||||
type Config = ();
|
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Self, Error>>;
|
type Future = Ready<Result<Self, Error>>;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, ResponseBody, S
|
||||||
|
|
||||||
#[cfg(feature = "__compress")]
|
#[cfg(feature = "__compress")]
|
||||||
pub use actix_http::encoding::Decoder as Decompress;
|
pub use actix_http::encoding::Decoder as Decompress;
|
||||||
pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead};
|
pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead};
|
||||||
pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
|
pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
|
||||||
pub use actix_server::Server;
|
pub use actix_server::Server;
|
||||||
pub use actix_service::{
|
pub use actix_service::{
|
||||||
|
@ -26,7 +26,7 @@ pub use actix_service::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::http::header::ContentEncoding;
|
use crate::http::header::ContentEncoding;
|
||||||
use actix_http::{Response, ResponseBuilder};
|
use actix_http::ResponseBuilder;
|
||||||
|
|
||||||
use actix_router::Patterns;
|
use actix_router::Patterns;
|
||||||
|
|
||||||
|
|
|
@ -13,13 +13,52 @@ use futures_core::ready;
|
||||||
|
|
||||||
use crate::{dev::Payload, Error, HttpRequest};
|
use crate::{dev::Payload, Error, HttpRequest};
|
||||||
|
|
||||||
/// Trait implemented by types that can be extracted from request.
|
/// A type that implements [`FromRequest`] is called an **extractor** and can extract data from
|
||||||
|
/// the request. Some types that implement this trait are: [`Json`], [`Header`], and [`Path`].
|
||||||
///
|
///
|
||||||
/// Types that implement this trait can be used with `Route` handlers.
|
/// # Configuration
|
||||||
|
/// An extractor can be customized by injecting the corresponding configuration with one of:
|
||||||
|
///
|
||||||
|
/// - [`App::app_data()`][crate::App::app_data]
|
||||||
|
/// - [`Scope::app_data()`][crate::Scope::app_data]
|
||||||
|
/// - [`Resource::app_data()`][crate::Resource::app_data]
|
||||||
|
///
|
||||||
|
/// Here are some built-in extractors and their corresponding configuration.
|
||||||
|
/// Please refer to the respective documentation for details.
|
||||||
|
///
|
||||||
|
/// | Extractor | Configuration |
|
||||||
|
/// |-------------|-------------------|
|
||||||
|
/// | [`Header`] | _None_ |
|
||||||
|
/// | [`Path`] | [`PathConfig`] |
|
||||||
|
/// | [`Json`] | [`JsonConfig`] |
|
||||||
|
/// | [`Form`] | [`FormConfig`] |
|
||||||
|
/// | [`Query`] | [`QueryConfig`] |
|
||||||
|
/// | [`Bytes`] | [`PayloadConfig`] |
|
||||||
|
/// | [`String`] | [`PayloadConfig`] |
|
||||||
|
/// | [`Payload`] | [`PayloadConfig`] |
|
||||||
|
///
|
||||||
|
/// # Implementing An Extractor
|
||||||
|
/// To reduce duplicate code in handlers where extracting certain parts of a request has a common
|
||||||
|
/// structure, you can implement `FromRequest` for your own types.
|
||||||
|
///
|
||||||
|
/// Note that the request payload can only be consumed by one extractor.
|
||||||
|
///
|
||||||
|
/// [`Header`]: crate::web::Header
|
||||||
|
/// [`Json`]: crate::web::Json
|
||||||
|
/// [`JsonConfig`]: crate::web::JsonConfig
|
||||||
|
/// [`Form`]: crate::web::Form
|
||||||
|
/// [`FormConfig`]: crate::web::FormConfig
|
||||||
|
/// [`Path`]: crate::web::Path
|
||||||
|
/// [`PathConfig`]: crate::web::PathConfig
|
||||||
|
/// [`Query`]: crate::web::Query
|
||||||
|
/// [`QueryConfig`]: crate::web::QueryConfig
|
||||||
|
/// [`Payload`]: crate::web::Payload
|
||||||
|
/// [`PayloadConfig`]: crate::web::PayloadConfig
|
||||||
|
/// [`String`]: FromRequest#impl-FromRequest-for-String
|
||||||
|
/// [`Bytes`]: crate::web::Bytes#impl-FromRequest
|
||||||
|
/// [`Either`]: crate::web::Either
|
||||||
|
#[doc(alias = "extract", alias = "extractor")]
|
||||||
pub trait FromRequest: Sized {
|
pub trait FromRequest: Sized {
|
||||||
/// Configuration for this extractor.
|
|
||||||
type Config: Default + 'static;
|
|
||||||
|
|
||||||
/// The associated error which can be returned.
|
/// The associated error which can be returned.
|
||||||
type Error: Into<Error>;
|
type Error: Into<Error>;
|
||||||
|
|
||||||
|
@ -35,14 +74,6 @@ pub trait FromRequest: Sized {
|
||||||
fn extract(req: &HttpRequest) -> Self::Future {
|
fn extract(req: &HttpRequest) -> Self::Future {
|
||||||
Self::from_request(req, &mut Payload::None)
|
Self::from_request(req, &mut Payload::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create and configure config instance.
|
|
||||||
fn configure<F>(f: F) -> Self::Config
|
|
||||||
where
|
|
||||||
F: FnOnce(Self::Config) -> Self::Config,
|
|
||||||
{
|
|
||||||
f(Self::Config::default())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optionally extract a field from the request
|
/// Optionally extract a field from the request
|
||||||
|
@ -65,7 +96,6 @@ pub trait FromRequest: Sized {
|
||||||
/// impl FromRequest for Thing {
|
/// impl FromRequest for Thing {
|
||||||
/// type Error = Error;
|
/// type Error = Error;
|
||||||
/// type Future = Ready<Result<Self, Self::Error>>;
|
/// type Future = Ready<Result<Self, Self::Error>>;
|
||||||
/// type Config = ();
|
|
||||||
///
|
///
|
||||||
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||||
/// if rand::random() {
|
/// if rand::random() {
|
||||||
|
@ -100,7 +130,6 @@ where
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = FromRequestOptFuture<T::Future>;
|
type Future = FromRequestOptFuture<T::Future>;
|
||||||
type Config = T::Config;
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||||
|
@ -156,7 +185,6 @@ where
|
||||||
/// impl FromRequest for Thing {
|
/// impl FromRequest for Thing {
|
||||||
/// type Error = Error;
|
/// type Error = Error;
|
||||||
/// type Future = Ready<Result<Thing, Error>>;
|
/// type Future = Ready<Result<Thing, Error>>;
|
||||||
/// type Config = ();
|
|
||||||
///
|
///
|
||||||
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||||
/// if rand::random() {
|
/// if rand::random() {
|
||||||
|
@ -189,7 +217,6 @@ where
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = FromRequestResFuture<T::Future>;
|
type Future = FromRequestResFuture<T::Future>;
|
||||||
type Config = T::Config;
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||||
|
@ -233,7 +260,6 @@ where
|
||||||
impl FromRequest for Uri {
|
impl FromRequest for Uri {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
type Future = Ready<Result<Self, Self::Error>>;
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
type Config = ();
|
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
ok(req.uri().clone())
|
ok(req.uri().clone())
|
||||||
|
@ -255,7 +281,6 @@ impl FromRequest for Uri {
|
||||||
impl FromRequest for Method {
|
impl FromRequest for Method {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
type Future = Ready<Result<Self, Self::Error>>;
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
type Config = ();
|
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
ok(req.method().clone())
|
ok(req.method().clone())
|
||||||
|
@ -266,7 +291,6 @@ impl FromRequest for Method {
|
||||||
impl FromRequest for () {
|
impl FromRequest for () {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
type Future = Ready<Result<Self, Self::Error>>;
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
type Config = ();
|
|
||||||
|
|
||||||
fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
ok(())
|
ok(())
|
||||||
|
@ -306,7 +330,6 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = $fut_type<$($T),+>;
|
type Future = $fut_type<$($T),+>;
|
||||||
type Config = ($($T::Config),+);
|
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||||
$fut_type {
|
$fut_type {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
//! # References
|
//! # References
|
||||||
//!
|
//!
|
||||||
//! "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt
|
//! "The Content-Disposition Header Field" <https://www.ietf.org/rfc/rfc2183.txt>
|
||||||
//! "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt
|
//! "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" <https://www.ietf.org/rfc/rfc6266.txt>
|
||||||
//! "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt
|
//! "Returning Values from Forms: multipart/form-data" <https://www.ietf.org/rfc/rfc7578.txt>
|
||||||
//! Browser conformance tests at: http://greenbytes.de/tech/tc2231/
|
//! Browser conformance tests at: <http://greenbytes.de/tech/tc2231/>
|
||||||
//! IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml
|
//! IANA assignment: <http://www.iana.org/assignments/cont-disp/cont-disp.xhtml>
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
|
@ -209,7 +209,6 @@ impl ConnectionInfo {
|
||||||
impl FromRequest for ConnectionInfo {
|
impl FromRequest for ConnectionInfo {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
type Future = Ready<Result<Self, Self::Error>>;
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
type Config = ();
|
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
ok(req.connection_info().clone())
|
ok(req.connection_info().clone())
|
||||||
|
@ -252,7 +251,6 @@ impl ResponseError for MissingPeerAddr {}
|
||||||
impl FromRequest for PeerAddr {
|
impl FromRequest for PeerAddr {
|
||||||
type Error = MissingPeerAddr;
|
type Error = MissingPeerAddr;
|
||||||
type Future = Ready<Result<Self, Self::Error>>;
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
type Config = ();
|
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
match req.peer_addr() {
|
match req.peer_addr() {
|
||||||
|
|
|
@ -96,7 +96,6 @@ pub mod test;
|
||||||
pub(crate) mod types;
|
pub(crate) mod types;
|
||||||
pub mod web;
|
pub mod web;
|
||||||
|
|
||||||
pub use actix_http::Response as BaseHttpResponse;
|
|
||||||
pub use actix_http::{body, HttpMessage};
|
pub use actix_http::{body, HttpMessage};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use actix_rt as rt;
|
pub use actix_rt as rt;
|
||||||
|
|
|
@ -18,7 +18,7 @@ use bytes::Bytes;
|
||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use regex::{Regex, RegexSet};
|
use regex::{Regex, RegexSet};
|
||||||
use time::OffsetDateTime;
|
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dev::{BodySize, MessageBody},
|
dev::{BodySize, MessageBody},
|
||||||
|
@ -538,7 +538,7 @@ impl FormatText {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
FormatText::UrlPath => *self = FormatText::Str(req.path().to_string()),
|
FormatText::UrlPath => *self = FormatText::Str(req.path().to_string()),
|
||||||
FormatText::RequestTime => *self = FormatText::Str(now.format("%Y-%m-%dT%H:%M:%S")),
|
FormatText::RequestTime => *self = FormatText::Str(now.format(&Rfc3339).unwrap()),
|
||||||
FormatText::RequestHeader(ref name) => {
|
FormatText::RequestHeader(ref name) => {
|
||||||
let s = if let Some(val) = req.headers().get(name) {
|
let s = if let Some(val) = req.headers().get(name) {
|
||||||
if let Ok(s) = val.to_str() {
|
if let Ok(s) = val.to_str() {
|
||||||
|
@ -767,7 +767,7 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
let s = format!("{}", FormatDisplay(&render));
|
let s = format!("{}", FormatDisplay(&render));
|
||||||
assert!(s.contains(&now.format("%Y-%m-%dT%H:%M:%S")));
|
assert!(s.contains(&now.format(&Rfc3339).unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
|
|
@ -358,7 +358,6 @@ impl Drop for HttpRequest {
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
impl FromRequest for HttpRequest {
|
impl FromRequest for HttpRequest {
|
||||||
type Config = ();
|
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Self, Error>>;
|
type Future = Ready<Result<Self, Error>>;
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,6 @@ impl<T: Clone + 'static> Deref for ReqData<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone + 'static> FromRequest for ReqData<T> {
|
impl<T: Clone + 'static> FromRequest for ReqData<T> {
|
||||||
type Config = ();
|
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Self, Error>>;
|
type Future = Ready<Result<Self, Error>>;
|
||||||
|
|
||||||
|
|
|
@ -466,7 +466,7 @@ impl WebService {
|
||||||
|
|
||||||
/// Set service name.
|
/// Set service name.
|
||||||
///
|
///
|
||||||
/// Name is used for url generation.
|
/// Name is used for URL generation.
|
||||||
pub fn name(mut self, name: &str) -> Self {
|
pub fn name(mut self, name: &str) -> Self {
|
||||||
self.name = Some(name.to_string());
|
self.name = Some(name.to_string());
|
||||||
self
|
self
|
||||||
|
|
22
src/test.rs
22
src/test.rs
|
@ -1,6 +1,6 @@
|
||||||
//! Various helpers for Actix applications to use during testing.
|
//! Various helpers for Actix applications to use during testing.
|
||||||
|
|
||||||
use std::{net::SocketAddr, rc::Rc};
|
use std::{borrow::Cow, net::SocketAddr, rc::Rc};
|
||||||
|
|
||||||
pub use actix_http::test::TestBuffer;
|
pub use actix_http::test::TestBuffer;
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
|
@ -470,19 +470,31 @@ impl TestRequest {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set request path pattern parameter
|
/// Set request path pattern parameter.
|
||||||
pub fn param(mut self, name: &'static str, value: &'static str) -> Self {
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::test::TestRequest;
|
||||||
|
///
|
||||||
|
/// let req = TestRequest::default().param("foo", "bar");
|
||||||
|
/// let req = TestRequest::default().param("foo".to_owned(), "bar".to_owned());
|
||||||
|
/// ```
|
||||||
|
pub fn param(
|
||||||
|
mut self,
|
||||||
|
name: impl Into<Cow<'static, str>>,
|
||||||
|
value: impl Into<Cow<'static, str>>,
|
||||||
|
) -> Self {
|
||||||
self.path.add_static(name, value);
|
self.path.add_static(name, value);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set peer addr
|
/// Set peer addr.
|
||||||
pub fn peer_addr(mut self, addr: SocketAddr) -> Self {
|
pub fn peer_addr(mut self, addr: SocketAddr) -> Self {
|
||||||
self.peer_addr = Some(addr);
|
self.peer_addr = Some(addr);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set request payload
|
/// Set request payload.
|
||||||
pub fn set_payload<B: Into<Bytes>>(mut self, data: B) -> Self {
|
pub fn set_payload<B: Into<Bytes>>(mut self, data: B) -> Self {
|
||||||
self.req.set_payload(data);
|
self.req.set_payload(data);
|
||||||
self
|
self
|
||||||
|
|
|
@ -187,7 +187,6 @@ where
|
||||||
{
|
{
|
||||||
type Error = EitherExtractError<L::Error, R::Error>;
|
type Error = EitherExtractError<L::Error, R::Error>;
|
||||||
type Future = EitherExtractFut<L, R>;
|
type Future = EitherExtractFut<L, R>;
|
||||||
type Config = ();
|
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||||
EitherExtractFut {
|
EitherExtractFut {
|
||||||
|
|
|
@ -32,7 +32,7 @@ use crate::{
|
||||||
/// To extract typed data from a request body, the inner type `T` must implement the
|
/// To extract typed data from a request body, the inner type `T` must implement the
|
||||||
/// [`DeserializeOwned`] trait.
|
/// [`DeserializeOwned`] trait.
|
||||||
///
|
///
|
||||||
/// Use [`FormConfig`] to configure extraction process.
|
/// Use [`FormConfig`] to configure extraction options.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{post, web};
|
/// use actix_web::{post, web};
|
||||||
|
@ -126,20 +126,12 @@ impl<T> FromRequest for Form<T>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned + 'static,
|
T: DeserializeOwned + 'static,
|
||||||
{
|
{
|
||||||
type Config = FormConfig;
|
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = FormExtractFut<T>;
|
type Future = FormExtractFut<T>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||||
let (limit, err_handler) = req
|
let FormConfig { limit, err_handler } = FormConfig::from_req(req).clone();
|
||||||
.app_data::<Self::Config>()
|
|
||||||
.or_else(|| {
|
|
||||||
req.app_data::<web::Data<Self::Config>>()
|
|
||||||
.map(|d| d.as_ref())
|
|
||||||
})
|
|
||||||
.map(|c| (c.limit, c.err_handler.clone()))
|
|
||||||
.unwrap_or((16384, None));
|
|
||||||
|
|
||||||
FormExtractFut {
|
FormExtractFut {
|
||||||
fut: UrlEncoded::new(req, payload).limit(limit),
|
fut: UrlEncoded::new(req, payload).limit(limit),
|
||||||
|
@ -241,14 +233,26 @@ impl FormConfig {
|
||||||
self.err_handler = Some(Rc::new(f));
|
self.err_handler = Some(Rc::new(f));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract payload config from app data.
|
||||||
|
///
|
||||||
|
/// Checks both `T` and `Data<T>`, in that order, and falls back to the default payload config.
|
||||||
|
fn from_req(req: &HttpRequest) -> &Self {
|
||||||
|
req.app_data::<Self>()
|
||||||
|
.or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
|
||||||
|
.unwrap_or(&DEFAULT_CONFIG)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allow shared refs used as default.
|
||||||
|
const DEFAULT_CONFIG: FormConfig = FormConfig {
|
||||||
|
limit: 16_384, // 2^14 bytes (~16kB)
|
||||||
|
err_handler: None,
|
||||||
|
};
|
||||||
|
|
||||||
impl Default for FormConfig {
|
impl Default for FormConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
FormConfig {
|
DEFAULT_CONFIG
|
||||||
limit: 16_384, // 2^14 bytes (~16kB)
|
|
||||||
err_handler: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,6 @@ where
|
||||||
{
|
{
|
||||||
type Error = ParseError;
|
type Error = ParseError;
|
||||||
type Future = Ready<Result<Self, Self::Error>>;
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
type Config = ();
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
|
|
|
@ -34,7 +34,7 @@ use crate::{
|
||||||
/// To extract typed data from a request body, the inner type `T` must implement the
|
/// To extract typed data from a request body, the inner type `T` must implement the
|
||||||
/// [`serde::Deserialize`] trait.
|
/// [`serde::Deserialize`] trait.
|
||||||
///
|
///
|
||||||
/// Use [`JsonConfig`] to configure extraction process.
|
/// Use [`JsonConfig`] to configure extraction options.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{post, web, App};
|
/// use actix_web::{post, web, App};
|
||||||
|
@ -127,22 +127,22 @@ impl<T: Serialize> Responder for Json<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [here](#extractor) for example of usage as an extractor.
|
/// See [here](#extractor) for example of usage as an extractor.
|
||||||
impl<T: DeserializeOwned + 'static> FromRequest for Json<T> {
|
impl<T: DeserializeOwned> FromRequest for Json<T> {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = JsonExtractFut<T>;
|
type Future = JsonExtractFut<T>;
|
||||||
type Config = JsonConfig;
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||||
let config = JsonConfig::from_req(req);
|
let config = JsonConfig::from_req(req);
|
||||||
|
|
||||||
let limit = config.limit;
|
let limit = config.limit;
|
||||||
let ctype = config.content_type.as_deref();
|
let ctype_required = config.content_type_required;
|
||||||
|
let ctype_fn = config.content_type.as_deref();
|
||||||
let err_handler = config.err_handler.clone();
|
let err_handler = config.err_handler.clone();
|
||||||
|
|
||||||
JsonExtractFut {
|
JsonExtractFut {
|
||||||
req: Some(req.clone()),
|
req: Some(req.clone()),
|
||||||
fut: JsonBody::new(req, payload, ctype).limit(limit),
|
fut: JsonBody::new(req, payload, ctype_fn, ctype_required).limit(limit),
|
||||||
err_handler,
|
err_handler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,7 +157,7 @@ pub struct JsonExtractFut<T> {
|
||||||
err_handler: JsonErrorHandler,
|
err_handler: JsonErrorHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: DeserializeOwned + 'static> Future for JsonExtractFut<T> {
|
impl<T: DeserializeOwned> Future for JsonExtractFut<T> {
|
||||||
type Output = Result<Json<T>, Error>;
|
type Output = Result<Json<T>, Error>;
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
@ -225,6 +225,7 @@ pub struct JsonConfig {
|
||||||
limit: usize,
|
limit: usize,
|
||||||
err_handler: JsonErrorHandler,
|
err_handler: JsonErrorHandler,
|
||||||
content_type: Option<Arc<dyn Fn(mime::Mime) -> bool + Send + Sync>>,
|
content_type: Option<Arc<dyn Fn(mime::Mime) -> bool + Send + Sync>>,
|
||||||
|
content_type_required: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JsonConfig {
|
impl JsonConfig {
|
||||||
|
@ -252,6 +253,12 @@ impl JsonConfig {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets whether or not the request must have a `Content-Type` header to be parsed.
|
||||||
|
pub fn content_type_required(mut self, content_type_required: bool) -> Self {
|
||||||
|
self.content_type_required = content_type_required;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
|
/// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
|
||||||
/// back to the default payload config.
|
/// back to the default payload config.
|
||||||
fn from_req(req: &HttpRequest) -> &Self {
|
fn from_req(req: &HttpRequest) -> &Self {
|
||||||
|
@ -268,6 +275,7 @@ const DEFAULT_CONFIG: JsonConfig = JsonConfig {
|
||||||
limit: DEFAULT_LIMIT,
|
limit: DEFAULT_LIMIT,
|
||||||
err_handler: None,
|
err_handler: None,
|
||||||
content_type: None,
|
content_type: None,
|
||||||
|
content_type_required: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Default for JsonConfig {
|
impl Default for JsonConfig {
|
||||||
|
@ -278,15 +286,18 @@ impl Default for JsonConfig {
|
||||||
|
|
||||||
/// Future that resolves to some `T` when parsed from a JSON payload.
|
/// Future that resolves to some `T` when parsed from a JSON payload.
|
||||||
///
|
///
|
||||||
/// Form can be deserialized from any type `T` that implements [`serde::Deserialize`].
|
/// Can deserialize any type `T` that implements [`Deserialize`][serde::Deserialize].
|
||||||
///
|
///
|
||||||
/// Returns error if:
|
/// Returns error if:
|
||||||
/// - content type is not `application/json`
|
/// - `Content-Type` is not `application/json` when `ctype_required` (passed to [`new`][Self::new])
|
||||||
/// - content length is greater than [limit](JsonBody::limit())
|
/// is `true`.
|
||||||
|
/// - `Content-Length` is greater than [limit](JsonBody::limit()).
|
||||||
|
/// - The payload, when consumed, is not valid JSON.
|
||||||
pub enum JsonBody<T> {
|
pub enum JsonBody<T> {
|
||||||
Error(Option<JsonPayloadError>),
|
Error(Option<JsonPayloadError>),
|
||||||
Body {
|
Body {
|
||||||
limit: usize,
|
limit: usize,
|
||||||
|
/// Length as reported by `Content-Length` header, if present.
|
||||||
length: Option<usize>,
|
length: Option<usize>,
|
||||||
#[cfg(feature = "__compress")]
|
#[cfg(feature = "__compress")]
|
||||||
payload: Decompress<Payload>,
|
payload: Decompress<Payload>,
|
||||||
|
@ -305,18 +316,21 @@ impl<T: DeserializeOwned> JsonBody<T> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
req: &HttpRequest,
|
req: &HttpRequest,
|
||||||
payload: &mut Payload,
|
payload: &mut Payload,
|
||||||
ctype: Option<&(dyn Fn(mime::Mime) -> bool + Send + Sync)>,
|
ctype_fn: Option<&(dyn Fn(mime::Mime) -> bool + Send + Sync)>,
|
||||||
|
ctype_required: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// check content-type
|
// check content-type
|
||||||
let json = if let Ok(Some(mime)) = req.mime_type() {
|
let can_parse_json = if let Ok(Some(mime)) = req.mime_type() {
|
||||||
mime.subtype() == mime::JSON
|
mime.subtype() == mime::JSON
|
||||||
|| mime.suffix() == Some(mime::JSON)
|
|| mime.suffix() == Some(mime::JSON)
|
||||||
|| ctype.map_or(false, |predicate| predicate(mime))
|
|| ctype_fn.map_or(false, |predicate| predicate(mime))
|
||||||
} else {
|
} else {
|
||||||
false
|
// if `ctype_required` is false, assume payload is
|
||||||
|
// json even when content-type header is missing
|
||||||
|
!ctype_required
|
||||||
};
|
};
|
||||||
|
|
||||||
if !json {
|
if !can_parse_json {
|
||||||
return JsonBody::Error(Some(JsonPayloadError::ContentType));
|
return JsonBody::Error(Some(JsonPayloadError::ContentType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,7 +340,7 @@ impl<T: DeserializeOwned> JsonBody<T> {
|
||||||
.and_then(|l| l.to_str().ok())
|
.and_then(|l| l.to_str().ok())
|
||||||
.and_then(|s| s.parse::<usize>().ok());
|
.and_then(|s| s.parse::<usize>().ok());
|
||||||
|
|
||||||
// Notice the content_length is not checked against limit of json config here.
|
// Notice the content-length is not checked against limit of json config here.
|
||||||
// As the internal usage always call JsonBody::limit after JsonBody::new.
|
// As the internal usage always call JsonBody::limit after JsonBody::new.
|
||||||
// And limit check to return an error variant of JsonBody happens there.
|
// And limit check to return an error variant of JsonBody happens there.
|
||||||
|
|
||||||
|
@ -380,7 +394,7 @@ impl<T: DeserializeOwned> JsonBody<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: DeserializeOwned + 'static> Future for JsonBody<T> {
|
impl<T: DeserializeOwned> Future for JsonBody<T> {
|
||||||
type Output = Result<T, JsonPayloadError>;
|
type Output = Result<T, JsonPayloadError>;
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
@ -563,7 +577,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_json_body() {
|
async fn test_json_body() {
|
||||||
let (req, mut pl) = TestRequest::default().to_http_parts();
|
let (req, mut pl) = TestRequest::default().to_http_parts();
|
||||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await;
|
let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true).await;
|
||||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::default()
|
let (req, mut pl) = TestRequest::default()
|
||||||
|
@ -572,7 +586,7 @@ mod tests {
|
||||||
header::HeaderValue::from_static("application/text"),
|
header::HeaderValue::from_static("application/text"),
|
||||||
))
|
))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await;
|
let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true).await;
|
||||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::default()
|
let (req, mut pl) = TestRequest::default()
|
||||||
|
@ -586,7 +600,7 @@ mod tests {
|
||||||
))
|
))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
||||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None)
|
let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true)
|
||||||
.limit(100)
|
.limit(100)
|
||||||
.await;
|
.await;
|
||||||
assert!(json_eq(
|
assert!(json_eq(
|
||||||
|
@ -605,7 +619,7 @@ mod tests {
|
||||||
.set_payload(Bytes::from_static(&[0u8; 1000]))
|
.set_payload(Bytes::from_static(&[0u8; 1000]))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
||||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None)
|
let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true)
|
||||||
.limit(100)
|
.limit(100)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -626,7 +640,7 @@ mod tests {
|
||||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
||||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await;
|
let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
json.ok().unwrap(),
|
json.ok().unwrap(),
|
||||||
MyObject {
|
MyObject {
|
||||||
|
@ -696,6 +710,21 @@ mod tests {
|
||||||
assert!(s.is_err())
|
assert!(s.is_err())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_json_with_no_content_type() {
|
||||||
|
let (req, mut pl) = TestRequest::default()
|
||||||
|
.insert_header((
|
||||||
|
header::CONTENT_LENGTH,
|
||||||
|
header::HeaderValue::from_static("16"),
|
||||||
|
))
|
||||||
|
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
|
.app_data(JsonConfig::default().content_type_required(false))
|
||||||
|
.to_http_parts();
|
||||||
|
|
||||||
|
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
||||||
|
assert!(s.is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_with_config_in_data_wrapper() {
|
async fn test_with_config_in_data_wrapper() {
|
||||||
let (req, mut pl) = TestRequest::default()
|
let (req, mut pl) = TestRequest::default()
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
||||||
|
|
||||||
/// Extract typed data from request path segments.
|
/// Extract typed data from request path segments.
|
||||||
///
|
///
|
||||||
/// Use [`PathConfig`] to configure extraction process.
|
/// Use [`PathConfig`] to configure extraction option.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -97,12 +97,11 @@ where
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Self, Self::Error>>;
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
type Config = PathConfig;
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
let error_handler = req
|
let error_handler = req
|
||||||
.app_data::<Self::Config>()
|
.app_data::<PathConfig>()
|
||||||
.and_then(|c| c.ehandler.clone());
|
.and_then(|c| c.ehandler.clone());
|
||||||
|
|
||||||
ready(
|
ready(
|
||||||
|
|
|
@ -43,10 +43,11 @@ use crate::{
|
||||||
/// Ok(format!("Request Body Bytes:\n{:?}", bytes))
|
/// Ok(format!("Request Body Bytes:\n{:?}", bytes))
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Payload(pub crate::dev::Payload);
|
pub struct Payload(crate::dev::Payload);
|
||||||
|
|
||||||
impl Payload {
|
impl Payload {
|
||||||
/// Unwrap to inner Payload type.
|
/// Unwrap to inner Payload type.
|
||||||
|
#[inline]
|
||||||
pub fn into_inner(self) -> crate::dev::Payload {
|
pub fn into_inner(self) -> crate::dev::Payload {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
@ -63,7 +64,6 @@ impl Stream for Payload {
|
||||||
|
|
||||||
/// See [here](#usage) for example of usage as an extractor.
|
/// See [here](#usage) for example of usage as an extractor.
|
||||||
impl FromRequest for Payload {
|
impl FromRequest for Payload {
|
||||||
type Config = PayloadConfig;
|
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Payload, Error>>;
|
type Future = Ready<Result<Payload, Error>>;
|
||||||
|
|
||||||
|
@ -90,7 +90,6 @@ impl FromRequest for Payload {
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
impl FromRequest for Bytes {
|
impl FromRequest for Bytes {
|
||||||
type Config = PayloadConfig;
|
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Either<BytesExtractFut, Ready<Result<Bytes, Error>>>;
|
type Future = Either<BytesExtractFut, Ready<Result<Bytes, Error>>>;
|
||||||
|
|
||||||
|
@ -126,8 +125,7 @@ impl<'a> Future for BytesExtractFut {
|
||||||
///
|
///
|
||||||
/// Text extractor automatically decode body according to the request's charset.
|
/// Text extractor automatically decode body according to the request's charset.
|
||||||
///
|
///
|
||||||
/// [**PayloadConfig**](PayloadConfig) allows to configure
|
/// Use [`PayloadConfig`] to configure extraction process.
|
||||||
/// extraction process.
|
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -139,7 +137,6 @@ impl<'a> Future for BytesExtractFut {
|
||||||
/// format!("Body {}!", text)
|
/// format!("Body {}!", text)
|
||||||
/// }
|
/// }
|
||||||
impl FromRequest for String {
|
impl FromRequest for String {
|
||||||
type Config = PayloadConfig;
|
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Either<StringExtractFut, Ready<Result<String, Error>>>;
|
type Future = Either<StringExtractFut, Ready<Result<String, Error>>>;
|
||||||
|
|
||||||
|
@ -198,14 +195,15 @@ fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result<String, E
|
||||||
|
|
||||||
/// Configuration for request payloads.
|
/// Configuration for request payloads.
|
||||||
///
|
///
|
||||||
/// Applies to the built-in `Bytes` and `String` extractors. Note that the `Payload` extractor does
|
/// Applies to the built-in [`Bytes`] and [`String`] extractors.
|
||||||
/// not automatically check conformance with this configuration to allow more flexibility when
|
/// Note that the [`Payload`] extractor does not automatically check
|
||||||
/// building extractors on top of `Payload`.
|
/// conformance with this configuration to allow more flexibility when
|
||||||
|
/// building extractors on top of [`Payload`].
|
||||||
///
|
///
|
||||||
/// By default, the payload size limit is 256kB and there is no mime type condition.
|
/// By default, the payload size limit is 256kB and there is no mime type condition.
|
||||||
///
|
///
|
||||||
/// To use this, add an instance of it to your app or service through one of the
|
/// To use this, add an instance of it to your [`app`](crate::App), [`scope`](crate::Scope)
|
||||||
/// `.app_data()` methods.
|
/// or [`resource`](crate::Resource) through the associated `.app_data()` method.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PayloadConfig {
|
pub struct PayloadConfig {
|
||||||
limit: usize,
|
limit: usize,
|
||||||
|
|
|
@ -109,12 +109,11 @@ impl<T: fmt::Display> fmt::Display for Query<T> {
|
||||||
impl<T: DeserializeOwned> FromRequest for Query<T> {
|
impl<T: DeserializeOwned> FromRequest for Query<T> {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Self, Error>>;
|
type Future = Ready<Result<Self, Error>>;
|
||||||
type Config = QueryConfig;
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
let error_handler = req
|
let error_handler = req
|
||||||
.app_data::<Self::Config>()
|
.app_data::<QueryConfig>()
|
||||||
.and_then(|c| c.err_handler.clone());
|
.and_then(|c| c.err_handler.clone());
|
||||||
|
|
||||||
serde_urlencoded::from_str::<T>(req.query_string())
|
serde_urlencoded::from_str::<T>(req.query_string())
|
||||||
|
|
220
src/web.rs
220
src/web.rs
|
@ -3,44 +3,36 @@
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use actix_http::http::Method;
|
use actix_http::http::Method;
|
||||||
pub use actix_http::Response as HttpResponse;
|
|
||||||
use actix_router::IntoPatterns;
|
use actix_router::IntoPatterns;
|
||||||
pub use bytes::{Buf, BufMut, Bytes, BytesMut};
|
pub use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
use crate::error::BlockingError;
|
use crate::{
|
||||||
use crate::extract::FromRequest;
|
error::BlockingError, extract::FromRequest, handler::Handler, resource::Resource,
|
||||||
use crate::handler::Handler;
|
responder::Responder, route::Route, scope::Scope, service::WebService,
|
||||||
use crate::resource::Resource;
|
};
|
||||||
use crate::responder::Responder;
|
|
||||||
use crate::route::Route;
|
|
||||||
use crate::scope::Scope;
|
|
||||||
use crate::service::WebService;
|
|
||||||
|
|
||||||
pub use crate::config::ServiceConfig;
|
pub use crate::config::ServiceConfig;
|
||||||
pub use crate::data::Data;
|
pub use crate::data::Data;
|
||||||
pub use crate::request::HttpRequest;
|
pub use crate::request::HttpRequest;
|
||||||
pub use crate::request_data::ReqData;
|
pub use crate::request_data::ReqData;
|
||||||
|
pub use crate::response::HttpResponse;
|
||||||
pub use crate::types::*;
|
pub use crate::types::*;
|
||||||
|
|
||||||
/// Create resource for a specific path.
|
/// Creates a new resource for a specific path.
|
||||||
///
|
///
|
||||||
/// Resources may have variable path segments. For example, a
|
/// Resources may have dynamic path segments. For example, a resource with the path `/a/{name}/c`
|
||||||
/// resource with the path `/a/{name}/c` would match all incoming
|
/// would match all incoming requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`.
|
||||||
/// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`.
|
|
||||||
///
|
///
|
||||||
/// A variable segment is specified in the form `{identifier}`,
|
/// A dynamic segment is specified in the form `{identifier}`, where the identifier can be used
|
||||||
/// where the identifier can be used later in a request handler to
|
/// later in a request handler to access the matched value for that segment. This is done by looking
|
||||||
/// access the matched value for that segment. This is done by
|
/// up the identifier in the `Path` object returned by [`HttpRequest.match_info()`] method.
|
||||||
/// looking up the identifier in the `Params` object returned by
|
|
||||||
/// `HttpRequest.match_info()` method.
|
|
||||||
///
|
///
|
||||||
/// By default, each segment matches the regular expression `[^{}/]+`.
|
/// By default, each segment matches the regular expression `[^{}/]+`.
|
||||||
///
|
///
|
||||||
/// You can also specify a custom regex in the form `{identifier:regex}`:
|
/// You can also specify a custom regex in the form `{identifier:regex}`:
|
||||||
///
|
///
|
||||||
/// For instance, to route `GET`-requests on any route matching
|
/// For instance, to route `GET`-requests on any route matching `/users/{userid}/{friend}` and store
|
||||||
/// `/users/{userid}/{friend}` and store `userid` and `friend` in
|
/// `userid` and `friend` in the exposed `Path` object:
|
||||||
/// the exposed `Params` object:
|
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{web, App, HttpResponse};
|
/// use actix_web::{web, App, HttpResponse};
|
||||||
|
@ -55,10 +47,16 @@ pub fn resource<T: IntoPatterns>(path: T) -> Resource {
|
||||||
Resource::new(path)
|
Resource::new(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure scope for common root path.
|
/// Creates scope for common path prefix.
|
||||||
///
|
///
|
||||||
/// Scopes collect multiple paths under a common path prefix.
|
/// Scopes collect multiple paths under a common path prefix. The scope's path can contain dynamic
|
||||||
/// Scope path can contain variable path segments as resources.
|
/// path segments.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// In this example, three routes are set up (and will handle any method):
|
||||||
|
/// * `/{project_id}/path1`
|
||||||
|
/// * `/{project_id}/path2`
|
||||||
|
/// * `/{project_id}/path3`
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{web, App, HttpResponse};
|
/// use actix_web::{web, App, HttpResponse};
|
||||||
|
@ -70,148 +68,50 @@ pub fn resource<T: IntoPatterns>(path: T) -> Resource {
|
||||||
/// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed()))
|
/// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed()))
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// In the above example, three routes get added:
|
|
||||||
/// * /{project_id}/path1
|
|
||||||
/// * /{project_id}/path2
|
|
||||||
/// * /{project_id}/path3
|
|
||||||
///
|
|
||||||
pub fn scope(path: &str) -> Scope {
|
pub fn scope(path: &str) -> Scope {
|
||||||
Scope::new(path)
|
Scope::new(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create *route* without configuration.
|
/// Creates a new un-configured route.
|
||||||
pub fn route() -> Route {
|
pub fn route() -> Route {
|
||||||
Route::new()
|
Route::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create *route* with `GET` method guard.
|
macro_rules! method_route {
|
||||||
///
|
($method_fn:ident, $method_const:ident) => {
|
||||||
/// ```
|
paste::paste! {
|
||||||
/// use actix_web::{web, App, HttpResponse};
|
#[doc = " Creates a new route with `" $method_const "` method guard."]
|
||||||
///
|
///
|
||||||
/// let app = App::new().service(
|
/// # Examples
|
||||||
/// web::resource("/{project_id}")
|
#[doc = " In this example, one `" $method_const " /{project_id}` route is set up:"]
|
||||||
/// .route(web::get().to(|| HttpResponse::Ok()))
|
/// ```
|
||||||
/// );
|
/// use actix_web::{web, App, HttpResponse};
|
||||||
/// ```
|
///
|
||||||
///
|
/// let app = App::new().service(
|
||||||
/// In the above example, one `GET` route gets added:
|
/// web::resource("/{project_id}")
|
||||||
/// * /{project_id}
|
#[doc = " .route(web::" $method_fn "().to(|| HttpResponse::Ok()))"]
|
||||||
///
|
///
|
||||||
pub fn get() -> Route {
|
/// );
|
||||||
method(Method::GET)
|
/// ```
|
||||||
|
pub fn $method_fn() -> Route {
|
||||||
|
method(Method::$method_const)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create *route* with `POST` method guard.
|
method_route!(get, GET);
|
||||||
///
|
method_route!(post, POST);
|
||||||
/// ```
|
method_route!(put, PUT);
|
||||||
/// use actix_web::{web, App, HttpResponse};
|
method_route!(patch, PATCH);
|
||||||
///
|
method_route!(delete, DELETE);
|
||||||
/// let app = App::new().service(
|
method_route!(head, HEAD);
|
||||||
/// web::resource("/{project_id}")
|
method_route!(trace, TRACE);
|
||||||
/// .route(web::post().to(|| HttpResponse::Ok()))
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// In the above example, one `POST` route gets added:
|
|
||||||
/// * /{project_id}
|
|
||||||
///
|
|
||||||
pub fn post() -> Route {
|
|
||||||
method(Method::POST)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create *route* with `PUT` method guard.
|
/// Creates a new route with specified method guard.
|
||||||
///
|
///
|
||||||
/// ```
|
/// # Examples
|
||||||
/// use actix_web::{web, App, HttpResponse};
|
/// In this example, one `GET /{project_id}` route is set up:
|
||||||
///
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/{project_id}")
|
|
||||||
/// .route(web::put().to(|| HttpResponse::Ok()))
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// In the above example, one `PUT` route gets added:
|
|
||||||
/// * /{project_id}
|
|
||||||
///
|
|
||||||
pub fn put() -> Route {
|
|
||||||
method(Method::PUT)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create *route* with `PATCH` method guard.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use actix_web::{web, App, HttpResponse};
|
|
||||||
///
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/{project_id}")
|
|
||||||
/// .route(web::patch().to(|| HttpResponse::Ok()))
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// In the above example, one `PATCH` route gets added:
|
|
||||||
/// * /{project_id}
|
|
||||||
///
|
|
||||||
pub fn patch() -> Route {
|
|
||||||
method(Method::PATCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create *route* with `DELETE` method guard.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use actix_web::{web, App, HttpResponse};
|
|
||||||
///
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/{project_id}")
|
|
||||||
/// .route(web::delete().to(|| HttpResponse::Ok()))
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// In the above example, one `DELETE` route gets added:
|
|
||||||
/// * /{project_id}
|
|
||||||
///
|
|
||||||
pub fn delete() -> Route {
|
|
||||||
method(Method::DELETE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create *route* with `HEAD` method guard.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use actix_web::{web, App, HttpResponse};
|
|
||||||
///
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/{project_id}")
|
|
||||||
/// .route(web::head().to(|| HttpResponse::Ok()))
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// In the above example, one `HEAD` route gets added:
|
|
||||||
/// * /{project_id}
|
|
||||||
///
|
|
||||||
pub fn head() -> Route {
|
|
||||||
method(Method::HEAD)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create *route* with `TRACE` method guard.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use actix_web::{web, App, HttpResponse};
|
|
||||||
///
|
|
||||||
/// let app = App::new().service(
|
|
||||||
/// web::resource("/{project_id}")
|
|
||||||
/// .route(web::trace().to(|| HttpResponse::Ok()))
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// In the above example, one `HEAD` route gets added:
|
|
||||||
/// * /{project_id}
|
|
||||||
///
|
|
||||||
pub fn trace() -> Route {
|
|
||||||
method(Method::TRACE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create *route* and add method guard.
|
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{web, http, App, HttpResponse};
|
/// use actix_web::{web, http, App, HttpResponse};
|
||||||
|
@ -221,15 +121,11 @@ pub fn trace() -> Route {
|
||||||
/// .route(web::method(http::Method::GET).to(|| HttpResponse::Ok()))
|
/// .route(web::method(http::Method::GET).to(|| HttpResponse::Ok()))
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// In the above example, one `GET` route gets added:
|
|
||||||
/// * /{project_id}
|
|
||||||
///
|
|
||||||
pub fn method(method: Method) -> Route {
|
pub fn method(method: Method) -> Route {
|
||||||
Route::new().method(method)
|
Route::new().method(method)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new route and add handler.
|
/// Creates a new any-method route with handler.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{web, App, HttpResponse, Responder};
|
/// use actix_web::{web, App, HttpResponse, Responder};
|
||||||
|
@ -253,7 +149,7 @@ where
|
||||||
Route::new().to(handler)
|
Route::new().to(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create raw service for a specific path.
|
/// Creates a raw service for a specific path.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{dev, web, guard, App, Error, HttpResponse};
|
/// use actix_web::{dev, web, guard, App, Error, HttpResponse};
|
||||||
|
@ -272,8 +168,8 @@ pub fn service<T: IntoPatterns>(path: T) -> WebService {
|
||||||
WebService::new(path)
|
WebService::new(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute blocking function on a thread pool, returns future that resolves
|
/// Executes blocking function on a thread pool, returns future that resolves to result of the
|
||||||
/// to result of the function execution.
|
/// function execution.
|
||||||
pub fn block<F, R>(f: F) -> impl Future<Output = Result<R, BlockingError>>
|
pub fn block<F, R>(f: F) -> impl Future<Output = Result<R, BlockingError>>
|
||||||
where
|
where
|
||||||
F: FnOnce() -> R + Send + 'static,
|
F: FnOnce() -> R + Send + 'static,
|
||||||
|
|
Loading…
Reference in New Issue