mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-03 01:34:32 +02:00
Compare commits
27 Commits
files-v0.6
...
http-test-
Author | SHA1 | Date | |
---|---|---|---|
a2f59c02f7 | |||
2754608f3c | |||
c020cedb63 | |||
5e554dca35 | |||
6ec2d7b909 | |||
ec6d284a8e | |||
be9530eb72 | |||
855e260fdb | |||
d13854505f | |||
d40b6748bc | |||
c79b9a0df3 | |||
4af414064b | |||
9abe166d52 | |||
c09ec6af4c | |||
37f2bf5625 | |||
4f6f0b0137 | |||
591abc37c3 | |||
ad22cc4e7f | |||
efdf3ab1c3 | |||
6b3ea4fc61 | |||
99985fc4ec | |||
a6707fb7ee | |||
a3806cde19 | |||
efefa0d0ce | |||
450ff5fa1d | |||
8ae278cb68 | |||
46699e3429 |
@ -7,3 +7,8 @@ ci-default = "check --workspace --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-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
||||
|
||||
ci-feature-powerset-check-no-tls="hack --workspace --feature-powerset --skip=__compress,rustls,openssl check"
|
||||
ci-feature-powerset-check-rustls="hack --workspace --feature-powerset --features=rustls --skip=__compress,openssl check"
|
||||
ci-feature-powerset-check-openssl="hack --workspace --feature-powerset --features=openssl --skip=__compress,rustls check"
|
||||
ci-feature-powerset-check-all="hack --workspace --feature-powerset --skip=__compress check"
|
||||
|
86
.github/workflows/ci.yml
vendored
86
.github/workflows/ci.yml
vendored
@ -14,9 +14,9 @@ jobs:
|
||||
target:
|
||||
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
|
||||
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
|
||||
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
|
||||
- { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc }
|
||||
version:
|
||||
- 1.51.0 # MSRV
|
||||
- 1.52.0 # MSRV
|
||||
- stable
|
||||
- nightly
|
||||
|
||||
@ -32,6 +32,8 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# install OpenSSL on Windows
|
||||
# TODO: GitHub actions docs state that OpenSSL is
|
||||
# already installed on these Windows machines somewhere
|
||||
- name: Set vcpkg root
|
||||
if: matrix.target.triple == 'x86_64-pc-windows-msvc'
|
||||
run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
@ -48,8 +50,7 @@ jobs:
|
||||
|
||||
- name: Generate Cargo.lock
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: generate-lockfile
|
||||
with: { command: generate-lockfile }
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v1.2.0
|
||||
|
||||
@ -82,32 +83,73 @@ jobs:
|
||||
command: ci-test
|
||||
args: --skip=test_reading_deflate_encoding_large_random_rustls
|
||||
|
||||
- name: Generate coverage file
|
||||
if: >
|
||||
matrix.target.os == 'ubuntu-latest'
|
||||
&& matrix.version == 'stable'
|
||||
&& github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
cargo install cargo-tarpaulin --vers "^0.13"
|
||||
cargo tarpaulin --out Xml --verbose
|
||||
- name: Upload to Codecov
|
||||
if: >
|
||||
matrix.target.os == 'ubuntu-latest'
|
||||
&& matrix.version == 'stable'
|
||||
&& github.ref == 'refs/heads/master'
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
file: cobertura.xml
|
||||
|
||||
- name: Clear the cargo caches
|
||||
run: |
|
||||
cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean
|
||||
cargo-cache
|
||||
|
||||
ci_feature_powerset_check:
|
||||
name: Verify Feature Combinations
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable-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.2.0
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-hack
|
||||
|
||||
- name: check feature combinations
|
||||
# if: github.ref == 'refs/heads/master'
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-feature-powerset-check-all }
|
||||
|
||||
coverage:
|
||||
name: coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable-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.2.0
|
||||
|
||||
- name: Generate coverage file
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
cargo install cargo-tarpaulin --vers "^0.13"
|
||||
cargo tarpaulin --out Xml --verbose
|
||||
- name: Upload to Codecov
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: codecov/codecov-action@v1
|
||||
with: { file: cobertura.xml }
|
||||
|
||||
rustdoc:
|
||||
name: rustdoc
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
36
CHANGES.md
36
CHANGES.md
@ -3,6 +3,42 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 4.0.0-beta.11 - 2021-11-15
|
||||
### Added
|
||||
* Re-export `dev::ServerHandle` from `actix-server`. [#2442]
|
||||
|
||||
### Changed
|
||||
* `ContentType::html` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423]
|
||||
* Update `actix-server` to `2.0.0-beta.9`. [#2442]
|
||||
|
||||
[#2423]: https://github.com/actix/actix-web/pull/2423
|
||||
[#2442]: https://github.com/actix/actix-web/pull/2442
|
||||
|
||||
|
||||
## 4.0.0-beta.10 - 2021-10-20
|
||||
### Added
|
||||
* Option to allow `Json` extractor to work without a `Content-Type` header present. [#2362]
|
||||
* `#[actix_web::test]` macro for setting up tests with a runtime. [#2409]
|
||||
|
||||
### Changed
|
||||
* Associated type `FromRequest::Config` was removed. [#2233]
|
||||
* Inner field made private on `web::Payload`. [#2384]
|
||||
* `Data::into_inner` and `Data::get_ref` no longer require T: Sized. [#2403]
|
||||
* Updated rustls to v0.20. [#2414]
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
### Removed
|
||||
* Useless `ServiceResponse::checked_expr` method. [#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
|
||||
[#2403]: https://github.com/actix/actix-web/pull/2403
|
||||
[#2409]: https://github.com/actix/actix-web/pull/2409
|
||||
[#2414]: https://github.com/actix/actix-web/pull/2414
|
||||
|
||||
|
||||
## 4.0.0-beta.9 - 2021-09-09
|
||||
### Added
|
||||
* Re-export actix-service `ServiceFactory` in `dev` module. [#2325]
|
||||
|
33
Cargo.toml
33
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "4.0.0-beta.9"
|
||||
version = "4.0.0-beta.11"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
||||
keywords = ["actix", "http", "web", "framework", "async"]
|
||||
@ -11,13 +11,14 @@ categories = [
|
||||
"web-programming::websocket"
|
||||
]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
# features that docs.rs will build with
|
||||
features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lib]
|
||||
name = "actix_web"
|
||||
@ -37,8 +38,6 @@ members = [
|
||||
"actix-test",
|
||||
"actix-router",
|
||||
]
|
||||
# enable when MSRV is 1.51+
|
||||
# resolver = "2"
|
||||
|
||||
[features]
|
||||
default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
|
||||
@ -62,22 +61,22 @@ openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"]
|
||||
# rustls
|
||||
rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"]
|
||||
|
||||
# Internal (PRIVATE!) features used to aid testing and cheking feature status.
|
||||
# Internal (PRIVATE!) features used to aid testing and checking feature status.
|
||||
# Don't rely on these whatsoever. They may disappear at anytime.
|
||||
__compress = []
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.4.0"
|
||||
actix-macros = "0.2.1"
|
||||
actix-router = "0.5.0-beta.2"
|
||||
actix-macros = "0.2.3"
|
||||
actix-rt = "2.2"
|
||||
actix-server = "2.0.0-beta.3"
|
||||
actix-server = "2.0.0-beta.9"
|
||||
actix-service = "2.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.7", default-features = false, optional = true }
|
||||
|
||||
actix-web-codegen = "0.5.0-beta.4"
|
||||
actix-http = "3.0.0-beta.10"
|
||||
actix-http = "3.0.0-beta.12"
|
||||
actix-router = "0.5.0-beta.2"
|
||||
actix-web-codegen = "0.5.0-beta.5"
|
||||
|
||||
ahash = "0.7"
|
||||
bytes = "1"
|
||||
@ -101,22 +100,24 @@ serde_json = "1.0"
|
||||
serde_urlencoded = "0.7"
|
||||
smallvec = "1.6.1"
|
||||
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"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] }
|
||||
awc = { version = "3.0.0-beta.8", features = ["openssl"] }
|
||||
actix-test = { version = "0.1.0-beta.6", features = ["openssl", "rustls"] }
|
||||
awc = { version = "3.0.0-beta.10", features = ["openssl"] }
|
||||
|
||||
brotli2 = "0.3.2"
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
env_logger = "0.8"
|
||||
flate2 = "1.0.13"
|
||||
zstd = "0.7"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||
rand = "0.8"
|
||||
rcgen = "0.8"
|
||||
rustls-pemfile = "0.2"
|
||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
||||
tls-rustls = { package = "rustls", version = "0.19.0" }
|
||||
tls-rustls = { package = "rustls", version = "0.20.0" }
|
||||
zstd = "0.7"
|
||||
|
||||
[profile.dev]
|
||||
# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much.
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
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).
|
||||
By default all compression algorithms are enabled.
|
||||
To select algorithm you want to include with `middleware::Compress` use following flags:
|
||||
|
@ -6,10 +6,10 @@
|
||||
<p>
|
||||
|
||||
[](https://crates.io/crates/actix-web)
|
||||
[](https://docs.rs/actix-web/4.0.0-beta.9)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||
[](https://docs.rs/actix-web/4.0.0-beta.11)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
[](https://deps.rs/crate/actix-web/4.0.0-beta.9)
|
||||
[](https://deps.rs/crate/actix-web/4.0.0-beta.11)
|
||||
<br />
|
||||
[](https://github.com/actix/actix-web/actions)
|
||||
[](https://codecov.io/gh/actix/actix-web)
|
||||
@ -32,7 +32,7 @@
|
||||
* SSL support using OpenSSL or Rustls
|
||||
* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
|
||||
* Includes an async [HTTP client](https://docs.rs/awc/)
|
||||
* Runs on stable Rust 1.51+
|
||||
* Runs on stable Rust 1.52+
|
||||
|
||||
## Documentation
|
||||
|
||||
|
@ -3,6 +3,10 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.6.0-beta.8 - 2021-10-20
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
|
||||
## 0.6.0-beta.7 - 2021-09-09
|
||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-files"
|
||||
version = "0.6.0-beta.7"
|
||||
version = "0.6.0-beta.8"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Static file serving for Actix Web"
|
||||
keywords = ["actix", "http", "async", "futures"]
|
||||
@ -15,8 +15,8 @@ name = "actix_files"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = { version = "4.0.0-beta.9", default-features = false }
|
||||
actix-http = "3.0.0-beta.10"
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false }
|
||||
actix-http = "3.0.0-beta.12"
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
|
||||
@ -33,5 +33,5 @@ percent-encoding = "2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-web = "4.0.0-beta.9"
|
||||
actix-test = "0.1.0-beta.3"
|
||||
actix-web = "4.0.0-beta.11"
|
||||
actix-test = "0.1.0-beta.6"
|
||||
|
@ -3,11 +3,11 @@
|
||||
> Static file serving for Actix Web
|
||||
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://docs.rs/actix-files/0.6.0-beta.7)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||
[](https://docs.rs/actix-files/0.6.0-beta.8)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-files/0.6.0-beta.7)
|
||||
[](https://deps.rs/crate/actix-files/0.6.0-beta.8)
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
@ -15,4 +15,4 @@
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-files/)
|
||||
- [Example Project](https://github.com/actix/examples/tree/master/basics/static_index)
|
||||
- Minimum supported Rust version: 1.51 or later
|
||||
- Minimum Supported Rust Version (MSRV): 1.52
|
||||
|
@ -83,7 +83,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
#[actix_web::test]
|
||||
async fn test_file_extension_to_mime() {
|
||||
let m = file_extension_to_mime("");
|
||||
assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
|
||||
|
@ -59,7 +59,6 @@ impl AsRef<Path> for PathBufWrap {
|
||||
impl FromRequest for PathBufWrap {
|
||||
type Error = UriSegmentError;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
ready(req.match_info().path().parse())
|
||||
|
@ -8,7 +8,7 @@ use actix_web::{
|
||||
App,
|
||||
};
|
||||
|
||||
#[actix_rt::test]
|
||||
#[actix_web::test]
|
||||
async fn test_utf8_file_contents() {
|
||||
// use default ISO-8859-1 encoding
|
||||
let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await;
|
||||
|
@ -7,7 +7,7 @@ use actix_web::{
|
||||
};
|
||||
use bytes::Bytes;
|
||||
|
||||
#[actix_rt::test]
|
||||
#[actix_web::test]
|
||||
async fn test_guard_filter() {
|
||||
let srv = test::init_service(
|
||||
App::new()
|
||||
|
@ -3,6 +3,14 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 3.0.0-beta.6 - 2021-11-15
|
||||
* `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442]
|
||||
* Update `actix-server` to `2.0.0-beta.9`. [#2442]
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
[#2442]: https://github.com/actix/actix-web/pull/2442
|
||||
|
||||
|
||||
## 3.0.0-beta.5 - 2021-09-09
|
||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-http-test"
|
||||
version = "3.0.0-beta.5"
|
||||
version = "3.0.0-beta.6"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Various helpers for Actix applications to use during testing"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
@ -31,25 +31,25 @@ openssl = ["tls-openssl", "awc/openssl"]
|
||||
[dependencies]
|
||||
actix-service = "2.0.0"
|
||||
actix-codec = "0.4.0"
|
||||
actix-tls = "3.0.0-beta.5"
|
||||
actix-tls = "3.0.0-beta.7"
|
||||
actix-utils = "3.0.0"
|
||||
actix-rt = "2.2"
|
||||
actix-server = "2.0.0-beta.3"
|
||||
awc = { version = "3.0.0-beta.8", default-features = false }
|
||||
actix-server = "2.0.0-beta.9"
|
||||
awc = { version = "3.0.0-beta.10", default-features = false }
|
||||
|
||||
base64 = "0.13"
|
||||
bytes = "1"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
http = "0.2.2"
|
||||
http = "0.2.5"
|
||||
log = "0.4"
|
||||
socket2 = "0.4"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
slab = "0.4"
|
||||
serde_urlencoded = "0.7"
|
||||
time = { version = "0.2.23", default-features = false, features = ["std"] }
|
||||
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||
tokio = { version = "1.2", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["cookies"] }
|
||||
actix-http = "3.0.0-beta.10"
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] }
|
||||
actix-http = "3.0.0-beta.12"
|
||||
|
@ -3,15 +3,15 @@
|
||||
> Various helpers for Actix applications to use during testing.
|
||||
|
||||
[](https://crates.io/crates/actix-http-test)
|
||||
[](https://docs.rs/actix-http-test/3.0.0-beta.5)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||
[](https://docs.rs/actix-http-test/3.0.0-beta.6)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br>
|
||||
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.5)
|
||||
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.6)
|
||||
[](https://crates.io/crates/actix-http-test)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-http-test)
|
||||
- Minimum Supported Rust Version (MSRV): 1.51.0
|
||||
- Minimum Supported Rust Version (MSRV): 1.52
|
||||
|
@ -7,8 +7,7 @@
|
||||
#[cfg(feature = "openssl")]
|
||||
extern crate tls_openssl as openssl;
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::{net, thread, time};
|
||||
use std::{net, thread, time::Duration};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_rt::{net::TcpStream, System};
|
||||
@ -20,6 +19,7 @@ use bytes::Bytes;
|
||||
use futures_core::stream::Stream;
|
||||
use http::Method;
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// Start test server
|
||||
///
|
||||
@ -37,7 +37,7 @@ use socket2::{Domain, Protocol, Socket, Type};
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// }
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// #[actix_web::test]
|
||||
/// async fn test_example() {
|
||||
/// let mut srv = TestServer::start(
|
||||
/// || HttpService::new(
|
||||
@ -56,12 +56,13 @@ pub async fn test_server<F: ServiceFactory<TcpStream>>(factory: F) -> TestServer
|
||||
test_server_with_addr(tcp, factory).await
|
||||
}
|
||||
|
||||
/// Start [`test server`](test_server()) on a concrete Address
|
||||
/// Start [`test server`](test_server()) on an existing address binding.
|
||||
pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
||||
tcp: net::TcpListener,
|
||||
factory: F,
|
||||
) -> TestServer {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let (started_tx, started_rx) = std::sync::mpsc::channel();
|
||||
let (thread_stop_tx, thread_stop_rx) = mpsc::channel(1);
|
||||
|
||||
// run server in separate thread
|
||||
thread::spawn(move || {
|
||||
@ -69,59 +70,73 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
||||
let local_addr = tcp.local_addr().unwrap();
|
||||
|
||||
let srv = Server::build()
|
||||
.listen("test", tcp, factory)?
|
||||
.workers(1)
|
||||
.disable_signals();
|
||||
.disable_signals()
|
||||
.listen("test", tcp, factory)
|
||||
.expect("test server could not be created");
|
||||
|
||||
sys.block_on(async {
|
||||
srv.run();
|
||||
tx.send((System::current(), local_addr)).unwrap();
|
||||
});
|
||||
let srv = srv.run();
|
||||
started_tx
|
||||
.send((System::current(), srv.handle(), local_addr))
|
||||
.unwrap();
|
||||
|
||||
sys.run()
|
||||
// drive server loop
|
||||
sys.block_on(srv).unwrap();
|
||||
|
||||
// start system event loop
|
||||
sys.run().unwrap();
|
||||
|
||||
// notify TestServer that server and system have shut down
|
||||
// all thread managed resources should be dropped at this point
|
||||
let _ = thread_stop_tx.send(());
|
||||
});
|
||||
|
||||
let (system, addr) = rx.recv().unwrap();
|
||||
let (system, server, addr) = started_rx.recv().unwrap();
|
||||
|
||||
let client = {
|
||||
#[cfg(feature = "openssl")]
|
||||
let connector = {
|
||||
#[cfg(feature = "openssl")]
|
||||
{
|
||||
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
|
||||
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
|
||||
|
||||
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
builder.set_verify(SslVerifyMode::NONE);
|
||||
let _ = builder
|
||||
.set_alpn_protos(b"\x02h2\x08http/1.1")
|
||||
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
|
||||
Connector::new()
|
||||
.conn_lifetime(time::Duration::from_secs(0))
|
||||
.timeout(time::Duration::from_millis(30000))
|
||||
.ssl(builder.build())
|
||||
}
|
||||
#[cfg(not(feature = "openssl"))]
|
||||
{
|
||||
Connector::new()
|
||||
.conn_lifetime(time::Duration::from_secs(0))
|
||||
.timeout(time::Duration::from_millis(30000))
|
||||
}
|
||||
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
|
||||
builder.set_verify(SslVerifyMode::NONE);
|
||||
let _ = builder
|
||||
.set_alpn_protos(b"\x02h2\x08http/1.1")
|
||||
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
|
||||
|
||||
Connector::new()
|
||||
.conn_lifetime(Duration::from_secs(0))
|
||||
.timeout(Duration::from_millis(30000))
|
||||
.ssl(builder.build())
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "openssl"))]
|
||||
let connector = {
|
||||
Connector::new()
|
||||
.conn_lifetime(Duration::from_secs(0))
|
||||
.timeout(Duration::from_millis(30000))
|
||||
};
|
||||
|
||||
Client::builder().connector(connector).finish()
|
||||
};
|
||||
|
||||
TestServer {
|
||||
addr,
|
||||
server,
|
||||
client,
|
||||
system,
|
||||
addr,
|
||||
thread_stop_rx,
|
||||
}
|
||||
}
|
||||
|
||||
/// Test server controller
|
||||
pub struct TestServer {
|
||||
server: actix_server::ServerHandle,
|
||||
client: awc::Client,
|
||||
system: actix_rt::System,
|
||||
addr: net::SocketAddr,
|
||||
client: Client,
|
||||
system: System,
|
||||
thread_stop_rx: mpsc::Receiver<()>,
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
@ -258,15 +273,32 @@ impl TestServer {
|
||||
self.client.headers()
|
||||
}
|
||||
|
||||
/// Stop HTTP server
|
||||
fn stop(&mut self) {
|
||||
/// Gracefully stop HTTP server.
|
||||
///
|
||||
/// Waits for spawned `Server` and `System` to shutdown gracefully.
|
||||
pub async fn stop(&mut self) {
|
||||
// signal server to stop
|
||||
self.server.stop(true).await;
|
||||
|
||||
// also signal system to stop
|
||||
// though this is handled by `ServerBuilder::exit_system` too
|
||||
self.system.stop();
|
||||
|
||||
// wait for thread to be stopped but don't care about result
|
||||
let _ = self.thread_stop_rx.recv().await;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestServer {
|
||||
fn drop(&mut self) {
|
||||
self.stop()
|
||||
// calls in this Drop impl should be enough to shut down the server, system, and thread
|
||||
// without needing to await anything
|
||||
|
||||
// signal server to stop
|
||||
let _ = self.server.stop(true);
|
||||
|
||||
// signal system to stop
|
||||
self.system.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,26 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 3.0.0-beta.12 - 2021-11-15
|
||||
### Changed
|
||||
* Update `actix-server` to `2.0.0-beta.9`. [#2442]
|
||||
|
||||
### Removed
|
||||
* `client` module. [#2425]
|
||||
* `trust-dns` feature. [#2425]
|
||||
|
||||
[#2425]: https://github.com/actix/actix-web/pull/2425
|
||||
[#2442]: https://github.com/actix/actix-web/pull/2442
|
||||
|
||||
|
||||
## 3.0.0-beta.11 - 2021-10-20
|
||||
### Changed
|
||||
* Updated rustls to v0.20. [#2414]
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
[#2414]: https://github.com/actix/actix-web/pull/2414
|
||||
|
||||
|
||||
## 3.0.0-beta.10 - 2021-09-09
|
||||
### Changed
|
||||
* `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377]
|
||||
|
@ -1,14 +1,17 @@
|
||||
[package]
|
||||
name = "actix-http"
|
||||
version = "3.0.0-beta.10"
|
||||
version = "3.0.0-beta.12"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "HTTP primitives for the Actix ecosystem"
|
||||
keywords = ["actix", "http", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
categories = ["network-programming", "asynchronous",
|
||||
"web-programming::http-server",
|
||||
"web-programming::websocket"]
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
categories = [
|
||||
"network-programming",
|
||||
"asynchronous",
|
||||
"web-programming::http-server",
|
||||
"web-programming::websocket",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
@ -24,19 +27,16 @@ path = "src/lib.rs"
|
||||
default = []
|
||||
|
||||
# openssl
|
||||
openssl = ["actix-tls/openssl"]
|
||||
openssl = ["actix-tls/accept", "actix-tls/openssl"]
|
||||
|
||||
# rustls support
|
||||
rustls = ["actix-tls/rustls"]
|
||||
rustls = ["actix-tls/accept", "actix-tls/rustls"]
|
||||
|
||||
# enable compression support
|
||||
compress-brotli = ["brotli2", "__compress"]
|
||||
compress-gzip = ["flate2", "__compress"]
|
||||
compress-zstd = ["zstd", "__compress"]
|
||||
|
||||
# trust-dns as client dns resolver
|
||||
trust-dns = ["trust-dns-resolver"]
|
||||
|
||||
# Internal (PRIVATE!) features used to aid testing and cheking feature status.
|
||||
# Don't rely on these whatsoever. They may disappear at anytime.
|
||||
__compress = []
|
||||
@ -46,7 +46,6 @@ actix-service = "2.0.0"
|
||||
actix-codec = "0.4.0"
|
||||
actix-utils = "3.0.0"
|
||||
actix-rt = "2.2"
|
||||
actix-tls = { version = "3.0.0-beta.5", features = ["accept", "connect"] }
|
||||
|
||||
ahash = "0.7"
|
||||
base64 = "0.13"
|
||||
@ -58,45 +57,44 @@ encoding_rs = "0.8"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
|
||||
h2 = "0.3.1"
|
||||
http = "0.2.2"
|
||||
http = "0.2.5"
|
||||
httparse = "1.5.1"
|
||||
httpdate = "1.0.1"
|
||||
itoa = "0.4"
|
||||
language-tags = "0.3"
|
||||
local-channel = "0.1"
|
||||
once_cell = "1.5"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
percent-encoding = "2.1"
|
||||
pin-project = "1.0.0"
|
||||
pin-project-lite = "0.2"
|
||||
rand = "0.8"
|
||||
regex = "1.3"
|
||||
serde = "1.0"
|
||||
sha-1 = "0.9"
|
||||
smallvec = "1.6.1"
|
||||
time = { version = "0.2.23", default-features = false, features = ["std"] }
|
||||
tokio = { version = "1.2", features = ["sync"] }
|
||||
|
||||
# tls
|
||||
actix-tls = { version = "3.0.0-beta.7", default-features = false, optional = true }
|
||||
|
||||
# compression
|
||||
brotli2 = { version="0.3.2", optional = true }
|
||||
flate2 = { version = "1.0.13", optional = true }
|
||||
zstd = { version = "0.7", optional = true }
|
||||
|
||||
trust-dns-resolver = { version = "0.20.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-server = "2.0.0-beta.3"
|
||||
actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] }
|
||||
actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] }
|
||||
actix-server = "2.0.0-beta.9"
|
||||
actix-http-test = { version = "3.0.0-beta.6", features = ["openssl"] }
|
||||
actix-tls = { version = "3.0.0-beta.7", features = ["openssl"] }
|
||||
async-stream = "0.3"
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
env_logger = "0.8"
|
||||
rcgen = "0.8"
|
||||
regex = "1.3"
|
||||
rustls-pemfile = "0.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tls-openssl = { version = "0.10", package = "openssl" }
|
||||
tls-rustls = { version = "0.19", package = "rustls" }
|
||||
webpki = { version = "0.21.0" }
|
||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
||||
tls-rustls = { package = "rustls", version = "0.20.0" }
|
||||
tokio = { version = "1.2", features = ["net", "rt"] }
|
||||
|
||||
[[example]]
|
||||
name = "ws"
|
||||
|
@ -3,18 +3,18 @@
|
||||
> HTTP primitives for the Actix ecosystem.
|
||||
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://docs.rs/actix-http/3.0.0-beta.10)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||
[](https://docs.rs/actix-http/3.0.0-beta.12)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-http/3.0.0-beta.10)
|
||||
[](https://deps.rs/crate/actix-http/3.0.0-beta.12)
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-http)
|
||||
- Minimum Supported Rust Version (MSRV): 1.51.0
|
||||
- Minimum Supported Rust Version (MSRV): 1.52
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -85,22 +85,31 @@ impl Stream for Heartbeat {
|
||||
fn tls_config() -> rustls::ServerConfig {
|
||||
use std::io::BufReader;
|
||||
|
||||
use rustls::{
|
||||
internal::pemfile::{certs, pkcs8_private_keys},
|
||||
NoClientAuth, ServerConfig,
|
||||
};
|
||||
use rustls::{Certificate, PrivateKey};
|
||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
|
||||
let mut config = ServerConfig::new(NoClientAuth::new());
|
||||
let cert_file = &mut BufReader::new(cert_file.as_bytes());
|
||||
let key_file = &mut BufReader::new(key_file.as_bytes());
|
||||
|
||||
let cert_chain = certs(cert_file).unwrap();
|
||||
let cert_chain = certs(cert_file)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
let mut keys = pkcs8_private_keys(key_file).unwrap();
|
||||
config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
|
||||
|
||||
let mut config = rustls::ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
|
||||
.unwrap();
|
||||
|
||||
config.alpn_protocols.push(b"http/1.1".to_vec());
|
||||
config.alpn_protocols.push(b"h2".to_vec());
|
||||
|
||||
config
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
use std::cell::Cell;
|
||||
use std::fmt::Write;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
use std::{fmt, net};
|
||||
use std::{
|
||||
cell::Cell,
|
||||
fmt::{self, Write},
|
||||
net,
|
||||
rc::Rc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use actix_rt::{
|
||||
task::JoinHandle,
|
||||
time::{interval, sleep_until, Instant, Sleep},
|
||||
};
|
||||
use bytes::BytesMut;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
/// "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)]
|
||||
/// Server keep-alive setting
|
||||
@ -206,12 +207,7 @@ impl Date {
|
||||
|
||||
fn update(&mut self) {
|
||||
self.pos = 0;
|
||||
write!(
|
||||
self,
|
||||
"{}",
|
||||
OffsetDateTime::now_utc().format("%a, %d %b %Y %H:%M:%S GMT")
|
||||
)
|
||||
.unwrap();
|
||||
write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,11 +265,11 @@ impl DateService {
|
||||
}
|
||||
|
||||
// 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
|
||||
/// with `actix_rt::spawn`
|
||||
///
|
||||
/// The target task must explicitly generate `NotifyOnDrop` when spawn the task
|
||||
#[cfg(test)]
|
||||
mod notify_on_drop {
|
||||
use std::cell::RefCell;
|
||||
|
||||
@ -283,9 +279,8 @@ mod notify_on_drop {
|
||||
|
||||
/// Check if the spawned task is dropped.
|
||||
///
|
||||
/// # Panic:
|
||||
///
|
||||
/// When there was no `NotifyOnDrop` instance on current thread
|
||||
/// # Panics
|
||||
/// Panics when there was no `NotifyOnDrop` instance on current thread.
|
||||
pub(crate) fn is_dropped() -> bool {
|
||||
NOTIFY_DROPPED.with(|bool| {
|
||||
bool.borrow()
|
||||
|
@ -303,9 +303,9 @@ where
|
||||
body: &impl MessageBody,
|
||||
) -> Result<BodySize, DispatchError> {
|
||||
let size = body.size();
|
||||
let mut this = self.project();
|
||||
let this = self.project();
|
||||
this.codec
|
||||
.encode(Message::Item((message, size)), &mut this.write_buf)
|
||||
.encode(Message::Item((message, size)), this.write_buf)
|
||||
.map_err(|err| {
|
||||
if let Some(mut payload) = this.payload.take() {
|
||||
payload.set_error(PayloadError::Incomplete(None));
|
||||
@ -425,13 +425,13 @@ where
|
||||
Poll::Ready(Some(Ok(item))) => {
|
||||
this.codec.encode(
|
||||
Message::Chunk(Some(item)),
|
||||
&mut this.write_buf,
|
||||
this.write_buf,
|
||||
)?;
|
||||
}
|
||||
|
||||
Poll::Ready(None) => {
|
||||
this.codec
|
||||
.encode(Message::Chunk(None), &mut this.write_buf)?;
|
||||
.encode(Message::Chunk(None), this.write_buf)?;
|
||||
// payload stream finished.
|
||||
// set state to None and handle next message
|
||||
this.state.set(State::None);
|
||||
@ -460,13 +460,13 @@ where
|
||||
Poll::Ready(Some(Ok(item))) => {
|
||||
this.codec.encode(
|
||||
Message::Chunk(Some(item)),
|
||||
&mut this.write_buf,
|
||||
this.write_buf,
|
||||
)?;
|
||||
}
|
||||
|
||||
Poll::Ready(None) => {
|
||||
this.codec
|
||||
.encode(Message::Chunk(None), &mut this.write_buf)?;
|
||||
.encode(Message::Chunk(None), this.write_buf)?;
|
||||
// payload stream finished.
|
||||
// set state to None and handle next message
|
||||
this.state.set(State::None);
|
||||
@ -592,7 +592,7 @@ where
|
||||
let mut updated = false;
|
||||
let mut this = self.as_mut().project();
|
||||
loop {
|
||||
match this.codec.decode(&mut this.read_buf) {
|
||||
match this.codec.decode(this.read_buf) {
|
||||
Ok(Some(msg)) => {
|
||||
updated = true;
|
||||
this.flags.insert(Flags::STARTED);
|
||||
|
@ -20,6 +20,7 @@ const AVERAGE_HEADER_SIZE: usize = 30;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MessageEncoder<T: MessageType> {
|
||||
#[allow(dead_code)]
|
||||
pub length: BodySize,
|
||||
pub te: TransferEncoding,
|
||||
_phantom: PhantomData<T>,
|
||||
|
@ -10,11 +10,15 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_rt::time::Sleep;
|
||||
use actix_service::Service;
|
||||
use actix_utils::future::poll_fn;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures_core::ready;
|
||||
use h2::server::{Connection, SendResponse};
|
||||
use h2::{
|
||||
server::{Connection, SendResponse},
|
||||
Ping, PingPong,
|
||||
};
|
||||
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
|
||||
use log::{error, trace};
|
||||
use pin_project_lite::pin_project;
|
||||
@ -36,29 +40,46 @@ pin_project! {
|
||||
on_connect_data: OnConnectData,
|
||||
config: ServiceConfig,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
_phantom: PhantomData<B>,
|
||||
ping_pong: Option<H2PingPong>,
|
||||
_phantom: PhantomData<B>
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U> {
|
||||
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
flow: Rc<HttpFlow<S, X, U>>,
|
||||
connection: Connection<T, Bytes>,
|
||||
mut connection: Connection<T, Bytes>,
|
||||
on_connect_data: OnConnectData,
|
||||
config: ServiceConfig,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
) -> Self {
|
||||
let ping_pong = config.keep_alive_timer().map(|timer| H2PingPong {
|
||||
timer: Box::pin(timer),
|
||||
on_flight: false,
|
||||
ping_pong: connection.ping_pong().unwrap(),
|
||||
});
|
||||
|
||||
Self {
|
||||
flow,
|
||||
config,
|
||||
peer_addr,
|
||||
connection,
|
||||
on_connect_data,
|
||||
ping_pong,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct H2PingPong {
|
||||
timer: Pin<Box<Sleep>>,
|
||||
on_flight: bool,
|
||||
ping_pong: PingPong,
|
||||
}
|
||||
|
||||
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
@ -77,54 +98,92 @@ where
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
|
||||
while let Some((req, tx)) =
|
||||
ready!(Pin::new(&mut this.connection).poll_accept(cx)?)
|
||||
{
|
||||
let (parts, body) = req.into_parts();
|
||||
let pl = crate::h2::Payload::new(body);
|
||||
let pl = Payload::<crate::payload::PayloadStream>::H2(pl);
|
||||
let mut req = Request::with_payload(pl);
|
||||
loop {
|
||||
match Pin::new(&mut this.connection).poll_accept(cx)? {
|
||||
Poll::Ready(Some((req, tx))) => {
|
||||
let (parts, body) = req.into_parts();
|
||||
let pl = crate::h2::Payload::new(body);
|
||||
let pl = Payload::<crate::payload::PayloadStream>::H2(pl);
|
||||
let mut req = Request::with_payload(pl);
|
||||
|
||||
let head = req.head_mut();
|
||||
head.uri = parts.uri;
|
||||
head.method = parts.method;
|
||||
head.version = parts.version;
|
||||
head.headers = parts.headers.into();
|
||||
head.peer_addr = this.peer_addr;
|
||||
let head = req.head_mut();
|
||||
head.uri = parts.uri;
|
||||
head.method = parts.method;
|
||||
head.version = parts.version;
|
||||
head.headers = parts.headers.into();
|
||||
head.peer_addr = this.peer_addr;
|
||||
|
||||
// merge on_connect_ext data into request extensions
|
||||
this.on_connect_data.merge_into(&mut req);
|
||||
// merge on_connect_ext data into request extensions
|
||||
this.on_connect_data.merge_into(&mut req);
|
||||
|
||||
let fut = this.flow.service.call(req);
|
||||
let config = this.config.clone();
|
||||
let fut = this.flow.service.call(req);
|
||||
let config = this.config.clone();
|
||||
|
||||
// multiplex request handling with spawn task
|
||||
actix_rt::spawn(async move {
|
||||
// resolve service call and send response.
|
||||
let res = match fut.await {
|
||||
Ok(res) => handle_response(res.into(), tx, config).await,
|
||||
Err(err) => {
|
||||
let res: Response<AnyBody> = err.into();
|
||||
handle_response(res, tx, config).await
|
||||
}
|
||||
};
|
||||
// multiplex request handling with spawn task
|
||||
actix_rt::spawn(async move {
|
||||
// resolve service call and send response.
|
||||
let res = match fut.await {
|
||||
Ok(res) => handle_response(res.into(), tx, config).await,
|
||||
Err(err) => {
|
||||
let res: Response<AnyBody> = err.into();
|
||||
handle_response(res, tx, config).await
|
||||
}
|
||||
};
|
||||
|
||||
// log error.
|
||||
if let Err(err) = res {
|
||||
match err {
|
||||
DispatchError::SendResponse(err) => {
|
||||
trace!("Error sending HTTP/2 response: {:?}", err)
|
||||
// log error.
|
||||
if let Err(err) = res {
|
||||
match err {
|
||||
DispatchError::SendResponse(err) => {
|
||||
trace!("Error sending HTTP/2 response: {:?}", err)
|
||||
}
|
||||
DispatchError::SendData(err) => warn!("{:?}", err),
|
||||
DispatchError::ResponseBody(err) => {
|
||||
error!("Response payload stream error: {:?}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
DispatchError::SendData(err) => warn!("{:?}", err),
|
||||
DispatchError::ResponseBody(err) => {
|
||||
error!("Response payload stream error: {:?}", err)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
Poll::Ready(None) => return Poll::Ready(Ok(())),
|
||||
Poll::Pending => match this.ping_pong.as_mut() {
|
||||
Some(ping_pong) => loop {
|
||||
if ping_pong.on_flight {
|
||||
// When have on flight ping pong. poll pong and and keep alive timer.
|
||||
// on success pong received update keep alive timer to determine the next timing of
|
||||
// ping pong.
|
||||
match ping_pong.ping_pong.poll_pong(cx)? {
|
||||
Poll::Ready(_) => {
|
||||
ping_pong.on_flight = false;
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
let dead_line =
|
||||
this.config.keep_alive_expire().unwrap();
|
||||
ping_pong.timer.as_mut().reset(dead_line);
|
||||
}
|
||||
Poll::Pending => {
|
||||
return ping_pong
|
||||
.timer
|
||||
.as_mut()
|
||||
.poll(cx)
|
||||
.map(|_| Ok(()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// When there is no on flight ping pong. keep alive timer is used to wait for next
|
||||
// timing of ping pong. Therefore at this point it serves as an interval instead.
|
||||
ready!(ping_pong.timer.as_mut().poll(cx));
|
||||
|
||||
ping_pong.ping_pong.send_ping(Ping::opaque())?;
|
||||
|
||||
let dead_line = this.config.keep_alive_expire().unwrap();
|
||||
ping_pong.timer.as_mut().reset(dead_line);
|
||||
|
||||
ping_pong.on_flight = true;
|
||||
}
|
||||
},
|
||||
None => return Poll::Pending,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,7 +177,7 @@ mod rustls {
|
||||
> {
|
||||
let mut protos = vec![b"h2".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.set_protocols(&protos);
|
||||
config.alpn_protocols = protos;
|
||||
|
||||
Acceptor::new(config)
|
||||
.map_err(TlsError::Tls)
|
||||
|
82
actix-http/src/header/shared/http_date.rs
Normal file
82
actix-http/src/header/shared/http_date.rs
Normal file
@ -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 content_encoding;
|
||||
mod extended;
|
||||
mod httpdate;
|
||||
mod http_date;
|
||||
mod quality_item;
|
||||
|
||||
pub use self::charset::Charset;
|
||||
pub use self::content_encoding::ContentEncoding;
|
||||
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 language_tags::LanguageTag;
|
||||
|
@ -29,7 +29,6 @@ extern crate log;
|
||||
|
||||
pub mod body;
|
||||
mod builder;
|
||||
pub mod client;
|
||||
mod config;
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
@ -44,7 +43,6 @@ mod request;
|
||||
mod response;
|
||||
mod response_builder;
|
||||
mod service;
|
||||
mod time_parser;
|
||||
|
||||
pub mod error;
|
||||
pub mod h1;
|
||||
@ -104,14 +102,9 @@ type ConnectCallback<IO> = dyn Fn(&IO, &mut Extensions);
|
||||
///
|
||||
/// # Implementation Details
|
||||
/// Uses Option to reduce necessary allocations when merging with request extensions.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct OnConnectData(Option<Extensions>);
|
||||
|
||||
impl Default for OnConnectData {
|
||||
fn default() -> Self {
|
||||
Self(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl OnConnectData {
|
||||
/// Construct by calling the on-connect callback with the underlying transport I/O.
|
||||
pub(crate) fn from_io<T>(
|
||||
|
@ -263,7 +263,7 @@ mod openssl {
|
||||
mod rustls {
|
||||
use std::io;
|
||||
|
||||
use actix_tls::accept::rustls::{Acceptor, ServerConfig, Session, TlsStream};
|
||||
use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream};
|
||||
use actix_tls::accept::TlsError;
|
||||
|
||||
use super::*;
|
||||
@ -308,14 +308,13 @@ mod rustls {
|
||||
> {
|
||||
let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.set_protocols(&protos);
|
||||
config.alpn_protocols = protos;
|
||||
|
||||
Acceptor::new(config)
|
||||
.map_err(TlsError::Tls)
|
||||
.map_init_err(|_| panic!())
|
||||
.and_then(|io: TlsStream<TcpStream>| async {
|
||||
let proto = if let Some(protos) = io.get_ref().1.get_alpn_protocol()
|
||||
{
|
||||
let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
|
||||
if protos.windows(2).any(|window| window == b"h2") {
|
||||
Protocol::Http2
|
||||
} else {
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
@ -25,8 +25,8 @@ pub fn apply_mask_fast32(buf: &mut [u8], mask: [u8; 4]) {
|
||||
//
|
||||
// un aligned prefix and suffix would be mask/unmask per byte.
|
||||
// proper aligned middle slice goes into fast path and operates on 4-byte blocks.
|
||||
let (mut prefix, words, mut suffix) = unsafe { buf.align_to_mut::<u32>() };
|
||||
apply_mask_fallback(&mut prefix, mask);
|
||||
let (prefix, words, suffix) = unsafe { buf.align_to_mut::<u32>() };
|
||||
apply_mask_fallback(prefix, mask);
|
||||
let head = prefix.len() & 3;
|
||||
let mask_u32 = if head > 0 {
|
||||
if cfg!(target_endian = "big") {
|
||||
@ -40,7 +40,7 @@ pub fn apply_mask_fast32(buf: &mut [u8], mask: [u8; 4]) {
|
||||
for word in words.iter_mut() {
|
||||
*word ^= mask_u32;
|
||||
}
|
||||
apply_mask_fallback(&mut suffix, mask_u32.to_ne_bytes());
|
||||
apply_mask_fallback(suffix, mask_u32.to_ne_bytes());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
77
actix-http/tests/test_h2_ping_pong.rs
Normal file
77
actix-http/tests/test_h2_ping_pong.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use std::io;
|
||||
|
||||
use actix_http::{error::Error, HttpService, Response};
|
||||
use actix_server::Server;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn h2_ping_pong() -> io::Result<()> {
|
||||
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||
|
||||
let lst = std::net::TcpListener::bind("127.0.0.1:0")?;
|
||||
|
||||
let addr = lst.local_addr().unwrap();
|
||||
|
||||
let join = std::thread::spawn(move || {
|
||||
actix_rt::System::new().block_on(async move {
|
||||
let srv = Server::build()
|
||||
.disable_signals()
|
||||
.workers(1)
|
||||
.listen("h2_ping_pong", lst, || {
|
||||
HttpService::build()
|
||||
.keep_alive(3)
|
||||
.h2(|_| async { Ok::<_, Error>(Response::ok()) })
|
||||
.tcp()
|
||||
})?
|
||||
.run();
|
||||
|
||||
tx.send(srv.handle()).unwrap();
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let handle = rx.recv().unwrap();
|
||||
|
||||
let (sync_tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||
|
||||
// use a separate thread for h2 client so it can be blocked.
|
||||
std::thread::spawn(move || {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async move {
|
||||
let stream = tokio::net::TcpStream::connect(addr).await.unwrap();
|
||||
|
||||
let (mut tx, conn) = h2::client::handshake(stream).await.unwrap();
|
||||
|
||||
tokio::spawn(async move { conn.await.unwrap() });
|
||||
|
||||
let (res, _) = tx.send_request(::http::Request::new(()), true).unwrap();
|
||||
let res = res.await.unwrap();
|
||||
|
||||
assert_eq!(res.status().as_u16(), 200);
|
||||
|
||||
sync_tx.send(()).unwrap();
|
||||
|
||||
// intentionally block the client thread so it can not answer ping pong.
|
||||
std::thread::sleep(std::time::Duration::from_secs(1000));
|
||||
})
|
||||
});
|
||||
|
||||
rx.recv().unwrap();
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
// stop server gracefully. this step would take up to 30 seconds.
|
||||
handle.stop(true).await;
|
||||
|
||||
// join server thread. only when connection are all gone this step would finish.
|
||||
join.join().unwrap()?;
|
||||
|
||||
// check the time used for join server thread so it's known that the server shutdown
|
||||
// is from keep alive and not server graceful shutdown timeout.
|
||||
assert!(now.elapsed() < std::time::Duration::from_secs(30));
|
||||
|
||||
Ok(())
|
||||
}
|
@ -8,7 +8,7 @@ use actix_http::{
|
||||
body::{AnyBody, Body, SizedStream},
|
||||
error::PayloadError,
|
||||
http::{
|
||||
header::{self, HeaderName, HeaderValue},
|
||||
header::{self, HeaderValue},
|
||||
Method, StatusCode, Version,
|
||||
},
|
||||
Error, HttpMessage, HttpService, Request, Response,
|
||||
@ -143,38 +143,25 @@ async fn test_h2_content_length() {
|
||||
})
|
||||
.await;
|
||||
|
||||
let header = HeaderName::from_static("content-length");
|
||||
let value = HeaderValue::from_static("0");
|
||||
static VALUE: HeaderValue = HeaderValue::from_static("0");
|
||||
|
||||
{
|
||||
for &i in &[0] {
|
||||
let req = srv
|
||||
.request(Method::HEAD, srv.surl(&format!("/{}", i)))
|
||||
.send();
|
||||
let _response = req.await.expect_err("should timeout on recv 1xx frame");
|
||||
// assert_eq!(response.headers().get(&header), None);
|
||||
let req = srv.request(Method::HEAD, srv.surl("/0")).send();
|
||||
req.await.expect_err("should timeout on recv 1xx frame");
|
||||
|
||||
let req = srv
|
||||
.request(Method::GET, srv.surl(&format!("/{}", i)))
|
||||
.send();
|
||||
let _response = req.await.expect_err("should timeout on recv 1xx frame");
|
||||
// assert_eq!(response.headers().get(&header), None);
|
||||
}
|
||||
let req = srv.request(Method::GET, srv.surl("/0")).send();
|
||||
req.await.expect_err("should timeout on recv 1xx frame");
|
||||
|
||||
for &i in &[1] {
|
||||
let req = srv
|
||||
.request(Method::GET, srv.surl(&format!("/{}", i)))
|
||||
.send();
|
||||
let response = req.await.unwrap();
|
||||
assert_eq!(response.headers().get(&header), None);
|
||||
}
|
||||
let req = srv.request(Method::GET, srv.surl("/1")).send();
|
||||
let response = req.await.unwrap();
|
||||
assert!(response.headers().get("content-length").is_none());
|
||||
|
||||
for &i in &[2, 3] {
|
||||
let req = srv
|
||||
.request(Method::GET, srv.surl(&format!("/{}", i)))
|
||||
.send();
|
||||
let response = req.await.unwrap();
|
||||
assert_eq!(response.headers().get(&header), Some(&value));
|
||||
assert_eq!(response.headers().get("content-length"), Some(&VALUE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
extern crate tls_rustls as rustls;
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
convert::{Infallible, TryFrom},
|
||||
io::{self, BufReader, Write},
|
||||
net::{SocketAddr, TcpStream as StdTcpStream},
|
||||
sync::Arc,
|
||||
@ -20,16 +20,14 @@ use actix_http::{
|
||||
};
|
||||
use actix_http_test::test_server;
|
||||
use actix_service::{fn_factory_with_config, fn_service};
|
||||
use actix_tls::connect::tls::rustls::webpki_roots_cert_store;
|
||||
use actix_utils::future::{err, ok};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use derive_more::{Display, Error};
|
||||
use futures_core::Stream;
|
||||
use futures_util::stream::{once, StreamExt as _};
|
||||
use rustls::{
|
||||
internal::pemfile::{certs, pkcs8_private_keys},
|
||||
NoClientAuth, ServerConfig as RustlsServerConfig, Session,
|
||||
};
|
||||
use webpki::DNSNameRef;
|
||||
use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName};
|
||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
|
||||
async fn load_body<S>(mut stream: S) -> Result<BytesMut, PayloadError>
|
||||
where
|
||||
@ -47,13 +45,24 @@ fn tls_config() -> RustlsServerConfig {
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
|
||||
let mut config = RustlsServerConfig::new(NoClientAuth::new());
|
||||
let cert_file = &mut BufReader::new(cert_file.as_bytes());
|
||||
let key_file = &mut BufReader::new(key_file.as_bytes());
|
||||
|
||||
let cert_chain = certs(cert_file).unwrap();
|
||||
let cert_chain = certs(cert_file)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
let mut keys = pkcs8_private_keys(key_file).unwrap();
|
||||
config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
|
||||
|
||||
let mut config = RustlsServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
|
||||
.unwrap();
|
||||
|
||||
config.alpn_protocols.push(HTTP1_1_ALPN_PROTOCOL.to_vec());
|
||||
config.alpn_protocols.push(H2_ALPN_PROTOCOL.to_vec());
|
||||
|
||||
config
|
||||
}
|
||||
@ -62,19 +71,28 @@ pub fn get_negotiated_alpn_protocol(
|
||||
addr: SocketAddr,
|
||||
client_alpn_protocol: &[u8],
|
||||
) -> Option<Vec<u8>> {
|
||||
let mut config = rustls::ClientConfig::new();
|
||||
let mut config = rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(webpki_roots_cert_store())
|
||||
.with_no_client_auth();
|
||||
|
||||
config.alpn_protocols.push(client_alpn_protocol.to_vec());
|
||||
let mut sess = rustls::ClientSession::new(
|
||||
&Arc::new(config),
|
||||
DNSNameRef::try_from_ascii_str("localhost").unwrap(),
|
||||
);
|
||||
|
||||
let mut sess = rustls::ClientConnection::new(
|
||||
Arc::new(config),
|
||||
ServerName::try_from("localhost").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut sock = StdTcpStream::connect(addr).unwrap();
|
||||
let mut stream = rustls::Stream::new(&mut sess, &mut sock);
|
||||
|
||||
// The handshake will fails because the client will not be able to verify the server
|
||||
// certificate, but it doesn't matter here as we are just interested in the negotiated ALPN
|
||||
// protocol
|
||||
let _ = stream.flush();
|
||||
sess.get_alpn_protocol().map(|proto| proto.to_vec())
|
||||
|
||||
sess.alpn_protocol().map(|proto| proto.to_vec())
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
|
@ -24,7 +24,7 @@ use regex::Regex;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_h1() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.keep_alive(KeepAlive::Disabled)
|
||||
.client_timeout(1000)
|
||||
@ -39,11 +39,13 @@ async fn test_h1() {
|
||||
|
||||
let response = srv.get("/").send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_h1_2() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.keep_alive(KeepAlive::Disabled)
|
||||
.client_timeout(1000)
|
||||
@ -59,6 +61,8 @@ async fn test_h1_2() {
|
||||
|
||||
let response = srv.get("/").send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, Error)]
|
||||
@ -73,7 +77,7 @@ impl From<ExpectFailed> for Response<AnyBody> {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_expect_continue() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.expect(fn_service(|req: Request| {
|
||||
if req.head().uri.query() == Some("yes=") {
|
||||
@ -98,11 +102,13 @@ async fn test_expect_continue() {
|
||||
let mut data = String::new();
|
||||
let _ = stream.read_to_string(&mut data);
|
||||
assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n"));
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_expect_continue_h1() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.expect(fn_service(|req: Request| {
|
||||
sleep(Duration::from_millis(20)).then(move |_| {
|
||||
@ -129,6 +135,8 @@ async fn test_expect_continue_h1() {
|
||||
let mut data = String::new();
|
||||
let _ = stream.read_to_string(&mut data);
|
||||
assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n"));
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@ -136,7 +144,7 @@ async fn test_chunked_payload() {
|
||||
let chunk_sizes = vec![32768, 32, 32768];
|
||||
let total_size: usize = chunk_sizes.iter().sum();
|
||||
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.h1(fn_service(|mut request: Request| {
|
||||
request
|
||||
@ -183,15 +191,18 @@ async fn test_chunked_payload() {
|
||||
Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(),
|
||||
None => panic!("Failed to find size in HTTP Response: {}", data),
|
||||
};
|
||||
|
||||
size
|
||||
};
|
||||
|
||||
assert_eq!(returned_size, total_size);
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_slow_request() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.client_timeout(100)
|
||||
.finish(|_| ok::<_, Infallible>(Response::ok()))
|
||||
@ -204,11 +215,13 @@ async fn test_slow_request() {
|
||||
let mut data = String::new();
|
||||
let _ = stream.read_to_string(&mut data);
|
||||
assert!(data.starts_with("HTTP/1.1 408 Request Timeout"));
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_http1_malformed_request() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
||||
.tcp()
|
||||
@ -220,11 +233,13 @@ async fn test_http1_malformed_request() {
|
||||
let mut data = String::new();
|
||||
let _ = stream.read_to_string(&mut data);
|
||||
assert!(data.starts_with("HTTP/1.1 400 Bad Request"));
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_http1_keepalive() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
||||
.tcp()
|
||||
@ -241,11 +256,13 @@ async fn test_http1_keepalive() {
|
||||
let mut data = vec![0; 1024];
|
||||
let _ = stream.read(&mut data);
|
||||
assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_http1_keepalive_timeout() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.keep_alive(1)
|
||||
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
||||
@ -263,11 +280,13 @@ async fn test_http1_keepalive_timeout() {
|
||||
let mut data = vec![0; 1024];
|
||||
let res = stream.read(&mut data).unwrap();
|
||||
assert_eq!(res, 0);
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_http1_keepalive_close() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
||||
.tcp()
|
||||
@ -284,11 +303,13 @@ async fn test_http1_keepalive_close() {
|
||||
let mut data = vec![0; 1024];
|
||||
let res = stream.read(&mut data).unwrap();
|
||||
assert_eq!(res, 0);
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_http10_keepalive_default_close() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
||||
.tcp()
|
||||
@ -304,11 +325,13 @@ async fn test_http10_keepalive_default_close() {
|
||||
let mut data = vec![0; 1024];
|
||||
let res = stream.read(&mut data).unwrap();
|
||||
assert_eq!(res, 0);
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_http10_keepalive() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
||||
.tcp()
|
||||
@ -331,11 +354,13 @@ async fn test_http10_keepalive() {
|
||||
let mut data = vec![0; 1024];
|
||||
let res = stream.read(&mut data).unwrap();
|
||||
assert_eq!(res, 0);
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_http1_keepalive_disabled() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.keep_alive(KeepAlive::Disabled)
|
||||
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
||||
@ -352,6 +377,8 @@ async fn test_http1_keepalive_disabled() {
|
||||
let mut data = vec![0; 1024];
|
||||
let res = stream.read(&mut data).unwrap();
|
||||
assert_eq!(res, 0);
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@ -361,7 +388,7 @@ async fn test_content_length() {
|
||||
StatusCode,
|
||||
};
|
||||
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.h1(|req: Request| {
|
||||
let indx: usize = req.uri().path()[1..].parse().unwrap();
|
||||
@ -399,6 +426,8 @@ async fn test_content_length() {
|
||||
assert_eq!(response.headers().get(&header), Some(&value));
|
||||
}
|
||||
}
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@ -438,6 +467,8 @@ async fn test_h1_headers() {
|
||||
// read response
|
||||
let bytes = srv.load_body(response).await.unwrap();
|
||||
assert_eq!(bytes, Bytes::from(data2));
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
|
||||
@ -477,6 +508,8 @@ async fn test_h1_body() {
|
||||
// read response
|
||||
let bytes = srv.load_body(response).await.unwrap();
|
||||
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@ -502,6 +535,8 @@ async fn test_h1_head_empty() {
|
||||
// read response
|
||||
let bytes = srv.load_body(response).await.unwrap();
|
||||
assert!(bytes.is_empty());
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@ -527,11 +562,13 @@ async fn test_h1_head_binary() {
|
||||
// read response
|
||||
let bytes = srv.load_body(response).await.unwrap();
|
||||
assert!(bytes.is_empty());
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_h1_head_binary2() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||
.tcp()
|
||||
@ -548,6 +585,8 @@ async fn test_h1_head_binary2() {
|
||||
.unwrap();
|
||||
assert_eq!(format!("{}", STR.len()), len.to_str().unwrap());
|
||||
}
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@ -570,6 +609,8 @@ async fn test_h1_body_length() {
|
||||
// read response
|
||||
let bytes = srv.load_body(response).await.unwrap();
|
||||
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@ -605,6 +646,8 @@ async fn test_h1_body_chunked_explicit() {
|
||||
|
||||
// decode
|
||||
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@ -634,6 +677,8 @@ async fn test_h1_body_chunked_implicit() {
|
||||
// read response
|
||||
let bytes = srv.load_body(response).await.unwrap();
|
||||
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@ -661,6 +706,8 @@ async fn test_h1_response_http_error_handling() {
|
||||
bytes,
|
||||
Bytes::from_static(b"error processing HTTP: failed to parse header value")
|
||||
);
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, Error)]
|
||||
@ -688,11 +735,13 @@ async fn test_h1_service_error() {
|
||||
// read response
|
||||
let bytes = srv.load_body(response).await.unwrap();
|
||||
assert_eq!(bytes, Bytes::from_static(b"error"));
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_h1_on_connect() {
|
||||
let srv = test_server(|| {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.on_connect_ext(|_, data| {
|
||||
data.insert(20isize);
|
||||
@ -707,4 +756,6 @@ async fn test_h1_on_connect() {
|
||||
|
||||
let response = srv.get("/").send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
@ -3,6 +3,10 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.4.0-beta.7 - 2021-10-20
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
|
||||
## 0.4.0-beta.6 - 2021-09-09
|
||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-multipart"
|
||||
version = "0.4.0-beta.6"
|
||||
version = "0.4.0-beta.7"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Multipart form support for Actix Web"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
@ -14,7 +14,7 @@ name = "actix_multipart"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = { version = "4.0.0-beta.9", default-features = false }
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false }
|
||||
actix-utils = "3.0.0"
|
||||
|
||||
bytes = "1"
|
||||
@ -29,6 +29,6 @@ twoway = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-http = "3.0.0-beta.10"
|
||||
actix-http = "3.0.0-beta.12"
|
||||
tokio = { version = "1", features = ["sync"] }
|
||||
tokio-stream = "0.1"
|
||||
|
@ -3,15 +3,15 @@
|
||||
> Multipart form support for Actix Web.
|
||||
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
[](https://docs.rs/actix-multipart/0.4.0-beta.6)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||
[](https://docs.rs/actix-multipart/0.4.0-beta.7)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.6)
|
||||
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.7)
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-multipart)
|
||||
- Minimum Supported Rust Version (MSRV): 1.51.0
|
||||
- Minimum Supported Rust Version (MSRV): 1.52
|
||||
|
@ -33,7 +33,6 @@ use crate::server::Multipart;
|
||||
impl FromRequest for Multipart {
|
||||
type Error = Error;
|
||||
type Future = Ready<Result<Multipart, Error>>;
|
||||
type Config = ();
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
|
||||
## 0.5.0-beta.2 - 2021-09-09
|
||||
|
@ -30,7 +30,7 @@ serde = "1"
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
firestorm = { version = "0.4", features = ["enable_system_time"] }
|
||||
http = "0.2.3"
|
||||
http = "0.2.5"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
[[bench]]
|
||||
|
@ -394,9 +394,7 @@ impl ResourceDef {
|
||||
pub fn set_name(&mut self, name: impl Into<String>) {
|
||||
let name = name.into();
|
||||
|
||||
if name.is_empty() {
|
||||
panic!("resource name should not be empty");
|
||||
}
|
||||
assert!(!name.is_empty(), "resource name should not be empty");
|
||||
|
||||
self.name = Some(name)
|
||||
}
|
||||
@ -978,9 +976,7 @@ impl ResourceDef {
|
||||
|
||||
let (name, pattern) = match param.find(':') {
|
||||
Some(idx) => {
|
||||
if tail {
|
||||
panic!("custom regex is not supported for tail match");
|
||||
}
|
||||
assert!(!tail, "custom regex is not supported for tail match");
|
||||
|
||||
let (name, pattern) = param.split_at(idx);
|
||||
(name, &pattern[1..])
|
||||
@ -1087,12 +1083,12 @@ impl ResourceDef {
|
||||
re.push_str(&escape(unprocessed));
|
||||
}
|
||||
|
||||
if dyn_segment_count > MAX_DYNAMIC_SEGMENTS {
|
||||
panic!(
|
||||
"Only {} dynamic segments are allowed, provided: {}",
|
||||
MAX_DYNAMIC_SEGMENTS, dyn_segment_count
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
dyn_segment_count <= MAX_DYNAMIC_SEGMENTS,
|
||||
"Only {} dynamic segments are allowed, provided: {}",
|
||||
MAX_DYNAMIC_SEGMENTS,
|
||||
dyn_segment_count
|
||||
);
|
||||
|
||||
// Store the pattern in capture group #1 to have context info outside it
|
||||
let mut re = format!("({})", re);
|
||||
|
@ -6,8 +6,9 @@ use crate::{IntoPatterns, Resource, ResourceDef, ResourcePath};
|
||||
pub struct ResourceId(pub u16);
|
||||
|
||||
/// Information about current resource
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResourceInfo {
|
||||
#[allow(dead_code)]
|
||||
resource: ResourceId,
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,17 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.1.0-beta.6 - 2021-11-15
|
||||
* No significant changes from `0.1.0-beta.5`.
|
||||
|
||||
|
||||
## 0.1.0-beta.5 - 2021-10-20
|
||||
* Updated rustls to v0.20. [#2414]
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
[#2414]: https://github.com/actix/actix-web/pull/2414
|
||||
|
||||
|
||||
## 0.1.0-beta.4 - 2021-09-09
|
||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
||||
|
@ -1,32 +1,41 @@
|
||||
[package]
|
||||
name = "actix-test"
|
||||
version = "0.1.0-beta.4"
|
||||
version = "0.1.0-beta.6"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
edition = "2018"
|
||||
description = "Integration testing tools for Actix Web applications"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
categories = [
|
||||
"network-programming",
|
||||
"asynchronous",
|
||||
"web-programming::http-server",
|
||||
"web-programming::websocket",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# rustls
|
||||
rustls = ["tls-rustls", "actix-http/rustls"]
|
||||
rustls = ["tls-rustls", "actix-http/rustls", "awc/rustls"]
|
||||
|
||||
# openssl
|
||||
openssl = ["tls-openssl", "actix-http/openssl"]
|
||||
openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.4.0"
|
||||
actix-http = "3.0.0-beta.10"
|
||||
actix-http-test = "3.0.0-beta.5"
|
||||
actix-http = "3.0.0-beta.12"
|
||||
actix-http-test = "3.0.0-beta.6"
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["cookies"] }
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] }
|
||||
actix-rt = "2.1"
|
||||
awc = { version = "3.0.0-beta.8", default-features = false, features = ["cookies"] }
|
||||
awc = { version = "3.0.0-beta.10", default-features = false, features = ["cookies"] }
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||
futures-util = { version = "0.3.7", default-features = false, features = [] }
|
||||
@ -35,4 +44,5 @@ serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_urlencoded = "0.7"
|
||||
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
|
||||
tls-rustls = { package = "rustls", version = "0.19.0", optional = true }
|
||||
tls-rustls = { package = "rustls", version = "0.20.0", optional = true }
|
||||
tokio = { version = "1.2", features = ["sync"] }
|
||||
|
@ -31,7 +31,7 @@ extern crate tls_openssl as openssl;
|
||||
#[cfg(feature = "rustls")]
|
||||
extern crate tls_rustls as rustls;
|
||||
|
||||
use std::{error::Error as StdError, fmt, net, sync::mpsc, thread, time};
|
||||
use std::{error::Error as StdError, fmt, net, thread, time::Duration};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
pub use actix_http::test::TestBuffer;
|
||||
@ -41,8 +41,9 @@ use actix_http::{
|
||||
};
|
||||
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
|
||||
use actix_web::{
|
||||
dev::{AppConfig, MessageBody, Server, Service},
|
||||
rt, web, Error,
|
||||
dev::{AppConfig, MessageBody, Server, ServerHandle, Service},
|
||||
rt::{self, System},
|
||||
web, Error,
|
||||
};
|
||||
use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
|
||||
use futures_core::Stream;
|
||||
@ -52,6 +53,7 @@ pub use actix_web::test::{
|
||||
call_service, default_service, init_service, load_stream, ok_service, read_body,
|
||||
read_body_json, read_response, read_response_json, TestRequest,
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// Start default [`TestServer`].
|
||||
///
|
||||
@ -64,7 +66,7 @@ pub use actix_web::test::{
|
||||
/// Ok(HttpResponse::Ok())
|
||||
/// }
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// #[actix_web::test]
|
||||
/// async fn test_example() {
|
||||
/// let srv = actix_test::start(||
|
||||
/// App::new().service(my_handler)
|
||||
@ -104,7 +106,7 @@ where
|
||||
/// Ok(HttpResponse::Ok())
|
||||
/// }
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// #[actix_web::test]
|
||||
/// async fn test_example() {
|
||||
/// let srv = actix_test::start_with(actix_test::config().h1(), ||
|
||||
/// App::new().service(my_handler)
|
||||
@ -128,7 +130,11 @@ where
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel();
|
||||
// for sending handles and server info back from the spawned thread
|
||||
let (started_tx, started_rx) = std::sync::mpsc::channel();
|
||||
|
||||
// for signaling the shutdown of spawned server and system
|
||||
let (thread_stop_tx, thread_stop_rx) = mpsc::channel(1);
|
||||
|
||||
let tls = match cfg.stream {
|
||||
StreamType::Tcp => false,
|
||||
@ -138,7 +144,7 @@ where
|
||||
StreamType::Rustls(_) => true,
|
||||
};
|
||||
|
||||
// run server in separate thread
|
||||
// run server in separate orphaned thread
|
||||
thread::spawn(move || {
|
||||
let sys = rt::System::new();
|
||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
@ -146,7 +152,7 @@ where
|
||||
let factory = factory.clone();
|
||||
let srv_cfg = cfg.clone();
|
||||
let timeout = cfg.client_timeout;
|
||||
let builder = Server::build().workers(1).disable_signals();
|
||||
let builder = Server::build().workers(1).disable_signals().system_exit();
|
||||
|
||||
let srv = match srv_cfg.stream {
|
||||
StreamType::Tcp => match srv_cfg.tp {
|
||||
@ -275,17 +281,25 @@ where
|
||||
}),
|
||||
},
|
||||
}
|
||||
.unwrap();
|
||||
.expect("test server could not be created");
|
||||
|
||||
sys.block_on(async {
|
||||
let srv = srv.run();
|
||||
tx.send((rt::System::current(), srv, local_addr)).unwrap();
|
||||
});
|
||||
let srv = srv.run();
|
||||
started_tx
|
||||
.send((System::current(), srv.handle(), local_addr))
|
||||
.unwrap();
|
||||
|
||||
sys.run()
|
||||
// drive server loop
|
||||
sys.block_on(srv).unwrap();
|
||||
|
||||
// start system event loop
|
||||
sys.run().unwrap();
|
||||
|
||||
// notify TestServer that server and system have shut down
|
||||
// all thread managed resources should be dropped at this point
|
||||
let _ = thread_stop_tx.send(());
|
||||
});
|
||||
|
||||
let (system, server, addr) = rx.recv().unwrap();
|
||||
let (system, server, addr) = started_rx.recv().unwrap();
|
||||
|
||||
let client = {
|
||||
let connector = {
|
||||
@ -299,15 +313,15 @@ where
|
||||
.set_alpn_protos(b"\x02h2\x08http/1.1")
|
||||
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
|
||||
Connector::new()
|
||||
.conn_lifetime(time::Duration::from_secs(0))
|
||||
.timeout(time::Duration::from_millis(30000))
|
||||
.conn_lifetime(Duration::from_secs(0))
|
||||
.timeout(Duration::from_millis(30000))
|
||||
.ssl(builder.build())
|
||||
}
|
||||
#[cfg(not(feature = "openssl"))]
|
||||
{
|
||||
Connector::new()
|
||||
.conn_lifetime(time::Duration::from_secs(0))
|
||||
.timeout(time::Duration::from_millis(30000))
|
||||
.conn_lifetime(Duration::from_secs(0))
|
||||
.timeout(Duration::from_millis(30000))
|
||||
}
|
||||
};
|
||||
|
||||
@ -315,11 +329,12 @@ where
|
||||
};
|
||||
|
||||
TestServer {
|
||||
addr,
|
||||
server,
|
||||
thread_stop_rx,
|
||||
client,
|
||||
system,
|
||||
addr,
|
||||
tls,
|
||||
server,
|
||||
}
|
||||
}
|
||||
|
||||
@ -405,11 +420,12 @@ impl TestServerConfig {
|
||||
///
|
||||
/// See [`start`] for usage example.
|
||||
pub struct TestServer {
|
||||
addr: net::SocketAddr,
|
||||
server: ServerHandle,
|
||||
thread_stop_rx: mpsc::Receiver<()>,
|
||||
client: awc::Client,
|
||||
system: rt::System,
|
||||
addr: net::SocketAddr,
|
||||
tls: bool,
|
||||
server: Server,
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
@ -505,15 +521,30 @@ impl TestServer {
|
||||
}
|
||||
|
||||
/// Gracefully stop HTTP server.
|
||||
pub async fn stop(self) {
|
||||
///
|
||||
/// Waits for spawned `Server` and `System` to shutdown gracefully.
|
||||
pub async fn stop(mut self) {
|
||||
// signal server to stop
|
||||
self.server.stop(true).await;
|
||||
|
||||
// also signal system to stop
|
||||
// though this is handled by `ServerBuilder::exit_system` too
|
||||
self.system.stop();
|
||||
rt::time::sleep(time::Duration::from_millis(100)).await;
|
||||
|
||||
// wait for thread to be stopped but don't care about result
|
||||
let _ = self.thread_stop_rx.recv().await;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestServer {
|
||||
fn drop(&mut self) {
|
||||
self.system.stop()
|
||||
// calls in this Drop impl should be enough to shut down the server, system, and thread
|
||||
// without needing to await anything
|
||||
|
||||
// signal server to stop
|
||||
let _ = self.server.stop(true);
|
||||
|
||||
// signal system to stop
|
||||
self.system.stop();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
|
||||
## 4.0.0-beta.7 - 2021-09-09
|
||||
|
@ -16,8 +16,8 @@ path = "src/lib.rs"
|
||||
[dependencies]
|
||||
actix = { version = "0.12.0", default-features = false }
|
||||
actix-codec = "0.4.0"
|
||||
actix-http = "3.0.0-beta.10"
|
||||
actix-web = { version = "4.0.0-beta.9", default-features = false }
|
||||
actix-http = "3.0.0-beta.12"
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false }
|
||||
|
||||
bytes = "1"
|
||||
bytestring = "1"
|
||||
@ -27,8 +27,8 @@ tokio = { version = "1", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-test = "0.1.0-beta.3"
|
||||
actix-test = "0.1.0-beta.6"
|
||||
|
||||
awc = { version = "3.0.0-beta.8", default-features = false }
|
||||
awc = { version = "3.0.0-beta.10", default-features = false }
|
||||
env_logger = "0.8"
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
[](https://crates.io/crates/actix-web-actors)
|
||||
[](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/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7)
|
||||
@ -14,4 +14,4 @@
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-web-actors)
|
||||
- Minimum supported Rust version: 1.51 or later
|
||||
- Minimum Supported Rust Version (MSRV): 1.52
|
||||
|
@ -3,6 +3,15 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.5.0-beta.5 - 2021-10-20
|
||||
* Improve error recovery potential when macro input is invalid. [#2410]
|
||||
* Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409]
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
[#2410]: https://github.com/actix/actix-web/pull/2410
|
||||
[#2409]: https://github.com/actix/actix-web/pull/2409
|
||||
|
||||
|
||||
## 0.5.0-beta.4 - 2021-09-09
|
||||
* In routing macros, paths are now validated at compile time. [#2350]
|
||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
@ -1,12 +1,13 @@
|
||||
[package]
|
||||
name = "actix-web-codegen"
|
||||
version = "0.5.0-beta.4"
|
||||
version = "0.5.0-beta.5"
|
||||
description = "Routing and runtime macros for Actix Web"
|
||||
readme = "README.md"
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
documentation = "https://docs.rs/actix-web-codegen"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
@ -21,9 +22,10 @@ actix-router = "0.5.0-beta.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-test = "0.1.0-beta.3"
|
||||
actix-macros = "0.2.3"
|
||||
actix-test = "0.1.0-beta.6"
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = "4.0.0-beta.9"
|
||||
actix-web = "4.0.0-beta.11"
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
trybuild = "1"
|
||||
|
@ -3,18 +3,18 @@
|
||||
> Routing and runtime macros for Actix Web.
|
||||
|
||||
[](https://crates.io/crates/actix-web-codegen)
|
||||
[](https://docs.rs/actix-web-codegen/0.5.0-beta.4)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||
[](https://docs.rs/actix-web-codegen/0.5.0-beta.5)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.4)
|
||||
[](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.5)
|
||||
[](https://crates.io/crates/actix-web-codegen)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-web-codegen)
|
||||
- Minimum supported Rust version: 1.51 or later.
|
||||
- Minimum Supported Rust Version (MSRV): 1.52
|
||||
|
||||
## Compile Testing
|
||||
|
||||
|
@ -59,6 +59,7 @@
|
||||
#![recursion_limit = "512"]
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
mod route;
|
||||
|
||||
@ -157,24 +158,41 @@ method_macro! {
|
||||
}
|
||||
|
||||
/// Marks async main function as the actix system entry-point.
|
||||
///
|
||||
/// # Actix Web Re-export
|
||||
/// This macro can be applied with `#[actix_web::main]` when used in Actix Web applications.
|
||||
///
|
||||
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// #[actix_web_codegen::main]
|
||||
/// #[actix_web::main]
|
||||
/// async fn main() {
|
||||
/// async { println!("Hello world"); }.await
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||
use quote::quote;
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
(quote! {
|
||||
#[actix_web::rt::main(system = "::actix_web::rt::System")]
|
||||
#input
|
||||
let mut output: TokenStream = (quote! {
|
||||
#[::actix_web::rt::main(system = "::actix_web::rt::System")]
|
||||
})
|
||||
.into()
|
||||
.into();
|
||||
|
||||
output.extend(item);
|
||||
output
|
||||
}
|
||||
|
||||
/// Marks async test functions to use the actix system entry-point.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// #[actix_web::test]
|
||||
/// async fn test() {
|
||||
/// assert_eq!(async { "Hello world" }.await, "Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut output: TokenStream = (quote! {
|
||||
#[::actix_web::rt::test(system = "::actix_web::rt::System")]
|
||||
})
|
||||
.into();
|
||||
|
||||
output.extend(item);
|
||||
output
|
||||
}
|
||||
|
@ -220,7 +220,7 @@ fn guess_resource_type(typ: &syn::Type) -> ResourceType {
|
||||
impl Route {
|
||||
pub fn new(
|
||||
args: AttributeArgs,
|
||||
input: TokenStream,
|
||||
ast: syn::ItemFn,
|
||||
method: Option<MethodType>,
|
||||
) -> syn::Result<Self> {
|
||||
if args.is_empty() {
|
||||
@ -234,14 +234,11 @@ impl Route {
|
||||
),
|
||||
));
|
||||
}
|
||||
let ast: syn::ItemFn = syn::parse(input)?;
|
||||
|
||||
let name = ast.sig.ident.clone();
|
||||
|
||||
// Try and pull out the doc comments so that we can reapply them to the
|
||||
// generated struct.
|
||||
//
|
||||
// Note that multi line doc comments are converted to multiple doc
|
||||
// attributes.
|
||||
// Try and pull out the doc comments so that we can reapply them to the generated struct.
|
||||
// Note that multi line doc comments are converted to multiple doc attributes.
|
||||
let doc_attributes = ast
|
||||
.attrs
|
||||
.iter()
|
||||
@ -349,8 +346,28 @@ pub(crate) fn with_method(
|
||||
input: TokenStream,
|
||||
) -> TokenStream {
|
||||
let args = parse_macro_input!(args as syn::AttributeArgs);
|
||||
match Route::new(args, input, method) {
|
||||
|
||||
let ast = match syn::parse::<syn::ItemFn>(input.clone()) {
|
||||
Ok(ast) => ast,
|
||||
// on parse error, make IDEs happy; see fn docs
|
||||
Err(err) => return input_and_compile_error(input, err),
|
||||
};
|
||||
|
||||
match Route::new(args, ast, method) {
|
||||
Ok(route) => route.into_token_stream().into(),
|
||||
Err(err) => err.to_compile_error().into(),
|
||||
// on macro related error, make IDEs happy; see fn docs
|
||||
Err(err) => input_and_compile_error(input, err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the error to a token stream and appends it to the original input.
|
||||
///
|
||||
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
||||
/// recover and show more precise errors within the macro body.
|
||||
///
|
||||
/// See <https://github.com/rust-analyzer/rust-analyzer/issues/10468> for more info.
|
||||
fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
|
||||
let compile_err = TokenStream::from(err.to_compile_error());
|
||||
item.extend(compile_err);
|
||||
item
|
||||
}
|
||||
|
@ -256,7 +256,7 @@ async fn test_auto_async() {
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
#[actix_web::test]
|
||||
async fn test_wrap() {
|
||||
let srv = actix_test::start(|| App::new().service(get_wrap));
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#[rustversion::stable(1.51)] // MSRV
|
||||
#[rustversion::stable(1.52)] // MSRV
|
||||
#[test]
|
||||
fn compile_macros() {
|
||||
let t = trybuild::TestCases::new();
|
||||
@ -13,4 +13,6 @@ fn compile_macros() {
|
||||
t.compile_fail("tests/trybuild/route-malformed-path-fail.rs");
|
||||
|
||||
t.pass("tests/trybuild/docstring-ok.rs");
|
||||
|
||||
t.pass("tests/trybuild/test-runtime.rs");
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ error: HTTP method defined more than once: `GET`
|
||||
3 | #[route("/", method="GET", method="GET")]
|
||||
| ^^^^^
|
||||
|
||||
error[E0425]: cannot find value `index` in this scope
|
||||
error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied
|
||||
--> $DIR/route-duplicate-method-fail.rs:12:55
|
||||
|
|
||||
12 | let srv = actix_test::start(|| App::new().service(index));
|
||||
| ^^^^^ not found in this scope
|
||||
| ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}`
|
||||
|
@ -6,8 +6,8 @@ error: The #[route(..)] macro requires at least one `method` attribute
|
||||
|
|
||||
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0425]: cannot find value `index` in this scope
|
||||
error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied
|
||||
--> $DIR/route-missing-method-fail.rs:12:55
|
||||
|
|
||||
12 | let srv = actix_test::start(|| App::new().service(index));
|
||||
| ^^^^^ not found in this scope
|
||||
| ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}`
|
||||
|
@ -4,8 +4,8 @@ error: Unexpected HTTP method: `UNEXPECTED`
|
||||
3 | #[route("/", method="UNEXPECTED")]
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error[E0425]: cannot find value `index` in this scope
|
||||
error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied
|
||||
--> $DIR/route-unexpected-method-fail.rs:12:55
|
||||
|
|
||||
12 | let srv = actix_test::start(|| App::new().service(index));
|
||||
| ^^^^^ not found in this scope
|
||||
| ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}`
|
||||
|
6
actix-web-codegen/tests/trybuild/test-runtime.rs
Normal file
6
actix-web-codegen/tests/trybuild/test-runtime.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#[actix_web::test]
|
||||
async fn my_test() {
|
||||
assert!(async { 1 }.await, 1);
|
||||
}
|
||||
|
||||
fn main() {}
|
@ -3,6 +3,15 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 3.0.0-beta.10 - 2021-11-15
|
||||
|
||||
|
||||
## 3.0.0-beta.9 - 2021-10-20
|
||||
* Updated rustls to v0.20. [#2414]
|
||||
|
||||
[#2414]: https://github.com/actix/actix-web/pull/2414
|
||||
|
||||
|
||||
## 3.0.0-beta.8 - 2021-09-09
|
||||
### Changed
|
||||
* Send headers within the redirect requests. [#2310]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "awc"
|
||||
version = "3.0.0-beta.8"
|
||||
version = "3.0.0-beta.10"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
@ -14,7 +14,7 @@ categories = [
|
||||
"web-programming::websocket",
|
||||
]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
@ -30,10 +30,10 @@ features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-z
|
||||
default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
|
||||
|
||||
# openssl
|
||||
openssl = ["tls-openssl", "actix-http/openssl"]
|
||||
openssl = ["tls-openssl", "actix-tls/openssl"]
|
||||
|
||||
# rustls
|
||||
rustls = ["tls-rustls", "actix-http/rustls"]
|
||||
rustls = ["tls-rustls", "actix-tls/rustls"]
|
||||
|
||||
# Brotli algorithm content-encoding support
|
||||
compress-brotli = ["actix-http/compress-brotli", "__compress"]
|
||||
@ -46,7 +46,7 @@ compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
||||
cookies = ["cookie"]
|
||||
|
||||
# trust-dns as dns resolver
|
||||
trust-dns = ["actix-http/trust-dns"]
|
||||
trust-dns = ["trust-dns-resolver"]
|
||||
|
||||
# Internal (PRIVATE!) features used to aid testing and cheking feature status.
|
||||
# Don't rely on these whatsoever. They may disappear at anytime.
|
||||
@ -55,15 +55,20 @@ __compress = []
|
||||
[dependencies]
|
||||
actix-codec = "0.4.0"
|
||||
actix-service = "2.0.0"
|
||||
actix-http = "3.0.0-beta.10"
|
||||
actix-http = "3.0.0-beta.12"
|
||||
actix-rt = { version = "2.1", default-features = false }
|
||||
actix-tls = { version = "3.0.0-beta.7", features = ["connect"] }
|
||||
actix-utils = "3.0.0"
|
||||
|
||||
ahash = "0.7"
|
||||
base64 = "0.13"
|
||||
bytes = "1"
|
||||
cfg-if = "1"
|
||||
cookie = { version = "0.15", features = ["percent-encode"], optional = true }
|
||||
derive_more = "0.99.5"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
h2 = "0.3"
|
||||
http = "0.2.5"
|
||||
itoa = "0.4"
|
||||
log =" 0.4"
|
||||
mime = "0.3"
|
||||
@ -73,24 +78,30 @@ rand = "0.8"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.7"
|
||||
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||
tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
|
||||
tokio = { version = "1", features = ["sync"] }
|
||||
|
||||
cookie = { version = "0.15", features = ["percent-encode"], optional = true }
|
||||
|
||||
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
|
||||
tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] }
|
||||
|
||||
trust-dns-resolver = { version = "0.20.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = { version = "4.0.0-beta.9", features = ["openssl"] }
|
||||
actix-http = { version = "3.0.0-beta.10", features = ["openssl"] }
|
||||
actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] }
|
||||
actix-web = { version = "4.0.0-beta.11", features = ["openssl"] }
|
||||
actix-http = { version = "3.0.0-beta.12", features = ["openssl"] }
|
||||
actix-http-test = { version = "3.0.0-beta.6", features = ["openssl"] }
|
||||
actix-utils = "3.0.0"
|
||||
actix-server = "2.0.0-beta.3"
|
||||
actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] }
|
||||
actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] }
|
||||
actix-server = "2.0.0-beta.9"
|
||||
actix-tls = { version = "3.0.0-beta.7", features = ["openssl", "rustls"] }
|
||||
actix-test = { version = "0.1.0-beta.6", features = ["openssl", "rustls"] }
|
||||
|
||||
brotli2 = "0.3.2"
|
||||
env_logger = "0.8"
|
||||
flate2 = "1.0.13"
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
rcgen = "0.8"
|
||||
webpki = "0.21"
|
||||
rustls-pemfile = "0.2"
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
|
@ -3,16 +3,16 @@
|
||||
> Async HTTP and WebSocket client library.
|
||||
|
||||
[](https://crates.io/crates/awc)
|
||||
[](https://docs.rs/awc/3.0.0-beta.8)
|
||||
[](https://docs.rs/awc/3.0.0-beta.10)
|
||||

|
||||
[](https://deps.rs/crate/awc/3.0.0-beta.8)
|
||||
[](https://deps.rs/crate/awc/3.0.0-beta.10)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/awc)
|
||||
- [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https)
|
||||
- Minimum Supported Rust Version (MSRV): 1.51.0
|
||||
- Minimum Supported Rust Version (MSRV): 1.52
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -4,13 +4,11 @@ use std::net::IpAddr;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use actix_http::{
|
||||
client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection},
|
||||
http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri},
|
||||
};
|
||||
use actix_http::http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri};
|
||||
use actix_rt::net::{ActixStream, TcpStream};
|
||||
use actix_service::{boxed, Service};
|
||||
|
||||
use crate::client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection};
|
||||
use crate::connect::DefaultConnector;
|
||||
use crate::error::SendRequestError;
|
||||
use crate::middleware::{NestTransform, Redirect, Transform};
|
||||
|
@ -1,5 +1,4 @@
|
||||
use std::net::IpAddr;
|
||||
use std::time::Duration;
|
||||
use std::{net::IpAddr, time::Duration};
|
||||
|
||||
const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2MB
|
||||
const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1MB
|
@ -12,10 +12,9 @@ use bytes::Bytes;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use h2::client::SendRequest;
|
||||
|
||||
use crate::h1::ClientCodec;
|
||||
use crate::message::{RequestHeadType, ResponseHead};
|
||||
use crate::payload::Payload;
|
||||
use crate::{body::MessageBody, Error};
|
||||
use actix_http::{
|
||||
body::MessageBody, h1::ClientCodec, Error, Payload, RequestHeadType, ResponseHead,
|
||||
};
|
||||
|
||||
use super::error::SendRequestError;
|
||||
use super::pool::Acquired;
|
||||
@ -174,6 +173,7 @@ impl H2ConnectionInner {
|
||||
/// Cancel spawned connection task on drop.
|
||||
impl Drop for H2ConnectionInner {
|
||||
fn drop(&mut self) {
|
||||
// TODO: this can end up sending extraneous requests; see if there is a better way to handle
|
||||
if self
|
||||
.sender
|
||||
.send_request(http::Request::new(()), true)
|
||||
@ -184,8 +184,8 @@ impl Drop for H2ConnectionInner {
|
||||
}
|
||||
}
|
||||
|
||||
/// Unified connection type cover HTTP/1 Plain/TLS and HTTP/2 protocols.
|
||||
#[allow(dead_code)]
|
||||
/// Unified connection type cover Http1 Plain/Tls and Http2 protocols
|
||||
pub enum Connection<A, B = Box<dyn ConnectionIo>>
|
||||
where
|
||||
A: ConnectionIo,
|
||||
@ -219,11 +219,7 @@ impl<Io: ConnectionIo> ConnectionType<Io> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn from_h1(
|
||||
io: Io,
|
||||
created: time::Instant,
|
||||
acquired: Acquired<Io>,
|
||||
) -> Self {
|
||||
pub(super) fn from_h1(io: Io, created: time::Instant, acquired: Acquired<Io>) -> Self {
|
||||
Self::H1(H1Connection {
|
||||
io: Some(io),
|
||||
created,
|
||||
@ -271,9 +267,7 @@ where
|
||||
Connection::Tls(ConnectionType::H2(conn)) => {
|
||||
h2proto::send_request(conn, head.into(), body).await
|
||||
}
|
||||
_ => unreachable!(
|
||||
"Plain Tcp connection can be used only in Http1 protocol"
|
||||
),
|
||||
_ => unreachable!("Plain Tcp connection can be used only in Http1 protocol"),
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -301,9 +295,7 @@ where
|
||||
Err(SendRequestError::TunnelNotSupported)
|
||||
}
|
||||
Connection::Tcp(ConnectionType::H2(_)) => {
|
||||
unreachable!(
|
||||
"Plain Tcp connection can be used only in Http1 protocol"
|
||||
)
|
||||
unreachable!("Plain Tcp connection can be used only in Http1 protocol")
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -321,12 +313,8 @@ where
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
match self.get_mut() {
|
||||
Connection::Tcp(ConnectionType::H1(conn)) => {
|
||||
Pin::new(conn).poll_read(cx, buf)
|
||||
}
|
||||
Connection::Tls(ConnectionType::H1(conn)) => {
|
||||
Pin::new(conn).poll_read(cx, buf)
|
||||
}
|
||||
Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_read(cx, buf),
|
||||
Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_read(cx, buf),
|
||||
_ => unreachable!("H2Connection can not impl AsyncRead trait"),
|
||||
}
|
||||
}
|
||||
@ -345,12 +333,8 @@ where
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
match self.get_mut() {
|
||||
Connection::Tcp(ConnectionType::H1(conn)) => {
|
||||
Pin::new(conn).poll_write(cx, buf)
|
||||
}
|
||||
Connection::Tls(ConnectionType::H1(conn)) => {
|
||||
Pin::new(conn).poll_write(cx, buf)
|
||||
}
|
||||
Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_write(cx, buf),
|
||||
Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_write(cx, buf),
|
||||
_ => unreachable!(H2_UNREACHABLE_WRITE),
|
||||
}
|
||||
}
|
||||
@ -363,17 +347,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
match self.get_mut() {
|
||||
Connection::Tcp(ConnectionType::H1(conn)) => {
|
||||
Pin::new(conn).poll_shutdown(cx)
|
||||
}
|
||||
Connection::Tls(ConnectionType::H1(conn)) => {
|
||||
Pin::new(conn).poll_shutdown(cx)
|
||||
}
|
||||
Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_shutdown(cx),
|
||||
Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_shutdown(cx),
|
||||
_ => unreachable!(H2_UNREACHABLE_WRITE),
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use actix_http::Protocol;
|
||||
use actix_rt::{
|
||||
net::{ActixStream, TcpStream},
|
||||
time::{sleep, Sleep},
|
||||
@ -19,27 +20,21 @@ use actix_tls::connect::{
|
||||
};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use http::Uri;
|
||||
use pin_project::pin_project;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::config::ConnectorConfig;
|
||||
use super::connection::{Connection, ConnectionIo};
|
||||
use super::error::ConnectError;
|
||||
use super::pool::ConnectionPool;
|
||||
use super::Connect;
|
||||
use super::Protocol;
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector;
|
||||
#[cfg(feature = "rustls")]
|
||||
use actix_tls::connect::ssl::rustls::ClientConfig;
|
||||
|
||||
enum SslConnector {
|
||||
#[allow(dead_code)]
|
||||
None,
|
||||
#[cfg(feature = "openssl")]
|
||||
Openssl(OpensslConnector),
|
||||
Openssl(actix_tls::connect::ssl::openssl::SslConnector),
|
||||
#[cfg(feature = "rustls")]
|
||||
Rustls(std::sync::Arc<ClientConfig>),
|
||||
Rustls(std::sync::Arc<actix_tls::connect::ssl::rustls::ClientConfig>),
|
||||
}
|
||||
|
||||
/// Manages HTTP client network connectivity.
|
||||
@ -78,10 +73,33 @@ impl Connector<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Build Ssl connector with openssl, based on supplied alpn protocols
|
||||
#[cfg(feature = "openssl")]
|
||||
/// Provides an empty TLS connector when no TLS feature is enabled.
|
||||
#[cfg(not(any(feature = "openssl", feature = "rustls")))]
|
||||
fn build_ssl(_: Vec<Vec<u8>>) -> SslConnector {
|
||||
SslConnector::None
|
||||
}
|
||||
|
||||
/// Build TLS connector with rustls, based on supplied ALPN protocols
|
||||
///
|
||||
/// Note that if both `openssl` and `rustls` features are enabled, rustls will be used.
|
||||
#[cfg(feature = "rustls")]
|
||||
fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector {
|
||||
use actix_tls::connect::ssl::openssl::SslMethod;
|
||||
use actix_tls::connect::tls::rustls::{webpki_roots_cert_store, ClientConfig};
|
||||
|
||||
let mut config = ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(webpki_roots_cert_store())
|
||||
.with_no_client_auth();
|
||||
|
||||
config.alpn_protocols = protocols;
|
||||
|
||||
SslConnector::Rustls(std::sync::Arc::new(config))
|
||||
}
|
||||
|
||||
/// Build TLS connector with openssl, based on supplied ALPN protocols
|
||||
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
|
||||
fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector {
|
||||
use actix_tls::connect::tls::openssl::{SslConnector as OpensslConnector, SslMethod};
|
||||
use bytes::{BufMut, BytesMut};
|
||||
|
||||
let mut alpn = BytesMut::with_capacity(20);
|
||||
@ -91,28 +109,12 @@ impl Connector<()> {
|
||||
}
|
||||
|
||||
let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap();
|
||||
let _ = ssl
|
||||
.set_alpn_protos(&alpn)
|
||||
.map_err(|e| error!("Can not set alpn protocol: {:?}", e));
|
||||
if let Err(err) = ssl.set_alpn_protos(&alpn) {
|
||||
log::error!("Can not set ALPN protocol: {:?}", err);
|
||||
}
|
||||
|
||||
SslConnector::Openssl(ssl.build())
|
||||
}
|
||||
|
||||
// Build Ssl connector with rustls, based on supplied alpn protocols
|
||||
#[cfg(all(not(feature = "openssl"), feature = "rustls"))]
|
||||
fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector {
|
||||
let mut config = ClientConfig::new();
|
||||
config.set_protocols(&protocols);
|
||||
config.root_store.add_server_trust_anchors(
|
||||
&actix_tls::connect::ssl::rustls::TLS_SERVER_ROOTS,
|
||||
);
|
||||
SslConnector::Rustls(std::sync::Arc::new(config))
|
||||
}
|
||||
|
||||
// ssl turned off, provides empty ssl connector
|
||||
#[cfg(not(any(feature = "openssl", feature = "rustls")))]
|
||||
fn build_ssl(_: Vec<Vec<u8>>) -> SslConnector {
|
||||
SslConnector::None
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Connector<S> {
|
||||
@ -144,11 +146,8 @@ where
|
||||
// This remap is to hide ActixStream's trait methods. They are not meant to be called
|
||||
// from user code.
|
||||
Io: ActixStream + fmt::Debug + 'static,
|
||||
S: Service<
|
||||
TcpConnect<Uri>,
|
||||
Response = TcpConnection<Uri, Io>,
|
||||
Error = TcpConnectError,
|
||||
> + Clone
|
||||
S: Service<TcpConnect<Uri>, Response = TcpConnection<Uri, Io>, Error = TcpConnectError>
|
||||
+ Clone
|
||||
+ 'static,
|
||||
{
|
||||
/// Tcp connection timeout, i.e. max time to connect to remote host including dns name
|
||||
@ -167,14 +166,17 @@ where
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
/// Use custom `SslConnector` instance.
|
||||
pub fn ssl(mut self, connector: OpensslConnector) -> Self {
|
||||
pub fn ssl(mut self, connector: actix_tls::connect::ssl::openssl::SslConnector) -> Self {
|
||||
self.ssl = SslConnector::Openssl(connector);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
/// Use custom `SslConnector` instance.
|
||||
pub fn rustls(mut self, connector: std::sync::Arc<ClientConfig>) -> Self {
|
||||
pub fn rustls(
|
||||
mut self,
|
||||
connector: std::sync::Arc<actix_tls::connect::ssl::rustls::ClientConfig>,
|
||||
) -> Self {
|
||||
self.ssl = SslConnector::Rustls(connector);
|
||||
self
|
||||
}
|
||||
@ -313,9 +315,7 @@ where
|
||||
SslConnector::Rustls(tls) => {
|
||||
const H2: &[u8] = b"h2";
|
||||
|
||||
use actix_tls::connect::ssl::rustls::{
|
||||
RustlsConnector, Session, TlsStream,
|
||||
};
|
||||
use actix_tls::connect::ssl::rustls::{RustlsConnector, TlsStream};
|
||||
|
||||
impl<Io: ConnectionIo> IntoConnectionIo for TcpConnection<Uri, TlsStream<Io>> {
|
||||
fn into_connection_io(self) -> (Box<dyn ConnectionIo>, Protocol) {
|
||||
@ -323,7 +323,7 @@ where
|
||||
let h2 = sock
|
||||
.get_ref()
|
||||
.1
|
||||
.get_alpn_protocol()
|
||||
.alpn_protocol()
|
||||
.map_or(false, |protos| protos.windows(2).any(|w| w == H2));
|
||||
if h2 {
|
||||
(Box::new(sock), Protocol::Http2)
|
||||
@ -350,8 +350,8 @@ where
|
||||
let tcp_pool = ConnectionPool::new(tcp_service, tcp_config);
|
||||
|
||||
let tls_config = self.config;
|
||||
let tls_pool = tls_service
|
||||
.map(move |tls_service| ConnectionPool::new(tls_service, tls_config));
|
||||
let tls_pool =
|
||||
tls_service.map(move |tls_service| ConnectionPool::new(tls_service, tls_config));
|
||||
|
||||
ConnectorServicePriv { tcp_pool, tls_pool }
|
||||
}
|
||||
@ -382,10 +382,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub struct TcpConnectorFuture<Fut> {
|
||||
#[pin]
|
||||
fut: Fut,
|
||||
pin_project! {
|
||||
#[project = TcpConnectorFutureProj]
|
||||
pub struct TcpConnectorFuture<Fut> {
|
||||
#[pin]
|
||||
fut: Fut,
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut, Io> Future for TcpConnectorFuture<Fut>
|
||||
@ -444,23 +446,25 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project(project = TlsConnectorProj)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum TlsConnectorFuture<S, Fut1, Fut2> {
|
||||
TcpConnect {
|
||||
#[pin]
|
||||
fut: Fut1,
|
||||
tls_service: Option<S>,
|
||||
timeout: Duration,
|
||||
},
|
||||
TlsConnect {
|
||||
#[pin]
|
||||
fut: Fut2,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
},
|
||||
}
|
||||
pin_project! {
|
||||
#[project = TlsConnectorProj]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum TlsConnectorFuture<S, Fut1, Fut2> {
|
||||
TcpConnect {
|
||||
#[pin]
|
||||
fut: Fut1,
|
||||
tls_service: Option<S>,
|
||||
timeout: Duration,
|
||||
},
|
||||
TlsConnect {
|
||||
#[pin]
|
||||
fut: Fut2,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
/// helper trait for generic over different TlsStream types between tls crates.
|
||||
trait IntoConnectionIo {
|
||||
fn into_connection_io(self) -> (Box<dyn ConnectionIo>, Protocol);
|
||||
@ -468,12 +472,7 @@ trait IntoConnectionIo {
|
||||
|
||||
impl<S, Io, Fut1, Fut2, Res> Future for TlsConnectorFuture<S, Fut1, Fut2>
|
||||
where
|
||||
S: Service<
|
||||
TcpConnection<Uri, Io>,
|
||||
Response = Res,
|
||||
Error = std::io::Error,
|
||||
Future = Fut2,
|
||||
>,
|
||||
S: Service<TcpConnection<Uri, Io>, Response = Res, Error = std::io::Error, Future = Fut2>,
|
||||
S::Response: IntoConnectionIo,
|
||||
Fut1: Future<Output = Result<TcpConnection<Uri, Io>, ConnectError>>,
|
||||
Fut2: Future<Output = Result<S::Response, S::Error>>,
|
||||
@ -515,11 +514,7 @@ pub struct TcpConnectorInnerService<S: Clone> {
|
||||
}
|
||||
|
||||
impl<S: Clone> TcpConnectorInnerService<S> {
|
||||
fn new(
|
||||
service: S,
|
||||
timeout: Duration,
|
||||
local_address: Option<std::net::IpAddr>,
|
||||
) -> Self {
|
||||
fn new(service: S, timeout: Duration, local_address: Option<std::net::IpAddr>) -> Self {
|
||||
Self {
|
||||
service,
|
||||
timeout,
|
||||
@ -530,11 +525,8 @@ impl<S: Clone> TcpConnectorInnerService<S> {
|
||||
|
||||
impl<S, Io> Service<Connect> for TcpConnectorInnerService<S>
|
||||
where
|
||||
S: Service<
|
||||
TcpConnect<Uri>,
|
||||
Response = TcpConnection<Uri, Io>,
|
||||
Error = TcpConnectError,
|
||||
> + Clone
|
||||
S: Service<TcpConnect<Uri>, Response = TcpConnection<Uri, Io>, Error = TcpConnectError>
|
||||
+ Clone
|
||||
+ 'static,
|
||||
{
|
||||
type Response = S::Response;
|
||||
@ -557,12 +549,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub struct TcpConnectorInnerFuture<Fut> {
|
||||
#[pin]
|
||||
fut: Fut,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
pin_project! {
|
||||
#[project = TcpConnectorInnerFutureProj]
|
||||
pub struct TcpConnectorInnerFuture<Fut> {
|
||||
#[pin]
|
||||
fut: Fut,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut, Io> Future for TcpConnectorInnerFuture<Fut>
|
||||
@ -611,12 +605,8 @@ where
|
||||
|
||||
impl<S1, S2, Io1, Io2> Service<Connect> for ConnectorServicePriv<S1, S2, Io1, Io2>
|
||||
where
|
||||
S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError>
|
||||
+ Clone
|
||||
+ 'static,
|
||||
S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError>
|
||||
+ Clone
|
||||
+ 'static,
|
||||
S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + Clone + 'static,
|
||||
S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + Clone + 'static,
|
||||
Io1: ConnectionIo,
|
||||
Io2: ConnectionIo,
|
||||
{
|
||||
@ -636,38 +626,46 @@ where
|
||||
match req.uri.scheme_str() {
|
||||
Some("https") | Some("wss") => match self.tls_pool {
|
||||
None => ConnectorServiceFuture::SslIsNotSupported,
|
||||
Some(ref pool) => ConnectorServiceFuture::Tls(pool.call(req)),
|
||||
Some(ref pool) => ConnectorServiceFuture::Tls {
|
||||
fut: pool.call(req),
|
||||
},
|
||||
},
|
||||
_ => ConnectorServiceFuture::Tcp {
|
||||
fut: self.tcp_pool.call(req),
|
||||
},
|
||||
_ => ConnectorServiceFuture::Tcp(self.tcp_pool.call(req)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project(project = ConnectorServiceProj)]
|
||||
pub enum ConnectorServiceFuture<S1, S2, Io1, Io2>
|
||||
where
|
||||
S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError>
|
||||
+ Clone
|
||||
+ 'static,
|
||||
S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError>
|
||||
+ Clone
|
||||
+ 'static,
|
||||
Io1: ConnectionIo,
|
||||
Io2: ConnectionIo,
|
||||
{
|
||||
Tcp(#[pin] <ConnectionPool<S1, Io1> as Service<Connect>>::Future),
|
||||
Tls(#[pin] <ConnectionPool<S2, Io2> as Service<Connect>>::Future),
|
||||
SslIsNotSupported,
|
||||
pin_project! {
|
||||
#[project = ConnectorServiceFutureProj]
|
||||
pub enum ConnectorServiceFuture<S1, S2, Io1, Io2>
|
||||
where
|
||||
S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError>,
|
||||
S1: Clone,
|
||||
S1: 'static,
|
||||
S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError>,
|
||||
S2: Clone,
|
||||
S2: 'static,
|
||||
Io1: ConnectionIo,
|
||||
Io2: ConnectionIo,
|
||||
{
|
||||
Tcp {
|
||||
#[pin]
|
||||
fut: <ConnectionPool<S1, Io1> as Service<Connect>>::Future
|
||||
},
|
||||
Tls {
|
||||
#[pin]
|
||||
fut: <ConnectionPool<S2, Io2> as Service<Connect>>::Future
|
||||
},
|
||||
SslIsNotSupported
|
||||
}
|
||||
}
|
||||
|
||||
impl<S1, S2, Io1, Io2> Future for ConnectorServiceFuture<S1, S2, Io1, Io2>
|
||||
where
|
||||
S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError>
|
||||
+ Clone
|
||||
+ 'static,
|
||||
S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError>
|
||||
+ Clone
|
||||
+ 'static,
|
||||
S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + Clone + 'static,
|
||||
S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + Clone + 'static,
|
||||
Io1: ConnectionIo,
|
||||
Io2: ConnectionIo,
|
||||
{
|
||||
@ -675,9 +673,9 @@ where
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.project() {
|
||||
ConnectorServiceProj::Tcp(fut) => fut.poll(cx).map_ok(Connection::Tcp),
|
||||
ConnectorServiceProj::Tls(fut) => fut.poll(cx).map_ok(Connection::Tls),
|
||||
ConnectorServiceProj::SslIsNotSupported => {
|
||||
ConnectorServiceFutureProj::Tcp { fut } => fut.poll(cx).map_ok(Connection::Tcp),
|
||||
ConnectorServiceFutureProj::Tls { fut } => fut.poll(cx).map_ok(Connection::Tls),
|
||||
ConnectorServiceFutureProj::SslIsNotSupported => {
|
||||
Poll::Ready(Err(ConnectError::SslIsNotSupported))
|
||||
}
|
||||
}
|
@ -2,12 +2,13 @@ use std::{error::Error as StdError, fmt, io};
|
||||
|
||||
use derive_more::{Display, From};
|
||||
|
||||
use actix_http::{
|
||||
error::{Error, ParseError},
|
||||
http::Error as HttpError,
|
||||
};
|
||||
#[cfg(feature = "openssl")]
|
||||
use actix_tls::accept::openssl::SslError;
|
||||
|
||||
use crate::error::{Error, ParseError};
|
||||
use crate::http::Error as HttpError;
|
||||
|
||||
/// A set of errors that can occur while connecting to an HTTP host
|
||||
#[derive(Debug, Display, From)]
|
||||
#[non_exhaustive]
|
@ -5,24 +5,25 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_http::{
|
||||
body::{BodySize, MessageBody},
|
||||
error::PayloadError,
|
||||
h1,
|
||||
http::{
|
||||
header::{HeaderMap, IntoHeaderValue, EXPECT, HOST},
|
||||
StatusCode,
|
||||
},
|
||||
Error, Payload, RequestHeadType, ResponseHead,
|
||||
};
|
||||
use actix_utils::future::poll_fn;
|
||||
use bytes::buf::BufMut;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures_core::{ready, Stream};
|
||||
use futures_util::SinkExt as _;
|
||||
|
||||
use crate::h1;
|
||||
use crate::http::{
|
||||
header::{HeaderMap, IntoHeaderValue, EXPECT, HOST},
|
||||
StatusCode,
|
||||
};
|
||||
use crate::message::{RequestHeadType, ResponseHead};
|
||||
use crate::payload::Payload;
|
||||
use crate::{error::PayloadError, Error};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::connection::{ConnectionIo, H1Connection};
|
||||
use super::error::{ConnectError, SendRequestError};
|
||||
use crate::body::{BodySize, MessageBody};
|
||||
|
||||
pub(crate) async fn send_request<Io, B>(
|
||||
io: H1Connection<Io>,
|
||||
@ -194,10 +195,11 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pin_project::pin_project]
|
||||
pub(crate) struct PlStream<Io: ConnectionIo> {
|
||||
#[pin]
|
||||
framed: Framed<H1Connection<Io>, h1::ClientPayloadCodec>,
|
||||
pin_project! {
|
||||
pub(crate) struct PlStream<Io: ConnectionIo> {
|
||||
#[pin]
|
||||
framed: Framed<H1Connection<Io>, h1::ClientPayloadCodec>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io: ConnectionIo> PlStream<Io> {
|
||||
@ -211,10 +213,7 @@ impl<Io: ConnectionIo> PlStream<Io> {
|
||||
impl<Io: ConnectionIo> Stream for PlStream<Io> {
|
||||
type Item = Result<Bytes, PayloadError>;
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut this = self.project();
|
||||
|
||||
match ready!(this.framed.as_mut().next_item(cx)?) {
|
@ -8,13 +8,12 @@ use h2::{
|
||||
};
|
||||
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING};
|
||||
use http::{request::Request, Method, Version};
|
||||
use log::trace;
|
||||
|
||||
use crate::{
|
||||
use actix_http::{
|
||||
body::{BodySize, MessageBody},
|
||||
header::HeaderMap,
|
||||
message::{RequestHeadType, ResponseHead},
|
||||
payload::Payload,
|
||||
Error,
|
||||
Error, Payload, RequestHeadType, ResponseHead,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@ -131,10 +130,7 @@ where
|
||||
Ok((head, payload))
|
||||
}
|
||||
|
||||
async fn send_body<B>(
|
||||
body: B,
|
||||
mut send: SendStream<Bytes>,
|
||||
) -> Result<(), SendRequestError>
|
||||
async fn send_body<B>(body: B, mut send: SendStream<Bytes>) -> Result<(), SendRequestError>
|
||||
where
|
||||
B: MessageBody,
|
||||
B::Error: Into<Error>,
|
||||
@ -184,8 +180,7 @@ where
|
||||
pub(crate) fn handshake<Io: ConnectionIo>(
|
||||
io: Io,
|
||||
config: &ConnectorConfig,
|
||||
) -> impl Future<Output = Result<(SendRequest<Bytes>, Connection<Io, Bytes>), h2::Error>>
|
||||
{
|
||||
) -> impl Future<Output = Result<(SendRequest<Bytes>, Connection<Io, Bytes>), h2::Error>> {
|
||||
let mut builder = Builder::new();
|
||||
builder
|
||||
.initial_window_size(config.stream_window_size)
|
@ -17,7 +17,6 @@ pub use actix_tls::connect::{
|
||||
pub use self::connection::{Connection, ConnectionIo};
|
||||
pub use self::connector::{Connector, ConnectorService};
|
||||
pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
|
||||
pub use crate::Protocol;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Connect {
|
@ -14,22 +14,21 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use actix_http::Protocol;
|
||||
use actix_rt::time::{sleep, Sleep};
|
||||
use actix_service::Service;
|
||||
use ahash::AHashMap;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use futures_util::FutureExt;
|
||||
use http::uri::Authority;
|
||||
use pin_project::pin_project;
|
||||
use pin_project_lite::pin_project;
|
||||
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
|
||||
|
||||
use super::config::ConnectorConfig;
|
||||
use super::connection::{
|
||||
ConnectionInnerType, ConnectionIo, ConnectionType, H2ConnectionInner,
|
||||
};
|
||||
use super::connection::{ConnectionInnerType, ConnectionIo, ConnectionType, H2ConnectionInner};
|
||||
use super::error::ConnectError;
|
||||
use super::h2proto::handshake;
|
||||
use super::Connect;
|
||||
use super::Protocol;
|
||||
|
||||
#[derive(Hash, Eq, PartialEq, Clone, Debug)]
|
||||
pub struct Key {
|
||||
@ -152,9 +151,7 @@ where
|
||||
|
||||
impl<S, Io> Service<Connect> for ConnectionPool<S, Io>
|
||||
where
|
||||
S: Service<Connect, Response = (Io, Protocol), Error = ConnectError>
|
||||
+ Clone
|
||||
+ 'static,
|
||||
S: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + Clone + 'static,
|
||||
Io: ConnectionIo,
|
||||
{
|
||||
type Response = ConnectionType<Io>;
|
||||
@ -195,8 +192,8 @@ where
|
||||
let config = &inner.config;
|
||||
let idle_dur = now - c.used;
|
||||
let age = now - c.created;
|
||||
let conn_ineligible = idle_dur > config.conn_keep_alive
|
||||
|| age > config.conn_lifetime;
|
||||
let conn_ineligible =
|
||||
idle_dur > config.conn_keep_alive || age > config.conn_lifetime;
|
||||
|
||||
if conn_ineligible {
|
||||
// drop connections that are too old
|
||||
@ -205,7 +202,7 @@ where
|
||||
// check if the connection is still usable
|
||||
if let ConnectionInnerType::H1(ref mut io) = c.conn {
|
||||
let check = ConnectionCheckFuture { io };
|
||||
match check.await {
|
||||
match check.now_or_never().expect("ConnectionCheckFuture must never yield with Poll::Pending.") {
|
||||
ConnectionState::Tainted => {
|
||||
inner.close(c.conn);
|
||||
continue;
|
||||
@ -231,9 +228,7 @@ where
|
||||
|
||||
// match the connection and spawn new one if did not get anything.
|
||||
match conn {
|
||||
Some(conn) => {
|
||||
Ok(ConnectionType::from_pool(conn.conn, conn.created, acquired))
|
||||
}
|
||||
Some(conn) => Ok(ConnectionType::from_pool(conn.conn, conn.created, acquired)),
|
||||
None => {
|
||||
let (io, proto) = connector.call(req).await?;
|
||||
|
||||
@ -284,9 +279,7 @@ where
|
||||
let mut read_buf = ReadBuf::new(&mut buf);
|
||||
|
||||
let state = match Pin::new(&mut this.io).poll_read(cx, &mut read_buf) {
|
||||
Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => {
|
||||
ConnectionState::Tainted
|
||||
}
|
||||
Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => ConnectionState::Tainted,
|
||||
|
||||
Poll::Pending => ConnectionState::Live,
|
||||
_ => ConnectionState::Skip,
|
||||
@ -302,11 +295,13 @@ struct PooledConnection<Io> {
|
||||
created: Instant,
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
struct CloseConnection<Io> {
|
||||
io: Io,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
pin_project! {
|
||||
#[project = CloseConnectionProj]
|
||||
struct CloseConnection<Io> {
|
||||
io: Io,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io> CloseConnection<Io>
|
||||
@ -413,17 +408,11 @@ mod test {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
_: &mut Context<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
_: &mut Context<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
@ -8,16 +8,14 @@ use std::{
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_http::{
|
||||
body::Body,
|
||||
client::{
|
||||
Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError,
|
||||
},
|
||||
h1::ClientCodec,
|
||||
Payload, RequestHead, RequestHeadType, ResponseHead,
|
||||
body::Body, h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead,
|
||||
};
|
||||
use actix_service::Service;
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
|
||||
use crate::client::{
|
||||
Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError,
|
||||
};
|
||||
use crate::response::ClientResponse;
|
||||
|
||||
pub type BoxConnectorService = Rc<
|
||||
|
@ -1,15 +1,15 @@
|
||||
//! HTTP client errors
|
||||
|
||||
pub use actix_http::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
|
||||
pub use actix_http::error::PayloadError;
|
||||
pub use actix_http::http::Error as HttpError;
|
||||
pub use actix_http::ws::HandshakeError as WsHandshakeError;
|
||||
pub use actix_http::ws::ProtocolError as WsProtocolError;
|
||||
pub use actix_http::{
|
||||
error::PayloadError,
|
||||
http::{header::HeaderValue, Error as HttpError, StatusCode},
|
||||
ws::{HandshakeError as WsHandshakeError, ProtocolError as WsProtocolError},
|
||||
};
|
||||
|
||||
use derive_more::{Display, From};
|
||||
use serde_json::error::Error as JsonError;
|
||||
|
||||
use actix_http::http::{header::HeaderValue, StatusCode};
|
||||
use derive_more::{Display, From};
|
||||
pub use crate::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
|
||||
|
||||
/// Websocket client error
|
||||
#[derive(Debug, Display, From)]
|
||||
|
@ -104,22 +104,8 @@
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
use std::{convert::TryFrom, rc::Rc, time::Duration};
|
||||
|
||||
#[cfg(feature = "cookies")]
|
||||
pub use cookie;
|
||||
|
||||
pub use actix_http::{client::Connector, http};
|
||||
|
||||
use actix_http::{
|
||||
client::{TcpConnect, TcpConnectError, TcpConnection},
|
||||
http::{Error as HttpError, HeaderMap, Method, Uri},
|
||||
RequestHead,
|
||||
};
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::Service;
|
||||
|
||||
mod builder;
|
||||
mod client;
|
||||
mod connect;
|
||||
pub mod error;
|
||||
mod frozen;
|
||||
@ -130,13 +116,29 @@ mod sender;
|
||||
pub mod test;
|
||||
pub mod ws;
|
||||
|
||||
pub use actix_http::http;
|
||||
#[cfg(feature = "cookies")]
|
||||
pub use cookie;
|
||||
|
||||
pub use self::builder::ClientBuilder;
|
||||
pub use self::client::Connector;
|
||||
pub use self::connect::{BoxConnectorService, BoxedSocket, ConnectRequest, ConnectResponse};
|
||||
pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder};
|
||||
pub use self::request::ClientRequest;
|
||||
pub use self::response::{ClientResponse, JsonBody, MessageBody};
|
||||
pub use self::sender::SendClientRequest;
|
||||
|
||||
use std::{convert::TryFrom, rc::Rc, time::Duration};
|
||||
|
||||
use actix_http::{
|
||||
http::{Error as HttpError, HeaderMap, Method, Uri},
|
||||
RequestHead,
|
||||
};
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::Service;
|
||||
|
||||
use self::client::{TcpConnect, TcpConnectError, TcpConnection};
|
||||
|
||||
/// An asynchronous HTTP and WebSocket client.
|
||||
///
|
||||
/// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU
|
||||
|
@ -9,7 +9,6 @@ use std::{
|
||||
|
||||
use actix_http::{
|
||||
body::Body,
|
||||
client::{InvalidUrl, SendRequestError},
|
||||
http::{header, Method, StatusCode, Uri},
|
||||
RequestHead, RequestHeadType,
|
||||
};
|
||||
@ -19,6 +18,7 @@ use futures_core::ready;
|
||||
|
||||
use super::Transform;
|
||||
|
||||
use crate::client::{InvalidUrl, SendRequestError};
|
||||
use crate::connect::{ConnectRequest, ConnectResponse};
|
||||
use crate::ClientResponse;
|
||||
|
||||
|
@ -795,17 +795,15 @@ async fn client_unread_response() {
|
||||
let lst = std::net::TcpListener::bind(addr).unwrap();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
for stream in lst.incoming() {
|
||||
let mut stream = stream.unwrap();
|
||||
let mut b = [0; 1000];
|
||||
let _ = stream.read(&mut b).unwrap();
|
||||
let _ = stream.write_all(
|
||||
b"HTTP/1.1 200 OK\r\n\
|
||||
let (mut stream, _) = lst.accept().unwrap();
|
||||
let mut b = [0; 1000];
|
||||
let _ = stream.read(&mut b).unwrap();
|
||||
let _ = stream.write_all(
|
||||
b"HTTP/1.1 200 OK\r\n\
|
||||
connection: close\r\n\
|
||||
\r\n\
|
||||
welcome!",
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// client request
|
||||
|
@ -8,44 +8,59 @@ use std::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use actix_http::HttpService;
|
||||
use actix_http_test::test_server;
|
||||
use actix_service::{fn_service, map_config, ServiceFactoryExt};
|
||||
use actix_tls::connect::tls::rustls::webpki_roots_cert_store;
|
||||
use actix_utils::future::ok;
|
||||
use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse};
|
||||
use rustls::internal::pemfile::{certs, pkcs8_private_keys};
|
||||
use rustls::{ClientConfig, NoClientAuth, ServerConfig};
|
||||
use rustls::{
|
||||
client::{ServerCertVerified, ServerCertVerifier},
|
||||
Certificate, ClientConfig, PrivateKey, ServerConfig, ServerName,
|
||||
};
|
||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
|
||||
fn tls_config() -> ServerConfig {
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
|
||||
let mut config = ServerConfig::new(NoClientAuth::new());
|
||||
let cert_file = &mut BufReader::new(cert_file.as_bytes());
|
||||
let key_file = &mut BufReader::new(key_file.as_bytes());
|
||||
|
||||
let cert_chain = certs(cert_file).unwrap();
|
||||
let cert_chain = certs(cert_file)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
let mut keys = pkcs8_private_keys(key_file).unwrap();
|
||||
config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
|
||||
|
||||
config
|
||||
ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
mod danger {
|
||||
use super::*;
|
||||
|
||||
pub struct NoCertificateVerification;
|
||||
|
||||
impl rustls::ServerCertVerifier for NoCertificateVerification {
|
||||
impl ServerCertVerifier for NoCertificateVerification {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
_roots: &rustls::RootCertStore,
|
||||
_presented_certs: &[rustls::Certificate],
|
||||
_dns_name: webpki::DNSNameRef<'_>,
|
||||
_ocsp: &[u8],
|
||||
) -> Result<rustls::ServerCertVerified, rustls::TLSError> {
|
||||
Ok(rustls::ServerCertVerified::assertion())
|
||||
_end_entity: &Certificate,
|
||||
_intermediates: &[Certificate],
|
||||
_server_name: &ServerName,
|
||||
_scts: &mut dyn Iterator<Item = &[u8]>,
|
||||
_ocsp_response: &[u8],
|
||||
_now: SystemTime,
|
||||
) -> Result<ServerCertVerified, rustls::Error> {
|
||||
Ok(ServerCertVerified::assertion())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -73,10 +88,15 @@ async fn test_connection_reuse_h2() {
|
||||
})
|
||||
.await;
|
||||
|
||||
// disable TLS verification
|
||||
let mut config = ClientConfig::new();
|
||||
let mut config = ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(webpki_roots_cert_store())
|
||||
.with_no_client_auth();
|
||||
|
||||
let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
config.set_protocols(&protos);
|
||||
config.alpn_protocols = protos;
|
||||
|
||||
// disable TLS verification
|
||||
config
|
||||
.dangerous()
|
||||
.set_certificate_verifier(Arc::new(danger::NoCertificateVerification));
|
||||
|
@ -1 +1 @@
|
||||
msrv = "1.51"
|
||||
msrv = "1.52"
|
||||
|
@ -35,7 +35,7 @@ async fn main() -> std::io::Result<()> {
|
||||
)
|
||||
.service(web::resource("/test1.html").to(|| async { "Test\r\n" }))
|
||||
})
|
||||
.bind("127.0.0.1:8080")?
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.workers(1)
|
||||
.run()
|
||||
.await
|
||||
|
@ -8,6 +8,7 @@ use std::{any::Any, io, net::SocketAddr};
|
||||
|
||||
use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer};
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct ConnectionInfo {
|
||||
bind: SocketAddr,
|
||||
|
39
src/data.rs
39
src/data.rs
@ -75,7 +75,9 @@ impl<T> Data<T> {
|
||||
pub fn new(state: T) -> Data<T> {
|
||||
Data(Arc::new(state))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Data<T> {
|
||||
/// Get reference to inner app data.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
self.0.as_ref()
|
||||
@ -120,7 +122,6 @@ where
|
||||
}
|
||||
|
||||
impl<T: ?Sized + 'static> FromRequest for Data<T> {
|
||||
type Config = ();
|
||||
type Error = Error;
|
||||
type Future = Ready<Result<Self, Error>>;
|
||||
|
||||
@ -136,7 +137,7 @@ impl<T: ?Sized + 'static> FromRequest for Data<T> {
|
||||
type_name::<T>(),
|
||||
);
|
||||
err(ErrorInternalServerError(
|
||||
"App data is not configured, to configure use App::data()",
|
||||
"App data is not configured, to configure construct it with web::Data::new() and pass it to App::app_data()",
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -305,4 +306,38 @@ mod tests {
|
||||
let data_arc = Data::from(dyn_arc);
|
||||
assert_eq!(data_arc_box.get_num(), data_arc.get_num())
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_dyn_data_into_arc() {
|
||||
trait TestTrait {
|
||||
fn get_num(&self) -> i32;
|
||||
}
|
||||
struct A {}
|
||||
impl TestTrait for A {
|
||||
fn get_num(&self) -> i32 {
|
||||
42
|
||||
}
|
||||
}
|
||||
let dyn_arc: Arc<dyn TestTrait> = Arc::new(A {});
|
||||
let data_arc = Data::from(dyn_arc);
|
||||
let arc_from_data = data_arc.clone().into_inner();
|
||||
assert_eq!(data_arc.get_num(), arc_from_data.get_num())
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_get_ref_from_dyn_data() {
|
||||
trait TestTrait {
|
||||
fn get_num(&self) -> i32;
|
||||
}
|
||||
struct A {}
|
||||
impl TestTrait for A {
|
||||
fn get_num(&self) -> i32 {
|
||||
42
|
||||
}
|
||||
}
|
||||
let dyn_arc: Arc<dyn TestTrait> = Arc::new(A {});
|
||||
let data_arc = Data::from(dyn_arc);
|
||||
let ref_data = data_arc.get_ref();
|
||||
assert_eq!(data_arc.get_num(), ref_data.get_num())
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, ResponseBody, S
|
||||
pub use actix_http::encoding::Decoder as Decompress;
|
||||
pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead};
|
||||
pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
|
||||
pub use actix_server::Server;
|
||||
pub use actix_server::{Server, ServerHandle};
|
||||
pub use actix_service::{
|
||||
always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform,
|
||||
};
|
||||
|
@ -13,13 +13,52 @@ use futures_core::ready;
|
||||
|
||||
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 {
|
||||
/// Configuration for this extractor.
|
||||
type Config: Default + 'static;
|
||||
|
||||
/// The associated error which can be returned.
|
||||
type Error: Into<Error>;
|
||||
|
||||
@ -35,14 +74,6 @@ pub trait FromRequest: Sized {
|
||||
fn extract(req: &HttpRequest) -> Self::Future {
|
||||
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
|
||||
@ -65,7 +96,6 @@ pub trait FromRequest: Sized {
|
||||
/// impl FromRequest for Thing {
|
||||
/// type Error = Error;
|
||||
/// type Future = Ready<Result<Self, Self::Error>>;
|
||||
/// type Config = ();
|
||||
///
|
||||
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
/// if rand::random() {
|
||||
@ -100,7 +130,6 @@ where
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = FromRequestOptFuture<T::Future>;
|
||||
type Config = T::Config;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
@ -156,7 +185,6 @@ where
|
||||
/// impl FromRequest for Thing {
|
||||
/// type Error = Error;
|
||||
/// type Future = Ready<Result<Thing, Error>>;
|
||||
/// type Config = ();
|
||||
///
|
||||
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
/// if rand::random() {
|
||||
@ -189,7 +217,6 @@ where
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = FromRequestResFuture<T::Future>;
|
||||
type Config = T::Config;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
@ -233,7 +260,6 @@ where
|
||||
impl FromRequest for Uri {
|
||||
type Error = Infallible;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
ok(req.uri().clone())
|
||||
@ -255,7 +281,6 @@ impl FromRequest for Uri {
|
||||
impl FromRequest for Method {
|
||||
type Error = Infallible;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
ok(req.method().clone())
|
||||
@ -266,7 +291,6 @@ impl FromRequest for Method {
|
||||
impl FromRequest for () {
|
||||
type Error = Infallible;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
ok(())
|
||||
@ -306,7 +330,6 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = $fut_type<$($T),+>;
|
||||
type Config = ($($T::Config),+);
|
||||
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
$fut_type {
|
||||
|
@ -60,52 +60,53 @@ crate::http::header::common_header! {
|
||||
}
|
||||
|
||||
impl ContentType {
|
||||
/// A constructor to easily create a `Content-Type: application/json`
|
||||
/// A constructor to easily create a `Content-Type: application/json`
|
||||
/// header.
|
||||
#[inline]
|
||||
pub fn json() -> ContentType {
|
||||
ContentType(mime::APPLICATION_JSON)
|
||||
}
|
||||
|
||||
/// A constructor to easily create a `Content-Type: text/plain;
|
||||
/// A constructor to easily create a `Content-Type: text/plain;
|
||||
/// charset=utf-8` header.
|
||||
#[inline]
|
||||
pub fn plaintext() -> ContentType {
|
||||
ContentType(mime::TEXT_PLAIN_UTF_8)
|
||||
}
|
||||
|
||||
/// A constructor to easily create a `Content-Type: text/html` header.
|
||||
/// A constructor to easily create a `Content-Type: text/html; charset=utf-8`
|
||||
/// header.
|
||||
#[inline]
|
||||
pub fn html() -> ContentType {
|
||||
ContentType(mime::TEXT_HTML)
|
||||
ContentType(mime::TEXT_HTML_UTF_8)
|
||||
}
|
||||
|
||||
/// A constructor to easily create a `Content-Type: text/xml` header.
|
||||
/// A constructor to easily create a `Content-Type: text/xml` header.
|
||||
#[inline]
|
||||
pub fn xml() -> ContentType {
|
||||
ContentType(mime::TEXT_XML)
|
||||
}
|
||||
|
||||
/// A constructor to easily create a `Content-Type:
|
||||
/// A constructor to easily create a `Content-Type:
|
||||
/// application/www-form-url-encoded` header.
|
||||
#[inline]
|
||||
pub fn form_url_encoded() -> ContentType {
|
||||
ContentType(mime::APPLICATION_WWW_FORM_URLENCODED)
|
||||
}
|
||||
|
||||
/// A constructor to easily create a `Content-Type: image/jpeg` header.
|
||||
/// A constructor to easily create a `Content-Type: image/jpeg` header.
|
||||
#[inline]
|
||||
pub fn jpeg() -> ContentType {
|
||||
ContentType(mime::IMAGE_JPEG)
|
||||
}
|
||||
|
||||
/// A constructor to easily create a `Content-Type: image/png` header.
|
||||
/// A constructor to easily create a `Content-Type: image/png` header.
|
||||
#[inline]
|
||||
pub fn png() -> ContentType {
|
||||
ContentType(mime::IMAGE_PNG)
|
||||
}
|
||||
|
||||
/// A constructor to easily create a `Content-Type:
|
||||
/// A constructor to easily create a `Content-Type:
|
||||
/// application/octet-stream` header.
|
||||
#[inline]
|
||||
pub fn octet_stream() -> ContentType {
|
||||
|
@ -209,7 +209,6 @@ impl ConnectionInfo {
|
||||
impl FromRequest for ConnectionInfo {
|
||||
type Error = Infallible;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
ok(req.connection_info().clone())
|
||||
@ -252,7 +251,6 @@ impl ResponseError for MissingPeerAddr {}
|
||||
impl FromRequest for PeerAddr {
|
||||
type Error = MissingPeerAddr;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
match req.peer_addr() {
|
||||
|
@ -53,7 +53,7 @@
|
||||
//! * SSL support using OpenSSL or Rustls
|
||||
//! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
|
||||
//! * Includes an async [HTTP client](https://docs.rs/awc/)
|
||||
//! * Runs on stable Rust 1.51+
|
||||
//! * Runs on stable Rust 1.52+
|
||||
//!
|
||||
//! # Crate Features
|
||||
//! * `cookies` - cookies support (enabled by default)
|
||||
|
@ -276,7 +276,7 @@ impl AcceptEncoding {
|
||||
let mut encodings = raw
|
||||
.replace(' ', "")
|
||||
.split(',')
|
||||
.filter_map(|l| AcceptEncoding::new(l))
|
||||
.filter_map(AcceptEncoding::new)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
encodings.sort();
|
||||
|
@ -18,11 +18,11 @@ use bytes::Bytes;
|
||||
use futures_core::ready;
|
||||
use log::{debug, warn};
|
||||
use regex::{Regex, RegexSet};
|
||||
use time::OffsetDateTime;
|
||||
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
||||
|
||||
use crate::{
|
||||
dev::{BodySize, MessageBody},
|
||||
http::{HeaderName, StatusCode},
|
||||
http::HeaderName,
|
||||
service::{ServiceRequest, ServiceResponse},
|
||||
Error, HttpResponse, Result,
|
||||
};
|
||||
@ -275,9 +275,7 @@ where
|
||||
};
|
||||
|
||||
if let Some(error) = res.response().error() {
|
||||
if res.response().head().status != StatusCode::INTERNAL_SERVER_ERROR {
|
||||
debug!("Error in response: {:?}", error);
|
||||
}
|
||||
debug!("Error in response: {:?}", error);
|
||||
}
|
||||
|
||||
if let Some(ref mut format) = this.format {
|
||||
@ -538,7 +536,7 @@ impl FormatText {
|
||||
};
|
||||
}
|
||||
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) => {
|
||||
let s = if let Some(val) = req.headers().get(name) {
|
||||
if let Ok(s) = val.to_str() {
|
||||
@ -767,7 +765,7 @@ mod tests {
|
||||
Ok(())
|
||||
};
|
||||
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]
|
||||
|
@ -358,7 +358,6 @@ impl Drop for HttpRequest {
|
||||
/// }
|
||||
/// ```
|
||||
impl FromRequest for HttpRequest {
|
||||
type Config = ();
|
||||
type Error = 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> {
|
||||
type Config = ();
|
||||
type Error = Error;
|
||||
type Future = Ready<Result<Self, Error>>;
|
||||
|
||||
|
@ -159,7 +159,7 @@ where
|
||||
///
|
||||
/// By default max connections is set to a 25k.
|
||||
pub fn max_connections(mut self, num: usize) -> Self {
|
||||
self.builder = self.builder.maxconn(num);
|
||||
self.builder = self.builder.max_concurrent_connections(num);
|
||||
self
|
||||
}
|
||||
|
||||
@ -233,7 +233,7 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Stop actix system.
|
||||
/// Stop Actix `System` after server shutdown.
|
||||
pub fn system_exit(mut self) -> Self {
|
||||
self.builder = self.builder.system_exit();
|
||||
self
|
||||
|
@ -393,16 +393,6 @@ impl<B> ServiceResponse<B> {
|
||||
self.response.headers_mut()
|
||||
}
|
||||
|
||||
/// Execute closure and in case of error convert it to response.
|
||||
pub fn checked_expr<F, E>(mut self, f: F) -> Result<Self, Error>
|
||||
where
|
||||
F: FnOnce(&mut Self) -> Result<(), E>,
|
||||
E: Into<Error>,
|
||||
{
|
||||
f(&mut self).map_err(Into::into)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Extract response body
|
||||
pub fn into_body(self) -> B {
|
||||
self.response.into_body()
|
||||
|
16
src/test.rs
16
src/test.rs
@ -52,7 +52,7 @@ pub fn default_service(
|
||||
/// use actix_service::Service;
|
||||
/// use actix_web::{test, web, App, HttpResponse, http::StatusCode};
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// #[actix_web::test]
|
||||
/// async fn test_init_service() {
|
||||
/// let app = test::init_service(
|
||||
/// App::new()
|
||||
@ -98,7 +98,7 @@ where
|
||||
/// ```
|
||||
/// use actix_web::{test, web, App, HttpResponse, http::StatusCode};
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// #[actix_web::test]
|
||||
/// async fn test_response() {
|
||||
/// let app = test::init_service(
|
||||
/// App::new()
|
||||
@ -129,7 +129,7 @@ where
|
||||
/// use actix_web::{test, web, App, HttpResponse, http::header};
|
||||
/// use bytes::Bytes;
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// #[actix_web::test]
|
||||
/// async fn test_index() {
|
||||
/// let app = test::init_service(
|
||||
/// App::new().service(
|
||||
@ -176,7 +176,7 @@ where
|
||||
/// use actix_web::{test, web, App, HttpResponse, http::header};
|
||||
/// use bytes::Bytes;
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// #[actix_web::test]
|
||||
/// async fn test_index() {
|
||||
/// let app = test::init_service(
|
||||
/// App::new().service(
|
||||
@ -224,7 +224,7 @@ where
|
||||
/// name: String,
|
||||
/// }
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// #[actix_web::test]
|
||||
/// async fn test_post_person() {
|
||||
/// let app = test::init_service(
|
||||
/// App::new().service(
|
||||
@ -296,7 +296,7 @@ where
|
||||
/// name: String
|
||||
/// }
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// #[actix_web::test]
|
||||
/// async fn test_add_person() {
|
||||
/// let app = test::init_service(
|
||||
/// App::new().service(
|
||||
@ -356,8 +356,8 @@ where
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[test]
|
||||
/// fn test_index() {
|
||||
/// #[actix_web::test]
|
||||
/// async fn test_index() {
|
||||
/// let req = test::TestRequest::default().insert_header("content-type", "text/plain")
|
||||
/// .to_http_request();
|
||||
///
|
||||
|
@ -187,7 +187,6 @@ where
|
||||
{
|
||||
type Error = EitherExtractError<L::Error, R::Error>;
|
||||
type Future = EitherExtractFut<L, R>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
EitherExtractFut {
|
||||
|
@ -32,7 +32,7 @@ use crate::{
|
||||
/// To extract typed data from a request body, the inner type `T` must implement the
|
||||
/// [`DeserializeOwned`] trait.
|
||||
///
|
||||
/// Use [`FormConfig`] to configure extraction process.
|
||||
/// Use [`FormConfig`] to configure extraction options.
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::{post, web};
|
||||
@ -126,20 +126,12 @@ impl<T> FromRequest for Form<T>
|
||||
where
|
||||
T: DeserializeOwned + 'static,
|
||||
{
|
||||
type Config = FormConfig;
|
||||
type Error = Error;
|
||||
type Future = FormExtractFut<T>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
let (limit, err_handler) = req
|
||||
.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));
|
||||
let FormConfig { limit, err_handler } = FormConfig::from_req(req).clone();
|
||||
|
||||
FormExtractFut {
|
||||
fut: UrlEncoded::new(req, payload).limit(limit),
|
||||
@ -241,14 +233,26 @@ impl FormConfig {
|
||||
self.err_handler = Some(Rc::new(f));
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
FormConfig {
|
||||
limit: 16_384, // 2^14 bytes (~16kB)
|
||||
err_handler: None,
|
||||
}
|
||||
DEFAULT_CONFIG
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,6 @@ where
|
||||
{
|
||||
type Error = ParseError;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
type Config = ();
|
||||
|
||||
#[inline]
|
||||
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
|
||||
/// [`serde::Deserialize`] trait.
|
||||
///
|
||||
/// Use [`JsonConfig`] to configure extraction process.
|
||||
/// Use [`JsonConfig`] to configure extraction options.
|
||||
///
|
||||
/// ```
|
||||
/// 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.
|
||||
impl<T: DeserializeOwned + 'static> FromRequest for Json<T> {
|
||||
impl<T: DeserializeOwned> FromRequest for Json<T> {
|
||||
type Error = Error;
|
||||
type Future = JsonExtractFut<T>;
|
||||
type Config = JsonConfig;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
let config = JsonConfig::from_req(req);
|
||||
|
||||
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();
|
||||
|
||||
JsonExtractFut {
|
||||
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,
|
||||
}
|
||||
}
|
||||
@ -157,7 +157,7 @@ pub struct JsonExtractFut<T> {
|
||||
err_handler: JsonErrorHandler,
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned + 'static> Future for JsonExtractFut<T> {
|
||||
impl<T: DeserializeOwned> Future for JsonExtractFut<T> {
|
||||
type Output = Result<Json<T>, Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
@ -225,6 +225,7 @@ pub struct JsonConfig {
|
||||
limit: usize,
|
||||
err_handler: JsonErrorHandler,
|
||||
content_type: Option<Arc<dyn Fn(mime::Mime) -> bool + Send + Sync>>,
|
||||
content_type_required: bool,
|
||||
}
|
||||
|
||||
impl JsonConfig {
|
||||
@ -252,6 +253,12 @@ impl JsonConfig {
|
||||
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
|
||||
/// back to the default payload config.
|
||||
fn from_req(req: &HttpRequest) -> &Self {
|
||||
@ -268,6 +275,7 @@ const DEFAULT_CONFIG: JsonConfig = JsonConfig {
|
||||
limit: DEFAULT_LIMIT,
|
||||
err_handler: None,
|
||||
content_type: None,
|
||||
content_type_required: true,
|
||||
};
|
||||
|
||||
impl Default for JsonConfig {
|
||||
@ -278,15 +286,18 @@ impl Default for JsonConfig {
|
||||
|
||||
/// 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:
|
||||
/// - content type is not `application/json`
|
||||
/// - content length is greater than [limit](JsonBody::limit())
|
||||
/// - `Content-Type` is not `application/json` when `ctype_required` (passed to [`new`][Self::new])
|
||||
/// is `true`.
|
||||
/// - `Content-Length` is greater than [limit](JsonBody::limit()).
|
||||
/// - The payload, when consumed, is not valid JSON.
|
||||
pub enum JsonBody<T> {
|
||||
Error(Option<JsonPayloadError>),
|
||||
Body {
|
||||
limit: usize,
|
||||
/// Length as reported by `Content-Length` header, if present.
|
||||
length: Option<usize>,
|
||||
#[cfg(feature = "__compress")]
|
||||
payload: Decompress<Payload>,
|
||||
@ -305,18 +316,21 @@ impl<T: DeserializeOwned> JsonBody<T> {
|
||||
pub fn new(
|
||||
req: &HttpRequest,
|
||||
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 {
|
||||
// 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.suffix() == Some(mime::JSON)
|
||||
|| ctype.map_or(false, |predicate| predicate(mime))
|
||||
|| ctype_fn.map_or(false, |predicate| predicate(mime))
|
||||
} 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));
|
||||
}
|
||||
|
||||
@ -326,7 +340,7 @@ impl<T: DeserializeOwned> JsonBody<T> {
|
||||
.and_then(|l| l.to_str().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.
|
||||
// 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>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
@ -563,7 +577,7 @@ mod tests {
|
||||
#[actix_rt::test]
|
||||
async fn test_json_body() {
|
||||
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));
|
||||
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
@ -572,7 +586,7 @@ mod tests {
|
||||
header::HeaderValue::from_static("application/text"),
|
||||
))
|
||||
.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));
|
||||
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
@ -586,7 +600,7 @@ mod tests {
|
||||
))
|
||||
.to_http_parts();
|
||||
|
||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None)
|
||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true)
|
||||
.limit(100)
|
||||
.await;
|
||||
assert!(json_eq(
|
||||
@ -605,7 +619,7 @@ mod tests {
|
||||
.set_payload(Bytes::from_static(&[0u8; 1000]))
|
||||
.to_http_parts();
|
||||
|
||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None)
|
||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true)
|
||||
.limit(100)
|
||||
.await;
|
||||
|
||||
@ -626,7 +640,7 @@ mod tests {
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.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!(
|
||||
json.ok().unwrap(),
|
||||
MyObject {
|
||||
@ -696,6 +710,21 @@ mod tests {
|
||||
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]
|
||||
async fn test_with_config_in_data_wrapper() {
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user