mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into implement-full-uri-function
This commit is contained in:
commit
660eeb3704
|
@ -1,17 +1,10 @@
|
|||
[alias]
|
||||
lint = "clippy --workspace --tests --examples --bins -- -Dclippy::todo"
|
||||
lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo"
|
||||
lint = "clippy --workspace --all-targets -- -Dclippy::todo"
|
||||
lint-all = "clippy --workspace --all-features --all-targets -- -Dclippy::todo"
|
||||
|
||||
# lib checking
|
||||
ci-check-min = "hack --workspace check --no-default-features"
|
||||
ci-check-default = "hack --workspace check"
|
||||
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-linux="hack --workspace --feature-powerset --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"
|
||||
|
||||
# compile docs as docs.rs would
|
||||
# RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace
|
||||
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 --depth=4 --skip=__compress check"
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
- package-ecosystem: cargo
|
||||
directory: /
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
open-pull-requests-limit: 10
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
interval: weekly
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
open-pull-requests-limit: 10
|
||||
interval: weekly
|
||||
|
|
|
@ -5,7 +5,7 @@ on:
|
|||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
@ -16,7 +16,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
run: |
|
||||
|
|
|
@ -22,30 +22,36 @@ jobs:
|
|||
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
|
||||
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
|
||||
version:
|
||||
- nightly
|
||||
- { name: nightly, version: nightly }
|
||||
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
|
||||
runs-on: ${{ matrix.target.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install nasm
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
uses: ilammy/setup-nasm@v1.5.1
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
run: choco install openssl -y --forcex64 --no-progress
|
||||
- name: Set OpenSSL dir in env
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
set -e
|
||||
choco install openssl --version=1.1.1.2100 -y --no-progress
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
|
||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Rust (${{ matrix.version }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
- name: Install Rust (${{ matrix.version.name }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: ${{ matrix.version }}
|
||||
toolchain: ${{ matrix.version.version }}
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.38.0
|
||||
with:
|
||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||
|
||||
- name: check minimal
|
||||
run: cargo ci-check-min
|
||||
|
@ -55,54 +61,31 @@ jobs:
|
|||
|
||||
- name: tests
|
||||
timeout-minutes: 60
|
||||
run: |
|
||||
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,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
|
||||
run: just test
|
||||
|
||||
- name: Clear the cargo caches
|
||||
run: |
|
||||
cargo install cargo-cache --version 0.8.3 --no-default-features --features ci-autoclean
|
||||
cargo-cache
|
||||
- name: CI cache clean
|
||||
run: cargo-ci-cache-clean
|
||||
|
||||
ci_feature_powerset_check:
|
||||
name: Verify Feature Combinations
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Free Disk Space
|
||||
run: ./scripts/free-disk-space.sh
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
uses: taiki-e/install-action@v2.38.0
|
||||
with:
|
||||
tool: cargo-hack
|
||||
|
||||
- name: check feature combinations
|
||||
run: cargo ci-check-all-feature-powerset
|
||||
|
||||
- name: check feature combinations
|
||||
run: cargo ci-check-all-feature-powerset-linux
|
||||
|
||||
nextest:
|
||||
name: nextest
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Test with cargo-nextest
|
||||
run: cargo nextest run
|
||||
|
|
|
@ -16,7 +16,13 @@ concurrency:
|
|||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
read_msrv:
|
||||
name: Read MSRV
|
||||
uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@v0.1.0
|
||||
|
||||
build_and_test:
|
||||
needs: read_msrv
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -26,35 +32,45 @@ jobs:
|
|||
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
|
||||
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
|
||||
version:
|
||||
- 1.68.0 # MSRV
|
||||
- stable
|
||||
- { name: msrv, version: "${{ needs.read_msrv.outputs.msrv }}" }
|
||||
- { name: stable, version: stable }
|
||||
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
|
||||
runs-on: ${{ matrix.target.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install nasm
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
uses: ilammy/setup-nasm@v1.5.1
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
run: choco install openssl -y --forcex64 --no-progress
|
||||
- name: Set OpenSSL dir in env
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
set -e
|
||||
choco install openssl --version=1.1.1.2100 -y --no-progress
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
|
||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Rust (${{ matrix.version }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
- name: Setup mold linker
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
uses: rui314/setup-mold@v1
|
||||
|
||||
- name: Install Rust (${{ matrix.version.name }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: ${{ matrix.version }}
|
||||
toolchain: ${{ matrix.version.version }}
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.38.0
|
||||
with:
|
||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||
|
||||
# - name: workaround MSRV issues
|
||||
# if: matrix.version != 'stable'
|
||||
# run: |
|
||||
- name: workaround MSRV issues
|
||||
if: matrix.version.name == 'msrv'
|
||||
run: just downgrade-for-msrv
|
||||
|
||||
- name: check minimal
|
||||
run: cargo ci-check-min
|
||||
|
@ -64,32 +80,21 @@ jobs:
|
|||
|
||||
- name: tests
|
||||
timeout-minutes: 60
|
||||
run: |
|
||||
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,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
|
||||
run: just test
|
||||
|
||||
- name: Clear the cargo caches
|
||||
run: |
|
||||
cargo install cargo-cache --version 0.8.3 --no-default-features --features ci-autoclean
|
||||
cargo-cache
|
||||
- name: CI cache clean
|
||||
run: cargo-ci-cache-clean
|
||||
|
||||
io-uring:
|
||||
name: io-uring tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { toolchain: nightly }
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: tests (io-uring)
|
||||
timeout-minutes: 60
|
||||
|
@ -100,12 +105,17 @@ jobs:
|
|||
name: doc tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { toolchain: nightly }
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@v2.38.0
|
||||
with:
|
||||
tool: just
|
||||
|
||||
- name: doc tests
|
||||
run: cargo ci-doctest
|
||||
timeout-minutes: 60
|
||||
run: just test-docs
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
|
||||
- run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
permissions:
|
||||
checks: write # to add clippy checks to PR diffs
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { components: clippy }
|
||||
|
||||
- uses: giraffate/clippy-action@v1
|
||||
with:
|
||||
reporter: 'github-pr-check'
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
clippy_flags: --workspace --all-features --tests --examples --bins -- -Dclippy::todo
|
||||
|
||||
lint-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { components: rust-docs }
|
||||
|
||||
- name: Check for broken intra-doc links
|
||||
env: { RUSTDOCFLAGS: "-D warnings" }
|
||||
run: cargo doc --no-deps --all-features --workspace
|
||||
|
||||
public-api-diff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
# temp: unpin once https://github.com/rust-lang/rust/issues/113152 is fixed
|
||||
with: { toolchain: nightly-2023-06-28 }
|
||||
|
||||
- uses: taiki-e/cache-cargo-install-action@v1
|
||||
with: { tool: cargo-public-api }
|
||||
|
||||
- name: generate API diff
|
||||
run: |
|
||||
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 }}
|
||||
done
|
|
@ -1,5 +1,3 @@
|
|||
# disabled because `cargo tarpaulin` currently segfaults
|
||||
|
||||
name: Coverage
|
||||
|
||||
on:
|
||||
|
@ -7,28 +5,35 @@ on:
|
|||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# job currently (1st Feb 2022) segfaults
|
||||
coverage:
|
||||
name: coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { toolchain: nightly }
|
||||
|
||||
- name: Generate coverage file
|
||||
run: |
|
||||
cargo install cargo-tarpaulin --vers "^0.13"
|
||||
cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@v3.1.4
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
file: cobertura.xml
|
||||
components: llvm-tools-preview
|
||||
|
||||
- name: Install just,cargo-llvm-cov
|
||||
uses: taiki-e/install-action@v2.38.0
|
||||
with:
|
||||
tool: just,cargo-llvm-cov
|
||||
|
||||
- name: Generate code coverage
|
||||
run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4.4.1
|
||||
with:
|
||||
files: codecov.json
|
||||
fail_ci_if_error: true
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
|
||||
- name: Check with Rustfmt
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write # to add clippy checks to PR diffs
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Check with Clippy
|
||||
uses: giraffate/clippy-action@v1.0.1
|
||||
with:
|
||||
reporter: github-pr-check
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
clippy_flags: >-
|
||||
--workspace --all-features --tests --examples --bins --
|
||||
-A unknown_lints -D clippy::todo -D clippy::dbg_macro
|
||||
|
||||
lint-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rust-docs
|
||||
|
||||
- name: Check for broken intra-doc links
|
||||
env:
|
||||
RUSTDOCFLAGS: -D warnings
|
||||
run: cargo +nightly doc --no-deps --workspace --all-features
|
||||
|
||||
public-api-diff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout main branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
- name: Checkout PR branch
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: nightly-2024-06-07
|
||||
|
||||
- name: Install cargo-public-api
|
||||
uses: taiki-e/install-action@v2.38.0
|
||||
with:
|
||||
tool: cargo-public-api
|
||||
|
||||
- name: Generate API diff
|
||||
run: |
|
||||
for f in $(find -mindepth 2 -maxdepth 2 -name Cargo.toml); do
|
||||
cargo public-api --manifest-path "$f" --simplified diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }}
|
||||
done
|
|
@ -1,38 +0,0 @@
|
|||
name: Upload Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: write # to push changes in repo (jamesives/github-pages-deploy-action)
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: dtolnay/rust-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.4.3
|
||||
with:
|
||||
folder: target/doc
|
||||
single-commit: true
|
|
@ -19,3 +19,7 @@ guide/build/
|
|||
|
||||
# Configuration directory generated by VSCode
|
||||
.vscode
|
||||
|
||||
# code coverage
|
||||
/lcov.info
|
||||
/codecov.json
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
overrides:
|
||||
- files: '*.md'
|
||||
- files: "*.md"
|
||||
options:
|
||||
printWidth: 9999
|
||||
proseWrap: never
|
||||
|
|
|
@ -14,6 +14,13 @@ members = [
|
|||
"awc",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.72"
|
||||
|
||||
[profile.dev]
|
||||
# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much.
|
||||
debug = 0
|
||||
|
|
|
@ -1,10 +1,22 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## Unreleased
|
||||
|
||||
## 0.6.6
|
||||
|
||||
- Update `tokio-uring` dependency to `0.4`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
## 0.6.5
|
||||
|
||||
- Fix handling of special characters in filenames.
|
||||
|
||||
## 0.6.4
|
||||
|
||||
- Fix handling of newlines in filenames.
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 0.6.3 - 2023-01-21
|
||||
## 0.6.3
|
||||
|
||||
- XHTML files now use `Content-Disposition: inline` instead of `attachment`. [#2903]
|
||||
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
|
||||
|
@ -12,14 +24,14 @@
|
|||
|
||||
[#2903]: https://github.com/actix/actix-web/pull/2903
|
||||
|
||||
## 0.6.2 - 2022-07-23
|
||||
## 0.6.2
|
||||
|
||||
- Allow partial range responses for video content to start streaming sooner. [#2817]
|
||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||
|
||||
[#2817]: https://github.com/actix/actix-web/pull/2817
|
||||
|
||||
## 0.6.1 - 2022-06-11
|
||||
## 0.6.1
|
||||
|
||||
- Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021]
|
||||
- Update `tokio-uring` dependency to `0.3`.
|
||||
|
@ -29,25 +41,25 @@
|
|||
[#2021]: https://github.com/actix/actix-web/pull/2021
|
||||
[#2645]: https://github.com/actix/actix-web/pull/2645
|
||||
|
||||
## 0.6.0 - 2022-02-25
|
||||
## 0.6.0
|
||||
|
||||
- No significant changes since `0.6.0-beta.16`.
|
||||
|
||||
## 0.6.0-beta.16 - 2022-01-31
|
||||
## 0.6.0-beta.16
|
||||
|
||||
- No significant changes since `0.6.0-beta.15`.
|
||||
|
||||
## 0.6.0-beta.15 - 2022-01-21
|
||||
## 0.6.0-beta.15
|
||||
|
||||
- No significant changes since `0.6.0-beta.14`.
|
||||
|
||||
## 0.6.0-beta.14 - 2022-01-14
|
||||
## 0.6.0-beta.14
|
||||
|
||||
- The `prefer_utf8` option introduced in `0.4.0` is now true by default. [#2583]
|
||||
|
||||
[#2583]: https://github.com/actix/actix-web/pull/2583
|
||||
|
||||
## 0.6.0-beta.13 - 2022-01-04
|
||||
## 0.6.0-beta.13
|
||||
|
||||
- The `Files` service now rejects requests with URL paths that include `%2F` (decoded: `/`). [#2398]
|
||||
- The `Files` service now correctly decodes `%25` in the URL path to `%` for the file path. [#2398]
|
||||
|
@ -55,19 +67,19 @@
|
|||
|
||||
[#2398]: https://github.com/actix/actix-web/pull/2398
|
||||
|
||||
## 0.6.0-beta.12 - 2021-12-29
|
||||
## 0.6.0-beta.12
|
||||
|
||||
- No significant changes since `0.6.0-beta.11`.
|
||||
|
||||
## 0.6.0-beta.11 - 2021-12-27
|
||||
## 0.6.0-beta.11
|
||||
|
||||
- No significant changes since `0.6.0-beta.10`.
|
||||
|
||||
## 0.6.0-beta.10 - 2021-12-11
|
||||
## 0.6.0-beta.10
|
||||
|
||||
- No significant changes since `0.6.0-beta.9`.
|
||||
|
||||
## 0.6.0-beta.9 - 2021-11-22
|
||||
## 0.6.0-beta.9
|
||||
|
||||
- Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408]
|
||||
- Add `NamedFile::open_async`. [#2408]
|
||||
|
@ -79,15 +91,15 @@
|
|||
[#2408]: https://github.com/actix/actix-web/pull/2408
|
||||
[#2453]: https://github.com/actix/actix-web/pull/2453
|
||||
|
||||
## 0.6.0-beta.8 - 2021-10-20
|
||||
## 0.6.0-beta.8
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
## 0.6.0-beta.7 - 2021-09-09
|
||||
## 0.6.0-beta.7
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
||||
## 0.6.0-beta.6 - 2021-06-26
|
||||
## 0.6.0-beta.6
|
||||
|
||||
- Added `Files::path_filter()`. [#2274]
|
||||
- `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228]
|
||||
|
@ -95,7 +107,7 @@
|
|||
[#2274]: https://github.com/actix/actix-web/pull/2274
|
||||
[#2228]: https://github.com/actix/actix-web/pull/2228
|
||||
|
||||
## 0.6.0-beta.5 - 2021-06-17
|
||||
## 0.6.0-beta.5
|
||||
|
||||
- `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135]
|
||||
- For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156]
|
||||
|
@ -107,17 +119,17 @@
|
|||
[#2225]: https://github.com/actix/actix-web/pull/2225
|
||||
[#2257]: https://github.com/actix/actix-web/pull/2257
|
||||
|
||||
## 0.6.0-beta.4 - 2021-04-02
|
||||
## 0.6.0-beta.4
|
||||
|
||||
- Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046]
|
||||
|
||||
[#2046]: https://github.com/actix/actix-web/pull/2046
|
||||
|
||||
## 0.6.0-beta.3 - 2021-03-09
|
||||
## 0.6.0-beta.3
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 0.6.0-beta.2 - 2021-02-10
|
||||
## 0.6.0-beta.2
|
||||
|
||||
- Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
|
||||
- Replace `v_htmlescape` with `askama_escape`. [#1953]
|
||||
|
@ -125,39 +137,39 @@
|
|||
[#1887]: https://github.com/actix/actix-web/pull/1887
|
||||
[#1953]: https://github.com/actix/actix-web/pull/1953
|
||||
|
||||
## 0.6.0-beta.1 - 2021-01-07
|
||||
## 0.6.0-beta.1
|
||||
|
||||
- `HttpRange::parse` now has its own error type.
|
||||
- Update `bytes` to `1.0`. [#1813]
|
||||
|
||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||
|
||||
## 0.5.0 - 2020-12-26
|
||||
## 0.5.0
|
||||
|
||||
- Optionally support hidden files/directories. [#1811]
|
||||
|
||||
[#1811]: https://github.com/actix/actix-web/pull/1811
|
||||
|
||||
## 0.4.1 - 2020-11-24
|
||||
## 0.4.1
|
||||
|
||||
- Clarify order of parameters in `Files::new` and improve docs.
|
||||
|
||||
## 0.4.0 - 2020-10-06
|
||||
## 0.4.0
|
||||
|
||||
- Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714]
|
||||
|
||||
[#1714]: https://github.com/actix/actix-web/pull/1714
|
||||
|
||||
## 0.3.0 - 2020-09-11
|
||||
## 0.3.0
|
||||
|
||||
- No significant changes from 0.3.0-beta.1.
|
||||
|
||||
## 0.3.0-beta.1 - 2020-07-15
|
||||
## 0.3.0-beta.1
|
||||
|
||||
- Update `v_htmlescape` to 0.10
|
||||
- Update `actix-web` and `actix-http` dependencies to beta.1
|
||||
|
||||
## 0.3.0-alpha.1 - 2020-05-23
|
||||
## 0.3.0-alpha.1
|
||||
|
||||
- Update `actix-web` and `actix-http` dependencies to alpha
|
||||
- Fix some typos in the docs
|
||||
|
@ -166,73 +178,73 @@
|
|||
|
||||
[#1384]: https://github.com/actix/actix-web/pull/1384
|
||||
|
||||
## 0.2.1 - 2019-12-22
|
||||
## 0.2.1
|
||||
|
||||
- Use the same format for file URLs regardless of platforms
|
||||
|
||||
## 0.2.0 - 2019-12-20
|
||||
## 0.2.0
|
||||
|
||||
- Fix BodyEncoding trait import #1220
|
||||
|
||||
## 0.2.0-alpha.1 - 2019-12-07
|
||||
## 0.2.0-alpha.1
|
||||
|
||||
- Migrate to `std::future`
|
||||
|
||||
## 0.1.7 - 2019-11-06
|
||||
## 0.1.7
|
||||
|
||||
- Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151)
|
||||
|
||||
## 0.1.6 - 2019-10-14
|
||||
## 0.1.6
|
||||
|
||||
- Add option to redirect to a slash-ended path `Files` #1132
|
||||
|
||||
## 0.1.5 - 2019-10-08
|
||||
## 0.1.5
|
||||
|
||||
- Bump up `mime_guess` crate version to 2.0.1
|
||||
- Bump up `percent-encoding` crate version to 2.1
|
||||
- Allow user defined request guards for `Files` #1113
|
||||
|
||||
## 0.1.4 - 2019-07-20
|
||||
## 0.1.4
|
||||
|
||||
- Allow to disable `Content-Disposition` header #686
|
||||
|
||||
## 0.1.3 - 2019-06-28
|
||||
## 0.1.3
|
||||
|
||||
- Do not set `Content-Length` header, let actix-http set it #930
|
||||
|
||||
## 0.1.2 - 2019-06-13
|
||||
## 0.1.2
|
||||
|
||||
- Content-Length is 0 for NamedFile HEAD request #914
|
||||
- Fix ring dependency from actix-web default features for #741
|
||||
|
||||
## 0.1.1 - 2019-06-01
|
||||
## 0.1.1
|
||||
|
||||
- Static files are incorrectly served as both chunked and with length #812
|
||||
|
||||
## 0.1.0 - 2019-05-25
|
||||
## 0.1.0
|
||||
|
||||
- NamedFile last-modified check always fails due to nano-seconds in file modified date #820
|
||||
|
||||
## 0.1.0-beta.4 - 2019-05-12
|
||||
## 0.1.0-beta.4
|
||||
|
||||
- Update actix-web to beta.4
|
||||
|
||||
## 0.1.0-beta.1 - 2019-04-20
|
||||
## 0.1.0-beta.1
|
||||
|
||||
- Update actix-web to beta.1
|
||||
|
||||
## 0.1.0-alpha.6 - 2019-04-14
|
||||
## 0.1.0-alpha.6
|
||||
|
||||
- Update actix-web to alpha6
|
||||
|
||||
## 0.1.0-alpha.4 - 2019-04-08
|
||||
## 0.1.0-alpha.4
|
||||
|
||||
- Update actix-web to alpha4
|
||||
|
||||
## 0.1.0-alpha.2 - 2019-04-02
|
||||
## 0.1.0-alpha.2
|
||||
|
||||
- Add default handler support
|
||||
|
||||
## 0.1.0-alpha.1 - 2019-03-28
|
||||
## 0.1.0-alpha.1
|
||||
|
||||
- Initial impl
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-files"
|
||||
version = "0.6.3"
|
||||
version = "0.6.6"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
|
@ -40,11 +40,12 @@ v_htmlescape = "0.15.5"
|
|||
|
||||
# experimental-io-uring
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tokio-uring = { version = "0.4", optional = true, features = ["bytes"] }
|
||||
actix-server = { version = "2.2", optional = true } # ensure matching tokio-uring versions
|
||||
tokio-uring = { version = "0.5", optional = true, features = ["bytes"] }
|
||||
actix-server = { version = "2.4", optional = true } # ensure matching tokio-uring versions
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.7"
|
||||
actix-test = "0.1"
|
||||
actix-web = "4"
|
||||
env_logger = "0.11"
|
||||
tempfile = "3.2"
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
# actix-files
|
||||
# `actix-files`
|
||||
|
||||
> Static file serving for Actix Web
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://docs.rs/actix-files/0.6.3)
|
||||

|
||||
[](https://docs.rs/actix-files/0.6.6)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-files/0.6.3)
|
||||
[](https://deps.rs/crate/actix-files/0.6.6)
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-files)
|
||||
- [Example Project](https://github.com/actix/examples/tree/master/basics/static-files)
|
||||
- Minimum Supported Rust Version (MSRV): 1.68
|
||||
<!-- cargo-rdme start -->
|
||||
|
||||
Static file serving for Actix Web.
|
||||
|
||||
Provides a non-blocking service for serving static files from disk.
|
||||
|
||||
## Examples
|
||||
|
||||
```rust
|
||||
use actix_web::App;
|
||||
use actix_files::Files;
|
||||
|
||||
let app = App::new()
|
||||
.service(Files::new("/static", ".").prefer_utf8(true));
|
||||
```
|
||||
|
||||
<!-- cargo-rdme end -->
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
use actix_files::Files;
|
||||
use actix_web::{get, guard, middleware, App, HttpServer, Responder};
|
||||
|
||||
const EXAMPLES_DIR: &str = concat![env!("CARGO_MANIFEST_DIR"), "/examples"];
|
||||
|
||||
#[get("/")]
|
||||
async fn index() -> impl Responder {
|
||||
"Hello world!"
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||
|
||||
log::info!("starting HTTP server at http://localhost:8080");
|
||||
|
||||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.service(index)
|
||||
.service(
|
||||
Files::new("/assets", EXAMPLES_DIR)
|
||||
.show_files_listing()
|
||||
.guard(guard::Header("show-listing", "?1")),
|
||||
)
|
||||
.service(Files::new("/assets", EXAMPLES_DIR))
|
||||
.wrap(middleware::Compress::default())
|
||||
.wrap(middleware::Logger::default())
|
||||
})
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.workers(2)
|
||||
.run()
|
||||
.await
|
||||
}
|
|
@ -235,7 +235,7 @@ impl Files {
|
|||
/// request starts being handled by the file service, it will not be able to back-out and try
|
||||
/// the next service, you will simply get a 404 (or 405) error response.
|
||||
///
|
||||
/// To allow `POST` requests to retrieve files, see [`Files::use_guards`].
|
||||
/// To allow `POST` requests to retrieve files, see [`Files::method_guard()`].
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
@ -66,6 +65,7 @@ type PathFilter = dyn Fn(&Path, &RequestHead) -> bool;
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
fmt::Write as _,
|
||||
fs::{self},
|
||||
ops::Add,
|
||||
time::{Duration, SystemTime},
|
||||
|
@ -75,7 +75,7 @@ mod tests {
|
|||
dev::ServiceFactory,
|
||||
guard,
|
||||
http::{
|
||||
header::{self, ContentDisposition, DispositionParam, DispositionType},
|
||||
header::{self, ContentDisposition, DispositionParam},
|
||||
Method, StatusCode,
|
||||
},
|
||||
middleware::Compress,
|
||||
|
@ -568,6 +568,30 @@ mod tests {
|
|||
assert_eq!(bytes, data);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[actix_rt::test]
|
||||
async fn test_static_files_with_special_characters() {
|
||||
// Create the file we want to test against ad-hoc. We can't check it in as otherwise
|
||||
// Windows can't even checkout this repository.
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let file_with_newlines = temp_dir.path().join("test\n\x0B\x0C\rnewline.text");
|
||||
fs::write(&file_with_newlines, "Look at my newlines").unwrap();
|
||||
|
||||
let srv = test::init_service(
|
||||
App::new().service(Files::new("/", temp_dir.path()).index_file("Cargo.toml")),
|
||||
)
|
||||
.await;
|
||||
let request = TestRequest::get()
|
||||
.uri("/test%0A%0B%0C%0Dnewline.text")
|
||||
.to_request();
|
||||
let response = test::call_service(&srv, request).await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let bytes = test::read_body(response).await;
|
||||
let data = web::Bytes::from(fs::read(file_with_newlines).unwrap());
|
||||
assert_eq!(bytes, data);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_files_not_allowed() {
|
||||
let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
|
||||
|
@ -840,19 +864,21 @@ mod tests {
|
|||
|
||||
#[actix_rt::test]
|
||||
async fn test_percent_encoding_2() {
|
||||
let tmpdir = tempfile::tempdir().unwrap();
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let filename = match cfg!(unix) {
|
||||
true => "ض:?#[]{}<>()@!$&'`|*+,;= %20.test",
|
||||
true => "ض:?#[]{}<>()@!$&'`|*+,;= %20\n.test",
|
||||
false => "ض#[]{}()@!$&'`+,;= %20.test",
|
||||
};
|
||||
let filename_encoded = filename
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.map(|c| format!("%{:02X}", c))
|
||||
.collect::<String>();
|
||||
std::fs::File::create(tmpdir.path().join(filename)).unwrap();
|
||||
.fold(String::new(), |mut buf, c| {
|
||||
write!(&mut buf, "%{:02X}", c).unwrap();
|
||||
buf
|
||||
});
|
||||
std::fs::File::create(temp_dir.path().join(filename)).unwrap();
|
||||
|
||||
let srv = test::init_service(App::new().service(Files::new("", tmpdir.path()))).await;
|
||||
let srv = test::init_service(App::new().service(Files::new("/", temp_dir.path()))).await;
|
||||
|
||||
let req = TestRequest::get()
|
||||
.uri(&format!("/{}", filename_encoded))
|
||||
|
|
|
@ -24,7 +24,6 @@ use bitflags::bitflags;
|
|||
use derive_more::{Deref, DerefMut};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use mime::Mime;
|
||||
use mime_guess::from_path;
|
||||
|
||||
use crate::{encoding::equiv_utf8_text, range::HttpRange};
|
||||
|
||||
|
@ -128,7 +127,7 @@ impl NamedFile {
|
|||
}
|
||||
};
|
||||
|
||||
let ct = from_path(&path).first_or_octet_stream();
|
||||
let ct = mime_guess::from_path(&path).first_or_octet_stream();
|
||||
|
||||
let disposition = match ct.type_() {
|
||||
mime::IMAGE | mime::TEXT | mime::AUDIO | mime::VIDEO => DispositionType::Inline,
|
||||
|
@ -140,7 +139,13 @@ impl NamedFile {
|
|||
_ => DispositionType::Attachment,
|
||||
};
|
||||
|
||||
let mut parameters = vec![DispositionParam::Filename(String::from(filename.as_ref()))];
|
||||
// replace special characters in filenames which could occur on some filesystems
|
||||
let filename_s = filename
|
||||
.replace('\n', "%0A") // \n line break
|
||||
.replace('\x0B', "%0B") // \v vertical tab
|
||||
.replace('\x0C', "%0C") // \f form feed
|
||||
.replace('\r', "%0D"); // \r carriage return
|
||||
let mut parameters = vec![DispositionParam::Filename(filename_s)];
|
||||
|
||||
if !filename.is_ascii() {
|
||||
parameters.push(DispositionParam::FilenameExt(ExtendedValue {
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
## 3.2.0
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 3.1.0 - 2023-01-21
|
||||
## 3.1.0
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.59.
|
||||
|
||||
## 3.0.0 - 2022-07-24
|
||||
## 3.0.0
|
||||
|
||||
- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442]
|
||||
- Added `TestServer::client_headers` method. [#2097]
|
||||
|
@ -24,41 +28,41 @@
|
|||
<details>
|
||||
<summary>3.0.0 Pre-Releases</summary>
|
||||
|
||||
## 3.0.0-beta.13 - 2022-02-16
|
||||
## 3.0.0-beta.13
|
||||
|
||||
- No significant changes since `3.0.0-beta.12`.
|
||||
|
||||
## 3.0.0-beta.12 - 2022-01-31
|
||||
## 3.0.0-beta.12
|
||||
|
||||
- No significant changes since `3.0.0-beta.11`.
|
||||
|
||||
## 3.0.0-beta.11 - 2022-01-04
|
||||
## 3.0.0-beta.11
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||
|
||||
## 3.0.0-beta.10 - 2021-12-27
|
||||
## 3.0.0-beta.10
|
||||
|
||||
- Update `actix-server` to `2.0.0-rc.2`. [#2550]
|
||||
|
||||
[#2550]: https://github.com/actix/actix-web/pull/2550
|
||||
|
||||
## 3.0.0-beta.9 - 2021-12-11
|
||||
## 3.0.0-beta.9
|
||||
|
||||
- No significant changes since `3.0.0-beta.8`.
|
||||
|
||||
## 3.0.0-beta.8 - 2021-11-30
|
||||
## 3.0.0-beta.8
|
||||
|
||||
- Update `actix-tls` to `3.0.0-rc.1`. [#2474]
|
||||
|
||||
[#2474]: https://github.com/actix/actix-web/pull/2474
|
||||
|
||||
## 3.0.0-beta.7 - 2021-11-22
|
||||
## 3.0.0-beta.7
|
||||
|
||||
- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]
|
||||
|
||||
[#2408]: https://github.com/actix/actix-web/pull/2408
|
||||
|
||||
## 3.0.0-beta.6 - 2021-11-15
|
||||
## 3.0.0-beta.6
|
||||
|
||||
- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442]
|
||||
- Update `actix-server` to `2.0.0-beta.9`. [#2442]
|
||||
|
@ -66,25 +70,25 @@
|
|||
|
||||
[#2442]: https://github.com/actix/actix-web/pull/2442
|
||||
|
||||
## 3.0.0-beta.5 - 2021-09-09
|
||||
## 3.0.0-beta.5
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
||||
## 3.0.0-beta.4 - 2021-04-02
|
||||
## 3.0.0-beta.4
|
||||
|
||||
- Added `TestServer::client_headers` method. [#2097]
|
||||
|
||||
[#2097]: https://github.com/actix/actix-web/pull/2097
|
||||
|
||||
## 3.0.0-beta.3 - 2021-03-09
|
||||
## 3.0.0-beta.3
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 3.0.0-beta.2 - 2021-02-10
|
||||
## 3.0.0-beta.2
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 3.0.0-beta.1 - 2021-01-07
|
||||
## 3.0.0-beta.1
|
||||
|
||||
- Update `bytes` to `1.0`. [#1813]
|
||||
|
||||
|
@ -92,7 +96,7 @@
|
|||
|
||||
</details>
|
||||
|
||||
## 2.1.0 - 2020-11-25
|
||||
## 2.1.0
|
||||
|
||||
- Add ability to set address for `TestServer`. [#1645]
|
||||
- Upgrade `base64` to `0.13`.
|
||||
|
@ -101,11 +105,11 @@
|
|||
[#1773]: https://github.com/actix/actix-web/pull/1773
|
||||
[#1645]: https://github.com/actix/actix-web/pull/1645
|
||||
|
||||
## 2.0.0 - 2020-09-11
|
||||
## 2.0.0
|
||||
|
||||
- Update actix-codec and actix-utils dependencies.
|
||||
|
||||
## 2.0.0-alpha.1 - 2020-05-23
|
||||
## 2.0.0-alpha.1
|
||||
|
||||
- Update the `time` dependency to 0.2.7
|
||||
- Update `actix-connect` dependency to 2.0.0-alpha.2
|
||||
|
@ -115,57 +119,57 @@
|
|||
- Update `base64` dependency to 0.12
|
||||
- Update `env_logger` dependency to 0.7
|
||||
|
||||
## 1.0.0 - 2019-12-13
|
||||
## 1.0.0
|
||||
|
||||
- Replaced `TestServer::start()` with `test_server()`
|
||||
|
||||
## 1.0.0-alpha.3 - 2019-12-07
|
||||
## 1.0.0-alpha.3
|
||||
|
||||
- Migrate to `std::future`
|
||||
|
||||
## 0.2.5 - 2019-09-17
|
||||
## 0.2.5
|
||||
|
||||
- Update serde_urlencoded to "0.6.1"
|
||||
- Increase TestServerRuntime timeouts from 500ms to 3000ms
|
||||
- Do not override current `System`
|
||||
|
||||
## 0.2.4 - 2019-07-18
|
||||
## 0.2.4
|
||||
|
||||
- Update actix-server to 0.6
|
||||
|
||||
## 0.2.3 - 2019-07-16
|
||||
## 0.2.3
|
||||
|
||||
- Add `delete`, `options`, `patch` methods to `TestServerRunner`
|
||||
|
||||
## 0.2.2 - 2019-06-16
|
||||
## 0.2.2
|
||||
|
||||
- Add .put() and .sput() methods
|
||||
|
||||
## 0.2.1 - 2019-06-05
|
||||
## 0.2.1
|
||||
|
||||
- Add license files
|
||||
|
||||
## 0.2.0 - 2019-05-12
|
||||
## 0.2.0
|
||||
|
||||
- Update awc and actix-http deps
|
||||
|
||||
## 0.1.1 - 2019-04-24
|
||||
## 0.1.1
|
||||
|
||||
- Always make new connection for http client
|
||||
|
||||
## 0.1.0 - 2019-04-16
|
||||
## 0.1.0
|
||||
|
||||
- No changes
|
||||
|
||||
## 0.1.0-alpha.3 - 2019-04-02
|
||||
## 0.1.0-alpha.3
|
||||
|
||||
- Request functions accept path #743
|
||||
|
||||
## 0.1.0-alpha.2 - 2019-03-29
|
||||
## 0.1.0-alpha.2
|
||||
|
||||
- Added TestServerRuntime::load_body() method
|
||||
- Update actix-http and awc libraries
|
||||
|
||||
## 0.1.0-alpha.1 - 2019-03-28
|
||||
## 0.1.0-alpha.1
|
||||
|
||||
- Initial impl
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
[package]
|
||||
name = "actix-http-test"
|
||||
version = "3.1.0"
|
||||
version = "3.2.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Various helpers for Actix applications to use during testing"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
categories = [
|
||||
"network-programming",
|
||||
"asynchronous",
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
# actix-http-test
|
||||
# `actix-http-test`
|
||||
|
||||
> Various helpers for Actix applications to use during testing.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-http-test)
|
||||
[](https://docs.rs/actix-http-test/3.1.0)
|
||||

|
||||
[](https://docs.rs/actix-http-test/3.2.0)
|
||||

|
||||

|
||||
<br>
|
||||
[](https://deps.rs/crate/actix-http-test/3.1.0)
|
||||
[](https://deps.rs/crate/actix-http-test/3.2.0)
|
||||
[](https://crates.io/crates/actix-http-test)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-http-test)
|
||||
- Minimum Supported Rust Version (MSRV): 1.68
|
||||
<!-- prettier-ignore-end -->
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
|
|
@ -1,23 +1,71 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Add `body::to_body_limit()` function.
|
||||
- Add `error::InvalidStatusCode` re-export.
|
||||
|
||||
## 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
|
||||
|
||||
- Update `brotli` dependency to `6`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
## 3.6.0
|
||||
|
||||
### Added
|
||||
|
||||
- Add `rustls-0_22` crate feature.
|
||||
- Add `{h1::H1Service, h2::H2Service, HttpService}::rustls_0_22()` and `HttpService::rustls_0_22_with_config()` service constructors.
|
||||
- Implement `From<&HeaderMap>` for `http::HeaderMap`.
|
||||
|
||||
## 3.5.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Prevent hang when returning zero-sized response bodies through compression layer.
|
||||
|
||||
## 3.5.0
|
||||
|
||||
### Added
|
||||
|
||||
- Implement `From<HeaderMap>` for `http::HeaderMap`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated `zstd` dependency to `0.13`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Prevent compression of zero-sized response bodies.
|
||||
|
||||
## 3.4.0
|
||||
|
||||
### Added
|
||||
|
||||
- Add `rustls-0_20` crate feature.
|
||||
- Add `{h1::H1Service, h2::H2Service, HttpService}::rustls_021()` and `HttpService::rustls_021_with_config()` service constructors.
|
||||
- Add `body::to_bytes_limited()` function.
|
||||
- Add `body::BodyLimitExceeded` error type.
|
||||
|
||||
### Changed
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 3.3.1 - 2023-03-02
|
||||
## 3.3.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Use correct `http` version requirement to ensure support for const `HeaderName` definitions.
|
||||
|
||||
## 3.3.0 - 2023-01-21
|
||||
## 3.3.0
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -53,7 +101,7 @@
|
|||
[#2956]: https://github.com/actix/actix-web/pull/2956
|
||||
[#2968]: https://github.com/actix/actix-web/pull/2968
|
||||
|
||||
## 3.2.2 - 2022-09-11
|
||||
## 3.2.2
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -65,7 +113,7 @@
|
|||
|
||||
[#2369]: https://github.com/actix/actix-web/pull/2369
|
||||
|
||||
## 3.2.1 - 2022-07-02
|
||||
## 3.2.1
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -73,7 +121,7 @@
|
|||
|
||||
[#2794]: https://github.com/actix/actix-web/pull/2794
|
||||
|
||||
## 3.2.0 - 2022-06-30
|
||||
## 3.2.0
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -87,7 +135,7 @@
|
|||
[#2790]: https://github.com/actix/actix-web/pull/2790
|
||||
[#2798]: https://github.com/actix/actix-web/pull/2798
|
||||
|
||||
## 3.1.0 - 2022-06-11
|
||||
## 3.1.0
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -101,13 +149,13 @@
|
|||
[#2357]: https://github.com/actix/actix-web/issues/2357
|
||||
[#2779]: https://github.com/actix/actix-web/pull/2779
|
||||
|
||||
## 3.0.4 - 2022-03-09
|
||||
## 3.0.4
|
||||
|
||||
### Fixed
|
||||
|
||||
- Document on docs.rs with `ws` feature enabled.
|
||||
|
||||
## 3.0.3 - 2022-03-08
|
||||
## 3.0.3
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -115,7 +163,7 @@
|
|||
|
||||
[#2684]: https://github.com/actix/actix-web/pull/2684
|
||||
|
||||
## 3.0.2 - 2022-03-05
|
||||
## 3.0.2
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -123,13 +171,13 @@
|
|||
|
||||
[#2683]: https://github.com/actix/actix-web/pull/2683
|
||||
|
||||
## 3.0.1 - 2022-03-04
|
||||
## 3.0.1
|
||||
|
||||
- Fix panic in H1 dispatcher when pipelining is used with keep-alive. [#2678]
|
||||
|
||||
[#2678]: https://github.com/actix/actix-web/issues/2678
|
||||
|
||||
## 3.0.0 - 2022-02-25
|
||||
## 3.0.0
|
||||
|
||||
### Dependencies
|
||||
|
||||
|
@ -417,7 +465,7 @@
|
|||
<details>
|
||||
<summary>3.0.0 Pre-Releases</summary>
|
||||
|
||||
## 3.0.0-rc.4 - 2022-02-22
|
||||
## 3.0.0-rc.4
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -425,11 +473,11 @@
|
|||
|
||||
[1ce58ecb]: https://github.com/actix/actix-web/commit/1ce58ecb305c60e51db06e6c913b7a1344e229ca
|
||||
|
||||
## 3.0.0-rc.3 - 2022-02-16
|
||||
## 3.0.0-rc.3
|
||||
|
||||
- No significant changes since `3.0.0-rc.2`.
|
||||
|
||||
## 3.0.0-rc.2 - 2022-02-08
|
||||
## 3.0.0-rc.2
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -446,7 +494,7 @@
|
|||
[#2624]: https://github.com/actix/actix-web/pull/2624
|
||||
[#2625]: https://github.com/actix/actix-web/pull/2625
|
||||
|
||||
## 3.0.0-rc.1 - 2022-01-31
|
||||
## 3.0.0-rc.1
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -482,7 +530,7 @@
|
|||
[#2611]: https://github.com/actix/actix-web/pull/2611
|
||||
[#2618]: https://github.com/actix/actix-web/pull/2618
|
||||
|
||||
## 3.0.0-beta.19 - 2022-01-21
|
||||
## 3.0.0-beta.19
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -502,7 +550,7 @@
|
|||
[#2585]: https://github.com/actix/actix-web/pull/2585
|
||||
[#2587]: https://github.com/actix/actix-web/pull/2587
|
||||
|
||||
## 3.0.0-beta.18 - 2022-01-04
|
||||
## 3.0.0-beta.18
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -533,7 +581,7 @@
|
|||
[#2501]: https://github.com/actix/actix-web/pull/2501
|
||||
[#2565]: https://github.com/actix/actix-web/pull/2565
|
||||
|
||||
## 3.0.0-beta.17 - 2021-12-27
|
||||
## 3.0.0-beta.17
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -551,7 +599,7 @@
|
|||
[#2527]: https://github.com/actix/actix-web/pull/2527
|
||||
[#2545]: https://github.com/actix/actix-web/pull/2545
|
||||
|
||||
## 3.0.0-beta.16 - 2021-12-17
|
||||
## 3.0.0-beta.16
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -570,7 +618,7 @@
|
|||
[#2510]: https://github.com/actix/actix-web/pull/2510
|
||||
[#2522]: https://github.com/actix/actix-web/pull/2522
|
||||
|
||||
## 3.0.0-beta.15 - 2021-12-11
|
||||
## 3.0.0-beta.15
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -624,7 +672,7 @@
|
|||
[#2497]: https://github.com/actix/actix-web/pull/2497
|
||||
[#2520]: https://github.com/actix/actix-web/pull/2520
|
||||
|
||||
## 3.0.0-beta.14 - 2021-11-30
|
||||
## 3.0.0-beta.14
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -637,7 +685,7 @@
|
|||
[#2470]: https://github.com/actix/actix-web/pull/2470
|
||||
[#2474]: https://github.com/actix/actix-web/pull/2474
|
||||
|
||||
## 3.0.0-beta.13 - 2021-11-22
|
||||
## 3.0.0-beta.13
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -666,7 +714,7 @@
|
|||
[#2448]: https://github.com/actix/actix-web/pull/2448
|
||||
[#2456]: https://github.com/actix/actix-web/pull/2456
|
||||
|
||||
## 3.0.0-beta.12 - 2021-11-15
|
||||
## 3.0.0-beta.12
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -680,7 +728,7 @@
|
|||
[#2425]: https://github.com/actix/actix-web/pull/2425
|
||||
[#2442]: https://github.com/actix/actix-web/pull/2442
|
||||
|
||||
## 3.0.0-beta.11 - 2021-10-20
|
||||
## 3.0.0-beta.11
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -689,7 +737,7 @@
|
|||
|
||||
[#2414]: https://github.com/actix/actix-web/pull/2414
|
||||
|
||||
## 3.0.0-beta.10 - 2021-09-09
|
||||
## 3.0.0-beta.10
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -707,13 +755,13 @@
|
|||
[#2344]: https://github.com/actix/actix-web/pull/2344
|
||||
[#2377]: https://github.com/actix/actix-web/pull/2377
|
||||
|
||||
## 3.0.0-beta.9 - 2021-08-09
|
||||
## 3.0.0-beta.9
|
||||
|
||||
### Fixed
|
||||
|
||||
- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
|
||||
|
||||
## 3.0.0-beta.8 - 2021-06-26
|
||||
## 3.0.0-beta.8
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -726,7 +774,7 @@
|
|||
[#2291]: https://github.com/actix/actix-web/pull/2291
|
||||
[#2250]: https://github.com/actix/actix-web/pull/2250
|
||||
|
||||
## 3.0.0-beta.7 - 2021-06-17
|
||||
## 3.0.0-beta.7
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -773,7 +821,7 @@
|
|||
[#2253]: https://github.com/actix/actix-web/pull/2253
|
||||
[#2244]: https://github.com/actix/actix-web/pull/2244
|
||||
|
||||
## 3.0.0-beta.6 - 2021-04-17
|
||||
## 3.0.0-beta.6
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -808,7 +856,7 @@
|
|||
[#2158]: https://github.com/actix/actix-web/pull/2158
|
||||
[#2161]: https://github.com/actix/actix-web/pull/2161
|
||||
|
||||
## 3.0.0-beta.5 - 2021-04-02
|
||||
## 3.0.0-beta.5
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -830,7 +878,7 @@
|
|||
[#2094]: https://github.com/actix/actix-web/pull/2094
|
||||
[#2127]: https://github.com/actix/actix-web/pull/2127
|
||||
|
||||
## 3.0.0-beta.4 - 2021-03-08
|
||||
## 3.0.0-beta.4
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -848,11 +896,11 @@
|
|||
[#2035]: https://github.com/actix/actix-web/pull/2035
|
||||
[#2052]: https://github.com/actix/actix-web/pull/2052
|
||||
|
||||
## 3.0.0-beta.3 - 2021-02-10
|
||||
## 3.0.0-beta.3
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 3.0.0-beta.2 - 2021-02-10
|
||||
## 3.0.0-beta.2
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -904,7 +952,7 @@
|
|||
[#1964]: https://github.com/actix/actix-web/pull/1964
|
||||
[#1969]: https://github.com/actix/actix-web/pull/1969
|
||||
|
||||
## 3.0.0-beta.1 - 2021-01-07
|
||||
## 3.0.0-beta.1
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -932,7 +980,7 @@
|
|||
|
||||
</details>
|
||||
|
||||
## 2.2.2 - 2022-01-21
|
||||
## 2.2.2
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -940,13 +988,13 @@
|
|||
|
||||
[ad7e3c06]: https://github.com/actix/actix-web/commit/ad7e3c06
|
||||
|
||||
## 2.2.1 - 2021-08-09
|
||||
## 2.2.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
|
||||
|
||||
## 2.2.0 - 2020-11-25
|
||||
## 2.2.0
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -968,7 +1016,7 @@
|
|||
[#1793]: https://github.com/actix/actix-web/pull/1793
|
||||
[#1797]: https://github.com/actix/actix-web/pull/1797
|
||||
|
||||
## 2.1.0 - 2020-10-30
|
||||
## 2.1.0
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -985,18 +1033,18 @@
|
|||
[#1733]: https://github.com/actix/actix-web/pull/1733
|
||||
[#1744]: https://github.com/actix/actix-web/pull/1744
|
||||
|
||||
## 2.0.0 - 2020-09-11
|
||||
## 2.0.0
|
||||
|
||||
- No significant changes from `2.0.0-beta.4`.
|
||||
|
||||
## 2.0.0-beta.4 - 2020-09-09
|
||||
## 2.0.0-beta.4
|
||||
|
||||
### Changed
|
||||
|
||||
- Update actix-codec and actix-utils dependencies.
|
||||
- Update actix-connect and actix-tls dependencies.
|
||||
|
||||
## 2.0.0-beta.3 - 2020-08-14
|
||||
## 2.0.0-beta.3
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -1004,7 +1052,7 @@
|
|||
|
||||
[#1626]: https://github.com/actix/actix-web/pull/1626
|
||||
|
||||
## 2.0.0-beta.2 - 2020-07-21
|
||||
## 2.0.0-beta.2
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -1017,7 +1065,7 @@
|
|||
[#1614]: https://github.com/actix/actix-web/pull/1614
|
||||
[#1615]: https://github.com/actix/actix-web/pull/1615
|
||||
|
||||
## 2.0.0-beta.1 - 2020-07-11
|
||||
## 2.0.0-beta.1
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -1030,7 +1078,7 @@
|
|||
[#1586]: https://github.com/actix/actix-web/pull/1586
|
||||
[#1580]: https://github.com/actix/actix-web/pull/1580
|
||||
|
||||
## 2.0.0-alpha.4 - 2020-05-21
|
||||
## 2.0.0-alpha.4
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -1046,7 +1094,7 @@
|
|||
[#1439]: https://github.com/actix/actix-web/pull/1439
|
||||
[#1503]: https://github.com/actix/actix-web/pull/1503
|
||||
|
||||
## 2.0.0-alpha.3 - 2020-05-08
|
||||
## 2.0.0-alpha.3
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -1061,7 +1109,7 @@
|
|||
[#1422]: https://github.com/actix/actix-web/pull/1422
|
||||
[#1487]: https://github.com/actix/actix-web/pull/1487
|
||||
|
||||
## 2.0.0-alpha.2 - 2020-03-07
|
||||
## 2.0.0-alpha.2
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -1073,7 +1121,7 @@
|
|||
[#1394]: https://github.com/actix/actix-web/pull/1394
|
||||
[#1395]: https://github.com/actix/actix-web/pull/1395
|
||||
|
||||
## 2.0.0-alpha.1 - 2020-02-27
|
||||
## 2.0.0-alpha.1
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -1086,14 +1134,14 @@
|
|||
|
||||
- Allow `SameSite=None` cookies to be sent in a response.
|
||||
|
||||
## 1.0.1 - 2019-12-20
|
||||
## 1.0.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Poll upgrade service's readiness from HTTP service handlers
|
||||
- Replace brotli with brotli2 #1224
|
||||
|
||||
## 1.0.0 - 2019-12-13
|
||||
## 1.0.0
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -1103,7 +1151,7 @@
|
|||
|
||||
- Replace `flate2-xxx` features with `compress`
|
||||
|
||||
## 1.0.0-alpha.5 - 2019-12-09
|
||||
## 1.0.0-alpha.5
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -1114,7 +1162,7 @@
|
|||
|
||||
- Websockets: Ping and Pong should have binary data #1049
|
||||
|
||||
## 1.0.0-alpha.4 - 2019-12-08
|
||||
## 1.0.0-alpha.4
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -1124,14 +1172,14 @@
|
|||
|
||||
- Use rust based brotli compression library
|
||||
|
||||
## 1.0.0-alpha.3 - 2019-12-07
|
||||
## 1.0.0-alpha.3
|
||||
|
||||
### Changed
|
||||
|
||||
- Migrate to tokio 0.2
|
||||
- Migrate to `std::future`
|
||||
|
||||
## 0.2.11 - 2019-11-06
|
||||
## 0.2.11
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -1145,7 +1193,7 @@
|
|||
|
||||
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||
|
||||
## 0.2.10 - 2019-09-11
|
||||
## 0.2.10
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -1156,7 +1204,7 @@
|
|||
- h2 will use error response #1080
|
||||
- on_connect result isn't added to request extensions for http2 requests #1009
|
||||
|
||||
## 0.2.9 - 2019-08-13
|
||||
## 0.2.9
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -1168,7 +1216,7 @@
|
|||
|
||||
- Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031)
|
||||
|
||||
## 0.2.8 - 2019-08-01
|
||||
## 0.2.8
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -1180,20 +1228,20 @@
|
|||
- awc client panic #1016
|
||||
- Invalid response with compression middleware enabled, but compression-related features disabled #997
|
||||
|
||||
## 0.2.7 - 2019-07-18
|
||||
## 0.2.7
|
||||
|
||||
### Added
|
||||
|
||||
- Add support for downcasting response errors #986
|
||||
|
||||
## 0.2.6 - 2019-07-17
|
||||
## 0.2.6
|
||||
|
||||
### Changed
|
||||
|
||||
- Replace `ClonableService` with local copy
|
||||
- Upgrade `rand` dependency version to 0.7
|
||||
|
||||
## 0.2.5 - 2019-06-28
|
||||
## 0.2.5
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -1204,13 +1252,13 @@
|
|||
- Use `encoding_rs` crate instead of unmaintained `encoding` crate
|
||||
- Add `Copy` and `Clone` impls for `ws::Codec`
|
||||
|
||||
## 0.2.4 - 2019-06-16
|
||||
## 0.2.4
|
||||
|
||||
### Fixed
|
||||
|
||||
- Do not compress NoContent (204) responses #918
|
||||
|
||||
## 0.2.3 - 2019-06-02
|
||||
## 0.2.3
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -1221,19 +1269,19 @@
|
|||
|
||||
- SizedStream uses u64
|
||||
|
||||
## 0.2.2 - 2019-05-29
|
||||
## 0.2.2
|
||||
|
||||
### Fixed
|
||||
|
||||
- Parse incoming stream before closing stream on disconnect #868
|
||||
|
||||
## 0.2.1 - 2019-05-25
|
||||
## 0.2.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Handle socket read disconnect
|
||||
|
||||
## 0.2.0 - 2019-05-12
|
||||
## 0.2.0
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -1244,13 +1292,13 @@
|
|||
|
||||
- `OneRequest` service
|
||||
|
||||
## 0.1.5 - 2019-05-04
|
||||
## 0.1.5
|
||||
|
||||
### Fixed
|
||||
|
||||
- Clean up response extensions in response pool #817
|
||||
|
||||
## 0.1.4 - 2019-04-24
|
||||
## 0.1.4
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -1260,20 +1308,20 @@
|
|||
|
||||
- Read until eof for http/1.0 responses #771
|
||||
|
||||
## 0.1.3 - 2019-04-23
|
||||
## 0.1.3
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix http client pool management
|
||||
- Fix http client wait queue management #794
|
||||
|
||||
## 0.1.2 - 2019-04-23
|
||||
## 0.1.2
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix BorrowMutError panic in client connector #793
|
||||
|
||||
## 0.1.1 - 2019-04-19
|
||||
## 0.1.1
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -1281,7 +1329,7 @@
|
|||
- Cookie::max_age_time() accepts value in time::Duration
|
||||
- Allow to specify server address for client connector
|
||||
|
||||
## 0.1.0 - 2019-04-16
|
||||
## 0.1.0
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -1292,7 +1340,7 @@
|
|||
- `actix_http::encoding` always available
|
||||
- use trust-dns-resolver 0.11.0
|
||||
|
||||
## 0.1.0-alpha.5 - 2019-04-12
|
||||
## 0.1.0-alpha.5
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -1304,7 +1352,7 @@
|
|||
- MessageBody::length() renamed to MessageBody::size() for consistency
|
||||
- ws handshake verification functions take RequestHead instead of Request
|
||||
|
||||
## 0.1.0-alpha.4 - 2019-04-08
|
||||
## 0.1.0-alpha.4
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -1321,7 +1369,7 @@
|
|||
|
||||
- Removed PayloadBuffer
|
||||
|
||||
## 0.1.0-alpha.3 - 2019-04-02
|
||||
## 0.1.0-alpha.3
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -1333,7 +1381,7 @@
|
|||
- Preallocate read buffer for h1 codec
|
||||
- Detect socket disconnection during protocol selection
|
||||
|
||||
## 0.1.0-alpha.2 - 2019-03-29
|
||||
## 0.1.0-alpha.2
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -1343,6 +1391,6 @@
|
|||
|
||||
- Do not use thread pool for decompression if chunk size is smaller than 2048.
|
||||
|
||||
## 0.1.0-alpha.1 - 2019-03-28
|
||||
## 0.1.0-alpha.1
|
||||
|
||||
- Initial impl
|
||||
|
|
|
@ -1,26 +1,38 @@
|
|||
[package]
|
||||
name = "actix-http"
|
||||
version = "3.3.1"
|
||||
version = "3.7.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
description = "HTTP primitives for the Actix ecosystem"
|
||||
description = "HTTP types and services for the Actix ecosystem"
|
||||
keywords = ["actix", "http", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
categories = [
|
||||
"network-programming",
|
||||
"asynchronous",
|
||||
"web-programming::http-server",
|
||||
"web-programming::websocket",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
# features that docs.rs will build with
|
||||
features = ["http2", "ws", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
features = [
|
||||
"http2",
|
||||
"ws",
|
||||
"openssl",
|
||||
"rustls-0_20",
|
||||
"rustls-0_21",
|
||||
"rustls-0_22",
|
||||
"rustls-0_23",
|
||||
"compress-brotli",
|
||||
"compress-gzip",
|
||||
"compress-zstd",
|
||||
]
|
||||
|
||||
[lib]
|
||||
name = "actix_http"
|
||||
|
@ -43,15 +55,27 @@ ws = [
|
|||
# TLS via OpenSSL
|
||||
openssl = ["actix-tls/accept", "actix-tls/openssl"]
|
||||
|
||||
# TLS via Rustls
|
||||
rustls = ["actix-tls/accept", "actix-tls/rustls"]
|
||||
# TLS via Rustls v0.20
|
||||
rustls = ["rustls-0_20"]
|
||||
|
||||
# TLS via Rustls v0.20
|
||||
rustls-0_20 = ["actix-tls/accept", "actix-tls/rustls-0_20"]
|
||||
|
||||
# TLS via Rustls v0.21
|
||||
rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"]
|
||||
|
||||
# TLS via Rustls v0.22
|
||||
rustls-0_22 = ["actix-tls/accept", "actix-tls/rustls-0_22"]
|
||||
|
||||
# TLS via Rustls v0.23
|
||||
rustls-0_23 = ["actix-tls/accept", "actix-tls/rustls-0_23"]
|
||||
|
||||
# Compression codecs
|
||||
compress-brotli = ["__compress", "brotli"]
|
||||
compress-gzip = ["__compress", "flate2"]
|
||||
compress-zstd = ["__compress", "zstd"]
|
||||
|
||||
# Internal (PRIVATE!) features used to aid testing and cheking feature status.
|
||||
# Internal (PRIVATE!) features used to aid testing and checking feature status.
|
||||
# Don't rely on these whatsoever. They are semver-exempt and may disappear at anytime.
|
||||
__compress = []
|
||||
|
||||
|
@ -82,50 +106,59 @@ tokio-util = { version = "0.7", features = ["io", "codec"] }
|
|||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
# http2
|
||||
h2 = { version = "0.3.17", optional = true }
|
||||
h2 = { version = "0.3.26", optional = true }
|
||||
|
||||
# websockets
|
||||
local-channel = { version = "0.1", optional = true }
|
||||
base64 = { version = "0.21", optional = true }
|
||||
base64 = { version = "0.22", optional = true }
|
||||
rand = { version = "0.8", optional = true }
|
||||
sha1 = { version = "0.10", optional = true }
|
||||
|
||||
# openssl/rustls
|
||||
actix-tls = { version = "3", default-features = false, optional = true }
|
||||
actix-tls = { version = "3.4", default-features = false, optional = true }
|
||||
|
||||
# compress-*
|
||||
brotli = { version = "3.3.3", optional = true }
|
||||
brotli = { version = "6", optional = true }
|
||||
flate2 = { version = "1.0.13", optional = true }
|
||||
zstd = { version = "0.12", optional = true }
|
||||
zstd = { version = "0.13", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http-test = { version = "3", features = ["openssl"] }
|
||||
actix-server = "2"
|
||||
actix-tls = { version = "3", features = ["openssl"] }
|
||||
actix-tls = { version = "3.4", features = ["openssl", "rustls-0_23-webpki-roots"] }
|
||||
actix-web = "4"
|
||||
|
||||
async-stream = "0.3"
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
env_logger = "0.10"
|
||||
divan = "0.1.8"
|
||||
env_logger = "0.11"
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
memchr = "2.4"
|
||||
once_cell = "1.9"
|
||||
rcgen = "0.11"
|
||||
rcgen = "0.13"
|
||||
regex = "1.3"
|
||||
rustversion = "1"
|
||||
rustls-pemfile = "1"
|
||||
rustls-pemfile = "2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
static_assertions = "1"
|
||||
tls-openssl = { package = "openssl", version = "0.10.55" }
|
||||
tls-rustls = { package = "rustls", version = "0.20" }
|
||||
tls-rustls_023 = { package = "rustls", version = "0.23" }
|
||||
tokio = { version = "1.24.2", features = ["net", "rt", "macros"] }
|
||||
|
||||
[[example]]
|
||||
name = "ws"
|
||||
required-features = ["ws", "rustls"]
|
||||
required-features = ["ws", "rustls-0_23"]
|
||||
|
||||
[[example]]
|
||||
name = "tls_rustls"
|
||||
required-features = ["http2", "rustls-0_23"]
|
||||
|
||||
[[bench]]
|
||||
name = "response-body-compression"
|
||||
harness = false
|
||||
required-features = ["compress-brotli", "compress-gzip", "compress-zstd"]
|
||||
|
||||
[[bench]]
|
||||
name = "date-formatting"
|
||||
harness = false
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
# actix-http
|
||||
# `actix-http`
|
||||
|
||||
> HTTP primitives for the Actix ecosystem.
|
||||
> HTTP types and services for the Actix ecosystem.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://docs.rs/actix-http/3.3.1)
|
||||

|
||||
[](https://docs.rs/actix-http/3.7.0)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-http/3.3.1)
|
||||
[](https://deps.rs/crate/actix-http/3.7.0)
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-http)
|
||||
- Minimum Supported Rust Version (MSRV): 1.68
|
||||
|
||||
## Example
|
||||
## Examples
|
||||
|
||||
```rust
|
||||
use std::{env, io};
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
use std::time::SystemTime;
|
||||
|
||||
use actix_http::header::HttpDate;
|
||||
use divan::{black_box, AllocProfiler, Bencher};
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOC: AllocProfiler = AllocProfiler::system();
|
||||
|
||||
#[divan::bench]
|
||||
fn date_formatting(b: Bencher<'_, '_>) {
|
||||
let now = SystemTime::now();
|
||||
|
||||
b.bench(|| {
|
||||
black_box(HttpDate::from(black_box(now)).to_string());
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
divan::main();
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use std::convert::Infallible;
|
||||
|
||||
use actix_http::{encoding::Encoder, ContentEncoding, Request, Response, StatusCode};
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
use std::{convert::Infallible, io};
|
||||
|
||||
use actix_http::{HttpService, Request, Response, StatusCode};
|
||||
use actix_http::{body::BodyStream, HttpService, Request, Response, StatusCode};
|
||||
use actix_server::Server;
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
|
@ -19,7 +19,12 @@ async fn main() -> io::Result<()> {
|
|||
.bind("h2c-detect", ("127.0.0.1", 8080), || {
|
||||
HttpService::build()
|
||||
.finish(|_req: Request| async move {
|
||||
Ok::<_, Infallible>(Response::build(StatusCode::OK).body("Hello!"))
|
||||
Ok::<_, Infallible>(Response::build(StatusCode::OK).body(BodyStream::new(
|
||||
futures_util::stream::iter([
|
||||
Ok::<_, String>("123".into()),
|
||||
Err("wertyuikmnbvcxdfty6t".to_owned()),
|
||||
]),
|
||||
)))
|
||||
})
|
||||
.tcp_auto_h2c()
|
||||
})?
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
//! Demonstrates TLS configuration (via Rustls) for HTTP/1.1 and HTTP/2 connections.
|
||||
//!
|
||||
//! Test using cURL:
|
||||
//!
|
||||
//! ```console
|
||||
//! $ curl --insecure https://127.0.0.1:8443
|
||||
//! Hello World!
|
||||
//! Protocol: HTTP/2.0
|
||||
//!
|
||||
//! $ curl --insecure --http1.1 https://127.0.0.1:8443
|
||||
//! Hello World!
|
||||
//! Protocol: HTTP/1.1
|
||||
//! ```
|
||||
|
||||
extern crate tls_rustls_023 as rustls;
|
||||
|
||||
use std::io;
|
||||
|
||||
use actix_http::{Error, HttpService, Request, Response};
|
||||
use actix_utils::future::ok;
|
||||
|
||||
#[actix_rt::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||
|
||||
tracing::info!("starting HTTP server at https://127.0.0.1:8443");
|
||||
|
||||
actix_server::Server::build()
|
||||
.bind("echo", ("127.0.0.1", 8443), || {
|
||||
HttpService::build()
|
||||
.finish(|req: Request| {
|
||||
let body = format!(
|
||||
"Hello World!\n\
|
||||
Protocol: {:?}",
|
||||
req.head().version
|
||||
);
|
||||
ok::<_, Error>(Response::ok().set_body(body))
|
||||
})
|
||||
.rustls_0_23(rustls_config())
|
||||
})?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
fn rustls_config() -> rustls::ServerConfig {
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert_file = &mut io::BufReader::new(cert_file.as_bytes());
|
||||
let key_file = &mut io::BufReader::new(key_file.as_bytes());
|
||||
|
||||
let cert_chain = rustls_pemfile::certs(cert_file)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
let mut keys = rustls_pemfile::pkcs8_private_keys(key_file)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
|
||||
let mut config = rustls::ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(
|
||||
cert_chain,
|
||||
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
const H1_ALPN: &[u8] = b"http/1.1";
|
||||
const H2_ALPN: &[u8] = b"h2";
|
||||
|
||||
config.alpn_protocols.push(H2_ALPN.to_vec());
|
||||
config.alpn_protocols.push(H1_ALPN.to_vec());
|
||||
|
||||
config
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
//! Sets up a WebSocket server over TCP and TLS.
|
||||
//! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames.
|
||||
|
||||
extern crate tls_rustls as rustls;
|
||||
extern crate tls_rustls_023 as rustls;
|
||||
|
||||
use std::{
|
||||
io,
|
||||
|
@ -28,7 +28,9 @@ async fn main() -> io::Result<()> {
|
|||
HttpService::build().h1(handler).tcp()
|
||||
})?
|
||||
.bind("tls", ("127.0.0.1", 8443), || {
|
||||
HttpService::build().finish(handler).rustls(tls_config())
|
||||
HttpService::build()
|
||||
.finish(handler)
|
||||
.rustls_0_23(tls_config())
|
||||
})?
|
||||
.run()
|
||||
.await
|
||||
|
@ -83,27 +85,27 @@ impl Stream for Heartbeat {
|
|||
fn tls_config() -> rustls::ServerConfig {
|
||||
use std::io::BufReader;
|
||||
|
||||
use rustls::{Certificate, PrivateKey};
|
||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert_file = &mut BufReader::new(cert_file.as_bytes());
|
||||
let key_file = &mut BufReader::new(key_file.as_bytes());
|
||||
|
||||
let cert_chain = certs(cert_file)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
let mut keys = pkcs8_private_keys(key_file).unwrap();
|
||||
let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap();
|
||||
let mut keys = pkcs8_private_keys(key_file)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
|
||||
let mut config = rustls::ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
|
||||
.with_single_cert(
|
||||
cert_chain,
|
||||
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
config.alpn_protocols.push(b"http/1.1".to_vec());
|
||||
|
|
|
@ -531,7 +531,6 @@ where
|
|||
mod tests {
|
||||
use actix_rt::pin;
|
||||
use actix_utils::future::poll_fn;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures_util::stream;
|
||||
|
||||
use super::*;
|
||||
|
|
|
@ -28,7 +28,7 @@ impl Date {
|
|||
|
||||
fn update(&mut self) {
|
||||
self.pos = 0;
|
||||
write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap();
|
||||
write!(self, "{}", httpdate::HttpDate::from(SystemTime::now())).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -191,7 +191,7 @@ impl ContentDecoder {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
|
@ -205,7 +205,7 @@ impl ContentDecoder {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
|
@ -218,7 +218,7 @@ impl ContentDecoder {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
|
@ -231,7 +231,7 @@ impl ContentDecoder {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -250,7 +250,7 @@ impl ContentDecoder {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
|
@ -265,7 +265,7 @@ impl ContentDecoder {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
|
@ -280,7 +280,7 @@ impl ContentDecoder {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
|
@ -295,7 +295,7 @@ impl ContentDecoder {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,10 +50,21 @@ impl<B: MessageBody> Encoder<B> {
|
|||
}
|
||||
}
|
||||
|
||||
fn empty() -> Self {
|
||||
Encoder {
|
||||
body: EncoderBody::Full { body: Bytes::new() },
|
||||
encoder: None,
|
||||
fut: None,
|
||||
eof: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, body: B) -> Self {
|
||||
// no need to compress an empty body
|
||||
if matches!(body.size(), BodySize::None) {
|
||||
return Self::none();
|
||||
// no need to compress empty bodies
|
||||
match body.size() {
|
||||
BodySize::None => return Self::none(),
|
||||
BodySize::Sized(0) => return Self::empty(),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let should_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};
|
||||
|
||||
use derive_more::{Display, Error, From};
|
||||
pub use http::Error as HttpError;
|
||||
pub use http::{status::InvalidStatusCode, Error as HttpError};
|
||||
use http::{uri::InvalidUri, StatusCode};
|
||||
|
||||
use crate::{body::BoxBody, Response};
|
||||
|
@ -399,9 +399,7 @@ pub enum ContentTypeError {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io;
|
||||
|
||||
use http::{Error as HttpError, StatusCode};
|
||||
use http::Error as HttpError;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
|
@ -198,9 +198,6 @@ impl Encoder<Message<(Response<()>, BodySize)>> for Codec {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bytes::BytesMut;
|
||||
use http::Method;
|
||||
|
||||
use super::*;
|
||||
use crate::HttpMessage as _;
|
||||
|
||||
|
|
|
@ -532,7 +532,7 @@ impl Decoder for PayloadDecoder {
|
|||
*state = match state.step(src, size, &mut buf) {
|
||||
Poll::Pending => return Ok(None),
|
||||
Poll::Ready(Ok(state)) => state,
|
||||
Poll::Ready(Err(e)) => return Err(e),
|
||||
Poll::Ready(Err(err)) => return Err(err),
|
||||
};
|
||||
|
||||
if *state == ChunkedState::End {
|
||||
|
@ -563,15 +563,8 @@ impl Decoder for PayloadDecoder {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use http::{Method, Version};
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
error::ParseError,
|
||||
header::{HeaderName, SET_COOKIE},
|
||||
HttpMessage as _,
|
||||
};
|
||||
use crate::{header::SET_COOKIE, HttpMessage as _};
|
||||
|
||||
impl PayloadType {
|
||||
pub(crate) fn unwrap(self) -> PayloadDecoder {
|
||||
|
|
|
@ -512,8 +512,10 @@ where
|
|||
}
|
||||
|
||||
Poll::Ready(Some(Err(err))) => {
|
||||
let err = err.into();
|
||||
tracing::error!("Response payload stream error: {err:?}");
|
||||
this.flags.insert(Flags::FINISHED);
|
||||
return Err(DispatchError::Body(err.into()));
|
||||
return Err(DispatchError::Body(err));
|
||||
}
|
||||
|
||||
Poll::Pending => return Ok(PollResponse::DoNothing),
|
||||
|
@ -549,6 +551,7 @@ where
|
|||
}
|
||||
|
||||
Poll::Ready(Some(Err(err))) => {
|
||||
tracing::error!("Response payload stream error: {err:?}");
|
||||
this.flags.insert(Flags::FINISHED);
|
||||
return Err(DispatchError::Body(
|
||||
Error::new_body().with_cause(err).into(),
|
||||
|
@ -703,7 +706,7 @@ where
|
|||
|
||||
req.head_mut().peer_addr = *this.peer_addr;
|
||||
|
||||
req.conn_data = this.conn_data.as_ref().map(Rc::clone);
|
||||
req.conn_data.clone_from(this.conn_data);
|
||||
|
||||
match this.codec.message_type() {
|
||||
// request has no payload
|
||||
|
|
|
@ -117,6 +117,7 @@ impl PayloadSender {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_ref_mut)]
|
||||
#[inline]
|
||||
pub fn need_read(&self, cx: &mut Context<'_>) -> PayloadStatus {
|
||||
// we check need_read only if Payload (other side) is alive,
|
||||
|
@ -174,7 +175,7 @@ impl Inner {
|
|||
|
||||
/// Register future waiting data from payload.
|
||||
/// Waker would be used in `Inner::wake`
|
||||
fn register(&mut self, cx: &mut Context<'_>) {
|
||||
fn register(&mut self, cx: &Context<'_>) {
|
||||
if self
|
||||
.task
|
||||
.as_ref()
|
||||
|
@ -186,7 +187,7 @@ impl Inner {
|
|||
|
||||
// Register future feeding data to payload.
|
||||
/// Waker would be used in `Inner::wake_io`
|
||||
fn register_io(&mut self, cx: &mut Context<'_>) {
|
||||
fn register_io(&mut self, cx: &Context<'_>) {
|
||||
if self
|
||||
.io_task
|
||||
.as_ref()
|
||||
|
@ -221,7 +222,7 @@ impl Inner {
|
|||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
cx: &Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, PayloadError>>> {
|
||||
if let Some(data) = self.items.pop_front() {
|
||||
self.len -= data.len();
|
||||
|
|
|
@ -152,13 +152,13 @@ mod openssl {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
mod rustls {
|
||||
#[cfg(feature = "rustls-0_20")]
|
||||
mod rustls_0_20 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
rustls_0_20::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
|
@ -188,7 +188,7 @@ mod rustls {
|
|||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create Rustls based service.
|
||||
/// Create Rustls v0.20 based service.
|
||||
pub fn rustls(
|
||||
self,
|
||||
config: ServerConfig,
|
||||
|
@ -213,6 +213,189 @@ mod rustls {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_21")]
|
||||
mod rustls_0_21 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_21::{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.21 based service.
|
||||
pub fn rustls_021(
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
mod rustls_0_22 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
|
||||
B: MessageBody,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<
|
||||
(Request, Framed<TlsStream<TcpStream>, Codec>),
|
||||
Config = (),
|
||||
Response = (),
|
||||
>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create Rustls v0.22 based service.
|
||||
pub fn rustls_0_22(
|
||||
self,
|
||||
config: ServerConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
Acceptor::new(config)
|
||||
.map_init_err(|_| {
|
||||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.map(|io: TlsStream<TcpStream>| {
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
(io, peer_addr)
|
||||
})
|
||||
.and_then(self.map_err(TlsError::Service))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::{
|
|||
future::Future,
|
||||
marker::PhantomData,
|
||||
net,
|
||||
pin::Pin,
|
||||
pin::{pin, Pin},
|
||||
rc::Rc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
@ -20,7 +20,6 @@ use h2::{
|
|||
Ping, PingPong,
|
||||
};
|
||||
use pin_project_lite::pin_project;
|
||||
use tracing::{error, trace, warn};
|
||||
|
||||
use crate::{
|
||||
body::{BodySize, BoxBody, MessageBody},
|
||||
|
@ -127,7 +126,7 @@ where
|
|||
head.headers = parts.headers.into();
|
||||
head.peer_addr = this.peer_addr;
|
||||
|
||||
req.conn_data = this.conn_data.as_ref().map(Rc::clone);
|
||||
req.conn_data.clone_from(&this.conn_data);
|
||||
|
||||
let fut = this.flow.service.call(req);
|
||||
let config = this.config.clone();
|
||||
|
@ -147,11 +146,13 @@ where
|
|||
if let Err(err) = res {
|
||||
match err {
|
||||
DispatchError::SendResponse(err) => {
|
||||
trace!("Error sending HTTP/2 response: {:?}", err)
|
||||
tracing::trace!("Error sending response: {err:?}");
|
||||
}
|
||||
DispatchError::SendData(err) => {
|
||||
tracing::warn!("Send data error: {err:?}");
|
||||
}
|
||||
DispatchError::SendData(err) => warn!("{:?}", err),
|
||||
DispatchError::ResponseBody(err) => {
|
||||
error!("Response payload stream error: {:?}", err)
|
||||
tracing::error!("Response payload stream error: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -228,9 +229,9 @@ where
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
// poll response body and send chunks to client
|
||||
actix_rt::pin!(body);
|
||||
let mut body = pin!(body);
|
||||
|
||||
// poll response body and send chunks to client
|
||||
while let Some(res) = poll_fn(|cx| body.as_mut().poll_next(cx)).await {
|
||||
let mut chunk = res.map_err(|err| DispatchError::ResponseBody(err.into()))?;
|
||||
|
||||
|
|
|
@ -140,8 +140,8 @@ mod openssl {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
mod rustls {
|
||||
#[cfg(feature = "rustls-0_20")]
|
||||
mod rustls_0_20 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
|
@ -162,7 +162,7 @@ mod rustls {
|
|||
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
/// Create Rustls based service.
|
||||
/// Create Rustls v0.20 based service.
|
||||
pub fn rustls(
|
||||
self,
|
||||
mut config: ServerConfig,
|
||||
|
@ -191,6 +191,159 @@ mod rustls {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_21")]
|
||||
mod rustls_0_21 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_21::{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.21 based service.
|
||||
pub fn rustls_021(
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
mod rustls_0_22 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
/// Create Rustls v0.22 based service.
|
||||
pub fn rustls_0_22(
|
||||
self,
|
||||
mut config: ServerConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = S::InitError,
|
||||
> {
|
||||
let mut protos = vec![b"h2".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
||||
Acceptor::new(config)
|
||||
.map_init_err(|_| {
|
||||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.map(|io: TlsStream<TcpStream>| {
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
(io, peer_addr)
|
||||
})
|
||||
.and_then(self.map_err(TlsError::Service))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||
|
|
|
@ -636,10 +636,24 @@ impl<'a> IntoIterator for &'a HeaderMap {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert `http::HeaderMap` to our `HeaderMap`.
|
||||
/// Convert a `http::HeaderMap` to our `HeaderMap`.
|
||||
impl From<http::HeaderMap> for HeaderMap {
|
||||
fn from(mut map: http::HeaderMap) -> HeaderMap {
|
||||
HeaderMap::from_drain(map.drain())
|
||||
fn from(mut map: http::HeaderMap) -> Self {
|
||||
Self::from_drain(map.drain())
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert our `HeaderMap` to a `http::HeaderMap`.
|
||||
impl From<HeaderMap> for http::HeaderMap {
|
||||
fn from(map: HeaderMap) -> Self {
|
||||
Self::from_iter(map)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert our `&HeaderMap` to a `http::HeaderMap`.
|
||||
impl From<&HeaderMap> for http::HeaderMap {
|
||||
fn from(map: &HeaderMap) -> Self {
|
||||
map.to_owned().into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,7 @@ impl FromStr for HttpDate {
|
|||
|
||||
impl fmt::Display for HttpDate {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let date_str = httpdate::fmt_http_date(self.0);
|
||||
f.write_str(&date_str)
|
||||
httpdate::HttpDate::from(self.0).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,7 +36,7 @@ impl TryIntoHeaderValue for HttpDate {
|
|||
let mut wrt = MutWriter(&mut buf);
|
||||
|
||||
// unwrap: date output is known to be well formed and of known length
|
||||
write!(wrt, "{}", httpdate::fmt_http_date(self.0)).unwrap();
|
||||
write!(wrt, "{}", self).unwrap();
|
||||
|
||||
HeaderValue::from_maybe_shared(buf.split().freeze())
|
||||
}
|
||||
|
|
|
@ -80,18 +80,18 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn comma_delimited_parsing() {
|
||||
let headers = vec![];
|
||||
let headers = [];
|
||||
let res: Vec<usize> = from_comma_delimited(headers.iter()).unwrap();
|
||||
assert_eq!(res, vec![0; 0]);
|
||||
|
||||
let headers = vec![
|
||||
let headers = [
|
||||
HeaderValue::from_static("1, 2"),
|
||||
HeaderValue::from_static("3,4"),
|
||||
];
|
||||
let res: Vec<usize> = from_comma_delimited(headers.iter()).unwrap();
|
||||
assert_eq!(res, vec![1, 2, 3, 4]);
|
||||
|
||||
let headers = vec![
|
||||
let headers = [
|
||||
HeaderValue::from_static(""),
|
||||
HeaderValue::from_static(","),
|
||||
HeaderValue::from_static(" "),
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
//! HTTP primitives for the Actix ecosystem.
|
||||
//! HTTP types and services for the Actix ecosystem.
|
||||
//!
|
||||
//! ## Crate Features
|
||||
//!
|
||||
//! | Feature | Functionality |
|
||||
//! | ------------------- | ------------------------------------------- |
|
||||
//! | `http2` | HTTP/2 support via [h2]. |
|
||||
//! | `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-gzip` | Payload compression support: Deflate, Gzip. |
|
||||
//! | `compress-zstd` | Payload compression support: Zstd. |
|
||||
|
@ -21,14 +25,13 @@
|
|||
#![allow(
|
||||
clippy::type_complexity,
|
||||
clippy::too_many_arguments,
|
||||
clippy::borrow_interior_mutable_const,
|
||||
clippy::uninlined_format_args
|
||||
clippy::borrow_interior_mutable_const
|
||||
)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![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;
|
||||
mod builder;
|
||||
|
@ -58,7 +61,13 @@ pub mod ws;
|
|||
|
||||
#[allow(deprecated)]
|
||||
pub use self::payload::PayloadStream;
|
||||
#[cfg(any(feature = "openssl", feature = "rustls"))]
|
||||
#[cfg(any(
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
pub use self::service::TlsAcceptorConfig;
|
||||
pub use self::{
|
||||
builder::HttpServiceBuilder,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
use std::cell::RefCell;
|
||||
|
||||
thread_local! {
|
||||
static NOTIFY_DROPPED: RefCell<Option<bool>> = RefCell::new(None);
|
||||
static NOTIFY_DROPPED: RefCell<Option<bool>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
/// Check if the spawned task is dropped.
|
||||
|
|
|
@ -16,7 +16,10 @@ pub struct RequestHead {
|
|||
pub uri: Uri,
|
||||
pub version: Version,
|
||||
pub headers: HeaderMap,
|
||||
|
||||
/// Will only be None when called in unit tests unless set manually.
|
||||
pub peer_addr: Option<net::SocketAddr>,
|
||||
|
||||
flags: Flags,
|
||||
}
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ impl<P> Request<P> {
|
|||
/// Peer address is the directly connected peer's socket address. If a proxy is used in front of
|
||||
/// the Actix Web server, then it would be address of this proxy.
|
||||
///
|
||||
/// Will only return None when called in unit tests.
|
||||
/// Will only return None when called in unit tests unless set manually.
|
||||
#[inline]
|
||||
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||
self.head().peer_addr
|
||||
|
|
|
@ -93,7 +93,7 @@ impl ResponseBuilder {
|
|||
Ok((key, value)) => {
|
||||
parts.headers.insert(key, value);
|
||||
}
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
Err(err) => self.err = Some(err.into()),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -119,7 +119,7 @@ impl ResponseBuilder {
|
|||
if let Some(parts) = self.inner() {
|
||||
match header.try_into_pair() {
|
||||
Ok((key, value)) => parts.headers.append(key, value),
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
Err(err) => self.err = Some(err.into()),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -193,7 +193,7 @@ impl ResponseBuilder {
|
|||
Ok(value) => {
|
||||
parts.headers.insert(header::CONTENT_TYPE, value);
|
||||
}
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
Err(err) => self.err = Some(err.into()),
|
||||
};
|
||||
}
|
||||
self
|
||||
|
|
|
@ -241,13 +241,25 @@ where
|
|||
}
|
||||
|
||||
/// Configuration options used when accepting TLS connection.
|
||||
#[cfg(any(feature = "openssl", feature = "rustls"))]
|
||||
#[cfg(any(
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TlsAcceptorConfig {
|
||||
pub(crate) handshake_timeout: Option<std::time::Duration>,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "openssl", feature = "rustls"))]
|
||||
#[cfg(any(
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
impl TlsAcceptorConfig {
|
||||
/// Set TLS handshake timeout duration.
|
||||
pub fn handshake_timeout(self, dur: std::time::Duration) -> Self {
|
||||
|
@ -352,13 +364,13 @@ mod openssl {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
mod rustls {
|
||||
#[cfg(feature = "rustls-0_20")]
|
||||
mod rustls_0_20 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
rustls_0_20::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
|
@ -389,7 +401,7 @@ mod rustls {
|
|||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create Rustls based service.
|
||||
/// Create Rustls v0.20 based service.
|
||||
pub fn rustls(
|
||||
self,
|
||||
config: ServerConfig,
|
||||
|
@ -403,7 +415,7 @@ mod rustls {
|
|||
self.rustls_with_config(config, TlsAcceptorConfig::default())
|
||||
}
|
||||
|
||||
/// Create Rustls based service with custom TLS acceptor configuration.
|
||||
/// Create Rustls v0.20 based service with custom TLS acceptor configuration.
|
||||
pub fn rustls_with_config(
|
||||
self,
|
||||
mut config: ServerConfig,
|
||||
|
@ -448,6 +460,294 @@ mod rustls {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_21")]
|
||||
mod rustls_0_21 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_21::{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.21 based service.
|
||||
pub fn rustls_021(
|
||||
self,
|
||||
config: ServerConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
self.rustls_021_with_config(config, TlsAcceptorConfig::default())
|
||||
}
|
||||
|
||||
/// Create Rustls v0.21 based service with custom TLS acceptor configuration.
|
||||
pub fn rustls_021_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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
mod rustls_0_22 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<
|
||||
(Request, Framed<TlsStream<TcpStream>, h1::Codec>),
|
||||
Config = (),
|
||||
Response = (),
|
||||
>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create Rustls v0.22 based service.
|
||||
pub fn rustls_0_22(
|
||||
self,
|
||||
config: ServerConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
self.rustls_0_22_with_config(config, TlsAcceptorConfig::default())
|
||||
}
|
||||
|
||||
/// Create Rustls v0.22 based service with custom TLS acceptor configuration.
|
||||
pub fn rustls_0_22_with_config(
|
||||
self,
|
||||
mut config: ServerConfig,
|
||||
tls_acceptor_config: TlsAcceptorConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
||||
let mut acceptor = Acceptor::new(config);
|
||||
|
||||
if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout {
|
||||
acceptor.set_handshake_timeout(handshake_timeout);
|
||||
}
|
||||
|
||||
acceptor
|
||||
.map_init_err(|_| {
|
||||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.and_then(|io: TlsStream<TcpStream>| async {
|
||||
let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
|
||||
if protos.windows(2).any(|window| window == b"h2") {
|
||||
Protocol::Http2
|
||||
} else {
|
||||
Protocol::Http1
|
||||
}
|
||||
} else {
|
||||
Protocol::Http1
|
||||
};
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
Ok((io, proto, peer_addr))
|
||||
})
|
||||
.and_then(self.map_err(TlsError::Service))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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>)>
|
||||
for HttpService<T, S, B, X, U>
|
||||
where
|
||||
|
|
|
@ -296,7 +296,7 @@ impl Decoder for Codec {
|
|||
}
|
||||
}
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -178,14 +178,14 @@ impl Parser {
|
|||
};
|
||||
|
||||
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]);
|
||||
} 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_u16(payload_len as u16);
|
||||
} else {
|
||||
dst.reserve(p_len + 10 + if mask { 4 } else { 0 });
|
||||
dst.reserve(p_len + 10);
|
||||
dst.put_slice(&[one, two | 127]);
|
||||
dst.put_u64(payload_len as u64);
|
||||
};
|
||||
|
|
|
@ -221,7 +221,7 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{header, test::TestRequest, Method};
|
||||
use crate::{header, test::TestRequest};
|
||||
|
||||
#[test]
|
||||
fn test_handshake() {
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
use std::{
|
||||
convert::{From, Into},
|
||||
fmt,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use base64::prelude::*;
|
||||
use tracing::error;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#![cfg(feature = "openssl")]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
extern crate tls_openssl as openssl;
|
||||
|
||||
|
@ -43,9 +42,11 @@ where
|
|||
}
|
||||
|
||||
fn tls_config() -> SslAcceptor {
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
||||
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#![cfg(feature = "rustls")]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
#![cfg(feature = "rustls-0_23")]
|
||||
|
||||
extern crate tls_rustls as rustls;
|
||||
extern crate tls_rustls_023 as rustls;
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
|
@ -21,13 +20,13 @@ use actix_http::{
|
|||
use actix_http_test::test_server;
|
||||
use actix_rt::pin;
|
||||
use actix_service::{fn_factory_with_config, fn_service};
|
||||
use actix_tls::connect::rustls::webpki_roots_cert_store;
|
||||
use actix_tls::connect::rustls_0_23::webpki_roots_cert_store;
|
||||
use actix_utils::future::{err, ok, poll_fn};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use derive_more::{Display, Error};
|
||||
use futures_core::{ready, Stream};
|
||||
use futures_util::stream::once;
|
||||
use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName};
|
||||
use rustls::{pki_types::ServerName, ServerConfig as RustlsServerConfig};
|
||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
|
||||
async fn load_body<S>(stream: S) -> Result<BytesMut, PayloadError>
|
||||
|
@ -53,24 +52,25 @@ where
|
|||
}
|
||||
|
||||
fn tls_config() -> RustlsServerConfig {
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert_file = &mut BufReader::new(cert_file.as_bytes());
|
||||
let key_file = &mut BufReader::new(key_file.as_bytes());
|
||||
|
||||
let cert_chain = certs(cert_file)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
let mut keys = pkcs8_private_keys(key_file).unwrap();
|
||||
let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap();
|
||||
let mut keys = pkcs8_private_keys(key_file)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
|
||||
let mut config = RustlsServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
|
||||
.with_single_cert(
|
||||
cert_chain,
|
||||
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
config.alpn_protocols.push(HTTP1_1_ALPN_PROTOCOL.to_vec());
|
||||
|
@ -84,7 +84,6 @@ pub fn get_negotiated_alpn_protocol(
|
|||
client_alpn_protocol: &[u8],
|
||||
) -> Option<Vec<u8>> {
|
||||
let mut config = rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(webpki_roots_cert_store())
|
||||
.with_no_client_auth();
|
||||
|
||||
|
@ -110,7 +109,7 @@ async fn h1() -> io::Result<()> {
|
|||
let srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h1(|_| ok::<_, Error>(Response::ok()))
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -124,7 +123,7 @@ async fn h2() -> io::Result<()> {
|
|||
let srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| ok::<_, Error>(Response::ok()))
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -142,7 +141,7 @@ async fn h1_1() -> io::Result<()> {
|
|||
assert_eq!(req.version(), Version::HTTP_11);
|
||||
ok::<_, Error>(Response::ok())
|
||||
})
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -160,7 +159,7 @@ async fn h2_1() -> io::Result<()> {
|
|||
assert_eq!(req.version(), Version::HTTP_2);
|
||||
ok::<_, Error>(Response::ok())
|
||||
})
|
||||
.rustls_with_config(
|
||||
.rustls_0_23_with_config(
|
||||
tls_config(),
|
||||
TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)),
|
||||
)
|
||||
|
@ -181,7 +180,7 @@ async fn h2_body1() -> io::Result<()> {
|
|||
let body = load_body(req.take_payload()).await?;
|
||||
Ok::<_, Error>(Response::ok().set_body(body))
|
||||
})
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -207,7 +206,7 @@ async fn h2_content_length() {
|
|||
];
|
||||
ok::<_, Infallible>(Response::new(statuses[indx]))
|
||||
})
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -279,7 +278,7 @@ async fn h2_headers() {
|
|||
}
|
||||
ok::<_, Infallible>(config.body(data.clone()))
|
||||
})
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -318,7 +317,7 @@ async fn h2_body2() {
|
|||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -335,7 +334,7 @@ async fn h2_head_empty() {
|
|||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -361,7 +360,7 @@ async fn h2_head_binary() {
|
|||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -386,7 +385,7 @@ async fn h2_head_binary2() {
|
|||
let srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -412,7 +411,7 @@ async fn h2_body_length() {
|
|||
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
|
||||
)
|
||||
})
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -436,7 +435,7 @@ async fn h2_body_chunked_explicit() {
|
|||
.body(BodyStream::new(body)),
|
||||
)
|
||||
})
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -465,7 +464,7 @@ async fn h2_response_http_error_handling() {
|
|||
)
|
||||
}))
|
||||
}))
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -495,7 +494,7 @@ async fn h2_service_error() {
|
|||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| err::<Response<BoxBody>, _>(BadRequest))
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -512,7 +511,7 @@ async fn h1_service_error() {
|
|||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h1(|_| err::<Response<BoxBody>, _>(BadRequest))
|
||||
.rustls(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -535,7 +534,7 @@ async fn alpn_h1() -> io::Result<()> {
|
|||
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
|
||||
HttpService::build()
|
||||
.h1(|_| ok::<_, Error>(Response::ok()))
|
||||
.rustls(config)
|
||||
.rustls_0_23(config)
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -557,7 +556,7 @@ async fn alpn_h2() -> io::Result<()> {
|
|||
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
|
||||
HttpService::build()
|
||||
.h2(|_| ok::<_, Error>(Response::ok()))
|
||||
.rustls(config)
|
||||
.rustls_0_23(config)
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -583,7 +582,7 @@ async fn alpn_h2_1() -> io::Result<()> {
|
|||
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
|
||||
HttpService::build()
|
||||
.finish(|_| ok::<_, Error>(Response::ok()))
|
||||
.rustls(config)
|
||||
.rustls_0_23(config)
|
||||
})
|
||||
.await;
|
||||
|
||||
|
|
|
@ -147,7 +147,7 @@ async fn chunked_payload() {
|
|||
.take_payload()
|
||||
.map(|res| match res {
|
||||
Ok(pl) => pl,
|
||||
Err(e) => panic!("Error reading payload: {}", e),
|
||||
Err(err) => panic!("Error reading payload: {err}"),
|
||||
})
|
||||
.fold(0usize, |acc, chunk| ready(acc + chunk.len()))
|
||||
.map(|req_size| {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use std::{
|
||||
cell::Cell,
|
||||
convert::Infallible,
|
||||
|
|
|
@ -2,9 +2,13 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
- Update `syn` dependency to `2`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 0.6.0 - 2023-02-26
|
||||
## 0.6.0
|
||||
|
||||
- Add `MultipartForm` derive macro.
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
[package]
|
||||
name = "actix-multipart-derive"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
authors = ["Jacob Halsey <jacob@jhalsey.com>"]
|
||||
description = "Multipart form derive macro for Actix Web"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
# actix-multipart-derive
|
||||
# `actix-multipart-derive`
|
||||
|
||||
> The derive macro implementation for actix-multipart-derive.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-multipart-derive)
|
||||
[](https://docs.rs/actix-multipart-derive/0.5.0)
|
||||

|
||||
[](https://docs.rs/actix-multipart-derive/0.6.1)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-multipart-derive/0.5.0)
|
||||
[](https://deps.rs/crate/actix-multipart-derive/0.6.1)
|
||||
[](https://crates.io/crates/actix-multipart-derive)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-multipart-derive)
|
||||
- Minimum Supported Rust Version (MSRV): 1.68
|
||||
<!-- prettier-ignore-end -->
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#[rustversion::stable(1.68)] // MSRV
|
||||
#[rustversion::stable(1.72)] // MSRV
|
||||
#[test]
|
||||
fn compile_macros() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
|
|
@ -1,49 +1,56 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## Unreleased
|
||||
|
||||
## 0.6.2
|
||||
|
||||
- Add testing utilities under new module `test`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 0.6.0 - 2023-02-26
|
||||
## 0.6.0
|
||||
|
||||
- Added `MultipartForm` typed data extractor. [#2883]
|
||||
|
||||
[#2883]: https://github.com/actix/actix-web/pull/2883
|
||||
|
||||
## 0.5.0 - 2023-01-21
|
||||
## 0.5.0
|
||||
|
||||
- `Field::content_type()` now returns `Option<&mime::Mime>`. [#2885]
|
||||
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
|
||||
|
||||
[#2885]: https://github.com/actix/actix-web/pull/2885
|
||||
|
||||
## 0.4.0 - 2022-02-25
|
||||
## 0.4.0
|
||||
|
||||
- No significant changes since `0.4.0-beta.13`.
|
||||
|
||||
## 0.4.0-beta.13 - 2022-01-31
|
||||
## 0.4.0-beta.13
|
||||
|
||||
- No significant changes since `0.4.0-beta.12`.
|
||||
|
||||
## 0.4.0-beta.12 - 2022-01-04
|
||||
## 0.4.0-beta.12
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||
|
||||
## 0.4.0-beta.11 - 2021-12-27
|
||||
## 0.4.0-beta.11
|
||||
|
||||
- No significant changes since `0.4.0-beta.10`.
|
||||
|
||||
## 0.4.0-beta.10 - 2021-12-11
|
||||
## 0.4.0-beta.10
|
||||
|
||||
- No significant changes since `0.4.0-beta.9`.
|
||||
|
||||
## 0.4.0-beta.9 - 2021-12-01
|
||||
## 0.4.0-beta.9
|
||||
|
||||
- Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463]
|
||||
|
||||
[#2463]: https://github.com/actix/actix-web/pull/2463
|
||||
|
||||
## 0.4.0-beta.8 - 2021-11-22
|
||||
## 0.4.0-beta.8
|
||||
|
||||
- Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451]
|
||||
- Added `MultipartError::NoContentDisposition` variant. [#2451]
|
||||
|
@ -54,31 +61,31 @@
|
|||
|
||||
[#2451]: https://github.com/actix/actix-web/pull/2451
|
||||
|
||||
## 0.4.0-beta.7 - 2021-10-20
|
||||
## 0.4.0-beta.7
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
## 0.4.0-beta.6 - 2021-09-09
|
||||
## 0.4.0-beta.6
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
||||
## 0.4.0-beta.5 - 2021-06-17
|
||||
## 0.4.0-beta.5
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 0.4.0-beta.4 - 2021-04-02
|
||||
## 0.4.0-beta.4
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 0.4.0-beta.3 - 2021-03-09
|
||||
## 0.4.0-beta.3
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 0.4.0-beta.2 - 2021-02-10
|
||||
## 0.4.0-beta.2
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 0.4.0-beta.1 - 2021-01-07
|
||||
## 0.4.0-beta.1
|
||||
|
||||
- Fix multipart consuming payload before header checks. [#1513]
|
||||
- Update `bytes` to `1.0`. [#1813]
|
||||
|
@ -86,19 +93,19 @@
|
|||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||
[#1513]: https://github.com/actix/actix-web/pull/1513
|
||||
|
||||
## 0.3.0 - 2020-09-11
|
||||
## 0.3.0
|
||||
|
||||
- No significant changes from `0.3.0-beta.2`.
|
||||
|
||||
## 0.3.0-beta.2 - 2020-09-10
|
||||
## 0.3.0-beta.2
|
||||
|
||||
- Update `actix-*` dependencies to latest versions.
|
||||
|
||||
## 0.3.0-beta.1 - 2020-07-15
|
||||
## 0.3.0-beta.1
|
||||
|
||||
- Update `actix-web` to 3.0.0-beta.1
|
||||
|
||||
## 0.3.0-alpha.1 - 2020-05-25
|
||||
## 0.3.0-alpha.1
|
||||
|
||||
- Update `actix-web` to 3.0.0-alpha.3
|
||||
- Bump minimum supported Rust version to 1.40
|
||||
|
@ -106,45 +113,45 @@
|
|||
- Remove the unused `time` dependency
|
||||
- Fix missing `std::error::Error` implement for `MultipartError`.
|
||||
|
||||
## [0.2.0] - 2019-12-20
|
||||
## 0.2.0
|
||||
|
||||
- Release
|
||||
|
||||
## [0.2.0-alpha.4] - 2019-12-xx
|
||||
## 0.2.0-alpha.4
|
||||
|
||||
- Multipart handling now handles Pending during read of boundary #1205
|
||||
|
||||
## [0.2.0-alpha.2] - 2019-12-03
|
||||
## 0.2.0-alpha.2
|
||||
|
||||
- Migrate to `std::future`
|
||||
|
||||
## [0.1.4] - 2019-09-12
|
||||
## 0.1.4
|
||||
|
||||
- Multipart handling now parses requests which do not end in CRLF #1038
|
||||
|
||||
## [0.1.3] - 2019-08-18
|
||||
## 0.1.3
|
||||
|
||||
- Fix ring dependency from actix-web default features for #741.
|
||||
|
||||
## [0.1.2] - 2019-06-02
|
||||
## 0.1.2
|
||||
|
||||
- Fix boundary parsing #876
|
||||
|
||||
## [0.1.1] - 2019-05-25
|
||||
## 0.1.1
|
||||
|
||||
- Fix disconnect handling #834
|
||||
|
||||
## [0.1.0] - 2019-05-18
|
||||
## 0.1.0
|
||||
|
||||
- Release
|
||||
|
||||
## [0.1.0-beta.4] - 2019-05-12
|
||||
## 0.1.0-beta.4
|
||||
|
||||
- Handle cancellation of uploads #736
|
||||
|
||||
- Upgrade to actix-web 1.0.0-beta.4
|
||||
|
||||
## [0.1.0-beta.1] - 2019-04-21
|
||||
## 0.1.0-beta.1
|
||||
|
||||
- Do not support nested multipart
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-multipart"
|
||||
version = "0.6.0"
|
||||
version = "0.6.2"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Jacob Halsey <jacob@jhalsey.com>",
|
||||
|
@ -8,7 +8,7 @@ authors = [
|
|||
description = "Multipart form support for Actix Web"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
|
@ -22,7 +22,7 @@ derive = ["actix-multipart-derive"]
|
|||
tempfile = ["dep:tempfile", "tokio/fs"]
|
||||
|
||||
[dependencies]
|
||||
actix-multipart-derive = { version = "=0.6.0", optional = true }
|
||||
actix-multipart-derive = { version = "=0.6.1", optional = true }
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4", default-features = false }
|
||||
|
||||
|
@ -35,6 +35,7 @@ local-waker = "0.1"
|
|||
log = "0.4"
|
||||
memchr = "2.5"
|
||||
mime = "0.3"
|
||||
rand = "0.8"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
serde_plain = "1"
|
||||
|
@ -46,7 +47,9 @@ actix-http = "3"
|
|||
actix-multipart-rfc7578 = "0.10"
|
||||
actix-rt = "2.2"
|
||||
actix-test = "0.1"
|
||||
actix-web = "4"
|
||||
awc = "3"
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
multer = "3"
|
||||
tokio = { version = "1.24.2", features = ["sync"] }
|
||||
tokio-stream = "0.1"
|
||||
|
|
|
@ -1,17 +1,77 @@
|
|||
# actix-multipart
|
||||
# `actix-multipart`
|
||||
|
||||
> Multipart form support for Actix Web.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
[](https://docs.rs/actix-multipart/0.6.0)
|
||||

|
||||
[](https://docs.rs/actix-multipart/0.6.2)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-multipart/0.6.0)
|
||||
[](https://deps.rs/crate/actix-multipart/0.6.2)
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-multipart)
|
||||
- Minimum Supported Rust Version (MSRV): 1.68
|
||||
## 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
|
||||
|
|
|
@ -131,14 +131,13 @@ impl Default for JsonConfig {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{collections::HashMap, io::Cursor};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use actix_multipart_rfc7578::client::multipart;
|
||||
use actix_web::{http::StatusCode, web, App, HttpResponse, Responder};
|
||||
use bytes::Bytes;
|
||||
|
||||
use crate::form::{
|
||||
json::{Json, JsonConfig},
|
||||
tests::send_form,
|
||||
MultipartForm,
|
||||
};
|
||||
|
||||
|
@ -155,6 +154,8 @@ mod tests {
|
|||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
||||
const TEST_JSON: &str = r#"{"key1": "value1", "key2": "value2"}"#;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_json_without_content_type() {
|
||||
let srv = actix_test::start(|| {
|
||||
|
@ -163,10 +164,16 @@ mod tests {
|
|||
.app_data(JsonConfig::default().validate_content_type(false))
|
||||
});
|
||||
|
||||
let mut form = multipart::Form::default();
|
||||
form.add_text("json", "{\"key1\": \"value1\", \"key2\": \"value2\"}");
|
||||
let response = send_form(&srv, form, "/").await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let (body, headers) = crate::test::create_form_data_payload_and_headers(
|
||||
"json",
|
||||
None,
|
||||
None,
|
||||
Bytes::from_static(TEST_JSON.as_bytes()),
|
||||
);
|
||||
let mut req = srv.post("/");
|
||||
*req.headers_mut() = headers;
|
||||
let res = req.send_body(body).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
|
@ -178,17 +185,27 @@ mod tests {
|
|||
});
|
||||
|
||||
// Deny because wrong content type
|
||||
let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}");
|
||||
let mut form = multipart::Form::default();
|
||||
form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_OCTET_STREAM);
|
||||
let response = send_form(&srv, form, "/").await;
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
let (body, headers) = crate::test::create_form_data_payload_and_headers(
|
||||
"json",
|
||||
None,
|
||||
Some(mime::APPLICATION_OCTET_STREAM),
|
||||
Bytes::from_static(TEST_JSON.as_bytes()),
|
||||
);
|
||||
let mut req = srv.post("/");
|
||||
*req.headers_mut() = headers;
|
||||
let res = req.send_body(body).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
// Allow because correct content type
|
||||
let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}");
|
||||
let mut form = multipart::Form::default();
|
||||
form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_JSON);
|
||||
let response = send_form(&srv, form, "/").await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let (body, headers) = crate::test::create_form_data_payload_and_headers(
|
||||
"json",
|
||||
None,
|
||||
Some(mime::APPLICATION_JSON),
|
||||
Bytes::from_static(TEST_JSON.as_bytes()),
|
||||
);
|
||||
let mut req = srv.post("/");
|
||||
*req.headers_mut() = headers;
|
||||
let res = req.send_body(body).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -313,7 +313,8 @@ where
|
|||
let entry = field_limits
|
||||
.entry(field.name().to_owned())
|
||||
.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?;
|
||||
|
||||
|
|
|
@ -1,8 +1,43 @@
|
|||
//! 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)]
|
||||
#![warn(future_incompatible)]
|
||||
#![allow(clippy::borrow_interior_mutable_const, clippy::uninlined_format_args)]
|
||||
#![allow(clippy::borrow_interior_mutable_const)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
@ -13,11 +48,14 @@ extern crate self as actix_multipart;
|
|||
|
||||
mod error;
|
||||
mod extractor;
|
||||
mod server;
|
||||
|
||||
pub mod form;
|
||||
mod server;
|
||||
pub mod test;
|
||||
|
||||
pub use self::{
|
||||
error::MultipartError,
|
||||
server::{Field, Multipart},
|
||||
test::{
|
||||
create_form_data_payload_and_headers, create_form_data_payload_and_headers_with_boundary,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -252,7 +252,7 @@ impl InnerMultipart {
|
|||
fn poll(
|
||||
&mut self,
|
||||
safety: &Safety,
|
||||
cx: &mut Context<'_>,
|
||||
cx: &Context<'_>,
|
||||
) -> Poll<Option<Result<Field, MultipartError>>> {
|
||||
if self.state == InnerState::Eof {
|
||||
Poll::Ready(None)
|
||||
|
@ -740,7 +740,7 @@ impl Safety {
|
|||
self.clean.get()
|
||||
}
|
||||
|
||||
fn clone(&self, cx: &mut Context<'_>) -> Safety {
|
||||
fn clone(&self, cx: &Context<'_>) -> Safety {
|
||||
let payload = Rc::clone(&self.payload);
|
||||
let s = Safety {
|
||||
task: LocalWaker::new(),
|
||||
|
@ -863,13 +863,15 @@ mod tests {
|
|||
test::TestRequest,
|
||||
FromRequest,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use bytes::BufMut as _;
|
||||
use futures_util::{future::lazy, StreamExt as _};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
use super::*;
|
||||
|
||||
const BOUNDARY: &str = "abbc761f78ff4d7cb7573b5a23f96ef0";
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_boundary() {
|
||||
let headers = HeaderMap::new();
|
||||
|
@ -965,6 +967,26 @@ mod tests {
|
|||
}
|
||||
|
||||
fn create_simple_request_with_header() -> (Bytes, HeaderMap) {
|
||||
let (body, headers) = crate::test::create_form_data_payload_and_headers_with_boundary(
|
||||
BOUNDARY,
|
||||
"file",
|
||||
Some("fn.txt".to_owned()),
|
||||
Some(mime::TEXT_PLAIN_UTF_8),
|
||||
Bytes::from_static(b"data"),
|
||||
);
|
||||
|
||||
let mut buf = BytesMut::with_capacity(body.len() + 14);
|
||||
|
||||
// add junk before form to test pre-boundary data rejection
|
||||
buf.put("testasdadsad\r\n".as_bytes());
|
||||
|
||||
buf.put(body);
|
||||
|
||||
(buf.freeze(), headers)
|
||||
}
|
||||
|
||||
// TODO: use test utility when multi-file support is introduced
|
||||
fn create_double_request_with_header() -> (Bytes, HeaderMap) {
|
||||
let bytes = Bytes::from(
|
||||
"testasdadsad\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
|
||||
|
@ -990,7 +1012,7 @@ mod tests {
|
|||
#[actix_rt::test]
|
||||
async fn test_multipart_no_end_crlf() {
|
||||
let (sender, payload) = create_stream();
|
||||
let (mut bytes, headers) = create_simple_request_with_header();
|
||||
let (mut bytes, headers) = create_double_request_with_header();
|
||||
let bytes_stripped = bytes.split_to(bytes.len()); // strip crlf
|
||||
|
||||
sender.send(Ok(bytes_stripped)).unwrap();
|
||||
|
@ -1017,7 +1039,7 @@ mod tests {
|
|||
#[actix_rt::test]
|
||||
async fn test_multipart() {
|
||||
let (sender, payload) = create_stream();
|
||||
let (bytes, headers) = create_simple_request_with_header();
|
||||
let (bytes, headers) = create_double_request_with_header();
|
||||
|
||||
sender.send(Ok(bytes)).unwrap();
|
||||
|
||||
|
@ -1080,7 +1102,7 @@ mod tests {
|
|||
|
||||
#[actix_rt::test]
|
||||
async fn test_stream() {
|
||||
let (bytes, headers) = create_simple_request_with_header();
|
||||
let (bytes, headers) = create_double_request_with_header();
|
||||
let payload = SlowStream::new(bytes);
|
||||
|
||||
let mut multipart = Multipart::new(&headers, payload);
|
||||
|
@ -1319,7 +1341,7 @@ mod tests {
|
|||
#[actix_rt::test]
|
||||
async fn test_drop_field_awaken_multipart() {
|
||||
let (sender, payload) = create_stream();
|
||||
let (bytes, headers) = create_simple_request_with_header();
|
||||
let (bytes, headers) = create_double_request_with_header();
|
||||
sender.send(Ok(bytes)).unwrap();
|
||||
drop(sender); // eof
|
||||
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
use actix_web::http::header::{self, HeaderMap};
|
||||
use bytes::{BufMut as _, Bytes, BytesMut};
|
||||
use mime::Mime;
|
||||
use rand::{
|
||||
distributions::{Alphanumeric, DistString as _},
|
||||
thread_rng,
|
||||
};
|
||||
|
||||
const CRLF: &[u8] = b"\r\n";
|
||||
const CRLF_CRLF: &[u8] = b"\r\n\r\n";
|
||||
const HYPHENS: &[u8] = b"--";
|
||||
const BOUNDARY_PREFIX: &str = "------------------------";
|
||||
|
||||
/// Constructs a `multipart/form-data` payload from bytes and metadata.
|
||||
///
|
||||
/// Returned header map can be extended or merged with existing headers.
|
||||
///
|
||||
/// Multipart boundary used is a random alphanumeric string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use actix_multipart::test::create_form_data_payload_and_headers;
|
||||
/// use actix_web::test::TestRequest;
|
||||
/// use bytes::Bytes;
|
||||
/// use memchr::memmem::find;
|
||||
///
|
||||
/// let (body, headers) = create_form_data_payload_and_headers(
|
||||
/// "foo",
|
||||
/// Some("lorem.txt".to_owned()),
|
||||
/// Some(mime::TEXT_PLAIN_UTF_8),
|
||||
/// Bytes::from_static(b"Lorem ipsum."),
|
||||
/// );
|
||||
///
|
||||
/// assert!(find(&body, b"foo").is_some());
|
||||
/// assert!(find(&body, b"lorem.txt").is_some());
|
||||
/// assert!(find(&body, b"text/plain; charset=utf-8").is_some());
|
||||
/// assert!(find(&body, b"Lorem ipsum.").is_some());
|
||||
///
|
||||
/// let req = TestRequest::default();
|
||||
///
|
||||
/// // merge header map into existing test request and set multipart body
|
||||
/// let req = headers
|
||||
/// .into_iter()
|
||||
/// .fold(req, |req, hdr| req.insert_header(hdr))
|
||||
/// .set_payload(body)
|
||||
/// .to_http_request();
|
||||
///
|
||||
/// assert!(
|
||||
/// req.headers()
|
||||
/// .get("content-type")
|
||||
/// .unwrap()
|
||||
/// .to_str()
|
||||
/// .unwrap()
|
||||
/// .starts_with("multipart/form-data; boundary=\"")
|
||||
/// );
|
||||
/// ```
|
||||
pub fn create_form_data_payload_and_headers(
|
||||
name: &str,
|
||||
filename: Option<String>,
|
||||
content_type: Option<Mime>,
|
||||
file: Bytes,
|
||||
) -> (Bytes, HeaderMap) {
|
||||
let boundary = Alphanumeric.sample_string(&mut thread_rng(), 32);
|
||||
|
||||
create_form_data_payload_and_headers_with_boundary(
|
||||
&boundary,
|
||||
name,
|
||||
filename,
|
||||
content_type,
|
||||
file,
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructs a `multipart/form-data` payload from bytes and metadata with a fixed boundary.
|
||||
///
|
||||
/// See [`create_form_data_payload_and_headers`] for more details.
|
||||
pub fn create_form_data_payload_and_headers_with_boundary(
|
||||
boundary: &str,
|
||||
name: &str,
|
||||
filename: Option<String>,
|
||||
content_type: Option<Mime>,
|
||||
file: Bytes,
|
||||
) -> (Bytes, HeaderMap) {
|
||||
let mut buf = BytesMut::with_capacity(file.len() + 128);
|
||||
|
||||
let boundary_str = [BOUNDARY_PREFIX, boundary].concat();
|
||||
let boundary = boundary_str.as_bytes();
|
||||
|
||||
buf.put(HYPHENS);
|
||||
buf.put(boundary);
|
||||
buf.put(CRLF);
|
||||
|
||||
buf.put(format!("Content-Disposition: form-data; name=\"{name}\"").as_bytes());
|
||||
if let Some(filename) = filename {
|
||||
buf.put(format!("; filename=\"{filename}\"").as_bytes());
|
||||
}
|
||||
buf.put(CRLF);
|
||||
|
||||
if let Some(ct) = content_type {
|
||||
buf.put(format!("Content-Type: {ct}").as_bytes());
|
||||
buf.put(CRLF);
|
||||
}
|
||||
|
||||
buf.put(format!("Content-Length: {}", file.len()).as_bytes());
|
||||
buf.put(CRLF_CRLF);
|
||||
|
||||
buf.put(file);
|
||||
buf.put(CRLF);
|
||||
|
||||
buf.put(HYPHENS);
|
||||
buf.put(boundary);
|
||||
buf.put(HYPHENS);
|
||||
buf.put(CRLF);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::CONTENT_TYPE,
|
||||
format!("multipart/form-data; boundary=\"{boundary_str}\"")
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
(buf.freeze(), headers)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::Infallible;
|
||||
|
||||
use futures_util::stream;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn find_boundary(headers: &HeaderMap) -> String {
|
||||
headers
|
||||
.get("content-type")
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.parse::<mime::Mime>()
|
||||
.unwrap()
|
||||
.get_param(mime::BOUNDARY)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wire_format() {
|
||||
let (pl, headers) = create_form_data_payload_and_headers_with_boundary(
|
||||
"qWeRtYuIoP",
|
||||
"foo",
|
||||
None,
|
||||
None,
|
||||
Bytes::from_static(b"Lorem ipsum dolor\nsit ame."),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
find_boundary(&headers),
|
||||
"------------------------qWeRtYuIoP",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
std::str::from_utf8(&pl).unwrap(),
|
||||
"--------------------------qWeRtYuIoP\r\n\
|
||||
Content-Disposition: form-data; name=\"foo\"\r\n\
|
||||
Content-Length: 26\r\n\
|
||||
\r\n\
|
||||
Lorem ipsum dolor\n\
|
||||
sit ame.\r\n\
|
||||
--------------------------qWeRtYuIoP--\r\n",
|
||||
);
|
||||
|
||||
let (pl, _headers) = create_form_data_payload_and_headers_with_boundary(
|
||||
"qWeRtYuIoP",
|
||||
"foo",
|
||||
Some("Lorem.txt".to_owned()),
|
||||
Some(mime::TEXT_PLAIN_UTF_8),
|
||||
Bytes::from_static(b"Lorem ipsum dolor\nsit ame."),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
std::str::from_utf8(&pl).unwrap(),
|
||||
"--------------------------qWeRtYuIoP\r\n\
|
||||
Content-Disposition: form-data; name=\"foo\"; filename=\"Lorem.txt\"\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\n\
|
||||
Content-Length: 26\r\n\
|
||||
\r\n\
|
||||
Lorem ipsum dolor\n\
|
||||
sit ame.\r\n\
|
||||
--------------------------qWeRtYuIoP--\r\n",
|
||||
);
|
||||
}
|
||||
|
||||
/// Test using an external library to prevent the two-wrongs-make-a-right class of errors.
|
||||
#[actix_web::test]
|
||||
async fn ecosystem_compat() {
|
||||
let (pl, headers) = create_form_data_payload_and_headers(
|
||||
"foo",
|
||||
None,
|
||||
None,
|
||||
Bytes::from_static(b"Lorem ipsum dolor\nsit ame."),
|
||||
);
|
||||
|
||||
let boundary = find_boundary(&headers);
|
||||
|
||||
let pl = stream::once(async { Ok::<_, Infallible>(pl) });
|
||||
|
||||
let mut form = multer::Multipart::new(pl, boundary);
|
||||
let field = form.next_field().await.unwrap().unwrap();
|
||||
assert_eq!(field.name().unwrap(), "foo");
|
||||
assert_eq!(field.file_name(), None);
|
||||
assert_eq!(field.content_type(), None);
|
||||
assert!(field.bytes().await.unwrap().starts_with(b"Lorem"));
|
||||
}
|
||||
}
|
|
@ -1,17 +1,24 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## 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.
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
## 0.5.2
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 0.5.1 - 2022-09-19
|
||||
## 0.5.1
|
||||
|
||||
- Correct typo in error string for `i32` deserialization. [#2876]
|
||||
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
|
||||
|
||||
[#2876]: https://github.com/actix/actix-web/pull/2876
|
||||
|
||||
## 0.5.0 - 2022-02-22
|
||||
## 0.5.0
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -86,7 +93,7 @@
|
|||
<details>
|
||||
<summary>0.5.0 Pre-Releases</summary>
|
||||
|
||||
## 0.5.0-rc.3 - 2022-01-31
|
||||
## 0.5.0-rc.3
|
||||
|
||||
- Remove unused `ResourceInfo`. [#2612]
|
||||
- Add `RouterBuilder::push`. [#2612]
|
||||
|
@ -98,32 +105,32 @@
|
|||
[#2612]: https://github.com/actix/actix-web/pull/2612
|
||||
[#2613]: https://github.com/actix/actix-web/pull/2613
|
||||
|
||||
## 0.5.0-rc.2 - 2022-01-21
|
||||
## 0.5.0-rc.2
|
||||
|
||||
- Add `Path::as_str`. [#2590]
|
||||
- Deprecate `Path::path`. [#2590]
|
||||
|
||||
[#2590]: https://github.com/actix/actix-web/pull/2590
|
||||
|
||||
## 0.5.0-rc.1 - 2022-01-14
|
||||
## 0.5.0-rc.1
|
||||
|
||||
- `Resource` trait now have an associated type, `Path`, instead of the generic parameter. [#2568]
|
||||
- `Resource` is now implemented for `&mut Path<_>` and `RefMut<Path<_>>`. [#2568]
|
||||
|
||||
[#2568]: https://github.com/actix/actix-web/pull/2568
|
||||
|
||||
## 0.5.0-beta.4 - 2022-01-04
|
||||
## 0.5.0-beta.4
|
||||
|
||||
- `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#2566]
|
||||
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||
|
||||
[#2566]: https://github.com/actix/actix-net/pull/2566
|
||||
|
||||
## 0.5.0-beta.3 - 2021-12-17
|
||||
## 0.5.0-beta.3
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
## 0.5.0-beta.2 - 2021-09-09
|
||||
## 0.5.0-beta.2
|
||||
|
||||
- Introduce `ResourceDef::join`. [#380][net#380]
|
||||
- Disallow prefix routes with tail segments. [#379][net#379]
|
||||
|
@ -143,7 +150,7 @@
|
|||
[#2355]: https://github.com/actix/actix-web/pull/2355
|
||||
[#2356]: https://github.com/actix/actix-web/pull/2356
|
||||
|
||||
## 0.5.0-beta.1 - 2021-07-20
|
||||
## 0.5.0-beta.1
|
||||
|
||||
- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366][net#366]
|
||||
- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373][net#373]
|
||||
|
@ -173,7 +180,7 @@
|
|||
|
||||
</details>
|
||||
|
||||
## 0.4.0 - 2021-06-06
|
||||
## 0.4.0
|
||||
|
||||
- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357][net#357]
|
||||
- Path tail patterns now match new lines (`\n`) in request URL. [#360][net#360]
|
||||
|
@ -185,70 +192,70 @@
|
|||
[net#359]: https://github.com/actix/actix-net/pull/359
|
||||
[net#360]: https://github.com/actix/actix-net/pull/360
|
||||
|
||||
## 0.3.0 - 2019-12-31
|
||||
## 0.3.0
|
||||
|
||||
- Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0
|
||||
|
||||
## 0.2.7 - 2021-02-06
|
||||
## 0.2.7
|
||||
|
||||
- Add `Router::recognize_checked` [#247][net#247]
|
||||
|
||||
[net#247]: https://github.com/actix/actix-net/pull/247
|
||||
|
||||
## 0.2.6 - 2021-01-09
|
||||
## 0.2.6
|
||||
|
||||
- Use `bytestring` version range compatible with Bytes v1.0. [#246][net#246]
|
||||
|
||||
[net#246]: https://github.com/actix/actix-net/pull/246
|
||||
|
||||
## 0.2.5 - 2020-09-20
|
||||
## 0.2.5
|
||||
|
||||
- Fix `from_hex()` method
|
||||
|
||||
## 0.2.4 - 2019-12-31
|
||||
## 0.2.4
|
||||
|
||||
- Add `ResourceDef::resource_path_named()` path generation method
|
||||
|
||||
## 0.2.3 - 2019-12-25
|
||||
## 0.2.3
|
||||
|
||||
- Add impl `IntoPattern` for `&String`
|
||||
|
||||
## 0.2.2 - 2019-12-25
|
||||
## 0.2.2
|
||||
|
||||
- Use `IntoPattern` for `RouterBuilder::path()`
|
||||
|
||||
## 0.2.1 - 2019-12-25
|
||||
## 0.2.1
|
||||
|
||||
- Add `IntoPattern` trait
|
||||
- Add multi-pattern resources
|
||||
|
||||
## 0.2.0 - 2019-12-07
|
||||
## 0.2.0
|
||||
|
||||
- Update http to 0.2
|
||||
- Update regex to 1.3
|
||||
- Use bytestring instead of string
|
||||
|
||||
## 0.1.5 - 2019-05-15
|
||||
## 0.1.5
|
||||
|
||||
- Remove debug prints
|
||||
|
||||
## 0.1.4 - 2019-05-15
|
||||
## 0.1.4
|
||||
|
||||
- Fix checked resource match
|
||||
|
||||
## 0.1.3 - 2019-04-22
|
||||
## 0.1.3
|
||||
|
||||
- Added support for `remainder match` (i.e "/path/{tail}\*")
|
||||
|
||||
## 0.1.2 - 2019-04-07
|
||||
## 0.1.2
|
||||
|
||||
- Export `Quoter` type
|
||||
- Allow to reset `Path` instance
|
||||
|
||||
## 0.1.1 - 2019-04-03
|
||||
## 0.1.1
|
||||
|
||||
- Get dynamic segment by name instead of iterator.
|
||||
|
||||
## 0.1.0 - 2019-03-09
|
||||
## 0.1.0
|
||||
|
||||
- Initial release
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-router"
|
||||
version = "0.5.1"
|
||||
version = "0.5.3"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||
|
@ -8,7 +8,7 @@ authors = [
|
|||
]
|
||||
description = "Resource path matching and router"
|
||||
keywords = ["actix", "router", "routing"]
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
|
@ -17,12 +17,16 @@ name = "actix_router"
|
|||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["http"]
|
||||
default = ["http", "unicode"]
|
||||
http = ["dep:http"]
|
||||
unicode = ["dep:regex"]
|
||||
|
||||
[dependencies]
|
||||
bytestring = ">=0.1.5, <2"
|
||||
cfg-if = "1"
|
||||
http = { version = "0.2.7", optional = true }
|
||||
regex = "1.5"
|
||||
regex = { version = "1.5", optional = true }
|
||||
regex-lite = "0.1"
|
||||
serde = "1"
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
|
@ -35,6 +39,7 @@ percent-encoding = "2.1"
|
|||
[[bench]]
|
||||
name = "router"
|
||||
harness = false
|
||||
required-features = ["unicode"]
|
||||
|
||||
[[bench]]
|
||||
name = "quoter"
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# `actix-router`
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-router)
|
||||
[](https://docs.rs/actix-router/0.5.3)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-router/0.5.3)
|
||||
[](https://crates.io/crates/actix-router)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- cargo-rdme start -->
|
||||
|
||||
Resource path matching and router.
|
||||
|
||||
<!-- cargo-rdme end -->
|
|
@ -1,6 +1,4 @@
|
|||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, fmt::Write as _};
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
|
@ -8,9 +6,10 @@ fn compare_quoters(c: &mut Criterion) {
|
|||
let mut group = c.benchmark_group("Compare Quoters");
|
||||
|
||||
let quoter = actix_router::Quoter::new(b"", b"");
|
||||
let path_quoted = (0..=0x7f)
|
||||
.map(|c| format!("%{:02X}", c))
|
||||
.collect::<String>();
|
||||
let path_quoted = (0..=0x7f).fold(String::new(), |mut buf, c| {
|
||||
write!(&mut buf, "%{:02X}", c).unwrap();
|
||||
buf
|
||||
});
|
||||
let path_unquoted = ('\u{00}'..='\u{7f}').collect::<String>();
|
||||
|
||||
group.bench_function("quoter_unquoted", |b| {
|
||||
|
|
|
@ -500,10 +500,10 @@ impl<'de> de::VariantAccess<'de> for UnitVariant {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde::{de, Deserialize};
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::*;
|
||||
use crate::{path::Path, router::Router, ResourceDef};
|
||||
use crate::{router::Router, ResourceDef};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MyStruct {
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
@ -11,6 +10,7 @@ mod de;
|
|||
mod path;
|
||||
mod pattern;
|
||||
mod quoter;
|
||||
mod regex_set;
|
||||
mod resource;
|
||||
mod resource_path;
|
||||
mod router;
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
|||
ops::{DerefMut, Index},
|
||||
};
|
||||
|
||||
use serde::de;
|
||||
use serde::{de, Deserialize};
|
||||
|
||||
use crate::{de::PathDeserializer, Resource, ResourcePath};
|
||||
|
||||
|
@ -24,8 +24,13 @@ impl Default for PathItem {
|
|||
/// If resource path contains variable patterns, `Path` stores them.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Path<T> {
|
||||
/// Full path representation.
|
||||
path: T,
|
||||
|
||||
/// Number of characters in `path` that have been processed into `segments`.
|
||||
pub(crate) skip: u16,
|
||||
|
||||
/// List of processed dynamic segments; name->value pairs.
|
||||
pub(crate) segments: Vec<(Cow<'static, str>, PathItem)>,
|
||||
}
|
||||
|
||||
|
@ -83,8 +88,8 @@ impl<T: ResourcePath> Path<T> {
|
|||
/// Set new path.
|
||||
#[inline]
|
||||
pub fn set(&mut self, path: T) {
|
||||
self.skip = 0;
|
||||
self.path = path;
|
||||
self.skip = 0;
|
||||
self.segments.clear();
|
||||
}
|
||||
|
||||
|
@ -103,7 +108,7 @@ impl<T: ResourcePath> Path<T> {
|
|||
|
||||
pub(crate) fn add(&mut self, name: impl Into<Cow<'static, str>>, value: PathItem) {
|
||||
match value {
|
||||
PathItem::Static(s) => self.segments.push((name.into(), PathItem::Static(s))),
|
||||
PathItem::Static(seg) => self.segments.push((name.into(), PathItem::Static(seg))),
|
||||
PathItem::Segment(begin, end) => self.segments.push((
|
||||
name.into(),
|
||||
PathItem::Segment(self.skip + begin, self.skip + end),
|
||||
|
@ -149,15 +154,11 @@ impl<T: ResourcePath> Path<T> {
|
|||
None
|
||||
}
|
||||
|
||||
/// Get matched parameter by name.
|
||||
/// Returns matched parameter by name.
|
||||
///
|
||||
/// If keyed parameter is not available empty string is used as default value.
|
||||
pub fn query(&self, key: &str) -> &str {
|
||||
if let Some(s) = self.get(key) {
|
||||
s
|
||||
} else {
|
||||
""
|
||||
}
|
||||
self.get(key).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Return iterator to items in parameter container.
|
||||
|
@ -168,9 +169,13 @@ impl<T: ResourcePath> Path<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Try to deserialize matching parameters to a specified type `U`
|
||||
pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result<U, de::value::Error> {
|
||||
de::Deserialize::deserialize(PathDeserializer::new(self))
|
||||
/// Deserializes matching parameters to a specified type `U`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns error when dynamic path segments cannot be deserialized into a `U` type.
|
||||
pub fn load<'de, U: Deserialize<'de>>(&'de self) -> Result<U, de::value::Error> {
|
||||
Deserialize::deserialize(PathDeserializer::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
//! Abstraction over `regex` and `regex-lite` depending on whether we have `unicode` crate feature
|
||||
//! enabled.
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
#[cfg(feature = "unicode")]
|
||||
pub(crate) use regex::{escape, Regex};
|
||||
#[cfg(not(feature = "unicode"))]
|
||||
pub(crate) use regex_lite::{escape, Regex};
|
||||
|
||||
#[cfg(feature = "unicode")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct RegexSet(regex::RegexSet);
|
||||
|
||||
#[cfg(not(feature = "unicode"))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct RegexSet(Vec<regex_lite::Regex>);
|
||||
|
||||
impl RegexSet {
|
||||
/// Create a new regex set.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if any path patterns are malformed.
|
||||
pub(crate) fn new(re_set: Vec<String>) -> Self {
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "unicode")] {
|
||||
Self(regex::RegexSet::new(re_set).unwrap())
|
||||
} else {
|
||||
Self(re_set.iter().map(|re| Regex::new(re).unwrap()).collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new empty regex set.
|
||||
pub(crate) fn empty() -> Self {
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "unicode")] {
|
||||
Self(regex::RegexSet::empty())
|
||||
} else {
|
||||
Self(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if regex set matches `path`.
|
||||
pub(crate) fn is_match(&self, path: &str) -> bool {
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "unicode")] {
|
||||
self.0.is_match(path)
|
||||
} else {
|
||||
self.0.iter().any(|re| re.is_match(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns index within `path` of first match.
|
||||
pub(crate) fn first_match_idx(&self, path: &str) -> Option<usize> {
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "unicode")] {
|
||||
self.0.matches(path).into_iter().next()
|
||||
} else {
|
||||
Some(self.0.iter().enumerate().find(|(_, re)| re.is_match(path))?.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,10 +5,13 @@ use std::{
|
|||
mem,
|
||||
};
|
||||
|
||||
use regex::{escape, Regex, RegexSet};
|
||||
use tracing::error;
|
||||
|
||||
use crate::{path::PathItem, IntoPatterns, Patterns, Resource, ResourcePath};
|
||||
use crate::{
|
||||
path::PathItem,
|
||||
regex_set::{escape, Regex, RegexSet},
|
||||
IntoPatterns, Patterns, Resource, ResourcePath,
|
||||
};
|
||||
|
||||
const MAX_DYNAMIC_SEGMENTS: usize = 16;
|
||||
|
||||
|
@ -193,8 +196,8 @@ const REGEX_FLAGS: &str = "(?s-m)";
|
|||
/// # Trailing Slashes
|
||||
/// It should be noted that this library takes no steps to normalize intra-path or trailing slashes.
|
||||
/// As such, all resource definitions implicitly expect a pre-processing step to normalize paths if
|
||||
/// they you wish to accommodate "recoverable" path errors. Below are several examples of
|
||||
/// resource-path pairs that would not be compatible.
|
||||
/// you wish to accommodate "recoverable" path errors. Below are several examples of resource-path
|
||||
/// pairs that would not be compatible.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
|
@ -233,7 +236,7 @@ enum PatternSegment {
|
|||
Var(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum PatternType {
|
||||
/// Single constant/literal segment.
|
||||
|
@ -603,7 +606,7 @@ impl ResourceDef {
|
|||
PatternType::Dynamic(re, _) => Some(re.captures(path)?[1].len()),
|
||||
|
||||
PatternType::DynamicSet(re, params) => {
|
||||
let idx = re.matches(path).into_iter().next()?;
|
||||
let idx = re.first_match_idx(path)?;
|
||||
let (ref pattern, _) = params[idx];
|
||||
Some(pattern.captures(path)?[1].len())
|
||||
}
|
||||
|
@ -706,7 +709,7 @@ impl ResourceDef {
|
|||
|
||||
PatternType::DynamicSet(re, params) => {
|
||||
let path = path.unprocessed();
|
||||
let (pattern, names) = match re.matches(path).into_iter().next() {
|
||||
let (pattern, names) = match re.first_match_idx(path) {
|
||||
Some(idx) => ¶ms[idx],
|
||||
_ => return false,
|
||||
};
|
||||
|
@ -870,7 +873,7 @@ impl ResourceDef {
|
|||
}
|
||||
}
|
||||
|
||||
let pattern_re_set = RegexSet::new(re_set).unwrap();
|
||||
let pattern_re_set = RegexSet::new(re_set);
|
||||
let segments = segments.unwrap_or_default();
|
||||
|
||||
(
|
||||
|
|
|
@ -97,6 +97,7 @@ impl<T, U> RouterBuilder<T, U> {
|
|||
ctx: U,
|
||||
) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||
self.routes.push((rdef, val, ctx));
|
||||
#[allow(clippy::map_identity)] // map is used to distribute &mut-ness to tuple elements
|
||||
self.routes
|
||||
.last_mut()
|
||||
.map(|(rdef, val, ctx)| (rdef, val, ctx))
|
||||
|
@ -186,11 +187,11 @@ mod tests {
|
|||
assert_eq!(path.get("file").unwrap(), "file");
|
||||
assert_eq!(path.get("ext").unwrap(), "gz");
|
||||
|
||||
let mut path = Path::new("/vtest/ttt/index.html");
|
||||
let mut path = Path::new("/v2/ttt/index.html");
|
||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 14);
|
||||
assert_eq!(info, ResourceId(4));
|
||||
assert_eq!(path.get("val").unwrap(), "test");
|
||||
assert_eq!(path.get("val").unwrap(), "2");
|
||||
assert_eq!(path.get("val2").unwrap(), "ttt");
|
||||
|
||||
let mut path = Path::new("/v/blah-blah/index.html");
|
||||
|
|
|
@ -62,6 +62,8 @@ impl ResourcePath for Url {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use http::Uri;
|
||||
|
||||
use super::*;
|
||||
|
@ -78,7 +80,11 @@ mod tests {
|
|||
}
|
||||
|
||||
fn percent_encode(data: &[u8]) -> String {
|
||||
data.iter().map(|c| format!("%{:02X}", c)).collect()
|
||||
data.iter()
|
||||
.fold(String::with_capacity(data.len() * 3), |mut buf, c| {
|
||||
write!(&mut buf, "%{:02X}", c).unwrap();
|
||||
buf
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,78 +1,97 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## Unreleased
|
||||
|
||||
- Add `TestServerConfig::workers()` setter method.
|
||||
## 0.1.5
|
||||
|
||||
- Add `TestServerConfig::listen_address()` method.
|
||||
|
||||
## 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.
|
||||
|
||||
## 0.1.3
|
||||
|
||||
- Add `TestServerConfig::rustls_0_22()` method for Rustls v0.22 support behind new `rustls-0_22` crate feature.
|
||||
|
||||
## 0.1.2
|
||||
|
||||
- Add `TestServerConfig::rustls_021()` method for Rustls v0.21 support behind new `rustls-0_21` crate feature.
|
||||
- Add `TestServerConfig::workers()` method.
|
||||
- Add `rustls-0_20` crate feature, which the existing `rustls` feature now aliases.
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 0.1.1 - 2023-02-26
|
||||
## 0.1.1
|
||||
|
||||
- Add `TestServerConfig::port()` setter method.
|
||||
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
|
||||
|
||||
## 0.1.0 - 2022-07-24
|
||||
## 0.1.0
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||
|
||||
## 0.1.0-beta.13 - 2022-02-16
|
||||
## 0.1.0-beta.13
|
||||
|
||||
- No significant changes since `0.1.0-beta.12`.
|
||||
|
||||
## 0.1.0-beta.12 - 2022-01-31
|
||||
## 0.1.0-beta.12
|
||||
|
||||
- Rename `TestServerConfig::{client_timeout => client_request_timeout}`. [#2611]
|
||||
|
||||
[#2611]: https://github.com/actix/actix-web/pull/2611
|
||||
|
||||
## 0.1.0-beta.11 - 2022-01-04
|
||||
## 0.1.0-beta.11
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||
|
||||
## 0.1.0-beta.10 - 2021-12-27
|
||||
## 0.1.0-beta.10
|
||||
|
||||
- No significant changes since `0.1.0-beta.9`.
|
||||
|
||||
## 0.1.0-beta.9 - 2021-12-17
|
||||
## 0.1.0-beta.9
|
||||
|
||||
- Re-export `actix_http::body::to_bytes`. [#2518]
|
||||
- Update `actix_web::test` re-exports. [#2518]
|
||||
|
||||
[#2518]: https://github.com/actix/actix-web/pull/2518
|
||||
|
||||
## 0.1.0-beta.8 - 2021-12-11
|
||||
## 0.1.0-beta.8
|
||||
|
||||
- No significant changes since `0.1.0-beta.7`.
|
||||
|
||||
## 0.1.0-beta.7 - 2021-11-22
|
||||
## 0.1.0-beta.7
|
||||
|
||||
- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]
|
||||
|
||||
[#2408]: https://github.com/actix/actix-web/pull/2408
|
||||
|
||||
## 0.1.0-beta.6 - 2021-11-15
|
||||
## 0.1.0-beta.6
|
||||
|
||||
- No significant changes from `0.1.0-beta.5`.
|
||||
|
||||
## 0.1.0-beta.5 - 2021-10-20
|
||||
## 0.1.0-beta.5
|
||||
|
||||
- Updated rustls to v0.20. [#2414]
|
||||
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
[#2414]: https://github.com/actix/actix-web/pull/2414
|
||||
|
||||
## 0.1.0-beta.4 - 2021-09-09
|
||||
## 0.1.0-beta.4
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
||||
## 0.1.0-beta.3 - 2021-06-20
|
||||
## 0.1.0-beta.3
|
||||
|
||||
- No significant changes from `0.1.0-beta.2`.
|
||||
|
||||
## 0.1.0-beta.2 - 2021-04-17
|
||||
## 0.1.0-beta.2
|
||||
|
||||
- No significant changes from `0.1.0-beta.1`.
|
||||
|
||||
## 0.1.0-beta.1 - 2021-04-02
|
||||
## 0.1.0-beta.1
|
||||
|
||||
- Move integration testing structs from `actix-web`. [#2112]
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-test"
|
||||
version = "0.1.1"
|
||||
version = "0.1.5"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
|
@ -8,7 +8,7 @@ authors = [
|
|||
description = "Integration testing tools for Actix Web applications"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
categories = [
|
||||
"network-programming",
|
||||
"asynchronous",
|
||||
|
@ -21,21 +21,29 @@ edition = "2021"
|
|||
[features]
|
||||
default = []
|
||||
|
||||
# rustls
|
||||
rustls = ["tls-rustls", "actix-http/rustls", "awc/rustls"]
|
||||
# TLS via Rustls v0.20
|
||||
rustls = ["rustls-0_20"]
|
||||
# TLS via Rustls v0.20
|
||||
rustls-0_20 = ["tls-rustls-0_20", "actix-http/rustls-0_20", "awc/rustls-0_20"]
|
||||
# TLS via Rustls v0.21
|
||||
rustls-0_21 = ["tls-rustls-0_21", "actix-http/rustls-0_21", "awc/rustls-0_21"]
|
||||
# TLS via Rustls v0.22
|
||||
rustls-0_22 = ["tls-rustls-0_22", "actix-http/rustls-0_22", "awc/rustls-0_22-webpki-roots"]
|
||||
# TLS via Rustls v0.23
|
||||
rustls-0_23 = ["tls-rustls-0_23", "actix-http/rustls-0_23", "awc/rustls-0_23-webpki-roots"]
|
||||
|
||||
# openssl
|
||||
# TLS via OpenSSL
|
||||
openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.5"
|
||||
actix-http = "3"
|
||||
actix-http = "3.7"
|
||||
actix-http-test = "3"
|
||||
actix-rt = "2.1"
|
||||
actix-service = "2"
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4", default-features = false, features = ["cookies"] }
|
||||
awc = { version = "3", default-features = false, features = ["cookies"] }
|
||||
actix-web = { version = "4.6", 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-util = { version = "0.3.17", default-features = false, features = [] }
|
||||
|
@ -44,5 +52,8 @@ serde = { version = "1", features = ["derive"] }
|
|||
serde_json = "1"
|
||||
serde_urlencoded = "0.7"
|
||||
tls-openssl = { package = "openssl", version = "0.10.55", optional = true }
|
||||
tls-rustls = { 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_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"] }
|
||||
|
|
|
@ -34,8 +34,6 @@
|
|||
|
||||
#[cfg(feature = "openssl")]
|
||||
extern crate tls_openssl as openssl;
|
||||
#[cfg(feature = "rustls")]
|
||||
extern crate tls_rustls as rustls;
|
||||
|
||||
use std::{fmt, net, thread, time::Duration};
|
||||
|
||||
|
@ -54,7 +52,7 @@ use actix_web::{
|
|||
rt::{self, System},
|
||||
web, Error,
|
||||
};
|
||||
use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
|
||||
pub use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
|
||||
use futures_core::Stream;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
|
@ -141,14 +139,22 @@ where
|
|||
StreamType::Tcp => false,
|
||||
#[cfg(feature = "openssl")]
|
||||
StreamType::Openssl(_) => true,
|
||||
#[cfg(feature = "rustls")]
|
||||
StreamType::Rustls(_) => true,
|
||||
#[cfg(feature = "rustls-0_20")]
|
||||
StreamType::Rustls020(_) => true,
|
||||
#[cfg(feature = "rustls-0_21")]
|
||||
StreamType::Rustls021(_) => true,
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
StreamType::Rustls022(_) => true,
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
StreamType::Rustls023(_) => true,
|
||||
};
|
||||
|
||||
let client_cfg = cfg.clone();
|
||||
|
||||
// run server in separate orphaned thread
|
||||
thread::spawn(move || {
|
||||
rt::System::new().block_on(async move {
|
||||
let tcp = net::TcpListener::bind(("127.0.0.1", cfg.port)).unwrap();
|
||||
let tcp = net::TcpListener::bind((cfg.listen_address.clone(), cfg.port)).unwrap();
|
||||
let local_addr = tcp.local_addr().unwrap();
|
||||
let factory = factory.clone();
|
||||
let srv_cfg = cfg.clone();
|
||||
|
@ -243,8 +249,8 @@ where
|
|||
.openssl(acceptor.clone())
|
||||
}),
|
||||
},
|
||||
#[cfg(feature = "rustls")]
|
||||
StreamType::Rustls(config) => match cfg.tp {
|
||||
#[cfg(feature = "rustls-0_20")]
|
||||
StreamType::Rustls020(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);
|
||||
|
@ -285,6 +291,132 @@ where
|
|||
.rustls(config.clone())
|
||||
}),
|
||||
},
|
||||
#[cfg(feature = "rustls-0_21")]
|
||||
StreamType::Rustls021(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_021(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_021(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_021(config.clone())
|
||||
}),
|
||||
},
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
StreamType::Rustls022(config) => match cfg.tp {
|
||||
HttpVer::Http1 => builder.listen("test", tcp, move || {
|
||||
let app_cfg =
|
||||
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||
|
||||
let fac = factory()
|
||||
.into_factory()
|
||||
.map_err(|err| err.into().error_response());
|
||||
|
||||
HttpService::build()
|
||||
.client_request_timeout(timeout)
|
||||
.h1(map_config(fac, move |_| app_cfg.clone()))
|
||||
.rustls_0_22(config.clone())
|
||||
}),
|
||||
HttpVer::Http2 => builder.listen("test", tcp, move || {
|
||||
let app_cfg =
|
||||
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||
|
||||
let fac = factory()
|
||||
.into_factory()
|
||||
.map_err(|err| err.into().error_response());
|
||||
|
||||
HttpService::build()
|
||||
.client_request_timeout(timeout)
|
||||
.h2(map_config(fac, move |_| app_cfg.clone()))
|
||||
.rustls_0_22(config.clone())
|
||||
}),
|
||||
HttpVer::Both => builder.listen("test", tcp, move || {
|
||||
let app_cfg =
|
||||
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||
|
||||
let fac = factory()
|
||||
.into_factory()
|
||||
.map_err(|err| err.into().error_response());
|
||||
|
||||
HttpService::build()
|
||||
.client_request_timeout(timeout)
|
||||
.finish(map_config(fac, move |_| app_cfg.clone()))
|
||||
.rustls_0_22(config.clone())
|
||||
}),
|
||||
},
|
||||
#[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");
|
||||
|
||||
|
@ -316,7 +448,7 @@ where
|
|||
builder.set_verify(SslVerifyMode::NONE);
|
||||
let _ = builder
|
||||
.set_alpn_protos(b"\x02h2\x08http/1.1")
|
||||
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
|
||||
.map_err(|err| log::error!("Can not set alpn protocol: {err:?}"));
|
||||
Connector::new()
|
||||
.conn_lifetime(Duration::from_secs(0))
|
||||
.timeout(Duration::from_millis(30000))
|
||||
|
@ -330,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 {
|
||||
|
@ -350,13 +488,20 @@ enum HttpVer {
|
|||
Both,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone)]
|
||||
enum StreamType {
|
||||
Tcp,
|
||||
#[cfg(feature = "openssl")]
|
||||
Openssl(openssl::ssl::SslAcceptor),
|
||||
#[cfg(feature = "rustls")]
|
||||
Rustls(rustls::ServerConfig),
|
||||
#[cfg(feature = "rustls-0_20")]
|
||||
Rustls020(tls_rustls_0_20::ServerConfig),
|
||||
#[cfg(feature = "rustls-0_21")]
|
||||
Rustls021(tls_rustls_0_21::ServerConfig),
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
Rustls022(tls_rustls_0_22::ServerConfig),
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
Rustls023(tls_rustls_0_23::ServerConfig),
|
||||
}
|
||||
|
||||
/// Create default test server config.
|
||||
|
@ -369,8 +514,10 @@ pub struct TestServerConfig {
|
|||
tp: HttpVer,
|
||||
stream: StreamType,
|
||||
client_request_timeout: Duration,
|
||||
listen_address: String,
|
||||
port: u16,
|
||||
workers: usize,
|
||||
disable_redirects: bool,
|
||||
}
|
||||
|
||||
impl Default for TestServerConfig {
|
||||
|
@ -380,49 +527,96 @@ impl Default for TestServerConfig {
|
|||
}
|
||||
|
||||
impl TestServerConfig {
|
||||
/// Create default server configuration
|
||||
/// Constructs default server configuration.
|
||||
pub(crate) fn new() -> TestServerConfig {
|
||||
TestServerConfig {
|
||||
tp: HttpVer::Both,
|
||||
stream: StreamType::Tcp,
|
||||
client_request_timeout: Duration::from_secs(5),
|
||||
listen_address: "127.0.0.1".to_string(),
|
||||
port: 0,
|
||||
workers: 1,
|
||||
disable_redirects: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept HTTP/1.1 only.
|
||||
/// Accepts HTTP/1.1 only.
|
||||
pub fn h1(mut self) -> Self {
|
||||
self.tp = HttpVer::Http1;
|
||||
self
|
||||
}
|
||||
|
||||
/// Accept HTTP/2 only.
|
||||
/// Accepts HTTP/2 only.
|
||||
pub fn h2(mut self) -> Self {
|
||||
self.tp = HttpVer::Http2;
|
||||
self
|
||||
}
|
||||
|
||||
/// Accept secure connections via OpenSSL.
|
||||
/// Accepts secure connections via OpenSSL.
|
||||
#[cfg(feature = "openssl")]
|
||||
pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self {
|
||||
self.stream = StreamType::Openssl(acceptor);
|
||||
self
|
||||
}
|
||||
|
||||
/// Accept secure connections via Rustls.
|
||||
#[cfg(feature = "rustls")]
|
||||
pub fn rustls(mut self, config: rustls::ServerConfig) -> Self {
|
||||
self.stream = StreamType::Rustls(config);
|
||||
#[doc(hidden)]
|
||||
#[deprecated(note = "Renamed to `rustls_0_20()`.")]
|
||||
#[cfg(feature = "rustls-0_20")]
|
||||
pub fn rustls(mut self, config: tls_rustls_0_20::ServerConfig) -> Self {
|
||||
self.stream = StreamType::Rustls020(config);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set client timeout for first request.
|
||||
/// Accepts secure connections via Rustls v0.20.
|
||||
#[cfg(feature = "rustls-0_20")]
|
||||
pub fn rustls_0_20(mut self, config: tls_rustls_0_20::ServerConfig) -> Self {
|
||||
self.stream = StreamType::Rustls020(config);
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(note = "Renamed to `rustls_0_21()`.")]
|
||||
#[cfg(feature = "rustls-0_21")]
|
||||
pub fn rustls_021(mut self, config: tls_rustls_0_21::ServerConfig) -> Self {
|
||||
self.stream = StreamType::Rustls021(config);
|
||||
self
|
||||
}
|
||||
|
||||
/// Accepts secure connections via Rustls v0.21.
|
||||
#[cfg(feature = "rustls-0_21")]
|
||||
pub fn rustls_0_21(mut self, config: tls_rustls_0_21::ServerConfig) -> Self {
|
||||
self.stream = StreamType::Rustls021(config);
|
||||
self
|
||||
}
|
||||
|
||||
/// Accepts secure connections via Rustls v0.22.
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
pub fn rustls_0_22(mut self, config: tls_rustls_0_22::ServerConfig) -> Self {
|
||||
self.stream = StreamType::Rustls022(config);
|
||||
self
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn client_request_timeout(mut self, dur: Duration) -> Self {
|
||||
self.client_request_timeout = dur;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the address the server will listen on.
|
||||
///
|
||||
/// By default, only listens on `127.0.0.1`.
|
||||
pub fn listen_address(mut self, addr: impl Into<String>) -> Self {
|
||||
self.listen_address = addr.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets test server port.
|
||||
///
|
||||
/// By default, a random free port is determined by the OS.
|
||||
|
@ -438,6 +632,15 @@ impl TestServerConfig {
|
|||
self.workers = workers;
|
||||
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
|
||||
|
@ -464,9 +667,9 @@ impl TestServer {
|
|||
let scheme = if self.tls { "https" } else { "http" };
|
||||
|
||||
if uri.starts_with('/') {
|
||||
format!("{}://localhost:{}{}", scheme, self.addr.port(), uri)
|
||||
format!("{}://{}{}", scheme, self.addr, uri)
|
||||
} else {
|
||||
format!("{}://localhost:{}/{}", scheme, self.addr.port(), uri)
|
||||
format!("{}://{}/{}", scheme, self.addr, uri)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,40 +1,45 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## Unreleased
|
||||
|
||||
- Take the encoded buffer when yielding bytes in the response stream rather than splitting the buffer, reducing memory use
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
## 4.3.0
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 4.2.0 - 2023-01-21
|
||||
## 4.2.0
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||
|
||||
## 4.1.0 - 2022-03-02
|
||||
## 4.1.0
|
||||
|
||||
- Add support for `actix` version `0.13`. [#2675]
|
||||
|
||||
[#2675]: https://github.com/actix/actix-web/pull/2675
|
||||
|
||||
## 4.0.0 - 2022-02-25
|
||||
## 4.0.0
|
||||
|
||||
- No significant changes since `4.0.0-beta.12`.
|
||||
|
||||
## 4.0.0-beta.12 - 2022-02-16
|
||||
## 4.0.0-beta.12
|
||||
|
||||
- No significant changes since `4.0.0-beta.11`.
|
||||
|
||||
## 4.0.0-beta.11 - 2022-01-31
|
||||
## 4.0.0-beta.11
|
||||
|
||||
- No significant changes since `4.0.0-beta.10`.
|
||||
|
||||
## 4.0.0-beta.10 - 2022-01-04
|
||||
## 4.0.0-beta.10
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||
|
||||
## 4.0.0-beta.9 - 2021-12-27
|
||||
## 4.0.0-beta.9
|
||||
|
||||
- No significant changes since `4.0.0-beta.8`.
|
||||
|
||||
## 4.0.0-beta.8 - 2021-12-11
|
||||
## 4.0.0-beta.8
|
||||
|
||||
- Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920]
|
||||
- Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920]
|
||||
|
@ -42,33 +47,33 @@
|
|||
|
||||
[#1920]: https://github.com/actix/actix-web/pull/1920
|
||||
|
||||
## 4.0.0-beta.7 - 2021-09-09
|
||||
## 4.0.0-beta.7
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
||||
## 4.0.0-beta.6 - 2021-06-26
|
||||
## 4.0.0-beta.6
|
||||
|
||||
- Update `actix` to `0.12`. [#2277]
|
||||
|
||||
[#2277]: https://github.com/actix/actix-web/pull/2277
|
||||
|
||||
## 4.0.0-beta.5 - 2021-06-17
|
||||
## 4.0.0-beta.5
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 4.0.0-beta.4 - 2021-04-02
|
||||
## 4.0.0-beta.4
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 4.0.0-beta.3 - 2021-03-09
|
||||
## 4.0.0-beta.3
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 4.0.0-beta.2 - 2021-02-10
|
||||
## 4.0.0-beta.2
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 4.0.0-beta.1 - 2021-01-07
|
||||
## 4.0.0-beta.1
|
||||
|
||||
- Update `pin-project` to `1.0`.
|
||||
- Update `bytes` to `1.0`. [#1813]
|
||||
|
@ -77,63 +82,63 @@
|
|||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||
[#1864]: https://github.com/actix/actix-web/pull/1864
|
||||
|
||||
## 3.0.0 - 2020-09-11
|
||||
## 3.0.0
|
||||
|
||||
- No significant changes from `3.0.0-beta.2`.
|
||||
|
||||
## 3.0.0-beta.2 - 2020-09-10
|
||||
## 3.0.0-beta.2
|
||||
|
||||
- Update `actix-*` dependencies to latest versions.
|
||||
|
||||
## [3.0.0-beta.1] - 2020-xx-xx
|
||||
## 3.0.0-beta.1
|
||||
|
||||
- Update `actix-web` & `actix-http` dependencies to beta.1
|
||||
- Bump minimum supported Rust version to 1.40
|
||||
|
||||
## [3.0.0-alpha.1] - 2020-05-08
|
||||
## 3.0.0-alpha.1
|
||||
|
||||
- Update the actix-web dependency to 3.0.0-alpha.1
|
||||
- Update the actix dependency to 0.10.0-alpha.2
|
||||
- Update the actix-http dependency to 2.0.0-alpha.3
|
||||
|
||||
## [2.0.0] - 2019-12-20
|
||||
## 2.0.0
|
||||
|
||||
- Release
|
||||
|
||||
## [2.0.0-alpha.1] - 2019-12-15
|
||||
## 2.0.0-alpha.1
|
||||
|
||||
- Migrate to actix-web 2.0.0
|
||||
|
||||
## [1.0.4] - 2019-12-07
|
||||
## 1.0.4
|
||||
|
||||
- Allow comma-separated websocket subprotocols without spaces (#1172)
|
||||
|
||||
## [1.0.3] - 2019-11-14
|
||||
## 1.0.3
|
||||
|
||||
- Update actix-web and actix-http dependencies
|
||||
|
||||
## [1.0.2] - 2019-07-20
|
||||
## 1.0.2
|
||||
|
||||
- Add `ws::start_with_addr()`, returning the address of the created actor, along with the `HttpResponse`.
|
||||
|
||||
- Add support for specifying protocols on websocket handshake #835
|
||||
|
||||
## [1.0.1] - 2019-06-28
|
||||
## 1.0.1
|
||||
|
||||
- Allow to use custom ws codec with `WebsocketContext` #925
|
||||
|
||||
## [1.0.0] - 2019-05-29
|
||||
## 1.0.0
|
||||
|
||||
- Update actix-http and actix-web
|
||||
|
||||
## [0.1.0-alpha.3] - 2019-04-02
|
||||
## 0.1.0-alpha.3
|
||||
|
||||
- Update actix-http and actix-web
|
||||
|
||||
## [0.1.0-alpha.2] - 2019-03-29
|
||||
## 0.1.0-alpha.2
|
||||
|
||||
- Update actix-http and actix-web
|
||||
|
||||
## [0.1.0-alpha.1] - 2019-03-28
|
||||
## 0.1.0-alpha.1
|
||||
|
||||
- Initial impl
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-web-actors"
|
||||
version = "4.2.0"
|
||||
version = "4.3.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix actors support for Actix Web"
|
||||
keywords = ["actix", "http", "web", "framework", "async"]
|
||||
|
@ -32,6 +32,6 @@ actix-test = "0.1"
|
|||
awc = { version = "3", default-features = false }
|
||||
actix-web = { version = "4", features = ["macros"] }
|
||||
|
||||
env_logger = "0.10"
|
||||
env_logger = "0.11"
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||
mime = "0.3"
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
# actix-web-actors
|
||||
# `actix-web-actors`
|
||||
|
||||
> Actix actors support for Actix Web.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-web-actors)
|
||||
[](https://docs.rs/actix-web-actors/4.2.0)
|
||||

|
||||
[](https://docs.rs/actix-web-actors/4.3.0)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-web-actors/4.2.0)
|
||||
[](https://deps.rs/crate/actix-web-actors/4.3.0)
|
||||
[](https://crates.io/crates/actix-web-actors)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-web-actors)
|
||||
- Minimum Supported Rust Version (MSRV): 1.68
|
||||
<!-- prettier-ignore-end -->
|
||||
|
|
|
@ -248,13 +248,11 @@ where
|
|||
mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use actix::Actor;
|
||||
use actix_web::{
|
||||
http::StatusCode,
|
||||
test::{call_service, init_service, read_body, TestRequest},
|
||||
web, App, HttpResponse,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
|
@ -57,7 +57,6 @@
|
|||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
|
|
@ -710,7 +710,7 @@ where
|
|||
}
|
||||
|
||||
if !this.buf.is_empty() {
|
||||
Poll::Ready(Some(Ok(this.buf.split().freeze())))
|
||||
Poll::Ready(Some(Ok(std::mem::take(&mut this.buf).freeze())))
|
||||
} else if this.fut.alive() && !this.closed {
|
||||
Poll::Pending
|
||||
} else {
|
||||
|
@ -775,10 +775,10 @@ where
|
|||
break;
|
||||
}
|
||||
Poll::Pending => break,
|
||||
Poll::Ready(Some(Err(e))) => {
|
||||
Poll::Ready(Some(Err(err))) => {
|
||||
return Poll::Ready(Some(Err(ProtocolError::Io(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("{}", e),
|
||||
format!("{err}"),
|
||||
)))));
|
||||
}
|
||||
}
|
||||
|
@ -817,10 +817,7 @@ where
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::{
|
||||
http::{header, Method},
|
||||
test::TestRequest,
|
||||
};
|
||||
use actix_web::test::TestRequest;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
|
@ -1,52 +1,65 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## 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.
|
||||
|
||||
## 4.2.2
|
||||
|
||||
- Fix regression when declaring `wrap` attribute using an expression.
|
||||
|
||||
## 4.2.1
|
||||
|
||||
- Update `syn` dependency to `2`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 4.2.0 - 2023-02-26
|
||||
## 4.2.0
|
||||
|
||||
- Add support for custom methods with the `#[route]` macro. [#2969]
|
||||
|
||||
[#2969]: https://github.com/actix/actix-web/pull/2969
|
||||
|
||||
## 4.1.0 - 2022-09-11
|
||||
## 4.1.0
|
||||
|
||||
- Add `#[routes]` macro to support multiple paths for one handler. [#2718]
|
||||
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
|
||||
|
||||
[#2718]: https://github.com/actix/actix-web/pull/2718
|
||||
|
||||
## 4.0.1 - 2022-06-11
|
||||
## 4.0.1
|
||||
|
||||
- Fix support for guard paths in route handler macros. [#2771]
|
||||
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency.
|
||||
|
||||
[#2771]: https://github.com/actix/actix-web/pull/2771
|
||||
|
||||
## 4.0.0 - 2022-02-24
|
||||
## 4.0.0
|
||||
|
||||
- Version aligned with `actix-web` and will remain in sync going forward.
|
||||
- No significant changes since `0.5.0`.
|
||||
|
||||
## 0.5.0 - 2022-02-24
|
||||
## 0.5.0
|
||||
|
||||
- No significant changes since `0.5.0-rc.2`.
|
||||
|
||||
## 0.5.0-rc.2 - 2022-02-01
|
||||
## 0.5.0-rc.2
|
||||
|
||||
- No significant changes since `0.5.0-rc.1`.
|
||||
|
||||
## 0.5.0-rc.1 - 2022-01-04
|
||||
## 0.5.0-rc.1
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||
|
||||
## 0.5.0-beta.6 - 2021-12-11
|
||||
## 0.5.0-beta.6
|
||||
|
||||
- No significant changes since `0.5.0-beta.5`.
|
||||
|
||||
## 0.5.0-beta.5 - 2021-10-20
|
||||
## 0.5.0-beta.5
|
||||
|
||||
- Improve error recovery potential when macro input is invalid. [#2410]
|
||||
- Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409]
|
||||
|
@ -55,18 +68,18 @@
|
|||
[#2410]: https://github.com/actix/actix-web/pull/2410
|
||||
[#2409]: https://github.com/actix/actix-web/pull/2409
|
||||
|
||||
## 0.5.0-beta.4 - 2021-09-09
|
||||
## 0.5.0-beta.4
|
||||
|
||||
- In routing macros, paths are now validated at compile time. [#2350]
|
||||
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
||||
[#2350]: https://github.com/actix/actix-web/pull/2350
|
||||
|
||||
## 0.5.0-beta.3 - 2021-06-17
|
||||
## 0.5.0-beta.3
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 0.5.0-beta.2 - 2021-03-09
|
||||
## 0.5.0-beta.2
|
||||
|
||||
- Preserve doc comments when using route macros. [#2022]
|
||||
- Add `name` attribute to `route` macro. [#1934]
|
||||
|
@ -74,11 +87,11 @@
|
|||
[#2022]: https://github.com/actix/actix-web/pull/2022
|
||||
[#1934]: https://github.com/actix/actix-web/pull/1934
|
||||
|
||||
## 0.5.0-beta.1 - 2021-02-10
|
||||
## 0.5.0-beta.1
|
||||
|
||||
- Use new call signature for `System::new`.
|
||||
|
||||
## 0.4.0 - 2020-09-20
|
||||
## 0.4.0
|
||||
|
||||
- Added compile success and failure testing. [#1677]
|
||||
- Add `route` macro for supporting multiple HTTP methods guards. [#1674]
|
||||
|
@ -86,23 +99,23 @@
|
|||
[#1677]: https://github.com/actix/actix-web/pull/1677
|
||||
[#1674]: https://github.com/actix/actix-web/pull/1674
|
||||
|
||||
## 0.3.0 - 2020-09-11
|
||||
## 0.3.0
|
||||
|
||||
- No significant changes from `0.3.0-beta.1`.
|
||||
|
||||
## 0.3.0-beta.1 - 2020-07-14
|
||||
## 0.3.0-beta.1
|
||||
|
||||
- Add main entry-point macro that uses re-exported runtime. [#1559]
|
||||
|
||||
[#1559]: https://github.com/actix/actix-web/pull/1559
|
||||
|
||||
## 0.2.2 - 2020-05-23
|
||||
## 0.2.2
|
||||
|
||||
- Add resource middleware on actix-web-codegen [#1467]
|
||||
|
||||
[#1467]: https://github.com/actix/actix-web/pull/1467
|
||||
|
||||
## 0.2.1 - 2020-02-25
|
||||
## 0.2.1
|
||||
|
||||
- Add `#[allow(missing_docs)]` attribute to generated structs [#1368]
|
||||
- Allow the handler function to be named as `config` [#1290]
|
||||
|
@ -110,35 +123,35 @@
|
|||
[#1368]: https://github.com/actix/actix-web/issues/1368
|
||||
[#1290]: https://github.com/actix/actix-web/issues/1290
|
||||
|
||||
## 0.2.0 - 2019-12-13
|
||||
## 0.2.0
|
||||
|
||||
- Generate code for actix-web 2.0
|
||||
|
||||
## 0.1.3 - 2019-10-14
|
||||
## 0.1.3
|
||||
|
||||
- Bump up `syn` & `quote` to 1.0
|
||||
- Provide better error message
|
||||
|
||||
## 0.1.2 - 2019-06-04
|
||||
## 0.1.2
|
||||
|
||||
- Add macros for head, options, trace, connect and patch http methods
|
||||
|
||||
## 0.1.1 - 2019-06-01
|
||||
## 0.1.1
|
||||
|
||||
- Add syn "extra-traits" feature
|
||||
|
||||
## 0.1.0 - 2019-05-18
|
||||
## 0.1.0
|
||||
|
||||
- Release
|
||||
|
||||
## 0.1.0-beta.1 - 2019-04-20
|
||||
## 0.1.0-beta.1
|
||||
|
||||
- Gen code for actix-web 1.0.0-beta.1
|
||||
|
||||
## 0.1.0-alpha.6 - 2019-04-14
|
||||
## 0.1.0-alpha.6
|
||||
|
||||
- Gen code for actix-web 1.0.0-alpha.6
|
||||
|
||||
## 0.1.0-alpha.1 - 2019-03-28
|
||||
## 0.1.0-alpha.1
|
||||
|
||||
- Initial impl
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
[package]
|
||||
name = "actix-web-codegen"
|
||||
version = "4.2.0"
|
||||
version = "4.3.0"
|
||||
description = "Routing and runtime macros for Actix Web"
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[features]
|
||||
default = ["compat-routing-macros-force-pub"]
|
||||
compat-routing-macros-force-pub = []
|
||||
|
||||
[dependencies]
|
||||
actix-router = "0.5"
|
||||
actix-router = { version = "0.5", default-features = false }
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = { version = "2", features = ["full", "extra-traits"] }
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
# actix-web-codegen
|
||||
# `actix-web-codegen`
|
||||
|
||||
> Routing and runtime macros for Actix Web.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-web-codegen)
|
||||
[](https://docs.rs/actix-web-codegen/4.2.0)
|
||||

|
||||
[](https://docs.rs/actix-web-codegen/4.3.0)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-web-codegen/4.2.0)
|
||||
[](https://deps.rs/crate/actix-web-codegen/4.3.0)
|
||||
[](https://crates.io/crates/actix-web-codegen)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-web-codegen)
|
||||
- Minimum Supported Rust Version (MSRV): 1.68
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Compile Testing
|
||||
|
||||
|
|
|
@ -83,6 +83,7 @@ use proc_macro::TokenStream;
|
|||
use quote::quote;
|
||||
|
||||
mod route;
|
||||
mod scope;
|
||||
|
||||
/// Creates resource handler, allowing multiple HTTP method guards.
|
||||
///
|
||||
|
@ -197,6 +198,43 @@ method_macro!(Options, options);
|
|||
method_macro!(Trace, trace);
|
||||
method_macro!(Patch, patch);
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Note that Actix Web also works under `#[tokio::main]` since version 4.0. However, this macro is
|
||||
|
@ -240,3 +278,15 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
|
|||
output.extend(item);
|
||||
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 syn::{punctuated::Punctuated, Ident, LitStr, Path, Token};
|
||||
|
||||
use crate::input_and_compile_error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RouteArgs {
|
||||
path: syn::LitStr,
|
||||
options: Punctuated<syn::MetaNameValue, Token![,]>,
|
||||
pub(crate) path: syn::LitStr,
|
||||
pub(crate) options: Punctuated<syn::MetaNameValue, Token![,]>,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for RouteArgs {
|
||||
|
@ -78,7 +80,7 @@ macro_rules! standard_method_type {
|
|||
}
|
||||
}
|
||||
|
||||
fn from_path(method: &Path) -> Result<Self, ()> {
|
||||
pub(crate) fn from_path(method: &Path) -> Result<Self, ()> {
|
||||
match () {
|
||||
$(_ if method.is_ident(stringify!($lower)) => Ok(Self::$variant),)+
|
||||
_ => Err(()),
|
||||
|
@ -224,7 +226,7 @@ struct Args {
|
|||
path: syn::LitStr,
|
||||
resource_name: Option<syn::LitStr>,
|
||||
guards: Vec<Path>,
|
||||
wrappers: Vec<syn::Type>,
|
||||
wrappers: Vec<syn::Expr>,
|
||||
methods: HashSet<MethodTypeExt>,
|
||||
}
|
||||
|
||||
|
@ -251,7 +253,7 @@ impl Args {
|
|||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
nv.value,
|
||||
"Attribute name expects literal string!",
|
||||
"Attribute name expects literal string",
|
||||
));
|
||||
}
|
||||
} else if nv.path.is_ident("guard") {
|
||||
|
@ -264,7 +266,7 @@ impl Args {
|
|||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
nv.value,
|
||||
"Attribute guard expects literal string!",
|
||||
"Attribute guard expects literal string",
|
||||
));
|
||||
}
|
||||
} else if nv.path.is_ident("wrap") {
|
||||
|
@ -283,9 +285,9 @@ impl Args {
|
|||
} else if nv.path.is_ident("method") {
|
||||
if !is_route_macro {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&nv,
|
||||
"HTTP method forbidden here. To handle multiple methods, use `route` instead",
|
||||
));
|
||||
&nv,
|
||||
"HTTP method forbidden here; to handle multiple methods, use `route` instead",
|
||||
));
|
||||
} else if let syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
|
@ -300,13 +302,13 @@ impl Args {
|
|||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
nv.value,
|
||||
"Attribute method expects literal string!",
|
||||
"Attribute method expects literal string",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
nv.path,
|
||||
"Unknown attribute key is specified. Allowed: guard, method and wrap",
|
||||
"Unknown attribute key is specified; allowed: guard, method and wrap",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -411,6 +413,13 @@ impl ToTokens for Route {
|
|||
doc_attributes,
|
||||
} = 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
|
||||
.iter()
|
||||
.map(|args| {
|
||||
|
@ -458,7 +467,7 @@ impl ToTokens for Route {
|
|||
let stream = quote! {
|
||||
#(#doc_attributes)*
|
||||
#[allow(non_camel_case_types, missing_docs)]
|
||||
pub struct #name;
|
||||
#vis struct #name;
|
||||
|
||||
impl ::actix_web::dev::HttpServiceFactory for #name {
|
||||
fn register(self, __config: &mut actix_web::dev::AppService) {
|
||||
|
@ -542,15 +551,3 @@ pub(crate) fn with_methods(input: TokenStream) -> TokenStream {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -212,6 +212,19 @@ async fn get_wrap(_: web::Path<String>) -> impl Responder {
|
|||
HttpResponse::Ok()
|
||||
}
|
||||
|
||||
/// Using expression, not just path to type, in wrap attribute.
|
||||
///
|
||||
/// Regression from <https://github.com/actix/actix-web/issues/3118>.
|
||||
#[route(
|
||||
"/catalog",
|
||||
method = "GET",
|
||||
method = "HEAD",
|
||||
wrap = "actix_web::middleware::Compress::default()"
|
||||
)]
|
||||
async fn get_catalog() -> impl Responder {
|
||||
HttpResponse::Ok().body("123123123")
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_params() {
|
||||
let srv = actix_test::start(|| {
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue