1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-06-02 13:29:24 +00:00

Merge branch 'master' into scope_work

update CHANGES for unreleased scope macro
This commit is contained in:
Jon Lim 2024-02-18 10:52:58 -08:00
commit f8f93d4dab
68 changed files with 1227 additions and 364 deletions

View file

@ -1,6 +1,6 @@
[alias] [alias]
lint = "clippy --workspace --tests --examples --bins -- -Dclippy::todo" lint = "clippy --workspace --all-targets -- -Dclippy::todo"
lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo" lint-all = "clippy --workspace --all-features --all-targets -- -Dclippy::todo"
# lib checking # lib checking
ci-check-min = "hack --workspace check --no-default-features" ci-check-min = "hack --workspace check --no-default-features"

View file

@ -32,22 +32,22 @@ jobs:
- name: Install OpenSSL - name: Install OpenSSL
if: matrix.target.os == 'windows-latest' if: matrix.target.os == 'windows-latest'
run: choco install openssl -y --forcex64 --no-progress shell: bash
- name: Set OpenSSL dir in env
if: matrix.target.os == 'windows-latest'
run: | run: |
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append set -e
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append choco install openssl --version=1.1.1.2100 -y --no-progress
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
- name: Install Rust (${{ matrix.version.name }}) - name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with: with:
toolchain: ${{ matrix.version.version }} toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack - name: Install cargo-hack and cargo-ci-cache-clean
uses: taiki-e/install-action@v2.25.9 uses: taiki-e/install-action@v2.26.12
with: with:
tool: cargo-hack tool: cargo-hack,cargo-ci-cache-clean
- name: check minimal - name: check minimal
run: cargo ci-check-min run: cargo ci-check-min
@ -57,10 +57,12 @@ jobs:
- name: tests - name: tests
timeout-minutes: 60 timeout-minutes: 60
shell: bash
run: | run: |
set -e
cargo test --lib --tests -p=actix-router --all-features cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,rustls-0_22,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features cargo test --lib --tests -p=actix-http-test --all-features
@ -69,10 +71,8 @@ jobs:
cargo test --lib --tests -p=actix-multipart --all-features cargo test --lib --tests -p=actix-multipart --all-features
cargo test --lib --tests -p=actix-web-actors --all-features cargo test --lib --tests -p=actix-web-actors --all-features
- name: Clear the cargo caches - name: CI cache clean
run: | run: cargo-ci-cache-clean
cargo --locked install cargo-cache --version 0.8.3 --no-default-features --features ci-autoclean
cargo-cache
ci_feature_powerset_check: ci_feature_powerset_check:
name: Verify Feature Combinations name: Verify Feature Combinations
@ -85,7 +85,7 @@ jobs:
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
- name: Install cargo-hack - name: Install cargo-hack
uses: taiki-e/install-action@v2.25.9 uses: taiki-e/install-action@v2.26.12
with: with:
tool: cargo-hack tool: cargo-hack
@ -106,7 +106,7 @@ jobs:
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
- name: Install nextest - name: Install nextest
uses: taiki-e/install-action@v2.25.9 uses: taiki-e/install-action@v2.26.12
with: with:
tool: nextest tool: nextest

View file

@ -26,7 +26,7 @@ jobs:
- { 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-latest, triple: x86_64-pc-windows-msvc }
version: version:
- { name: msrv, version: 1.68.0 } - { name: msrv, version: 1.72.0 }
- { name: stable, version: stable } - { name: stable, version: stable }
name: ${{ matrix.target.name }} / ${{ matrix.version.name }} name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
@ -37,29 +37,27 @@ jobs:
- name: Install OpenSSL - name: Install OpenSSL
if: matrix.target.os == 'windows-latest' if: matrix.target.os == 'windows-latest'
run: choco install openssl -y --forcex64 --no-progress shell: bash
- name: Set OpenSSL dir in env
if: matrix.target.os == 'windows-latest'
run: | run: |
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append set -e
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append choco install openssl --version=1.1.1.2100 -y --no-progress
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
- name: Install Rust (${{ matrix.version.name }}) - name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with: with:
toolchain: ${{ matrix.version.version }} toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack - name: Install cargo-hack and cargo-ci-cache-clean
uses: taiki-e/install-action@v2.25.9 uses: taiki-e/install-action@v2.26.12
with: with:
tool: cargo-hack tool: cargo-hack,cargo-ci-cache-clean
- name: workaround MSRV issues - name: workaround MSRV issues
if: matrix.version.name == 'msrv' if: matrix.version.name == 'msrv'
run: | run: |
cargo update -p=clap --precise=4.3.24 cargo update -p=clap --precise=4.4.18
cargo update -p=clap_lex --precise=0.5.0
cargo update -p=anstyle --precise=1.0.2
- name: check minimal - name: check minimal
run: cargo ci-check-min run: cargo ci-check-min
@ -69,10 +67,12 @@ jobs:
- name: tests - name: tests
timeout-minutes: 60 timeout-minutes: 60
shell: bash
run: | run: |
set -e
cargo test --lib --tests -p=actix-router --all-features cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,rustls-0_22,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features cargo test --lib --tests -p=actix-http-test --all-features
@ -81,10 +81,8 @@ jobs:
cargo test --lib --tests -p=actix-multipart --all-features cargo test --lib --tests -p=actix-multipart --all-features
cargo test --lib --tests -p=actix-web-actors --all-features cargo test --lib --tests -p=actix-web-actors --all-features
- name: Clear the cargo caches - name: CI cache clean
run: | run: cargo-ci-cache-clean
cargo --locked install cargo-cache --version 0.8.3 --no-default-features --features ci-autoclean
cargo-cache
io-uring: io-uring:
name: io-uring tests name: io-uring tests

View file

@ -23,7 +23,7 @@ jobs:
components: llvm-tools-preview components: llvm-tools-preview
- name: Install cargo-llvm-cov - name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2.25.9 uses: taiki-e/install-action@v2.26.12
with: with:
tool: cargo-llvm-cov tool: cargo-llvm-cov
@ -31,7 +31,9 @@ jobs:
run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v3.1.4 uses: codecov/codecov-action@v4.0.1
with: with:
files: codecov.json files: codecov.json
fail_ci_if_error: true fail_ci_if_error: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View file

@ -82,7 +82,7 @@ jobs:
toolchain: nightly-2023-08-25 toolchain: nightly-2023-08-25
- name: Install cargo-public-api - name: Install cargo-public-api
uses: taiki-e/install-action@v2.24.1 uses: taiki-e/install-action@v2.26.12
with: with:
tool: cargo-public-api tool: cargo-public-api

View file

