1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-26 02:29:57 +02:00

Compare commits

...

27 Commits

Author SHA1 Message Date
Rob Ede
a2f59c02f7 bump actix-server to beta 9 (#2442) 2021-11-15 04:03:33 +00:00
Rob Ede
2754608f3c fix codegen tests 2021-11-08 02:46:43 +00:00
Ibraheem Ahmed
c020cedb63 Log internal server errors (#2387) 2021-11-07 17:02:23 +00:00
fakeshadow
5e554dca35 fix awc clippy warning (#2431) 2021-11-04 15:57:55 +00:00
fakeshadow
6ec2d7b909 add keep alive to h2 through ping pong (#2433) 2021-11-04 15:15:23 +00:00
Ali MJ Al-Nasrawy
ec6d284a8e improve "data no configured" message (#2429) 2021-10-31 13:19:21 +00:00
fakeshadow
be9530eb72 avoid building actix-tls with no-default-features (#2426) 2021-10-26 13:16:48 +01:00
Luca Palmieri
855e260fdb Add html_utf8 content type. (#2423) 2021-10-26 09:24:38 +01:00
fakeshadow
d13854505f move actix_http::client module to awc (#2425) 2021-10-26 00:37:40 +01:00
fakeshadow
d40b6748bc remove dead dep (#2420) 2021-10-22 00:22:58 +01:00
Rob Ede
c79b9a0df3 prepare actix-files release 0.6.0-beta.8 2021-10-20 23:32:46 +01:00
Rob Ede
4af414064b prepare actix-multipart release 0.4.0-beta.7 2021-10-20 23:31:46 +01:00
Rob Ede
9abe166d52 actix-web beta 10 releases (#2417) 2021-10-20 22:32:05 +01:00
Rob Ede
c09ec6af4c split off coverage ci job 2021-10-20 02:27:30 +01:00
Rob Ede
37f2bf5625 clippy 2021-10-20 02:06:51 +01:00
Rob Ede
4f6f0b0137 chore: Bump rustls to 0.20.0 (#2416)
Co-authored-by: Kirill Mironov <vetrokm@gmail.com>
2021-10-20 02:00:11 +01:00
Rob Ede
591abc37c3 add test runtime macro (#2409) 2021-10-19 17:30:32 +01:00
Rob Ede
ad22cc4e7f bump msrv to 1.52.1 2021-10-19 01:59:28 +01:00
Rob Ede
efdf3ab1c3 clippy 2021-10-19 01:32:58 +01:00
Rob Ede
6b3ea4fc61 copy original route macro input with compile errors (#2410) 2021-10-14 18:06:31 +01:00
James Rhodes
99985fc4ec web: implement into_inner for Data<T: ?Sized> (#2407) 2021-10-12 18:35:33 +01:00
Omid Rad
a6707fb7ee Remove checked_expr (#2401) 2021-10-11 18:28:09 +01:00
Rob Ede
a3806cde19 fix changelog 2021-09-12 22:41:08 +01:00
Jake
efefa0d0ce web: add option to not require content type header for Json (#2362)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-09-11 17:27:50 +01:00
Rob Ede
450ff5fa1d improve extract docs (#2384) 2021-09-11 16:48:47 +01:00
Arniu Tseng
8ae278cb68 Remove FromRequest::Config (#2233)
Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
Co-authored-by: Igor Aleksanov <popzxc@yandex.ru>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-09-11 01:11:16 +01:00
Rob Ede
46699e3429 remove time dep from actix-http (#2383) 2021-09-11 00:01:01 +01:00
106 changed files with 1545 additions and 1070 deletions

View File

@@ -7,3 +7,8 @@ ci-default = "check --workspace --bins --tests --examples"
ci-full = "check --workspace --all-features --bins --tests --examples" ci-full = "check --workspace --all-features --bins --tests --examples"
ci-test = "test --workspace --all-features --lib --tests --no-fail-fast -- --nocapture" ci-test = "test --workspace --all-features --lib --tests --no-fail-fast -- --nocapture"
ci-doctest = "test --workspace --all-features --doc --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"

View File

@@ -14,9 +14,9 @@ jobs:
target: target:
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { 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: version:
- 1.51.0 # MSRV - 1.52.0 # MSRV
- stable - stable
- nightly - nightly
@@ -32,6 +32,8 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
# install OpenSSL on Windows # install OpenSSL on Windows
# TODO: GitHub actions docs state that OpenSSL is
# already installed on these Windows machines somewhere
- name: Set vcpkg root - name: Set vcpkg root
if: matrix.target.triple == 'x86_64-pc-windows-msvc' if: matrix.target.triple == 'x86_64-pc-windows-msvc'
run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
@@ -48,8 +50,7 @@ jobs:
- name: Generate Cargo.lock - name: Generate Cargo.lock
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with: { command: generate-lockfile }
command: generate-lockfile
- name: Cache Dependencies - name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0 uses: Swatinem/rust-cache@v1.2.0
@@ -82,32 +83,73 @@ jobs:
command: ci-test command: ci-test
args: --skip=test_reading_deflate_encoding_large_random_rustls args: --skip=test_reading_deflate_encoding_large_random_rustls
- name: 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 - name: Clear the cargo caches
run: | run: |
cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean
cargo-cache 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: rustdoc:
name: rustdoc name: rustdoc
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@@ -3,6 +3,42 @@
## Unreleased - 2021-xx-xx ## 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 ## 4.0.0-beta.9 - 2021-09-09
### Added ### Added
* Re-export actix-service `ServiceFactory` in `dev` module. [#2325] * Re-export actix-service `ServiceFactory` in `dev` module. [#2325]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "4.0.0-beta.9" version = "4.0.0-beta.11"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
keywords = ["actix", "http", "web", "framework", "async"] keywords = ["actix", "http", "web", "framework", "async"]
@@ -11,13 +11,14 @@ categories = [
"web-programming::websocket" "web-programming::websocket"
] ]
homepage = "https://actix.rs" 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" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[package.metadata.docs.rs] [package.metadata.docs.rs]
# features that docs.rs will build with # features that docs.rs will build with
features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"] features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"]
rustdoc-args = ["--cfg", "docsrs"]
[lib] [lib]
name = "actix_web" name = "actix_web"
@@ -37,8 +38,6 @@ members = [
"actix-test", "actix-test",
"actix-router", "actix-router",
] ]
# enable when MSRV is 1.51+
# resolver = "2"
[features] [features]
default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
@@ -62,22 +61,22 @@ openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"]
# rustls # rustls
rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/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. # Don't rely on these whatsoever. They may disappear at anytime.
__compress = [] __compress = []
[dependencies] [dependencies]
actix-codec = "0.4.0" actix-codec = "0.4.0"
actix-macros = "0.2.1" actix-macros = "0.2.3"
actix-router = "0.5.0-beta.2"
actix-rt = "2.2" actix-rt = "2.2"
actix-server = "2.0.0-beta.3" actix-server = "2.0.0-beta.9"
actix-service = "2.0.0" actix-service = "2.0.0"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } actix-tls = { version = "3.0.0-beta.7", default-features = false, optional = true }
actix-web-codegen = "0.5.0-beta.4" actix-http = "3.0.0-beta.12"
actix-http = "3.0.0-beta.10" actix-router = "0.5.0-beta.2"
actix-web-codegen = "0.5.0-beta.5"
ahash = "0.7" ahash = "0.7"
bytes = "1" bytes = "1"
@@ -101,22 +100,24 @@ serde_json = "1.0"
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
smallvec = "1.6.1" smallvec = "1.6.1"
socket2 = "0.4.0" socket2 = "0.4.0"
time = { version = "0.2.23", default-features = false, features = ["std"] } time = { version = "0.3", default-features = false, features = ["formatting"] }
url = "2.1" url = "2.1"
[dev-dependencies] [dev-dependencies]
actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.6", features = ["openssl", "rustls"] }
awc = { version = "3.0.0-beta.8", features = ["openssl"] } awc = { version = "3.0.0-beta.10", features = ["openssl"] }
brotli2 = "0.3.2" brotli2 = "0.3.2"
criterion = { version = "0.3", features = ["html_reports"] } criterion = { version = "0.3", features = ["html_reports"] }
env_logger = "0.8" env_logger = "0.8"
flate2 = "1.0.13" flate2 = "1.0.13"
zstd = "0.7" futures-util = { version = "0.3.7", default-features = false, features = ["std"] }
rand = "0.8" rand = "0.8"
rcgen = "0.8" rcgen = "0.8"
rustls-pemfile = "0.2"
tls-openssl = { package = "openssl", version = "0.10.9" } tls-openssl = { package = "openssl", version = "0.10.9" }
tls-rustls = { package = "rustls", version = "0.19.0" } tls-rustls = { package = "rustls", version = "0.20.0" }
zstd = "0.7"
[profile.dev] [profile.dev]
# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much. # Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much.

View File

@@ -11,6 +11,8 @@
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`. Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
* The `type Config` of `FromRequest` was removed.
* Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). * Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd).
By default all compression algorithms are enabled. By default all compression algorithms are enabled.
To select algorithm you want to include with `middleware::Compress` use following flags: To select algorithm you want to include with `middleware::Compress` use following flags:

View File

@@ -6,10 +6,10 @@
<p> <p>
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.9)](https://docs.rs/actix-web/4.0.0-beta.9) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.11)](https://docs.rs/actix-web/4.0.0-beta.11)
[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.9) [![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.11/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.11)
<br /> <br />
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions) [![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)
@@ -32,7 +32,7 @@
* SSL support using OpenSSL or Rustls * SSL support using OpenSSL or Rustls
* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
* Includes an async [HTTP client](https://docs.rs/awc/) * Includes an async [HTTP client](https://docs.rs/awc/)
* Runs on stable Rust 1.51+ * Runs on stable Rust 1.52+
## Documentation ## Documentation

View File

@@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx ## 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 ## 0.6.0-beta.7 - 2021-09-09
* Minimum supported Rust version (MSRV) is now 1.51. * Minimum supported Rust version (MSRV) is now 1.51.

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-files" name = "actix-files"
version = "0.6.0-beta.7" version = "0.6.0-beta.8"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static file serving for Actix Web" description = "Static file serving for Actix Web"
keywords = ["actix", "http", "async", "futures"] keywords = ["actix", "http", "async", "futures"]
@@ -15,8 +15,8 @@ name = "actix_files"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = { version = "4.0.0-beta.9", default-features = false } actix-web = { version = "4.0.0-beta.11", default-features = false }
actix-http = "3.0.0-beta.10" actix-http = "3.0.0-beta.12"
actix-service = "2.0.0" actix-service = "2.0.0"
actix-utils = "3.0.0" actix-utils = "3.0.0"
@@ -33,5 +33,5 @@ percent-encoding = "2.1"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.2" actix-rt = "2.2"
actix-web = "4.0.0-beta.9" actix-web = "4.0.0-beta.11"
actix-test = "0.1.0-beta.3" actix-test = "0.1.0-beta.6"

View File

@@ -3,11 +3,11 @@
> Static file serving for Actix Web > Static file serving for Actix Web
[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.7)](https://docs.rs/actix-files/0.6.0-beta.7) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.8)](https://docs.rs/actix-files/0.6.0-beta.8)
[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![License](https://img.shields.io/crates/l/actix-files.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.7/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.7) [![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.8/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.8)
[![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
@@ -15,4 +15,4 @@
- [API Documentation](https://docs.rs/actix-files/) - [API Documentation](https://docs.rs/actix-files/)
- [Example Project](https://github.com/actix/examples/tree/master/basics/static_index) - [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

View File

@@ -83,7 +83,7 @@ mod tests {
use super::*; use super::*;
#[actix_rt::test] #[actix_web::test]
async fn test_file_extension_to_mime() { async fn test_file_extension_to_mime() {
let m = file_extension_to_mime(""); let m = file_extension_to_mime("");
assert_eq!(m, mime::APPLICATION_OCTET_STREAM); assert_eq!(m, mime::APPLICATION_OCTET_STREAM);

View File

@@ -59,7 +59,6 @@ impl AsRef<Path> for PathBufWrap {
impl FromRequest for PathBufWrap { impl FromRequest for PathBufWrap {
type Error = UriSegmentError; type Error = UriSegmentError;
type Future = Ready<Result<Self, Self::Error>>; type Future = Ready<Result<Self, Self::Error>>;
type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
ready(req.match_info().path().parse()) ready(req.match_info().path().parse())

View File

@@ -8,7 +8,7 @@ use actix_web::{
App, App,
}; };
#[actix_rt::test] #[actix_web::test]
async fn test_utf8_file_contents() { async fn test_utf8_file_contents() {
// use default ISO-8859-1 encoding // use default ISO-8859-1 encoding
let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await; let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await;

View File

@@ -7,7 +7,7 @@ use actix_web::{
}; };
use bytes::Bytes; use bytes::Bytes;
#[actix_rt::test] #[actix_web::test]
async fn test_guard_filter() { async fn test_guard_filter() {
let srv = test::init_service( let srv = test::init_service(
App::new() App::new()

View File

@@ -3,6 +3,14 @@
## Unreleased - 2021-xx-xx ## 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 ## 3.0.0-beta.5 - 2021-09-09
* Minimum supported Rust version (MSRV) is now 1.51. * Minimum supported Rust version (MSRV) is now 1.51.

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-http-test" name = "actix-http-test"
version = "3.0.0-beta.5" version = "3.0.0-beta.6"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Various helpers for Actix applications to use during testing" description = "Various helpers for Actix applications to use during testing"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
@@ -31,25 +31,25 @@ openssl = ["tls-openssl", "awc/openssl"]
[dependencies] [dependencies]
actix-service = "2.0.0" actix-service = "2.0.0"
actix-codec = "0.4.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-utils = "3.0.0"
actix-rt = "2.2" actix-rt = "2.2"
actix-server = "2.0.0-beta.3" actix-server = "2.0.0-beta.9"
awc = { version = "3.0.0-beta.8", default-features = false } awc = { version = "3.0.0-beta.10", default-features = false }
base64 = "0.13" base64 = "0.13"
bytes = "1" bytes = "1"
futures-core = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.7", default-features = false }
http = "0.2.2" http = "0.2.5"
log = "0.4" log = "0.4"
socket2 = "0.4" socket2 = "0.4"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
slab = "0.4" slab = "0.4"
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
time = { version = "0.2.23", default-features = false, features = ["std"] }
tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
tokio = { version = "1.2", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
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-http = "3.0.0-beta.10" actix-http = "3.0.0-beta.12"

View File

@@ -3,15 +3,15 @@
> Various helpers for Actix applications to use during testing. > Various helpers for Actix applications to use during testing.
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.5)](https://docs.rs/actix-http-test/3.0.0-beta.5) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.6)](https://docs.rs/actix-http-test/3.0.0-beta.6)
[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
<br> <br>
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.5/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.5) [![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.6/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.6)
[![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http-test) - [API Documentation](https://docs.rs/actix-http-test)
- Minimum Supported Rust Version (MSRV): 1.51.0 - Minimum Supported Rust Version (MSRV): 1.52

View File

@@ -7,8 +7,7 @@
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
extern crate tls_openssl as openssl; extern crate tls_openssl as openssl;
use std::sync::mpsc; use std::{net, thread, time::Duration};
use std::{net, thread, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_rt::{net::TcpStream, System}; use actix_rt::{net::TcpStream, System};
@@ -20,6 +19,7 @@ use bytes::Bytes;
use futures_core::stream::Stream; use futures_core::stream::Stream;
use http::Method; use http::Method;
use socket2::{Domain, Protocol, Socket, Type}; use socket2::{Domain, Protocol, Socket, Type};
use tokio::sync::mpsc;
/// Start test server /// Start test server
/// ///
@@ -37,7 +37,7 @@ use socket2::{Domain, Protocol, Socket, Type};
/// Ok(HttpResponse::Ok().into()) /// Ok(HttpResponse::Ok().into())
/// } /// }
/// ///
/// #[actix_rt::test] /// #[actix_web::test]
/// async fn test_example() { /// async fn test_example() {
/// let mut srv = TestServer::start( /// let mut srv = TestServer::start(
/// || HttpService::new( /// || HttpService::new(
@@ -56,12 +56,13 @@ pub async fn test_server<F: ServiceFactory<TcpStream>>(factory: F) -> TestServer
test_server_with_addr(tcp, factory).await 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>>( pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
tcp: net::TcpListener, tcp: net::TcpListener,
factory: F, factory: F,
) -> TestServer { ) -> 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 // run server in separate thread
thread::spawn(move || { 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 local_addr = tcp.local_addr().unwrap();
let srv = Server::build() let srv = Server::build()
.listen("test", tcp, factory)?
.workers(1) .workers(1)
.disable_signals(); .disable_signals()
.listen("test", tcp, factory)
.expect("test server could not be created");
sys.block_on(async { let srv = srv.run();
srv.run(); started_tx
tx.send((System::current(), local_addr)).unwrap(); .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 = { let client = {
#[cfg(feature = "openssl")]
let connector = { 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(); let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_verify(SslVerifyMode::NONE);
let _ = builder builder.set_verify(SslVerifyMode::NONE);
.set_alpn_protos(b"\x02h2\x08http/1.1") let _ = builder
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); .set_alpn_protos(b"\x02h2\x08http/1.1")
Connector::new() .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
.conn_lifetime(time::Duration::from_secs(0))
.timeout(time::Duration::from_millis(30000)) Connector::new()
.ssl(builder.build()) .conn_lifetime(Duration::from_secs(0))
} .timeout(Duration::from_millis(30000))
#[cfg(not(feature = "openssl"))] .ssl(builder.build())
{ };
Connector::new()
.conn_lifetime(time::Duration::from_secs(0)) #[cfg(not(feature = "openssl"))]
.timeout(time::Duration::from_millis(30000)) let connector = {
} Connector::new()
.conn_lifetime(Duration::from_secs(0))
.timeout(Duration::from_millis(30000))
}; };
Client::builder().connector(connector).finish() Client::builder().connector(connector).finish()
}; };
TestServer { TestServer {
addr, server,
client, client,
system, system,
addr,
thread_stop_rx,
} }
} }
/// Test server controller /// Test server controller
pub struct TestServer { pub struct TestServer {
server: actix_server::ServerHandle,
client: awc::Client,
system: actix_rt::System,
addr: net::SocketAddr, addr: net::SocketAddr,
client: Client, thread_stop_rx: mpsc::Receiver<()>,
system: System,
} }
impl TestServer { impl TestServer {
@@ -258,15 +273,32 @@ impl TestServer {
self.client.headers() self.client.headers()
} }
/// Stop HTTP server /// Gracefully stop HTTP server.
fn stop(&mut 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(); 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 { impl Drop for TestServer {
fn drop(&mut self) { 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();
} }
} }

View File

@@ -3,6 +3,26 @@
## Unreleased - 2021-xx-xx ## 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 ## 3.0.0-beta.10 - 2021-09-09
### Changed ### Changed
* `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377] * `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377]

View File

@@ -1,14 +1,17 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "3.0.0-beta.10" version = "3.0.0-beta.12"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "HTTP primitives for the Actix ecosystem" description = "HTTP primitives for the Actix ecosystem"
keywords = ["actix", "http", "framework", "async", "futures"] keywords = ["actix", "http", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web" repository = "https://github.com/actix/actix-web.git"
categories = ["network-programming", "asynchronous", categories = [
"web-programming::http-server", "network-programming",
"web-programming::websocket"] "asynchronous",
"web-programming::http-server",
"web-programming::websocket",
]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
@@ -24,19 +27,16 @@ path = "src/lib.rs"
default = [] default = []
# openssl # openssl
openssl = ["actix-tls/openssl"] openssl = ["actix-tls/accept", "actix-tls/openssl"]
# rustls support # rustls support
rustls = ["actix-tls/rustls"] rustls = ["actix-tls/accept", "actix-tls/rustls"]
# enable compression support # enable compression support
compress-brotli = ["brotli2", "__compress"] compress-brotli = ["brotli2", "__compress"]
compress-gzip = ["flate2", "__compress"] compress-gzip = ["flate2", "__compress"]
compress-zstd = ["zstd", "__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. # Internal (PRIVATE!) features used to aid testing and cheking feature status.
# Don't rely on these whatsoever. They may disappear at anytime. # Don't rely on these whatsoever. They may disappear at anytime.
__compress = [] __compress = []
@@ -46,7 +46,6 @@ actix-service = "2.0.0"
actix-codec = "0.4.0" actix-codec = "0.4.0"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-rt = "2.2" actix-rt = "2.2"
actix-tls = { version = "3.0.0-beta.5", features = ["accept", "connect"] }
ahash = "0.7" ahash = "0.7"
base64 = "0.13" base64 = "0.13"
@@ -58,45 +57,44 @@ encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
h2 = "0.3.1" h2 = "0.3.1"
http = "0.2.2" http = "0.2.5"
httparse = "1.5.1" httparse = "1.5.1"
httpdate = "1.0.1"
itoa = "0.4" itoa = "0.4"
language-tags = "0.3" language-tags = "0.3"
local-channel = "0.1" local-channel = "0.1"
once_cell = "1.5"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
percent-encoding = "2.1" percent-encoding = "2.1"
pin-project = "1.0.0" pin-project = "1.0.0"
pin-project-lite = "0.2" pin-project-lite = "0.2"
rand = "0.8" rand = "0.8"
regex = "1.3"
serde = "1.0"
sha-1 = "0.9" sha-1 = "0.9"
smallvec = "1.6.1" smallvec = "1.6.1"
time = { version = "0.2.23", default-features = false, features = ["std"] }
tokio = { version = "1.2", features = ["sync"] } # tls
actix-tls = { version = "3.0.0-beta.7", default-features = false, optional = true }
# compression # compression
brotli2 = { version="0.3.2", optional = true } brotli2 = { version="0.3.2", optional = true }
flate2 = { version = "1.0.13", optional = true } flate2 = { version = "1.0.13", optional = true }
zstd = { version = "0.7", optional = true } zstd = { version = "0.7", optional = true }
trust-dns-resolver = { version = "0.20.0", optional = true }
[dev-dependencies] [dev-dependencies]
actix-server = "2.0.0-beta.3" actix-server = "2.0.0-beta.9"
actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.6", features = ["openssl"] }
actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.7", features = ["openssl"] }
async-stream = "0.3" async-stream = "0.3"
criterion = { version = "0.3", features = ["html_reports"] } criterion = { version = "0.3", features = ["html_reports"] }
env_logger = "0.8" env_logger = "0.8"
rcgen = "0.8" rcgen = "0.8"
regex = "1.3"
rustls-pemfile = "0.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
tls-openssl = { version = "0.10", package = "openssl" } tls-openssl = { package = "openssl", version = "0.10.9" }
tls-rustls = { version = "0.19", package = "rustls" } tls-rustls = { package = "rustls", version = "0.20.0" }
webpki = { version = "0.21.0" } tokio = { version = "1.2", features = ["net", "rt"] }
[[example]] [[example]]
name = "ws" name = "ws"

View File

@@ -3,18 +3,18 @@
> HTTP primitives for the Actix ecosystem. > HTTP primitives for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.10)](https://docs.rs/actix-http/3.0.0-beta.10) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.12)](https://docs.rs/actix-http/3.0.0-beta.12)
[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.10/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.10) [![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.12/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.12)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http) - [API Documentation](https://docs.rs/actix-http)
- Minimum Supported Rust Version (MSRV): 1.51.0 - Minimum Supported Rust Version (MSRV): 1.52
## Example ## Example

View File

@@ -85,22 +85,31 @@ impl Stream for Heartbeat {
fn tls_config() -> rustls::ServerConfig { fn tls_config() -> rustls::ServerConfig {
use std::io::BufReader; use std::io::BufReader;
use rustls::{ use rustls::{Certificate, PrivateKey};
internal::pemfile::{certs, pkcs8_private_keys}, use rustls_pemfile::{certs, pkcs8_private_keys};
NoClientAuth, ServerConfig,
};
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
let cert_file = cert.serialize_pem().unwrap(); let cert_file = cert.serialize_pem().unwrap();
let key_file = cert.serialize_private_key_pem(); 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 cert_file = &mut BufReader::new(cert_file.as_bytes());
let key_file = &mut BufReader::new(key_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(); 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 config
} }

View File

@@ -1,18 +1,19 @@
use std::cell::Cell; use std::{
use std::fmt::Write; cell::Cell,
use std::rc::Rc; fmt::{self, Write},
use std::time::Duration; net,
use std::{fmt, net}; rc::Rc,
time::{Duration, SystemTime},
};
use actix_rt::{ use actix_rt::{
task::JoinHandle, task::JoinHandle,
time::{interval, sleep_until, Instant, Sleep}, time::{interval, sleep_until, Instant, Sleep},
}; };
use bytes::BytesMut; use bytes::BytesMut;
use time::OffsetDateTime;
/// "Sun, 06 Nov 1994 08:49:37 GMT".len() /// "Sun, 06 Nov 1994 08:49:37 GMT".len()
const DATE_VALUE_LENGTH: usize = 29; pub(crate) const DATE_VALUE_LENGTH: usize = 29;
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
/// Server keep-alive setting /// Server keep-alive setting
@@ -206,12 +207,7 @@ impl Date {
fn update(&mut self) { fn update(&mut self) {
self.pos = 0; self.pos = 0;
write!( write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap();
self,
"{}",
OffsetDateTime::now_utc().format("%a, %d %b %Y %H:%M:%S GMT")
)
.unwrap();
} }
} }
@@ -269,11 +265,11 @@ impl DateService {
} }
// TODO: move to a util module for testing all spawn handle drop style tasks. // TODO: move to a util module for testing all spawn handle drop style tasks.
#[cfg(test)]
/// Test Module for checking the drop state of certain async tasks that are spawned /// Test Module for checking the drop state of certain async tasks that are spawned
/// with `actix_rt::spawn` /// with `actix_rt::spawn`
/// ///
/// The target task must explicitly generate `NotifyOnDrop` when spawn the task /// The target task must explicitly generate `NotifyOnDrop` when spawn the task
#[cfg(test)]
mod notify_on_drop { mod notify_on_drop {
use std::cell::RefCell; use std::cell::RefCell;
@@ -283,9 +279,8 @@ mod notify_on_drop {
/// Check if the spawned task is dropped. /// Check if the spawned task is dropped.
/// ///
/// # Panic: /// # Panics
/// /// Panics when there was no `NotifyOnDrop` instance on current thread.
/// When there was no `NotifyOnDrop` instance on current thread
pub(crate) fn is_dropped() -> bool { pub(crate) fn is_dropped() -> bool {
NOTIFY_DROPPED.with(|bool| { NOTIFY_DROPPED.with(|bool| {
bool.borrow() bool.borrow()

View File

@@ -303,9 +303,9 @@ where
body: &impl MessageBody, body: &impl MessageBody,
) -> Result<BodySize, DispatchError> { ) -> Result<BodySize, DispatchError> {
let size = body.size(); let size = body.size();
let mut this = self.project(); let this = self.project();
this.codec this.codec
.encode(Message::Item((message, size)), &mut this.write_buf) .encode(Message::Item((message, size)), this.write_buf)
.map_err(|err| { .map_err(|err| {
if let Some(mut payload) = this.payload.take() { if let Some(mut payload) = this.payload.take() {
payload.set_error(PayloadError::Incomplete(None)); payload.set_error(PayloadError::Incomplete(None));
@@ -425,13 +425,13 @@ where
Poll::Ready(Some(Ok(item))) => { Poll::Ready(Some(Ok(item))) => {
this.codec.encode( this.codec.encode(
Message::Chunk(Some(item)), Message::Chunk(Some(item)),
&mut this.write_buf, this.write_buf,
)?; )?;
} }
Poll::Ready(None) => { Poll::Ready(None) => {
this.codec this.codec
.encode(Message::Chunk(None), &mut this.write_buf)?; .encode(Message::Chunk(None), this.write_buf)?;
// payload stream finished. // payload stream finished.
// set state to None and handle next message // set state to None and handle next message
this.state.set(State::None); this.state.set(State::None);
@@ -460,13 +460,13 @@ where
Poll::Ready(Some(Ok(item))) => { Poll::Ready(Some(Ok(item))) => {
this.codec.encode( this.codec.encode(
Message::Chunk(Some(item)), Message::Chunk(Some(item)),
&mut this.write_buf, this.write_buf,
)?; )?;
} }
Poll::Ready(None) => { Poll::Ready(None) => {
this.codec this.codec
.encode(Message::Chunk(None), &mut this.write_buf)?; .encode(Message::Chunk(None), this.write_buf)?;
// payload stream finished. // payload stream finished.
// set state to None and handle next message // set state to None and handle next message
this.state.set(State::None); this.state.set(State::None);
@@ -592,7 +592,7 @@ where
let mut updated = false; let mut updated = false;
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
loop { loop {
match this.codec.decode(&mut this.read_buf) { match this.codec.decode(this.read_buf) {
Ok(Some(msg)) => { Ok(Some(msg)) => {
updated = true; updated = true;
this.flags.insert(Flags::STARTED); this.flags.insert(Flags::STARTED);

View File

@@ -20,6 +20,7 @@ const AVERAGE_HEADER_SIZE: usize = 30;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct MessageEncoder<T: MessageType> { pub(crate) struct MessageEncoder<T: MessageType> {
#[allow(dead_code)]
pub length: BodySize, pub length: BodySize,
pub te: TransferEncoding, pub te: TransferEncoding,
_phantom: PhantomData<T>, _phantom: PhantomData<T>,

View File

@@ -10,11 +10,15 @@ use std::{
}; };
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::time::Sleep;
use actix_service::Service; use actix_service::Service;
use actix_utils::future::poll_fn; use actix_utils::future::poll_fn;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures_core::ready; 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 http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
use log::{error, trace}; use log::{error, trace};
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
@@ -36,29 +40,46 @@ pin_project! {
on_connect_data: OnConnectData, on_connect_data: OnConnectData,
config: ServiceConfig, config: ServiceConfig,
peer_addr: Option<net::SocketAddr>, 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( pub(crate) fn new(
flow: Rc<HttpFlow<S, X, U>>, flow: Rc<HttpFlow<S, X, U>>,
connection: Connection<T, Bytes>, mut connection: Connection<T, Bytes>,
on_connect_data: OnConnectData, on_connect_data: OnConnectData,
config: ServiceConfig, config: ServiceConfig,
peer_addr: Option<net::SocketAddr>, peer_addr: Option<net::SocketAddr>,
) -> Self { ) -> 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 { Self {
flow, flow,
config, config,
peer_addr, peer_addr,
connection, connection,
on_connect_data, on_connect_data,
ping_pong,
_phantom: PhantomData, _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> impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
@@ -77,54 +98,92 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut(); let this = self.get_mut();
while let Some((req, tx)) = loop {
ready!(Pin::new(&mut this.connection).poll_accept(cx)?) match Pin::new(&mut this.connection).poll_accept(cx)? {
{ Poll::Ready(Some((req, tx))) => {
let (parts, body) = req.into_parts(); let (parts, body) = req.into_parts();
let pl = crate::h2::Payload::new(body); let pl = crate::h2::Payload::new(body);
let pl = Payload::<crate::payload::PayloadStream>::H2(pl); let pl = Payload::<crate::payload::PayloadStream>::H2(pl);
let mut req = Request::with_payload(pl); let mut req = Request::with_payload(pl);
let head = req.head_mut(); let head = req.head_mut();
head.uri = parts.uri; head.uri = parts.uri;
head.method = parts.method; head.method = parts.method;
head.version = parts.version; head.version = parts.version;
head.headers = parts.headers.into(); head.headers = parts.headers.into();
head.peer_addr = this.peer_addr; head.peer_addr = this.peer_addr;
// merge on_connect_ext data into request extensions // merge on_connect_ext data into request extensions
this.on_connect_data.merge_into(&mut req); this.on_connect_data.merge_into(&mut req);
let fut = this.flow.service.call(req); let fut = this.flow.service.call(req);
let config = this.config.clone(); let config = this.config.clone();
// multiplex request handling with spawn task // multiplex request handling with spawn task
actix_rt::spawn(async move { actix_rt::spawn(async move {
// resolve service call and send response. // resolve service call and send response.
let res = match fut.await { let res = match fut.await {
Ok(res) => handle_response(res.into(), tx, config).await, Ok(res) => handle_response(res.into(), tx, config).await,
Err(err) => { Err(err) => {
let res: Response<AnyBody> = err.into(); let res: Response<AnyBody> = err.into();
handle_response(res, tx, config).await handle_response(res, tx, config).await
} }
}; };
// log error. // log error.
if let Err(err) = res { if let Err(err) = res {
match err { match err {
DispatchError::SendResponse(err) => { DispatchError::SendResponse(err) => {
trace!("Error sending HTTP/2 response: {:?}", 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,
},
}
}
} }
} }

View File

@@ -177,7 +177,7 @@ mod rustls {
> { > {
let mut protos = vec![b"h2".to_vec()]; let mut protos = vec![b"h2".to_vec()];
protos.extend_from_slice(&config.alpn_protocols); protos.extend_from_slice(&config.alpn_protocols);
config.set_protocols(&protos); config.alpn_protocols = protos;
Acceptor::new(config) Acceptor::new(config)
.map_err(TlsError::Tls) .map_err(TlsError::Tls)

View 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());
}
}

View File

@@ -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());
}
}

View File

@@ -3,12 +3,12 @@
mod charset; mod charset;
mod content_encoding; mod content_encoding;
mod extended; mod extended;
mod httpdate; mod http_date;
mod quality_item; mod quality_item;
pub use self::charset::Charset; pub use self::charset::Charset;
pub use self::content_encoding::ContentEncoding; pub use self::content_encoding::ContentEncoding;
pub use self::extended::{parse_extended_value, ExtendedValue}; pub use self::extended::{parse_extended_value, ExtendedValue};
pub use self::httpdate::HttpDate; pub use self::http_date::HttpDate;
pub use self::quality_item::{q, qitem, Quality, QualityItem}; pub use self::quality_item::{q, qitem, Quality, QualityItem};
pub use language_tags::LanguageTag; pub use language_tags::LanguageTag;

View File

@@ -29,7 +29,6 @@ extern crate log;
pub mod body; pub mod body;
mod builder; mod builder;
pub mod client;
mod config; mod config;
#[cfg(feature = "__compress")] #[cfg(feature = "__compress")]
@@ -44,7 +43,6 @@ mod request;
mod response; mod response;
mod response_builder; mod response_builder;
mod service; mod service;
mod time_parser;
pub mod error; pub mod error;
pub mod h1; pub mod h1;
@@ -104,14 +102,9 @@ type ConnectCallback<IO> = dyn Fn(&IO, &mut Extensions);
/// ///
/// # Implementation Details /// # Implementation Details
/// Uses Option to reduce necessary allocations when merging with request extensions. /// Uses Option to reduce necessary allocations when merging with request extensions.
#[derive(Default)]
pub(crate) struct OnConnectData(Option<Extensions>); pub(crate) struct OnConnectData(Option<Extensions>);
impl Default for OnConnectData {
fn default() -> Self {
Self(None)
}
}
impl OnConnectData { impl OnConnectData {
/// Construct by calling the on-connect callback with the underlying transport I/O. /// Construct by calling the on-connect callback with the underlying transport I/O.
pub(crate) fn from_io<T>( pub(crate) fn from_io<T>(

View File

@@ -263,7 +263,7 @@ mod openssl {
mod rustls { mod rustls {
use std::io; 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 actix_tls::accept::TlsError;
use super::*; use super::*;
@@ -308,14 +308,13 @@ mod rustls {
> { > {
let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
protos.extend_from_slice(&config.alpn_protocols); protos.extend_from_slice(&config.alpn_protocols);
config.set_protocols(&protos); config.alpn_protocols = protos;
Acceptor::new(config) Acceptor::new(config)
.map_err(TlsError::Tls) .map_err(TlsError::Tls)
.map_init_err(|_| panic!()) .map_init_err(|_| panic!())
.and_then(|io: TlsStream<TcpStream>| async { .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") { if protos.windows(2).any(|window| window == b"h2") {
Protocol::Http2 Protocol::Http2
} else { } else {

View File

@@ -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)));
}
}

View File

@@ -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. // 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. // 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>() }; let (prefix, words, suffix) = unsafe { buf.align_to_mut::<u32>() };
apply_mask_fallback(&mut prefix, mask); apply_mask_fallback(prefix, mask);
let head = prefix.len() & 3; let head = prefix.len() & 3;
let mask_u32 = if head > 0 { let mask_u32 = if head > 0 {
if cfg!(target_endian = "big") { 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() { for word in words.iter_mut() {
*word ^= mask_u32; *word ^= mask_u32;
} }
apply_mask_fallback(&mut suffix, mask_u32.to_ne_bytes()); apply_mask_fallback(suffix, mask_u32.to_ne_bytes());
} }
#[cfg(test)] #[cfg(test)]

View 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(())
}

View File

@@ -8,7 +8,7 @@ use actix_http::{
body::{AnyBody, Body, SizedStream}, body::{AnyBody, Body, SizedStream},
error::PayloadError, error::PayloadError,
http::{ http::{
header::{self, HeaderName, HeaderValue}, header::{self, HeaderValue},
Method, StatusCode, Version, Method, StatusCode, Version,
}, },
Error, HttpMessage, HttpService, Request, Response, Error, HttpMessage, HttpService, Request, Response,
@@ -143,38 +143,25 @@ async fn test_h2_content_length() {
}) })
.await; .await;
let header = HeaderName::from_static("content-length"); static VALUE: HeaderValue = HeaderValue::from_static("0");
let value = HeaderValue::from_static("0");
{ {
for &i in &[0] { let req = srv.request(Method::HEAD, srv.surl("/0")).send();
let req = srv req.await.expect_err("should timeout on recv 1xx frame");
.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 let req = srv.request(Method::GET, srv.surl("/0")).send();
.request(Method::GET, srv.surl(&format!("/{}", i))) req.await.expect_err("should timeout on recv 1xx frame");
.send();
let _response = req.await.expect_err("should timeout on recv 1xx frame");
// assert_eq!(response.headers().get(&header), None);
}
for &i in &[1] { let req = srv.request(Method::GET, srv.surl("/1")).send();
let req = srv let response = req.await.unwrap();
.request(Method::GET, srv.surl(&format!("/{}", i))) assert!(response.headers().get("content-length").is_none());
.send();
let response = req.await.unwrap();
assert_eq!(response.headers().get(&header), None);
}
for &i in &[2, 3] { for &i in &[2, 3] {
let req = srv let req = srv
.request(Method::GET, srv.surl(&format!("/{}", i))) .request(Method::GET, srv.surl(&format!("/{}", i)))
.send(); .send();
let response = req.await.unwrap(); let response = req.await.unwrap();
assert_eq!(response.headers().get(&header), Some(&value)); assert_eq!(response.headers().get("content-length"), Some(&VALUE));
} }
} }
} }

View File

@@ -3,7 +3,7 @@
extern crate tls_rustls as rustls; extern crate tls_rustls as rustls;
use std::{ use std::{
convert::Infallible, convert::{Infallible, TryFrom},
io::{self, BufReader, Write}, io::{self, BufReader, Write},
net::{SocketAddr, TcpStream as StdTcpStream}, net::{SocketAddr, TcpStream as StdTcpStream},
sync::Arc, sync::Arc,
@@ -20,16 +20,14 @@ use actix_http::{
}; };
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::{fn_factory_with_config, fn_service}; 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 actix_utils::future::{err, ok};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use derive_more::{Display, Error}; use derive_more::{Display, Error};
use futures_core::Stream; use futures_core::Stream;
use futures_util::stream::{once, StreamExt as _}; use futures_util::stream::{once, StreamExt as _};
use rustls::{ use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName};
internal::pemfile::{certs, pkcs8_private_keys}, use rustls_pemfile::{certs, pkcs8_private_keys};
NoClientAuth, ServerConfig as RustlsServerConfig, Session,
};
use webpki::DNSNameRef;
async fn load_body<S>(mut stream: S) -> Result<BytesMut, PayloadError> async fn load_body<S>(mut stream: S) -> Result<BytesMut, PayloadError>
where where
@@ -47,13 +45,24 @@ fn tls_config() -> RustlsServerConfig {
let cert_file = cert.serialize_pem().unwrap(); let cert_file = cert.serialize_pem().unwrap();
let key_file = cert.serialize_private_key_pem(); 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 cert_file = &mut BufReader::new(cert_file.as_bytes());
let key_file = &mut BufReader::new(key_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(); 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 config
} }
@@ -62,19 +71,28 @@ pub fn get_negotiated_alpn_protocol(
addr: SocketAddr, addr: SocketAddr,
client_alpn_protocol: &[u8], client_alpn_protocol: &[u8],
) -> Option<Vec<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()); config.alpn_protocols.push(client_alpn_protocol.to_vec());
let mut sess = rustls::ClientSession::new(
&Arc::new(config), let mut sess = rustls::ClientConnection::new(
DNSNameRef::try_from_ascii_str("localhost").unwrap(), Arc::new(config),
); ServerName::try_from("localhost").unwrap(),
)
.unwrap();
let mut sock = StdTcpStream::connect(addr).unwrap(); let mut sock = StdTcpStream::connect(addr).unwrap();
let mut stream = rustls::Stream::new(&mut sess, &mut sock); 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 // 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 // certificate, but it doesn't matter here as we are just interested in the negotiated ALPN
// protocol // protocol
let _ = stream.flush(); let _ = stream.flush();
sess.get_alpn_protocol().map(|proto| proto.to_vec())
sess.alpn_protocol().map(|proto| proto.to_vec())
} }
#[actix_rt::test] #[actix_rt::test]

View File

@@ -24,7 +24,7 @@ use regex::Regex;
#[actix_rt::test] #[actix_rt::test]
async fn test_h1() { async fn test_h1() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.keep_alive(KeepAlive::Disabled) .keep_alive(KeepAlive::Disabled)
.client_timeout(1000) .client_timeout(1000)
@@ -39,11 +39,13 @@ async fn test_h1() {
let response = srv.get("/").send().await.unwrap(); let response = srv.get("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_h1_2() { async fn test_h1_2() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.keep_alive(KeepAlive::Disabled) .keep_alive(KeepAlive::Disabled)
.client_timeout(1000) .client_timeout(1000)
@@ -59,6 +61,8 @@ async fn test_h1_2() {
let response = srv.get("/").send().await.unwrap(); let response = srv.get("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
srv.stop().await;
} }
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
@@ -73,7 +77,7 @@ impl From<ExpectFailed> for Response<AnyBody> {
#[actix_rt::test] #[actix_rt::test]
async fn test_expect_continue() { async fn test_expect_continue() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.expect(fn_service(|req: Request| { .expect(fn_service(|req: Request| {
if req.head().uri.query() == Some("yes=") { if req.head().uri.query() == Some("yes=") {
@@ -98,11 +102,13 @@ async fn test_expect_continue() {
let mut data = String::new(); let mut data = String::new();
let _ = stream.read_to_string(&mut data); 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")); 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] #[actix_rt::test]
async fn test_expect_continue_h1() { async fn test_expect_continue_h1() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.expect(fn_service(|req: Request| { .expect(fn_service(|req: Request| {
sleep(Duration::from_millis(20)).then(move |_| { sleep(Duration::from_millis(20)).then(move |_| {
@@ -129,6 +135,8 @@ async fn test_expect_continue_h1() {
let mut data = String::new(); let mut data = String::new();
let _ = stream.read_to_string(&mut data); 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")); 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] #[actix_rt::test]
@@ -136,7 +144,7 @@ async fn test_chunked_payload() {
let chunk_sizes = vec![32768, 32, 32768]; let chunk_sizes = vec![32768, 32, 32768];
let total_size: usize = chunk_sizes.iter().sum(); let total_size: usize = chunk_sizes.iter().sum();
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(fn_service(|mut request: Request| { .h1(fn_service(|mut request: Request| {
request request
@@ -183,15 +191,18 @@ async fn test_chunked_payload() {
Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(), Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(),
None => panic!("Failed to find size in HTTP Response: {}", data), None => panic!("Failed to find size in HTTP Response: {}", data),
}; };
size size
}; };
assert_eq!(returned_size, total_size); assert_eq!(returned_size, total_size);
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_slow_request() { async fn test_slow_request() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.client_timeout(100) .client_timeout(100)
.finish(|_| ok::<_, Infallible>(Response::ok())) .finish(|_| ok::<_, Infallible>(Response::ok()))
@@ -204,11 +215,13 @@ async fn test_slow_request() {
let mut data = String::new(); let mut data = String::new();
let _ = stream.read_to_string(&mut data); let _ = stream.read_to_string(&mut data);
assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); assert!(data.starts_with("HTTP/1.1 408 Request Timeout"));
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_http1_malformed_request() { async fn test_http1_malformed_request() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, Infallible>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
@@ -220,11 +233,13 @@ async fn test_http1_malformed_request() {
let mut data = String::new(); let mut data = String::new();
let _ = stream.read_to_string(&mut data); let _ = stream.read_to_string(&mut data);
assert!(data.starts_with("HTTP/1.1 400 Bad Request")); assert!(data.starts_with("HTTP/1.1 400 Bad Request"));
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_http1_keepalive() { async fn test_http1_keepalive() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, Infallible>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
@@ -241,11 +256,13 @@ async fn test_http1_keepalive() {
let mut data = vec![0; 1024]; let mut data = vec![0; 1024];
let _ = stream.read(&mut data); let _ = stream.read(&mut data);
assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_http1_keepalive_timeout() { async fn test_http1_keepalive_timeout() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.keep_alive(1) .keep_alive(1)
.h1(|_| ok::<_, Infallible>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
@@ -263,11 +280,13 @@ async fn test_http1_keepalive_timeout() {
let mut data = vec![0; 1024]; let mut data = vec![0; 1024];
let res = stream.read(&mut data).unwrap(); let res = stream.read(&mut data).unwrap();
assert_eq!(res, 0); assert_eq!(res, 0);
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_http1_keepalive_close() { async fn test_http1_keepalive_close() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, Infallible>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
@@ -284,11 +303,13 @@ async fn test_http1_keepalive_close() {
let mut data = vec![0; 1024]; let mut data = vec![0; 1024];
let res = stream.read(&mut data).unwrap(); let res = stream.read(&mut data).unwrap();
assert_eq!(res, 0); assert_eq!(res, 0);
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_http10_keepalive_default_close() { async fn test_http10_keepalive_default_close() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, Infallible>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
@@ -304,11 +325,13 @@ async fn test_http10_keepalive_default_close() {
let mut data = vec![0; 1024]; let mut data = vec![0; 1024];
let res = stream.read(&mut data).unwrap(); let res = stream.read(&mut data).unwrap();
assert_eq!(res, 0); assert_eq!(res, 0);
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_http10_keepalive() { async fn test_http10_keepalive() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, Infallible>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
.tcp() .tcp()
@@ -331,11 +354,13 @@ async fn test_http10_keepalive() {
let mut data = vec![0; 1024]; let mut data = vec![0; 1024];
let res = stream.read(&mut data).unwrap(); let res = stream.read(&mut data).unwrap();
assert_eq!(res, 0); assert_eq!(res, 0);
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_http1_keepalive_disabled() { async fn test_http1_keepalive_disabled() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.keep_alive(KeepAlive::Disabled) .keep_alive(KeepAlive::Disabled)
.h1(|_| ok::<_, Infallible>(Response::ok())) .h1(|_| ok::<_, Infallible>(Response::ok()))
@@ -352,6 +377,8 @@ async fn test_http1_keepalive_disabled() {
let mut data = vec![0; 1024]; let mut data = vec![0; 1024];
let res = stream.read(&mut data).unwrap(); let res = stream.read(&mut data).unwrap();
assert_eq!(res, 0); assert_eq!(res, 0);
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
@@ -361,7 +388,7 @@ async fn test_content_length() {
StatusCode, StatusCode,
}; };
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|req: Request| { .h1(|req: Request| {
let indx: usize = req.uri().path()[1..].parse().unwrap(); 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)); assert_eq!(response.headers().get(&header), Some(&value));
} }
} }
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
@@ -438,6 +467,8 @@ async fn test_h1_headers() {
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from(data2)); assert_eq!(bytes, Bytes::from(data2));
srv.stop().await;
} }
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
@@ -477,6 +508,8 @@ async fn test_h1_body() {
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
@@ -502,6 +535,8 @@ async fn test_h1_head_empty() {
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert!(bytes.is_empty()); assert!(bytes.is_empty());
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
@@ -527,11 +562,13 @@ async fn test_h1_head_binary() {
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert!(bytes.is_empty()); assert!(bytes.is_empty());
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_h1_head_binary2() { async fn test_h1_head_binary2() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.tcp() .tcp()
@@ -548,6 +585,8 @@ async fn test_h1_head_binary2() {
.unwrap(); .unwrap();
assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap());
} }
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
@@ -570,6 +609,8 @@ async fn test_h1_body_length() {
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
@@ -605,6 +646,8 @@ async fn test_h1_body_chunked_explicit() {
// decode // decode
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
@@ -634,6 +677,8 @@ async fn test_h1_body_chunked_implicit() {
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
@@ -661,6 +706,8 @@ async fn test_h1_response_http_error_handling() {
bytes, bytes,
Bytes::from_static(b"error processing HTTP: failed to parse header value") Bytes::from_static(b"error processing HTTP: failed to parse header value")
); );
srv.stop().await;
} }
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
@@ -688,11 +735,13 @@ async fn test_h1_service_error() {
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(b"error")); assert_eq!(bytes, Bytes::from_static(b"error"));
srv.stop().await;
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_h1_on_connect() { async fn test_h1_on_connect() {
let srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.on_connect_ext(|_, data| { .on_connect_ext(|_, data| {
data.insert(20isize); data.insert(20isize);
@@ -707,4 +756,6 @@ async fn test_h1_on_connect() {
let response = srv.get("/").send().await.unwrap(); let response = srv.get("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
srv.stop().await;
} }

View File

@@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx ## 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 ## 0.4.0-beta.6 - 2021-09-09
* Minimum supported Rust version (MSRV) is now 1.51. * Minimum supported Rust version (MSRV) is now 1.51.

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-multipart" name = "actix-multipart"
version = "0.4.0-beta.6" version = "0.4.0-beta.7"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Multipart form support for Actix Web" description = "Multipart form support for Actix Web"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
@@ -14,7 +14,7 @@ name = "actix_multipart"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [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" actix-utils = "3.0.0"
bytes = "1" bytes = "1"
@@ -29,6 +29,6 @@ twoway = "0.2"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.2" actix-rt = "2.2"
actix-http = "3.0.0-beta.10" actix-http = "3.0.0-beta.12"
tokio = { version = "1", features = ["sync"] } tokio = { version = "1", features = ["sync"] }
tokio-stream = "0.1" tokio-stream = "0.1"

View File

@@ -3,15 +3,15 @@
> Multipart form support for Actix Web. > Multipart form support for Actix Web.
[![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart)
[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.6)](https://docs.rs/actix-multipart/0.4.0-beta.6) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.7)](https://docs.rs/actix-multipart/0.4.0-beta.7)
[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.6/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.6) [![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.7/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.7)
[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart) - [API Documentation](https://docs.rs/actix-multipart)
- Minimum Supported Rust Version (MSRV): 1.51.0 - Minimum Supported Rust Version (MSRV): 1.52

View File

@@ -33,7 +33,6 @@ use crate::server::Multipart;
impl FromRequest for Multipart { impl FromRequest for Multipart {
type Error = Error; type Error = Error;
type Future = Ready<Result<Multipart, Error>>; type Future = Ready<Result<Multipart, Error>>;
type Config = ();
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {

View File

@@ -1,6 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* Minimum supported Rust version (MSRV) is now 1.52.
## 0.5.0-beta.2 - 2021-09-09 ## 0.5.0-beta.2 - 2021-09-09

View File

@@ -30,7 +30,7 @@ serde = "1"
[dev-dependencies] [dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] } criterion = { version = "0.3", features = ["html_reports"] }
firestorm = { version = "0.4", features = ["enable_system_time"] } firestorm = { version = "0.4", features = ["enable_system_time"] }
http = "0.2.3" http = "0.2.5"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
[[bench]] [[bench]]

View File

@@ -394,9 +394,7 @@ impl ResourceDef {
pub fn set_name(&mut self, name: impl Into<String>) { pub fn set_name(&mut self, name: impl Into<String>) {
let name = name.into(); let name = name.into();
if name.is_empty() { assert!(!name.is_empty(), "resource name should not be empty");
panic!("resource name should not be empty");
}
self.name = Some(name) self.name = Some(name)
} }
@@ -978,9 +976,7 @@ impl ResourceDef {
let (name, pattern) = match param.find(':') { let (name, pattern) = match param.find(':') {
Some(idx) => { Some(idx) => {
if tail { assert!(!tail, "custom regex is not supported for tail match");
panic!("custom regex is not supported for tail match");
}
let (name, pattern) = param.split_at(idx); let (name, pattern) = param.split_at(idx);
(name, &pattern[1..]) (name, &pattern[1..])
@@ -1087,12 +1083,12 @@ impl ResourceDef {
re.push_str(&escape(unprocessed)); re.push_str(&escape(unprocessed));
} }
if dyn_segment_count > MAX_DYNAMIC_SEGMENTS { assert!(
panic!( dyn_segment_count <= MAX_DYNAMIC_SEGMENTS,
"Only {} dynamic segments are allowed, provided: {}", "Only {} dynamic segments are allowed, provided: {}",
MAX_DYNAMIC_SEGMENTS, dyn_segment_count MAX_DYNAMIC_SEGMENTS,
); dyn_segment_count
} );
// Store the pattern in capture group #1 to have context info outside it // Store the pattern in capture group #1 to have context info outside it
let mut re = format!("({})", re); let mut re = format!("({})", re);

View File

@@ -6,8 +6,9 @@ use crate::{IntoPatterns, Resource, ResourceDef, ResourcePath};
pub struct ResourceId(pub u16); pub struct ResourceId(pub u16);
/// Information about current resource /// Information about current resource
#[derive(Clone, Debug)] #[derive(Debug, Clone)]
pub struct ResourceInfo { pub struct ResourceInfo {
#[allow(dead_code)]
resource: ResourceId, resource: ResourceId,
} }

View File

@@ -3,6 +3,17 @@
## Unreleased - 2021-xx-xx ## 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 ## 0.1.0-beta.4 - 2021-09-09
* Minimum supported Rust version (MSRV) is now 1.51. * Minimum supported Rust version (MSRV) is now 1.51.

View File

@@ -1,32 +1,41 @@
[package] [package]
name = "actix-test" name = "actix-test"
version = "0.1.0-beta.4" version = "0.1.0-beta.6"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>", "Rob Ede <robjtede@icloud.com>",
] ]
edition = "2018"
description = "Integration testing tools for Actix Web applications" 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" license = "MIT OR Apache-2.0"
edition = "2018"
[features] [features]
default = [] default = []
# rustls # rustls
rustls = ["tls-rustls", "actix-http/rustls"] rustls = ["tls-rustls", "actix-http/rustls", "awc/rustls"]
# openssl # openssl
openssl = ["tls-openssl", "actix-http/openssl"] openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
[dependencies] [dependencies]
actix-codec = "0.4.0" actix-codec = "0.4.0"
actix-http = "3.0.0-beta.10" actix-http = "3.0.0-beta.12"
actix-http-test = "3.0.0-beta.5" actix-http-test = "3.0.0-beta.6"
actix-service = "2.0.0" actix-service = "2.0.0"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["cookies"] } actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] }
actix-rt = "2.1" 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-core = { version = "0.3.7", default-features = false, features = ["std"] }
futures-util = { version = "0.3.7", default-features = false, features = [] } futures-util = { version = "0.3.7", default-features = false, features = [] }
@@ -35,4 +44,5 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
tls-rustls = { package = "rustls", version = "0.19.0", optional = true } tls-rustls = { package = "rustls", version = "0.20.0", optional = true }
tokio = { version = "1.2", features = ["sync"] }

View File

@@ -31,7 +31,7 @@ extern crate tls_openssl as openssl;
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
extern crate tls_rustls as 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}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
pub use actix_http::test::TestBuffer; pub use actix_http::test::TestBuffer;
@@ -41,8 +41,9 @@ use actix_http::{
}; };
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _}; use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
use actix_web::{ use actix_web::{
dev::{AppConfig, MessageBody, Server, Service}, dev::{AppConfig, MessageBody, Server, ServerHandle, Service},
rt, web, Error, rt::{self, System},
web, Error,
}; };
use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector}; use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
use futures_core::Stream; 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, call_service, default_service, init_service, load_stream, ok_service, read_body,
read_body_json, read_response, read_response_json, TestRequest, read_body_json, read_response, read_response_json, TestRequest,
}; };
use tokio::sync::mpsc;
/// Start default [`TestServer`]. /// Start default [`TestServer`].
/// ///
@@ -64,7 +66,7 @@ pub use actix_web::test::{
/// Ok(HttpResponse::Ok()) /// Ok(HttpResponse::Ok())
/// } /// }
/// ///
/// #[actix_rt::test] /// #[actix_web::test]
/// async fn test_example() { /// async fn test_example() {
/// let srv = actix_test::start(|| /// let srv = actix_test::start(||
/// App::new().service(my_handler) /// App::new().service(my_handler)
@@ -104,7 +106,7 @@ where
/// Ok(HttpResponse::Ok()) /// Ok(HttpResponse::Ok())
/// } /// }
/// ///
/// #[actix_rt::test] /// #[actix_web::test]
/// async fn test_example() { /// async fn test_example() {
/// let srv = actix_test::start_with(actix_test::config().h1(), || /// let srv = actix_test::start_with(actix_test::config().h1(), ||
/// App::new().service(my_handler) /// App::new().service(my_handler)
@@ -128,7 +130,11 @@ where
B: MessageBody + 'static, B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>, 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 { let tls = match cfg.stream {
StreamType::Tcp => false, StreamType::Tcp => false,
@@ -138,7 +144,7 @@ where
StreamType::Rustls(_) => true, StreamType::Rustls(_) => true,
}; };
// run server in separate thread // run server in separate orphaned thread
thread::spawn(move || { thread::spawn(move || {
let sys = rt::System::new(); let sys = rt::System::new();
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
@@ -146,7 +152,7 @@ where
let factory = factory.clone(); let factory = factory.clone();
let srv_cfg = cfg.clone(); let srv_cfg = cfg.clone();
let timeout = cfg.client_timeout; 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 { let srv = match srv_cfg.stream {
StreamType::Tcp => match srv_cfg.tp { 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();
let srv = srv.run(); started_tx
tx.send((rt::System::current(), srv, local_addr)).unwrap(); .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 client = {
let connector = { let connector = {
@@ -299,15 +313,15 @@ where
.set_alpn_protos(b"\x02h2\x08http/1.1") .set_alpn_protos(b"\x02h2\x08http/1.1")
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
Connector::new() Connector::new()
.conn_lifetime(time::Duration::from_secs(0)) .conn_lifetime(Duration::from_secs(0))
.timeout(time::Duration::from_millis(30000)) .timeout(Duration::from_millis(30000))
.ssl(builder.build()) .ssl(builder.build())
} }
#[cfg(not(feature = "openssl"))] #[cfg(not(feature = "openssl"))]
{ {
Connector::new() Connector::new()
.conn_lifetime(time::Duration::from_secs(0)) .conn_lifetime(Duration::from_secs(0))
.timeout(time::Duration::from_millis(30000)) .timeout(Duration::from_millis(30000))
} }
}; };
@@ -315,11 +329,12 @@ where
}; };
TestServer { TestServer {
addr, server,
thread_stop_rx,
client, client,
system, system,
addr,
tls, tls,
server,
} }
} }
@@ -405,11 +420,12 @@ impl TestServerConfig {
/// ///
/// See [`start`] for usage example. /// See [`start`] for usage example.
pub struct TestServer { pub struct TestServer {
addr: net::SocketAddr, server: ServerHandle,
thread_stop_rx: mpsc::Receiver<()>,
client: awc::Client, client: awc::Client,
system: rt::System, system: rt::System,
addr: net::SocketAddr,
tls: bool, tls: bool,
server: Server,
} }
impl TestServer { impl TestServer {
@@ -505,15 +521,30 @@ impl TestServer {
} }
/// Gracefully stop HTTP server. /// 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; self.server.stop(true).await;
// also signal system to stop
// though this is handled by `ServerBuilder::exit_system` too
self.system.stop(); 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 { impl Drop for TestServer {
fn drop(&mut self) { 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();
} }
} }

View File

@@ -1,6 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* Minimum supported Rust version (MSRV) is now 1.52.
## 4.0.0-beta.7 - 2021-09-09 ## 4.0.0-beta.7 - 2021-09-09

View File

@@ -16,8 +16,8 @@ path = "src/lib.rs"
[dependencies] [dependencies]
actix = { version = "0.12.0", default-features = false } actix = { version = "0.12.0", default-features = false }
actix-codec = "0.4.0" actix-codec = "0.4.0"
actix-http = "3.0.0-beta.10" actix-http = "3.0.0-beta.12"
actix-web = { version = "4.0.0-beta.9", default-features = false } actix-web = { version = "4.0.0-beta.11", default-features = false }
bytes = "1" bytes = "1"
bytestring = "1" bytestring = "1"
@@ -27,8 +27,8 @@ tokio = { version = "1", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
actix-rt = "2.2" actix-rt = "2.2"
actix-test = "0.1.0-beta.3" actix-test = "0.1.0-beta.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" env_logger = "0.8"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }

View File

@@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.7)](https://docs.rs/actix-web-actors/4.0.0-beta.7) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.7)](https://docs.rs/actix-web-actors/4.0.0-beta.7)
[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![License](https://img.shields.io/crates/l/actix-web-actors.svg) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7) [![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7)
@@ -14,4 +14,4 @@
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-web-actors) - [API Documentation](https://docs.rs/actix-web-actors)
- Minimum supported Rust version: 1.51 or later - Minimum Supported Rust Version (MSRV): 1.52

View File

@@ -3,6 +3,15 @@
## Unreleased - 2021-xx-xx ## 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 ## 0.5.0-beta.4 - 2021-09-09
* In routing macros, paths are now validated at compile time. [#2350] * In routing macros, paths are now validated at compile time. [#2350]
* Minimum supported Rust version (MSRV) is now 1.51. * Minimum supported Rust version (MSRV) is now 1.51.

View File

@@ -1,12 +1,13 @@
[package] [package]
name = "actix-web-codegen" name = "actix-web-codegen"
version = "0.5.0-beta.4" version = "0.5.0-beta.5"
description = "Routing and runtime macros for Actix Web" description = "Routing and runtime macros for Actix Web"
readme = "README.md"
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web-codegen" authors = [
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] "Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
@@ -21,9 +22,10 @@ actix-router = "0.5.0-beta.2"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.2" actix-rt = "2.2"
actix-test = "0.1.0-beta.3" actix-macros = "0.2.3"
actix-test = "0.1.0-beta.6"
actix-utils = "3.0.0" 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"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
trybuild = "1" trybuild = "1"

View File

@@ -3,18 +3,18 @@
> Routing and runtime macros for Actix Web. > Routing and runtime macros for Actix Web.
[![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen)
[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.4)](https://docs.rs/actix-web-codegen/0.5.0-beta.4) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.5)](https://docs.rs/actix-web-codegen/0.5.0-beta.5)
[![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![License](https://img.shields.io/crates/l/actix-web-codegen.svg) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.4/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.4) [![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.5/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.5)
[![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-web-codegen) - [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 ## Compile Testing

View File

@@ -59,6 +59,7 @@
#![recursion_limit = "512"] #![recursion_limit = "512"]
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote;
mod route; mod route;
@@ -157,24 +158,41 @@ method_macro! {
} }
/// Marks async main function as the actix system entry-point. /// 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 /// # Examples
/// ``` /// ```
/// #[actix_web_codegen::main] /// #[actix_web::main]
/// async fn main() { /// async fn main() {
/// async { println!("Hello world"); }.await /// async { println!("Hello world"); }.await
/// } /// }
/// ``` /// ```
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
use quote::quote; let mut output: TokenStream = (quote! {
let input = syn::parse_macro_input!(item as syn::ItemFn); #[::actix_web::rt::main(system = "::actix_web::rt::System")]
(quote! {
#[actix_web::rt::main(system = "::actix_web::rt::System")]
#input
}) })
.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
} }

View File

@@ -220,7 +220,7 @@ fn guess_resource_type(typ: &syn::Type) -> ResourceType {
impl Route { impl Route {
pub fn new( pub fn new(
args: AttributeArgs, args: AttributeArgs,
input: TokenStream, ast: syn::ItemFn,
method: Option<MethodType>, method: Option<MethodType>,
) -> syn::Result<Self> { ) -> syn::Result<Self> {
if args.is_empty() { if args.is_empty() {
@@ -234,14 +234,11 @@ impl Route {
), ),
)); ));
} }
let ast: syn::ItemFn = syn::parse(input)?;
let name = ast.sig.ident.clone(); let name = ast.sig.ident.clone();
// Try and pull out the doc comments so that we can reapply them to the // Try and pull out the doc comments so that we can reapply them to the generated struct.
// generated struct. // Note that multi line doc comments are converted to multiple doc attributes.
//
// Note that multi line doc comments are converted to multiple doc
// attributes.
let doc_attributes = ast let doc_attributes = ast
.attrs .attrs
.iter() .iter()
@@ -349,8 +346,28 @@ pub(crate) fn with_method(
input: TokenStream, input: TokenStream,
) -> TokenStream { ) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); 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(), 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
}

View File

@@ -256,7 +256,7 @@ async fn test_auto_async() {
assert!(response.status().is_success()); assert!(response.status().is_success());
} }
#[actix_rt::test] #[actix_web::test]
async fn test_wrap() { async fn test_wrap() {
let srv = actix_test::start(|| App::new().service(get_wrap)); let srv = actix_test::start(|| App::new().service(get_wrap));

View File

@@ -1,4 +1,4 @@
#[rustversion::stable(1.51)] // MSRV #[rustversion::stable(1.52)] // MSRV
#[test] #[test]
fn compile_macros() { fn compile_macros() {
let t = trybuild::TestCases::new(); let t = trybuild::TestCases::new();
@@ -13,4 +13,6 @@ fn compile_macros() {
t.compile_fail("tests/trybuild/route-malformed-path-fail.rs"); t.compile_fail("tests/trybuild/route-malformed-path-fail.rs");
t.pass("tests/trybuild/docstring-ok.rs"); t.pass("tests/trybuild/docstring-ok.rs");
t.pass("tests/trybuild/test-runtime.rs");
} }

View File

@@ -4,8 +4,8 @@ error: HTTP method defined more than once: `GET`
3 | #[route("/", method="GET", method="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 --> $DIR/route-duplicate-method-fail.rs:12:55
| |
12 | let srv = actix_test::start(|| App::new().service(index)); 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}`

View File

@@ -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) = 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 --> $DIR/route-missing-method-fail.rs:12:55
| |
12 | let srv = actix_test::start(|| App::new().service(index)); 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}`

View File

@@ -4,8 +4,8 @@ error: Unexpected HTTP method: `UNEXPECTED`
3 | #[route("/", 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 --> $DIR/route-unexpected-method-fail.rs:12:55
| |
12 | let srv = actix_test::start(|| App::new().service(index)); 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}`

View File

@@ -0,0 +1,6 @@
#[actix_web::test]
async fn my_test() {
assert!(async { 1 }.await, 1);
}
fn main() {}

View File

@@ -3,6 +3,15 @@
## Unreleased - 2021-xx-xx ## 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 ## 3.0.0-beta.8 - 2021-09-09
### Changed ### Changed
* Send headers within the redirect requests. [#2310] * Send headers within the redirect requests. [#2310]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "awc" name = "awc"
version = "3.0.0-beta.8" version = "3.0.0-beta.10"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>", "fakeshadow <24548779@qq.com>",
@@ -14,7 +14,7 @@ categories = [
"web-programming::websocket", "web-programming::websocket",
] ]
homepage = "https://actix.rs" 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" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
@@ -30,10 +30,10 @@ features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-z
default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
# openssl # openssl
openssl = ["tls-openssl", "actix-http/openssl"] openssl = ["tls-openssl", "actix-tls/openssl"]
# rustls # rustls
rustls = ["tls-rustls", "actix-http/rustls"] rustls = ["tls-rustls", "actix-tls/rustls"]
# Brotli algorithm content-encoding support # Brotli algorithm content-encoding support
compress-brotli = ["actix-http/compress-brotli", "__compress"] compress-brotli = ["actix-http/compress-brotli", "__compress"]
@@ -46,7 +46,7 @@ compress-zstd = ["actix-http/compress-zstd", "__compress"]
cookies = ["cookie"] cookies = ["cookie"]
# trust-dns as dns resolver # 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. # Internal (PRIVATE!) features used to aid testing and cheking feature status.
# Don't rely on these whatsoever. They may disappear at anytime. # Don't rely on these whatsoever. They may disappear at anytime.
@@ -55,15 +55,20 @@ __compress = []
[dependencies] [dependencies]
actix-codec = "0.4.0" actix-codec = "0.4.0"
actix-service = "2.0.0" actix-service = "2.0.0"
actix-http = "3.0.0-beta.10" actix-http = "3.0.0-beta.12"
actix-rt = { version = "2.1", default-features = false } 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" base64 = "0.13"
bytes = "1" bytes = "1"
cfg-if = "1" cfg-if = "1"
cookie = { version = "0.15", features = ["percent-encode"], optional = true }
derive_more = "0.99.5" derive_more = "0.99.5"
futures-core = { version = "0.3.7", default-features = false } 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" itoa = "0.4"
log =" 0.4" log =" 0.4"
mime = "0.3" mime = "0.3"
@@ -73,24 +78,30 @@ rand = "0.8"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1", features = ["sync"] }
tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
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] [dev-dependencies]
actix-web = { version = "4.0.0-beta.9", features = ["openssl"] } actix-web = { version = "4.0.0-beta.11", features = ["openssl"] }
actix-http = { version = "3.0.0-beta.10", features = ["openssl"] } actix-http = { version = "3.0.0-beta.12", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.6", features = ["openssl"] }
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-server = "2.0.0-beta.3" actix-server = "2.0.0-beta.9"
actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0-beta.7", features = ["openssl", "rustls"] }
actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.6", features = ["openssl", "rustls"] }
brotli2 = "0.3.2" brotli2 = "0.3.2"
env_logger = "0.8" env_logger = "0.8"
flate2 = "1.0.13" flate2 = "1.0.13"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
rcgen = "0.8" rcgen = "0.8"
webpki = "0.21" rustls-pemfile = "0.2"
[[example]] [[example]]
name = "client" name = "client"

View File

@@ -3,16 +3,16 @@
> Async HTTP and WebSocket client library. > Async HTTP and WebSocket client library.
[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc)
[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.8)](https://docs.rs/awc/3.0.0-beta.8) [![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.10)](https://docs.rs/awc/3.0.0-beta.10)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc)
[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.8/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.8) [![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.10/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.10)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/awc) - [API Documentation](https://docs.rs/awc)
- [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https) - [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 ## Example

View File

@@ -4,13 +4,11 @@ use std::net::IpAddr;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use actix_http::{ use actix_http::http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri};
client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection},
http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri},
};
use actix_rt::net::{ActixStream, TcpStream}; use actix_rt::net::{ActixStream, TcpStream};
use actix_service::{boxed, Service}; use actix_service::{boxed, Service};
use crate::client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection};
use crate::connect::DefaultConnector; use crate::connect::DefaultConnector;
use crate::error::SendRequestError; use crate::error::SendRequestError;
use crate::middleware::{NestTransform, Redirect, Transform}; use crate::middleware::{NestTransform, Redirect, Transform};

View File

@@ -1,5 +1,4 @@
use std::net::IpAddr; use std::{net::IpAddr, time::Duration};
use std::time::Duration;
const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2MB const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2MB
const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1MB const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1MB

View File

@@ -12,10 +12,9 @@ use bytes::Bytes;
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use h2::client::SendRequest; use h2::client::SendRequest;
use crate::h1::ClientCodec; use actix_http::{
use crate::message::{RequestHeadType, ResponseHead}; body::MessageBody, h1::ClientCodec, Error, Payload, RequestHeadType, ResponseHead,
use crate::payload::Payload; };
use crate::{body::MessageBody, Error};
use super::error::SendRequestError; use super::error::SendRequestError;
use super::pool::Acquired; use super::pool::Acquired;
@@ -174,6 +173,7 @@ impl H2ConnectionInner {
/// Cancel spawned connection task on drop. /// Cancel spawned connection task on drop.
impl Drop for H2ConnectionInner { impl Drop for H2ConnectionInner {
fn drop(&mut self) { fn drop(&mut self) {
// TODO: this can end up sending extraneous requests; see if there is a better way to handle
if self if self
.sender .sender
.send_request(http::Request::new(()), true) .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)] #[allow(dead_code)]
/// Unified connection type cover Http1 Plain/Tls and Http2 protocols
pub enum Connection<A, B = Box<dyn ConnectionIo>> pub enum Connection<A, B = Box<dyn ConnectionIo>>
where where
A: ConnectionIo, A: ConnectionIo,
@@ -219,11 +219,7 @@ impl<Io: ConnectionIo> ConnectionType<Io> {
} }
} }
pub(super) fn from_h1( pub(super) fn from_h1(io: Io, created: time::Instant, acquired: Acquired<Io>) -> Self {
io: Io,
created: time::Instant,
acquired: Acquired<Io>,
) -> Self {
Self::H1(H1Connection { Self::H1(H1Connection {
io: Some(io), io: Some(io),
created, created,
@@ -271,9 +267,7 @@ where
Connection::Tls(ConnectionType::H2(conn)) => { Connection::Tls(ConnectionType::H2(conn)) => {
h2proto::send_request(conn, head.into(), body).await h2proto::send_request(conn, head.into(), body).await
} }
_ => unreachable!( _ => unreachable!("Plain Tcp connection can be used only in Http1 protocol"),
"Plain Tcp connection can be used only in Http1 protocol"
),
} }
}) })
} }
@@ -301,9 +295,7 @@ where
Err(SendRequestError::TunnelNotSupported) Err(SendRequestError::TunnelNotSupported)
} }
Connection::Tcp(ConnectionType::H2(_)) => { Connection::Tcp(ConnectionType::H2(_)) => {
unreachable!( unreachable!("Plain Tcp connection can be used only in Http1 protocol")
"Plain Tcp connection can be used only in Http1 protocol"
)
} }
} }
}) })
@@ -321,12 +313,8 @@ where
buf: &mut ReadBuf<'_>, buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> { ) -> Poll<io::Result<()>> {
match self.get_mut() { match self.get_mut() {
Connection::Tcp(ConnectionType::H1(conn)) => { Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_read(cx, buf),
Pin::new(conn).poll_read(cx, buf) Connection::Tls(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"), _ => unreachable!("H2Connection can not impl AsyncRead trait"),
} }
} }
@@ -345,12 +333,8 @@ where
buf: &[u8], buf: &[u8],
) -> Poll<io::Result<usize>> { ) -> Poll<io::Result<usize>> {
match self.get_mut() { match self.get_mut() {
Connection::Tcp(ConnectionType::H1(conn)) => { Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_write(cx, buf),
Pin::new(conn).poll_write(cx, buf) Connection::Tls(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), _ => unreachable!(H2_UNREACHABLE_WRITE),
} }
} }
@@ -363,17 +347,10 @@ where
} }
} }
fn poll_shutdown( fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>> {
match self.get_mut() { match self.get_mut() {
Connection::Tcp(ConnectionType::H1(conn)) => { Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_shutdown(cx),
Pin::new(conn).poll_shutdown(cx) Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_shutdown(cx),
}
Connection::Tls(ConnectionType::H1(conn)) => {
Pin::new(conn).poll_shutdown(cx)
}
_ => unreachable!(H2_UNREACHABLE_WRITE), _ => unreachable!(H2_UNREACHABLE_WRITE),
} }
} }

View File

@@ -8,6 +8,7 @@ use std::{
time::Duration, time::Duration,
}; };
use actix_http::Protocol;
use actix_rt::{ use actix_rt::{
net::{ActixStream, TcpStream}, net::{ActixStream, TcpStream},
time::{sleep, Sleep}, time::{sleep, Sleep},
@@ -19,27 +20,21 @@ use actix_tls::connect::{
}; };
use futures_core::{future::LocalBoxFuture, ready}; use futures_core::{future::LocalBoxFuture, ready};
use http::Uri; use http::Uri;
use pin_project::pin_project; use pin_project_lite::pin_project;
use super::config::ConnectorConfig; use super::config::ConnectorConfig;
use super::connection::{Connection, ConnectionIo}; use super::connection::{Connection, ConnectionIo};
use super::error::ConnectError; use super::error::ConnectError;
use super::pool::ConnectionPool; use super::pool::ConnectionPool;
use super::Connect; 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 { enum SslConnector {
#[allow(dead_code)] #[allow(dead_code)]
None, None,
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
Openssl(OpensslConnector), Openssl(actix_tls::connect::ssl::openssl::SslConnector),
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
Rustls(std::sync::Arc<ClientConfig>), Rustls(std::sync::Arc<actix_tls::connect::ssl::rustls::ClientConfig>),
} }
/// Manages HTTP client network connectivity. /// Manages HTTP client network connectivity.
@@ -78,10 +73,33 @@ impl Connector<()> {
} }
} }
// Build Ssl connector with openssl, based on supplied alpn protocols /// Provides an empty TLS connector when no TLS feature is enabled.
#[cfg(feature = "openssl")] #[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 { 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}; use bytes::{BufMut, BytesMut};
let mut alpn = BytesMut::with_capacity(20); let mut alpn = BytesMut::with_capacity(20);
@@ -91,28 +109,12 @@ impl Connector<()> {
} }
let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap(); let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap();
let _ = ssl if let Err(err) = ssl.set_alpn_protos(&alpn) {
.set_alpn_protos(&alpn) log::error!("Can not set ALPN protocol: {:?}", err);
.map_err(|e| error!("Can not set alpn protocol: {:?}", e)); }
SslConnector::Openssl(ssl.build()) 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> { 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 // This remap is to hide ActixStream's trait methods. They are not meant to be called
// from user code. // from user code.
Io: ActixStream + fmt::Debug + 'static, Io: ActixStream + fmt::Debug + 'static,
S: Service< S: Service<TcpConnect<Uri>, Response = TcpConnection<Uri, Io>, Error = TcpConnectError>
TcpConnect<Uri>, + Clone
Response = TcpConnection<Uri, Io>,
Error = TcpConnectError,
> + Clone
+ 'static, + 'static,
{ {
/// Tcp connection timeout, i.e. max time to connect to remote host including dns name /// Tcp connection timeout, i.e. max time to connect to remote host including dns name
@@ -167,14 +166,17 @@ where
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
/// Use custom `SslConnector` instance. /// 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.ssl = SslConnector::Openssl(connector);
self self
} }
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
/// Use custom `SslConnector` instance. /// 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.ssl = SslConnector::Rustls(connector);
self self
} }
@@ -313,9 +315,7 @@ where
SslConnector::Rustls(tls) => { SslConnector::Rustls(tls) => {
const H2: &[u8] = b"h2"; const H2: &[u8] = b"h2";
use actix_tls::connect::ssl::rustls::{ use actix_tls::connect::ssl::rustls::{RustlsConnector, TlsStream};
RustlsConnector, Session, TlsStream,
};
impl<Io: ConnectionIo> IntoConnectionIo for TcpConnection<Uri, TlsStream<Io>> { impl<Io: ConnectionIo> IntoConnectionIo for TcpConnection<Uri, TlsStream<Io>> {
fn into_connection_io(self) -> (Box<dyn ConnectionIo>, Protocol) { fn into_connection_io(self) -> (Box<dyn ConnectionIo>, Protocol) {
@@ -323,7 +323,7 @@ where
let h2 = sock let h2 = sock
.get_ref() .get_ref()
.1 .1
.get_alpn_protocol() .alpn_protocol()
.map_or(false, |protos| protos.windows(2).any(|w| w == H2)); .map_or(false, |protos| protos.windows(2).any(|w| w == H2));
if h2 { if h2 {
(Box::new(sock), Protocol::Http2) (Box::new(sock), Protocol::Http2)
@@ -350,8 +350,8 @@ where
let tcp_pool = ConnectionPool::new(tcp_service, tcp_config); let tcp_pool = ConnectionPool::new(tcp_service, tcp_config);
let tls_config = self.config; let tls_config = self.config;
let tls_pool = tls_service let tls_pool =
.map(move |tls_service| ConnectionPool::new(tls_service, tls_config)); tls_service.map(move |tls_service| ConnectionPool::new(tls_service, tls_config));
ConnectorServicePriv { tcp_pool, tls_pool } ConnectorServicePriv { tcp_pool, tls_pool }
} }
@@ -382,10 +382,12 @@ where
} }
} }
#[pin_project] pin_project! {
pub struct TcpConnectorFuture<Fut> { #[project = TcpConnectorFutureProj]
#[pin] pub struct TcpConnectorFuture<Fut> {
fut: Fut, #[pin]
fut: Fut,
}
} }
impl<Fut, Io> Future for TcpConnectorFuture<Fut> impl<Fut, Io> Future for TcpConnectorFuture<Fut>
@@ -444,23 +446,25 @@ where
} }
} }
#[pin_project(project = TlsConnectorProj)] pin_project! {
#[allow(clippy::large_enum_variant)] #[project = TlsConnectorProj]
enum TlsConnectorFuture<S, Fut1, Fut2> { #[allow(clippy::large_enum_variant)]
TcpConnect { enum TlsConnectorFuture<S, Fut1, Fut2> {
#[pin] TcpConnect {
fut: Fut1, #[pin]
tls_service: Option<S>, fut: Fut1,
timeout: Duration, tls_service: Option<S>,
}, timeout: Duration,
TlsConnect { },
#[pin] TlsConnect {
fut: Fut2, #[pin]
#[pin] fut: Fut2,
timeout: Sleep, #[pin]
}, timeout: Sleep,
} },
}
}
/// helper trait for generic over different TlsStream types between tls crates. /// helper trait for generic over different TlsStream types between tls crates.
trait IntoConnectionIo { trait IntoConnectionIo {
fn into_connection_io(self) -> (Box<dyn ConnectionIo>, Protocol); 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> impl<S, Io, Fut1, Fut2, Res> Future for TlsConnectorFuture<S, Fut1, Fut2>
where where
S: Service< S: Service<TcpConnection<Uri, Io>, Response = Res, Error = std::io::Error, Future = Fut2>,
TcpConnection<Uri, Io>,
Response = Res,
Error = std::io::Error,
Future = Fut2,
>,
S::Response: IntoConnectionIo, S::Response: IntoConnectionIo,
Fut1: Future<Output = Result<TcpConnection<Uri, Io>, ConnectError>>, Fut1: Future<Output = Result<TcpConnection<Uri, Io>, ConnectError>>,
Fut2: Future<Output = Result<S::Response, S::Error>>, Fut2: Future<Output = Result<S::Response, S::Error>>,
@@ -515,11 +514,7 @@ pub struct TcpConnectorInnerService<S: Clone> {
} }
impl<S: Clone> TcpConnectorInnerService<S> { impl<S: Clone> TcpConnectorInnerService<S> {
fn new( fn new(service: S, timeout: Duration, local_address: Option<std::net::IpAddr>) -> Self {
service: S,
timeout: Duration,
local_address: Option<std::net::IpAddr>,
) -> Self {
Self { Self {
service, service,
timeout, timeout,
@@ -530,11 +525,8 @@ impl<S: Clone> TcpConnectorInnerService<S> {
impl<S, Io> Service<Connect> for TcpConnectorInnerService<S> impl<S, Io> Service<Connect> for TcpConnectorInnerService<S>
where where
S: Service< S: Service<TcpConnect<Uri>, Response = TcpConnection<Uri, Io>, Error = TcpConnectError>
TcpConnect<Uri>, + Clone
Response = TcpConnection<Uri, Io>,
Error = TcpConnectError,
> + Clone
+ 'static, + 'static,
{ {
type Response = S::Response; type Response = S::Response;
@@ -557,12 +549,14 @@ where
} }
} }
#[pin_project] pin_project! {
pub struct TcpConnectorInnerFuture<Fut> { #[project = TcpConnectorInnerFutureProj]
#[pin] pub struct TcpConnectorInnerFuture<Fut> {
fut: Fut, #[pin]
#[pin] fut: Fut,
timeout: Sleep, #[pin]
timeout: Sleep,
}
} }
impl<Fut, Io> Future for TcpConnectorInnerFuture<Fut> 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> impl<S1, S2, Io1, Io2> Service<Connect> for ConnectorServicePriv<S1, S2, Io1, Io2>
where where
S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + Clone + 'static,
+ Clone S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + Clone + 'static,
+ 'static,
S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError>
+ Clone
+ 'static,
Io1: ConnectionIo, Io1: ConnectionIo,
Io2: ConnectionIo, Io2: ConnectionIo,
{ {
@@ -636,38 +626,46 @@ where
match req.uri.scheme_str() { match req.uri.scheme_str() {
Some("https") | Some("wss") => match self.tls_pool { Some("https") | Some("wss") => match self.tls_pool {
None => ConnectorServiceFuture::SslIsNotSupported, 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)] pin_project! {
pub enum ConnectorServiceFuture<S1, S2, Io1, Io2> #[project = ConnectorServiceFutureProj]
where pub enum ConnectorServiceFuture<S1, S2, Io1, Io2>
S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> where
+ Clone S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError>,
+ 'static, S1: Clone,
S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> S1: 'static,
+ Clone S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError>,
+ 'static, S2: Clone,
Io1: ConnectionIo, S2: 'static,
Io2: ConnectionIo, Io1: ConnectionIo,
{ Io2: ConnectionIo,
Tcp(#[pin] <ConnectionPool<S1, Io1> as Service<Connect>>::Future), {
Tls(#[pin] <ConnectionPool<S2, Io2> as Service<Connect>>::Future), Tcp {
SslIsNotSupported, #[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> impl<S1, S2, Io1, Io2> Future for ConnectorServiceFuture<S1, S2, Io1, Io2>
where where
S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + Clone + 'static,
+ Clone S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + Clone + 'static,
+ 'static,
S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError>
+ Clone
+ 'static,
Io1: ConnectionIo, Io1: ConnectionIo,
Io2: ConnectionIo, Io2: ConnectionIo,
{ {
@@ -675,9 +673,9 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project() { match self.project() {
ConnectorServiceProj::Tcp(fut) => fut.poll(cx).map_ok(Connection::Tcp), ConnectorServiceFutureProj::Tcp { fut } => fut.poll(cx).map_ok(Connection::Tcp),
ConnectorServiceProj::Tls(fut) => fut.poll(cx).map_ok(Connection::Tls), ConnectorServiceFutureProj::Tls { fut } => fut.poll(cx).map_ok(Connection::Tls),
ConnectorServiceProj::SslIsNotSupported => { ConnectorServiceFutureProj::SslIsNotSupported => {
Poll::Ready(Err(ConnectError::SslIsNotSupported)) Poll::Ready(Err(ConnectError::SslIsNotSupported))
} }
} }

View File

@@ -2,12 +2,13 @@ use std::{error::Error as StdError, fmt, io};
use derive_more::{Display, From}; use derive_more::{Display, From};
use actix_http::{
error::{Error, ParseError},
http::Error as HttpError,
};
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
use actix_tls::accept::openssl::SslError; 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 /// A set of errors that can occur while connecting to an HTTP host
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
#[non_exhaustive] #[non_exhaustive]

View File

@@ -5,24 +5,25 @@ use std::{
}; };
use actix_codec::Framed; 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 actix_utils::future::poll_fn;
use bytes::buf::BufMut; use bytes::buf::BufMut;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures_core::{ready, Stream}; use futures_core::{ready, Stream};
use futures_util::SinkExt as _; use futures_util::SinkExt as _;
use pin_project_lite::pin_project;
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 super::connection::{ConnectionIo, H1Connection}; use super::connection::{ConnectionIo, H1Connection};
use super::error::{ConnectError, SendRequestError}; use super::error::{ConnectError, SendRequestError};
use crate::body::{BodySize, MessageBody};
pub(crate) async fn send_request<Io, B>( pub(crate) async fn send_request<Io, B>(
io: H1Connection<Io>, io: H1Connection<Io>,
@@ -194,10 +195,11 @@ where
Ok(()) Ok(())
} }
#[pin_project::pin_project] pin_project! {
pub(crate) struct PlStream<Io: ConnectionIo> { pub(crate) struct PlStream<Io: ConnectionIo> {
#[pin] #[pin]
framed: Framed<H1Connection<Io>, h1::ClientPayloadCodec>, framed: Framed<H1Connection<Io>, h1::ClientPayloadCodec>,
}
} }
impl<Io: ConnectionIo> PlStream<Io> { impl<Io: ConnectionIo> PlStream<Io> {
@@ -211,10 +213,7 @@ impl<Io: ConnectionIo> PlStream<Io> {
impl<Io: ConnectionIo> Stream for PlStream<Io> { impl<Io: ConnectionIo> Stream for PlStream<Io> {
type Item = Result<Bytes, PayloadError>; type Item = Result<Bytes, PayloadError>;
fn poll_next( fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
let mut this = self.project(); let mut this = self.project();
match ready!(this.framed.as_mut().next_item(cx)?) { match ready!(this.framed.as_mut().next_item(cx)?) {

View File

@@ -8,13 +8,12 @@ use h2::{
}; };
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING};
use http::{request::Request, Method, Version}; use http::{request::Request, Method, Version};
use log::trace;
use crate::{ use actix_http::{
body::{BodySize, MessageBody}, body::{BodySize, MessageBody},
header::HeaderMap, header::HeaderMap,
message::{RequestHeadType, ResponseHead}, Error, Payload, RequestHeadType, ResponseHead,
payload::Payload,
Error,
}; };
use super::{ use super::{
@@ -131,10 +130,7 @@ where
Ok((head, payload)) Ok((head, payload))
} }
async fn send_body<B>( async fn send_body<B>(body: B, mut send: SendStream<Bytes>) -> Result<(), SendRequestError>
body: B,
mut send: SendStream<Bytes>,
) -> Result<(), SendRequestError>
where where
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<Error>,
@@ -184,8 +180,7 @@ where
pub(crate) fn handshake<Io: ConnectionIo>( pub(crate) fn handshake<Io: ConnectionIo>(
io: Io, io: Io,
config: &ConnectorConfig, 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(); let mut builder = Builder::new();
builder builder
.initial_window_size(config.stream_window_size) .initial_window_size(config.stream_window_size)

View File

@@ -17,7 +17,6 @@ pub use actix_tls::connect::{
pub use self::connection::{Connection, ConnectionIo}; pub use self::connection::{Connection, ConnectionIo};
pub use self::connector::{Connector, ConnectorService}; pub use self::connector::{Connector, ConnectorService};
pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
pub use crate::Protocol;
#[derive(Clone)] #[derive(Clone)]
pub struct Connect { pub struct Connect {

View File

@@ -14,22 +14,21 @@ use std::{
}; };
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
use actix_http::Protocol;
use actix_rt::time::{sleep, Sleep}; use actix_rt::time::{sleep, Sleep};
use actix_service::Service; use actix_service::Service;
use ahash::AHashMap; use ahash::AHashMap;
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use futures_util::FutureExt;
use http::uri::Authority; use http::uri::Authority;
use pin_project::pin_project; use pin_project_lite::pin_project;
use tokio::sync::{OwnedSemaphorePermit, Semaphore}; use tokio::sync::{OwnedSemaphorePermit, Semaphore};
use super::config::ConnectorConfig; use super::config::ConnectorConfig;
use super::connection::{ use super::connection::{ConnectionInnerType, ConnectionIo, ConnectionType, H2ConnectionInner};
ConnectionInnerType, ConnectionIo, ConnectionType, H2ConnectionInner,
};
use super::error::ConnectError; use super::error::ConnectError;
use super::h2proto::handshake; use super::h2proto::handshake;
use super::Connect; use super::Connect;
use super::Protocol;
#[derive(Hash, Eq, PartialEq, Clone, Debug)] #[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct Key { pub struct Key {
@@ -152,9 +151,7 @@ where
impl<S, Io> Service<Connect> for ConnectionPool<S, Io> impl<S, Io> Service<Connect> for ConnectionPool<S, Io>
where where
S: Service<Connect, Response = (Io, Protocol), Error = ConnectError> S: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + Clone + 'static,
+ Clone
+ 'static,
Io: ConnectionIo, Io: ConnectionIo,
{ {
type Response = ConnectionType<Io>; type Response = ConnectionType<Io>;
@@ -195,8 +192,8 @@ where
let config = &inner.config; let config = &inner.config;
let idle_dur = now - c.used; let idle_dur = now - c.used;
let age = now - c.created; let age = now - c.created;
let conn_ineligible = idle_dur > config.conn_keep_alive let conn_ineligible =
|| age > config.conn_lifetime; idle_dur > config.conn_keep_alive || age > config.conn_lifetime;
if conn_ineligible { if conn_ineligible {
// drop connections that are too old // drop connections that are too old
@@ -205,7 +202,7 @@ where
// check if the connection is still usable // check if the connection is still usable
if let ConnectionInnerType::H1(ref mut io) = c.conn { if let ConnectionInnerType::H1(ref mut io) = c.conn {
let check = ConnectionCheckFuture { io }; let check = ConnectionCheckFuture { io };
match check.await { match check.now_or_never().expect("ConnectionCheckFuture must never yield with Poll::Pending.") {
ConnectionState::Tainted => { ConnectionState::Tainted => {
inner.close(c.conn); inner.close(c.conn);
continue; continue;
@@ -231,9 +228,7 @@ where
// match the connection and spawn new one if did not get anything. // match the connection and spawn new one if did not get anything.
match conn { match conn {
Some(conn) => { Some(conn) => Ok(ConnectionType::from_pool(conn.conn, conn.created, acquired)),
Ok(ConnectionType::from_pool(conn.conn, conn.created, acquired))
}
None => { None => {
let (io, proto) = connector.call(req).await?; let (io, proto) = connector.call(req).await?;
@@ -284,9 +279,7 @@ where
let mut read_buf = ReadBuf::new(&mut buf); let mut read_buf = ReadBuf::new(&mut buf);
let state = match Pin::new(&mut this.io).poll_read(cx, &mut read_buf) { let state = match Pin::new(&mut this.io).poll_read(cx, &mut read_buf) {
Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => ConnectionState::Tainted,
ConnectionState::Tainted
}
Poll::Pending => ConnectionState::Live, Poll::Pending => ConnectionState::Live,
_ => ConnectionState::Skip, _ => ConnectionState::Skip,
@@ -302,11 +295,13 @@ struct PooledConnection<Io> {
created: Instant, created: Instant,
} }
#[pin_project] pin_project! {
struct CloseConnection<Io> { #[project = CloseConnectionProj]
io: Io, struct CloseConnection<Io> {
#[pin] io: Io,
timeout: Sleep, #[pin]
timeout: Sleep,
}
} }
impl<Io> CloseConnection<Io> impl<Io> CloseConnection<Io>
@@ -413,17 +408,11 @@ mod test {
unimplemented!() unimplemented!()
} }
fn poll_flush( fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
self: Pin<&mut Self>,
_: &mut Context<'_>,
) -> Poll<io::Result<()>> {
unimplemented!() unimplemented!()
} }
fn poll_shutdown( fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
self: Pin<&mut Self>,
_: &mut Context<'_>,
) -> Poll<io::Result<()>> {
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} }
} }

View File

@@ -8,16 +8,14 @@ use std::{
use actix_codec::Framed; use actix_codec::Framed;
use actix_http::{ use actix_http::{
body::Body, body::Body, h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead,
client::{
Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError,
},
h1::ClientCodec,
Payload, RequestHead, RequestHeadType, ResponseHead,
}; };
use actix_service::Service; use actix_service::Service;
use futures_core::{future::LocalBoxFuture, ready}; use futures_core::{future::LocalBoxFuture, ready};
use crate::client::{
Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError,
};
use crate::response::ClientResponse; use crate::response::ClientResponse;
pub type BoxConnectorService = Rc< pub type BoxConnectorService = Rc<

View File

@@ -1,15 +1,15 @@
//! HTTP client errors //! HTTP client errors
pub use actix_http::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; pub use actix_http::{
pub use actix_http::error::PayloadError; error::PayloadError,
pub use actix_http::http::Error as HttpError; http::{header::HeaderValue, Error as HttpError, StatusCode},
pub use actix_http::ws::HandshakeError as WsHandshakeError; ws::{HandshakeError as WsHandshakeError, ProtocolError as WsProtocolError},
pub use actix_http::ws::ProtocolError as WsProtocolError; };
use derive_more::{Display, From};
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use actix_http::http::{header::HeaderValue, StatusCode}; pub use crate::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
use derive_more::{Display, From};
/// Websocket client error /// Websocket client error
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]

View File

@@ -104,22 +104,8 @@
#![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![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 builder;
mod client;
mod connect; mod connect;
pub mod error; pub mod error;
mod frozen; mod frozen;
@@ -130,13 +116,29 @@ mod sender;
pub mod test; pub mod test;
pub mod ws; pub mod ws;
pub use actix_http::http;
#[cfg(feature = "cookies")]
pub use cookie;
pub use self::builder::ClientBuilder; pub use self::builder::ClientBuilder;
pub use self::client::Connector;
pub use self::connect::{BoxConnectorService, BoxedSocket, ConnectRequest, ConnectResponse}; pub use self::connect::{BoxConnectorService, BoxedSocket, ConnectRequest, ConnectResponse};
pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder}; pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder};
pub use self::request::ClientRequest; pub use self::request::ClientRequest;
pub use self::response::{ClientResponse, JsonBody, MessageBody}; pub use self::response::{ClientResponse, JsonBody, MessageBody};
pub use self::sender::SendClientRequest; 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. /// An asynchronous HTTP and WebSocket client.
/// ///
/// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU /// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU

View File

@@ -9,7 +9,6 @@ use std::{
use actix_http::{ use actix_http::{
body::Body, body::Body,
client::{InvalidUrl, SendRequestError},
http::{header, Method, StatusCode, Uri}, http::{header, Method, StatusCode, Uri},
RequestHead, RequestHeadType, RequestHead, RequestHeadType,
}; };
@@ -19,6 +18,7 @@ use futures_core::ready;
use super::Transform; use super::Transform;
use crate::client::{InvalidUrl, SendRequestError};
use crate::connect::{ConnectRequest, ConnectResponse}; use crate::connect::{ConnectRequest, ConnectResponse};
use crate::ClientResponse; use crate::ClientResponse;

View File

@@ -795,17 +795,15 @@ async fn client_unread_response() {
let lst = std::net::TcpListener::bind(addr).unwrap(); let lst = std::net::TcpListener::bind(addr).unwrap();
std::thread::spawn(move || { std::thread::spawn(move || {
for stream in lst.incoming() { let (mut stream, _) = lst.accept().unwrap();
let mut stream = stream.unwrap(); let mut b = [0; 1000];
let mut b = [0; 1000]; let _ = stream.read(&mut b).unwrap();
let _ = stream.read(&mut b).unwrap(); let _ = stream.write_all(
let _ = stream.write_all( b"HTTP/1.1 200 OK\r\n\
b"HTTP/1.1 200 OK\r\n\
connection: close\r\n\ connection: close\r\n\
\r\n\ \r\n\
welcome!", welcome!",
); );
}
}); });
// client request // client request

View File

@@ -8,44 +8,59 @@ use std::{
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
Arc, Arc,
}, },
time::SystemTime,
}; };
use actix_http::HttpService; use actix_http::HttpService;
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::{fn_service, map_config, ServiceFactoryExt}; 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_utils::future::ok;
use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse};
use rustls::internal::pemfile::{certs, pkcs8_private_keys}; use rustls::{
use rustls::{ClientConfig, NoClientAuth, ServerConfig}; client::{ServerCertVerified, ServerCertVerifier},
Certificate, ClientConfig, PrivateKey, ServerConfig, ServerName,
};
use rustls_pemfile::{certs, pkcs8_private_keys};
fn tls_config() -> ServerConfig { fn tls_config() -> ServerConfig {
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
let cert_file = cert.serialize_pem().unwrap(); let cert_file = cert.serialize_pem().unwrap();
let key_file = cert.serialize_private_key_pem(); 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 cert_file = &mut BufReader::new(cert_file.as_bytes());
let key_file = &mut BufReader::new(key_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(); 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 { mod danger {
use super::*;
pub struct NoCertificateVerification; pub struct NoCertificateVerification;
impl rustls::ServerCertVerifier for NoCertificateVerification { impl ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert( fn verify_server_cert(
&self, &self,
_roots: &rustls::RootCertStore, _end_entity: &Certificate,
_presented_certs: &[rustls::Certificate], _intermediates: &[Certificate],
_dns_name: webpki::DNSNameRef<'_>, _server_name: &ServerName,
_ocsp: &[u8], _scts: &mut dyn Iterator<Item = &[u8]>,
) -> Result<rustls::ServerCertVerified, rustls::TLSError> { _ocsp_response: &[u8],
Ok(rustls::ServerCertVerified::assertion()) _now: SystemTime,
) -> Result<ServerCertVerified, rustls::Error> {
Ok(ServerCertVerified::assertion())
} }
} }
} }
@@ -73,10 +88,15 @@ async fn test_connection_reuse_h2() {
}) })
.await; .await;
// disable TLS verification let mut config = ClientConfig::builder()
let mut config = ClientConfig::new(); .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()]; 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 config
.dangerous() .dangerous()
.set_certificate_verifier(Arc::new(danger::NoCertificateVerification)); .set_certificate_verifier(Arc::new(danger::NoCertificateVerification));

View File

@@ -1 +1 @@
msrv = "1.51" msrv = "1.52"

View File

@@ -35,7 +35,7 @@ async fn main() -> std::io::Result<()> {
) )
.service(web::resource("/test1.html").to(|| async { "Test\r\n" })) .service(web::resource("/test1.html").to(|| async { "Test\r\n" }))
}) })
.bind("127.0.0.1:8080")? .bind(("127.0.0.1", 8080))?
.workers(1) .workers(1)
.run() .run()
.await .await

View File

@@ -8,6 +8,7 @@ use std::{any::Any, io, net::SocketAddr};
use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer}; use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer};
#[allow(dead_code)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct ConnectionInfo { struct ConnectionInfo {
bind: SocketAddr, bind: SocketAddr,

View File

@@ -75,7 +75,9 @@ impl<T> Data<T> {
pub fn new(state: T) -> Data<T> { pub fn new(state: T) -> Data<T> {
Data(Arc::new(state)) Data(Arc::new(state))
} }
}
impl<T: ?Sized> Data<T> {
/// Get reference to inner app data. /// Get reference to inner app data.
pub fn get_ref(&self) -> &T { pub fn get_ref(&self) -> &T {
self.0.as_ref() self.0.as_ref()
@@ -120,7 +122,6 @@ where
} }
impl<T: ?Sized + 'static> FromRequest for Data<T> { impl<T: ?Sized + 'static> FromRequest for Data<T> {
type Config = ();
type Error = Error; type Error = Error;
type Future = Ready<Result<Self, Error>>; type Future = Ready<Result<Self, Error>>;
@@ -136,7 +137,7 @@ impl<T: ?Sized + 'static> FromRequest for Data<T> {
type_name::<T>(), type_name::<T>(),
); );
err(ErrorInternalServerError( 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); let data_arc = Data::from(dyn_arc);
assert_eq!(data_arc_box.get_num(), data_arc.get_num()) 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())
}
} }

View File

@@ -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::encoding::Decoder as Decompress;
pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead}; pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead};
pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
pub use actix_server::Server; pub use actix_server::{Server, ServerHandle};
pub use actix_service::{ pub use actix_service::{
always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform, always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform,
}; };

View File

@@ -13,13 +13,52 @@ use futures_core::ready;
use crate::{dev::Payload, Error, HttpRequest}; use crate::{dev::Payload, Error, HttpRequest};
/// Trait implemented by types that can be extracted from request. /// A type that implements [`FromRequest`] is called an **extractor** and can extract data from
/// the request. Some types that implement this trait are: [`Json`], [`Header`], and [`Path`].
/// ///
/// Types that implement this trait can be used with `Route` handlers. /// # Configuration
/// An extractor can be customized by injecting the corresponding configuration with one of:
///
/// - [`App::app_data()`][crate::App::app_data]
/// - [`Scope::app_data()`][crate::Scope::app_data]
/// - [`Resource::app_data()`][crate::Resource::app_data]
///
/// Here are some built-in extractors and their corresponding configuration.
/// Please refer to the respective documentation for details.
///
/// | Extractor | Configuration |
/// |-------------|-------------------|
/// | [`Header`] | _None_ |
/// | [`Path`] | [`PathConfig`] |
/// | [`Json`] | [`JsonConfig`] |
/// | [`Form`] | [`FormConfig`] |
/// | [`Query`] | [`QueryConfig`] |
/// | [`Bytes`] | [`PayloadConfig`] |
/// | [`String`] | [`PayloadConfig`] |
/// | [`Payload`] | [`PayloadConfig`] |
///
/// # Implementing An Extractor
/// To reduce duplicate code in handlers where extracting certain parts of a request has a common
/// structure, you can implement `FromRequest` for your own types.
///
/// Note that the request payload can only be consumed by one extractor.
///
/// [`Header`]: crate::web::Header
/// [`Json`]: crate::web::Json
/// [`JsonConfig`]: crate::web::JsonConfig
/// [`Form`]: crate::web::Form
/// [`FormConfig`]: crate::web::FormConfig
/// [`Path`]: crate::web::Path
/// [`PathConfig`]: crate::web::PathConfig
/// [`Query`]: crate::web::Query
/// [`QueryConfig`]: crate::web::QueryConfig
/// [`Payload`]: crate::web::Payload
/// [`PayloadConfig`]: crate::web::PayloadConfig
/// [`String`]: FromRequest#impl-FromRequest-for-String
/// [`Bytes`]: crate::web::Bytes#impl-FromRequest
/// [`Either`]: crate::web::Either
#[doc(alias = "extract", alias = "extractor")]
pub trait FromRequest: Sized { pub trait FromRequest: Sized {
/// Configuration for this extractor.
type Config: Default + 'static;
/// The associated error which can be returned. /// The associated error which can be returned.
type Error: Into<Error>; type Error: Into<Error>;
@@ -35,14 +74,6 @@ pub trait FromRequest: Sized {
fn extract(req: &HttpRequest) -> Self::Future { fn extract(req: &HttpRequest) -> Self::Future {
Self::from_request(req, &mut Payload::None) Self::from_request(req, &mut Payload::None)
} }
/// Create and configure config instance.
fn configure<F>(f: F) -> Self::Config
where
F: FnOnce(Self::Config) -> Self::Config,
{
f(Self::Config::default())
}
} }
/// Optionally extract a field from the request /// Optionally extract a field from the request
@@ -65,7 +96,6 @@ pub trait FromRequest: Sized {
/// impl FromRequest for Thing { /// impl FromRequest for Thing {
/// type Error = Error; /// type Error = Error;
/// type Future = Ready<Result<Self, Self::Error>>; /// type Future = Ready<Result<Self, Self::Error>>;
/// type Config = ();
/// ///
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { /// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
/// if rand::random() { /// if rand::random() {
@@ -100,7 +130,6 @@ where
{ {
type Error = Error; type Error = Error;
type Future = FromRequestOptFuture<T::Future>; type Future = FromRequestOptFuture<T::Future>;
type Config = T::Config;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
@@ -156,7 +185,6 @@ where
/// impl FromRequest for Thing { /// impl FromRequest for Thing {
/// type Error = Error; /// type Error = Error;
/// type Future = Ready<Result<Thing, Error>>; /// type Future = Ready<Result<Thing, Error>>;
/// type Config = ();
/// ///
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { /// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
/// if rand::random() { /// if rand::random() {
@@ -189,7 +217,6 @@ where
{ {
type Error = Error; type Error = Error;
type Future = FromRequestResFuture<T::Future>; type Future = FromRequestResFuture<T::Future>;
type Config = T::Config;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
@@ -233,7 +260,6 @@ where
impl FromRequest for Uri { impl FromRequest for Uri {
type Error = Infallible; type Error = Infallible;
type Future = Ready<Result<Self, Self::Error>>; type Future = Ready<Result<Self, Self::Error>>;
type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
ok(req.uri().clone()) ok(req.uri().clone())
@@ -255,7 +281,6 @@ impl FromRequest for Uri {
impl FromRequest for Method { impl FromRequest for Method {
type Error = Infallible; type Error = Infallible;
type Future = Ready<Result<Self, Self::Error>>; type Future = Ready<Result<Self, Self::Error>>;
type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
ok(req.method().clone()) ok(req.method().clone())
@@ -266,7 +291,6 @@ impl FromRequest for Method {
impl FromRequest for () { impl FromRequest for () {
type Error = Infallible; type Error = Infallible;
type Future = Ready<Result<Self, Self::Error>>; type Future = Ready<Result<Self, Self::Error>>;
type Config = ();
fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future {
ok(()) ok(())
@@ -306,7 +330,6 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
{ {
type Error = Error; type Error = Error;
type Future = $fut_type<$($T),+>; type Future = $fut_type<$($T),+>;
type Config = ($($T::Config),+);
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
$fut_type { $fut_type {

View File

@@ -60,52 +60,53 @@ crate::http::header::common_header! {
} }
impl ContentType { impl ContentType {
/// A constructor to easily create a `Content-Type: application/json` /// A constructor to easily create a `Content-Type: application/json`
/// header. /// header.
#[inline] #[inline]
pub fn json() -> ContentType { pub fn json() -> ContentType {
ContentType(mime::APPLICATION_JSON) 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. /// charset=utf-8` header.
#[inline] #[inline]
pub fn plaintext() -> ContentType { pub fn plaintext() -> ContentType {
ContentType(mime::TEXT_PLAIN_UTF_8) 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] #[inline]
pub fn html() -> ContentType { 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] #[inline]
pub fn xml() -> ContentType { pub fn xml() -> ContentType {
ContentType(mime::TEXT_XML) 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. /// application/www-form-url-encoded` header.
#[inline] #[inline]
pub fn form_url_encoded() -> ContentType { pub fn form_url_encoded() -> ContentType {
ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) 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] #[inline]
pub fn jpeg() -> ContentType { pub fn jpeg() -> ContentType {
ContentType(mime::IMAGE_JPEG) 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] #[inline]
pub fn png() -> ContentType { pub fn png() -> ContentType {
ContentType(mime::IMAGE_PNG) ContentType(mime::IMAGE_PNG)
} }
/// A constructor to easily create a `Content-Type: /// A constructor to easily create a `Content-Type:
/// application/octet-stream` header. /// application/octet-stream` header.
#[inline] #[inline]
pub fn octet_stream() -> ContentType { pub fn octet_stream() -> ContentType {

View File

@@ -209,7 +209,6 @@ impl ConnectionInfo {
impl FromRequest for ConnectionInfo { impl FromRequest for ConnectionInfo {
type Error = Infallible; type Error = Infallible;
type Future = Ready<Result<Self, Self::Error>>; type Future = Ready<Result<Self, Self::Error>>;
type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
ok(req.connection_info().clone()) ok(req.connection_info().clone())
@@ -252,7 +251,6 @@ impl ResponseError for MissingPeerAddr {}
impl FromRequest for PeerAddr { impl FromRequest for PeerAddr {
type Error = MissingPeerAddr; type Error = MissingPeerAddr;
type Future = Ready<Result<Self, Self::Error>>; type Future = Ready<Result<Self, Self::Error>>;
type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
match req.peer_addr() { match req.peer_addr() {

View File

@@ -53,7 +53,7 @@
//! * SSL support using OpenSSL or Rustls //! * SSL support using OpenSSL or Rustls
//! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) //! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
//! * Includes an async [HTTP client](https://docs.rs/awc/) //! * Includes an async [HTTP client](https://docs.rs/awc/)
//! * Runs on stable Rust 1.51+ //! * Runs on stable Rust 1.52+
//! //!
//! # Crate Features //! # Crate Features
//! * `cookies` - cookies support (enabled by default) //! * `cookies` - cookies support (enabled by default)

View File

@@ -276,7 +276,7 @@ impl AcceptEncoding {
let mut encodings = raw let mut encodings = raw
.replace(' ', "") .replace(' ', "")
.split(',') .split(',')
.filter_map(|l| AcceptEncoding::new(l)) .filter_map(AcceptEncoding::new)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
encodings.sort(); encodings.sort();

View File

@@ -18,11 +18,11 @@ use bytes::Bytes;
use futures_core::ready; use futures_core::ready;
use log::{debug, warn}; use log::{debug, warn};
use regex::{Regex, RegexSet}; use regex::{Regex, RegexSet};
use time::OffsetDateTime; use time::{format_description::well_known::Rfc3339, OffsetDateTime};
use crate::{ use crate::{
dev::{BodySize, MessageBody}, dev::{BodySize, MessageBody},
http::{HeaderName, StatusCode}, http::HeaderName,
service::{ServiceRequest, ServiceResponse}, service::{ServiceRequest, ServiceResponse},
Error, HttpResponse, Result, Error, HttpResponse, Result,
}; };
@@ -275,9 +275,7 @@ where
}; };
if let Some(error) = res.response().error() { 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 { if let Some(ref mut format) = this.format {
@@ -538,7 +536,7 @@ impl FormatText {
}; };
} }
FormatText::UrlPath => *self = FormatText::Str(req.path().to_string()), FormatText::UrlPath => *self = FormatText::Str(req.path().to_string()),
FormatText::RequestTime => *self = FormatText::Str(now.format("%Y-%m-%dT%H:%M:%S")), FormatText::RequestTime => *self = FormatText::Str(now.format(&Rfc3339).unwrap()),
FormatText::RequestHeader(ref name) => { FormatText::RequestHeader(ref name) => {
let s = if let Some(val) = req.headers().get(name) { let s = if let Some(val) = req.headers().get(name) {
if let Ok(s) = val.to_str() { if let Ok(s) = val.to_str() {
@@ -767,7 +765,7 @@ mod tests {
Ok(()) Ok(())
}; };
let s = format!("{}", FormatDisplay(&render)); let s = format!("{}", FormatDisplay(&render));
assert!(s.contains(&now.format("%Y-%m-%dT%H:%M:%S"))); assert!(s.contains(&now.format(&Rfc3339).unwrap()));
} }
#[actix_rt::test] #[actix_rt::test]

View File

@@ -358,7 +358,6 @@ impl Drop for HttpRequest {
/// } /// }
/// ``` /// ```
impl FromRequest for HttpRequest { impl FromRequest for HttpRequest {
type Config = ();
type Error = Error; type Error = Error;
type Future = Ready<Result<Self, Error>>; type Future = Ready<Result<Self, Error>>;

View File

@@ -64,7 +64,6 @@ impl<T: Clone + 'static> Deref for ReqData<T> {
} }
impl<T: Clone + 'static> FromRequest for ReqData<T> { impl<T: Clone + 'static> FromRequest for ReqData<T> {
type Config = ();
type Error = Error; type Error = Error;
type Future = Ready<Result<Self, Error>>; type Future = Ready<Result<Self, Error>>;

View File

@@ -159,7 +159,7 @@ where
/// ///
/// By default max connections is set to a 25k. /// By default max connections is set to a 25k.
pub fn max_connections(mut self, num: usize) -> Self { pub fn max_connections(mut self, num: usize) -> Self {
self.builder = self.builder.maxconn(num); self.builder = self.builder.max_concurrent_connections(num);
self self
} }
@@ -233,7 +233,7 @@ where
self self
} }
/// Stop actix system. /// Stop Actix `System` after server shutdown.
pub fn system_exit(mut self) -> Self { pub fn system_exit(mut self) -> Self {
self.builder = self.builder.system_exit(); self.builder = self.builder.system_exit();
self self

View File

@@ -393,16 +393,6 @@ impl<B> ServiceResponse<B> {
self.response.headers_mut() 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 /// Extract response body
pub fn into_body(self) -> B { pub fn into_body(self) -> B {
self.response.into_body() self.response.into_body()

View File

@@ -52,7 +52,7 @@ pub fn default_service(
/// use actix_service::Service; /// use actix_service::Service;
/// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// use actix_web::{test, web, App, HttpResponse, http::StatusCode};
/// ///
/// #[actix_rt::test] /// #[actix_web::test]
/// async fn test_init_service() { /// async fn test_init_service() {
/// let app = test::init_service( /// let app = test::init_service(
/// App::new() /// App::new()
@@ -98,7 +98,7 @@ where
/// ``` /// ```
/// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// use actix_web::{test, web, App, HttpResponse, http::StatusCode};
/// ///
/// #[actix_rt::test] /// #[actix_web::test]
/// async fn test_response() { /// async fn test_response() {
/// let app = test::init_service( /// let app = test::init_service(
/// App::new() /// App::new()
@@ -129,7 +129,7 @@ where
/// use actix_web::{test, web, App, HttpResponse, http::header}; /// use actix_web::{test, web, App, HttpResponse, http::header};
/// use bytes::Bytes; /// use bytes::Bytes;
/// ///
/// #[actix_rt::test] /// #[actix_web::test]
/// async fn test_index() { /// async fn test_index() {
/// let app = test::init_service( /// let app = test::init_service(
/// App::new().service( /// App::new().service(
@@ -176,7 +176,7 @@ where
/// use actix_web::{test, web, App, HttpResponse, http::header}; /// use actix_web::{test, web, App, HttpResponse, http::header};
/// use bytes::Bytes; /// use bytes::Bytes;
/// ///
/// #[actix_rt::test] /// #[actix_web::test]
/// async fn test_index() { /// async fn test_index() {
/// let app = test::init_service( /// let app = test::init_service(
/// App::new().service( /// App::new().service(
@@ -224,7 +224,7 @@ where
/// name: String, /// name: String,
/// } /// }
/// ///
/// #[actix_rt::test] /// #[actix_web::test]
/// async fn test_post_person() { /// async fn test_post_person() {
/// let app = test::init_service( /// let app = test::init_service(
/// App::new().service( /// App::new().service(
@@ -296,7 +296,7 @@ where
/// name: String /// name: String
/// } /// }
/// ///
/// #[actix_rt::test] /// #[actix_web::test]
/// async fn test_add_person() { /// async fn test_add_person() {
/// let app = test::init_service( /// let app = test::init_service(
/// App::new().service( /// App::new().service(
@@ -356,8 +356,8 @@ where
/// } /// }
/// } /// }
/// ///
/// #[test] /// #[actix_web::test]
/// fn test_index() { /// async fn test_index() {
/// let req = test::TestRequest::default().insert_header("content-type", "text/plain") /// let req = test::TestRequest::default().insert_header("content-type", "text/plain")
/// .to_http_request(); /// .to_http_request();
/// ///

View File

@@ -187,7 +187,6 @@ where
{ {
type Error = EitherExtractError<L::Error, R::Error>; type Error = EitherExtractError<L::Error, R::Error>;
type Future = EitherExtractFut<L, R>; type Future = EitherExtractFut<L, R>;
type Config = ();
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
EitherExtractFut { EitherExtractFut {

View File

@@ -32,7 +32,7 @@ use crate::{
/// To extract typed data from a request body, the inner type `T` must implement the /// To extract typed data from a request body, the inner type `T` must implement the
/// [`DeserializeOwned`] trait. /// [`DeserializeOwned`] trait.
/// ///
/// Use [`FormConfig`] to configure extraction process. /// Use [`FormConfig`] to configure extraction options.
/// ///
/// ``` /// ```
/// use actix_web::{post, web}; /// use actix_web::{post, web};
@@ -126,20 +126,12 @@ impl<T> FromRequest for Form<T>
where where
T: DeserializeOwned + 'static, T: DeserializeOwned + 'static,
{ {
type Config = FormConfig;
type Error = Error; type Error = Error;
type Future = FormExtractFut<T>; type Future = FormExtractFut<T>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let (limit, err_handler) = req let FormConfig { limit, err_handler } = FormConfig::from_req(req).clone();
.app_data::<Self::Config>()
.or_else(|| {
req.app_data::<web::Data<Self::Config>>()
.map(|d| d.as_ref())
})
.map(|c| (c.limit, c.err_handler.clone()))
.unwrap_or((16384, None));
FormExtractFut { FormExtractFut {
fut: UrlEncoded::new(req, payload).limit(limit), fut: UrlEncoded::new(req, payload).limit(limit),
@@ -241,14 +233,26 @@ impl FormConfig {
self.err_handler = Some(Rc::new(f)); self.err_handler = Some(Rc::new(f));
self self
} }
/// Extract payload config from app data.
///
/// Checks both `T` and `Data<T>`, in that order, and falls back to the default payload config.
fn from_req(req: &HttpRequest) -> &Self {
req.app_data::<Self>()
.or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
.unwrap_or(&DEFAULT_CONFIG)
}
} }
/// Allow shared refs used as default.
const DEFAULT_CONFIG: FormConfig = FormConfig {
limit: 16_384, // 2^14 bytes (~16kB)
err_handler: None,
};
impl Default for FormConfig { impl Default for FormConfig {
fn default() -> Self { fn default() -> Self {
FormConfig { DEFAULT_CONFIG
limit: 16_384, // 2^14 bytes (~16kB)
err_handler: None,
}
} }
} }

View File

@@ -62,7 +62,6 @@ where
{ {
type Error = ParseError; type Error = ParseError;
type Future = Ready<Result<Self, Self::Error>>; type Future = Ready<Result<Self, Self::Error>>;
type Config = ();
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {

View File

@@ -34,7 +34,7 @@ use crate::{
/// To extract typed data from a request body, the inner type `T` must implement the /// To extract typed data from a request body, the inner type `T` must implement the
/// [`serde::Deserialize`] trait. /// [`serde::Deserialize`] trait.
/// ///
/// Use [`JsonConfig`] to configure extraction process. /// Use [`JsonConfig`] to configure extraction options.
/// ///
/// ``` /// ```
/// use actix_web::{post, web, App}; /// use actix_web::{post, web, App};
@@ -127,22 +127,22 @@ impl<T: Serialize> Responder for Json<T> {
} }
/// See [here](#extractor) for example of usage as an extractor. /// See [here](#extractor) for example of usage as an extractor.
impl<T: DeserializeOwned + 'static> FromRequest for Json<T> { impl<T: DeserializeOwned> FromRequest for Json<T> {
type Error = Error; type Error = Error;
type Future = JsonExtractFut<T>; type Future = JsonExtractFut<T>;
type Config = JsonConfig;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let config = JsonConfig::from_req(req); let config = JsonConfig::from_req(req);
let limit = config.limit; let limit = config.limit;
let ctype = config.content_type.as_deref(); let ctype_required = config.content_type_required;
let ctype_fn = config.content_type.as_deref();
let err_handler = config.err_handler.clone(); let err_handler = config.err_handler.clone();
JsonExtractFut { JsonExtractFut {
req: Some(req.clone()), req: Some(req.clone()),
fut: JsonBody::new(req, payload, ctype).limit(limit), fut: JsonBody::new(req, payload, ctype_fn, ctype_required).limit(limit),
err_handler, err_handler,
} }
} }
@@ -157,7 +157,7 @@ pub struct JsonExtractFut<T> {
err_handler: JsonErrorHandler, err_handler: JsonErrorHandler,
} }
impl<T: DeserializeOwned + 'static> Future for JsonExtractFut<T> { impl<T: DeserializeOwned> Future for JsonExtractFut<T> {
type Output = Result<Json<T>, Error>; type Output = Result<Json<T>, Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
@@ -225,6 +225,7 @@ pub struct JsonConfig {
limit: usize, limit: usize,
err_handler: JsonErrorHandler, err_handler: JsonErrorHandler,
content_type: Option<Arc<dyn Fn(mime::Mime) -> bool + Send + Sync>>, content_type: Option<Arc<dyn Fn(mime::Mime) -> bool + Send + Sync>>,
content_type_required: bool,
} }
impl JsonConfig { impl JsonConfig {
@@ -252,6 +253,12 @@ impl JsonConfig {
self self
} }
/// Sets whether or not the request must have a `Content-Type` header to be parsed.
pub fn content_type_required(mut self, content_type_required: bool) -> Self {
self.content_type_required = content_type_required;
self
}
/// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall /// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
/// back to the default payload config. /// back to the default payload config.
fn from_req(req: &HttpRequest) -> &Self { fn from_req(req: &HttpRequest) -> &Self {
@@ -268,6 +275,7 @@ const DEFAULT_CONFIG: JsonConfig = JsonConfig {
limit: DEFAULT_LIMIT, limit: DEFAULT_LIMIT,
err_handler: None, err_handler: None,
content_type: None, content_type: None,
content_type_required: true,
}; };
impl Default for JsonConfig { impl Default for JsonConfig {
@@ -278,15 +286,18 @@ impl Default for JsonConfig {
/// Future that resolves to some `T` when parsed from a JSON payload. /// Future that resolves to some `T` when parsed from a JSON payload.
/// ///
/// Form can be deserialized from any type `T` that implements [`serde::Deserialize`]. /// Can deserialize any type `T` that implements [`Deserialize`][serde::Deserialize].
/// ///
/// Returns error if: /// Returns error if:
/// - content type is not `application/json` /// - `Content-Type` is not `application/json` when `ctype_required` (passed to [`new`][Self::new])
/// - content length is greater than [limit](JsonBody::limit()) /// is `true`.
/// - `Content-Length` is greater than [limit](JsonBody::limit()).
/// - The payload, when consumed, is not valid JSON.
pub enum JsonBody<T> { pub enum JsonBody<T> {
Error(Option<JsonPayloadError>), Error(Option<JsonPayloadError>),
Body { Body {
limit: usize, limit: usize,
/// Length as reported by `Content-Length` header, if present.
length: Option<usize>, length: Option<usize>,
#[cfg(feature = "__compress")] #[cfg(feature = "__compress")]
payload: Decompress<Payload>, payload: Decompress<Payload>,
@@ -305,18 +316,21 @@ impl<T: DeserializeOwned> JsonBody<T> {
pub fn new( pub fn new(
req: &HttpRequest, req: &HttpRequest,
payload: &mut Payload, payload: &mut Payload,
ctype: Option<&(dyn Fn(mime::Mime) -> bool + Send + Sync)>, ctype_fn: Option<&(dyn Fn(mime::Mime) -> bool + Send + Sync)>,
ctype_required: bool,
) -> Self { ) -> Self {
// check content-type // check content-type
let json = if let Ok(Some(mime)) = req.mime_type() { let can_parse_json = if let Ok(Some(mime)) = req.mime_type() {
mime.subtype() == mime::JSON mime.subtype() == mime::JSON
|| mime.suffix() == Some(mime::JSON) || mime.suffix() == Some(mime::JSON)
|| ctype.map_or(false, |predicate| predicate(mime)) || ctype_fn.map_or(false, |predicate| predicate(mime))
} else { } else {
false // if `ctype_required` is false, assume payload is
// json even when content-type header is missing
!ctype_required
}; };
if !json { if !can_parse_json {
return JsonBody::Error(Some(JsonPayloadError::ContentType)); return JsonBody::Error(Some(JsonPayloadError::ContentType));
} }
@@ -326,7 +340,7 @@ impl<T: DeserializeOwned> JsonBody<T> {
.and_then(|l| l.to_str().ok()) .and_then(|l| l.to_str().ok())
.and_then(|s| s.parse::<usize>().ok()); .and_then(|s| s.parse::<usize>().ok());
// Notice the content_length is not checked against limit of json config here. // Notice the content-length is not checked against limit of json config here.
// As the internal usage always call JsonBody::limit after JsonBody::new. // As the internal usage always call JsonBody::limit after JsonBody::new.
// And limit check to return an error variant of JsonBody happens there. // And limit check to return an error variant of JsonBody happens there.
@@ -380,7 +394,7 @@ impl<T: DeserializeOwned> JsonBody<T> {
} }
} }
impl<T: DeserializeOwned + 'static> Future for JsonBody<T> { impl<T: DeserializeOwned> Future for JsonBody<T> {
type Output = Result<T, JsonPayloadError>; type Output = Result<T, JsonPayloadError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
@@ -563,7 +577,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_json_body() { async fn test_json_body() {
let (req, mut pl) = TestRequest::default().to_http_parts(); let (req, mut pl) = TestRequest::default().to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await; let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true).await;
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let (req, mut pl) = TestRequest::default() let (req, mut pl) = TestRequest::default()
@@ -572,7 +586,7 @@ mod tests {
header::HeaderValue::from_static("application/text"), header::HeaderValue::from_static("application/text"),
)) ))
.to_http_parts(); .to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await; let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true).await;
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let (req, mut pl) = TestRequest::default() let (req, mut pl) = TestRequest::default()
@@ -586,7 +600,7 @@ mod tests {
)) ))
.to_http_parts(); .to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None) let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true)
.limit(100) .limit(100)
.await; .await;
assert!(json_eq( assert!(json_eq(
@@ -605,7 +619,7 @@ mod tests {
.set_payload(Bytes::from_static(&[0u8; 1000])) .set_payload(Bytes::from_static(&[0u8; 1000]))
.to_http_parts(); .to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None) let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true)
.limit(100) .limit(100)
.await; .await;
@@ -626,7 +640,7 @@ mod tests {
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.to_http_parts(); .to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await; let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true).await;
assert_eq!( assert_eq!(
json.ok().unwrap(), json.ok().unwrap(),
MyObject { MyObject {
@@ -696,6 +710,21 @@ mod tests {
assert!(s.is_err()) assert!(s.is_err())
} }
#[actix_rt::test]
async fn test_json_with_no_content_type() {
let (req, mut pl) = TestRequest::default()
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.app_data(JsonConfig::default().content_type_required(false))
.to_http_parts();
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
assert!(s.is_ok())
}
#[actix_rt::test] #[actix_rt::test]
async fn test_with_config_in_data_wrapper() { async fn test_with_config_in_data_wrapper() {
let (req, mut pl) = TestRequest::default() let (req, mut pl) = TestRequest::default()

Some files were not shown because too many files have changed in this diff Show More