1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-24 01:38:20 +02:00

Compare commits

..

17 Commits

Author SHA1 Message Date
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
84 changed files with 791 additions and 588 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,30 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 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.10"
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.3"
actix-service = "2.0.0" actix-service = "2.0.0"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } actix-tls = { version = "3.0.0-beta.7", default-features = false, optional = true }
actix-web-codegen = "0.5.0-beta.4" actix-http = "3.0.0-beta.11"
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.3", features = ["openssl", "rustls"] }
awc = { version = "3.0.0-beta.8", features = ["openssl"] } awc = { version = "3.0.0-beta.9", 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.10)](https://docs.rs/actix-web/4.0.0-beta.10)
[![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.10/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.10)
<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.10", default-features = false }
actix-http = "3.0.0-beta.10" actix-http = "3.0.0-beta.11"
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.10"
actix-test = "0.1.0-beta.3" actix-test = "0.1.0-beta.5"

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

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

View File

@@ -31,11 +31,11 @@ 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.3"
awc = { version = "3.0.0-beta.8", default-features = false } awc = { version = "3.0.0-beta.9", default-features = false }
base64 = "0.13" base64 = "0.13"
bytes = "1" bytes = "1"
@@ -47,9 +47,8 @@ serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
slab = "0.4" slab = "0.4"
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
time = { version = "0.2.23", default-features = false, features = ["std"] }
tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
[dev-dependencies] [dev-dependencies]
actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["cookies"] } actix-web = { version = "4.0.0-beta.10", default-features = false, features = ["cookies"] }
actix-http = "3.0.0-beta.10" actix-http = "3.0.0-beta.11"

View File

@@ -4,7 +4,7 @@
[![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.5)](https://docs.rs/actix-http-test/3.0.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)
![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.5/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.5)
@@ -14,4 +14,4 @@
## 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, sync::mpsc, thread, time::Duration};
use std::{net, thread, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_rt::{net::TcpStream, System}; use actix_rt::{net::TcpStream, System};
@@ -37,7 +36,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(
@@ -95,15 +94,15 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
.set_alpn_protos(b"\x02h2\x08http/1.1") .set_alpn_protos(b"\x02h2\x08http/1.1")
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
Connector::new() Connector::new()
.conn_lifetime(time::Duration::from_secs(0)) .conn_lifetime(Duration::from_secs(0))
.timeout(time::Duration::from_millis(30000)) .timeout(Duration::from_millis(30000))
.ssl(builder.build()) .ssl(builder.build())
} }
#[cfg(not(feature = "openssl"))] #[cfg(not(feature = "openssl"))]
{ {
Connector::new() Connector::new()
.conn_lifetime(time::Duration::from_secs(0)) .conn_lifetime(Duration::from_secs(0))
.timeout(time::Duration::from_millis(30000)) .timeout(Duration::from_millis(30000))
} }
}; };

View File

@@ -3,6 +3,14 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 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.11"
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"
@@ -46,7 +49,7 @@ 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"] } actix-tls = { version = "3.0.0-beta.7", features = ["accept", "connect"] }
ahash = "0.7" ahash = "0.7"
base64 = "0.13" base64 = "0.13"
@@ -60,6 +63,7 @@ futures-util = { version = "0.3.7", default-features = false, features = ["alloc
h2 = "0.3.1" h2 = "0.3.1"
http = "0.2.2" http = "0.2.2"
httparse = "1.5.1" httparse = "1.5.1"
httpdate = "1.0.1"
itoa = "0.4" itoa = "0.4"
language-tags = "0.3" language-tags = "0.3"
local-channel = "0.1" local-channel = "0.1"
@@ -70,11 +74,8 @@ percent-encoding = "2.1"
pin-project = "1.0.0" pin-project = "1.0.0"
pin-project-lite = "0.2" pin-project-lite = "0.2"
rand = "0.8" rand = "0.8"
regex = "1.3"
serde = "1.0"
sha-1 = "0.9" sha-1 = "0.9"
smallvec = "1.6.1" smallvec = "1.6.1"
time = { version = "0.2.23", default-features = false, features = ["std"] }
tokio = { version = "1.2", features = ["sync"] } tokio = { version = "1.2", features = ["sync"] }
# compression # compression
@@ -87,16 +88,17 @@ trust-dns-resolver = { version = "0.20.0", optional = true }
[dev-dependencies] [dev-dependencies]
actix-server = "2.0.0-beta.3" actix-server = "2.0.0-beta.3"
actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] }
actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.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" }
[[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.11)](https://docs.rs/actix-http/3.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-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.11/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.11)
[![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

@@ -28,18 +28,13 @@ use super::pool::ConnectionPool;
use super::Connect; use super::Connect;
use super::Protocol; 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,35 @@ 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 +111,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) 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> {
@@ -167,14 +171,20 @@ 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,18 +323,15 @@ 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) {
let sock = self.into_parts().0; let sock = self.into_parts().0;
let h2 = sock let h2 =
.get_ref() sock.get_ref().1.alpn_protocol().map_or(false, |protos| {
.1 protos.windows(2).any(|w| w == H2)
.get_alpn_protocol() });
.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)
} else { } else {

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

@@ -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

@@ -44,7 +44,6 @@ mod request;
mod response; mod response;
mod response_builder; mod response_builder;
mod service; mod service;
mod time_parser;
pub mod error; pub mod error;
pub mod h1; pub mod h1;
@@ -104,14 +103,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

@@ -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,17 @@ 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::{
internal::pemfile::{certs, pkcs8_private_keys}, Certificate, OwnedTrustAnchor, PrivateKey, RootCertStore,
NoClientAuth, ServerConfig as RustlsServerConfig, Session, ServerConfig as RustlsServerConfig, ServerName,
}; };
use webpki::DNSNameRef; use rustls_pemfile::{certs, pkcs8_private_keys};
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 +48,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 +74,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

@@ -183,6 +183,7 @@ async fn test_chunked_payload() {
Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(), Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(),
None => panic!("Failed to find size in HTTP Response: {}", data), None => panic!("Failed to find size in HTTP Response: {}", data),
}; };
size size
}; };

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.10", 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.11"
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

@@ -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,13 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 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,13 +1,22 @@
[package] [package]
name = "actix-test" name = "actix-test"
version = "0.1.0-beta.4" version = "0.1.0-beta.5"
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 = []
@@ -20,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl"]
[dependencies] [dependencies]
actix-codec = "0.4.0" actix-codec = "0.4.0"
actix-http = "3.0.0-beta.10" actix-http = "3.0.0-beta.11"
actix-http-test = "3.0.0-beta.5" actix-http-test = "3.0.0-beta.5"
actix-service = "2.0.0" actix-service = "2.0.0"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["cookies"] } actix-web = { version = "4.0.0-beta.10", 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.9", 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,4 @@ 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 }

View File

@@ -64,7 +64,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 +104,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)

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.11"
actix-web = { version = "4.0.0-beta.9", default-features = false } actix-web = { version = "4.0.0-beta.10", 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.5"
awc = { version = "3.0.0-beta.8", default-features = false } awc = { version = "3.0.0-beta.9", 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.5"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-web = "4.0.0-beta.9" actix-web = "4.0.0-beta.10"
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,12 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 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.9"
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"
@@ -55,7 +55,7 @@ __compress = []
[dependencies] [dependencies]
actix-codec = "0.4.0" actix-codec = "0.4.0"
actix-service = "2.0.0" actix-service = "2.0.0"
actix-http = "3.0.0-beta.10" actix-http = "3.0.0-beta.11"
actix-rt = { version = "2.1", default-features = false } actix-rt = { version = "2.1", default-features = false }
base64 = "0.13" base64 = "0.13"
@@ -73,24 +73,24 @@ 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 } tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] }
[dev-dependencies] [dev-dependencies]
actix-web = { version = "4.0.0-beta.9", features = ["openssl"] } actix-web = { version = "4.0.0-beta.10", features = ["openssl"] }
actix-http = { version = "3.0.0-beta.10", features = ["openssl"] } actix-http = { version = "3.0.0-beta.11", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.5", features = ["openssl"] }
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-server = "2.0.0-beta.3" actix-server = "2.0.0-beta.3"
actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0-beta.7", features = ["openssl", "rustls"] }
actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.5", 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.9)](https://docs.rs/awc/3.0.0-beta.9)
![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.9/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.9)
[![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

@@ -8,44 +8,60 @@ 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, OwnedTrustAnchor, PrivateKey, RootCertStore, 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 +89,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

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

@@ -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

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

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

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

View File

@@ -14,7 +14,7 @@ use crate::{
/// Extract typed data from request path segments. /// Extract typed data from request path segments.
/// ///
/// Use [`PathConfig`] to configure extraction process. /// Use [`PathConfig`] to configure extraction option.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
@@ -97,13 +97,12 @@ where
{ {
type Error = Error; type Error = Error;
type Future = Ready<Result<Self, Self::Error>>; type Future = Ready<Result<Self, Self::Error>>;
type Config = PathConfig;
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let error_handler = req let error_handler = req
.app_data::<Self::Config>() .app_data::<PathConfig>()
.and_then(|c| c.ehandler.clone()); .and_then(|c| c.err_handler.clone());
ready( ready(
de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
@@ -159,9 +158,9 @@ where
/// ); /// );
/// } /// }
/// ``` /// ```
#[derive(Clone)] #[derive(Clone, Default)]
pub struct PathConfig { pub struct PathConfig {
ehandler: Option<Arc<dyn Fn(PathError, &HttpRequest) -> Error + Send + Sync>>, err_handler: Option<Arc<dyn Fn(PathError, &HttpRequest) -> Error + Send + Sync>>,
} }
impl PathConfig { impl PathConfig {
@@ -170,17 +169,11 @@ impl PathConfig {
where where
F: Fn(PathError, &HttpRequest) -> Error + Send + Sync + 'static, F: Fn(PathError, &HttpRequest) -> Error + Send + Sync + 'static,
{ {
self.ehandler = Some(Arc::new(f)); self.err_handler = Some(Arc::new(f));
self self
} }
} }
impl Default for PathConfig {
fn default() -> Self {
PathConfig { ehandler: None }
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_router::ResourceDef; use actix_router::ResourceDef;

View File

@@ -43,10 +43,11 @@ use crate::{
/// Ok(format!("Request Body Bytes:\n{:?}", bytes)) /// Ok(format!("Request Body Bytes:\n{:?}", bytes))
/// } /// }
/// ``` /// ```
pub struct Payload(pub crate::dev::Payload); pub struct Payload(crate::dev::Payload);
impl Payload { impl Payload {
/// Unwrap to inner Payload type. /// Unwrap to inner Payload type.
#[inline]
pub fn into_inner(self) -> crate::dev::Payload { pub fn into_inner(self) -> crate::dev::Payload {
self.0 self.0
} }
@@ -63,7 +64,6 @@ impl Stream for Payload {
/// See [here](#usage) for example of usage as an extractor. /// See [here](#usage) for example of usage as an extractor.
impl FromRequest for Payload { impl FromRequest for Payload {
type Config = PayloadConfig;
type Error = Error; type Error = Error;
type Future = Ready<Result<Payload, Error>>; type Future = Ready<Result<Payload, Error>>;
@@ -90,7 +90,6 @@ impl FromRequest for Payload {
/// } /// }
/// ``` /// ```
impl FromRequest for Bytes { impl FromRequest for Bytes {
type Config = PayloadConfig;
type Error = Error; type Error = Error;
type Future = Either<BytesExtractFut, Ready<Result<Bytes, Error>>>; type Future = Either<BytesExtractFut, Ready<Result<Bytes, Error>>>;
@@ -126,8 +125,7 @@ impl<'a> Future for BytesExtractFut {
/// ///
/// Text extractor automatically decode body according to the request's charset. /// Text extractor automatically decode body according to the request's charset.
/// ///
/// [**PayloadConfig**](PayloadConfig) allows to configure /// Use [`PayloadConfig`] to configure extraction process.
/// extraction process.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
@@ -139,7 +137,6 @@ impl<'a> Future for BytesExtractFut {
/// format!("Body {}!", text) /// format!("Body {}!", text)
/// } /// }
impl FromRequest for String { impl FromRequest for String {
type Config = PayloadConfig;
type Error = Error; type Error = Error;
type Future = Either<StringExtractFut, Ready<Result<String, Error>>>; type Future = Either<StringExtractFut, Ready<Result<String, Error>>>;
@@ -198,14 +195,15 @@ fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result<String, E
/// Configuration for request payloads. /// Configuration for request payloads.
/// ///
/// Applies to the built-in `Bytes` and `String` extractors. Note that the `Payload` extractor does /// Applies to the built-in [`Bytes`] and [`String`] extractors.
/// not automatically check conformance with this configuration to allow more flexibility when /// Note that the [`Payload`] extractor does not automatically check
/// building extractors on top of `Payload`. /// conformance with this configuration to allow more flexibility when
/// building extractors on top of [`Payload`].
/// ///
/// By default, the payload size limit is 256kB and there is no mime type condition. /// By default, the payload size limit is 256kB and there is no mime type condition.
/// ///
/// To use this, add an instance of it to your app or service through one of the /// To use this, add an instance of it to your [`app`](crate::App), [`scope`](crate::Scope)
/// `.app_data()` methods. /// or [`resource`](crate::Resource) through the associated `.app_data()` method.
#[derive(Clone)] #[derive(Clone)]
pub struct PayloadConfig { pub struct PayloadConfig {
limit: usize, limit: usize,

View File

@@ -109,12 +109,11 @@ impl<T: fmt::Display> fmt::Display for Query<T> {
impl<T: DeserializeOwned> FromRequest for Query<T> { impl<T: DeserializeOwned> FromRequest for Query<T> {
type Error = Error; type Error = Error;
type Future = Ready<Result<Self, Error>>; type Future = Ready<Result<Self, Error>>;
type Config = QueryConfig;
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let error_handler = req let error_handler = req
.app_data::<Self::Config>() .app_data::<QueryConfig>()
.and_then(|c| c.err_handler.clone()); .and_then(|c| c.err_handler.clone());
serde_urlencoded::from_str::<T>(req.query_string()) serde_urlencoded::from_str::<T>(req.query_string())
@@ -168,7 +167,7 @@ impl<T: DeserializeOwned> FromRequest for Query<T> {
/// .app_data(query_cfg) /// .app_data(query_cfg)
/// .service(index); /// .service(index);
/// ``` /// ```
#[derive(Clone)] #[derive(Clone, Default)]
pub struct QueryConfig { pub struct QueryConfig {
err_handler: Option<Arc<dyn Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync>>, err_handler: Option<Arc<dyn Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync>>,
} }
@@ -184,12 +183,6 @@ impl QueryConfig {
} }
} }
impl Default for QueryConfig {
fn default() -> Self {
QueryConfig { err_handler: None }
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_http::http::StatusCode; use actix_http::http::StatusCode;

View File

@@ -0,0 +1,15 @@
//! Checks that test macro does not cause problems in the presence of imports named "test" that
//! could be either a module with test items or the "test with runtime" macro itself.
//!
//! Before actix/actix-net#399 was implemented, this macro was running twice. The first run output
//! `#[test]` and it got run again and since it was in scope.
//!
//! Prevented by using the fully-qualified test marker (`#[::core::prelude::v1::test]`).
use actix_web::test;
#[actix_web::test]
async fn test_macro_naming_conflict() {
let _req = test::TestRequest::default();
assert_eq!(async { 1 }.await, 1);
}

View File

@@ -883,27 +883,31 @@ async fn test_brotli_encoding_large_openssl() {
mod plus_rustls { mod plus_rustls {
use std::io::BufReader; use std::io::BufReader;
use rustls::{ use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig};
internal::pemfile::{certs, pkcs8_private_keys}, use rustls_pemfile::{certs, pkcs8_private_keys};
NoClientAuth, ServerConfig as RustlsServerConfig,
};
use super::*; use super::*;
fn rustls_config() -> RustlsServerConfig { fn tls_config() -> RustlsServerConfig {
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 = 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();
config RustlsServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
.unwrap()
} }
#[actix_rt::test] #[actix_rt::test]
@@ -914,7 +918,7 @@ mod plus_rustls {
.map(char::from) .map(char::from)
.collect::<String>(); .collect::<String>();
let srv = actix_test::start_with(actix_test::config().rustls(rustls_config()), || { let srv = actix_test::start_with(actix_test::config().rustls(tls_config()), || {
App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| {
HttpResponse::Ok() HttpResponse::Ok()
.encoding(actix_web::http::ContentEncoding::Identity) .encoding(actix_web::http::ContentEncoding::Identity)