@ -17,7 +17,7 @@ members = [
[workspace.package] [workspace.package]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.68" rust-version = "1.72"
[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

@ -2,6 +2,8 @@
## Unreleased ## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 0.6.5 ## 0.6.5
- Fix handling of special characters in filenames. - Fix handling of special characters in filenames.

View file

@ -4,7 +4,7 @@
[![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.5)](https://docs.rs/actix-files/0.6.5) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.5)](https://docs.rs/actix-files/0.6.5)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![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.5/status.svg)](https://deps.rs/crate/actix-files/0.6.5) [![dependency status](https://deps.rs/crate/actix-files/0.6.5/status.svg)](https://deps.rs/crate/actix-files/0.6.5)

View file

@ -568,6 +568,7 @@ mod tests {
assert_eq!(bytes, data); assert_eq!(bytes, data);
} }
#[cfg(not(target_os = "windows"))]
#[actix_rt::test] #[actix_rt::test]
async fn test_static_files_with_special_characters() { async fn test_static_files_with_special_characters() {
// Create the file we want to test against ad-hoc. We can't check it in as otherwise // Create the file we want to test against ad-hoc. We can't check it in as otherwise

View file

@ -2,6 +2,10 @@
## Unreleased ## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 3.2.0
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency. - Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
## 3.1.0 ## 3.1.0

View file

@ -1,6 +1,6 @@
[package] [package]
name = "actix-http-test" name = "actix-http-test"
version = "3.1.0" version = "3.2.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Various helpers for Actix applications to use during testing" description = "Various helpers for Actix applications to use during testing"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]

View file

@ -5,17 +5,12 @@
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[![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.1.0)](https://docs.rs/actix-http-test/3.1.0) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.2.0)](https://docs.rs/actix-http-test/3.2.0)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![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.1.0/status.svg)](https://deps.rs/crate/actix-http-test/3.1.0) [![Dependency Status](https://deps.rs/crate/actix-http-test/3.2.0/status.svg)](https://deps.rs/crate/actix-http-test/3.2.0)
[![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http-test)
- Minimum Supported Rust Version (MSRV): 1.68

View file

@ -2,6 +2,18 @@
## Unreleased ## Unreleased
### Changed
- Minimum supported Rust version (MSRV) is now 1.72.
## 3.6.0
### Added
- Add `rustls-0_22` crate feature.
- Add `{h1::H1Service, h2::H2Service, HttpService}::rustls_0_22()` and `HttpService::rustls_0_22_with_config()` service constructors.
- Implement `From<&HeaderMap>` for `http::HeaderMap`.
## 3.5.1 ## 3.5.1
### Fixed ### Fixed

View file

@ -1,6 +1,6 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "3.5.1" version = "3.6.0"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>", "Rob Ede <robjtede@icloud.com>",
@ -20,8 +20,18 @@ edition.workspace = true
rust-version.workspace = true rust-version.workspace = true
[package.metadata.docs.rs] [package.metadata.docs.rs]
# features that docs.rs will build with rustdoc-args = ["--cfg", "docsrs"]
features = ["http2", "ws", "openssl", "rustls-0_20", "rustls-0_21", "compress-brotli", "compress-gzip", "compress-zstd"] features = [
"http2",
"ws",
"openssl",
"rustls-0_20",
"rustls-0_21",
"rustls-0_22",
"compress-brotli",
"compress-gzip",
"compress-zstd",
]
[lib] [lib]
name = "actix_http" name = "actix_http"
@ -53,6 +63,9 @@ rustls-0_20 = ["actix-tls/accept", "actix-tls/rustls-0_20"]
# TLS via Rustls v0.21 # TLS via Rustls v0.21
rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"] rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"]
# TLS via Rustls v0.22
rustls-0_22 = ["actix-tls/accept", "actix-tls/rustls-0_22"]
# Compression codecs # Compression codecs
compress-brotli = ["__compress", "brotli"] compress-brotli = ["__compress", "brotli"]
compress-gzip = ["__compress", "flate2"] compress-gzip = ["__compress", "flate2"]
@ -98,7 +111,7 @@ rand = { version = "0.8", optional = true }
sha1 = { version = "0.10", optional = true } sha1 = { version = "0.10", optional = true }
# openssl/rustls # openssl/rustls
actix-tls = { version = "3.1", default-features = false, optional = true } actix-tls = { version = "3.3", default-features = false, optional = true }
# compress-* # compress-*
brotli = { version = "3.3.3", optional = true } brotli = { version = "3.3.3", optional = true }
@ -108,35 +121,40 @@ zstd = { version = "0.13", optional = true }
[dev-dependencies] [dev-dependencies]
actix-http-test = { version = "3", features = ["openssl"] } actix-http-test = { version = "3", features = ["openssl"] }
actix-server = "2" actix-server = "2"
actix-tls = { version = "3.1", features = ["openssl"] } actix-tls = { version = "3.3", features = ["openssl", "rustls-0_22-webpki-roots"] }
actix-web = "4" actix-web = "4"
async-stream = "0.3" async-stream = "0.3"
criterion = { version = "0.5", features = ["html_reports"] } criterion = { version = "0.5", features = ["html_reports"] }
divan = "0.1.8"
env_logger = "0.10" env_logger = "0.10"
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
memchr = "2.4" memchr = "2.4"
once_cell = "1.9" once_cell = "1.9"
rcgen = "0.11" rcgen = "0.12"
regex = "1.3" regex = "1.3"
rustversion = "1" rustversion = "1"
rustls-pemfile = "1" rustls-pemfile = "2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
static_assertions = "1" static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.55" } tls-openssl = { package = "openssl", version = "0.10.55" }
tls-rustls_021 = { package = "rustls", version = "0.21" } tls-rustls_022 = { package = "rustls", version = "0.22" }
tokio = { version = "1.24.2", features = ["net", "rt", "macros"] } tokio = { version = "1.24.2", features = ["net", "rt", "macros"] }
[[example]] [[example]]
name = "ws" name = "ws"
required-features = ["ws", "rustls-0_21"] required-features = ["ws", "rustls-0_22"]
[[example]] [[example]]
name = "tls_rustls" name = "tls_rustls"
required-features = ["http2", "rustls-0_21"] required-features = ["http2", "rustls-0_22"]
[[bench]] [[bench]]
name = "response-body-compression" name = "response-body-compression"
harness = false harness = false
required-features = ["compress-brotli", "compress-gzip", "compress-zstd"] required-features = ["compress-brotli", "compress-gzip", "compress-zstd"]
[[bench]]
name = "date-formatting"
harness = false

View file

@ -5,21 +5,16 @@
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[![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.5.1)](https://docs.rs/actix-http/3.5.1) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.6.0)](https://docs.rs/actix-http/3.6.0)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![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.5.1/status.svg)](https://deps.rs/crate/actix-http/3.5.1) [![dependency status](https://deps.rs/crate/actix-http/3.6.0/status.svg)](https://deps.rs/crate/actix-http/3.6.0)
[![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)
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http)
- Minimum Supported Rust Version (MSRV): 1.68
## Examples ## Examples
```rust ```rust

View file

@ -0,0 +1,20 @@
use std::time::SystemTime;
use actix_http::header::HttpDate;
use divan::{black_box, AllocProfiler, Bencher};
#[global_allocator]
static ALLOC: AllocProfiler = AllocProfiler::system();
#[divan::bench]
fn date_formatting(b: Bencher<'_, '_>) {
let now = SystemTime::now();
b.bench(|| {
black_box(HttpDate::from(black_box(now)).to_string());
})
}
fn main() {
divan::main();
}

View file

@ -8,7 +8,7 @@
use std::{convert::Infallible, io}; use std::{convert::Infallible, io};
use actix_http::{HttpService, Request, Response, StatusCode}; use actix_http::{body::BodyStream, HttpService, Request, Response, StatusCode};
use actix_server::Server; use actix_server::Server;
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
@ -19,7 +19,12 @@ async fn main() -> io::Result<()> {
.bind("h2c-detect", ("127.0.0.1", 8080), || { .bind("h2c-detect", ("127.0.0.1", 8080), || {
HttpService::build() HttpService::build()
.finish(|_req: Request| async move { .finish(|_req: Request| async move {
Ok::<_, Infallible>(Response::build(StatusCode::OK).body("Hello!")) Ok::<_, Infallible>(Response::build(StatusCode::OK).body(BodyStream::new(
futures_util::stream::iter([
Ok::<_, String>("123".into()),
Err("wertyuikmnbvcxdfty6t".to_owned()),
]),
)))
}) })
.tcp_auto_h2c() .tcp_auto_h2c()
})? })?

View file

@ -12,7 +12,7 @@
//! Protocol: HTTP/1.1 //! Protocol: HTTP/1.1
//! ``` //! ```
extern crate tls_rustls_021 as rustls; extern crate tls_rustls_022 as rustls;
use std::io; use std::io;
@ -36,7 +36,7 @@ async fn main() -> io::Result<()> {
); );
ok::<_, Error>(Response::ok().set_body(body)) ok::<_, Error>(Response::ok().set_body(body))
}) })
.rustls_021(rustls_config()) .rustls_0_22(rustls_config())
})? })?
.run() .run()
.await .await
@ -51,16 +51,18 @@ fn rustls_config() -> rustls::ServerConfig {
let key_file = &mut io::BufReader::new(key_file.as_bytes()); let key_file = &mut io::BufReader::new(key_file.as_bytes());
let cert_chain = rustls_pemfile::certs(cert_file) let cert_chain = rustls_pemfile::certs(cert_file)
.unwrap() .collect::<Result<Vec<_>, _>>()
.into_iter() .unwrap();
.map(rustls::Certificate) let mut keys = rustls_pemfile::pkcs8_private_keys(key_file)
.collect(); .collect::<Result<Vec<_>, _>>()
let mut keys = rustls_pemfile::pkcs8_private_keys(key_file).unwrap(); .unwrap();
let mut config = rustls::ServerConfig::builder() let mut config = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth() .with_no_client_auth()
.with_single_cert(cert_chain, rustls::PrivateKey(keys.remove(0))) .with_single_cert(
cert_chain,
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
)
.unwrap(); .unwrap();
const H1_ALPN: &[u8] = b"http/1.1"; const H1_ALPN: &[u8] = b"http/1.1";

View file

@ -1,7 +1,7 @@
//! Sets up a WebSocket server over TCP and TLS. //! Sets up a WebSocket server over TCP and TLS.
//! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames. //! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames.
extern crate tls_rustls_021 as rustls; extern crate tls_rustls_022 as rustls;
use std::{ use std::{
io, io,
@ -30,7 +30,7 @@ async fn main() -> io::Result<()> {
.bind("tls", ("127.0.0.1", 8443), || { .bind("tls", ("127.0.0.1", 8443), || {
HttpService::build() HttpService::build()
.finish(handler) .finish(handler)
.rustls_021(tls_config()) .rustls_0_22(tls_config())
})? })?
.run() .run()
.await .await
@ -85,7 +85,6 @@ impl Stream for Heartbeat {
fn tls_config() -> rustls::ServerConfig { fn tls_config() -> rustls::ServerConfig {
use std::io::BufReader; use std::io::BufReader;
use rustls::{Certificate, PrivateKey};
use rustls_pemfile::{certs, pkcs8_private_keys}; use rustls_pemfile::{certs, pkcs8_private_keys};
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
@ -95,17 +94,17 @@ fn tls_config() -> rustls::ServerConfig {
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) let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap();
.unwrap() let mut keys = pkcs8_private_keys(key_file)
.into_iter() .collect::<Result<Vec<_>, _>>()
.map(Certificate) .unwrap();
.collect();
let mut keys = pkcs8_private_keys(key_file).unwrap();
let mut config = rustls::ServerConfig::builder() let mut config = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth() .with_no_client_auth()
.with_single_cert(cert_chain, PrivateKey(keys.remove(0))) .with_single_cert(
cert_chain,
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
)
.unwrap(); .unwrap();
config.alpn_protocols.push(b"http/1.1".to_vec()); config.alpn_protocols.push(b"http/1.1".to_vec());

View file

@ -28,7 +28,7 @@ impl Date {
fn update(&mut self) { fn update(&mut self) {
self.pos = 0; self.pos = 0;
write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap(); write!(self, "{}", httpdate::HttpDate::from(SystemTime::now())).unwrap();
} }
} }

View file

@ -512,8 +512,10 @@ where
} }
Poll::Ready(Some(Err(err))) => { Poll::Ready(Some(Err(err))) => {
let err = err.into();
tracing::error!("Response payload stream error: {err:?}");
this.flags.insert(Flags::FINISHED); this.flags.insert(Flags::FINISHED);
return Err(DispatchError::Body(err.into())); return Err(DispatchError::Body(err));
} }
Poll::Pending => return Ok(PollResponse::DoNothing), Poll::Pending => return Ok(PollResponse::DoNothing),
@ -549,6 +551,7 @@ where
} }
Poll::Ready(Some(Err(err))) => { Poll::Ready(Some(Err(err))) => {
tracing::error!("Response payload stream error: {err:?}");
this.flags.insert(Flags::FINISHED); this.flags.insert(Flags::FINISHED);
return Err(DispatchError::Body( return Err(DispatchError::Body(
Error::new_body().with_cause(err).into(), Error::new_body().with_cause(err).into(),
@ -703,7 +706,7 @@ where
req.head_mut().peer_addr = *this.peer_addr; req.head_mut().peer_addr = *this.peer_addr;
req.conn_data = this.conn_data.as_ref().map(Rc::clone); req.conn_data = this.conn_data.clone();
match this.codec.message_type() { match this.codec.message_type() {
// request has no payload // request has no payload

View file

@ -153,7 +153,7 @@ mod openssl {
} }
#[cfg(feature = "rustls-0_20")] #[cfg(feature = "rustls-0_20")]
mod rustls_020 { mod rustls_0_20 {
use std::io; use std::io;
use actix_service::ServiceFactoryExt as _; use actix_service::ServiceFactoryExt as _;
@ -214,7 +214,7 @@ mod rustls_020 {
} }
#[cfg(feature = "rustls-0_21")] #[cfg(feature = "rustls-0_21")]
mod rustls_021 { mod rustls_0_21 {
use std::io; use std::io;
use actix_service::ServiceFactoryExt as _; use actix_service::ServiceFactoryExt as _;
@ -274,6 +274,67 @@ mod rustls_021 {
} }
} }
#[cfg(feature = "rustls-0_22")]
mod rustls_0_22 {
use std::io;
use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{
rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError,
};
use super::*;
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
(Request, Framed<TlsStream<TcpStream>, Codec>),
Config = (),
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create Rustls v0.22 based service.
pub fn rustls_0_22(
self,
config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = (),
> {
Acceptor::new(config)
.map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init")
})
.map_err(TlsError::into_service_error)
.map(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
(io, peer_addr)
})
.and_then(self.map_err(TlsError::Service))
}
}
}
impl<T, S, B, X, U> H1Service<T, S, B, X, U> impl<T, S, B, X, U> H1Service<T, S, B, X, U>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,

View file

@ -4,7 +4,7 @@ use std::{
future::Future, future::Future,
marker::PhantomData, marker::PhantomData,
net, net,
pin::Pin, pin::{pin, Pin},
rc::Rc, rc::Rc,
task::{Context, Poll}, task::{Context, Poll},
}; };
@ -20,7 +20,6 @@ use h2::{
Ping, PingPong, Ping, PingPong,
}; };
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use tracing::{error, trace, warn};
use crate::{ use crate::{
body::{BodySize, BoxBody, MessageBody}, body::{BodySize, BoxBody, MessageBody},
@ -127,7 +126,7 @@ where
head.headers = parts.headers.into(); head.headers = parts.headers.into();
head.peer_addr = this.peer_addr; head.peer_addr = this.peer_addr;
req.conn_data = this.conn_data.as_ref().map(Rc::clone); req.conn_data = this.conn_data.clone();
let fut = this.flow.service.call(req); let fut = this.flow.service.call(req);
let config = this.config.clone(); let config = this.config.clone();
@ -147,11 +146,13 @@ where
if let Err(err) = res { if let Err(err) = res {
match err { match err {
DispatchError::SendResponse(err) => { DispatchError::SendResponse(err) => {
trace!("Error sending HTTP/2 response: {:?}", err) tracing::trace!("Error sending response: {err:?}");
}
DispatchError::SendData(err) => {
tracing::warn!("Send data error: {err:?}");
} }
DispatchError::SendData(err) => warn!("{:?}", err),
DispatchError::ResponseBody(err) => { DispatchError::ResponseBody(err) => {
error!("Response payload stream error: {:?}", err) tracing::error!("Response payload stream error: {err:?}");
} }
} }
} }
@ -228,9 +229,9 @@ where
return Ok(()); return Ok(());
} }
// poll response body and send chunks to client let mut body = pin!(body);
actix_rt::pin!(body);
// poll response body and send chunks to client
while let Some(res) = poll_fn(|cx| body.as_mut().poll_next(cx)).await { while let Some(res) = poll_fn(|cx| body.as_mut().poll_next(cx)).await {
let mut chunk = res.map_err(|err| DispatchError::ResponseBody(err.into()))?; let mut chunk = res.map_err(|err| DispatchError::ResponseBody(err.into()))?;

View file

@ -141,7 +141,7 @@ mod openssl {
} }
#[cfg(feature = "rustls-0_20")] #[cfg(feature = "rustls-0_20")]
mod rustls_020 { mod rustls_0_20 {
use std::io; use std::io;
use actix_service::ServiceFactoryExt as _; use actix_service::ServiceFactoryExt as _;
@ -192,7 +192,7 @@ mod rustls_020 {
} }
#[cfg(feature = "rustls-0_21")] #[cfg(feature = "rustls-0_21")]
mod rustls_021 { mod rustls_0_21 {
use std::io; use std::io;
use actix_service::ServiceFactoryExt as _; use actix_service::ServiceFactoryExt as _;
@ -242,6 +242,57 @@ mod rustls_021 {
} }
} }
#[cfg(feature = "rustls-0_22")]
mod rustls_0_22 {
use std::io;
use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{
rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError,
};
use super::*;
impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
/// Create Rustls v0.22 based service.
pub fn rustls_0_22(
self,
mut config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = S::InitError,
> {
let mut protos = vec![b"h2".to_vec()];
protos.extend_from_slice(&config.alpn_protocols);
config.alpn_protocols = protos;
Acceptor::new(config)
.map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init")
})
.map_err(TlsError::into_service_error)
.map(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
(io, peer_addr)
})
.and_then(self.map_err(TlsError::Service))
}
}
}
impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B> impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B>
where where
T: AsyncRead + AsyncWrite + Unpin + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,

View file

@ -650,6 +650,13 @@ impl From<HeaderMap> for http::HeaderMap {
} }
} }
/// Convert our `&HeaderMap` to a `http::HeaderMap`.
impl From<&HeaderMap> for http::HeaderMap {
fn from(map: &HeaderMap) -> Self {
map.to_owned().into()
}
}
/// Iterator over removed, owned values with the same associated name. /// Iterator over removed, owned values with the same associated name.
/// ///
/// Returned from methods that remove or replace items. See [`HeaderMap::insert`] /// Returned from methods that remove or replace items. See [`HeaderMap::insert`]

