mirror of
https://github.com/actix/actix-web.git
synced 2024-12-17 13:46:36 +00:00
Merge branch 'master' into prelim
This commit is contained in:
commit
afd1ce1d71
81 changed files with 1533 additions and 332 deletions
|
@ -6,9 +6,5 @@ lint-all = "clippy --workspace --all-features --all-targets -- -Dclippy::todo"
|
||||||
ci-check-min = "hack --workspace check --no-default-features"
|
ci-check-min = "hack --workspace check --no-default-features"
|
||||||
ci-check-default = "hack --workspace check"
|
ci-check-default = "hack --workspace check"
|
||||||
ci-check-default-tests = "check --workspace --tests"
|
ci-check-default-tests = "check --workspace --tests"
|
||||||
ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,experimental-io-uring check"
|
ci-check-all-feature-powerset="hack --workspace --feature-powerset --depth=4 --skip=__compress,experimental-io-uring check"
|
||||||
ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=__compress check"
|
ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --depth=4 --skip=__compress check"
|
||||||
|
|
||||||
# testing
|
|
||||||
ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture"
|
|
||||||
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
|
||||||
|
|
44
.github/workflows/ci-post-merge.yml
vendored
44
.github/workflows/ci-post-merge.yml
vendored
|
@ -30,6 +30,10 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install nasm
|
||||||
|
if: matrix.target.os == 'windows-latest'
|
||||||
|
uses: ilammy/setup-nasm@v1.5.1
|
||||||
|
|
||||||
- name: Install OpenSSL
|
- name: Install OpenSSL
|
||||||
if: matrix.target.os == 'windows-latest'
|
if: matrix.target.os == 'windows-latest'
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -44,10 +48,10 @@ jobs:
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.version.version }}
|
toolchain: ${{ matrix.version.version }}
|
||||||
|
|
||||||
- name: Install cargo-hack and cargo-ci-cache-clean
|
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||||
uses: taiki-e/install-action@v2.28.0
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: cargo-hack,cargo-ci-cache-clean
|
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||||
|
|
||||||
- name: check minimal
|
- name: check minimal
|
||||||
run: cargo ci-check-min
|
run: cargo ci-check-min
|
||||||
|
@ -57,19 +61,7 @@ jobs:
|
||||||
|
|
||||||
- name: tests
|
- name: tests
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
shell: bash
|
run: just test
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
cargo test --lib --tests -p=actix-router --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,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=awc --all-features
|
|
||||||
cargo test --lib --tests -p=actix-http-test --all-features
|
|
||||||
cargo test --lib --tests -p=actix-test --all-features
|
|
||||||
cargo test --lib --tests -p=actix-files
|
|
||||||
cargo test --lib --tests -p=actix-multipart --all-features
|
|
||||||
cargo test --lib --tests -p=actix-web-actors --all-features
|
|
||||||
|
|
||||||
- name: CI cache clean
|
- name: CI cache clean
|
||||||
run: cargo-ci-cache-clean
|
run: cargo-ci-cache-clean
|
||||||
|
@ -88,7 +80,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.28.0
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: cargo-hack
|
tool: cargo-hack
|
||||||
|
|
||||||
|
@ -97,21 +89,3 @@ jobs:
|
||||||
|
|
||||||
- name: check feature combinations
|
- name: check feature combinations
|
||||||
run: cargo ci-check-all-feature-powerset-linux
|
run: cargo ci-check-all-feature-powerset-linux
|
||||||
|
|
||||||
nextest:
|
|
||||||
name: nextest
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Rust
|
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
|
||||||
|
|
||||||
- name: Install nextest
|
|
||||||
uses: taiki-e/install-action@v2.28.0
|
|
||||||
with:
|
|
||||||
tool: nextest
|
|
||||||
|
|
||||||
- name: Test with cargo-nextest
|
|
||||||
run: cargo nextest run
|
|
||||||
|
|
48
.github/workflows/ci.yml
vendored
48
.github/workflows/ci.yml
vendored
|
@ -16,7 +16,13 @@ concurrency:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
read_msrv:
|
||||||
|
name: Read MSRV
|
||||||
|
uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@v0.1.0
|
||||||
|
|
||||||
build_and_test:
|
build_and_test:
|
||||||
|
needs: read_msrv
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -26,7 +32,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.72.0 }
|
- { name: msrv, version: "${{ needs.read_msrv.outputs.msrv }}" }
|
||||||
- { name: stable, version: stable }
|
- { name: stable, version: stable }
|
||||||
|
|
||||||
name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
|
name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
|
||||||
|
@ -35,6 +41,10 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install nasm
|
||||||
|
if: matrix.target.os == 'windows-latest'
|
||||||
|
uses: ilammy/setup-nasm@v1.5.1
|
||||||
|
|
||||||
- name: Install OpenSSL
|
- name: Install OpenSSL
|
||||||
if: matrix.target.os == 'windows-latest'
|
if: matrix.target.os == 'windows-latest'
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -44,20 +54,23 @@ jobs:
|
||||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
|
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
|
||||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Setup mold linker
|
||||||
|
if: matrix.target.os == 'ubuntu-latest'
|
||||||
|
uses: rui314/setup-mold@v1
|
||||||
|
|
||||||
- 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 and cargo-ci-cache-clean
|
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||||
uses: taiki-e/install-action@v2.28.0
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: cargo-hack,cargo-ci-cache-clean
|
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||||
|
|
||||||
- name: workaround MSRV issues
|
- name: workaround MSRV issues
|
||||||
if: matrix.version.name == 'msrv'
|
if: matrix.version.name == 'msrv'
|
||||||
run: |
|
run: just downgrade-for-msrv
|
||||||
cargo update -p=clap --precise=4.4.18
|
|
||||||
|
|
||||||
- name: check minimal
|
- name: check minimal
|
||||||
run: cargo ci-check-min
|
run: cargo ci-check-min
|
||||||
|
@ -67,20 +80,7 @@ jobs:
|
||||||
|
|
||||||
- name: tests
|
- name: tests
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
shell: bash
|
run: just test
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
cargo test --lib --tests -p=actix-router --no-default-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-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=awc --all-features
|
|
||||||
cargo test --lib --tests -p=actix-http-test --all-features
|
|
||||||
cargo test --lib --tests -p=actix-test --all-features
|
|
||||||
cargo test --lib --tests -p=actix-files
|
|
||||||
cargo test --lib --tests -p=actix-multipart --all-features
|
|
||||||
cargo test --lib --tests -p=actix-web-actors --all-features
|
|
||||||
|
|
||||||
- name: CI cache clean
|
- name: CI cache clean
|
||||||
run: cargo-ci-cache-clean
|
run: cargo-ci-cache-clean
|
||||||
|
@ -112,6 +112,10 @@ jobs:
|
||||||
with:
|
with:
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
|
|
||||||
|
- name: Install just
|
||||||
|
uses: taiki-e/install-action@v2.34.0
|
||||||
|
with:
|
||||||
|
tool: just
|
||||||
|
|
||||||
- name: doc tests
|
- name: doc tests
|
||||||
run: cargo ci-doctest
|
run: just test-docs
|
||||||
timeout-minutes: 60
|
|
||||||
|
|
8
.github/workflows/coverage.yml
vendored
8
.github/workflows/coverage.yml
vendored
|
@ -22,16 +22,16 @@ jobs:
|
||||||
with:
|
with:
|
||||||
components: llvm-tools-preview
|
components: llvm-tools-preview
|
||||||
|
|
||||||
- name: Install cargo-llvm-cov
|
- name: Install just,cargo-llvm-cov
|
||||||
uses: taiki-e/install-action@v2.28.0
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: cargo-llvm-cov
|
tool: just,cargo-llvm-cov
|
||||||
|
|
||||||
- name: Generate code coverage
|
- name: Generate code coverage
|
||||||
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@v4.1.0
|
uses: codecov/codecov-action@v4.4.1
|
||||||
with:
|
with:
|
||||||
files: codecov.json
|
files: codecov.json
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
|
|
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
|
@ -79,15 +79,15 @@ jobs:
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
with:
|
with:
|
||||||
toolchain: nightly-2023-08-25
|
toolchain: nightly-2024-06-07
|
||||||
|
|
||||||
- name: Install cargo-public-api
|
- name: Install cargo-public-api
|
||||||
uses: taiki-e/install-action@v2.28.0
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: cargo-public-api
|
tool: cargo-public-api
|
||||||
|
|
||||||
- name: Generate API diff
|
- name: Generate API diff
|
||||||
run: |
|
run: |
|
||||||
for f in $(find -mindepth 2 -maxdepth 2 -name Cargo.toml); do
|
for f in $(find -mindepth 2 -maxdepth 2 -name Cargo.toml); do
|
||||||
cargo public-api --manifest-path "$f" diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }}
|
cargo public-api --manifest-path "$f" --simplified diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }}
|
||||||
done
|
done
|
||||||
|
|
41
.github/workflows/upload-doc.yml
vendored
41
.github/workflows/upload-doc.yml
vendored
|
@ -1,41 +0,0 @@
|
||||||
name: Upload Documentation
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Rust
|
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
|
||||||
with:
|
|
||||||
toolchain: nightly
|
|
||||||
|
|
||||||
- name: Build Docs
|
|
||||||
run: cargo +nightly doc --no-deps --workspace --all-features
|
|
||||||
env:
|
|
||||||
RUSTDOCFLAGS: --cfg=docsrs
|
|
||||||
|
|
||||||
- name: Tweak HTML
|
|
||||||
run: echo '<meta http-equiv="refresh" content="0;url=actix_web/index.html">' > target/doc/index.html
|
|
||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
uses: JamesIves/github-pages-deploy-action@v4.5.0
|
|
||||||
with:
|
|
||||||
folder: target/doc
|
|
||||||
single-commit: true
|
|
|
@ -15,6 +15,8 @@ members = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
homepage = "https://actix.rs"
|
||||||
|
repository = "https://github.com/actix/actix-web"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.72"
|
rust-version = "1.72"
|
||||||
|
|
|
@ -2,8 +2,16 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 3.7.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add `rustls-0_23` crate feature
|
||||||
|
- Add `{h1::H1Service, h2::H2Service, HttpService}::rustls_0_23()` and `HttpService::rustls_0_23_with_config()` service constructors.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Update `brotli` dependency to `6`.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||||
|
|
||||||
## 3.6.0
|
## 3.6.0
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "3.6.0"
|
version = "3.7.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
|
@ -28,6 +28,7 @@ features = [
|
||||||
"rustls-0_20",
|
"rustls-0_20",
|
||||||
"rustls-0_21",
|
"rustls-0_21",
|
||||||
"rustls-0_22",
|
"rustls-0_22",
|
||||||
|
"rustls-0_23",
|
||||||
"compress-brotli",
|
"compress-brotli",
|
||||||
"compress-gzip",
|
"compress-gzip",
|
||||||
"compress-zstd",
|
"compress-zstd",
|
||||||
|
@ -66,6 +67,9 @@ rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"]
|
||||||
# TLS via Rustls v0.22
|
# TLS via Rustls v0.22
|
||||||
rustls-0_22 = ["actix-tls/accept", "actix-tls/rustls-0_22"]
|
rustls-0_22 = ["actix-tls/accept", "actix-tls/rustls-0_22"]
|
||||||
|
|
||||||
|
# TLS via Rustls v0.23
|
||||||
|
rustls-0_23 = ["actix-tls/accept", "actix-tls/rustls-0_23"]
|
||||||
|
|
||||||
# Compression codecs
|
# Compression codecs
|
||||||
compress-brotli = ["__compress", "brotli"]
|
compress-brotli = ["__compress", "brotli"]
|
||||||
compress-gzip = ["__compress", "flate2"]
|
compress-gzip = ["__compress", "flate2"]
|
||||||
|
@ -111,17 +115,17 @@ 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.3", default-features = false, optional = true }
|
actix-tls = { version = "3.4", default-features = false, optional = true }
|
||||||
|
|
||||||
# compress-*
|
# compress-*
|
||||||
brotli = { version = "3.3.3", optional = true }
|
brotli = { version = "6", optional = true }
|
||||||
flate2 = { version = "1.0.13", optional = true }
|
flate2 = { version = "1.0.13", optional = true }
|
||||||
zstd = { version = "0.13", optional = true }
|
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.3", features = ["openssl", "rustls-0_22-webpki-roots"] }
|
actix-tls = { version = "3.4", features = ["openssl", "rustls-0_23-webpki-roots"] }
|
||||||
actix-web = "4"
|
actix-web = "4"
|
||||||
|
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
|
@ -131,7 +135,7 @@ env_logger = "0.11"
|
||||||
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.12"
|
rcgen = "0.13"
|
||||||
regex = "1.3"
|
regex = "1.3"
|
||||||
rustversion = "1"
|
rustversion = "1"
|
||||||
rustls-pemfile = "2"
|
rustls-pemfile = "2"
|
||||||
|
@ -139,16 +143,16 @@ 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_022 = { package = "rustls", version = "0.22" }
|
tls-rustls_023 = { package = "rustls", version = "0.23" }
|
||||||
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_22"]
|
required-features = ["ws", "rustls-0_23"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "tls_rustls"
|
name = "tls_rustls"
|
||||||
required-features = ["http2", "rustls-0_22"]
|
required-features = ["http2", "rustls-0_23"]
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "response-body-compression"
|
name = "response-body-compression"
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
<!-- 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.6.0)](https://docs.rs/actix-http/3.6.0)
|
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.7.0)](https://docs.rs/actix-http/3.7.0)
|
||||||
![Version](https://img.shields.io/badge/rustc-1.72+-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.6.0/status.svg)](https://deps.rs/crate/actix-http/3.6.0)
|
[![dependency status](https://deps.rs/crate/actix-http/3.7.0/status.svg)](https://deps.rs/crate/actix-http/3.7.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)
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
//! Protocol: HTTP/1.1
|
//! Protocol: HTTP/1.1
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
extern crate tls_rustls_022 as rustls;
|
extern crate tls_rustls_023 as rustls;
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
@ -36,16 +36,17 @@ async fn main() -> io::Result<()> {
|
||||||
);
|
);
|
||||||
ok::<_, Error>(Response::ok().set_body(body))
|
ok::<_, Error>(Response::ok().set_body(body))
|
||||||
})
|
})
|
||||||
.rustls_0_22(rustls_config())
|
.rustls_0_23(rustls_config())
|
||||||
})?
|
})?
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rustls_config() -> rustls::ServerConfig {
|
fn rustls_config() -> rustls::ServerConfig {
|
||||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
let rcgen::CertifiedKey { cert, key_pair } =
|
||||||
let cert_file = cert.serialize_pem().unwrap();
|
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||||
let key_file = cert.serialize_private_key_pem();
|
let cert_file = cert.pem();
|
||||||
|
let key_file = key_pair.serialize_pem();
|
||||||
|
|
||||||
let cert_file = &mut io::BufReader::new(cert_file.as_bytes());
|
let cert_file = &mut io::BufReader::new(cert_file.as_bytes());
|
||||||
let key_file = &mut io::BufReader::new(key_file.as_bytes());
|
let key_file = &mut io::BufReader::new(key_file.as_bytes());
|
||||||
|
|
|
@ -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_022 as rustls;
|
extern crate tls_rustls_023 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_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})?
|
})?
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
|
@ -87,9 +87,10 @@ fn tls_config() -> rustls::ServerConfig {
|
||||||
|
|
||||||
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 rcgen::CertifiedKey { cert, key_pair } =
|
||||||
let cert_file = cert.serialize_pem().unwrap();
|
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||||
let key_file = cert.serialize_private_key_pem();
|
let cert_file = cert.pem();
|
||||||
|
let key_file = key_pair.serialize_pem();
|
||||||
|
|
||||||
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());
|
||||||
|
|
|
@ -706,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.clone();
|
req.conn_data.clone_from(this.conn_data);
|
||||||
|
|
||||||
match this.codec.message_type() {
|
match this.codec.message_type() {
|
||||||
// request has no payload
|
// request has no payload
|
||||||
|
|
|
@ -335,6 +335,67 @@ mod rustls_0_22 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls-0_23")]
|
||||||
|
mod rustls_0_23 {
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use actix_service::ServiceFactoryExt as _;
|
||||||
|
use actix_tls::accept::{
|
||||||
|
rustls_0_23::{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.23 based service.
|
||||||
|
pub fn rustls_0_23(
|
||||||
|
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 = ()>,
|
||||||
|
|
|
@ -126,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.clone();
|
req.conn_data.clone_from(&this.conn_data);
|
||||||
|
|
||||||
let fut = this.flow.service.call(req);
|
let fut = this.flow.service.call(req);
|
||||||
let config = this.config.clone();
|
let config = this.config.clone();
|
||||||
|
|
|
@ -293,6 +293,57 @@ mod rustls_0_22 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls-0_23")]
|
||||||
|
mod rustls_0_23 {
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use actix_service::ServiceFactoryExt as _;
|
||||||
|
use actix_tls::accept::{
|
||||||
|
rustls_0_23::{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.23 based service.
|
||||||
|
pub fn rustls_0_23(
|
||||||
|
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,
|
||||||
|
|
|
@ -6,7 +6,10 @@
|
||||||
//! | ------------------- | ------------------------------------------- |
|
//! | ------------------- | ------------------------------------------- |
|
||||||
//! | `http2` | HTTP/2 support via [h2]. |
|
//! | `http2` | HTTP/2 support via [h2]. |
|
||||||
//! | `openssl` | TLS support via [OpenSSL]. |
|
//! | `openssl` | TLS support via [OpenSSL]. |
|
||||||
//! | `rustls` | TLS support via [rustls]. |
|
//! | `rustls` | TLS support via [rustls] 0.20. |
|
||||||
|
//! | `rustls-0_21` | TLS support via [rustls] 0.21. |
|
||||||
|
//! | `rustls-0_22` | TLS support via [rustls] 0.22. |
|
||||||
|
//! | `rustls-0_23` | TLS support via [rustls] 0.23. |
|
||||||
//! | `compress-brotli` | Payload compression support: Brotli. |
|
//! | `compress-brotli` | Payload compression support: Brotli. |
|
||||||
//! | `compress-gzip` | Payload compression support: Deflate, Gzip. |
|
//! | `compress-gzip` | Payload compression support: Deflate, Gzip. |
|
||||||
//! | `compress-zstd` | Payload compression support: Zstd. |
|
//! | `compress-zstd` | Payload compression support: Zstd. |
|
||||||
|
@ -28,7 +31,7 @@
|
||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
|
||||||
pub use ::http::{uri, uri::Uri, Method, StatusCode, Version};
|
pub use http::{uri, uri::Uri, Method, StatusCode, Version};
|
||||||
|
|
||||||
pub mod body;
|
pub mod body;
|
||||||
mod builder;
|
mod builder;
|
||||||
|
@ -63,6 +66,7 @@ pub use self::payload::PayloadStream;
|
||||||
feature = "rustls-0_20",
|
feature = "rustls-0_20",
|
||||||
feature = "rustls-0_21",
|
feature = "rustls-0_21",
|
||||||
feature = "rustls-0_22",
|
feature = "rustls-0_22",
|
||||||
|
feature = "rustls-0_23",
|
||||||
))]
|
))]
|
||||||
pub use self::service::TlsAcceptorConfig;
|
pub use self::service::TlsAcceptorConfig;
|
||||||
pub use self::{
|
pub use self::{
|
||||||
|
|
|
@ -246,6 +246,7 @@ where
|
||||||
feature = "rustls-0_20",
|
feature = "rustls-0_20",
|
||||||
feature = "rustls-0_21",
|
feature = "rustls-0_21",
|
||||||
feature = "rustls-0_22",
|
feature = "rustls-0_22",
|
||||||
|
feature = "rustls-0_23",
|
||||||
))]
|
))]
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TlsAcceptorConfig {
|
pub struct TlsAcceptorConfig {
|
||||||
|
@ -257,6 +258,7 @@ pub struct TlsAcceptorConfig {
|
||||||
feature = "rustls-0_20",
|
feature = "rustls-0_20",
|
||||||
feature = "rustls-0_21",
|
feature = "rustls-0_21",
|
||||||
feature = "rustls-0_22",
|
feature = "rustls-0_22",
|
||||||
|
feature = "rustls-0_23",
|
||||||
))]
|
))]
|
||||||
impl TlsAcceptorConfig {
|
impl TlsAcceptorConfig {
|
||||||
/// Set TLS handshake timeout duration.
|
/// Set TLS handshake timeout duration.
|
||||||
|
@ -650,6 +652,102 @@ mod rustls_0_22 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls-0_23")]
|
||||||
|
mod rustls_0_23 {
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use actix_service::ServiceFactoryExt as _;
|
||||||
|
use actix_tls::accept::{
|
||||||
|
rustls_0_23::{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.23 based service.
|
||||||
|
pub fn rustls_0_23(
|
||||||
|
self,
|
||||||
|
config: ServerConfig,
|
||||||
|
) -> impl ServiceFactory<
|
||||||
|
TcpStream,
|
||||||
|
Config = (),
|
||||||
|
Response = (),
|
||||||
|
Error = TlsError<io::Error, DispatchError>,
|
||||||
|
InitError = (),
|
||||||
|
> {
|
||||||
|
self.rustls_0_23_with_config(config, TlsAcceptorConfig::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create Rustls v0.23 based service with custom TLS acceptor configuration.
|
||||||
|
pub fn rustls_0_23_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
|
||||||
|
|
|
@ -178,14 +178,14 @@ impl Parser {
|
||||||
};
|
};
|
||||||
|
|
||||||
if payload_len < 126 {
|
if payload_len < 126 {
|
||||||
dst.reserve(p_len + 2 + if mask { 4 } else { 0 });
|
dst.reserve(p_len + 2);
|
||||||
dst.put_slice(&[one, two | payload_len as u8]);
|
dst.put_slice(&[one, two | payload_len as u8]);
|
||||||
} else if payload_len <= 65_535 {
|
} else if payload_len <= 65_535 {
|
||||||
dst.reserve(p_len + 4 + if mask { 4 } else { 0 });
|
dst.reserve(p_len + 4);
|
||||||
dst.put_slice(&[one, two | 126]);
|
dst.put_slice(&[one, two | 126]);
|
||||||
dst.put_u16(payload_len as u16);
|
dst.put_u16(payload_len as u16);
|
||||||
} else {
|
} else {
|
||||||
dst.reserve(p_len + 10 + if mask { 4 } else { 0 });
|
dst.reserve(p_len + 10);
|
||||||
dst.put_slice(&[one, two | 127]);
|
dst.put_slice(&[one, two | 127]);
|
||||||
dst.put_u64(payload_len as u64);
|
dst.put_u64(payload_len as u64);
|
||||||
};
|
};
|
||||||
|
|
|
@ -42,9 +42,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tls_config() -> SslAcceptor {
|
fn tls_config() -> SslAcceptor {
|
||||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
let rcgen::CertifiedKey { cert, key_pair } =
|
||||||
let cert_file = cert.serialize_pem().unwrap();
|
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||||
let key_file = cert.serialize_private_key_pem();
|
let cert_file = cert.pem();
|
||||||
|
let key_file = key_pair.serialize_pem();
|
||||||
|
|
||||||
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
||||||
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#![cfg(feature = "rustls-0_22")]
|
#![cfg(feature = "rustls-0_23")]
|
||||||
|
|
||||||
extern crate tls_rustls_022 as rustls;
|
extern crate tls_rustls_023 as rustls;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
|
@ -20,7 +20,7 @@ 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_22::webpki_roots_cert_store;
|
use actix_tls::connect::rustls_0_23::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};
|
||||||
|
@ -52,9 +52,10 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tls_config() -> RustlsServerConfig {
|
fn tls_config() -> RustlsServerConfig {
|
||||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
let rcgen::CertifiedKey { cert, key_pair } =
|
||||||
let cert_file = cert.serialize_pem().unwrap();
|
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||||
let key_file = cert.serialize_private_key_pem();
|
let cert_file = cert.pem();
|
||||||
|
let key_file = key_pair.serialize_pem();
|
||||||
|
|
||||||
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());
|
||||||
|
@ -108,7 +109,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_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -122,7 +123,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_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -140,7 +141,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_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -158,7 +159,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_0_22_with_config(
|
.rustls_0_23_with_config(
|
||||||
tls_config(),
|
tls_config(),
|
||||||
TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)),
|
TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)),
|
||||||
)
|
)
|
||||||
|
@ -179,7 +180,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_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -205,7 +206,7 @@ async fn h2_content_length() {
|
||||||
];
|
];
|
||||||
ok::<_, Infallible>(Response::new(statuses[indx]))
|
ok::<_, Infallible>(Response::new(statuses[indx]))
|
||||||
})
|
})
|
||||||
.rustls_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -277,7 +278,7 @@ async fn h2_headers() {
|
||||||
}
|
}
|
||||||
ok::<_, Infallible>(config.body(data.clone()))
|
ok::<_, Infallible>(config.body(data.clone()))
|
||||||
})
|
})
|
||||||
.rustls_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -316,7 +317,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_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -333,7 +334,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_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -359,7 +360,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_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -384,7 +385,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_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -410,7 +411,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_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -434,7 +435,7 @@ async fn h2_body_chunked_explicit() {
|
||||||
.body(BodyStream::new(body)),
|
.body(BodyStream::new(body)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.rustls_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -463,7 +464,7 @@ async fn h2_response_http_error_handling() {
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
}))
|
}))
|
||||||
.rustls_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -493,7 +494,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_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -510,7 +511,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_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -533,7 +534,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_0_22(config)
|
.rustls_0_23(config)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -555,7 +556,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_0_22(config)
|
.rustls_0_23(config)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -581,7 +582,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_0_22(config)
|
.rustls_0_23(config)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,11 @@ version = "0.6.1"
|
||||||
authors = ["Jacob Halsey <jacob@jhalsey.com>"]
|
authors = ["Jacob Halsey <jacob@jhalsey.com>"]
|
||||||
description = "Multipart form derive macro for Actix Web"
|
description = "Multipart form derive macro for Actix Web"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://actix.rs"
|
homepage.workspace = true
|
||||||
repository = "https://github.com/actix/actix-web"
|
repository.workspace = true
|
||||||
license = "MIT OR Apache-2.0"
|
license.workspace = true
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 0.6.2
|
||||||
|
|
||||||
- Add testing utilities under new module `test`.
|
- Add testing utilities under new module `test`.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-multipart"
|
name = "actix-multipart"
|
||||||
version = "0.6.1"
|
version = "0.6.2"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Jacob Halsey <jacob@jhalsey.com>",
|
"Jacob Halsey <jacob@jhalsey.com>",
|
||||||
|
|
|
@ -5,12 +5,73 @@
|
||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
|
|
||||||
[![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.2)](https://docs.rs/actix-multipart/0.6.2)
|
||||||
![Version](https://img.shields.io/badge/rustc-1.72+-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.2/status.svg)](https://deps.rs/crate/actix-multipart/0.6.2)
|
||||||
[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart)
|
[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart)
|
||||||
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
|
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
<!-- prettier-ignore-end -->
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
actix-multipart = "0.6"
|
||||||
|
actix-web = "4.5"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
Code:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::{post, App, HttpServer, Responder};
|
||||||
|
|
||||||
|
use actix_multipart::form::{json::Json as MPJson, tempfile::TempFile, MultipartForm};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Metadata {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, MultipartForm)]
|
||||||
|
struct UploadForm {
|
||||||
|
#[multipart(limit = "100MB")]
|
||||||
|
file: TempFile,
|
||||||
|
json: MPJson<Metadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/videos")]
|
||||||
|
pub async fn post_video(MultipartForm(form): MultipartForm<UploadForm>) -> impl Responder {
|
||||||
|
format!(
|
||||||
|
"Uploaded file {}, with size: {}",
|
||||||
|
form.json.name, form.file.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
HttpServer::new(move || App::new().service(post_video))
|
||||||
|
.bind(("127.0.0.1", 8080))?
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Curl request :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -v --request POST \
|
||||||
|
--url http://localhost:8080/videos \
|
||||||
|
-F 'json={"name": "Cargo.lock"};type=application/json' \
|
||||||
|
-F file=@./Cargo.lock
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
https://github.com/actix/examples/tree/master/forms/multipart
|
||||||
|
|
|
@ -313,7 +313,8 @@ where
|
||||||
let entry = field_limits
|
let entry = field_limits
|
||||||
.entry(field.name().to_owned())
|
.entry(field.name().to_owned())
|
||||||
.or_insert_with(|| T::limit(field.name()));
|
.or_insert_with(|| T::limit(field.name()));
|
||||||
limits.field_limit_remaining = entry.to_owned();
|
|
||||||
|
limits.field_limit_remaining.clone_from(entry);
|
||||||
|
|
||||||
T::handle_field(&req, field, &mut limits, &mut state).await?;
|
T::handle_field(&req, field, &mut limits, &mut state).await?;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,39 @@
|
||||||
//! Multipart form support for Actix Web.
|
//! Multipart form support for Actix Web.
|
||||||
|
//! # Examples
|
||||||
|
//! ```no_run
|
||||||
|
//! use actix_web::{post, App, HttpServer, Responder};
|
||||||
|
//!
|
||||||
|
//! use actix_multipart::form::{json::Json as MPJson, tempfile::TempFile, MultipartForm};
|
||||||
|
//! use serde::Deserialize;
|
||||||
|
//!
|
||||||
|
//! #[derive(Debug, Deserialize)]
|
||||||
|
//! struct Metadata {
|
||||||
|
//! name: String,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[derive(Debug, MultipartForm)]
|
||||||
|
//! struct UploadForm {
|
||||||
|
//! #[multipart(limit = "100MB")]
|
||||||
|
//! file: TempFile,
|
||||||
|
//! json: MPJson<Metadata>,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[post("/videos")]
|
||||||
|
//! pub async fn post_video(MultipartForm(form): MultipartForm<UploadForm>) -> impl Responder {
|
||||||
|
//! format!(
|
||||||
|
//! "Uploaded file {}, with size: {}",
|
||||||
|
//! form.json.name, form.file.size
|
||||||
|
//! )
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[actix_web::main]
|
||||||
|
//! async fn main() -> std::io::Result<()> {
|
||||||
|
//! HttpServer::new(move || App::new().service(post_video))
|
||||||
|
//! .bind(("127.0.0.1", 8080))?
|
||||||
|
//! .run()
|
||||||
|
//! .await
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
#![warn(future_incompatible)]
|
#![warn(future_incompatible)]
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 0.5.3
|
||||||
|
|
||||||
- Add `unicode` crate feature (on-by-default) to switch between `regex` and `regex-lite` as a trade-off between full unicode support and binary size.
|
- Add `unicode` crate feature (on-by-default) to switch between `regex` and `regex-lite` as a trade-off between full unicode support and binary size.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.5.2"
|
version = "0.5.3"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
|
|
||||||
[![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.3)](https://docs.rs/actix-router/0.5.3)
|
||||||
![Version](https://img.shields.io/badge/rustc-1.72+-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.3/status.svg)](https://deps.rs/crate/actix-router/0.5.3)
|
||||||
[![Download](https://img.shields.io/crates/d/actix-router.svg)](https://crates.io/crates/actix-router)
|
[![Download](https://img.shields.io/crates/d/actix-router.svg)](https://crates.io/crates/actix-router)
|
||||||
[![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)
|
||||||
|
|
||||||
|
|
|
@ -154,15 +154,11 @@ impl<T: ResourcePath> Path<T> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get matched parameter by name.
|
/// Returns matched parameter by name.
|
||||||
///
|
///
|
||||||
/// If keyed parameter is not available empty string is used as default value.
|
/// If keyed parameter is not available empty string is used as default value.
|
||||||
pub fn query(&self, key: &str) -> &str {
|
pub fn query(&self, key: &str) -> &str {
|
||||||
if let Some(s) = self.get(key) {
|
self.get(key).unwrap_or_default()
|
||||||
s
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return iterator to items in parameter container.
|
/// Return iterator to items in parameter container.
|
||||||
|
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 0.1.4
|
||||||
|
|
||||||
|
- Add `TestServerConfig::rustls_0_23()` method for Rustls v0.23 support behind new `rustls-0_23` crate feature.
|
||||||
|
- Add `TestServerConfig::disable_redirects()` method.
|
||||||
|
- Various types from `awc`, such as `ClientRequest` and `ClientResponse`, are now re-exported.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||||
|
|
||||||
## 0.1.3
|
## 0.1.3
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-test"
|
name = "actix-test"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
|
@ -29,19 +29,21 @@ rustls-0_20 = ["tls-rustls-0_20", "actix-http/rustls-0_20", "awc/rustls-0_20"]
|
||||||
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
|
# TLS via Rustls v0.22
|
||||||
rustls-0_22 = ["tls-rustls-0_22", "actix-http/rustls-0_22", "awc/rustls-0_22-webpki-roots"]
|
rustls-0_22 = ["tls-rustls-0_22", "actix-http/rustls-0_22", "awc/rustls-0_22-webpki-roots"]
|
||||||
|
# TLS via Rustls v0.23
|
||||||
|
rustls-0_23 = ["tls-rustls-0_23", "actix-http/rustls-0_23", "awc/rustls-0_23-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.6"
|
actix-http = "3.7"
|
||||||
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.5", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4.6", default-features = false, features = ["cookies"] }
|
||||||
awc = { version = "3.4", default-features = false, features = ["cookies"] }
|
awc = { version = "3.5", 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 = [] }
|
||||||
|
@ -53,4 +55,5 @@ 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 }
|
tls-rustls-0_22 = { package = "rustls", version = "0.22", optional = true }
|
||||||
|
tls-rustls-0_23 = { package = "rustls", version = "0.23", default-features = false, optional = true }
|
||||||
tokio = { version = "1.24.2", features = ["sync"] }
|
tokio = { version = "1.24.2", features = ["sync"] }
|
||||||
|
|
|
@ -52,7 +52,7 @@ use actix_web::{
|
||||||
rt::{self, System},
|
rt::{self, System},
|
||||||
web, Error,
|
web, Error,
|
||||||
};
|
};
|
||||||
use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
|
pub use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
@ -145,8 +145,12 @@ where
|
||||||
StreamType::Rustls021(_) => true,
|
StreamType::Rustls021(_) => true,
|
||||||
#[cfg(feature = "rustls-0_22")]
|
#[cfg(feature = "rustls-0_22")]
|
||||||
StreamType::Rustls022(_) => true,
|
StreamType::Rustls022(_) => true,
|
||||||
|
#[cfg(feature = "rustls-0_23")]
|
||||||
|
StreamType::Rustls023(_) => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let client_cfg = cfg.clone();
|
||||||
|
|
||||||
// run server in separate orphaned thread
|
// run server in separate orphaned thread
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
rt::System::new().block_on(async move {
|
rt::System::new().block_on(async move {
|
||||||
|
@ -371,6 +375,48 @@ where
|
||||||
.rustls_0_22(config.clone())
|
.rustls_0_22(config.clone())
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "rustls-0_23")]
|
||||||
|
StreamType::Rustls023(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_23(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_23(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_23(config.clone())
|
||||||
|
}),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
.expect("test server could not be created");
|
.expect("test server could not be created");
|
||||||
|
|
||||||
|
@ -416,7 +462,13 @@ where
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Client::builder().connector(connector).finish()
|
let mut client_builder = Client::builder().connector(connector);
|
||||||
|
|
||||||
|
if client_cfg.disable_redirects {
|
||||||
|
client_builder = client_builder.disable_redirects();
|
||||||
|
}
|
||||||
|
|
||||||
|
client_builder.finish()
|
||||||
};
|
};
|
||||||
|
|
||||||
TestServer {
|
TestServer {
|
||||||
|
@ -436,6 +488,7 @@ enum HttpVer {
|
||||||
Both,
|
Both,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum StreamType {
|
enum StreamType {
|
||||||
Tcp,
|
Tcp,
|
||||||
|
@ -447,6 +500,8 @@ enum StreamType {
|
||||||
Rustls021(tls_rustls_0_21::ServerConfig),
|
Rustls021(tls_rustls_0_21::ServerConfig),
|
||||||
#[cfg(feature = "rustls-0_22")]
|
#[cfg(feature = "rustls-0_22")]
|
||||||
Rustls022(tls_rustls_0_22::ServerConfig),
|
Rustls022(tls_rustls_0_22::ServerConfig),
|
||||||
|
#[cfg(feature = "rustls-0_23")]
|
||||||
|
Rustls023(tls_rustls_0_23::ServerConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create default test server config.
|
/// Create default test server config.
|
||||||
|
@ -461,6 +516,7 @@ pub struct TestServerConfig {
|
||||||
client_request_timeout: Duration,
|
client_request_timeout: Duration,
|
||||||
port: u16,
|
port: u16,
|
||||||
workers: usize,
|
workers: usize,
|
||||||
|
disable_redirects: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TestServerConfig {
|
impl Default for TestServerConfig {
|
||||||
|
@ -478,6 +534,7 @@ impl TestServerConfig {
|
||||||
client_request_timeout: Duration::from_secs(5),
|
client_request_timeout: Duration::from_secs(5),
|
||||||
port: 0,
|
port: 0,
|
||||||
workers: 1,
|
workers: 1,
|
||||||
|
disable_redirects: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,6 +594,13 @@ impl TestServerConfig {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Accepts secure connections via Rustls v0.23.
|
||||||
|
#[cfg(feature = "rustls-0_23")]
|
||||||
|
pub fn rustls_0_23(mut self, config: tls_rustls_0_23::ServerConfig) -> Self {
|
||||||
|
self.stream = StreamType::Rustls023(config);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets client timeout for first request.
|
/// 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;
|
||||||
|
@ -558,6 +622,15 @@ impl TestServerConfig {
|
||||||
self.workers = workers;
|
self.workers = workers;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Instruct the client to not follow redirects.
|
||||||
|
///
|
||||||
|
/// By default, the client will follow up to 10 consecutive redirects
|
||||||
|
/// before giving up.
|
||||||
|
pub fn disable_redirects(mut self) -> Self {
|
||||||
|
self.disable_redirects = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A basic HTTP server controller that simplifies the process of writing integration tests for
|
/// A basic HTTP server controller that simplifies the process of writing integration tests for
|
||||||
|
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 4.3.0
|
||||||
|
|
||||||
|
- Add `#[scope]` macro.
|
||||||
|
- Add `compat-routing-macros-force-pub` crate feature which, on-by-default, which when disabled causes handlers to inherit their attached function's visibility.
|
||||||
|
- Prevent inclusion of default `actix-router` features.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||||
|
|
||||||
## 4.2.2
|
## 4.2.2
|
||||||
|
|
|
@ -1,21 +1,26 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-web-codegen"
|
name = "actix-web-codegen"
|
||||||
version = "4.2.2"
|
version = "4.3.0"
|
||||||
description = "Routing and runtime macros for Actix Web"
|
description = "Routing and runtime macros for Actix Web"
|
||||||
homepage = "https://actix.rs"
|
|
||||||
repository = "https://github.com/actix/actix-web"
|
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
]
|
]
|
||||||
license = "MIT OR Apache-2.0"
|
homepage.workspace = true
|
||||||
edition = "2021"
|
repository.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["compat-routing-macros-force-pub"]
|
||||||
|
compat-routing-macros-force-pub = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-router = "0.5"
|
actix-router = { version = "0.5", default-features = false }
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
syn = { version = "2", features = ["full", "extra-traits"] }
|
syn = { version = "2", features = ["full", "extra-traits"] }
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
|
|
||||||
[![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.3.0)](https://docs.rs/actix-web-codegen/4.3.0)
|
||||||
![Version](https://img.shields.io/badge/rustc-1.72+-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.3.0/status.svg)](https://deps.rs/crate/actix-web-codegen/4.3.0)
|
||||||
[![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen)
|
[![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen)
|
||||||
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
|
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,7 @@ use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
mod route;
|
mod route;
|
||||||
|
mod scope;
|
||||||
|
|
||||||
/// Creates resource handler , allowing multiple HTTP method guards.
|
/// Creates resource handler , allowing multiple HTTP method guards.
|
||||||
///
|
///
|
||||||
|
@ -198,6 +199,43 @@ method_macro!(Trace, trace);
|
||||||
method_macro!(Patch, patch);
|
method_macro!(Patch, patch);
|
||||||
method_macro!(All, all);
|
method_macro!(All, all);
|
||||||
|
|
||||||
|
/// Prepends a path prefix to all handlers using routing macros inside the attached module.
|
||||||
|
///
|
||||||
|
/// # Syntax
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use actix_web_codegen::scope;
|
||||||
|
/// #[scope("/prefix")]
|
||||||
|
/// mod api {
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `"/prefix"` - Raw literal string to be prefixed onto contained handlers' paths.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use actix_web_codegen::{scope, get};
|
||||||
|
/// # use actix_web::Responder;
|
||||||
|
/// #[scope("/api")]
|
||||||
|
/// mod api {
|
||||||
|
/// # use super::*;
|
||||||
|
/// #[get("/hello")]
|
||||||
|
/// pub async fn hello() -> impl Responder {
|
||||||
|
/// // this has path /api/hello
|
||||||
|
/// "Hello, world!"
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn scope(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
scope::with_scope(args, input)
|
||||||
|
}
|
||||||
|
|
||||||
/// Marks async main function as the Actix Web system entry-point.
|
/// Marks async main function as the Actix Web system entry-point.
|
||||||
///
|
///
|
||||||
/// Note that Actix Web also works under `#[tokio::main]` since version 4.0. However, this macro is
|
/// Note that Actix Web also works under `#[tokio::main]` since version 4.0. However, this macro is
|
||||||
|
@ -241,3 +279,15 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
output.extend(item);
|
output.extend(item);
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the error to a token stream and appends it to the original input.
|
||||||
|
///
|
||||||
|
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
||||||
|
/// recover and show more precise errors within the macro body.
|
||||||
|
///
|
||||||
|
/// See <https://github.com/rust-analyzer/rust-analyzer/issues/10468> for more info.
|
||||||
|
fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
|
||||||
|
let compile_err = TokenStream::from(err.to_compile_error());
|
||||||
|
item.extend(compile_err);
|
||||||
|
item
|
||||||
|
}
|
||||||
|
|
|
@ -6,10 +6,12 @@ use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
use quote::{quote, ToTokens, TokenStreamExt};
|
use quote::{quote, ToTokens, TokenStreamExt};
|
||||||
use syn::{punctuated::Punctuated, Ident, LitStr, Path, Token};
|
use syn::{punctuated::Punctuated, Ident, LitStr, Path, Token};
|
||||||
|
|
||||||
|
use crate::input_and_compile_error;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RouteArgs {
|
pub struct RouteArgs {
|
||||||
path: syn::LitStr,
|
pub(crate) path: syn::LitStr,
|
||||||
options: Punctuated<syn::MetaNameValue, Token![,]>,
|
pub(crate) options: Punctuated<syn::MetaNameValue, Token![,]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl syn::parse::Parse for RouteArgs {
|
impl syn::parse::Parse for RouteArgs {
|
||||||
|
@ -79,7 +81,7 @@ macro_rules! standard_method_type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_path(method: &Path) -> Result<Self, ()> {
|
pub(crate) fn from_path(method: &Path) -> Result<Self, ()> {
|
||||||
match () {
|
match () {
|
||||||
$(_ if method.is_ident(stringify!($lower)) => Ok(Self::$variant),)+
|
$(_ if method.is_ident(stringify!($lower)) => Ok(Self::$variant),)+
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
|
@ -412,6 +414,13 @@ impl ToTokens for Route {
|
||||||
doc_attributes,
|
doc_attributes,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
#[allow(unused_variables)] // used when force-pub feature is disabled
|
||||||
|
let vis = &ast.vis;
|
||||||
|
|
||||||
|
// TODO(breaking): remove this force-pub forwards-compatibility feature
|
||||||
|
#[cfg(feature = "compat-routing-macros-force-pub")]
|
||||||
|
let vis = syn::Visibility::Public(<Token![pub]>::default());
|
||||||
|
|
||||||
let registrations: TokenStream2 = args
|
let registrations: TokenStream2 = args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|args| {
|
.map(|args| {
|
||||||
|
@ -459,7 +468,7 @@ impl ToTokens for Route {
|
||||||
let stream = quote! {
|
let stream = quote! {
|
||||||
#(#doc_attributes)*
|
#(#doc_attributes)*
|
||||||
#[allow(non_camel_case_types, missing_docs)]
|
#[allow(non_camel_case_types, missing_docs)]
|
||||||
pub struct #name;
|
#vis struct #name;
|
||||||
|
|
||||||
impl ::actix_web::dev::HttpServiceFactory for #name {
|
impl ::actix_web::dev::HttpServiceFactory for #name {
|
||||||
fn register(self, __config: &mut actix_web::dev::AppService) {
|
fn register(self, __config: &mut actix_web::dev::AppService) {
|
||||||
|
@ -543,15 +552,3 @@ pub(crate) fn with_methods(input: TokenStream) -> TokenStream {
|
||||||
Err(err) => input_and_compile_error(input, err),
|
Err(err) => input_and_compile_error(input, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the error to a token stream and appends it to the original input.
|
|
||||||
///
|
|
||||||
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
|
||||||
/// recover and show more precise errors within the macro body.
|
|
||||||
///
|
|
||||||
/// See <https://github.com/rust-analyzer/rust-analyzer/issues/10468> for more info.
|
|
||||||
fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
|
|
||||||
let compile_err = TokenStream::from(err.to_compile_error());
|
|
||||||
item.extend(compile_err);
|
|
||||||
item
|
|
||||||
}
|
|
||||||
|
|
103
actix-web-codegen/src/scope.rs
Normal file
103
actix-web-codegen/src/scope.rs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
|
use quote::{quote, ToTokens as _};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
input_and_compile_error,
|
||||||
|
route::{MethodType, RouteArgs},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn with_scope(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
match with_scope_inner(args, input.clone()) {
|
||||||
|
Ok(stream) => stream,
|
||||||
|
Err(err) => input_and_compile_error(input, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_scope_inner(args: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
if args.is_empty() {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
Span::call_site(),
|
||||||
|
"missing arguments for scope macro, expected: #[scope(\"/prefix\")]",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let scope_prefix = syn::parse::<syn::LitStr>(args.clone()).map_err(|err| {
|
||||||
|
syn::Error::new(
|
||||||
|
err.span(),
|
||||||
|
"argument to scope macro is not a string literal, expected: #[scope(\"/prefix\")]",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let scope_prefix_value = scope_prefix.value();
|
||||||
|
|
||||||
|
if scope_prefix_value.ends_with('/') {
|
||||||
|
// trailing slashes cause non-obvious problems
|
||||||
|
// it's better to point them out to developers rather than
|
||||||
|
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
scope_prefix.span(),
|
||||||
|
"scopes should not have trailing slashes; see https://docs.rs/actix-web/4/actix_web/struct.Scope.html#avoid-trailing-slashes",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut module = syn::parse::<syn::ItemMod>(input).map_err(|err| {
|
||||||
|
syn::Error::new(err.span(), "#[scope] macro must be attached to a module")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// modify any routing macros (method or route[s]) attached to
|
||||||
|
// functions by prefixing them with this scope macro's argument
|
||||||
|
if let Some((_, items)) = &mut module.content {
|
||||||
|
for item in items {
|
||||||
|
if let syn::Item::Fn(fun) = item {
|
||||||
|
fun.attrs = fun
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.map(|attr| modify_attribute_with_scope(attr, &scope_prefix_value))
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(module.to_token_stream().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the attribute is a method type and has a route path, then modifies it.
|
||||||
|
fn modify_attribute_with_scope(attr: &syn::Attribute, scope_path: &str) -> syn::Attribute {
|
||||||
|
match (attr.parse_args::<RouteArgs>(), attr.clone().meta) {
|
||||||
|
(Ok(route_args), syn::Meta::List(meta_list)) if has_allowed_methods_in_scope(attr) => {
|
||||||
|
let modified_path = format!("{}{}", scope_path, route_args.path.value());
|
||||||
|
|
||||||
|
let options_tokens: Vec<TokenStream2> = route_args
|
||||||
|
.options
|
||||||
|
.iter()
|
||||||
|
.map(|option| {
|
||||||
|
quote! { ,#option }
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let combined_options_tokens: TokenStream2 =
|
||||||
|
options_tokens
|
||||||
|
.into_iter()
|
||||||
|
.fold(TokenStream2::new(), |mut acc, ts| {
|
||||||
|
acc.extend(std::iter::once(ts));
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
|
||||||
|
syn::Attribute {
|
||||||
|
meta: syn::Meta::List(syn::MetaList {
|
||||||
|
tokens: quote! { #modified_path #combined_options_tokens },
|
||||||
|
..meta_list.clone()
|
||||||
|
}),
|
||||||
|
..attr.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => attr.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_allowed_methods_in_scope(attr: &syn::Attribute) -> bool {
|
||||||
|
MethodType::from_path(attr.path()).is_ok()
|
||||||
|
|| attr.path().is_ident("route")
|
||||||
|
|| attr.path().is_ident("ROUTE")
|
||||||
|
}
|
200
actix-web-codegen/tests/scopes.rs
Normal file
200
actix-web-codegen/tests/scopes.rs
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
use actix_web::{guard::GuardContext, http, http::header, web, App, HttpResponse, Responder};
|
||||||
|
use actix_web_codegen::{delete, get, post, route, routes, scope};
|
||||||
|
|
||||||
|
pub fn image_guard(ctx: &GuardContext) -> bool {
|
||||||
|
ctx.header::<header::Accept>()
|
||||||
|
.map(|h| h.preference() == "image/*")
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[scope("/test")]
|
||||||
|
mod scope_module {
|
||||||
|
// ensure that imports can be brought into the scope
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[get("/test/guard", guard = "image_guard")]
|
||||||
|
pub async fn guard() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/test")]
|
||||||
|
pub async fn test() -> impl Responder {
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/twice-test/{value}")]
|
||||||
|
pub async fn twice(value: web::Path<String>) -> impl actix_web::Responder {
|
||||||
|
let int_value: i32 = value.parse().unwrap_or(0);
|
||||||
|
let doubled = int_value * 2;
|
||||||
|
HttpResponse::Ok().body(format!("Twice value: {}", doubled))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/test")]
|
||||||
|
pub async fn post() -> impl Responder {
|
||||||
|
HttpResponse::Ok().body("post works")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[delete("/test")]
|
||||||
|
pub async fn delete() -> impl Responder {
|
||||||
|
"delete works"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[route("/test", method = "PUT", method = "PATCH", method = "CUSTOM")]
|
||||||
|
pub async fn multiple_shared_path() -> impl Responder {
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[routes]
|
||||||
|
#[head("/test1")]
|
||||||
|
#[connect("/test2")]
|
||||||
|
#[options("/test3")]
|
||||||
|
#[trace("/test4")]
|
||||||
|
pub async fn multiple_separate_paths() -> impl Responder {
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
// test calling this from other mod scope with scope attribute...
|
||||||
|
pub fn mod_common(message: String) -> impl actix_web::Responder {
|
||||||
|
HttpResponse::Ok().body(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scope doc string to check in cargo expand.
|
||||||
|
#[scope("/v1")]
|
||||||
|
mod mod_scope_v1 {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Route doc string to check in cargo expand.
|
||||||
|
#[get("/test")]
|
||||||
|
pub async fn test() -> impl Responder {
|
||||||
|
scope_module::mod_common("version1 works".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[scope("/v2")]
|
||||||
|
mod mod_scope_v2 {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// check to make sure non-function tokens in the scope block are preserved...
|
||||||
|
enum TestEnum {
|
||||||
|
Works,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/test")]
|
||||||
|
pub async fn test() -> impl Responder {
|
||||||
|
// make sure this type still exists...
|
||||||
|
let test_enum = TestEnum::Works;
|
||||||
|
|
||||||
|
match test_enum {
|
||||||
|
TestEnum::Works => scope_module::mod_common("version2 works".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn scope_get_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::test));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/test/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn scope_get_param_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::twice));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/test/twice-test/4"));
|
||||||
|
let mut response = request.send().await.unwrap();
|
||||||
|
let body = response.body().await.unwrap();
|
||||||
|
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
assert_eq!(body_str, "Twice value: 8");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn scope_post_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::post));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::POST, srv.url("/test/test"));
|
||||||
|
let mut response = request.send().await.unwrap();
|
||||||
|
let body = response.body().await.unwrap();
|
||||||
|
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
assert_eq!(body_str, "post works");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn multiple_shared_path_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::multiple_shared_path));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::PUT, srv.url("/test/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::PATCH, srv.url("/test/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn multiple_multi_path_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::multiple_separate_paths));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::HEAD, srv.url("/test/test1"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::CONNECT, srv.url("/test/test2"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::OPTIONS, srv.url("/test/test3"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::TRACE, srv.url("/test/test4"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn scope_delete_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::delete));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::DELETE, srv.url("/test/test"));
|
||||||
|
let mut response = request.send().await.unwrap();
|
||||||
|
let body = response.body().await.unwrap();
|
||||||
|
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
assert_eq!(body_str, "delete works");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn scope_get_with_guard_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::guard));
|
||||||
|
|
||||||
|
let request = srv
|
||||||
|
.request(http::Method::GET, srv.url("/test/test/guard"))
|
||||||
|
.insert_header(("Accept", "image/*"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn scope_v1_v2_async() {
|
||||||
|
let srv = actix_test::start(|| {
|
||||||
|
App::new()
|
||||||
|
.service(mod_scope_v1::test)
|
||||||
|
.service(mod_scope_v2::test)
|
||||||
|
});
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/v1/test"));
|
||||||
|
let mut response = request.send().await.unwrap();
|
||||||
|
let body = response.body().await.unwrap();
|
||||||
|
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
assert_eq!(body_str, "version1 works");
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/v2/test"));
|
||||||
|
let mut response = request.send().await.unwrap();
|
||||||
|
let body = response.body().await.unwrap();
|
||||||
|
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
assert_eq!(body_str, "version2 works");
|
||||||
|
}
|
|
@ -18,6 +18,11 @@ fn compile_macros() {
|
||||||
t.compile_fail("tests/trybuild/routes-missing-method-fail.rs");
|
t.compile_fail("tests/trybuild/routes-missing-method-fail.rs");
|
||||||
t.compile_fail("tests/trybuild/routes-missing-args-fail.rs");
|
t.compile_fail("tests/trybuild/routes-missing-args-fail.rs");
|
||||||
|
|
||||||
|
t.compile_fail("tests/trybuild/scope-on-handler.rs");
|
||||||
|
t.compile_fail("tests/trybuild/scope-missing-args.rs");
|
||||||
|
t.compile_fail("tests/trybuild/scope-invalid-args.rs");
|
||||||
|
t.compile_fail("tests/trybuild/scope-trailing-slash.rs");
|
||||||
|
|
||||||
t.pass("tests/trybuild/docstring-ok.rs");
|
t.pass("tests/trybuild/docstring-ok.rs");
|
||||||
|
|
||||||
t.pass("tests/trybuild/test-runtime.rs");
|
t.pass("tests/trybuild/test-runtime.rs");
|
||||||
|
|
|
@ -20,10 +20,7 @@ error: custom attribute panicked
|
||||||
13 | #[get("/{}")]
|
13 | #[get("/{}")]
|
||||||
| ^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: message: Wrong path pattern: "/{}" regex parse error:
|
= help: message: Wrong path pattern: "/{}" empty capture group names are not allowed
|
||||||
((?s-m)^/(?P<>[^/]+))$
|
|
||||||
^
|
|
||||||
error: empty capture group name
|
|
||||||
|
|
||||||
error: custom attribute panicked
|
error: custom attribute panicked
|
||||||
--> $DIR/route-malformed-path-fail.rs:23:1
|
--> $DIR/route-malformed-path-fail.rs:23:1
|
||||||
|
|
14
actix-web-codegen/tests/trybuild/scope-invalid-args.rs
Normal file
14
actix-web-codegen/tests/trybuild/scope-invalid-args.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
use actix_web_codegen::scope;
|
||||||
|
|
||||||
|
const PATH: &str = "/api";
|
||||||
|
|
||||||
|
#[scope(PATH)]
|
||||||
|
mod api_const {}
|
||||||
|
|
||||||
|
#[scope(true)]
|
||||||
|
mod api_bool {}
|
||||||
|
|
||||||
|
#[scope(123)]
|
||||||
|
mod api_num {}
|
||||||
|
|
||||||
|
fn main() {}
|
17
actix-web-codegen/tests/trybuild/scope-invalid-args.stderr
Normal file
17
actix-web-codegen/tests/trybuild/scope-invalid-args.stderr
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
error: argument to scope macro is not a string literal, expected: #[scope("/prefix")]
|
||||||
|
--> tests/trybuild/scope-invalid-args.rs:5:9
|
||||||
|
|
|
||||||
|
5 | #[scope(PATH)]
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: argument to scope macro is not a string literal, expected: #[scope("/prefix")]
|
||||||
|
--> tests/trybuild/scope-invalid-args.rs:8:9
|
||||||
|
|
|
||||||
|
8 | #[scope(true)]
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: argument to scope macro is not a string literal, expected: #[scope("/prefix")]
|
||||||
|
--> tests/trybuild/scope-invalid-args.rs:11:9
|
||||||
|
|
|
||||||
|
11 | #[scope(123)]
|
||||||
|
| ^^^
|
6
actix-web-codegen/tests/trybuild/scope-missing-args.rs
Normal file
6
actix-web-codegen/tests/trybuild/scope-missing-args.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
use actix_web_codegen::scope;
|
||||||
|
|
||||||
|
#[scope]
|
||||||
|
mod api {}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
error: missing arguments for scope macro, expected: #[scope("/prefix")]
|
||||||
|
--> tests/trybuild/scope-missing-args.rs:3:1
|
||||||
|
|
|
||||||
|
3 | #[scope]
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in the attribute macro `scope` (in Nightly builds, run with -Z macro-backtrace for more info)
|
8
actix-web-codegen/tests/trybuild/scope-on-handler.rs
Normal file
8
actix-web-codegen/tests/trybuild/scope-on-handler.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use actix_web_codegen::scope;
|
||||||
|
|
||||||
|
#[scope("/api")]
|
||||||
|
async fn index() -> &'static str {
|
||||||
|
"Hello World!"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
5
actix-web-codegen/tests/trybuild/scope-on-handler.stderr
Normal file
5
actix-web-codegen/tests/trybuild/scope-on-handler.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: #[scope] macro must be attached to a module
|
||||||
|
--> tests/trybuild/scope-on-handler.rs:4:1
|
||||||
|
|
|
||||||
|
4 | async fn index() -> &'static str {
|
||||||
|
| ^^^^^
|
6
actix-web-codegen/tests/trybuild/scope-trailing-slash.rs
Normal file
6
actix-web-codegen/tests/trybuild/scope-trailing-slash.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
use actix_web_codegen::scope;
|
||||||
|
|
||||||
|
#[scope("/api/")]
|
||||||
|
mod api {}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,5 @@
|
||||||
|
error: scopes should not have trailing slashes; see https://docs.rs/actix-web/4/actix_web/struct.Scope.html#avoid-trailing-slashes
|
||||||
|
--> tests/trybuild/scope-trailing-slash.rs:3:9
|
||||||
|
|
|
||||||
|
3 | #[scope("/api/")]
|
||||||
|
| ^^^^^^^
|
|
@ -2,14 +2,35 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 4.7.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add `middleware::Identity` type.
|
||||||
|
- Add `CustomizeResponder::add_cookie()` method.
|
||||||
|
- Add `guard::GuardContext::app_data()` method.
|
||||||
|
- Add `compat-routing-macros-force-pub` crate feature which (on-by-default) which, when disabled, causes handlers to inherit their attached function's visibility.
|
||||||
|
- Add `compat` crate feature group (on-by-default) which, when disabled, helps with transitioning to some planned v5.0 breaking changes, starting only with `compat-routing-macros-force-pub`.
|
||||||
|
- Implement `From<Box<dyn ResponseError>>` for `Error`.
|
||||||
|
|
||||||
|
## 4.6.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add `unicode` crate feature (on-by-default) to switch between `regex` and `regex-lite` as a trade-off between full unicode support and binary size.
|
- Add `unicode` crate feature (on-by-default) to switch between `regex` and `regex-lite` as a trade-off between full unicode support and binary size.
|
||||||
|
- Add `rustls-0_23` crate feature.
|
||||||
|
- Add `HttpServer::{bind_rustls_0_23, listen_rustls_0_23}()` builder methods.
|
||||||
|
- Add `HttpServer::tls_handshake_timeout()` builder method for `rustls-0_22` and `rustls-0_23`.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Update `brotli` dependency to `6`.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Avoid type confusion with `rustls` in some circumstances.
|
||||||
|
|
||||||
## 4.5.1
|
## 4.5.1
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "4.5.1"
|
version = "4.7.0"
|
||||||
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>",
|
||||||
|
@ -27,6 +27,7 @@ features = [
|
||||||
"rustls-0_20",
|
"rustls-0_20",
|
||||||
"rustls-0_21",
|
"rustls-0_21",
|
||||||
"rustls-0_22",
|
"rustls-0_22",
|
||||||
|
"rustls-0_23",
|
||||||
"compress-brotli",
|
"compress-brotli",
|
||||||
"compress-gzip",
|
"compress-gzip",
|
||||||
"compress-zstd",
|
"compress-zstd",
|
||||||
|
@ -34,13 +35,21 @@ features = [
|
||||||
"secure-cookies",
|
"secure-cookies",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "actix_web"
|
name = "actix_web"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["macros", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "http2", "unicode"]
|
default = [
|
||||||
|
"macros",
|
||||||
|
"compress-brotli",
|
||||||
|
"compress-gzip",
|
||||||
|
"compress-zstd",
|
||||||
|
"cookies",
|
||||||
|
"http2",
|
||||||
|
"unicode",
|
||||||
|
"compat",
|
||||||
|
]
|
||||||
|
|
||||||
# Brotli algorithm content-encoding support
|
# Brotli algorithm content-encoding support
|
||||||
compress-brotli = ["actix-http/compress-brotli", "__compress"]
|
compress-brotli = ["actix-http/compress-brotli", "__compress"]
|
||||||
|
@ -50,14 +59,15 @@ compress-gzip = ["actix-http/compress-gzip", "__compress"]
|
||||||
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
||||||
|
|
||||||
# Routing and runtime proc macros
|
# Routing and runtime proc macros
|
||||||
macros = ["actix-macros", "actix-web-codegen"]
|
macros = ["dep:actix-macros", "dep:actix-web-codegen"]
|
||||||
|
|
||||||
# Cookies support
|
# Cookies support
|
||||||
cookies = ["cookie"]
|
cookies = ["dep:cookie"]
|
||||||
|
|
||||||
# Secure & signed cookies
|
# Secure & signed cookies
|
||||||
secure-cookies = ["cookies", "cookie/secure"]
|
secure-cookies = ["cookies", "cookie/secure"]
|
||||||
|
|
||||||
|
# HTTP/2 support (including h2c).
|
||||||
http2 = ["actix-http/http2"]
|
http2 = ["actix-http/http2"]
|
||||||
|
|
||||||
# TLS via OpenSSL
|
# TLS via OpenSSL
|
||||||
|
@ -71,6 +81,8 @@ rustls-0_20 = ["http2", "actix-http/rustls-0_20", "actix-tls/accept", "actix-tls
|
||||||
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
|
# TLS via Rustls v0.22
|
||||||
rustls-0_22 = ["http2", "actix-http/rustls-0_22", "actix-tls/accept", "actix-tls/rustls-0_22"]
|
rustls-0_22 = ["http2", "actix-http/rustls-0_22", "actix-tls/accept", "actix-tls/rustls-0_22"]
|
||||||
|
# TLS via Rustls v0.23
|
||||||
|
rustls-0_23 = ["http2", "actix-http/rustls-0_23", "actix-tls/accept", "actix-tls/rustls-0_23"]
|
||||||
|
|
||||||
# Full unicode support
|
# Full unicode support
|
||||||
unicode = ["dep:regex", "actix-router/unicode"]
|
unicode = ["dep:regex", "actix-router/unicode"]
|
||||||
|
@ -82,6 +94,14 @@ __compress = []
|
||||||
# io-uring feature only available for Linux OSes.
|
# io-uring feature only available for Linux OSes.
|
||||||
experimental-io-uring = ["actix-server/io-uring"]
|
experimental-io-uring = ["actix-server/io-uring"]
|
||||||
|
|
||||||
|
# Feature group which, when disabled, helps migrate code to v5.0.
|
||||||
|
compat = [
|
||||||
|
"compat-routing-macros-force-pub",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Opt-out forwards-compatibility for handler visibility inheritance fix.
|
||||||
|
compat-routing-macros-force-pub = ["actix-web-codegen?/compat-routing-macros-force-pub"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.5"
|
actix-codec = "0.5"
|
||||||
actix-macros = { version = "0.2.3", optional = true }
|
actix-macros = { version = "0.2.3", optional = true }
|
||||||
|
@ -89,11 +109,11 @@ 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.3", default-features = false, optional = true }
|
actix-tls = { version = "3.4", default-features = false, optional = true }
|
||||||
|
|
||||||
actix-http = { version = "3.6", features = ["ws"] }
|
actix-http = { version = "3.7", features = ["ws"] }
|
||||||
actix-router = { version = "0.5", default-features = false, features = ["http"] }
|
actix-router = { version = "0.5.3", default-features = false, features = ["http"] }
|
||||||
actix-web-codegen = { version = "4.2", optional = true }
|
actix-web-codegen = { version = "4.3", optional = true, default-features = false }
|
||||||
|
|
||||||
ahash = "0.8"
|
ahash = "0.8"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
@ -122,22 +142,23 @@ url = "2.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-files = "0.6"
|
actix-files = "0.6"
|
||||||
actix-test = { version = "0.1", features = ["openssl", "rustls-0_22"] }
|
actix-test = { version = "0.1", features = ["openssl", "rustls-0_23"] }
|
||||||
awc = { version = "3", features = ["openssl"] }
|
awc = { version = "3", features = ["openssl"] }
|
||||||
|
|
||||||
brotli = "3.3.3"
|
brotli = "6"
|
||||||
const-str = "0.5"
|
const-str = "0.5"
|
||||||
|
core_affinity = "0.8"
|
||||||
criterion = { version = "0.5", features = ["html_reports"] }
|
criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
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.12"
|
rcgen = "0.13"
|
||||||
rustls-pemfile = "2"
|
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.22" }
|
tls-rustls = { package = "rustls", version = "0.23" }
|
||||||
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"
|
||||||
|
|
||||||
|
|
|
@ -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.5.1)](https://docs.rs/actix-web/4.5.1)
|
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.7.0)](https://docs.rs/actix-web/4.7.0)
|
||||||
![MSRV](https://img.shields.io/badge/rustc-1.72+-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.5.1/status.svg)](https://deps.rs/crate/actix-web/4.5.1)
|
[![Dependency Status](https://deps.rs/crate/actix-web/4.7.0/status.svg)](https://deps.rs/crate/actix-web/4.7.0)
|
||||||
<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)
|
||||||
|
|
41
actix-web/examples/worker-cpu-pin.rs
Normal file
41
actix-web/examples/worker-cpu-pin.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use std::{
|
||||||
|
io,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
use actix_web::{middleware, web, App, HttpServer};
|
||||||
|
|
||||||
|
async fn hello() -> &'static str {
|
||||||
|
"Hello world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> io::Result<()> {
|
||||||
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
|
|
||||||
|
let core_ids = core_affinity::get_core_ids().unwrap();
|
||||||
|
let n_core_ids = core_ids.len();
|
||||||
|
let next_core_id = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
HttpServer::new(move || {
|
||||||
|
let pin = Arc::clone(&next_core_id).fetch_add(1, Ordering::AcqRel);
|
||||||
|
log::info!(
|
||||||
|
"setting CPU affinity for worker {}: pinning to core {}",
|
||||||
|
thread::current().name().unwrap(),
|
||||||
|
pin,
|
||||||
|
);
|
||||||
|
core_affinity::set_for_current(core_ids[pin]);
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.wrap(middleware::Logger::default())
|
||||||
|
.service(web::resource("/").get(hello))
|
||||||
|
})
|
||||||
|
.bind(("127.0.0.1", 8080))?
|
||||||
|
.workers(n_core_ids)
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
|
@ -112,8 +112,8 @@ where
|
||||||
/// })
|
/// })
|
||||||
/// ```
|
/// ```
|
||||||
#[doc(alias = "manage")]
|
#[doc(alias = "manage")]
|
||||||
pub fn app_data<U: 'static>(mut self, ext: U) -> Self {
|
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
|
||||||
self.extensions.insert(ext);
|
self.extensions.insert(data);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -263,8 +263,9 @@ impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
||||||
let guards = guards.borrow_mut().take().unwrap_or_default();
|
let guards = guards.borrow_mut().take().unwrap_or_default();
|
||||||
let factory_fut = factory.new_service(());
|
let factory_fut = factory.new_service(());
|
||||||
async move {
|
async move {
|
||||||
let service = factory_fut.await?;
|
factory_fut
|
||||||
Ok((path, guards, service))
|
.await
|
||||||
|
.map(move |service| (path, guards, service))
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ impl AppConfig {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn set_host(&mut self, host: &str) {
|
pub(crate) fn set_host(&mut self, host: &str) {
|
||||||
self.host = host.to_owned();
|
host.clone_into(&mut self.host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,12 @@ impl<T: ResponseError + 'static> From<T> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Box<dyn ResponseError>> for Error {
|
||||||
|
fn from(value: Box<dyn ResponseError>) -> Self {
|
||||||
|
Error { cause: value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Error> for Response<BoxBody> {
|
impl From<Error> for Response<BoxBody> {
|
||||||
fn from(err: Error) -> Response<BoxBody> {
|
fn from(err: Error) -> Response<BoxBody> {
|
||||||
err.error_response().into()
|
err.error_response().into()
|
||||||
|
|
|
@ -110,6 +110,12 @@ impl<'a> GuardContext<'a> {
|
||||||
pub fn header<H: Header>(&self) -> Option<H> {
|
pub fn header<H: Header>(&self) -> Option<H> {
|
||||||
H::parse(self.req).ok()
|
H::parse(self.req).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Counterpart to [HttpRequest::app_data](crate::HttpRequest::app_data).
|
||||||
|
#[inline]
|
||||||
|
pub fn app_data<T: 'static>(&self) -> Option<&T> {
|
||||||
|
self.req.app_data()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interface for routing guards.
|
/// Interface for routing guards.
|
||||||
|
@ -512,4 +518,18 @@ mod tests {
|
||||||
.to_srv_request();
|
.to_srv_request();
|
||||||
assert!(guard.check(&req.guard_ctx()));
|
assert!(guard.check(&req.guard_ctx()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn app_data() {
|
||||||
|
const TEST_VALUE: u32 = 42;
|
||||||
|
let guard = fn_guard(|ctx| dbg!(ctx.app_data::<u32>()) == Some(&TEST_VALUE));
|
||||||
|
|
||||||
|
let req = TestRequest::default().app_data(TEST_VALUE).to_srv_request();
|
||||||
|
assert!(guard.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.app_data(TEST_VALUE * 2)
|
||||||
|
.to_srv_request();
|
||||||
|
assert!(!guard.check(&req.guard_ctx()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,10 @@
|
||||||
//! - `compress-gzip` - gzip and deflate content encoding compression support (enabled by default)
|
//! - `compress-gzip` - gzip and deflate content encoding compression support (enabled by default)
|
||||||
//! - `compress-zstd` - zstd content encoding compression support (enabled by default)
|
//! - `compress-zstd` - zstd content encoding compression support (enabled by default)
|
||||||
//! - `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2`
|
//! - `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2`
|
||||||
//! - `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2`
|
//! - `rustls` - HTTPS support via `rustls` 0.20 crate, supports `HTTP/2`
|
||||||
|
//! - `rustls-0_21` - HTTPS support via `rustls` 0.21 crate, supports `HTTP/2`
|
||||||
|
//! - `rustls-0_22` - HTTPS support via `rustls` 0.22 crate, supports `HTTP/2`
|
||||||
|
//! - `rustls-0_23` - HTTPS support via `rustls` 0.23 crate, supports `HTTP/2`
|
||||||
//! - `secure-cookies` - secure cookies support
|
//! - `secure-cookies` - secure cookies support
|
||||||
|
|
||||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
|
@ -142,5 +145,6 @@ codegen_reexport!(delete);
|
||||||
codegen_reexport!(trace);
|
codegen_reexport!(trace);
|
||||||
codegen_reexport!(connect);
|
codegen_reexport!(connect);
|
||||||
codegen_reexport!(options);
|
codegen_reexport!(options);
|
||||||
|
codegen_reexport!(scope);
|
||||||
|
|
||||||
pub(crate) type BoxError = Box<dyn std::error::Error>;
|
pub(crate) type BoxError = Box<dyn std::error::Error>;
|
||||||
|
|
|
@ -38,15 +38,6 @@ pub struct Compat<T> {
|
||||||
transform: T,
|
transform: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl Compat<super::Noop> {
|
|
||||||
pub(crate) fn noop() -> Self {
|
|
||||||
Self {
|
|
||||||
transform: super::Noop,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Compat<T> {
|
impl<T> Compat<T> {
|
||||||
/// Wrap a middleware to give it broader compatibility.
|
/// Wrap a middleware to give it broader compatibility.
|
||||||
pub fn new(middleware: T) -> Self {
|
pub fn new(middleware: T) -> Self {
|
||||||
|
@ -152,7 +143,7 @@ mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
dev::ServiceRequest,
|
dev::ServiceRequest,
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
middleware::{self, Condition, Logger},
|
middleware::{self, Condition, Identity, Logger},
|
||||||
test::{self, call_service, init_service, TestRequest},
|
test::{self, call_service, init_service, TestRequest},
|
||||||
web, App, HttpResponse,
|
web, App, HttpResponse,
|
||||||
};
|
};
|
||||||
|
@ -225,7 +216,7 @@ mod tests {
|
||||||
async fn compat_noop_is_noop() {
|
async fn compat_noop_is_noop() {
|
||||||
let srv = test::ok_service();
|
let srv = test::ok_service();
|
||||||
|
|
||||||
let mw = Compat::noop()
|
let mw = Compat::new(Identity)
|
||||||
.new_transform(srv.into_service())
|
.new_transform(srv.into_service())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -141,7 +141,7 @@ mod tests {
|
||||||
header::{HeaderValue, CONTENT_TYPE},
|
header::{HeaderValue, CONTENT_TYPE},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
},
|
},
|
||||||
middleware::{self, ErrorHandlerResponse, ErrorHandlers},
|
middleware::{self, ErrorHandlerResponse, ErrorHandlers, Identity},
|
||||||
test::{self, TestRequest},
|
test::{self, TestRequest},
|
||||||
web::Bytes,
|
web::Bytes,
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
|
@ -158,7 +158,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compat_with_builtin_middleware() {
|
fn compat_with_builtin_middleware() {
|
||||||
let _ = Condition::new(true, middleware::Compat::noop());
|
let _ = Condition::new(true, middleware::Compat::new(Identity));
|
||||||
let _ = Condition::new(true, middleware::Logger::default());
|
let _ = Condition::new(true, middleware::Logger::default());
|
||||||
let _ = Condition::new(true, middleware::Compress::default());
|
let _ = Condition::new(true, middleware::Compress::default());
|
||||||
let _ = Condition::new(true, middleware::NormalizePath::trim());
|
let _ = Condition::new(true, middleware::NormalizePath::trim());
|
||||||
|
|
|
@ -2,35 +2,39 @@
|
||||||
|
|
||||||
use actix_utils::future::{ready, Ready};
|
use actix_utils::future::{ready, Ready};
|
||||||
|
|
||||||
use crate::dev::{Service, Transform};
|
use crate::dev::{forward_ready, Service, Transform};
|
||||||
|
|
||||||
/// A no-op middleware that passes through request and response untouched.
|
/// A no-op middleware that passes through request and response untouched.
|
||||||
pub(crate) struct Noop;
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Identity;
|
||||||
|
|
||||||
impl<S: Service<Req>, Req> Transform<S, Req> for Noop {
|
impl<S: Service<Req>, Req> Transform<S, Req> for Identity {
|
||||||
type Response = S::Response;
|
type Response = S::Response;
|
||||||
type Error = S::Error;
|
type Error = S::Error;
|
||||||
type Transform = NoopService<S>;
|
type Transform = IdentityMiddleware<S>;
|
||||||
type InitError = ();
|
type InitError = ();
|
||||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn new_transform(&self, service: S) -> Self::Future {
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
ready(Ok(NoopService { service }))
|
ready(Ok(IdentityMiddleware { service }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub(crate) struct NoopService<S> {
|
pub struct IdentityMiddleware<S> {
|
||||||
service: S,
|
service: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: Service<Req>, Req> Service<Req> for NoopService<S> {
|
impl<S: Service<Req>, Req> Service<Req> for IdentityMiddleware<S> {
|
||||||
type Response = S::Response;
|
type Response = S::Response;
|
||||||
type Error = S::Error;
|
type Error = S::Error;
|
||||||
type Future = S::Future;
|
type Future = S::Future;
|
||||||
|
|
||||||
crate::dev::forward_ready!(service);
|
forward_ready!(service);
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn call(&self, req: Req) -> Self::Future {
|
fn call(&self, req: Req) -> Self::Future {
|
||||||
self.service.call(req)
|
self.service.call(req)
|
||||||
}
|
}
|
|
@ -33,13 +33,13 @@
|
||||||
//!
|
//!
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//! # // These aren't snake_case, because they are supposed to be unit structs.
|
//! # // These aren't snake_case, because they are supposed to be unit structs.
|
||||||
//! # let MiddlewareA = middleware::Compress::default();
|
//! # type MiddlewareA = middleware::Compress;
|
||||||
//! # let MiddlewareB = middleware::Compress::default();
|
//! # type MiddlewareB = middleware::Compress;
|
||||||
//! # let MiddlewareC = middleware::Compress::default();
|
//! # type MiddlewareC = middleware::Compress;
|
||||||
//! let app = App::new()
|
//! let app = App::new()
|
||||||
//! .wrap(MiddlewareA)
|
//! .wrap(MiddlewareA::default())
|
||||||
//! .wrap(MiddlewareB)
|
//! .wrap(MiddlewareB::default())
|
||||||
//! .wrap(MiddlewareC)
|
//! .wrap(MiddlewareC::default())
|
||||||
//! .service(service);
|
//! .service(service);
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
@ -218,31 +218,27 @@
|
||||||
//! [lab_from_fn]: https://docs.rs/actix-web-lab/latest/actix_web_lab/middleware/fn.from_fn.html
|
//! [lab_from_fn]: https://docs.rs/actix-web-lab/latest/actix_web_lab/middleware/fn.from_fn.html
|
||||||
|
|
||||||
mod compat;
|
mod compat;
|
||||||
|
#[cfg(feature = "__compress")]
|
||||||
|
mod compress;
|
||||||
mod condition;
|
mod condition;
|
||||||
mod default_headers;
|
mod default_headers;
|
||||||
mod err_handlers;
|
mod err_handlers;
|
||||||
|
mod identity;
|
||||||
mod logger;
|
mod logger;
|
||||||
#[cfg(test)]
|
|
||||||
mod noop;
|
|
||||||
mod normalize;
|
mod normalize;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(feature = "__compress")]
|
||||||
pub(crate) use self::noop::Noop;
|
pub use self::compress::Compress;
|
||||||
pub use self::{
|
pub use self::{
|
||||||
compat::Compat,
|
compat::Compat,
|
||||||
condition::Condition,
|
condition::Condition,
|
||||||
default_headers::DefaultHeaders,
|
default_headers::DefaultHeaders,
|
||||||
err_handlers::{ErrorHandlerResponse, ErrorHandlers},
|
err_handlers::{ErrorHandlerResponse, ErrorHandlers},
|
||||||
|
identity::Identity,
|
||||||
logger::Logger,
|
logger::Logger,
|
||||||
normalize::{NormalizePath, TrailingSlash},
|
normalize::{NormalizePath, TrailingSlash},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "__compress")]
|
|
||||||
mod compress;
|
|
||||||
|
|
||||||
#[cfg(feature = "__compress")]
|
|
||||||
pub use self::compress::Compress;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -771,7 +771,7 @@ mod tests {
|
||||||
data3: web::Data<f64>| {
|
data3: web::Data<f64>| {
|
||||||
assert_eq!(**data1, 10);
|
assert_eq!(**data1, 10);
|
||||||
assert_eq!(**data2, '*');
|
assert_eq!(**data2, '*');
|
||||||
let error = std::f64::EPSILON;
|
let error = f64::EPSILON;
|
||||||
assert!((**data3 - 1.0).abs() < error);
|
assert!((**data3 - 1.0).abs() < error);
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,7 +7,7 @@ use actix_http::{
|
||||||
|
|
||||||
use crate::{HttpRequest, HttpResponse, Responder};
|
use crate::{HttpRequest, HttpResponse, Responder};
|
||||||
|
|
||||||
/// Allows overriding status code and headers for a [`Responder`].
|
/// Allows overriding status code and headers (including cookies) for a [`Responder`].
|
||||||
///
|
///
|
||||||
/// Created by calling the [`customize`](Responder::customize) method on a [`Responder`] type.
|
/// Created by calling the [`customize`](Responder::customize) method on a [`Responder`] type.
|
||||||
pub struct CustomizeResponder<R> {
|
pub struct CustomizeResponder<R> {
|
||||||
|
@ -137,6 +137,29 @@ impl<R: Responder> CustomizeResponder<R> {
|
||||||
Some(&mut self.inner)
|
Some(&mut self.inner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Appends a `cookie` to the final response.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Final response will be an error if `cookie` cannot be converted into a valid header value.
|
||||||
|
#[cfg(feature = "cookies")]
|
||||||
|
pub fn add_cookie(mut self, cookie: &crate::cookie::Cookie<'_>) -> Self {
|
||||||
|
use actix_http::header::{TryIntoHeaderValue as _, SET_COOKIE};
|
||||||
|
|
||||||
|
if let Some(inner) = self.inner() {
|
||||||
|
match cookie.to_string().try_into_value() {
|
||||||
|
Ok(val) => {
|
||||||
|
inner.append_headers.append(SET_COOKIE, val);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
self.error = Some(err.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Responder for CustomizeResponder<T>
|
impl<T> Responder for CustomizeResponder<T>
|
||||||
|
@ -175,6 +198,7 @@ mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
cookie::Cookie,
|
||||||
http::header::{HeaderValue, CONTENT_TYPE},
|
http::header::{HeaderValue, CONTENT_TYPE},
|
||||||
test::TestRequest,
|
test::TestRequest,
|
||||||
};
|
};
|
||||||
|
@ -209,6 +233,22 @@ mod tests {
|
||||||
to_bytes(res.into_body()).await.unwrap(),
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
Bytes::from_static(b"test"),
|
Bytes::from_static(b"test"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let res = "test"
|
||||||
|
.to_string()
|
||||||
|
.customize()
|
||||||
|
.add_cookie(&Cookie::new("name", "value"))
|
||||||
|
.respond_to(&req);
|
||||||
|
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
assert_eq!(
|
||||||
|
res.cookies().collect::<Vec<Cookie<'_>>>(),
|
||||||
|
vec![Cookie::new("name", "value")],
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
|
|
@ -470,8 +470,9 @@ impl ServiceFactory<ServiceRequest> for ScopeFactory {
|
||||||
let guards = guards.borrow_mut().take().unwrap_or_default();
|
let guards = guards.borrow_mut().take().unwrap_or_default();
|
||||||
let factory_fut = factory.new_service(());
|
let factory_fut = factory.new_service(());
|
||||||
async move {
|
async move {
|
||||||
let service = factory_fut.await?;
|
factory_fut
|
||||||
Ok((path, guards, service))
|
.await
|
||||||
|
.map(move |service| (path, guards, service))
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ use std::{
|
||||||
feature = "rustls-0_20",
|
feature = "rustls-0_20",
|
||||||
feature = "rustls-0_21",
|
feature = "rustls-0_21",
|
||||||
feature = "rustls-0_22",
|
feature = "rustls-0_22",
|
||||||
|
feature = "rustls-0_23",
|
||||||
))]
|
))]
|
||||||
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};
|
||||||
|
@ -242,7 +243,13 @@ where
|
||||||
/// time, the connection is closed.
|
/// time, the connection is closed.
|
||||||
///
|
///
|
||||||
/// By default, the handshake timeout is 3 seconds.
|
/// By default, the handshake timeout is 3 seconds.
|
||||||
#[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",
|
||||||
|
feature = "rustls-0_23",
|
||||||
|
))]
|
||||||
pub fn tls_handshake_timeout(self, dur: Duration) -> Self {
|
pub fn tls_handshake_timeout(self, dur: Duration) -> Self {
|
||||||
self.config
|
self.config
|
||||||
.lock()
|
.lock()
|
||||||
|
@ -270,6 +277,10 @@ where
|
||||||
/// Rustls v0.20.
|
/// Rustls v0.20.
|
||||||
/// - `actix_tls::accept::rustls_0_21::TlsStream<actix_web::rt::net::TcpStream>` when using
|
/// - `actix_tls::accept::rustls_0_21::TlsStream<actix_web::rt::net::TcpStream>` when using
|
||||||
/// Rustls v0.21.
|
/// Rustls v0.21.
|
||||||
|
/// - `actix_tls::accept::rustls_0_22::TlsStream<actix_web::rt::net::TcpStream>` when using
|
||||||
|
/// Rustls v0.22.
|
||||||
|
/// - `actix_tls::accept::rustls_0_23::TlsStream<actix_web::rt::net::TcpStream>` when using
|
||||||
|
/// Rustls v0.23.
|
||||||
/// - `actix_web::rt::net::TcpStream` when no encryption is used.
|
/// - `actix_web::rt::net::TcpStream` when no encryption is used.
|
||||||
///
|
///
|
||||||
/// See the `on_connect` example for additional details.
|
/// See the `on_connect` example for additional details.
|
||||||
|
@ -466,6 +477,25 @@ where
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves socket address(es) and binds server to created listener(s) for TLS connections
|
||||||
|
/// using Rustls v0.23.
|
||||||
|
///
|
||||||
|
/// 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_23")]
|
||||||
|
pub fn bind_rustls_0_23<A: net::ToSocketAddrs>(
|
||||||
|
mut self,
|
||||||
|
addrs: A,
|
||||||
|
config: actix_tls::accept::rustls_0_23::reexports::ServerConfig,
|
||||||
|
) -> io::Result<Self> {
|
||||||
|
let sockets = bind_addrs(addrs, self.backlog)?;
|
||||||
|
for lst in sockets {
|
||||||
|
self = self.listen_rustls_0_23_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.
|
||||||
///
|
///
|
||||||
|
@ -595,7 +625,7 @@ where
|
||||||
/// Binds to existing listener for accepting incoming TLS connection requests using Rustls
|
/// Binds to existing listener for accepting incoming TLS connection requests using Rustls
|
||||||
/// v0.21.
|
/// v0.21.
|
||||||
///
|
///
|
||||||
/// See [`listen()`](Self::listen) for more details on the `lst` argument.
|
/// See [`listen()`](Self::listen()) for more details on the `lst` argument.
|
||||||
///
|
///
|
||||||
/// ALPN protocols "h2" and "http/1.1" are added to any configured ones.
|
/// ALPN protocols "h2" and "http/1.1" are added to any configured ones.
|
||||||
#[cfg(feature = "rustls-0_21")]
|
#[cfg(feature = "rustls-0_21")]
|
||||||
|
@ -712,7 +742,7 @@ where
|
||||||
/// Binds to existing listener for accepting incoming TLS connection requests using Rustls
|
/// Binds to existing listener for accepting incoming TLS connection requests using Rustls
|
||||||
/// v0.22.
|
/// v0.22.
|
||||||
///
|
///
|
||||||
/// See [`listen()`](Self::listen) for more details on the `lst` argument.
|
/// See [`listen()`](Self::listen()) for more details on the `lst` argument.
|
||||||
///
|
///
|
||||||
/// ALPN protocols "h2" and "http/1.1" are added to any configured ones.
|
/// ALPN protocols "h2" and "http/1.1" are added to any configured ones.
|
||||||
#[cfg(feature = "rustls-0_22")]
|
#[cfg(feature = "rustls-0_22")]
|
||||||
|
@ -775,6 +805,72 @@ where
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Binds to existing listener for accepting incoming TLS connection requests using Rustls
|
||||||
|
/// v0.23.
|
||||||
|
///
|
||||||
|
/// 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_23")]
|
||||||
|
pub fn listen_rustls_0_23(
|
||||||
|
self,
|
||||||
|
lst: net::TcpListener,
|
||||||
|
config: actix_tls::accept::rustls_0_23::reexports::ServerConfig,
|
||||||
|
) -> io::Result<Self> {
|
||||||
|
self.listen_rustls_0_23_inner(lst, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls-0_23")]
|
||||||
|
fn listen_rustls_0_23_inner(
|
||||||
|
mut self,
|
||||||
|
lst: net::TcpListener,
|
||||||
|
config: actix_tls::accept::rustls_0_23::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_23_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.
|
||||||
|
|
|
@ -64,9 +64,11 @@ fn ssl_acceptor() -> openssl::ssl::SslAcceptorBuilder {
|
||||||
x509::X509,
|
x509::X509,
|
||||||
};
|
};
|
||||||
|
|
||||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
let rcgen::CertifiedKey { cert, key_pair } =
|
||||||
let cert_file = cert.serialize_pem().unwrap();
|
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||||
let key_file = cert.serialize_private_key_pem();
|
let cert_file = cert.pem();
|
||||||
|
let key_file = key_pair.serialize_pem();
|
||||||
|
|
||||||
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
||||||
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -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_22")]
|
#[cfg(feature = "rustls-0_23")]
|
||||||
extern crate tls_rustls as rustls;
|
extern crate tls_rustls as rustls;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -34,9 +34,11 @@ const STR: &str = const_str::repeat!(S, 100);
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
fn openssl_config() -> SslAcceptor {
|
fn openssl_config() -> SslAcceptor {
|
||||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
let rcgen::CertifiedKey { cert, key_pair } =
|
||||||
let cert_file = cert.serialize_pem().unwrap();
|
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||||
let key_file = cert.serialize_private_key_pem();
|
let cert_file = cert.pem();
|
||||||
|
let key_file = key_pair.serialize_pem();
|
||||||
|
|
||||||
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
||||||
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
@ -704,7 +706,7 @@ async fn test_brotli_encoding_large_openssl() {
|
||||||
srv.stop().await;
|
srv.stop().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rustls-0_22")]
|
#[cfg(feature = "rustls-0_23")]
|
||||||
mod plus_rustls {
|
mod plus_rustls {
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
|
||||||
|
@ -714,9 +716,10 @@ mod plus_rustls {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn tls_config() -> RustlsServerConfig {
|
fn tls_config() -> RustlsServerConfig {
|
||||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
let rcgen::CertifiedKey { cert, key_pair } =
|
||||||
let cert_file = cert.serialize_pem().unwrap();
|
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||||
let key_file = cert.serialize_private_key_pem();
|
let cert_file = cert.pem();
|
||||||
|
let key_file = key_pair.serialize_pem();
|
||||||
|
|
||||||
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());
|
||||||
|
@ -740,7 +743,7 @@ mod plus_rustls {
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
|
||||||
let srv = actix_test::start_with(actix_test::config().rustls_0_22(tls_config()), || {
|
let srv = actix_test::start_with(actix_test::config().rustls_0_23(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()
|
||||||
|
|
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 3.5.0
|
||||||
|
|
||||||
|
- Add `rustls-0_23`, `rustls-0_23-webpki-roots`, and `rustls-0_23-native-roots` crate features.
|
||||||
|
- Add `awc::Connector::rustls_0_23()` constructor.
|
||||||
|
- Fix `rustls-0_22-native-roots` root store lookup
|
||||||
|
- Update `brotli` dependency to `6`.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||||
|
|
||||||
## 3.4.0
|
## 3.4.0
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "awc"
|
name = "awc"
|
||||||
version = "3.4.0"
|
version = "3.5.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"]
|
||||||
|
@ -27,6 +27,7 @@ features = [
|
||||||
"rustls-0_20",
|
"rustls-0_20",
|
||||||
"rustls-0_21",
|
"rustls-0_21",
|
||||||
"rustls-0_22-webpki-roots",
|
"rustls-0_22-webpki-roots",
|
||||||
|
"rustls-0_23-webpki-roots",
|
||||||
"compress-brotli",
|
"compress-brotli",
|
||||||
"compress-gzip",
|
"compress-gzip",
|
||||||
"compress-zstd",
|
"compress-zstd",
|
||||||
|
@ -48,6 +49,12 @@ rustls-0_21 = ["tls-rustls-0_21", "actix-tls/rustls-0_21"]
|
||||||
rustls-0_22-webpki-roots = ["tls-rustls-0_22", "actix-tls/rustls-0_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)
|
# TLS via Rustls v0.22 (Native roots)
|
||||||
rustls-0_22-native-roots = ["tls-rustls-0_22", "actix-tls/rustls-0_22-native-roots"]
|
rustls-0_22-native-roots = ["tls-rustls-0_22", "actix-tls/rustls-0_22-native-roots"]
|
||||||
|
# TLS via Rustls v0.23
|
||||||
|
rustls-0_23 = ["tls-rustls-0_23", "actix-tls/rustls-0_23"]
|
||||||
|
# TLS via Rustls v0.23 (WebPKI roots)
|
||||||
|
rustls-0_23-webpki-roots = ["rustls-0_23", "actix-tls/rustls-0_23-webpki-roots"]
|
||||||
|
# TLS via Rustls v0.23 (Native roots)
|
||||||
|
rustls-0_23-native-roots = ["rustls-0_23", "actix-tls/rustls-0_23-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"]
|
||||||
|
@ -57,7 +64,7 @@ compress-gzip = ["actix-http/compress-gzip", "__compress"]
|
||||||
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
||||||
|
|
||||||
# Cookie parsing and cookie jar
|
# Cookie parsing and cookie jar
|
||||||
cookies = ["cookie"]
|
cookies = ["dep:cookie"]
|
||||||
|
|
||||||
# Use `trust-dns-resolver` crate as DNS resolver
|
# Use `trust-dns-resolver` crate as DNS resolver
|
||||||
trust-dns = ["trust-dns-resolver"]
|
trust-dns = ["trust-dns-resolver"]
|
||||||
|
@ -74,9 +81,9 @@ dangerous-h2c = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.5"
|
actix-codec = "0.5"
|
||||||
actix-service = "2"
|
actix-service = "2"
|
||||||
actix-http = { version = "3.6", features = ["http2", "ws"] }
|
actix-http = { version = "3.7", features = ["http2", "ws"] }
|
||||||
actix-rt = { version = "2.1", default-features = false }
|
actix-rt = { version = "2.1", default-features = false }
|
||||||
actix-tls = { version = "3.3", features = ["connect", "uri"] }
|
actix-tls = { version = "3.4", features = ["connect", "uri"] }
|
||||||
actix-utils = "3"
|
actix-utils = "3"
|
||||||
|
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
|
@ -104,29 +111,31 @@ 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 }
|
tls-rustls-0_22 = { package = "rustls", version = "0.22", optional = true }
|
||||||
|
tls-rustls-0_23 = { package = "rustls", version = "0.23", optional = true, default-features = false }
|
||||||
|
|
||||||
trust-dns-resolver = { version = "0.23", optional = true }
|
trust-dns-resolver = { version = "0.23", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-http = { version = "3.6", features = ["openssl"] }
|
actix-http = { version = "3.7", 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_22"] }
|
actix-test = { version = "0.1", features = ["openssl", "rustls-0_23"] }
|
||||||
actix-tls = { version = "3.3", features = ["openssl", "rustls-0_22"] }
|
actix-tls = { version = "3.4", features = ["openssl", "rustls-0_23"] }
|
||||||
actix-utils = "3"
|
actix-utils = "3"
|
||||||
actix-web = { version = "4", features = ["openssl"] }
|
actix-web = { version = "4", features = ["openssl"] }
|
||||||
|
|
||||||
brotli = "3.3.3"
|
brotli = "6"
|
||||||
const-str = "0.5"
|
const-str = "0.5"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
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.12"
|
rcgen = "0.13"
|
||||||
rustls-pemfile = "2"
|
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"
|
||||||
|
tls-rustls-0_23 = { package = "rustls", version = "0.23" } # add rustls 0.23 with default features to make aws_lc_rs work in tests
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "client"
|
name = "client"
|
||||||
required-features = ["rustls-0_22-webpki-roots"]
|
required-features = ["rustls-0_23-webpki-roots"]
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
<!-- 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.4.0)](https://docs.rs/awc/3.4.0)
|
[![Documentation](https://docs.rs/awc/badge.svg?version=3.5.0)](https://docs.rs/awc/3.5.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.4.0/status.svg)](https://deps.rs/crate/awc/3.4.0)
|
[![Dependency Status](https://deps.rs/crate/awc/3.5.0/status.svg)](https://deps.rs/crate/awc/3.5.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 -->
|
||||||
|
|
|
@ -37,6 +37,12 @@ pub struct ClientBuilder<S = (), M = ()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientBuilder {
|
impl ClientBuilder {
|
||||||
|
/// Create a new ClientBuilder with default settings
|
||||||
|
///
|
||||||
|
/// Note: If the `rustls-0_23` feature is enabled and neither `rustls-0_23-native-roots` nor
|
||||||
|
/// `rustls-0_23-webpki-roots` are enabled, this ClientBuilder will build without TLS. In order
|
||||||
|
/// to enable TLS in this scenario, a custom `Connector` _must_ be added to the builder before
|
||||||
|
/// finishing construction.
|
||||||
#[allow(clippy::new_ret_no_self)]
|
#[allow(clippy::new_ret_no_self)]
|
||||||
pub fn new() -> ClientBuilder<
|
pub fn new() -> ClientBuilder<
|
||||||
impl Service<
|
impl Service<
|
||||||
|
|
|
@ -57,6 +57,10 @@ enum OurTlsConnector {
|
||||||
))]
|
))]
|
||||||
#[allow(dead_code)] // false positive; used in build_tls
|
#[allow(dead_code)] // false positive; used in build_tls
|
||||||
Rustls022(std::sync::Arc<actix_tls::connect::rustls_0_22::reexports::ClientConfig>),
|
Rustls022(std::sync::Arc<actix_tls::connect::rustls_0_22::reexports::ClientConfig>),
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls-0_23")]
|
||||||
|
#[allow(dead_code)] // false positive; used in build_tls
|
||||||
|
Rustls023(std::sync::Arc<actix_tls::connect::rustls_0_23::reexports::ClientConfig>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manages HTTP client network connectivity.
|
/// Manages HTTP client network connectivity.
|
||||||
|
@ -80,6 +84,14 @@ pub struct Connector<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Connector<()> {
|
impl Connector<()> {
|
||||||
|
/// Create a new connector with default TLS settings
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// - When the `rustls-0_23-webpki-roots` or `rustls-0_23-native-roots` features are enabled
|
||||||
|
/// and no default crypto provider has been loaded, this method will panic.
|
||||||
|
/// - When the `rustls-0_23-native-roots` or `rustls-0_22-native-roots` features are enabled
|
||||||
|
/// and the runtime system has no native root certificates, this method will panic.
|
||||||
#[allow(clippy::new_ret_no_self, clippy::let_unit_value)]
|
#[allow(clippy::new_ret_no_self, clippy::let_unit_value)]
|
||||||
pub fn new() -> Connector<
|
pub fn new() -> Connector<
|
||||||
impl Service<
|
impl Service<
|
||||||
|
@ -96,10 +108,31 @@ impl Connector<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
cfg_if::cfg_if! {
|
||||||
if #[cfg(any(feature = "rustls-0_22-webpki-roots", feature = "rustls-0_22-webpki-roots"))] {
|
if #[cfg(any(feature = "rustls-0_23-webpki-roots", feature = "rustls-0_23-native-roots"))] {
|
||||||
/// Build TLS connector with Rustls v0.22, based on supplied ALPN protocols.
|
/// Build TLS connector with Rustls v0.23, based on supplied ALPN protocols.
|
||||||
///
|
///
|
||||||
/// Note that if other TLS crate features are enabled, Rustls v0.22 will be used.
|
/// Note that if other TLS crate features are enabled, Rustls v0.23 will be used.
|
||||||
|
fn build_tls(protocols: Vec<Vec<u8>>) -> OurTlsConnector {
|
||||||
|
use actix_tls::connect::rustls_0_23::{self, reexports::ClientConfig};
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "rustls-0_23-webpki-roots")] {
|
||||||
|
let certs = rustls_0_23::webpki_roots_cert_store();
|
||||||
|
} else if #[cfg(feature = "rustls-0_23-native-roots")] {
|
||||||
|
let certs = rustls_0_23::native_roots_cert_store().expect("Failed to find native root certificates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut config = ClientConfig::builder()
|
||||||
|
.with_root_certificates(certs)
|
||||||
|
.with_no_client_auth();
|
||||||
|
|
||||||
|
config.alpn_protocols = protocols;
|
||||||
|
|
||||||
|
OurTlsConnector::Rustls023(std::sync::Arc::new(config))
|
||||||
|
}
|
||||||
|
} else if #[cfg(any(feature = "rustls-0_22-webpki-roots", feature = "rustls-0_22-native-roots"))] {
|
||||||
|
/// Build TLS connector with Rustls v0.22, based on supplied ALPN protocols.
|
||||||
fn build_tls(protocols: Vec<Vec<u8>>) -> OurTlsConnector {
|
fn build_tls(protocols: Vec<Vec<u8>>) -> OurTlsConnector {
|
||||||
use actix_tls::connect::rustls_0_22::{self, reexports::ClientConfig};
|
use actix_tls::connect::rustls_0_22::{self, reexports::ClientConfig};
|
||||||
|
|
||||||
|
@ -107,7 +140,7 @@ impl Connector<()> {
|
||||||
if #[cfg(feature = "rustls-0_22-webpki-roots")] {
|
if #[cfg(feature = "rustls-0_22-webpki-roots")] {
|
||||||
let certs = rustls_0_22::webpki_roots_cert_store();
|
let certs = rustls_0_22::webpki_roots_cert_store();
|
||||||
} else if #[cfg(feature = "rustls-0_22-native-roots")] {
|
} else if #[cfg(feature = "rustls-0_22-native-roots")] {
|
||||||
let certs = rustls_0_22::native_roots_cert_store();
|
let certs = rustls_0_22::native_roots_cert_store().expect("Failed to find native root certificates");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +200,8 @@ impl Connector<()> {
|
||||||
OurTlsConnector::OpensslBuilder(ssl)
|
OurTlsConnector::OpensslBuilder(ssl)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/// Provides an empty TLS connector when no TLS feature is enabled.
|
/// Provides an empty TLS connector when no TLS feature is enabled, or when only the
|
||||||
|
/// `rustls-0_23` crate feature is enabled.
|
||||||
fn build_tls(_: Vec<Vec<u8>>) -> OurTlsConnector {
|
fn build_tls(_: Vec<Vec<u8>>) -> OurTlsConnector {
|
||||||
OurTlsConnector::None
|
OurTlsConnector::None
|
||||||
}
|
}
|
||||||
|
@ -278,6 +312,24 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets custom Rustls v0.23 `ClientConfig` instance.
|
||||||
|
///
|
||||||
|
/// In order to enable ALPN, set the `.alpn_protocols` field on the ClientConfig to the
|
||||||
|
/// following:
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// vec![b"h2".to_vec(), b"http/1.1".to_vec()]
|
||||||
|
/// # ;
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "rustls-0_23")]
|
||||||
|
pub fn rustls_0_23(
|
||||||
|
mut self,
|
||||||
|
connector: std::sync::Arc<actix_tls::connect::rustls_0_23::reexports::ClientConfig>,
|
||||||
|
) -> Self {
|
||||||
|
self.tls = OurTlsConnector::Rustls023(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.
|
||||||
|
@ -588,6 +640,40 @@ where
|
||||||
|
|
||||||
Some(actix_service::boxed::rc_service(tls_service))
|
Some(actix_service::boxed::rc_service(tls_service))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls-0_23")]
|
||||||
|
OurTlsConnector::Rustls023(tls) => {
|
||||||
|
const H2: &[u8] = b"h2";
|
||||||
|
|
||||||
|
use actix_tls::connect::rustls_0_23::{reexports::AsyncTlsStream, TlsConnector};
|
||||||
|
|
||||||
|
#[allow(non_local_definitions)]
|
||||||
|
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();
|
||||||
|
@ -649,6 +735,17 @@ where
|
||||||
|
|
||||||
/// service for establish tcp connection and do client tls handshake.
|
/// service for establish tcp connection and do client tls handshake.
|
||||||
/// operation is canceled when timeout limit reached.
|
/// operation is canceled when timeout limit reached.
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "dangerous-h2c",
|
||||||
|
feature = "openssl",
|
||||||
|
feature = "rustls-0_20",
|
||||||
|
feature = "rustls-0_21",
|
||||||
|
feature = "rustls-0_22-webpki-roots",
|
||||||
|
feature = "rustls-0_22-native-roots",
|
||||||
|
feature = "rustls-0_23",
|
||||||
|
feature = "rustls-0_23-webpki-roots",
|
||||||
|
feature = "rustls-0_23-native-roots"
|
||||||
|
))]
|
||||||
struct TlsConnectorService<Tcp, Tls> {
|
struct TlsConnectorService<Tcp, Tls> {
|
||||||
/// TCP connection is canceled on `TcpConnectorInnerService`'s timeout setting.
|
/// TCP connection is canceled on `TcpConnectorInnerService`'s timeout setting.
|
||||||
tcp_service: Tcp,
|
tcp_service: Tcp,
|
||||||
|
@ -659,6 +756,15 @@ struct TlsConnectorService<Tcp, Tls> {
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "dangerous-h2c",
|
||||||
|
feature = "openssl",
|
||||||
|
feature = "rustls-0_20",
|
||||||
|
feature = "rustls-0_21",
|
||||||
|
feature = "rustls-0_22-webpki-roots",
|
||||||
|
feature = "rustls-0_22-native-roots",
|
||||||
|
feature = "rustls-0_23",
|
||||||
|
))]
|
||||||
impl<Tcp, Tls, IO> Service<Connect> for TlsConnectorService<Tcp, Tls>
|
impl<Tcp, Tls, IO> Service<Connect> for TlsConnectorService<Tcp, Tls>
|
||||||
where
|
where
|
||||||
Tcp:
|
Tcp:
|
||||||
|
@ -974,7 +1080,7 @@ mod resolver {
|
||||||
|
|
||||||
// resolver struct is cached in thread local so new clients can reuse the existing instance
|
// resolver struct is cached in thread local so new clients can reuse the existing instance
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static TRUST_DNS_RESOLVER: RefCell<Option<Resolver>> = RefCell::new(None);
|
static TRUST_DNS_RESOLVER: RefCell<Option<Resolver>> = const { RefCell::new(None) };
|
||||||
}
|
}
|
||||||
|
|
||||||
// get from thread local or construct a new trust-dns resolver.
|
// get from thread local or construct a new trust-dns resolver.
|
||||||
|
|
|
@ -13,9 +13,11 @@ use openssl::{
|
||||||
};
|
};
|
||||||
|
|
||||||
fn tls_config() -> SslAcceptor {
|
fn tls_config() -> SslAcceptor {
|
||||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
let rcgen::CertifiedKey { cert, key_pair } =
|
||||||
let cert_file = cert.serialize_pem().unwrap();
|
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||||
let key_file = cert.serialize_private_key_pem();
|
let cert_file = cert.pem();
|
||||||
|
let key_file = key_pair.serialize_pem();
|
||||||
|
|
||||||
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
||||||
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#![cfg(feature = "rustls-0_22-webpki-roots")]
|
#![cfg(feature = "rustls-0_23-webpki-roots")]
|
||||||
|
|
||||||
extern crate tls_rustls_0_22 as rustls;
|
extern crate tls_rustls_0_23 as rustls;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::BufReader,
|
io::BufReader,
|
||||||
|
@ -13,7 +13,7 @@ use std::{
|
||||||
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_22::webpki_roots_cert_store;
|
use actix_tls::connect::rustls_0_23::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::{
|
||||||
|
@ -23,9 +23,10 @@ use rustls::{
|
||||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||||
|
|
||||||
fn tls_config() -> ServerConfig {
|
fn tls_config() -> ServerConfig {
|
||||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
let rcgen::CertifiedKey { cert, key_pair } =
|
||||||
let cert_file = cert.serialize_pem().unwrap();
|
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||||
let key_file = cert.serialize_private_key_pem();
|
let cert_file = cert.pem();
|
||||||
|
let key_file = key_pair.serialize_pem();
|
||||||
|
|
||||||
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());
|
||||||
|
@ -83,7 +84,7 @@ mod danger {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
|
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
|
||||||
rustls::crypto::ring::default_provider()
|
rustls::crypto::aws_lc_rs::default_provider()
|
||||||
.signature_verification_algorithms
|
.signature_verification_algorithms
|
||||||
.supported_schemes()
|
.supported_schemes()
|
||||||
}
|
}
|
||||||
|
@ -107,7 +108,7 @@ 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_0_22(tls_config())
|
.rustls_0_23(tls_config())
|
||||||
.map_err(|_| ()),
|
.map_err(|_| ()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -126,7 +127,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_0_22(Arc::new(config)))
|
.connector(awc::Connector::new().rustls_0_23(Arc::new(config)))
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
// req 1
|
// req 1
|
||||||
|
|
|
@ -19,9 +19,11 @@ use openssl::{
|
||||||
};
|
};
|
||||||
|
|
||||||
fn tls_config() -> SslAcceptor {
|
fn tls_config() -> SslAcceptor {
|
||||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
let rcgen::CertifiedKey { cert, key_pair } =
|
||||||
let cert_file = cert.serialize_pem().unwrap();
|
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||||
let key_file = cert.serialize_private_key_pem();
|
let cert_file = cert.pem();
|
||||||
|
let key_file = key_pair.serialize_pem();
|
||||||
|
|
||||||
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
||||||
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
|
57
justfile
57
justfile
|
@ -4,16 +4,63 @@ _list:
|
||||||
# Format workspace.
|
# Format workspace.
|
||||||
fmt:
|
fmt:
|
||||||
cargo +nightly fmt
|
cargo +nightly fmt
|
||||||
npx -y prettier --write $(fd --type=file --hidden --extension=md --extension=yml)
|
fd --hidden --type=file --extension=md --extension=yml --exec-batch npx -y prettier --write
|
||||||
|
|
||||||
|
# Downgrade dev-dependencies necessary to run MSRV checks/tests.
|
||||||
|
[private]
|
||||||
|
downgrade-for-msrv:
|
||||||
|
cargo update -p=clap --precise=4.4.18
|
||||||
|
|
||||||
|
msrv := ```
|
||||||
|
cargo metadata --format-version=1 \
|
||||||
|
| jq -r 'first(.packages[] | select(.source == null and .rust_version)) | .rust_version' \
|
||||||
|
| sed -E 's/^1\.([0-9]{2})$/1\.\1\.0/'
|
||||||
|
```
|
||||||
|
msrv_rustup := "+" + msrv
|
||||||
|
|
||||||
|
non_linux_all_features_list := ```
|
||||||
|
cargo metadata --format-version=1 \
|
||||||
|
| jq '.packages[] | select(.source == null) | .features | keys' \
|
||||||
|
| jq -r --slurp \
|
||||||
|
--arg exclusions "tokio-uring,io-uring,experimental-io-uring" \
|
||||||
|
'add | unique | . - ($exclusions | split(",")) | join(",")'
|
||||||
|
```
|
||||||
|
|
||||||
|
all_crate_features := if os() == "linux" {
|
||||||
|
"--all-features"
|
||||||
|
} else {
|
||||||
|
"--features='" + non_linux_all_features_list + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run Clippy over workspace.
|
||||||
|
clippy toolchain="":
|
||||||
|
cargo {{ toolchain }} clippy --workspace --all-targets {{ all_crate_features }}
|
||||||
|
|
||||||
|
# Test workspace using MSRV.
|
||||||
|
test-msrv: downgrade-for-msrv (test msrv_rustup)
|
||||||
|
|
||||||
|
# Test workspace code.
|
||||||
|
test toolchain="":
|
||||||
|
cargo {{ toolchain }} test --lib --tests -p=actix-web-codegen --all-features
|
||||||
|
cargo {{ toolchain }} test --lib --tests -p=actix-multipart-derive --all-features
|
||||||
|
cargo {{ toolchain }} nextest run -p=actix-router --no-default-features
|
||||||
|
cargo {{ toolchain }} nextest run --workspace --exclude=actix-web-codegen --exclude=actix-multipart-derive {{ all_crate_features }} --filter-expr="not test(test_reading_deflate_encoding_large_random_rustls)"
|
||||||
|
|
||||||
|
# Test workspace docs.
|
||||||
|
test-docs toolchain="": && doc
|
||||||
|
cargo {{ toolchain }} test --doc --workspace {{ all_crate_features }} --no-fail-fast -- --nocapture
|
||||||
|
|
||||||
|
# Test workspace.
|
||||||
|
test-all toolchain="": (test toolchain) (test-docs toolchain)
|
||||||
|
|
||||||
# Document crates in workspace.
|
# Document crates in workspace.
|
||||||
doc:
|
doc *args:
|
||||||
RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --features=rustls,openssl
|
RUSTDOCFLAGS="--cfg=docsrs -Dwarnings" cargo +nightly doc --no-deps --workspace {{ all_crate_features }} {{ args }}
|
||||||
|
|
||||||
# Document crates in workspace and watch for changes.
|
# Document crates in workspace and watch for changes.
|
||||||
doc-watch:
|
doc-watch:
|
||||||
RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --features=rustls,openssl --open
|
@just doc --open
|
||||||
cargo watch -- RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --features=rustls,openssl
|
cargo watch -- just doc
|
||||||
|
|
||||||
# Update READMEs from crate root documentation.
|
# Update READMEs from crate root documentation.
|
||||||
update-readmes: && fmt
|
update-readmes: && fmt
|
||||||
|
|
Loading…
Reference in a new issue