View file

@ -24,8 +24,7 @@ impl FromStr for HttpDate {
impl fmt::Display for HttpDate { impl fmt::Display for HttpDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let date_str = httpdate::fmt_http_date(self.0); httpdate::HttpDate::from(self.0).fmt(f)
f.write_str(&date_str)
} }
} }
@ -37,7 +36,7 @@ impl TryIntoHeaderValue for HttpDate {
let mut wrt = MutWriter(&mut buf); let mut wrt = MutWriter(&mut buf);
// unwrap: date output is known to be well formed and of known length // unwrap: date output is known to be well formed and of known length
write!(wrt, "{}", httpdate::fmt_http_date(self.0)).unwrap(); write!(wrt, "{}", self).unwrap();
HeaderValue::from_maybe_shared(buf.split().freeze()) HeaderValue::from_maybe_shared(buf.split().freeze())
} }

View file

@ -80,18 +80,18 @@ mod tests {
#[test] #[test]
fn comma_delimited_parsing() { fn comma_delimited_parsing() {
let headers = vec![]; let headers = [];
let res: Vec<usize> = from_comma_delimited(headers.iter()).unwrap(); let res: Vec<usize> = from_comma_delimited(headers.iter()).unwrap();
assert_eq!(res, vec![0; 0]); assert_eq!(res, vec![0; 0]);
let headers = vec![ let headers = [
HeaderValue::from_static("1, 2"), HeaderValue::from_static("1, 2"),
HeaderValue::from_static("3,4"), HeaderValue::from_static("3,4"),
]; ];
let res: Vec<usize> = from_comma_delimited(headers.iter()).unwrap(); let res: Vec<usize> = from_comma_delimited(headers.iter()).unwrap();
assert_eq!(res, vec![1, 2, 3, 4]); assert_eq!(res, vec![1, 2, 3, 4]);
let headers = vec![ let headers = [
HeaderValue::from_static(""), HeaderValue::from_static(""),
HeaderValue::from_static(","), HeaderValue::from_static(","),
HeaderValue::from_static(" "), HeaderValue::from_static(" "),

View file

@ -58,7 +58,12 @@ pub mod ws;
#[allow(deprecated)] #[allow(deprecated)]
pub use self::payload::PayloadStream; pub use self::payload::PayloadStream;
#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))] #[cfg(any(
feature = "openssl",
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
))]
pub use self::service::TlsAcceptorConfig; pub use self::service::TlsAcceptorConfig;
pub use self::{ pub use self::{
builder::HttpServiceBuilder, builder::HttpServiceBuilder,

View file

@ -5,7 +5,7 @@
use std::cell::RefCell; use std::cell::RefCell;
thread_local! { thread_local! {
static NOTIFY_DROPPED: RefCell<Option<bool>> = RefCell::new(None); static NOTIFY_DROPPED: RefCell<Option<bool>> = const { RefCell::new(None) };
} }
/// Check if the spawned task is dropped. /// Check if the spawned task is dropped.

View file

@ -241,13 +241,23 @@ where
} }
/// Configuration options used when accepting TLS connection. /// Configuration options used when accepting TLS connection.
#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))] #[cfg(any(
feature = "openssl",
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
))]
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct TlsAcceptorConfig { pub struct TlsAcceptorConfig {
pub(crate) handshake_timeout: Option<std::time::Duration>, pub(crate) handshake_timeout: Option<std::time::Duration>,
} }
#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))] #[cfg(any(
feature = "openssl",
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
))]
impl TlsAcceptorConfig { impl TlsAcceptorConfig {
/// Set TLS handshake timeout duration. /// Set TLS handshake timeout duration.
pub fn handshake_timeout(self, dur: std::time::Duration) -> Self { pub fn handshake_timeout(self, dur: std::time::Duration) -> Self {
@ -353,12 +363,12 @@ mod openssl {
} }
#[cfg(feature = "rustls-0_20")] #[cfg(feature = "rustls-0_20")]
mod rustls_020 { mod rustls_0_20 {
use std::io; use std::io;
use actix_service::ServiceFactoryExt as _; use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{ use actix_tls::accept::{
rustls::{reexports::ServerConfig, Acceptor, TlsStream}, rustls_0_20::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError, TlsError,
}; };
@ -389,7 +399,7 @@ mod rustls_020 {
U::Error: fmt::Display + Into<Response<BoxBody>>, U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
/// Create Rustls based service. /// Create Rustls v0.20 based service.
pub fn rustls( pub fn rustls(
self, self,
config: ServerConfig, config: ServerConfig,
@ -403,7 +413,7 @@ mod rustls_020 {
self.rustls_with_config(config, TlsAcceptorConfig::default()) self.rustls_with_config(config, TlsAcceptorConfig::default())
} }
/// Create Rustls based service with custom TLS acceptor configuration. /// Create Rustls v0.20 based service with custom TLS acceptor configuration.
pub fn rustls_with_config( pub fn rustls_with_config(
self, self,
mut config: ServerConfig, mut config: ServerConfig,
@ -449,7 +459,7 @@ mod rustls_020 {
} }
#[cfg(feature = "rustls-0_21")] #[cfg(feature = "rustls-0_21")]
mod rustls_021 { mod rustls_0_21 {
use std::io; use std::io;
use actix_service::ServiceFactoryExt as _; use actix_service::ServiceFactoryExt as _;
@ -485,7 +495,7 @@ mod rustls_021 {
U::Error: fmt::Display + Into<Response<BoxBody>>, U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
/// Create Rustls based service. /// Create Rustls v0.21 based service.
pub fn rustls_021( pub fn rustls_021(
self, self,
config: ServerConfig, config: ServerConfig,
@ -499,7 +509,7 @@ mod rustls_021 {
self.rustls_021_with_config(config, TlsAcceptorConfig::default()) self.rustls_021_with_config(config, TlsAcceptorConfig::default())
} }
/// Create Rustls based service with custom TLS acceptor configuration. /// Create Rustls v0.21 based service with custom TLS acceptor configuration.
pub fn rustls_021_with_config( pub fn rustls_021_with_config(
self, self,
mut config: ServerConfig, mut config: ServerConfig,
@ -544,6 +554,102 @@ mod rustls_021 {
} }
} }
#[cfg(feature = "rustls-0_22")]
mod rustls_0_22 {
use std::io;
use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{
rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError,
};
use super::*;
impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
(Request, Framed<TlsStream<TcpStream>, h1::Codec>),
Config = (),
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create Rustls v0.22 based service.
pub fn rustls_0_22(
self,
config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = (),
> {
self.rustls_0_22_with_config(config, TlsAcceptorConfig::default())
}
/// Create Rustls v0.22 based service with custom TLS acceptor configuration.
pub fn rustls_0_22_with_config(
self,
mut config: ServerConfig,
tls_acceptor_config: TlsAcceptorConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = (),
> {
let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
protos.extend_from_slice(&config.alpn_protocols);
config.alpn_protocols = protos;
let mut acceptor = Acceptor::new(config);
if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout {
acceptor.set_handshake_timeout(handshake_timeout);
}
acceptor
.map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init")
})
.map_err(TlsError::into_service_error)
.and_then(|io: TlsStream<TcpStream>| async {
let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
if protos.windows(2).any(|window| window == b"h2") {
Protocol::Http2
} else {
Protocol::Http1
}
} else {
Protocol::Http1
};
let peer_addr = io.get_ref().0.peer_addr().ok();
Ok((io, proto, peer_addr))
})
.and_then(self.map_err(TlsError::Service))
}
}
}
impl<T, S, B, X, U> ServiceFactory<(T, Protocol, Option<net::SocketAddr>)> impl<T, S, B, X, U> ServiceFactory<(T, Protocol, Option<net::SocketAddr>)>
for HttpService<T, S, B, X, U> for HttpService<T, S, B, X, U>
where where

View file

@ -1,6 +1,6 @@
#![cfg(feature = "rustls-0_21")] #![cfg(feature = "rustls-0_22")]
extern crate tls_rustls_021 as rustls; extern crate tls_rustls_022 as rustls;
use std::{ use std::{
convert::Infallible, convert::Infallible,
@ -20,13 +20,13 @@ use actix_http::{
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_rt::pin; use actix_rt::pin;
use actix_service::{fn_factory_with_config, fn_service}; use actix_service::{fn_factory_with_config, fn_service};
use actix_tls::connect::rustls_0_21::webpki_roots_cert_store; use actix_tls::connect::rustls_0_22::webpki_roots_cert_store;
use actix_utils::future::{err, ok, poll_fn}; use actix_utils::future::{err, ok, poll_fn};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use derive_more::{Display, Error}; use derive_more::{Display, Error};
use futures_core::{ready, Stream}; use futures_core::{ready, Stream};
use futures_util::stream::once; use futures_util::stream::once;
use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName}; use rustls::{pki_types::ServerName, ServerConfig as RustlsServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys}; use rustls_pemfile::{certs, pkcs8_private_keys};
async fn load_body<S>(stream: S) -> Result<BytesMut, PayloadError> async fn load_body<S>(stream: S) -> Result<BytesMut, PayloadError>
@ -59,17 +59,17 @@ fn tls_config() -> RustlsServerConfig {
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) let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap();
.unwrap() let mut keys = pkcs8_private_keys(key_file)
.into_iter() .collect::<Result<Vec<_>, _>>()
.map(Certificate) .unwrap();
.collect();
let mut keys = pkcs8_private_keys(key_file).unwrap();
let mut config = RustlsServerConfig::builder() let mut config = RustlsServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth() .with_no_client_auth()
.with_single_cert(cert_chain, PrivateKey(keys.remove(0))) .with_single_cert(
cert_chain,
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
)
.unwrap(); .unwrap();
config.alpn_protocols.push(HTTP1_1_ALPN_PROTOCOL.to_vec()); config.alpn_protocols.push(HTTP1_1_ALPN_PROTOCOL.to_vec());
@ -83,7 +83,6 @@ pub fn get_negotiated_alpn_protocol(
client_alpn_protocol: &[u8], client_alpn_protocol: &[u8],
) -> Option<Vec<u8>> { ) -> Option<Vec<u8>> {
let mut config = rustls::ClientConfig::builder() let mut config = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(webpki_roots_cert_store()) .with_root_certificates(webpki_roots_cert_store())
.with_no_client_auth(); .with_no_client_auth();
@ -109,7 +108,7 @@ async fn h1() -> io::Result<()> {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, Error>(Response::ok())) .h1(|_| ok::<_, Error>(Response::ok()))
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -123,7 +122,7 @@ async fn h2() -> io::Result<()> {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, Error>(Response::ok())) .h2(|_| ok::<_, Error>(Response::ok()))
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -141,7 +140,7 @@ async fn h1_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_11); assert_eq!(req.version(), Version::HTTP_11);
ok::<_, Error>(Response::ok()) ok::<_, Error>(Response::ok())
}) })
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -159,7 +158,7 @@ async fn h2_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_2); assert_eq!(req.version(), Version::HTTP_2);
ok::<_, Error>(Response::ok()) ok::<_, Error>(Response::ok())
}) })
.rustls_021_with_config( .rustls_0_22_with_config(
tls_config(), tls_config(),
TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)), TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)),
) )
@ -180,7 +179,7 @@ async fn h2_body1() -> io::Result<()> {
let body = load_body(req.take_payload()).await?; let body = load_body(req.take_payload()).await?;
Ok::<_, Error>(Response::ok().set_body(body)) Ok::<_, Error>(Response::ok().set_body(body))
}) })
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -206,7 +205,7 @@ async fn h2_content_length() {
]; ];
ok::<_, Infallible>(Response::new(statuses[indx])) ok::<_, Infallible>(Response::new(statuses[indx]))
}) })
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -278,7 +277,7 @@ async fn h2_headers() {
} }
ok::<_, Infallible>(config.body(data.clone())) ok::<_, Infallible>(config.body(data.clone()))
}) })
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -317,7 +316,7 @@ async fn h2_body2() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -334,7 +333,7 @@ async fn h2_head_empty() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -360,7 +359,7 @@ async fn h2_head_binary() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -385,7 +384,7 @@ async fn h2_head_binary2() {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -411,7 +410,7 @@ async fn h2_body_length() {
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
) )
}) })
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -435,7 +434,7 @@ async fn h2_body_chunked_explicit() {
.body(BodyStream::new(body)), .body(BodyStream::new(body)),
) )
}) })
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -464,7 +463,7 @@ async fn h2_response_http_error_handling() {
) )
})) }))
})) }))
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -494,7 +493,7 @@ async fn h2_service_error() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| err::<Response<BoxBody>, _>(BadRequest)) .h2(|_| err::<Response<BoxBody>, _>(BadRequest))
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -511,7 +510,7 @@ async fn h1_service_error() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h1(|_| err::<Response<BoxBody>, _>(BadRequest)) .h1(|_| err::<Response<BoxBody>, _>(BadRequest))
.rustls_021(tls_config()) .rustls_0_22(tls_config())
}) })
.await; .await;
@ -534,7 +533,7 @@ async fn alpn_h1() -> io::Result<()> {
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
HttpService::build() HttpService::build()
.h1(|_| ok::<_, Error>(Response::ok())) .h1(|_| ok::<_, Error>(Response::ok()))
.rustls_021(config) .rustls_0_22(config)
}) })
.await; .await;
@ -556,7 +555,7 @@ async fn alpn_h2() -> io::Result<()> {
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
HttpService::build() HttpService::build()
.h2(|_| ok::<_, Error>(Response::ok())) .h2(|_| ok::<_, Error>(Response::ok()))
.rustls_021(config) .rustls_0_22(config)
}) })
.await; .await;
@ -582,7 +581,7 @@ async fn alpn_h2_1() -> io::Result<()> {
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
HttpService::build() HttpService::build()
.finish(|_| ok::<_, Error>(Response::ok())) .finish(|_| ok::<_, Error>(Response::ok()))
.rustls_021(config) .rustls_0_22(config)
}) })
.await; .await;

View file

@ -2,6 +2,8 @@
## Unreleased ## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 0.6.1 ## 0.6.1
- Update `syn` dependency to `2`. - Update `syn` dependency to `2`.

View file

@ -6,7 +6,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-multipart-derive?label=latest)](https://crates.io/crates/actix-multipart-derive) [![crates.io](https://img.shields.io/crates/v/actix-multipart-derive?label=latest)](https://crates.io/crates/actix-multipart-derive)
[![Documentation](https://docs.rs/actix-multipart-derive/badge.svg?version=0.6.1)](https://docs.rs/actix-multipart-derive/0.6.1) [![Documentation](https://docs.rs/actix-multipart-derive/badge.svg?version=0.6.1)](https://docs.rs/actix-multipart-derive/0.6.1)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart-derive.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart-derive.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-multipart-derive/0.6.1/status.svg)](https://deps.rs/crate/actix-multipart-derive/0.6.1) [![dependency status](https://deps.rs/crate/actix-multipart-derive/0.6.1/status.svg)](https://deps.rs/crate/actix-multipart-derive/0.6.1)
@ -14,8 +14,3 @@
[![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)
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart-derive)
- Minimum Supported Rust Version (MSRV): 1.68

View file

@ -1,4 +1,4 @@
#[rustversion::stable(1.68)] // MSRV #[rustversion::stable(1.72)] // MSRV
#[test] #[test]
fn compile_macros() { fn compile_macros() {
let t = trybuild::TestCases::new(); let t = trybuild::TestCases::new();

View file

@ -2,6 +2,9 @@
## Unreleased ## Unreleased
- Add testing utilities under new module `test`.
- Minimum supported Rust version (MSRV) is now 1.72.
## 0.6.1 ## 0.6.1
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency. - Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.

View file

@ -35,6 +35,7 @@ local-waker = "0.1"
log = "0.4" log = "0.4"
memchr = "2.5" memchr = "2.5"
mime = "0.3" mime = "0.3"
rand = "0.8"
serde = "1" serde = "1"
serde_json = "1" serde_json = "1"
serde_plain = "1" serde_plain = "1"
@ -46,7 +47,9 @@ actix-http = "3"
actix-multipart-rfc7578 = "0.10" actix-multipart-rfc7578 = "0.10"
actix-rt = "2.2" actix-rt = "2.2"
actix-test = "0.1" actix-test = "0.1"
actix-web = "4"
awc = "3" awc = "3"
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
multer = "3"
tokio = { version = "1.24.2", features = ["sync"] } tokio = { version = "1.24.2", features = ["sync"] }
tokio-stream = "0.1" tokio-stream = "0.1"

View file

@ -6,7 +6,7 @@
[![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.6.1)](https://docs.rs/actix-multipart/0.6.1) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.6.1)](https://docs.rs/actix-multipart/0.6.1)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![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.6.1/status.svg)](https://deps.rs/crate/actix-multipart/0.6.1) [![dependency status](https://deps.rs/crate/actix-multipart/0.6.1/status.svg)](https://deps.rs/crate/actix-multipart/0.6.1)
@ -14,8 +14,3 @@
[![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)
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart)
- Minimum Supported Rust Version (MSRV): 1.68

View file

@ -131,14 +131,13 @@ impl Default for JsonConfig {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{collections::HashMap, io::Cursor}; use std::collections::HashMap;
use actix_multipart_rfc7578::client::multipart;
use actix_web::{http::StatusCode, web, App, HttpResponse, Responder}; use actix_web::{http::StatusCode, web, App, HttpResponse, Responder};
use bytes::Bytes;
use crate::form::{ use crate::form::{
json::{Json, JsonConfig}, json::{Json, JsonConfig},
tests::send_form,
MultipartForm, MultipartForm,
}; };
@ -155,6 +154,8 @@ mod tests {
HttpResponse::Ok().finish() HttpResponse::Ok().finish()
} }
const TEST_JSON: &str = r#"{"key1": "value1", "key2": "value2"}"#;
#[actix_rt::test] #[actix_rt::test]
async fn test_json_without_content_type() { async fn test_json_without_content_type() {
let srv = actix_test::start(|| { let srv = actix_test::start(|| {
@ -163,10 +164,16 @@ mod tests {
.app_data(JsonConfig::default().validate_content_type(false)) .app_data(JsonConfig::default().validate_content_type(false))
}); });
let mut form = multipart::Form::default(); let (body, headers) = crate::test::create_form_data_payload_and_headers(
form.add_text("json", "{\"key1\": \"value1\", \"key2\": \"value2\"}"); "json",
let response = send_form(&srv, form, "/").await; None,
assert_eq!(response.status(), StatusCode::OK); None,
Bytes::from_static(TEST_JSON.as_bytes()),
);
let mut req = srv.post("/");
*req.headers_mut() = headers;
let res = req.send_body(body).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
} }
#[actix_rt::test] #[actix_rt::test]
@ -178,17 +185,27 @@ mod tests {
}); });
// Deny because wrong content type // Deny because wrong content type
let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}"); let (body, headers) = crate::test::create_form_data_payload_and_headers(
let mut form = multipart::Form::default(); "json",
form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_OCTET_STREAM); None,
let response = send_form(&srv, form, "/").await; Some(mime::APPLICATION_OCTET_STREAM),
assert_eq!(response.status(), StatusCode::BAD_REQUEST); Bytes::from_static(TEST_JSON.as_bytes()),
);
let mut req = srv.post("/");
*req.headers_mut() = headers;
let res = req.send_body(body).await.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
// Allow because correct content type // Allow because correct content type
let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}"); let (body, headers) = crate::test::create_form_data_payload_and_headers(
let mut form = multipart::Form::default(); "json",
form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_JSON); None,
let response = send_form(&srv, form, "/").await; Some(mime::APPLICATION_JSON),
assert_eq!(response.status(), StatusCode::OK); Bytes::from_static(TEST_JSON.as_bytes()),
);
let mut req = srv.post("/");
*req.headers_mut() = headers;
let res = req.send_body(body).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
} }
} }

View file

@ -13,11 +13,14 @@ extern crate self as actix_multipart;
mod error; mod error;
mod extractor; mod extractor;
mod server;
pub mod form; pub mod form;
mod server;
pub mod test;
pub use self::{ pub use self::{
error::MultipartError, error::MultipartError,
server::{Field, Multipart}, server::{Field, Multipart},
test::{
create_form_data_payload_and_headers, create_form_data_payload_and_headers_with_boundary,
},
}; };

View file

@ -863,13 +863,15 @@ mod tests {
test::TestRequest, test::TestRequest,
FromRequest, FromRequest,
}; };
use bytes::Bytes; use bytes::{BufMut as _, Bytes};
use futures_util::{future::lazy, StreamExt as _}; use futures_util::{future::lazy, StreamExt as _};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_stream::wrappers::UnboundedReceiverStream;
use super::*; use super::*;
const BOUNDARY: &str = "abbc761f78ff4d7cb7573b5a23f96ef0";
#[actix_rt::test] #[actix_rt::test]
async fn test_boundary() { async fn test_boundary() {
let headers = HeaderMap::new(); let headers = HeaderMap::new();
@ -965,6 +967,26 @@ mod tests {
} }
fn create_simple_request_with_header() -> (Bytes, HeaderMap) { fn create_simple_request_with_header() -> (Bytes, HeaderMap) {
let (body, headers) = crate::test::create_form_data_payload_and_headers_with_boundary(
BOUNDARY,
"file",
Some("fn.txt".to_owned()),
Some(mime::TEXT_PLAIN_UTF_8),
Bytes::from_static(b"data"),
);
let mut buf = BytesMut::with_capacity(body.len() + 14);
// add junk before form to test pre-boundary data rejection
buf.put("testasdadsad\r\n".as_bytes());
buf.put(body);
(buf.freeze(), headers)
}
// TODO: use test utility when multi-file support is introduced
fn create_double_request_with_header() -> (Bytes, HeaderMap) {
let bytes = Bytes::from( let bytes = Bytes::from(
"testasdadsad\r\n\ "testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
@ -990,7 +1012,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_multipart_no_end_crlf() { async fn test_multipart_no_end_crlf() {
let (sender, payload) = create_stream(); let (sender, payload) = create_stream();
let (mut bytes, headers) = create_simple_request_with_header(); let (mut bytes, headers) = create_double_request_with_header();
let bytes_stripped = bytes.split_to(bytes.len()); // strip crlf let bytes_stripped = bytes.split_to(bytes.len()); // strip crlf
sender.send(Ok(bytes_stripped)).unwrap(); sender.send(Ok(bytes_stripped)).unwrap();
@ -1017,7 +1039,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_multipart() { async fn test_multipart() {
let (sender, payload) = create_stream(); let (sender, payload) = create_stream();
let (bytes, headers) = create_simple_request_with_header(); let (bytes, headers) = create_double_request_with_header();
sender.send(Ok(bytes)).unwrap(); sender.send(Ok(bytes)).unwrap();
@ -1080,7 +1102,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_stream() { async fn test_stream() {
let (bytes, headers) = create_simple_request_with_header(); let (bytes, headers) = create_double_request_with_header();
let payload = SlowStream::new(bytes); let payload = SlowStream::new(bytes);
let mut multipart = Multipart::new(&headers, payload); let mut multipart = Multipart::new(&headers, payload);
@ -1319,7 +1341,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_drop_field_awaken_multipart() { async fn test_drop_field_awaken_multipart() {
let (sender, payload) = create_stream(); let (sender, payload) = create_stream();
let (bytes, headers) = create_simple_request_with_header(); let (bytes, headers) = create_double_request_with_header();
sender.send(Ok(bytes)).unwrap(); sender.send(Ok(bytes)).unwrap();
drop(sender); // eof drop(sender); // eof

217
actix-multipart/src/test.rs Normal file
View file

@ -0,0 +1,217 @@
use actix_web::http::header::{self, HeaderMap};
use bytes::{BufMut as _, Bytes, BytesMut};
use mime::Mime;
use rand::{
distributions::{Alphanumeric, DistString as _},
thread_rng,
};
const CRLF: &[u8] = b"\r\n";
const CRLF_CRLF: &[u8] = b"\r\n\r\n";
const HYPHENS: &[u8] = b"--";
const BOUNDARY_PREFIX: &str = "------------------------";
/// Constructs a `multipart/form-data` payload from bytes and metadata.
///
/// Returned header map can be extended or merged with existing headers.
///
/// Multipart boundary used is a random alphanumeric string.
///
/// # Examples
///
/// ```
/// use actix_multipart::test::create_form_data_payload_and_headers;
/// use actix_web::test::TestRequest;
/// use bytes::Bytes;
/// use memchr::memmem::find;
///
/// let (body, headers) = create_form_data_payload_and_headers(
/// "foo",
/// Some("lorem.txt".to_owned()),
/// Some(mime::TEXT_PLAIN_UTF_8),
/// Bytes::from_static(b"Lorem ipsum."),
/// );
///
/// assert!(find(&body, b"foo").is_some());
/// assert!(find(&body, b"lorem.txt").is_some());
/// assert!(find(&body, b"text/plain; charset=utf-8").is_some());
/// assert!(find(&body, b"Lorem ipsum.").is_some());
///
/// let req = TestRequest::default();
///
/// // merge header map into existing test request and set multipart body
/// let req = headers
/// .into_iter()
/// .fold(req, |req, hdr| req.insert_header(hdr))
/// .set_payload(body)
/// .to_http_request();
///
/// assert!(
/// req.headers()
/// .get("content-type")
/// .unwrap()
/// .to_str()
/// .unwrap()
/// .starts_with("multipart/form-data; boundary=\"")
/// );
/// ```
pub fn create_form_data_payload_and_headers(
name: &str,
filename: Option<String>,
content_type: Option<Mime>,
file: Bytes,
) -> (Bytes, HeaderMap) {
let boundary = Alphanumeric.sample_string(&mut thread_rng(), 32);
create_form_data_payload_and_headers_with_boundary(
&boundary,
name,
filename,
content_type,
file,
)
}
/// Constructs a `multipart/form-data` payload from bytes and metadata with a fixed boundary.
///
/// See [`create_form_data_payload_and_headers`] for more details.
pub fn create_form_data_payload_and_headers_with_boundary(
boundary: &str,
name: &str,
filename: Option<String>,
content_type: Option<Mime>,
file: Bytes,
) -> (Bytes, HeaderMap) {
let mut buf = BytesMut::with_capacity(file.len() + 128);
let boundary_str = [BOUNDARY_PREFIX, boundary].concat();
let boundary = boundary_str.as_bytes();
buf.put(HYPHENS);
buf.put(boundary);
buf.put(CRLF);
buf.put(format!("Content-Disposition: form-data; name=\"{name}\"").as_bytes());
if let Some(filename) = filename {
buf.put(format!("; filename=\"{filename}\"").as_bytes());
}
buf.put(CRLF);
if let Some(ct) = content_type {
buf.put(format!("Content-Type: {ct}").as_bytes());
buf.put(CRLF);
}
buf.put(format!("Content-Length: {}", file.len()).as_bytes());
buf.put(CRLF_CRLF);
buf.put(file);
buf.put(CRLF);
buf.put(HYPHENS);
buf.put(boundary);
buf.put(HYPHENS);
buf.put(CRLF);
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
format!("multipart/form-data; boundary=\"{boundary_str}\"")
.parse()
.unwrap(),
);
(buf.freeze(), headers)
}
#[cfg(test)]
mod tests {
use std::convert::Infallible;
use futures_util::stream;
use super::*;
fn find_boundary(headers: &HeaderMap) -> String {
headers
.get("content-type")
.unwrap()
.to_str()
.unwrap()
.parse::<mime::Mime>()
.unwrap()
.get_param(mime::BOUNDARY)
.unwrap()
.as_str()
.to_owned()
}
#[test]
fn wire_format() {
let (pl, headers) = create_form_data_payload_and_headers_with_boundary(
"qWeRtYuIoP",
"foo",
None,
None,
Bytes::from_static(b"Lorem ipsum dolor\nsit ame."),
);
assert_eq!(
find_boundary(&headers),
"------------------------qWeRtYuIoP",
);
assert_eq!(
std::str::from_utf8(&pl).unwrap(),
"--------------------------qWeRtYuIoP\r\n\
Content-Disposition: form-data; name=\"foo\"\r\n\
Content-Length: 26\r\n\
\r\n\
Lorem ipsum dolor\n\
sit ame.\r\n\
--------------------------qWeRtYuIoP--\r\n",
);
let (pl, _headers) = create_form_data_payload_and_headers_with_boundary(
"qWeRtYuIoP",
"foo",
Some("Lorem.txt".to_owned()),
Some(mime::TEXT_PLAIN_UTF_8),
Bytes::from_static(b"Lorem ipsum dolor\nsit ame."),
);
assert_eq!(
std::str::from_utf8(&pl).unwrap(),
"--------------------------qWeRtYuIoP\r\n\
Content-Disposition: form-data; name=\"foo\"; filename=\"Lorem.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\n\
Content-Length: 26\r\n\
\r\n\
Lorem ipsum dolor\n\
sit ame.\r\n\
--------------------------qWeRtYuIoP--\r\n",
);
}
/// Test using an external library to prevent the two-wrongs-make-a-right class of errors.
#[actix_web::test]
async fn ecosystem_compat() {
let (pl, headers) = create_form_data_payload_and_headers(
"foo",
None,
None,
Bytes::from_static(b"Lorem ipsum dolor\nsit ame."),
);
let boundary = find_boundary(&headers);
let pl = stream::once(async { Ok::<_, Infallible>(pl) });
let mut form = multer::Multipart::new(pl, boundary);
let field = form.next_field().await.unwrap().unwrap();
assert_eq!(field.name().unwrap(), "foo");
assert_eq!(field.file_name(), None);
assert_eq!(field.content_type(), None);
assert!(field.bytes().await.unwrap().starts_with(b"Lorem"));
}
}

View file

@ -2,6 +2,8 @@
## Unreleased ## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 0.5.2 ## 0.5.2
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency. - Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.

View file

@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-router?label=latest)](https://crates.io/crates/actix-router) [![crates.io](https://img.shields.io/crates/v/actix-router?label=latest)](https://crates.io/crates/actix-router)
[![Documentation](https://docs.rs/actix-router/badge.svg?version=0.5.2)](https://docs.rs/actix-router/0.5.2) [![Documentation](https://docs.rs/actix-router/badge.svg?version=0.5.2)](https://docs.rs/actix-router/0.5.2)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-router.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-router.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-router/0.5.2/status.svg)](https://deps.rs/crate/actix-router/0.5.2) [![dependency status](https://deps.rs/crate/actix-router/0.5.2/status.svg)](https://deps.rs/crate/actix-router/0.5.2)

View file

@ -2,6 +2,12 @@
## Unreleased ## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 0.1.3
- Add `TestServerConfig::rustls_0_22()` method for Rustls v0.22 support behind new `rustls-0_22` crate feature.
## 0.1.2 ## 0.1.2
- Add `TestServerConfig::rustls_021()` method for Rustls v0.21 support behind new `rustls-0_21` crate feature. - Add `TestServerConfig::rustls_021()` method for Rustls v0.21 support behind new `rustls-0_21` crate feature.

View file

@ -1,6 +1,6 @@
[package] [package]
name = "actix-test" name = "actix-test"
version = "0.1.2" version = "0.1.3"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>", "Rob Ede <robjtede@icloud.com>",
@ -27,19 +27,21 @@ rustls = ["rustls-0_20"]
rustls-0_20 = ["tls-rustls-0_20", "actix-http/rustls-0_20", "awc/rustls-0_20"] rustls-0_20 = ["tls-rustls-0_20", "actix-http/rustls-0_20", "awc/rustls-0_20"]
# TLS via Rustls v0.21 # TLS via Rustls v0.21
rustls-0_21 = ["tls-rustls-0_21", "actix-http/rustls-0_21", "awc/rustls-0_21"] rustls-0_21 = ["tls-rustls-0_21", "actix-http/rustls-0_21", "awc/rustls-0_21"]
# TLS via Rustls v0.22
rustls-0_22 = ["tls-rustls-0_22", "actix-http/rustls-0_22", "awc/rustls-0_22-webpki-roots"]
# TLS via OpenSSL # TLS via OpenSSL
openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
[dependencies] [dependencies]
actix-codec = "0.5" actix-codec = "0.5"
actix-http = "3" actix-http = "3.6"
actix-http-test = "3" actix-http-test = "3"
actix-rt = "2.1" actix-rt = "2.1"
actix-service = "2" actix-service = "2"
actix-utils = "3" actix-utils = "3"
actix-web = { version = "4", default-features = false, features = ["cookies"] } actix-web = { version = "4.5", default-features = false, features = ["cookies"] }
awc = { version = "3", default-features = false, features = ["cookies"] } awc = { version = "3.4", default-features = false, features = ["cookies"] }
futures-core = { version = "0.3.17", default-features = false, features = ["std"] } futures-core = { version = "0.3.17", default-features = false, features = ["std"] }
futures-util = { version = "0.3.17", default-features = false, features = [] } futures-util = { version = "0.3.17", default-features = false, features = [] }
@ -50,4 +52,5 @@ serde_urlencoded = "0.7"
tls-openssl = { package = "openssl", version = "0.10.55", optional = true } tls-openssl = { package = "openssl", version = "0.10.55", optional = true }
tls-rustls-0_20 = { package = "rustls", version = "0.20", optional = true } tls-rustls-0_20 = { package = "rustls", version = "0.20", optional = true }
tls-rustls-0_21 = { package = "rustls", version = "0.21", optional = true } tls-rustls-0_21 = { package = "rustls", version = "0.21", optional = true }
tls-rustls-0_22 = { package = "rustls", version = "0.22", optional = true }
tokio = { version = "1.24.2", features = ["sync"] } tokio = { version = "1.24.2", features = ["sync"] }

View file

@ -143,6 +143,8 @@ where
StreamType::Rustls020(_) => true, StreamType::Rustls020(_) => true,
#[cfg(feature = "rustls-0_21")] #[cfg(feature = "rustls-0_21")]
StreamType::Rustls021(_) => true, StreamType::Rustls021(_) => true,
#[cfg(feature = "rustls-0_22")]
StreamType::Rustls022(_) => true,
}; };
// run server in separate orphaned thread // run server in separate orphaned thread
@ -327,6 +329,48 @@ where
.rustls_021(config.clone()) .rustls_021(config.clone())
}), }),
}, },
#[cfg(feature = "rustls-0_22")]
StreamType::Rustls022(config) => match cfg.tp {
HttpVer::Http1 => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build()
.client_request_timeout(timeout)
.h1(map_config(fac, move |_| app_cfg.clone()))
.rustls_0_22(config.clone())
}),
HttpVer::Http2 => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build()
.client_request_timeout(timeout)
.h2(map_config(fac, move |_| app_cfg.clone()))
.rustls_0_22(config.clone())
}),
HttpVer::Both => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build()
.client_request_timeout(timeout)
.finish(map_config(fac, move |_| app_cfg.clone()))
.rustls_0_22(config.clone())
}),
},
} }
.expect("test server could not be created"); .expect("test server could not be created");
@ -401,6 +445,8 @@ enum StreamType {
Rustls020(tls_rustls_0_20::ServerConfig), Rustls020(tls_rustls_0_20::ServerConfig),
#[cfg(feature = "rustls-0_21")] #[cfg(feature = "rustls-0_21")]
Rustls021(tls_rustls_0_21::ServerConfig), Rustls021(tls_rustls_0_21::ServerConfig),
#[cfg(feature = "rustls-0_22")]
Rustls022(tls_rustls_0_22::ServerConfig),
} }
/// Create default test server config. /// Create default test server config.
@ -424,7 +470,7 @@ impl Default for TestServerConfig {
} }
impl TestServerConfig { impl TestServerConfig {
/// Create default server configuration /// Constructs default server configuration.
pub(crate) fn new() -> TestServerConfig { pub(crate) fn new() -> TestServerConfig {
TestServerConfig { TestServerConfig {
tp: HttpVer::Both, tp: HttpVer::Both,
@ -435,40 +481,63 @@ impl TestServerConfig {
} }
} }
/// Accept HTTP/1.1 only. /// Accepts HTTP/1.1 only.
pub fn h1(mut self) -> Self { pub fn h1(mut self) -> Self {
self.tp = HttpVer::Http1; self.tp = HttpVer::Http1;
self self
} }
/// Accept HTTP/2 only. /// Accepts HTTP/2 only.
pub fn h2(mut self) -> Self { pub fn h2(mut self) -> Self {
self.tp = HttpVer::Http2; self.tp = HttpVer::Http2;
self self
} }
/// Accept secure connections via OpenSSL. /// Accepts secure connections via OpenSSL.
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self { pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self {
self.stream = StreamType::Openssl(acceptor); self.stream = StreamType::Openssl(acceptor);
self self
} }
/// Accept secure connections via Rustls. #[doc(hidden)]
#[deprecated(note = "Renamed to `rustls_0_20()`.")]
#[cfg(feature = "rustls-0_20")] #[cfg(feature = "rustls-0_20")]
pub fn rustls(mut self, config: tls_rustls_0_20::ServerConfig) -> Self { pub fn rustls(mut self, config: tls_rustls_0_20::ServerConfig) -> Self {
self.stream = StreamType::Rustls020(config); self.stream = StreamType::Rustls020(config);
self self
} }
/// Accept secure connections via Rustls. /// Accepts secure connections via Rustls v0.20.
#[cfg(feature = "rustls-0_20")]
pub fn rustls_0_20(mut self, config: tls_rustls_0_20::ServerConfig) -> Self {
self.stream = StreamType::Rustls020(config);
self
}
#[doc(hidden)]
#[deprecated(note = "Renamed to `rustls_0_21()`.")]
#[cfg(feature = "rustls-0_21")] #[cfg(feature = "rustls-0_21")]
pub fn rustls_021(mut self, config: tls_rustls_0_21::ServerConfig) -> Self { pub fn rustls_021(mut self, config: tls_rustls_0_21::ServerConfig) -> Self {
self.stream = StreamType::Rustls021(config); self.stream = StreamType::Rustls021(config);
self self
} }
/// Set client timeout for first request. /// Accepts secure connections via Rustls v0.21.
#[cfg(feature = "rustls-0_21")]
pub fn rustls_0_21(mut self, config: tls_rustls_0_21::ServerConfig) -> Self {
self.stream = StreamType::Rustls021(config);
self
}
/// Accepts secure connections via Rustls v0.22.
#[cfg(feature = "rustls-0_22")]
pub fn rustls_0_22(mut self, config: tls_rustls_0_22::ServerConfig) -> Self {
self.stream = StreamType::Rustls022(config);
self
}
/// Sets client timeout for first request.
pub fn client_request_timeout(mut self, dur: Duration) -> Self { pub fn client_request_timeout(mut self, dur: Duration) -> Self {
self.client_request_timeout = dur; self.client_request_timeout = dur;
self self

View file

@ -2,6 +2,10 @@
## Unreleased ## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 4.3.0
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency. - Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
## 4.2.0 ## 4.2.0

View file

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-actors" name = "actix-web-actors"
version = "4.2.0" version = "4.3.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for Actix Web" description = "Actix actors support for Actix Web"
keywords = ["actix", "http", "web", "framework", "async"] keywords = ["actix", "http", "web", "framework", "async"]

View file

@ -5,17 +5,12 @@
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[![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.2.0)](https://docs.rs/actix-web-actors/4.2.0) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.3.0)](https://docs.rs/actix-web-actors/4.3.0)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![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.2.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.2.0) [![dependency status](https://deps.rs/crate/actix-web-actors/4.3.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.3.0)
[![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors)
[![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)
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-web-actors)
- Minimum Supported Rust Version (MSRV): 1.68

View file

@ -2,8 +2,7 @@
## Unreleased ## Unreleased
## 4.2.3 - Minimum supported Rust version (MSRV) is now 1.72.
- Add a scope macro that takes a path - Add a scope macro that takes a path
## 4.2.2 ## 4.2.2

View file

@ -6,7 +6,7 @@
[![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=4.2.2)](https://docs.rs/actix-web-codegen/4.2.2) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.2.2)](https://docs.rs/actix-web-codegen/4.2.2)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![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/4.2.2/status.svg)](https://deps.rs/crate/actix-web-codegen/4.2.2) [![dependency status](https://deps.rs/crate/actix-web-codegen/4.2.2/status.svg)](https://deps.rs/crate/actix-web-codegen/4.2.2)
@ -15,11 +15,6 @@
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-web-codegen)
- Minimum Supported Rust Version (MSRV): 1.68
## Compile Testing ## Compile Testing
Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this. Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this.

View file

@ -1,4 +1,4 @@
#[rustversion::stable(1.68)] // MSRV #[rustversion::stable(1.72)] // MSRV
#[test] #[test]
fn compile_macros() { fn compile_macros() {
let t = trybuild::TestCases::new(); let t = trybuild::TestCases::new();

View file

@ -13,17 +13,20 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call | required by a bound introduced by this call
| |
= help: the following other types implement trait `HttpServiceFactory`: = help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B) (A, B)
(A, B, C) (A, B, C)
(A, B, C, D) (A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others and $N others
note: required by a bound in `App::<T>::service` note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs --> $WORKSPACE/actix-web/src/app.rs
| |
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static, | F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service` | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View file

@ -13,17 +13,20 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call | required by a bound introduced by this call
| |
= help: the following other types implement trait `HttpServiceFactory`: = help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B) (A, B)
(A, B, C) (A, B, C)
(A, B, C, D) (A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others and $N others
note: required by a bound in `App::<T>::service` note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs --> $WORKSPACE/actix-web/src/app.rs
| |
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static, | F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service` | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View file

@ -15,17 +15,20 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call | required by a bound introduced by this call
| |
= help: the following other types implement trait `HttpServiceFactory`: = help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B) (A, B)
(A, B, C) (A, B, C)
(A, B, C, D) (A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others and $N others
note: required by a bound in `App::<T>::service` note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs --> $WORKSPACE/actix-web/src/app.rs
| |
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static, | F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service` | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View file

@ -29,17 +29,20 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call | required by a bound introduced by this call
| |
= help: the following other types implement trait `HttpServiceFactory`: = help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B) (A, B)
(A, B, C) (A, B, C)
(A, B, C, D) (A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others and $N others
note: required by a bound in `App::<T>::service` note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs --> $WORKSPACE/actix-web/src/app.rs
| |
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static, | F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service` | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View file

@ -15,17 +15,20 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call | required by a bound introduced by this call
| |
= help: the following other types implement trait `HttpServiceFactory`: = help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B) (A, B)
(A, B, C) (A, B, C)
(A, B, C, D) (A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others and $N others
note: required by a bound in `App::<T>::service` note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs --> $WORKSPACE/actix-web/src/app.rs
| |
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static, | F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service` | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View file

@ -2,6 +2,23 @@
## Unreleased ## Unreleased
### Changed
- Minimum supported Rust version (MSRV) is now 1.72.
## 4.5.1
### Fixed
- Fix missing import when using enabling Rustls v0.22 support.
## 4.5.0
### Added
- Add `rustls-0_22` crate feature.
- Add `HttpServer::{bind_rustls_0_22, listen_rustls_0_22}()` builder methods.
## 4.4.1 ## 4.4.1
### Changed ### Changed

View file

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "4.4.1" version = "4.5.1"
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"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
@ -20,9 +20,20 @@ edition.workspace = true
rust-version.workspace = true rust-version.workspace = true
[package.metadata.docs.rs] [package.metadata.docs.rs]
# features that docs.rs will build with
features = ["macros", "openssl", "rustls-0_20", "rustls-0_21", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
features = [
"macros",
"openssl",
"rustls-0_20",
"rustls-0_21",
"rustls-0_22",
"compress-brotli",
"compress-gzip",
"compress-zstd",
"cookies",
"secure-cookies",
]
[lib] [lib]
name = "actix_web" name = "actix_web"
@ -58,6 +69,8 @@ rustls = ["rustls-0_20"]
rustls-0_20 = ["http2", "actix-http/rustls-0_20", "actix-tls/accept", "actix-tls/rustls-0_20"] rustls-0_20 = ["http2", "actix-http/rustls-0_20", "actix-tls/accept", "actix-tls/rustls-0_20"]
# TLS via Rustls v0.21 # TLS via Rustls v0.21
rustls-0_21 = ["http2", "actix-http/rustls-0_21", "actix-tls/accept", "actix-tls/rustls-0_21"] rustls-0_21 = ["http2", "actix-http/rustls-0_21", "actix-tls/accept", "actix-tls/rustls-0_21"]
# TLS via Rustls v0.22
rustls-0_22 = ["http2", "actix-http/rustls-0_22", "actix-tls/accept", "actix-tls/rustls-0_22"]
# Internal (PRIVATE!) features used to aid testing and checking 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.
@ -73,9 +86,9 @@ actix-rt = { version = "2.6", default-features = false }
actix-server = "2" actix-server = "2"
actix-service = "2" actix-service = "2"
actix-utils = "3" actix-utils = "3"
actix-tls = { version = "3.1", default-features = false, optional = true } actix-tls = { version = "3.3", default-features = false, optional = true }
actix-http = { version = "3.5", features = ["ws"] } actix-http = { version = "3.6", features = ["ws"] }
actix-router = "0.5" actix-router = "0.5"
actix-web-codegen = { version = "4.2", optional = true } actix-web-codegen = { version = "4.2", optional = true }
@ -105,7 +118,7 @@ url = "2.1"
[dev-dependencies] [dev-dependencies]
actix-files = "0.6" actix-files = "0.6"
actix-test = { version = "0.1", features = ["openssl", "rustls-0_21"] } actix-test = { version = "0.1", features = ["openssl", "rustls-0_22"] }
awc = { version = "3", features = ["openssl"] } awc = { version = "3", features = ["openssl"] }
brotli = "3.3.3" brotli = "3.3.3"
@ -115,12 +128,12 @@ env_logger = "0.10"
flate2 = "1.0.13" flate2 = "1.0.13"
futures-util = { version = "0.3.17", default-features = false, features = ["std"] } futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
rand = "0.8" rand = "0.8"
rcgen = "0.11" rcgen = "0.12"
rustls-pemfile = "1" rustls-pemfile = "2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
static_assertions = "1" static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.55" } tls-openssl = { package = "openssl", version = "0.10.55" }
tls-rustls = { package = "rustls", version = "0.21" } tls-rustls = { package = "rustls", version = "0.22" }
tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] } tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] }
zstd = "0.13" zstd = "0.13"

View file

@ -8,10 +8,10 @@
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[![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.4.1)](https://docs.rs/actix-web/4.4.1) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.5.1)](https://docs.rs/actix-web/4.5.1)
![MSRV](https://img.shields.io/badge/rustc-1.68+-ab6000.svg) ![MSRV](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![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.4.1/status.svg)](https://deps.rs/crate/actix-web/4.4.1) [![Dependency Status](https://deps.rs/crate/actix-web/4.5.1/status.svg)](https://deps.rs/crate/actix-web/4.5.1)
<br /> <br />
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml)
[![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)
@ -37,7 +37,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/))
- Integrates with the [`awc` HTTP client](https://docs.rs/awc/) - Integrates with the [`awc` HTTP client](https://docs.rs/awc/)
- Runs on stable Rust 1.68+ - Runs on stable Rust 1.72+
## Documentation ## Documentation

View file

@ -7,7 +7,12 @@ use std::{
time::Duration, time::Duration,
}; };
#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))] #[cfg(any(
feature = "openssl",
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
))]
use actix_http::TlsAcceptorConfig; use actix_http::TlsAcceptorConfig;
use actix_http::{body::MessageBody, Extensions, HttpService, KeepAlive, Request, Response}; use actix_http::{body::MessageBody, Extensions, HttpService, KeepAlive, Request, Response};
use actix_server::{Server, ServerBuilder}; use actix_server::{Server, ServerBuilder};
@ -442,6 +447,25 @@ where
Ok(self) Ok(self)
} }
/// Resolves socket address(es) and binds server to created listener(s) for TLS connections
/// using Rustls v0.22.
///
/// See [`bind()`](Self::bind()) for more details on `addrs` argument.
///
/// ALPN protocols "h2" and "http/1.1" are added to any configured ones.
#[cfg(feature = "rustls-0_22")]
pub fn bind_rustls_0_22<A: net::ToSocketAddrs>(
mut self,
addrs: A,
config: actix_tls::accept::rustls_0_22::reexports::ServerConfig,
) -> io::Result<Self> {
let sockets = bind_addrs(addrs, self.backlog)?;
for lst in sockets {
self = self.listen_rustls_0_22_inner(lst, config.clone())?;
}
Ok(self)
}
/// Resolves socket address(es) and binds server to created listener(s) for TLS connections /// Resolves socket address(es) and binds server to created listener(s) for TLS connections
/// using OpenSSL. /// using OpenSSL.
/// ///
@ -685,6 +709,72 @@ where
Ok(self) Ok(self)
} }
/// Binds to existing listener for accepting incoming TLS connection requests using Rustls
/// v0.22.
///
/// See [`listen()`](Self::listen) for more details on the `lst` argument.
///
/// ALPN protocols "h2" and "http/1.1" are added to any configured ones.
#[cfg(feature = "rustls-0_22")]
pub fn listen_rustls_0_22(
self,
lst: net::TcpListener,
config: actix_tls::accept::rustls_0_22::reexports::ServerConfig,
) -> io::Result<Self> {
self.listen_rustls_0_22_inner(lst, config)
}
#[cfg(feature = "rustls-0_22")]
fn listen_rustls_0_22_inner(
mut self,
lst: net::TcpListener,
config: actix_tls::accept::rustls_0_22::reexports::ServerConfig,
) -> io::Result<Self> {
let factory = self.factory.clone();
let cfg = self.config.clone();
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
scheme: "https",
});
let on_connect_fn = self.on_connect_fn.clone();
self.builder =
self.builder
.listen(format!("actix-web-service-{}", addr), lst, move || {
let c = cfg.lock().unwrap();
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
let svc = HttpService::build()
.keep_alive(c.keep_alive)
.client_request_timeout(c.client_request_timeout)
.client_disconnect_timeout(c.client_disconnect_timeout);
let svc = if let Some(handler) = on_connect_fn.clone() {
svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext))
} else {
svc
};
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
let acceptor_config = match c.tls_handshake_timeout {
Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur),
None => TlsAcceptorConfig::default(),
};
svc.finish(map_config(fac, move |_| {
AppConfig::new(true, host.clone(), addr)
}))
.rustls_0_22_with_config(config.clone(), acceptor_config)
})?;
Ok(self)
}
/// Binds to existing listener for accepting incoming TLS connection requests using OpenSSL. /// Binds to existing listener for accepting incoming TLS connection requests using OpenSSL.
/// ///
/// See [`listen()`](Self::listen) for more details on the `lst` argument. /// See [`listen()`](Self::listen) for more details on the `lst` argument.

View file

@ -1,6 +1,6 @@
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
extern crate tls_openssl as openssl; extern crate tls_openssl as openssl;
#[cfg(feature = "rustls-0_21")] #[cfg(feature = "rustls-0_22")]
extern crate tls_rustls as rustls; extern crate tls_rustls as rustls;
use std::{ use std::{
@ -704,11 +704,11 @@ async fn test_brotli_encoding_large_openssl() {
srv.stop().await; srv.stop().await;
} }
#[cfg(feature = "rustls-0_21")] #[cfg(feature = "rustls-0_22")]
mod plus_rustls { mod plus_rustls {
use std::io::BufReader; use std::io::BufReader;
use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig}; use rustls::{pki_types::PrivateKeyDer, ServerConfig as RustlsServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys}; use rustls_pemfile::{certs, pkcs8_private_keys};
use super::*; use super::*;
@ -721,17 +721,14 @@ mod plus_rustls {
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) let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap();
.unwrap() let mut keys = pkcs8_private_keys(key_file)
.into_iter() .collect::<Result<Vec<_>, _>>()
.map(Certificate) .unwrap();
.collect();
let mut keys = pkcs8_private_keys(key_file).unwrap();
RustlsServerConfig::builder() RustlsServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth() .with_no_client_auth()
.with_single_cert(cert_chain, PrivateKey(keys.remove(0))) .with_single_cert(cert_chain, PrivateKeyDer::Pkcs8(keys.remove(0)))
.unwrap() .unwrap()
} }
@ -743,7 +740,7 @@ mod plus_rustls {
.map(char::from) .map(char::from)
.collect::<String>(); .collect::<String>();
let srv = actix_test::start_with(actix_test::config().rustls_021(tls_config()), || { let srv = actix_test::start_with(actix_test::config().rustls_0_22(tls_config()), || {
App::new().service(web::resource("/").route(web::to(|bytes: Bytes| async { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| async {
// echo decompressed request body back in response // echo decompressed request body back in response
HttpResponse::Ok() HttpResponse::Ok()

View file

@ -2,6 +2,13 @@
## Unreleased ## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 3.4.0
- Add `rustls-0_22-webpki-roots` and `rustls-0_22-native-roots` crate feature.
- Add `awc::Connector::rustls_0_22()` method.
## 3.3.0 ## 3.3.0
- Update `trust-dns-resolver` dependency to `0.23`. - Update `trust-dns-resolver` dependency to `0.23`.

View file

@ -1,6 +1,6 @@
[package] [package]
name = "awc" name = "awc"
version = "3.3.0" version = "3.4.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Async HTTP and WebSocket client library" description = "Async HTTP and WebSocket client library"
keywords = ["actix", "http", "framework", "async", "web"] keywords = ["actix", "http", "framework", "async", "web"]
@ -20,8 +20,17 @@ name = "awc"
path = "src/lib.rs" path = "src/lib.rs"
[package.metadata.docs.rs] [package.metadata.docs.rs]
# features that docs.rs will build with rustdoc-args = ["--cfg", "docsrs"]
features = ["openssl", "rustls-0_20", "rustls-0_21", "compress-brotli", "compress-gzip", "compress-zstd", "cookies"] features = [
"cookies",
"openssl",
"rustls-0_20",
"rustls-0_21",
"rustls-0_22-webpki-roots",
"compress-brotli",
"compress-gzip",
"compress-zstd",
]
[features] [features]
default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
@ -35,6 +44,10 @@ rustls = ["rustls-0_20"]
rustls-0_20 = ["tls-rustls-0_20", "actix-tls/rustls-0_20"] rustls-0_20 = ["tls-rustls-0_20", "actix-tls/rustls-0_20"]
# TLS via Rustls v0.21 # TLS via Rustls v0.21
rustls-0_21 = ["tls-rustls-0_21", "actix-tls/rustls-0_21"] rustls-0_21 = ["tls-rustls-0_21", "actix-tls/rustls-0_21"]
# TLS via Rustls v0.22 (WebPKI roots)
rustls-0_22-webpki-roots = ["tls-rustls-0_22", "actix-tls/rustls-0_22-webpki-roots"]
# TLS via Rustls v0.22 (Native roots)
rustls-0_22-native-roots = ["tls-rustls-0_22", "actix-tls/rustls-0_22-native-roots"]
# Brotli algorithm content-encoding support # Brotli algorithm content-encoding support
compress-brotli = ["actix-http/compress-brotli", "__compress"] compress-brotli = ["actix-http/compress-brotli", "__compress"]
@ -61,9 +74,9 @@ dangerous-h2c = []
[dependencies] [dependencies]
actix-codec = "0.5" actix-codec = "0.5"
actix-service = "2" actix-service = "2"
actix-http = { version = "3.5", features = ["http2", "ws"] } actix-http = { version = "3.6", features = ["http2", "ws"] }
actix-rt = { version = "2.1", default-features = false } actix-rt = { version = "2.1", default-features = false }
actix-tls = { version = "3.1", features = ["connect", "uri"] } actix-tls = { version = "3.3", features = ["connect", "uri"] }
actix-utils = "3" actix-utils = "3"
base64 = "0.21" base64 = "0.21"
@ -72,7 +85,7 @@ cfg-if = "1"
derive_more = "0.99.5" derive_more = "0.99.5"
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
futures-util = { version = "0.3.17", default-features = false, features = ["alloc", "sink"] } futures-util = { version = "0.3.17", default-features = false, features = ["alloc", "sink"] }
h2 = "0.3.17" h2 = "0.3.24"
http = "0.2.7" http = "0.2.7"
itoa = "1" itoa = "1"
log =" 0.4" log =" 0.4"
@ -90,15 +103,16 @@ cookie = { version = "0.16", features = ["percent-encode"], optional = true }
tls-openssl = { package = "openssl", version = "0.10.55", optional = true } tls-openssl = { package = "openssl", version = "0.10.55", optional = true }
tls-rustls-0_20 = { package = "rustls", version = "0.20", optional = true, features = ["dangerous_configuration"] } tls-rustls-0_20 = { package = "rustls", version = "0.20", optional = true, features = ["dangerous_configuration"] }
tls-rustls-0_21 = { package = "rustls", version = "0.21", optional = true, features = ["dangerous_configuration"] } tls-rustls-0_21 = { package = "rustls", version = "0.21", optional = true, features = ["dangerous_configuration"] }
tls-rustls-0_22 = { package = "rustls", version = "0.22", optional = true }
trust-dns-resolver = { version = "0.23", optional = true } trust-dns-resolver = { version = "0.23", optional = true }
[dev-dependencies] [dev-dependencies]
actix-http = { version = "3.5", features = ["openssl"] } actix-http = { version = "3.6", features = ["openssl"] }
actix-http-test = { version = "3", features = ["openssl"] } actix-http-test = { version = "3", features = ["openssl"] }
actix-server = "2" actix-server = "2"
actix-test = { version = "0.1", features = ["openssl", "rustls-0_21"] } actix-test = { version = "0.1", features = ["openssl", "rustls-0_22"] }
actix-tls = { version = "3", features = ["openssl", "rustls-0_21"] } actix-tls = { version = "3.3", features = ["openssl", "rustls-0_22"] }
actix-utils = "3" actix-utils = "3"
actix-web = { version = "4", features = ["openssl"] } actix-web = { version = "4", features = ["openssl"] }
@ -108,11 +122,11 @@ env_logger = "0.10"
flate2 = "1.0.13" flate2 = "1.0.13"
futures-util = { version = "0.3.17", default-features = false } futures-util = { version = "0.3.17", default-features = false }
static_assertions = "1.1" static_assertions = "1.1"
rcgen = "0.11" rcgen = "0.12"
rustls-pemfile = "1" rustls-pemfile = "2"
tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] } tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] }
zstd = "0.13" zstd = "0.13"
[[example]] [[example]]
name = "client" name = "client"
required-features = ["rustls-0_21"] required-features = ["rustls-0_22-webpki-roots"]

View file

@ -5,20 +5,18 @@
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[![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.3.0)](https://docs.rs/awc/3.3.0) [![Documentation](https://docs.rs/awc/badge.svg?version=3.4.0)](https://docs.rs/awc/3.4.0)
![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.3.0/status.svg)](https://deps.rs/crate/awc/3.3.0) [![Dependency Status](https://deps.rs/crate/awc/3.4.0/status.svg)](https://deps.rs/crate/awc/3.4.0)
[![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)
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->
## Documentation & Resources ## Examples
- [API Documentation](https://docs.rs/awc) [Example project using TLS-enabled client →](https://github.com/actix/examples/tree/master/https-tls/awc-https)
- [Example Project](https://github.com/actix/examples/tree/master/https-tls/awc-https)
- Minimum Supported Rust Version (MSRV): 1.68
## Example Basic usage:
```rust ```rust
use actix_rt::System; use actix_rt::System;

View file

@ -40,14 +40,23 @@ enum OurTlsConnector {
/// Provided because building the OpenSSL context on newer versions can be very slow. /// Provided because building the OpenSSL context on newer versions can be very slow.
/// This prevents unnecessary calls to `.build()` while constructing the client connector. /// This prevents unnecessary calls to `.build()` while constructing the client connector.
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
#[allow(dead_code)] // false positive; used in build_ssl #[allow(dead_code)] // false positive; used in build_tls
OpensslBuilder(actix_tls::connect::openssl::reexports::SslConnectorBuilder), OpensslBuilder(actix_tls::connect::openssl::reexports::SslConnectorBuilder),
#[cfg(feature = "rustls-0_20")] #[cfg(feature = "rustls-0_20")]
#[allow(dead_code)] // false positive; used in build_tls
Rustls020(std::sync::Arc<actix_tls::connect::rustls_0_20::reexports::ClientConfig>), Rustls020(std::sync::Arc<actix_tls::connect::rustls_0_20::reexports::ClientConfig>),
#[cfg(feature = "rustls-0_21")] #[cfg(feature = "rustls-0_21")]
#[allow(dead_code)] // false positive; used in build_tls
Rustls021(std::sync::Arc<actix_tls::connect::rustls_0_21::reexports::ClientConfig>), Rustls021(std::sync::Arc<actix_tls::connect::rustls_0_21::reexports::ClientConfig>),
#[cfg(any(
feature = "rustls-0_22-webpki-roots",
feature = "rustls-0_22-native-roots",
))]
#[allow(dead_code)] // false positive; used in build_tls
Rustls022(std::sync::Arc<actix_tls::connect::rustls_0_22::reexports::ClientConfig>),
} }
/// Manages HTTP client network connectivity. /// Manages HTTP client network connectivity.
@ -86,67 +95,83 @@ impl Connector<()> {
} }
} }
/// Provides an empty TLS connector when no TLS feature is enabled. cfg_if::cfg_if! {
#[cfg(not(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21")))] if #[cfg(any(feature = "rustls-0_22-webpki-roots", feature = "rustls-0_22-webpki-roots"))] {
fn build_tls(_: Vec<Vec<u8>>) -> OurTlsConnector { /// Build TLS connector with Rustls v0.22, based on supplied ALPN protocols.
OurTlsConnector::None ///
} /// Note that if other TLS crate features are enabled, Rustls v0.22 will be used.
fn build_tls(protocols: Vec<Vec<u8>>) -> OurTlsConnector {
use actix_tls::connect::rustls_0_22::{self, reexports::ClientConfig};
/// Build TLS connector with Rustls v0.21, based on supplied ALPN protocols cfg_if::cfg_if! {
/// if #[cfg(feature = "rustls-0_22-webpki-roots")] {
/// Note that if other TLS crate features are enabled, Rustls v0.21 will be used. let certs = rustls_0_22::webpki_roots_cert_store();
#[cfg(feature = "rustls-0_21")] } else if #[cfg(feature = "rustls-0_22-native-roots")] {
fn build_tls(protocols: Vec<Vec<u8>>) -> OurTlsConnector { let certs = rustls_0_22::native_roots_cert_store();
use actix_tls::connect::rustls_0_21::{reexports::ClientConfig, webpki_roots_cert_store}; }
}
let mut config = ClientConfig::builder() let mut config = ClientConfig::builder()
.with_safe_defaults() .with_root_certificates(certs)
.with_root_certificates(webpki_roots_cert_store()) .with_no_client_auth();
.with_no_client_auth();
config.alpn_protocols = protocols; config.alpn_protocols = protocols;
OurTlsConnector::Rustls021(std::sync::Arc::new(config)) OurTlsConnector::Rustls022(std::sync::Arc::new(config))
} }
} else if #[cfg(feature = "rustls-0_21")] {
/// Build TLS connector with Rustls v0.21, based on supplied ALPN protocols.
fn build_tls(protocols: Vec<Vec<u8>>) -> OurTlsConnector {
use actix_tls::connect::rustls_0_21::{reexports::ClientConfig, webpki_roots_cert_store};
/// Build TLS connector with Rustls v0.20, based on supplied ALPN protocols let mut config = ClientConfig::builder()
/// .with_safe_defaults()
/// Note that if other TLS crate features are enabled, Rustls v0.21 will be used. .with_root_certificates(webpki_roots_cert_store())
#[cfg(all(feature = "rustls-0_20", not(feature = "rustls-0_21")))] .with_no_client_auth();
fn build_tls(protocols: Vec<Vec<u8>>) -> OurTlsConnector {
use actix_tls::connect::rustls_0_20::{reexports::ClientConfig, webpki_roots_cert_store};
let mut config = ClientConfig::builder() config.alpn_protocols = protocols;
.with_safe_defaults()
.with_root_certificates(webpki_roots_cert_store())
.with_no_client_auth();
config.alpn_protocols = protocols; OurTlsConnector::Rustls021(std::sync::Arc::new(config))
}
} else if #[cfg(feature = "rustls-0_20")] {
/// Build TLS connector with Rustls v0.20, based on supplied ALPN protocols.
fn build_tls(protocols: Vec<Vec<u8>>) -> OurTlsConnector {
use actix_tls::connect::rustls_0_20::{reexports::ClientConfig, webpki_roots_cert_store};
OurTlsConnector::Rustls020(std::sync::Arc::new(config)) let mut config = ClientConfig::builder()
} .with_safe_defaults()
.with_root_certificates(webpki_roots_cert_store())
.with_no_client_auth();
/// Build TLS connector with OpenSSL, based on supplied ALPN protocols config.alpn_protocols = protocols;
#[cfg(all(
feature = "openssl",
not(any(feature = "rustls-0_20", feature = "rustls-0_21")),
))]
fn build_tls(protocols: Vec<Vec<u8>>) -> OurTlsConnector {
use actix_tls::connect::openssl::reexports::{SslConnector, SslMethod};
use bytes::{BufMut, BytesMut};
let mut alpn = BytesMut::with_capacity(20); OurTlsConnector::Rustls020(std::sync::Arc::new(config))
for proto in &protocols { }
alpn.put_u8(proto.len() as u8); } else if #[cfg(feature = "openssl")] {
alpn.put(proto.as_slice()); /// Build TLS connector with OpenSSL, based on supplied ALPN protocols.
fn build_tls(protocols: Vec<Vec<u8>>) -> OurTlsConnector {
use actix_tls::connect::openssl::reexports::{SslConnector, SslMethod};
use bytes::{BufMut, BytesMut};
let mut alpn = BytesMut::with_capacity(20);
for proto in &protocols {
alpn.put_u8(proto.len() as u8);
alpn.put(proto.as_slice());
}
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
if let Err(err) = ssl.set_alpn_protos(&alpn) {
log::error!("Can not set ALPN protocol: {err:?}");
}
OurTlsConnector::OpensslBuilder(ssl)
}
} else {
/// Provides an empty TLS connector when no TLS feature is enabled.
fn build_tls(_: Vec<Vec<u8>>) -> OurTlsConnector {
OurTlsConnector::None
}
} }
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
if let Err(err) = ssl.set_alpn_protos(&alpn) {
log::error!("Can not set ALPN protocol: {:?}", err);
}
OurTlsConnector::OpensslBuilder(ssl)
} }
} }
@ -240,6 +265,19 @@ where
self self
} }
/// Sets custom Rustls v0.22 `ClientConfig` instance.
#[cfg(any(
feature = "rustls-0_22-webpki-roots",
feature = "rustls-0_22-native-roots",
))]
pub fn rustls_0_22(
mut self,
connector: std::sync::Arc<actix_tls::connect::rustls_0_22::reexports::ClientConfig>,
) -> Self {
self.tls = OurTlsConnector::Rustls022(connector);
self
}
/// Sets maximum supported HTTP major version. /// Sets maximum supported HTTP major version.
/// ///
/// Supported versions are HTTP/1.1 and HTTP/2. /// Supported versions are HTTP/1.1 and HTTP/2.
@ -509,6 +547,42 @@ where
Some(actix_service::boxed::rc_service(tls_service)) Some(actix_service::boxed::rc_service(tls_service))
} }
#[cfg(any(
feature = "rustls-0_22-webpki-roots",
feature = "rustls-0_22-native-roots",
))]
OurTlsConnector::Rustls022(tls) => {
const H2: &[u8] = b"h2";
use actix_tls::connect::rustls_0_22::{reexports::AsyncTlsStream, TlsConnector};
impl<Io: ConnectionIo> IntoConnectionIo for TcpConnection<Uri, AsyncTlsStream<Io>> {
fn into_connection_io(self) -> (Box<dyn ConnectionIo>, Protocol) {
let sock = self.into_parts().0;
let h2 = sock
.get_ref()
.1
.alpn_protocol()
.map_or(false, |protos| protos.windows(2).any(|w| w == H2));
if h2 {
(Box::new(sock), Protocol::Http2)
} else {
(Box::new(sock), Protocol::Http1)
}
}
}
let handshake_timeout = self.config.handshake_timeout;
let tls_service = TlsConnectorService {
tcp_service: tcp_service_inner,
tls_service: TlsConnector::service(tls),
timeout: handshake_timeout,
};
Some(actix_service::boxed::rc_service(tls_service))
}
}; };
let tcp_config = self.config.no_disconnect_timeout(); let tcp_config = self.config.no_disconnect_timeout();

View file

@ -1,6 +1,6 @@
#![cfg(feature = "rustls-0_21")] #![cfg(feature = "rustls-0_22-webpki-roots")]
extern crate tls_rustls_0_21 as rustls; extern crate tls_rustls_0_22 as rustls;
use std::{ use std::{
io::BufReader, io::BufReader,
@ -8,18 +8,17 @@ 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::rustls_0_21::webpki_roots_cert_store; use actix_tls::connect::rustls_0_22::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::{ use rustls::{
client::{ServerCertVerified, ServerCertVerifier}, pki_types::{CertificateDer, PrivateKeyDer, ServerName},
Certificate, ClientConfig, PrivateKey, ServerConfig, ServerName, ClientConfig, ServerConfig,
}; };
use rustls_pemfile::{certs, pkcs8_private_keys}; use rustls_pemfile::{certs, pkcs8_private_keys};
@ -31,36 +30,62 @@ fn tls_config() -> ServerConfig {
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) let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap();
.unwrap() let mut keys = pkcs8_private_keys(key_file)
.into_iter() .collect::<Result<Vec<_>, _>>()
.map(Certificate) .unwrap();
.collect();
let mut keys = pkcs8_private_keys(key_file).unwrap();
ServerConfig::builder() ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth() .with_no_client_auth()
.with_single_cert(cert_chain, PrivateKey(keys.remove(0))) .with_single_cert(cert_chain, PrivateKeyDer::Pkcs8(keys.remove(0)))
.unwrap() .unwrap()
} }
mod danger { mod danger {
use rustls::{
client::danger::{ServerCertVerified, ServerCertVerifier},
pki_types::UnixTime,
};
use super::*; use super::*;
#[derive(Debug)]
pub struct NoCertificateVerification; pub struct NoCertificateVerification;
impl ServerCertVerifier for NoCertificateVerification { impl ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert( fn verify_server_cert(
&self, &self,
_end_entity: &Certificate, _end_entity: &CertificateDer<'_>,
_intermediates: &[Certificate], _intermediates: &[CertificateDer<'_>],
_server_name: &ServerName, _server_name: &ServerName<'_>,
_scts: &mut dyn Iterator<Item = &[u8]>,
_ocsp_response: &[u8], _ocsp_response: &[u8],
_now: SystemTime, _now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> { ) -> Result<ServerCertVerified, rustls::Error> {
Ok(ServerCertVerified::assertion()) Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
rustls::crypto::ring::default_provider()
.signature_verification_algorithms
.supported_schemes()
} }
} }
} }
@ -82,14 +107,13 @@ async fn test_connection_reuse_h2() {
App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))),
|_| AppConfig::default(), |_| AppConfig::default(),
)) ))
.rustls_021(tls_config()) .rustls_0_22(tls_config())
.map_err(|_| ()), .map_err(|_| ()),
) )
}) })
.await; .await;
let mut config = ClientConfig::builder() let mut config = ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(webpki_roots_cert_store()) .with_root_certificates(webpki_roots_cert_store())
.with_no_client_auth(); .with_no_client_auth();
@ -102,7 +126,7 @@ async fn test_connection_reuse_h2() {
.set_certificate_verifier(Arc::new(danger::NoCertificateVerification)); .set_certificate_verifier(Arc::new(danger::NoCertificateVerification));
let client = awc::Client::builder() let client = awc::Client::builder()
.connector(awc::Connector::new().rustls_021(Arc::new(config))) .connector(awc::Connector::new().rustls_0_22(Arc::new(config)))
.finish(); .finish();
// req 1 // req 1

View file

@ -112,7 +112,6 @@ echo
read -p "Update all references: (y/N) " UPDATE_REFERENCES read -p "Update all references: (y/N) " UPDATE_REFERENCES
UPDATE_REFERENCES="${UPDATE_REFERENCES:-n}" UPDATE_REFERENCES="${UPDATE_REFERENCES:-n}"
if [ "$UPDATE_REFERENCES" = 'y' ] || [ "$UPDATE_REFERENCES" = 'Y' ]; then if [ "$UPDATE_REFERENCES" = 'y' ] || [ "$UPDATE_REFERENCES" = 'Y' ]; then
if [[ $NEW_VERSION == *".0.0" ]]; then if [[ $NEW_VERSION == *".0.0" ]]; then
NEW_VERSION_SPEC="${NEW_VERSION%.0.0}" NEW_VERSION_SPEC="${NEW_VERSION%.0.0}"