1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-06-02 21:39:26 +00:00

Merge branch 'master' into master

This commit is contained in:
Armand Mégrot 2023-09-12 10:51:58 +02:00 committed by GitHub
commit 5dcad17a61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
232 changed files with 3688 additions and 2492 deletions

View file

@ -12,6 +12,3 @@ ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=
# testing # testing
ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture" ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture"
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture" ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
# compile docs as docs.rs would
# RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace

10
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: cargo
directory: /
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily

View file

@ -2,8 +2,7 @@ name: Benchmark
on: on:
push: push:
branches: branches: [master]
- master
permissions: permissions:
contents: read # to fetch code (actions/checkout) contents: read # to fetch code (actions/checkout)
@ -17,7 +16,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install Rust - name: Install Rust
run: | run: |

View file

@ -5,7 +5,7 @@ on:
branches: [master] branches: [master]
permissions: permissions:
contents: read # to fetch code (actions/checkout) contents: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@ -16,48 +16,38 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
# prettier-ignore
target: target:
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
- { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc } - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
version: version:
- nightly - { name: nightly, version: nightly }
name: ${{ matrix.target.name }} / ${{ matrix.version }} name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
runs-on: ${{ matrix.target.os }} runs-on: ${{ matrix.target.os }}
env:
CI: 1
CARGO_INCREMENTAL: 0
VCPKGRS_DYNAMIC: 1
CARGO_UNSTABLE_SPARSE_REGISTRY: true
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
# install OpenSSL on Windows
# TODO: GitHub actions docs state that OpenSSL is
# already installed on these Windows machines somewhere
- name: Set vcpkg root
if: matrix.target.triple == 'x86_64-pc-windows-msvc'
run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Install OpenSSL - name: Install OpenSSL
if: matrix.target.triple == 'x86_64-pc-windows-msvc' if: matrix.target.os == 'windows-latest'
run: vcpkg install openssl:x64-windows run: choco install openssl -y --forcex64 --no-progress
- name: Set OpenSSL dir in env
- name: Install ${{ matrix.version }} if: matrix.target.os == 'windows-latest'
run: | run: |
rustup set profile minimal echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append
rustup install ${{ matrix.version }} echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append
rustup override set ${{ matrix.version }}
- name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
with:
toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack - name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack uses: taiki-e/install-action@v2.18.9
with:
- name: Generate Cargo.lock tool: cargo-hack
run: cargo generate-lockfile
- name: Cache Dependencies
uses: Swatinem/rust-cache@v2.2.1
- name: check minimal - name: check minimal
run: cargo ci-check-min run: cargo ci-check-min
@ -70,7 +60,7 @@ jobs:
run: | run: |
cargo test --lib --tests -p=actix-router --all-features cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features cargo test --lib --tests -p=actix-http-test --all-features
@ -88,22 +78,16 @@ jobs:
name: Verify Feature Combinations name: Verify Feature Combinations
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
CI: 1
CARGO_INCREMENTAL: 0
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
- name: Install cargo-hack - name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack uses: taiki-e/install-action@v2.18.9
with:
- name: Generate Cargo.lock tool: cargo-hack
run: cargo generate-lockfile
- name: Cache Dependencies
uses: Swatinem/rust-cache@v2.2.1
- name: check feature combinations - name: check feature combinations
run: cargo ci-check-all-feature-powerset run: cargo ci-check-all-feature-powerset
@ -115,22 +99,16 @@ jobs:
name: nextest name: nextest
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
CI: 1
CARGO_INCREMENTAL: 0
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
- name: Install nextest - name: Install nextest
uses: taiki-e/install-action@nextest uses: taiki-e/install-action@v2.18.9
with:
- name: Generate Cargo.lock tool: nextest
run: cargo generate-lockfile
- name: Cache Dependencies
uses: Swatinem/rust-cache@v2.2.1
- name: Test with cargo-nextest - name: Test with cargo-nextest
run: cargo nextest run run: cargo nextest run

View file

@ -3,11 +3,13 @@ name: CI
on: on:
pull_request: pull_request:
types: [opened, synchronize, reopened] types: [opened, synchronize, reopened]
merge_group:
types: [checks_requested]
push: push:
branches: [master] branches: [master]
permissions: permissions:
contents: read # to fetch code (actions/checkout) contents: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@ -18,48 +20,46 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
# prettier-ignore
target: target:
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc } - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
version: version:
- 1.59.0 # MSRV - { name: msrv, version: 1.68.0 }
- stable - { name: stable, version: stable }
name: ${{ matrix.target.name }} / ${{ matrix.version }} name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
runs-on: ${{ matrix.target.os }} runs-on: ${{ matrix.target.os }}
env: {}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install OpenSSL - name: Install OpenSSL
if: matrix.target.os == 'windows-latest' if: matrix.target.os == 'windows-latest'
run: choco install openssl run: choco install openssl -y --forcex64 --no-progress
- name: Set OpenSSL dir in env - name: Set OpenSSL dir in env
if: matrix.target.os == 'windows-latest' if: matrix.target.os == 'windows-latest'
run: echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append 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
- name: Install Rust (${{ matrix.version }}) - name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
with: with:
toolchain: ${{ matrix.version }} toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack - name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack uses: taiki-e/install-action@v2.18.9
with:
tool: cargo-hack
- name: workaround MSRV issues - name: workaround MSRV issues
if: matrix.version != 'stable' if: matrix.version.name == 'msrv'
run: | run: |
cargo install cargo-edit --version=0.8.0 cargo update -p=clap --precise=4.3.24
cargo add const-str@0.3 --dev -p=actix-web cargo update -p=clap_lex --precise=0.5.0
cargo add const-str@0.3 --dev -p=awc cargo update -p=anstyle --precise=1.0.2
- name: workaround MSRV issues
if: matrix.version != 'stable'
run: |
cargo update -p=zstd-sys --precise=2.0.1+zstd.1.5.2
- name: check minimal - name: check minimal
run: cargo ci-check-min run: cargo ci-check-min
@ -72,7 +72,7 @@ jobs:
run: | run: |
cargo test --lib --tests -p=actix-router --all-features cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features cargo test --lib --tests -p=actix-http-test --all-features
@ -90,11 +90,12 @@ jobs:
name: io-uring tests name: io-uring tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install Rust - name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
with: { toolchain: nightly } with:
toolchain: nightly
- name: tests (io-uring) - name: tests (io-uring)
timeout-minutes: 60 timeout-minutes: 60
@ -105,11 +106,12 @@ jobs:
name: doc tests name: doc tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install Rust (nightly) - name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
with: { toolchain: nightly } with:
toolchain: nightly
- name: doc tests - name: doc tests
run: cargo ci-doctest run: cargo ci-doctest

View file

@ -5,7 +5,7 @@ on:
types: [opened, synchronize, reopened] types: [opened, synchronize, reopened]
permissions: permissions:
contents: read # to fetch code (actions/checkout) contents: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@ -15,9 +15,9 @@ jobs:
fmt: fmt:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
with: with:
toolchain: nightly toolchain: nightly
components: rustfmt components: rustfmt
@ -30,43 +30,49 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
with: { components: clippy }
- uses: giraffate/clippy-action@v1
with: with:
reporter: 'github-pr-check' components: clippy
- uses: giraffate/clippy-action@v1.0.1
with:
reporter: github-pr-check
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
clippy_flags: --workspace --all-features --tests --examples --bins -- -Dclippy::todo clippy_flags: --workspace --all-features --tests --examples --bins -- -Dclippy::todo -Aunknown_lints
lint-docs: lint-docs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
with: { components: rust-docs } with:
toolchain: nightly
components: rust-docs
- name: Check for broken intra-doc links - name: Check for broken intra-doc links
env: { RUSTDOCFLAGS: "-D warnings" } env:
run: cargo doc --no-deps --all-features --workspace RUSTDOCFLAGS: -D warnings
run: cargo +nightly doc --no-deps --workspace --all-features
public-api-diff: public-api-diff:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
ref: ${{ github.base_ref }} ref: ${{ github.base_ref }}
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
with: { toolchain: nightly } with:
toolchain: nightly-2023-08-25
- uses: taiki-e/cache-cargo-install-action@v1 - uses: taiki-e/cache-cargo-install-action@v1.2.1
with: { tool: cargo-public-api } with:
tool: cargo-public-api
- name: generate API diff - name: generate API diff
run: | run: |

View file

@ -1,5 +1,3 @@
# disabled because `cargo tarpaulin` currently segfaults
name: Coverage name: Coverage
on: on:
@ -7,27 +5,33 @@ on:
branches: [master] branches: [master]
permissions: permissions:
contents: read # to fetch code (actions/checkout) contents: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
# job currently (1st Feb 2022) segfaults
coverage: coverage:
name: coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1 - name: Install Rust
with: { toolchain: nightly } uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
with:
components: llvm-tools-preview
- name: Generate coverage file - name: Install cargo-llvm-cov
run: | uses: taiki-e/install-action@v2.13.4
cargo install cargo-tarpaulin --vers "^0.13" with:
cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose tool: cargo-llvm-cov
- name: Upload to Codecov
uses: codecov/codecov-action@v1 - name: Generate code coverage
with: { file: cobertura.xml } run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3.1.4
with:
files: codecov.json
fail_ci_if_error: true

View file

@ -5,8 +5,7 @@ on:
branches: [master] branches: [master]
permissions: permissions:
contents: read # to fetch code (actions/checkout) contents: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
@ -14,14 +13,17 @@ concurrency:
jobs: jobs:
build: build:
permissions: permissions:
contents: write # to push changes in repo (jamesives/github-pages-deploy-action) contents: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly - name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
with:
toolchain: nightly
- name: Build Docs - name: Build Docs
run: cargo +nightly doc --no-deps --workspace --all-features run: cargo +nightly doc --no-deps --workspace --all-features
@ -32,7 +34,7 @@ jobs:
run: echo '<meta http-equiv="refresh" content="0;url=actix_web/index.html">' > target/doc/index.html run: echo '<meta http-equiv="refresh" content="0;url=actix_web/index.html">' > target/doc/index.html
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4.4.1 uses: JamesIves/github-pages-deploy-action@v4.4.3
with: with:
folder: target/doc folder: target/doc
single-commit: true single-commit: true

4
.gitignore vendored
View file

@ -19,3 +19,7 @@ guide/build/
# Configuration directory generated by VSCode # Configuration directory generated by VSCode
.vscode .vscode
# code coverage
/lcov.info
/codecov.json

View file

@ -1 +0,0 @@
proseWrap: never

5
.prettierrc.yml Normal file
View file

@ -0,0 +1,5 @@
overrides:
- files: '*.md'
options:
printWidth: 9999
proseWrap: never

3
.rustfmt.toml Normal file
View file

@ -0,0 +1,3 @@
group_imports = "StdExternalCrate"
imports_granularity = "Crate"
use_field_init_shorthand = true

View file

@ -14,6 +14,11 @@ members = [
"awc", "awc",
] ]
[workspace.package]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.68"
[profile.dev] [profile.dev]
# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much. # Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much.
debug = 0 debug = 0

View file

@ -1,8 +1,10 @@
# Changes # Changes
## Unreleased - 2023-xx-xx ## Unreleased
## 0.6.3 - 2023-01-21 - Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
## 0.6.3
- XHTML files now use `Content-Disposition: inline` instead of `attachment`. [#2903] - 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. - Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
@ -10,14 +12,14 @@
[#2903]: https://github.com/actix/actix-web/pull/2903 [#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] - 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. - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
[#2817]: https://github.com/actix/actix-web/pull/2817 [#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] - Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021]
- Update `tokio-uring` dependency to `0.3`. - Update `tokio-uring` dependency to `0.3`.
@ -27,25 +29,25 @@
[#2021]: https://github.com/actix/actix-web/pull/2021 [#2021]: https://github.com/actix/actix-web/pull/2021
[#2645]: https://github.com/actix/actix-web/pull/2645 [#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`. - 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`. - 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`. - 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] - The `prefer_utf8` option introduced in `0.4.0` is now true by default. [#2583]
[#2583]: https://github.com/actix/actix-web/pull/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 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] - The `Files` service now correctly decodes `%25` in the URL path to `%` for the file path. [#2398]
@ -53,19 +55,19 @@
[#2398]: https://github.com/actix/actix-web/pull/2398 [#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`. - 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`. - 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`. - 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 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] - Add `NamedFile::open_async`. [#2408]
@ -77,15 +79,15 @@
[#2408]: https://github.com/actix/actix-web/pull/2408 [#2408]: https://github.com/actix/actix-web/pull/2408
[#2453]: https://github.com/actix/actix-web/pull/2453 [#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. - 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. - 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] - 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] - `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]
@ -93,7 +95,7 @@
[#2274]: https://github.com/actix/actix-web/pull/2274 [#2274]: https://github.com/actix/actix-web/pull/2274
[#2228]: https://github.com/actix/actix-web/pull/2228 [#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] - `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] - For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156]
@ -105,17 +107,17 @@
[#2225]: https://github.com/actix/actix-web/pull/2225 [#2225]: https://github.com/actix/actix-web/pull/2225
[#2257]: https://github.com/actix/actix-web/pull/2257 [#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] - Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046]
[#2046]: https://github.com/actix/actix-web/pull/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. - 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] - Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
- Replace `v_htmlescape` with `askama_escape`. [#1953] - Replace `v_htmlescape` with `askama_escape`. [#1953]
@ -123,39 +125,39 @@
[#1887]: https://github.com/actix/actix-web/pull/1887 [#1887]: https://github.com/actix/actix-web/pull/1887
[#1953]: https://github.com/actix/actix-web/pull/1953 [#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. - `HttpRange::parse` now has its own error type.
- Update `bytes` to `1.0`. [#1813] - Update `bytes` to `1.0`. [#1813]
[#1813]: https://github.com/actix/actix-web/pull/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] - Optionally support hidden files/directories. [#1811]
[#1811]: https://github.com/actix/actix-web/pull/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. - 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] - Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714]
[#1714]: https://github.com/actix/actix-web/pull/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. - 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 `v_htmlescape` to 0.10
- Update `actix-web` and `actix-http` dependencies to beta.1 - 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 - Update `actix-web` and `actix-http` dependencies to alpha
- Fix some typos in the docs - Fix some typos in the docs
@ -164,73 +166,73 @@
[#1384]: https://github.com/actix/actix-web/pull/1384 [#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 - Use the same format for file URLs regardless of platforms
## 0.2.0 - 2019-12-20 ## 0.2.0
- Fix BodyEncoding trait import #1220 - Fix BodyEncoding trait import #1220
## 0.2.0-alpha.1 - 2019-12-07 ## 0.2.0-alpha.1
- Migrate to `std::future` - 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) - 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 - 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 `mime_guess` crate version to 2.0.1
- Bump up `percent-encoding` crate version to 2.1 - Bump up `percent-encoding` crate version to 2.1
- Allow user defined request guards for `Files` #1113 - Allow user defined request guards for `Files` #1113
## 0.1.4 - 2019-07-20 ## 0.1.4
- Allow to disable `Content-Disposition` header #686 - 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 - 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 - Content-Length is 0 for NamedFile HEAD request #914
- Fix ring dependency from actix-web default features for #741 - 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 - 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 - 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 - 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 - Update actix-web to beta.1
## 0.1.0-alpha.6 - 2019-04-14 ## 0.1.0-alpha.6
- Update actix-web to alpha6 - Update actix-web to alpha6
## 0.1.0-alpha.4 - 2019-04-08 ## 0.1.0-alpha.4
- Update actix-web to alpha4 - Update actix-web to alpha4
## 0.1.0-alpha.2 - 2019-04-02 ## 0.1.0-alpha.2
- Add default handler support - Add default handler support
## 0.1.0-alpha.1 - 2019-03-28 ## 0.1.0-alpha.1
- Initial impl - Initial impl

View file

@ -11,7 +11,7 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web" repository = "https://github.com/actix/actix-web"
categories = ["asynchronous", "web-programming::http-server"] categories = ["asynchronous", "web-programming::http-server"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2021"
[lib] [lib]
name = "actix_files" name = "actix_files"

View file

@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.3)](https://docs.rs/actix-files/0.6.3) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.3)](https://docs.rs/actix-files/0.6.3)
![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![License](https://img.shields.io/crates/l/actix-files.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-files/0.6.3/status.svg)](https://deps.rs/crate/actix-files/0.6.3) [![dependency status](https://deps.rs/crate/actix-files/0.6.3/status.svg)](https://deps.rs/crate/actix-files/0.6.3)
@ -15,4 +15,4 @@
- [API Documentation](https://docs.rs/actix-files) - [API Documentation](https://docs.rs/actix-files)
- [Example Project](https://github.com/actix/examples/tree/master/basics/static-files) - [Example Project](https://github.com/actix/examples/tree/master/basics/static-files)
- Minimum Supported Rust Version (MSRV): 1.59 - Minimum Supported Rust Version (MSRV): 1.68

View file

@ -7,11 +7,10 @@ use std::{
}; };
use actix_web::{error::Error, web::Bytes}; use actix_web::{error::Error, web::Bytes};
use futures_core::{ready, Stream};
use pin_project_lite::pin_project;
#[cfg(feature = "experimental-io-uring")] #[cfg(feature = "experimental-io-uring")]
use bytes::BytesMut; use bytes::BytesMut;
use futures_core::{ready, Stream};
use pin_project_lite::pin_project;
use super::named::File; use super::named::File;

View file

@ -1,4 +1,9 @@
use std::{fmt::Write, fs::DirEntry, io, path::Path, path::PathBuf}; use std::{
fmt::Write,
fs::DirEntry,
io,
path::{Path, PathBuf},
};
use actix_web::{dev::ServiceResponse, HttpRequest, HttpResponse}; use actix_web::{dev::ServiceResponse, HttpRequest, HttpResponse};
use percent_encoding::{utf8_percent_encode, CONTROLS}; use percent_encoding::{utf8_percent_encode, CONTROLS};

View file

@ -8,8 +8,7 @@ use std::{
use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt}; use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt};
use actix_web::{ use actix_web::{
dev::{ dev::{
AppService, HttpServiceFactory, RequestHead, ResourceDef, ServiceRequest, AppService, HttpServiceFactory, RequestHead, ResourceDef, ServiceRequest, ServiceResponse,
ServiceResponse,
}, },
error::Error, error::Error,
guard::Guard, guard::Guard,
@ -301,12 +300,8 @@ impl Files {
pub fn default_handler<F, U>(mut self, f: F) -> Self pub fn default_handler<F, U>(mut self, f: F) -> Self
where where
F: IntoServiceFactory<U, ServiceRequest>, F: IntoServiceFactory<U, ServiceRequest>,
U: ServiceFactory< U: ServiceFactory<ServiceRequest, Config = (), Response = ServiceResponse, Error = Error>
ServiceRequest, + 'static,
Config = (),
Response = ServiceResponse,
Error = Error,
> + 'static,
{ {
// create and configure default resource // create and configure default resource
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory( self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
@ -422,10 +417,14 @@ mod tests {
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
let body = test::read_body(res).await; let body = test::read_body(res).await;
let body_str = std::str::from_utf8(&body).unwrap();
let actual_path = Path::new(&body_str);
let expected_path = Path::new("actix-files/tests");
assert!( assert!(
body.ends_with(b"actix-files/tests/"), actual_path.ends_with(expected_path),
"body {:?} does not end with `actix-files/tests/`", "body {:?} does not end with {:?}",
body actual_path,
expected_path
); );
} }
} }

View file

@ -18,6 +18,8 @@
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
use std::path::Path;
use actix_service::boxed::{BoxService, BoxServiceFactory}; use actix_service::boxed::{BoxService, BoxServiceFactory};
use actix_web::{ use actix_web::{
dev::{RequestHead, ServiceRequest, ServiceResponse}, dev::{RequestHead, ServiceRequest, ServiceResponse},
@ -25,7 +27,6 @@ use actix_web::{
http::header::DispositionType, http::header::DispositionType,
}; };
use mime_guess::from_ext; use mime_guess::from_ext;
use std::path::Path;
mod chunked; mod chunked;
mod directory; mod directory;
@ -37,16 +38,15 @@ mod path_buf;
mod range; mod range;
mod service; mod service;
pub use self::chunked::ChunkedReadFile; pub use self::{
pub use self::directory::Directory; chunked::ChunkedReadFile, directory::Directory, files::Files, named::NamedFile,
pub use self::files::Files; range::HttpRange, service::FilesService,
pub use self::named::NamedFile; };
pub use self::range::HttpRange; use self::{
pub use self::service::FilesService; directory::{directory_listing, DirectoryRenderer},
error::FilesError,
use self::directory::{directory_listing, DirectoryRenderer}; path_buf::PathBufWrap,
use self::error::FilesError; };
use self::path_buf::PathBufWrap;
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>; type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
@ -66,6 +66,7 @@ type PathFilter = dyn Fn(&Path, &RequestHead) -> bool;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{ use std::{
fmt::Write as _,
fs::{self}, fs::{self},
ops::Add, ops::Add,
time::{Duration, SystemTime}, time::{Duration, SystemTime},
@ -554,10 +555,9 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_static_files_with_spaces() { async fn test_static_files_with_spaces() {
let srv = test::init_service( let srv =
App::new().service(Files::new("/", ".").index_file("Cargo.toml")), test::init_service(App::new().service(Files::new("/", ".").index_file("Cargo.toml")))
) .await;
.await;
let request = TestRequest::get() let request = TestRequest::get()
.uri("/tests/test%20space.binary") .uri("/tests/test%20space.binary")
.to_request(); .to_request();
@ -667,8 +667,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_static_files() { async fn test_static_files() {
let srv = let srv =
test::init_service(App::new().service(Files::new("/", ".").show_files_listing())) test::init_service(App::new().service(Files::new("/", ".").show_files_listing())).await;
.await;
let req = TestRequest::with_uri("/missing").to_request(); let req = TestRequest::with_uri("/missing").to_request();
let resp = test::call_service(&srv, req).await; let resp = test::call_service(&srv, req).await;
@ -681,8 +680,7 @@ mod tests {
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let srv = let srv =
test::init_service(App::new().service(Files::new("/", ".").show_files_listing())) test::init_service(App::new().service(Files::new("/", ".").show_files_listing())).await;
.await;
let req = TestRequest::with_uri("/tests").to_request(); let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&srv, req).await; let resp = test::call_service(&srv, req).await;
assert_eq!( assert_eq!(
@ -851,8 +849,10 @@ mod tests {
let filename_encoded = filename let filename_encoded = filename
.as_bytes() .as_bytes()
.iter() .iter()
.map(|c| format!("%{:02X}", c)) .fold(String::new(), |mut buf, c| {
.collect::<String>(); write!(&mut buf, "%{:02X}", c).unwrap();
buf
});
std::fs::File::create(tmpdir.path().join(filename)).unwrap(); std::fs::File::create(tmpdir.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("", tmpdir.path()))).await;

View file

@ -8,13 +8,13 @@ use std::{
use actix_web::{ use actix_web::{
body::{self, BoxBody, SizedStream}, body::{self, BoxBody, SizedStream},
dev::{ dev::{
self, AppService, HttpServiceFactory, ResourceDef, Service, ServiceFactory, self, AppService, HttpServiceFactory, ResourceDef, Service, ServiceFactory, ServiceRequest,
ServiceRequest, ServiceResponse, ServiceResponse,
}, },
http::{ http::{
header::{ header::{
self, Charset, ContentDisposition, ContentEncoding, DispositionParam, self, Charset, ContentDisposition, ContentEncoding, DispositionParam, DispositionType,
DispositionType, ExtendedValue, HeaderValue, ExtendedValue, HeaderValue,
}, },
StatusCode, StatusCode,
}, },
@ -85,6 +85,7 @@ pub struct NamedFile {
#[cfg(not(feature = "experimental-io-uring"))] #[cfg(not(feature = "experimental-io-uring"))]
pub(crate) use std::fs::File; pub(crate) use std::fs::File;
#[cfg(feature = "experimental-io-uring")] #[cfg(feature = "experimental-io-uring")]
pub(crate) use tokio_uring::fs::File; pub(crate) use tokio_uring::fs::File;
@ -139,8 +140,7 @@ impl NamedFile {
_ => DispositionType::Attachment, _ => DispositionType::Attachment,
}; };
let mut parameters = let mut parameters = vec![DispositionParam::Filename(String::from(filename.as_ref()))];
vec![DispositionParam::Filename(String::from(filename.as_ref()))];
if !filename.is_ascii() { if !filename.is_ascii() {
parameters.push(DispositionParam::FilenameExt(ExtendedValue { parameters.push(DispositionParam::FilenameExt(ExtendedValue {

View file

@ -97,8 +97,6 @@ impl FromRequest for PathBufWrap {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::iter::FromIterator;
use super::*; use super::*;
#[test] #[test]

View file

@ -48,8 +48,8 @@ impl HttpRange {
/// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`).
/// `size` is full size of response (file). /// `size` is full size of response (file).
pub fn parse(header: &str, size: u64) -> Result<Vec<HttpRange>, ParseRangeErr> { pub fn parse(header: &str, size: u64) -> Result<Vec<HttpRange>, ParseRangeErr> {
let ranges = http_range::HttpRange::parse(header, size) let ranges =
.map_err(|err| ParseRangeErr(err.into()))?; http_range::HttpRange::parse(header, size).map_err(|err| ParseRangeErr(err.into()))?;
Ok(ranges Ok(ranges
.iter() .iter()

View file

@ -62,11 +62,7 @@ impl FilesService {
} }
} }
fn serve_named_file( fn serve_named_file(&self, req: ServiceRequest, mut named_file: NamedFile) -> ServiceResponse {
&self,
req: ServiceRequest,
mut named_file: NamedFile,
) -> ServiceResponse {
if let Some(ref mime_override) = self.mime_override { if let Some(ref mime_override) = self.mime_override {
let new_disposition = mime_override(&named_file.content_type.type_()); let new_disposition = mime_override(&named_file.content_type.type_());
named_file.content_disposition.disposition = new_disposition; named_file.content_disposition.disposition = new_disposition;
@ -120,13 +116,11 @@ impl Service<ServiceRequest> for FilesService {
)); ));
} }
let path_on_disk = match PathBufWrap::parse_path( let path_on_disk =
req.match_info().unprocessed(), match PathBufWrap::parse_path(req.match_info().unprocessed(), this.hidden_files) {
this.hidden_files, Ok(item) => item,
) { Err(err) => return Ok(req.error_response(err)),
Ok(item) => item, };
Err(err) => return Ok(req.error_response(err)),
};
if let Some(filter) = &this.path_filter { if let Some(filter) = &this.path_filter {
if !filter(path_on_disk.as_ref(), req.head()) { if !filter(path_on_disk.as_ref(), req.head()) {
@ -177,8 +171,7 @@ impl Service<ServiceRequest> for FilesService {
match NamedFile::open_async(&path).await { match NamedFile::open_async(&path).await {
Ok(mut named_file) => { Ok(mut named_file) => {
if let Some(ref mime_override) = this.mime_override { if let Some(ref mime_override) = this.mime_override {
let new_disposition = let new_disposition = mime_override(&named_file.content_type.type_());
mime_override(&named_file.content_type.type_());
named_file.content_disposition.disposition = new_disposition; named_file.content_disposition.disposition = new_disposition;
} }
named_file.flags = this.file_flags; named_file.flags = this.file_flags;

View file

@ -24,8 +24,7 @@ async fn test_utf8_file_contents() {
// disable UTF-8 attribute // disable UTF-8 attribute
let srv = let srv =
test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(false))) test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(false))).await;
.await;
let req = TestRequest::with_uri("/utf8.txt").to_request(); let req = TestRequest::with_uri("/utf8.txt").to_request();
let res = test::call_service(&srv, req).await; let res = test::call_service(&srv, req).await;

View file

@ -12,9 +12,7 @@ async fn test_guard_filter() {
let srv = test::init_service( let srv = test::init_service(
App::new() App::new()
.service(Files::new("/", "./tests/fixtures/guards/first").guard(Host("first.com"))) .service(Files::new("/", "./tests/fixtures/guards/first").guard(Host("first.com")))
.service( .service(Files::new("/", "./tests/fixtures/guards/second").guard(Host("second.com"))),
Files::new("/", "./tests/fixtures/guards/second").guard(Host("second.com")),
),
) )
.await; .await;

View file

@ -9,8 +9,7 @@ use actix_web::{
async fn test_directory_traversal_prevention() { async fn test_directory_traversal_prevention() {
let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await; let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await;
let req = let req = TestRequest::with_uri("/../../../../../../../../../../../etc/passwd").to_request();
TestRequest::with_uri("/../../../../../../../../../../../etc/passwd").to_request();
let res = test::call_service(&srv, req).await; let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::NOT_FOUND); assert_eq!(res.status(), StatusCode::NOT_FOUND);

View file

@ -1,12 +1,14 @@
# Changes # Changes
## Unreleased - 2023-xx-xx ## Unreleased
## 3.1.0 - 2023-01-21 - Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
## 3.1.0
- Minimum supported Rust version (MSRV) is now 1.59. - 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] - `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442]
- Added `TestServer::client_headers` method. [#2097] - Added `TestServer::client_headers` method. [#2097]
@ -22,41 +24,41 @@
<details> <details>
<summary>3.0.0 Pre-Releases</summary> <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`. - 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`. - 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. - 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] - Update `actix-server` to `2.0.0-rc.2`. [#2550]
[#2550]: https://github.com/actix/actix-web/pull/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`. - 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] - Update `actix-tls` to `3.0.0-rc.1`. [#2474]
[#2474]: https://github.com/actix/actix-web/pull/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] - Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]
[#2408]: https://github.com/actix/actix-web/pull/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] - `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] - Update `actix-server` to `2.0.0-beta.9`. [#2442]
@ -64,25 +66,25 @@
[#2442]: https://github.com/actix/actix-web/pull/2442 [#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. - 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] - Added `TestServer::client_headers` method. [#2097]
[#2097]: https://github.com/actix/actix-web/pull/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. - No notable changes.
## 3.0.0-beta.2 - 2021-02-10 ## 3.0.0-beta.2
- No notable changes. - No notable changes.
## 3.0.0-beta.1 - 2021-01-07 ## 3.0.0-beta.1
- Update `bytes` to `1.0`. [#1813] - Update `bytes` to `1.0`. [#1813]
@ -90,7 +92,7 @@
</details> </details>
## 2.1.0 - 2020-11-25 ## 2.1.0
- Add ability to set address for `TestServer`. [#1645] - Add ability to set address for `TestServer`. [#1645]
- Upgrade `base64` to `0.13`. - Upgrade `base64` to `0.13`.
@ -99,11 +101,11 @@
[#1773]: https://github.com/actix/actix-web/pull/1773 [#1773]: https://github.com/actix/actix-web/pull/1773
[#1645]: https://github.com/actix/actix-web/pull/1645 [#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. - 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 the `time` dependency to 0.2.7
- Update `actix-connect` dependency to 2.0.0-alpha.2 - Update `actix-connect` dependency to 2.0.0-alpha.2
@ -113,57 +115,57 @@
- Update `base64` dependency to 0.12 - Update `base64` dependency to 0.12
- Update `env_logger` dependency to 0.7 - Update `env_logger` dependency to 0.7
## 1.0.0 - 2019-12-13 ## 1.0.0
- Replaced `TestServer::start()` with `test_server()` - Replaced `TestServer::start()` with `test_server()`
## 1.0.0-alpha.3 - 2019-12-07 ## 1.0.0-alpha.3
- Migrate to `std::future` - Migrate to `std::future`
## 0.2.5 - 2019-09-17 ## 0.2.5
- Update serde_urlencoded to "0.6.1" - Update serde_urlencoded to "0.6.1"
- Increase TestServerRuntime timeouts from 500ms to 3000ms - Increase TestServerRuntime timeouts from 500ms to 3000ms
- Do not override current `System` - Do not override current `System`
## 0.2.4 - 2019-07-18 ## 0.2.4
- Update actix-server to 0.6 - Update actix-server to 0.6
## 0.2.3 - 2019-07-16 ## 0.2.3
- Add `delete`, `options`, `patch` methods to `TestServerRunner` - Add `delete`, `options`, `patch` methods to `TestServerRunner`
## 0.2.2 - 2019-06-16 ## 0.2.2
- Add .put() and .sput() methods - Add .put() and .sput() methods
## 0.2.1 - 2019-06-05 ## 0.2.1
- Add license files - Add license files
## 0.2.0 - 2019-05-12 ## 0.2.0
- Update awc and actix-http deps - Update awc and actix-http deps
## 0.1.1 - 2019-04-24 ## 0.1.1
- Always make new connection for http client - Always make new connection for http client
## 0.1.0 - 2019-04-16 ## 0.1.0
- No changes - No changes
## 0.1.0-alpha.3 - 2019-04-02 ## 0.1.0-alpha.3
- Request functions accept path #743 - Request functions accept path #743
## 0.1.0-alpha.2 - 2019-03-29 ## 0.1.0-alpha.2
- Added TestServerRuntime::load_body() method - Added TestServerRuntime::load_body() method
- Update actix-http and awc libraries - Update actix-http and awc libraries
## 0.1.0-alpha.1 - 2019-03-28 ## 0.1.0-alpha.1
- Initial impl - Initial impl

View file

@ -13,7 +13,7 @@ categories = [
"web-programming::websocket", "web-programming::websocket",
] ]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2021"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = [] features = []
@ -41,14 +41,13 @@ bytes = "1"
futures-core = { version = "0.3.17", default-features = false } futures-core = { version = "0.3.17", default-features = false }
http = "0.2.7" http = "0.2.7"
log = "0.4" log = "0.4"
socket2 = "0.4" socket2 = "0.5"
serde = "1" serde = "1"
serde_json = "1" serde_json = "1"
slab = "0.4" slab = "0.4"
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tls-openssl = { version = "0.10.55", package = "openssl", optional = true }
tokio = { version = "1.24.2", features = ["sync"] } tokio = { version = "1.24.2", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
actix-web = { version = "4", default-features = false, features = ["cookies"] }
actix-http = "3" actix-http = "3"

View file

@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.1.0)](https://docs.rs/actix-http-test/3.1.0) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.1.0)](https://docs.rs/actix-http-test/3.1.0)
![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
<br> <br>
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.1.0/status.svg)](https://deps.rs/crate/actix-http-test/3.1.0) [![Dependency Status](https://deps.rs/crate/actix-http-test/3.1.0/status.svg)](https://deps.rs/crate/actix-http-test/3.1.0)
@ -14,4 +14,4 @@
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http-test) - [API Documentation](https://docs.rs/actix-http-test)
- Minimum Supported Rust Version (MSRV): 1.59 - Minimum Supported Rust Version (MSRV): 1.68

View file

@ -31,24 +31,20 @@ use tokio::sync::mpsc;
/// for HTTP applications. /// for HTTP applications.
/// ///
/// # Examples /// # Examples
/// ```no_run ///
/// use actix_http::HttpService; /// ```
/// use actix_http::{HttpService, Response, Error, StatusCode};
/// use actix_http_test::test_server; /// use actix_http_test::test_server;
/// use actix_service::map_config; /// use actix_service::{fn_service, map_config, ServiceFactoryExt as _};
/// use actix_service::ServiceFactoryExt;
/// use actix_web::{dev::AppConfig, web, App, Error, HttpResponse};
/// ///
/// async fn my_handler() -> Result<HttpResponse, Error> { /// #[actix_rt::test]
/// Ok(HttpResponse::Ok().into()) /// # async fn hidden_test() {}
/// }
///
/// #[actix_web::test]
/// async fn test_example() { /// async fn test_example() {
/// let srv = test_server(|| { /// let srv = test_server(|| {
/// let app = App::new().service(web::resource("/").to(my_handler));
///
/// HttpService::build() /// HttpService::build()
/// .h1(map_config(app, |_| AppConfig::default())) /// .h1(fn_service(|req| async move {
/// Ok::<_, Error>(Response::ok())
/// }))
/// .tcp() /// .tcp()
/// .map_err(|_| ()) /// .map_err(|_| ())
/// }) /// })
@ -57,8 +53,9 @@ use tokio::sync::mpsc;
/// let req = srv.get("/"); /// let req = srv.get("/");
/// let response = req.send().await.unwrap(); /// let response = req.send().await.unwrap();
/// ///
/// assert!(response.status().is_success()); /// assert_eq!(response.status(), StatusCode::OK);
/// } /// }
/// # actix_rt::System::new().block_on(test_example());
/// ``` /// ```
pub async fn test_server<F: ServerServiceFactory<TcpStream>>(factory: F) -> TestServer { pub async fn test_server<F: ServerServiceFactory<TcpStream>>(factory: F) -> TestServer {
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();

View file

@ -1,23 +1,31 @@
# Changes # Changes
## Unreleased - 2023-xx-xx ## Unreleased
## 3.4.0
### Added ### Added
- Add `body::to_body_limit()` function. - 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. - Add `body::BodyLimitExceeded` error type.
### Changed
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
### Fixed ### Fixed
- Fix `MessageType::set_headers` not using the correct payload decoder when Transfer-Encoding and Content-Length are absent. - Fix `MessageType::set_headers` not using the correct payload decoder when Transfer-Encoding and Content-Length are absent.
## 3.3.1 - 2023-03-02 ## 3.3.1
### Fixed ### Fixed
- Use correct `http` version requirement to ensure support for const `HeaderName` definitions. - Use correct `http` version requirement to ensure support for const `HeaderName` definitions.
## 3.3.0 - 2023-01-21 ## 3.3.0
### Added ### Added
@ -53,7 +61,7 @@
[#2956]: https://github.com/actix/actix-web/pull/2956 [#2956]: https://github.com/actix/actix-web/pull/2956
[#2968]: https://github.com/actix/actix-web/pull/2968 [#2968]: https://github.com/actix/actix-web/pull/2968
## 3.2.2 - 2022-09-11 ## 3.2.2
### Changed ### Changed
@ -65,7 +73,7 @@
[#2369]: https://github.com/actix/actix-web/pull/2369 [#2369]: https://github.com/actix/actix-web/pull/2369
## 3.2.1 - 2022-07-02 ## 3.2.1
### Fixed ### Fixed
@ -73,7 +81,7 @@
[#2794]: https://github.com/actix/actix-web/pull/2794 [#2794]: https://github.com/actix/actix-web/pull/2794
## 3.2.0 - 2022-06-30 ## 3.2.0
### Changed ### Changed
@ -87,7 +95,7 @@
[#2790]: https://github.com/actix/actix-web/pull/2790 [#2790]: https://github.com/actix/actix-web/pull/2790
[#2798]: https://github.com/actix/actix-web/pull/2798 [#2798]: https://github.com/actix/actix-web/pull/2798
## 3.1.0 - 2022-06-11 ## 3.1.0
### Changed ### Changed
@ -101,13 +109,13 @@
[#2357]: https://github.com/actix/actix-web/issues/2357 [#2357]: https://github.com/actix/actix-web/issues/2357
[#2779]: https://github.com/actix/actix-web/pull/2779 [#2779]: https://github.com/actix/actix-web/pull/2779
## 3.0.4 - 2022-03-09 ## 3.0.4
### Fixed ### Fixed
- Document on docs.rs with `ws` feature enabled. - Document on docs.rs with `ws` feature enabled.
## 3.0.3 - 2022-03-08 ## 3.0.3
### Fixed ### Fixed
@ -115,7 +123,7 @@
[#2684]: https://github.com/actix/actix-web/pull/2684 [#2684]: https://github.com/actix/actix-web/pull/2684
## 3.0.2 - 2022-03-05 ## 3.0.2
### Fixed ### Fixed
@ -123,13 +131,13 @@
[#2683]: https://github.com/actix/actix-web/pull/2683 [#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] - Fix panic in H1 dispatcher when pipelining is used with keep-alive. [#2678]
[#2678]: https://github.com/actix/actix-web/issues/2678 [#2678]: https://github.com/actix/actix-web/issues/2678
## 3.0.0 - 2022-02-25 ## 3.0.0
### Dependencies ### Dependencies
@ -417,7 +425,7 @@
<details> <details>
<summary>3.0.0 Pre-Releases</summary> <summary>3.0.0 Pre-Releases</summary>
## 3.0.0-rc.4 - 2022-02-22 ## 3.0.0-rc.4
### Fixed ### Fixed
@ -425,11 +433,11 @@
[1ce58ecb]: https://github.com/actix/actix-web/commit/1ce58ecb305c60e51db06e6c913b7a1344e229ca [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`. - No significant changes since `3.0.0-rc.2`.
## 3.0.0-rc.2 - 2022-02-08 ## 3.0.0-rc.2
### Added ### Added
@ -446,7 +454,7 @@
[#2624]: https://github.com/actix/actix-web/pull/2624 [#2624]: https://github.com/actix/actix-web/pull/2624
[#2625]: https://github.com/actix/actix-web/pull/2625 [#2625]: https://github.com/actix/actix-web/pull/2625
## 3.0.0-rc.1 - 2022-01-31 ## 3.0.0-rc.1
### Added ### Added
@ -482,7 +490,7 @@
[#2611]: https://github.com/actix/actix-web/pull/2611 [#2611]: https://github.com/actix/actix-web/pull/2611
[#2618]: https://github.com/actix/actix-web/pull/2618 [#2618]: https://github.com/actix/actix-web/pull/2618
## 3.0.0-beta.19 - 2022-01-21 ## 3.0.0-beta.19
### Added ### Added
@ -502,7 +510,7 @@
[#2585]: https://github.com/actix/actix-web/pull/2585 [#2585]: https://github.com/actix/actix-web/pull/2585
[#2587]: https://github.com/actix/actix-web/pull/2587 [#2587]: https://github.com/actix/actix-web/pull/2587
## 3.0.0-beta.18 - 2022-01-04 ## 3.0.0-beta.18
### Added ### Added
@ -533,7 +541,7 @@
[#2501]: https://github.com/actix/actix-web/pull/2501 [#2501]: https://github.com/actix/actix-web/pull/2501
[#2565]: https://github.com/actix/actix-web/pull/2565 [#2565]: https://github.com/actix/actix-web/pull/2565
## 3.0.0-beta.17 - 2021-12-27 ## 3.0.0-beta.17
### Changed ### Changed
@ -551,7 +559,7 @@
[#2527]: https://github.com/actix/actix-web/pull/2527 [#2527]: https://github.com/actix/actix-web/pull/2527
[#2545]: https://github.com/actix/actix-web/pull/2545 [#2545]: https://github.com/actix/actix-web/pull/2545
## 3.0.0-beta.16 - 2021-12-17 ## 3.0.0-beta.16
### Added ### Added
@ -570,7 +578,7 @@
[#2510]: https://github.com/actix/actix-web/pull/2510 [#2510]: https://github.com/actix/actix-web/pull/2510
[#2522]: https://github.com/actix/actix-web/pull/2522 [#2522]: https://github.com/actix/actix-web/pull/2522
## 3.0.0-beta.15 - 2021-12-11 ## 3.0.0-beta.15
### Added ### Added
@ -624,7 +632,7 @@
[#2497]: https://github.com/actix/actix-web/pull/2497 [#2497]: https://github.com/actix/actix-web/pull/2497
[#2520]: https://github.com/actix/actix-web/pull/2520 [#2520]: https://github.com/actix/actix-web/pull/2520
## 3.0.0-beta.14 - 2021-11-30 ## 3.0.0-beta.14
### Changed ### Changed
@ -637,7 +645,7 @@
[#2470]: https://github.com/actix/actix-web/pull/2470 [#2470]: https://github.com/actix/actix-web/pull/2470
[#2474]: https://github.com/actix/actix-web/pull/2474 [#2474]: https://github.com/actix/actix-web/pull/2474
## 3.0.0-beta.13 - 2021-11-22 ## 3.0.0-beta.13
### Added ### Added
@ -666,7 +674,7 @@
[#2448]: https://github.com/actix/actix-web/pull/2448 [#2448]: https://github.com/actix/actix-web/pull/2448
[#2456]: https://github.com/actix/actix-web/pull/2456 [#2456]: https://github.com/actix/actix-web/pull/2456
## 3.0.0-beta.12 - 2021-11-15 ## 3.0.0-beta.12
### Changed ### Changed
@ -680,7 +688,7 @@
[#2425]: https://github.com/actix/actix-web/pull/2425 [#2425]: https://github.com/actix/actix-web/pull/2425
[#2442]: https://github.com/actix/actix-web/pull/2442 [#2442]: https://github.com/actix/actix-web/pull/2442
## 3.0.0-beta.11 - 2021-10-20 ## 3.0.0-beta.11
### Changed ### Changed
@ -689,7 +697,7 @@
[#2414]: https://github.com/actix/actix-web/pull/2414 [#2414]: https://github.com/actix/actix-web/pull/2414
## 3.0.0-beta.10 - 2021-09-09 ## 3.0.0-beta.10
### Changed ### Changed
@ -707,13 +715,13 @@
[#2344]: https://github.com/actix/actix-web/pull/2344 [#2344]: https://github.com/actix/actix-web/pull/2344
[#2377]: https://github.com/actix/actix-web/pull/2377 [#2377]: https://github.com/actix/actix-web/pull/2377
## 3.0.0-beta.9 - 2021-08-09 ## 3.0.0-beta.9
### Fixed ### Fixed
- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) - 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 ### Changed
@ -726,7 +734,7 @@
[#2291]: https://github.com/actix/actix-web/pull/2291 [#2291]: https://github.com/actix/actix-web/pull/2291
[#2250]: https://github.com/actix/actix-web/pull/2250 [#2250]: https://github.com/actix/actix-web/pull/2250
## 3.0.0-beta.7 - 2021-06-17 ## 3.0.0-beta.7
### Added ### Added
@ -773,7 +781,7 @@
[#2253]: https://github.com/actix/actix-web/pull/2253 [#2253]: https://github.com/actix/actix-web/pull/2253
[#2244]: https://github.com/actix/actix-web/pull/2244 [#2244]: https://github.com/actix/actix-web/pull/2244
## 3.0.0-beta.6 - 2021-04-17 ## 3.0.0-beta.6
### Added ### Added
@ -808,7 +816,7 @@
[#2158]: https://github.com/actix/actix-web/pull/2158 [#2158]: https://github.com/actix/actix-web/pull/2158
[#2161]: https://github.com/actix/actix-web/pull/2161 [#2161]: https://github.com/actix/actix-web/pull/2161
## 3.0.0-beta.5 - 2021-04-02 ## 3.0.0-beta.5
### Added ### Added
@ -830,7 +838,7 @@
[#2094]: https://github.com/actix/actix-web/pull/2094 [#2094]: https://github.com/actix/actix-web/pull/2094
[#2127]: https://github.com/actix/actix-web/pull/2127 [#2127]: https://github.com/actix/actix-web/pull/2127
## 3.0.0-beta.4 - 2021-03-08 ## 3.0.0-beta.4
### Changed ### Changed
@ -848,11 +856,11 @@
[#2035]: https://github.com/actix/actix-web/pull/2035 [#2035]: https://github.com/actix/actix-web/pull/2035
[#2052]: https://github.com/actix/actix-web/pull/2052 [#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. - No notable changes.
## 3.0.0-beta.2 - 2021-02-10 ## 3.0.0-beta.2
### Added ### Added
@ -904,7 +912,7 @@
[#1964]: https://github.com/actix/actix-web/pull/1964 [#1964]: https://github.com/actix/actix-web/pull/1964
[#1969]: https://github.com/actix/actix-web/pull/1969 [#1969]: https://github.com/actix/actix-web/pull/1969
## 3.0.0-beta.1 - 2021-01-07 ## 3.0.0-beta.1
### Added ### Added
@ -932,7 +940,7 @@
</details> </details>
## 2.2.2 - 2022-01-21 ## 2.2.2
### Changed ### Changed
@ -940,13 +948,13 @@
[ad7e3c06]: https://github.com/actix/actix-web/commit/ad7e3c06 [ad7e3c06]: https://github.com/actix/actix-web/commit/ad7e3c06
## 2.2.1 - 2021-08-09 ## 2.2.1
### Fixed ### Fixed
- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977) - 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 ### Added
@ -968,7 +976,7 @@
[#1793]: https://github.com/actix/actix-web/pull/1793 [#1793]: https://github.com/actix/actix-web/pull/1793
[#1797]: https://github.com/actix/actix-web/pull/1797 [#1797]: https://github.com/actix/actix-web/pull/1797
## 2.1.0 - 2020-10-30 ## 2.1.0
### Added ### Added
@ -985,18 +993,18 @@
[#1733]: https://github.com/actix/actix-web/pull/1733 [#1733]: https://github.com/actix/actix-web/pull/1733
[#1744]: https://github.com/actix/actix-web/pull/1744 [#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`. - No significant changes from `2.0.0-beta.4`.
## 2.0.0-beta.4 - 2020-09-09 ## 2.0.0-beta.4
### Changed ### Changed
- Update actix-codec and actix-utils dependencies. - Update actix-codec and actix-utils dependencies.
- Update actix-connect and actix-tls dependencies. - Update actix-connect and actix-tls dependencies.
## 2.0.0-beta.3 - 2020-08-14 ## 2.0.0-beta.3
### Fixed ### Fixed
@ -1004,7 +1012,7 @@
[#1626]: https://github.com/actix/actix-web/pull/1626 [#1626]: https://github.com/actix/actix-web/pull/1626
## 2.0.0-beta.2 - 2020-07-21 ## 2.0.0-beta.2
### Fixed ### Fixed
@ -1017,7 +1025,7 @@
[#1614]: https://github.com/actix/actix-web/pull/1614 [#1614]: https://github.com/actix/actix-web/pull/1614
[#1615]: https://github.com/actix/actix-web/pull/1615 [#1615]: https://github.com/actix/actix-web/pull/1615
## 2.0.0-beta.1 - 2020-07-11 ## 2.0.0-beta.1
### Changed ### Changed
@ -1030,7 +1038,7 @@
[#1586]: https://github.com/actix/actix-web/pull/1586 [#1586]: https://github.com/actix/actix-web/pull/1586
[#1580]: https://github.com/actix/actix-web/pull/1580 [#1580]: https://github.com/actix/actix-web/pull/1580
## 2.0.0-alpha.4 - 2020-05-21 ## 2.0.0-alpha.4
### Changed ### Changed
@ -1046,7 +1054,7 @@
[#1439]: https://github.com/actix/actix-web/pull/1439 [#1439]: https://github.com/actix/actix-web/pull/1439
[#1503]: https://github.com/actix/actix-web/pull/1503 [#1503]: https://github.com/actix/actix-web/pull/1503
## 2.0.0-alpha.3 - 2020-05-08 ## 2.0.0-alpha.3
### Fixed ### Fixed
@ -1061,7 +1069,7 @@
[#1422]: https://github.com/actix/actix-web/pull/1422 [#1422]: https://github.com/actix/actix-web/pull/1422
[#1487]: https://github.com/actix/actix-web/pull/1487 [#1487]: https://github.com/actix/actix-web/pull/1487
## 2.0.0-alpha.2 - 2020-03-07 ## 2.0.0-alpha.2
### Changed ### Changed
@ -1073,7 +1081,7 @@
[#1394]: https://github.com/actix/actix-web/pull/1394 [#1394]: https://github.com/actix/actix-web/pull/1394
[#1395]: https://github.com/actix/actix-web/pull/1395 [#1395]: https://github.com/actix/actix-web/pull/1395
## 2.0.0-alpha.1 - 2020-02-27 ## 2.0.0-alpha.1
### Changed ### Changed
@ -1086,14 +1094,14 @@
- Allow `SameSite=None` cookies to be sent in a response. - Allow `SameSite=None` cookies to be sent in a response.
## 1.0.1 - 2019-12-20 ## 1.0.1
### Fixed ### Fixed
- Poll upgrade service's readiness from HTTP service handlers - Poll upgrade service's readiness from HTTP service handlers
- Replace brotli with brotli2 #1224 - Replace brotli with brotli2 #1224
## 1.0.0 - 2019-12-13 ## 1.0.0
### Added ### Added
@ -1103,7 +1111,7 @@
- Replace `flate2-xxx` features with `compress` - Replace `flate2-xxx` features with `compress`
## 1.0.0-alpha.5 - 2019-12-09 ## 1.0.0-alpha.5
### Fixed ### Fixed
@ -1114,7 +1122,7 @@
- Websockets: Ping and Pong should have binary data #1049 - Websockets: Ping and Pong should have binary data #1049
## 1.0.0-alpha.4 - 2019-12-08 ## 1.0.0-alpha.4
### Added ### Added
@ -1124,14 +1132,14 @@
- Use rust based brotli compression library - Use rust based brotli compression library
## 1.0.0-alpha.3 - 2019-12-07 ## 1.0.0-alpha.3
### Changed ### Changed
- Migrate to tokio 0.2 - Migrate to tokio 0.2
- Migrate to `std::future` - Migrate to `std::future`
## 0.2.11 - 2019-11-06 ## 0.2.11
### Added ### Added
@ -1145,7 +1153,7 @@
[#1878]: https://github.com/actix/actix-web/pull/1878 [#1878]: https://github.com/actix/actix-web/pull/1878
## 0.2.10 - 2019-09-11 ## 0.2.10
### Added ### Added
@ -1156,7 +1164,7 @@
- h2 will use error response #1080 - h2 will use error response #1080
- on_connect result isn't added to request extensions for http2 requests #1009 - on_connect result isn't added to request extensions for http2 requests #1009
## 0.2.9 - 2019-08-13 ## 0.2.9
### Changed ### Changed
@ -1168,7 +1176,7 @@
- Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) - Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031)
## 0.2.8 - 2019-08-01 ## 0.2.8
### Added ### Added
@ -1180,20 +1188,20 @@
- awc client panic #1016 - awc client panic #1016
- Invalid response with compression middleware enabled, but compression-related features disabled #997 - Invalid response with compression middleware enabled, but compression-related features disabled #997
## 0.2.7 - 2019-07-18 ## 0.2.7
### Added ### Added
- Add support for downcasting response errors #986 - Add support for downcasting response errors #986
## 0.2.6 - 2019-07-17 ## 0.2.6
### Changed ### Changed
- Replace `ClonableService` with local copy - Replace `ClonableService` with local copy
- Upgrade `rand` dependency version to 0.7 - Upgrade `rand` dependency version to 0.7
## 0.2.5 - 2019-06-28 ## 0.2.5
### Added ### Added
@ -1204,13 +1212,13 @@
- Use `encoding_rs` crate instead of unmaintained `encoding` crate - Use `encoding_rs` crate instead of unmaintained `encoding` crate
- Add `Copy` and `Clone` impls for `ws::Codec` - Add `Copy` and `Clone` impls for `ws::Codec`
## 0.2.4 - 2019-06-16 ## 0.2.4
### Fixed ### Fixed
- Do not compress NoContent (204) responses #918 - Do not compress NoContent (204) responses #918
## 0.2.3 - 2019-06-02 ## 0.2.3
### Added ### Added
@ -1221,19 +1229,19 @@
- SizedStream uses u64 - SizedStream uses u64
## 0.2.2 - 2019-05-29 ## 0.2.2
### Fixed ### Fixed
- Parse incoming stream before closing stream on disconnect #868 - Parse incoming stream before closing stream on disconnect #868
## 0.2.1 - 2019-05-25 ## 0.2.1
### Fixed ### Fixed
- Handle socket read disconnect - Handle socket read disconnect
## 0.2.0 - 2019-05-12 ## 0.2.0
### Changed ### Changed
@ -1244,13 +1252,13 @@
- `OneRequest` service - `OneRequest` service
## 0.1.5 - 2019-05-04 ## 0.1.5
### Fixed ### Fixed
- Clean up response extensions in response pool #817 - Clean up response extensions in response pool #817
## 0.1.4 - 2019-04-24 ## 0.1.4
### Added ### Added
@ -1260,20 +1268,20 @@
- Read until eof for http/1.0 responses #771 - Read until eof for http/1.0 responses #771
## 0.1.3 - 2019-04-23 ## 0.1.3
### Fixed ### Fixed
- Fix http client pool management - Fix http client pool management
- Fix http client wait queue management #794 - Fix http client wait queue management #794
## 0.1.2 - 2019-04-23 ## 0.1.2
### Fixed ### Fixed
- Fix BorrowMutError panic in client connector #793 - Fix BorrowMutError panic in client connector #793
## 0.1.1 - 2019-04-19 ## 0.1.1
### Changed ### Changed
@ -1281,7 +1289,7 @@
- Cookie::max_age_time() accepts value in time::Duration - Cookie::max_age_time() accepts value in time::Duration
- Allow to specify server address for client connector - Allow to specify server address for client connector
## 0.1.0 - 2019-04-16 ## 0.1.0
### Added ### Added
@ -1292,7 +1300,7 @@
- `actix_http::encoding` always available - `actix_http::encoding` always available
- use trust-dns-resolver 0.11.0 - use trust-dns-resolver 0.11.0
## 0.1.0-alpha.5 - 2019-04-12 ## 0.1.0-alpha.5
### Added ### Added
@ -1304,7 +1312,7 @@
- MessageBody::length() renamed to MessageBody::size() for consistency - MessageBody::length() renamed to MessageBody::size() for consistency
- ws handshake verification functions take RequestHead instead of Request - ws handshake verification functions take RequestHead instead of Request
## 0.1.0-alpha.4 - 2019-04-08 ## 0.1.0-alpha.4
### Added ### Added
@ -1321,7 +1329,7 @@
- Removed PayloadBuffer - Removed PayloadBuffer
## 0.1.0-alpha.3 - 2019-04-02 ## 0.1.0-alpha.3
### Added ### Added
@ -1333,7 +1341,7 @@
- Preallocate read buffer for h1 codec - Preallocate read buffer for h1 codec
- Detect socket disconnection during protocol selection - Detect socket disconnection during protocol selection
## 0.1.0-alpha.2 - 2019-03-29 ## 0.1.0-alpha.2
### Added ### Added
@ -1343,6 +1351,6 @@
- Do not use thread pool for decompression if chunk size is smaller than 2048. - 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 - Initial impl

View file

@ -1,6 +1,6 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "3.3.1" version = "3.4.0"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>", "Rob Ede <robjtede@icloud.com>",
@ -15,12 +15,13 @@ categories = [
"web-programming::http-server", "web-programming::http-server",
"web-programming::websocket", "web-programming::websocket",
] ]
license = "MIT OR Apache-2.0" license.workspace = true
edition = "2018" edition.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs] [package.metadata.docs.rs]
# features that docs.rs will build with # features that docs.rs will build with
features = ["http2", "ws", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"] features = ["http2", "ws", "openssl", "rustls-0_20", "rustls-0_21", "compress-brotli", "compress-gzip", "compress-zstd"]
[lib] [lib]
name = "actix_http" name = "actix_http"
@ -43,15 +44,21 @@ ws = [
# TLS via OpenSSL # TLS via OpenSSL
openssl = ["actix-tls/accept", "actix-tls/openssl"] openssl = ["actix-tls/accept", "actix-tls/openssl"]
# TLS via Rustls # TLS via Rustls v0.20
rustls = ["actix-tls/accept", "actix-tls/rustls"] 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"]
# Compression codecs # Compression codecs
compress-brotli = ["__compress", "brotli"] compress-brotli = ["__compress", "brotli"]
compress-gzip = ["__compress", "flate2"] compress-gzip = ["__compress", "flate2"]
compress-zstd = ["__compress", "zstd"] 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. # Don't rely on these whatsoever. They are semver-exempt and may disappear at anytime.
__compress = [] __compress = []
@ -91,7 +98,7 @@ rand = { version = "0.8", optional = true }
sha1 = { version = "0.10", optional = true } sha1 = { version = "0.10", optional = true }
# openssl/rustls # openssl/rustls
actix-tls = { version = "3", default-features = false, optional = true } actix-tls = { version = "3.1", default-features = false, optional = true }
# compress-* # compress-*
brotli = { version = "3.3.3", optional = true } brotli = { version = "3.3.3", optional = true }
@ -101,29 +108,33 @@ zstd = { version = "0.12", optional = true }
[dev-dependencies] [dev-dependencies]
actix-http-test = { version = "3", features = ["openssl"] } actix-http-test = { version = "3", features = ["openssl"] }
actix-server = "2" actix-server = "2"
actix-tls = { version = "3", features = ["openssl"] } actix-tls = { version = "3.1", features = ["openssl"] }
actix-web = "4" actix-web = "4"
async-stream = "0.3" async-stream = "0.3"
criterion = { version = "0.4", features = ["html_reports"] } criterion = { version = "0.5", features = ["html_reports"] }
env_logger = "0.9" env_logger = "0.10"
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
memchr = "2.4" memchr = "2.4"
once_cell = "1.9" once_cell = "1.9"
rcgen = "0.9" rcgen = "0.11"
regex = "1.3" regex = "1.3"
rustversion = "1" rustversion = "1"
rustls-pemfile = "1" rustls-pemfile = "1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
static_assertions = "1" static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.9" } tls-openssl = { package = "openssl", version = "0.10.55" }
tls-rustls = { package = "rustls", version = "0.20.0" } tls-rustls_021 = { package = "rustls", version = "0.21" }
tokio = { version = "1.24.2", features = ["net", "rt", "macros"] } tokio = { version = "1.24.2", features = ["net", "rt", "macros"] }
[[example]] [[example]]
name = "ws" name = "ws"
required-features = ["ws", "rustls"] required-features = ["ws", "rustls-0_21"]
[[example]]
name = "tls_rustls"
required-features = ["http2", "rustls-0_21"]
[[bench]] [[bench]]
name = "response-body-compression" name = "response-body-compression"

View file

@ -3,18 +3,18 @@
> HTTP primitives for the Actix ecosystem. > HTTP primitives for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.3.1)](https://docs.rs/actix-http/3.3.1) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.4.0)](https://docs.rs/actix-http/3.4.0)
![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-http/3.3.1/status.svg)](https://deps.rs/crate/actix-http/3.3.1) [![dependency status](https://deps.rs/crate/actix-http/3.4.0/status.svg)](https://deps.rs/crate/actix-http/3.4.0)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http) - [API Documentation](https://docs.rs/actix-http)
- Minimum Supported Rust Version (MSRV): 1.59 - Minimum Supported Rust Version (MSRV): 1.68
## Example ## Example

View file

@ -23,10 +23,7 @@ async fn main() -> io::Result<()> {
res.insert_header(("x-head", HeaderValue::from_static("dummy value!"))); res.insert_header(("x-head", HeaderValue::from_static("dummy value!")));
let forty_two = req.conn_data::<u32>().unwrap().to_string(); let forty_two = req.conn_data::<u32>().unwrap().to_string();
res.insert_header(( res.insert_header(("x-forty-two", HeaderValue::from_str(&forty_two).unwrap()));
"x-forty-two",
HeaderValue::from_str(&forty_two).unwrap(),
));
Ok::<_, Infallible>(res.body("Hello world!")) Ok::<_, Infallible>(res.body("Hello world!"))
}) })

View file

@ -0,0 +1,73 @@
//! 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_021 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_021(rustls_config())
})?
.run()
.await
}
fn rustls_config() -> rustls::ServerConfig {
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 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)
.unwrap()
.into_iter()
.map(rustls::Certificate)
.collect();
let mut keys = rustls_pemfile::pkcs8_private_keys(key_file).unwrap();
let mut config = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(cert_chain, rustls::PrivateKey(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
}

View file

@ -1,7 +1,7 @@
//! Sets up a WebSocket server over TCP and TLS. //! Sets up a WebSocket server over TCP and TLS.
//! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames. //! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames.
extern crate tls_rustls as rustls; extern crate tls_rustls_021 as rustls;
use std::{ use std::{
io, io,
@ -28,7 +28,9 @@ async fn main() -> io::Result<()> {
HttpService::build().h1(handler).tcp() HttpService::build().h1(handler).tcp()
})? })?
.bind("tls", ("127.0.0.1", 8443), || { .bind("tls", ("127.0.0.1", 8443), || {
HttpService::build().finish(handler).rustls(tls_config()) HttpService::build()
.finish(handler)
.rustls_021(tls_config())
})? })?
.run() .run()
.await .await

View file

@ -47,9 +47,8 @@ where
/// Attempts to pull out the next value of the underlying [`Stream`]. /// Attempts to pull out the next value of the underlying [`Stream`].
/// ///
/// Empty values are skipped to prevent [`BodyStream`]'s transmission being /// Empty values are skipped to prevent [`BodyStream`]'s transmission being ended on a
/// ended on a zero-length chunk, but rather proceed until the underlying /// zero-length chunk, but rather proceed until the underlying [`Stream`] ends.
/// [`Stream`] ends.
fn poll_next( fn poll_next(
mut self: Pin<&mut Self>, mut self: Pin<&mut Self>,
cx: &mut Context<'_>, cx: &mut Context<'_>,

View file

@ -77,12 +77,8 @@ impl MessageBody for BoxBody {
cx: &mut Context<'_>, cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> { ) -> Poll<Option<Result<Bytes, Self::Error>>> {
match &mut self.0 { match &mut self.0 {
BoxBodyInner::None(body) => { BoxBodyInner::None(body) => Pin::new(body).poll_next(cx).map_err(|err| match err {}),
Pin::new(body).poll_next(cx).map_err(|err| match err {}) BoxBodyInner::Bytes(body) => Pin::new(body).poll_next(cx).map_err(|err| match err {}),
}
BoxBodyInner::Bytes(body) => {
Pin::new(body).poll_next(cx).map_err(|err| match err {})
}
BoxBodyInner::Stream(body) => Pin::new(body).poll_next(cx), BoxBodyInner::Stream(body) => Pin::new(body).poll_next(cx),
} }
} }
@ -104,7 +100,6 @@ impl MessageBody for BoxBody {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use static_assertions::{assert_impl_all, assert_not_impl_any}; use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*; use super::*;

View file

@ -555,6 +555,7 @@ mod tests {
}; };
} }
#[allow(unused_allocation)] // triggered by `Box::new(()).size()`
#[actix_rt::test] #[actix_rt::test]
async fn boxing_equivalence() { async fn boxing_equivalence() {
assert_eq!(().size(), BodySize::Sized(0)); assert_eq!(().size(), BodySize::Sized(0));

View file

@ -14,12 +14,14 @@ mod size;
mod sized_stream; mod sized_stream;
mod utils; mod utils;
pub use self::body_stream::BodyStream;
pub use self::boxed::BoxBody;
pub use self::either::EitherBody;
pub use self::message_body::MessageBody;
pub(crate) use self::message_body::MessageBodyMapErr; pub(crate) use self::message_body::MessageBodyMapErr;
pub use self::none::None; pub use self::{
pub use self::size::BodySize; body_stream::BodyStream,
pub use self::sized_stream::SizedStream; boxed::BoxBody,
pub use self::utils::{to_bytes, to_bytes_limited, BodyLimitExceeded}; either::EitherBody,
message_body::MessageBody,
none::None,
size::BodySize,
sized_stream::SizedStream,
utils::{to_bytes, to_bytes_limited, BodyLimitExceeded},
};

View file

@ -132,15 +132,15 @@ impl ServiceConfig {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use crate::{date::DATE_VALUE_LENGTH, notify_on_drop};
use actix_rt::{ use actix_rt::{
task::yield_now, task::yield_now,
time::{sleep, sleep_until}, time::{sleep, sleep_until},
}; };
use memchr::memmem; use memchr::memmem;
use super::*;
use crate::{date::DATE_VALUE_LENGTH, notify_on_drop};
#[actix_rt::test] #[actix_rt::test]
async fn test_date_service_update() { async fn test_date_service_update() {
let settings = let settings =

View file

@ -9,11 +9,9 @@ use std::{
use actix_rt::task::{spawn_blocking, JoinHandle}; use actix_rt::task::{spawn_blocking, JoinHandle};
use bytes::Bytes; use bytes::Bytes;
use futures_core::{ready, Stream};
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
use flate2::write::{GzDecoder, ZlibDecoder}; use flate2::write::{GzDecoder, ZlibDecoder};
use futures_core::{ready, Stream};
#[cfg(feature = "compress-zstd")] #[cfg(feature = "compress-zstd")]
use zstd::stream::write::Decoder as ZstdDecoder; use zstd::stream::write::Decoder as ZstdDecoder;
@ -49,9 +47,9 @@ where
))), ))),
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new( ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(ZlibDecoder::new(
ZlibDecoder::new(Writer::new()), Writer::new(),
))), )))),
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new( ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new(
@ -193,7 +191,7 @@ impl ContentDecoder {
Ok(None) Ok(None)
} }
} }
Err(e) => Err(e), Err(err) => Err(err),
}, },
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
@ -207,7 +205,7 @@ impl ContentDecoder {
Ok(None) Ok(None)
} }
} }
Err(e) => Err(e), Err(err) => Err(err),
}, },
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
@ -220,7 +218,7 @@ impl ContentDecoder {
Ok(None) Ok(None)
} }
} }
Err(e) => Err(e), Err(err) => Err(err),
}, },
#[cfg(feature = "compress-zstd")] #[cfg(feature = "compress-zstd")]
@ -233,7 +231,7 @@ impl ContentDecoder {
Ok(None) Ok(None)
} }
} }
Err(e) => Err(e), Err(err) => Err(err),
}, },
} }
} }
@ -252,7 +250,7 @@ impl ContentDecoder {
Ok(None) Ok(None)
} }
} }
Err(e) => Err(e), Err(err) => Err(err),
}, },
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
@ -267,7 +265,7 @@ impl ContentDecoder {
Ok(None) Ok(None)
} }
} }
Err(e) => Err(e), Err(err) => Err(err),
}, },
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
@ -282,7 +280,7 @@ impl ContentDecoder {
Ok(None) Ok(None)
} }
} }
Err(e) => Err(e), Err(err) => Err(err),
}, },
#[cfg(feature = "compress-zstd")] #[cfg(feature = "compress-zstd")]
@ -297,7 +295,7 @@ impl ContentDecoder {
Ok(None) Ok(None)
} }
} }
Err(e) => Err(e), Err(err) => Err(err),
}, },
} }
} }

View file

@ -11,12 +11,10 @@ use std::{
use actix_rt::task::{spawn_blocking, JoinHandle}; use actix_rt::task::{spawn_blocking, JoinHandle};
use bytes::Bytes; use bytes::Bytes;
use derive_more::Display; use derive_more::Display;
use futures_core::ready;
use pin_project_lite::pin_project;
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
use flate2::write::{GzEncoder, ZlibEncoder}; use flate2::write::{GzEncoder, ZlibEncoder};
use futures_core::ready;
use pin_project_lite::pin_project;
use tracing::trace; use tracing::trace;
#[cfg(feature = "compress-zstd")] #[cfg(feature = "compress-zstd")]
use zstd::stream::write::Encoder as ZstdEncoder; use zstd::stream::write::Encoder as ZstdEncoder;

View file

@ -7,13 +7,12 @@ use bytes::{Bytes, BytesMut};
mod decoder; mod decoder;
mod encoder; mod encoder;
pub use self::decoder::Decoder; pub use self::{decoder::Decoder, encoder::Encoder};
pub use self::encoder::Encoder;
/// Special-purpose writer for streaming (de-)compression. /// Special-purpose writer for streaming (de-)compression.
/// ///
/// Pre-allocates 8KiB of capacity. /// Pre-allocates 8KiB of capacity.
pub(self) struct Writer { struct Writer {
buf: BytesMut, buf: BytesMut,
} }

View file

@ -3,12 +3,11 @@
use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error}; use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};
use derive_more::{Display, Error, From}; use derive_more::{Display, Error, From};
pub use http::Error as HttpError;
use http::{uri::InvalidUri, StatusCode}; use http::{uri::InvalidUri, StatusCode};
use crate::{body::BoxBody, Response}; use crate::{body::BoxBody, Response};
pub use http::Error as HttpError;
pub struct Error { pub struct Error {
inner: Box<ErrorInner>, inner: Box<ErrorInner>,
} }

View file

@ -9,9 +9,7 @@ use super::{
decoder::{self, PayloadDecoder, PayloadItem, PayloadType}, decoder::{self, PayloadDecoder, PayloadItem, PayloadType},
encoder, Message, MessageType, encoder, Message, MessageType,
}; };
use crate::{ use crate::{body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig};
body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig,
};
bitflags! { bitflags! {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]

View file

@ -1,4 +1,4 @@
use std::{convert::TryFrom, io, marker::PhantomData, mem::MaybeUninit, task::Poll}; use std::{io, marker::PhantomData, mem::MaybeUninit, task::Poll};
use actix_codec::Decoder; use actix_codec::Decoder;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
@ -94,9 +94,7 @@ pub(crate) trait MessageType: Sized {
// SAFETY: httparse already checks header value is only visible ASCII bytes // SAFETY: httparse already checks header value is only visible ASCII bytes
// from_maybe_shared_unchecked contains debug assertions so they are omitted here // from_maybe_shared_unchecked contains debug assertions so they are omitted here
let value = unsafe { let value = unsafe {
HeaderValue::from_maybe_shared_unchecked( HeaderValue::from_maybe_shared_unchecked(slice.slice(idx.value.0..idx.value.1))
slice.slice(idx.value.0..idx.value.1),
)
}; };
match name { match name {
@ -275,8 +273,7 @@ impl MessageType for Request {
let mut msg = Request::new(); let mut msg = Request::new();
// convert headers // convert headers
let mut length = let mut length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?;
msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?;
// disallow HTTP/1.0 POST requests that do not contain a Content-Length headers // disallow HTTP/1.0 POST requests that do not contain a Content-Length headers
// see https://datatracker.ietf.org/doc/html/rfc1945#section-7.2.2 // see https://datatracker.ietf.org/doc/html/rfc1945#section-7.2.2
@ -356,8 +353,8 @@ impl MessageType for ResponseHead {
Version::HTTP_10 Version::HTTP_10
}; };
let status = StatusCode::from_u16(res.code.unwrap()) let status =
.map_err(|_| ParseError::Status)?; StatusCode::from_u16(res.code.unwrap()).map_err(|_| ParseError::Status)?;
HeaderIndex::record(src, res.headers, &mut headers); HeaderIndex::record(src, res.headers, &mut headers);
(len, version, status, res.headers.len()) (len, version, status, res.headers.len())
@ -378,8 +375,7 @@ impl MessageType for ResponseHead {
msg.version = ver; msg.version = ver;
// convert headers // convert headers
let mut length = let mut length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?;
msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?;
// Remove CL value if 0 now that all headers and HTTP/1.0 special cases are processed. // Remove CL value if 0 now that all headers and HTTP/1.0 special cases are processed.
// Protects against some request smuggling attacks. // Protects against some request smuggling attacks.
@ -548,7 +544,7 @@ impl Decoder for PayloadDecoder {
*state = match state.step(src, size, &mut buf) { *state = match state.step(src, size, &mut buf) {
Poll::Pending => return Ok(None), Poll::Pending => return Ok(None),
Poll::Ready(Ok(state)) => state, Poll::Ready(Ok(state)) => state,
Poll::Ready(Err(e)) => return Err(e), Poll::Ready(Err(err)) => return Err(err),
}; };
if *state == ChunkedState::End { if *state == ChunkedState::End {

View file

@ -19,14 +19,6 @@ use tokio::io::{AsyncRead, AsyncWrite};
use tokio_util::codec::{Decoder as _, Encoder as _}; use tokio_util::codec::{Decoder as _, Encoder as _};
use tracing::{error, trace}; use tracing::{error, trace};
use crate::{
body::{BodySize, BoxBody, MessageBody},
config::ServiceConfig,
error::{DispatchError, ParseError, PayloadError},
service::HttpFlow,
Error, Extensions, OnConnectData, Request, Response, StatusCode,
};
use super::{ use super::{
codec::Codec, codec::Codec,
decoder::MAX_BUFFER_SIZE, decoder::MAX_BUFFER_SIZE,
@ -34,6 +26,13 @@ use super::{
timer::TimerState, timer::TimerState,
Message, MessageType, Message, MessageType,
}; };
use crate::{
body::{BodySize, BoxBody, MessageBody},
config::ServiceConfig,
error::{DispatchError, ParseError, PayloadError},
service::HttpFlow,
Error, Extensions, OnConnectData, Request, Response, StatusCode,
};
const LW_BUFFER_SIZE: usize = 1024; const LW_BUFFER_SIZE: usize = 1024;
const HW_BUFFER_SIZE: usize = 1024 * 8; const HW_BUFFER_SIZE: usize = 1024 * 8;
@ -213,9 +212,7 @@ where
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::None => write!(f, "State::None"), Self::None => write!(f, "State::None"),
Self::ExpectCall { .. } => { Self::ExpectCall { .. } => f.debug_struct("State::ExpectCall").finish_non_exhaustive(),
f.debug_struct("State::ExpectCall").finish_non_exhaustive()
}
Self::ServiceCall { .. } => { Self::ServiceCall { .. } => {
f.debug_struct("State::ServiceCall").finish_non_exhaustive() f.debug_struct("State::ServiceCall").finish_non_exhaustive()
} }
@ -276,9 +273,7 @@ where
head_timer: TimerState::new(config.client_request_deadline().is_some()), head_timer: TimerState::new(config.client_request_deadline().is_some()),
ka_timer: TimerState::new(config.keep_alive().enabled()), ka_timer: TimerState::new(config.keep_alive().enabled()),
shutdown_timer: TimerState::new( shutdown_timer: TimerState::new(config.client_disconnect_deadline().is_some()),
config.client_disconnect_deadline().is_some(),
),
io: Some(io), io: Some(io),
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
@ -456,9 +451,7 @@ where
} }
// return with upgrade request and poll it exclusively // return with upgrade request and poll it exclusively
Some(DispatcherMessage::Upgrade(req)) => { Some(DispatcherMessage::Upgrade(req)) => return Ok(PollResponse::Upgrade(req)),
return Ok(PollResponse::Upgrade(req))
}
// all messages are dealt with // all messages are dealt with
None => { None => {
@ -675,9 +668,7 @@ where
} }
_ => { _ => {
unreachable!( unreachable!("State must be set to ServiceCall or ExceptCall in handle_request")
"State must be set to ServiceCall or ExceptCall in handle_request"
)
} }
} }
} }
@ -686,10 +677,7 @@ where
/// Process one incoming request. /// Process one incoming request.
/// ///
/// Returns true if any meaningful work was done. /// Returns true if any meaningful work was done.
fn poll_request( fn poll_request(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Result<bool, DispatchError> {
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Result<bool, DispatchError> {
let pipeline_queue_full = self.messages.len() >= MAX_PIPELINED_MESSAGES; let pipeline_queue_full = self.messages.len() >= MAX_PIPELINED_MESSAGES;
let can_not_read = !self.can_read(cx); let can_not_read = !self.can_read(cx);
@ -859,10 +847,7 @@ where
Ok(()) Ok(())
} }
fn poll_ka_timer( fn poll_ka_timer(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Result<(), DispatchError> {
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Result<(), DispatchError> {
let this = self.as_mut().project(); let this = self.as_mut().project();
if let TimerState::Active { timer } = this.ka_timer { if let TimerState::Active { timer } = this.ka_timer {
debug_assert!( debug_assert!(
@ -927,10 +912,7 @@ where
} }
/// Poll head, keep-alive, and disconnect timer. /// Poll head, keep-alive, and disconnect timer.
fn poll_timers( fn poll_timers(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Result<(), DispatchError> {
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Result<(), DispatchError> {
self.as_mut().poll_head_timer(cx)?; self.as_mut().poll_head_timer(cx)?;
self.as_mut().poll_ka_timer(cx)?; self.as_mut().poll_ka_timer(cx)?;
self.as_mut().poll_shutdown_timer(cx)?; self.as_mut().poll_shutdown_timer(cx)?;
@ -944,10 +926,7 @@ where
/// - `std::io::ErrorKind::ConnectionReset` after partial read; /// - `std::io::ErrorKind::ConnectionReset` after partial read;
/// - all data read done. /// - all data read done.
#[inline(always)] // TODO: bench this inline #[inline(always)] // TODO: bench this inline
fn read_available( fn read_available(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Result<bool, DispatchError> {
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Result<bool, DispatchError> {
let this = self.project(); let this = self.project();
if this.flags.contains(Flags::READ_DISCONNECT) { if this.flags.contains(Flags::READ_DISCONNECT) {

View file

@ -1,14 +1,11 @@
use std::{future::Future, str, task::Poll, time::Duration}; use std::{future::Future, str, task::Poll, time::Duration};
use actix_rt::{pin, time::sleep};
use actix_service::fn_service;
use actix_utils::future::{ready, Ready};
use bytes::Bytes;
use futures_util::future::lazy;
use actix_codec::Framed; use actix_codec::Framed;
use actix_service::Service; use actix_rt::{pin, time::sleep};
use bytes::{Buf, BytesMut}; use actix_service::{fn_service, Service};
use actix_utils::future::{ready, Ready};
use bytes::{Buf, Bytes, BytesMut};
use futures_util::future::lazy;
use super::dispatcher::{Dispatcher, DispatcherState, DispatcherStateProj, Flags}; use super::dispatcher::{Dispatcher, DispatcherState, DispatcherStateProj, Flags};
use crate::{ use crate::{
@ -43,8 +40,8 @@ fn status_service(
fn_service(move |_req: Request| ready(Ok::<_, Error>(Response::new(status)))) fn_service(move |_req: Request| ready(Ok::<_, Error>(Response::new(status))))
} }
fn echo_path_service( fn echo_path_service() -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> { {
fn_service(|req: Request| { fn_service(|req: Request| {
let path = req.path().as_bytes(); let path = req.path().as_bytes();
ready(Ok::<_, Error>( ready(Ok::<_, Error>(
@ -53,8 +50,8 @@ fn echo_path_service(
}) })
} }
fn drop_payload_service( fn drop_payload_service() -> impl Service<Request, Response = Response<&'static str>, Error = Error>
) -> impl Service<Request, Response = Response<&'static str>, Error = Error> { {
fn_service(|mut req: Request| async move { fn_service(|mut req: Request| async move {
let _ = req.take_payload(); let _ = req.take_payload();
Ok::<_, Error>(Response::with_body(StatusCode::OK, "payload dropped")) Ok::<_, Error>(Response::with_body(StatusCode::OK, "payload dropped"))

View file

@ -17,14 +17,16 @@ mod timer;
mod upgrade; mod upgrade;
mod utils; mod utils;
pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::{
pub use self::codec::Codec; client::{ClientCodec, ClientPayloadCodec},
pub use self::dispatcher::Dispatcher; codec::Codec,
pub use self::expect::ExpectHandler; dispatcher::Dispatcher,
pub use self::payload::Payload; expect::ExpectHandler,
pub use self::service::{H1Service, H1ServiceHandler}; payload::Payload,
pub use self::upgrade::UpgradeHandler; service::{H1Service, H1ServiceHandler},
pub use self::utils::SendResponse; upgrade::UpgradeHandler,
utils::SendResponse,
};
#[derive(Debug)] #[derive(Debug)]
/// Codec message /// Codec message

View file

@ -117,6 +117,7 @@ impl PayloadSender {
} }
} }
#[allow(clippy::needless_pass_by_ref_mut)]
#[inline] #[inline]
pub fn need_read(&self, cx: &mut Context<'_>) -> PayloadStatus { pub fn need_read(&self, cx: &mut Context<'_>) -> PayloadStatus {
// we check need_read only if Payload (other side) is alive, // we check need_read only if Payload (other side) is alive,
@ -174,7 +175,7 @@ impl Inner {
/// Register future waiting data from payload. /// Register future waiting data from payload.
/// Waker would be used in `Inner::wake` /// Waker would be used in `Inner::wake`
fn register(&mut self, cx: &mut Context<'_>) { fn register(&mut self, cx: &Context<'_>) {
if self if self
.task .task
.as_ref() .as_ref()
@ -186,7 +187,7 @@ impl Inner {
// Register future feeding data to payload. // Register future feeding data to payload.
/// Waker would be used in `Inner::wake_io` /// 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 if self
.io_task .io_task
.as_ref() .as_ref()
@ -221,7 +222,7 @@ impl Inner {
fn poll_next( fn poll_next(
mut self: Pin<&mut Self>, mut self: Pin<&mut Self>,
cx: &mut Context<'_>, cx: &Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> { ) -> Poll<Option<Result<Bytes, PayloadError>>> {
if let Some(data) = self.items.pop_front() { if let Some(data) = self.items.pop_front() {
self.len -= data.len(); self.len -= data.len();

View file

@ -15,6 +15,7 @@ use actix_utils::future::ready;
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use tracing::error; use tracing::error;
use super::{codec::Codec, dispatcher::Dispatcher, ExpectHandler, UpgradeHandler};
use crate::{ use crate::{
body::{BoxBody, MessageBody}, body::{BoxBody, MessageBody},
config::ServiceConfig, config::ServiceConfig,
@ -23,8 +24,6 @@ use crate::{
ConnectCallback, OnConnectData, Request, Response, ConnectCallback, OnConnectData, Request, Response,
}; };
use super::{codec::Codec, dispatcher::Dispatcher, ExpectHandler, UpgradeHandler};
/// `ServiceFactory` implementation for HTTP1 transport /// `ServiceFactory` implementation for HTTP1 transport
pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> { pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> {
srv: S, srv: S,
@ -82,13 +81,8 @@ where
/// Create simple tcp stream service /// Create simple tcp stream service
pub fn tcp( pub fn tcp(
self, self,
) -> impl ServiceFactory< ) -> impl ServiceFactory<TcpStream, Config = (), Response = (), Error = DispatchError, InitError = ()>
TcpStream, {
Config = (),
Response = (),
Error = DispatchError,
InitError = (),
> {
fn_service(|io: TcpStream| { fn_service(|io: TcpStream| {
let peer_addr = io.peer_addr().ok(); let peer_addr = io.peer_addr().ok();
ready(Ok((io, peer_addr))) ready(Ok((io, peer_addr)))
@ -99,8 +93,6 @@ where
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
mod openssl { mod openssl {
use super::*;
use actix_tls::accept::{ use actix_tls::accept::{
openssl::{ openssl::{
reexports::{Error as SslError, SslAcceptor}, reexports::{Error as SslError, SslAcceptor},
@ -109,6 +101,8 @@ mod openssl {
TlsError, TlsError,
}; };
use super::*;
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U> impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
@ -158,14 +152,13 @@ mod openssl {
} }
} }
#[cfg(feature = "rustls")] #[cfg(feature = "rustls-0_20")]
mod rustls { mod rustls_020 {
use std::io; use std::io;
use actix_service::ServiceFactoryExt as _; use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{ use actix_tls::accept::{
rustls::{reexports::ServerConfig, Acceptor, TlsStream}, rustls_0_20::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError, TlsError,
}; };
@ -195,7 +188,7 @@ mod rustls {
U::Error: fmt::Display + Into<Response<BoxBody>>, U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
/// Create Rustls based service. /// Create Rustls v0.20 based service.
pub fn rustls( pub fn rustls(
self, self,
config: ServerConfig, config: ServerConfig,
@ -220,6 +213,67 @@ mod rustls {
} }
} }
#[cfg(feature = "rustls-0_21")]
mod rustls_021 {
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))
}
}
}
impl<T, S, B, X, U> H1Service<T, S, B, X, U> impl<T, S, B, X, U> H1Service<T, S, B, X, U>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,

View file

@ -23,8 +23,7 @@ use crate::{
mod dispatcher; mod dispatcher;
mod service; mod service;
pub use self::dispatcher::Dispatcher; pub use self::{dispatcher::Dispatcher, service::H2Service};
pub use self::service::H2Service;
/// HTTP/2 peer stream. /// HTTP/2 peer stream.
pub struct Payload { pub struct Payload {
@ -58,10 +57,7 @@ impl Stream for Payload {
} }
} }
pub(crate) fn handshake_with_timeout<T>( pub(crate) fn handshake_with_timeout<T>(io: T, config: &ServiceConfig) -> HandshakeWithTimeout<T>
io: T,
config: &ServiceConfig,
) -> HandshakeWithTimeout<T>
where where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
{ {

View file

@ -16,6 +16,7 @@ use actix_utils::future::ready;
use futures_core::{future::LocalBoxFuture, ready}; use futures_core::{future::LocalBoxFuture, ready};
use tracing::{error, trace}; use tracing::{error, trace};
use super::{dispatcher::Dispatcher, handshake_with_timeout, HandshakeWithTimeout};
use crate::{ use crate::{
body::{BoxBody, MessageBody}, body::{BoxBody, MessageBody},
config::ServiceConfig, config::ServiceConfig,
@ -24,8 +25,6 @@ use crate::{
ConnectCallback, OnConnectData, Request, Response, ConnectCallback, OnConnectData, Request, Response,
}; };
use super::{dispatcher::Dispatcher, handshake_with_timeout, HandshakeWithTimeout};
/// `ServiceFactory` implementation for HTTP/2 transport /// `ServiceFactory` implementation for HTTP/2 transport
pub struct H2Service<T, S, B> { pub struct H2Service<T, S, B> {
srv: S, srv: S,
@ -141,8 +140,8 @@ mod openssl {
} }
} }
#[cfg(feature = "rustls")] #[cfg(feature = "rustls-0_20")]
mod rustls { mod rustls_020 {
use std::io; use std::io;
use actix_service::ServiceFactoryExt as _; use actix_service::ServiceFactoryExt as _;
@ -163,7 +162,7 @@ mod rustls {
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
/// Create Rustls based service. /// Create Rustls v0.20 based service.
pub fn rustls( pub fn rustls(
self, self,
mut config: ServerConfig, mut config: ServerConfig,
@ -192,6 +191,57 @@ mod rustls {
} }
} }
#[cfg(feature = "rustls-0_21")]
mod rustls_021 {
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))
}
}
}
impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B> impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B>
where where
T: AsyncRead + AsyncWrite + Unpin + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,

View file

@ -1,7 +1,5 @@
//! [`TryIntoHeaderPair`] trait and implementations. //! [`TryIntoHeaderPair`] trait and implementations.
use std::convert::TryFrom as _;
use super::{ use super::{
Header, HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, TryIntoHeaderValue, Header, HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, TryIntoHeaderValue,
}; };

View file

@ -1,7 +1,5 @@
//! [`TryIntoHeaderValue`] trait and implementations. //! [`TryIntoHeaderValue`] trait and implementations.
use std::convert::TryFrom as _;
use bytes::Bytes; use bytes::Bytes;
use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue}; use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue};
use mime::Mime; use mime::Mime;

View file

@ -1120,9 +1120,7 @@ mod tests {
assert!(vals.next().is_none()); assert!(vals.next().is_none());
} }
fn owned_pair<'a>( fn owned_pair<'a>((name, val): (&'a HeaderName, &'a HeaderValue)) -> (HeaderName, HeaderValue) {
(name, val): (&'a HeaderName, &'a HeaderValue),
) -> (HeaderName, HeaderValue) {
(name.clone(), val.clone()) (name.clone(), val.clone())
} }
} }

View file

@ -3,33 +3,30 @@
// declaring new header consts will yield this error // declaring new header consts will yield this error
#![allow(clippy::declare_interior_mutable_const)] #![allow(clippy::declare_interior_mutable_const)]
use percent_encoding::{AsciiSet, CONTROLS};
// re-export from http except header map related items // re-export from http except header map related items
pub use ::http::header::{ pub use ::http::header::{
HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, ToStrError, HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, ToStrError,
}; };
// re-export const header names, list is explicit so that any updates to `common` module do not // re-export const header names, list is explicit so that any updates to `common` module do not
// conflict with this set // conflict with this set
pub use ::http::header::{ pub use ::http::header::{
ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES, ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES,
ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS,
ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_MAX_AGE,
ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE, ALLOW, ALT_SVC,
ALLOW, ALT_SVC, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, CONTENT_ENCODING,
CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE, CONTENT_SECURITY_POLICY,
CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, DATE, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, DATE, DNT, ETAG, EXPECT, EXPIRES,
DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE, FORWARDED, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE, IF_NONE_MATCH, IF_RANGE,
IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED, LINK, LOCATION, MAX_FORWARDS, IF_UNMODIFIED_SINCE, LAST_MODIFIED, LINK, LOCATION, MAX_FORWARDS, ORIGIN, PRAGMA,
ORIGIN, PRAGMA, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, PUBLIC_KEY_PINS, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, PUBLIC_KEY_PINS, PUBLIC_KEY_PINS_REPORT_ONLY, RANGE,
PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER, REFERRER_POLICY, REFRESH, RETRY_AFTER, REFERER, REFERRER_POLICY, REFRESH, RETRY_AFTER, SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS,
SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL, SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE,
SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE, STRICT_TRANSPORT_SECURITY, TE, TRAILER, STRICT_TRANSPORT_SECURITY, TE, TRAILER, TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS,
TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA, WARNING, USER_AGENT, VARY, VIA, WARNING, WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS,
WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL, X_FRAME_OPTIONS, X_DNS_PREFETCH_CONTROL, X_FRAME_OPTIONS, X_XSS_PROTECTION,
X_XSS_PROTECTION,
}; };
use percent_encoding::{AsciiSet, CONTROLS};
use crate::{error::ParseError, HttpMessage}; use crate::{error::ParseError, HttpMessage};
@ -43,23 +40,22 @@ mod utils;
pub use self::{ pub use self::{
as_name::AsHeaderName, as_name::AsHeaderName,
// re-export list is explicit so that any updates to `http` do not conflict with this set
common::{
CACHE_STATUS, CDN_CACHE_CONTROL, CROSS_ORIGIN_EMBEDDER_POLICY, CROSS_ORIGIN_OPENER_POLICY,
CROSS_ORIGIN_RESOURCE_POLICY, PERMISSIONS_POLICY, X_FORWARDED_FOR, X_FORWARDED_HOST,
X_FORWARDED_PROTO,
},
into_pair::TryIntoHeaderPair, into_pair::TryIntoHeaderPair,
into_value::TryIntoHeaderValue, into_value::TryIntoHeaderValue,
map::HeaderMap, map::HeaderMap,
shared::{ shared::{
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag,
LanguageTag, Quality, QualityItem, Quality, QualityItem,
}, },
utils::{fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode}, utils::{fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode},
}; };
// re-export list is explicit so that any updates to `http` do not conflict with this set
pub use self::common::{
CACHE_STATUS, CDN_CACHE_CONTROL, CROSS_ORIGIN_EMBEDDER_POLICY, CROSS_ORIGIN_OPENER_POLICY,
CROSS_ORIGIN_RESOURCE_POLICY, PERMISSIONS_POLICY, X_FORWARDED_FOR, X_FORWARDED_HOST,
X_FORWARDED_PROTO,
};
/// An interface for types that already represent a valid header. /// An interface for types that already represent a valid header.
pub trait Header: TryIntoHeaderValue { pub trait Header: TryIntoHeaderValue {
/// Returns the name of the header field. /// Returns the name of the header field.

View file

@ -1,4 +1,4 @@
use std::{convert::TryFrom, str::FromStr}; use std::str::FromStr;
use derive_more::{Display, Error}; use derive_more::{Display, Error};
use http::header::InvalidHeaderValue; use http::header::InvalidHeaderValue;

View file

@ -1,5 +1,7 @@
//! Originally taken from `hyper::header::shared`. //! Originally taken from `hyper::header::shared`.
pub use language_tags::LanguageTag;
mod charset; mod charset;
mod content_encoding; mod content_encoding;
mod extended; mod extended;
@ -7,10 +9,11 @@ mod http_date;
mod quality; mod quality;
mod quality_item; mod quality_item;
pub use self::charset::Charset; pub use self::{
pub use self::content_encoding::ContentEncoding; charset::Charset,
pub use self::extended::{parse_extended_value, ExtendedValue}; content_encoding::ContentEncoding,
pub use self::http_date::HttpDate; extended::{parse_extended_value, ExtendedValue},
pub use self::quality::{q, Quality}; http_date::HttpDate,
pub use self::quality_item::QualityItem; quality::{q, Quality},
pub use language_tags::LanguageTag; quality_item::QualityItem,
};

View file

@ -1,7 +1,4 @@
use std::{ use std::fmt;
convert::{TryFrom, TryInto},
fmt,
};
use derive_more::{Display, Error}; use derive_more::{Display, Error};

View file

@ -1,8 +1,7 @@
use std::{cmp, convert::TryFrom as _, fmt, str}; use std::{cmp, fmt, str};
use crate::error::ParseError;
use super::Quality; use super::Quality;
use crate::error::ParseError;
/// Represents an item with a quality value as defined /// Represents an item with a quality value as defined
/// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1). /// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1).

View file

@ -61,9 +61,7 @@ pub trait HttpMessage: Sized {
fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> { fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> {
if let Some(mime_type) = self.mime_type()? { if let Some(mime_type) = self.mime_type()? {
if let Some(charset) = mime_type.get_param("charset") { if let Some(charset) = mime_type.get_param("charset") {
if let Some(enc) = if let Some(enc) = Encoding::for_label_no_replacement(charset.as_str().as_bytes()) {
Encoding::for_label_no_replacement(charset.as_str().as_bytes())
{
Ok(enc) Ok(enc)
} else { } else {
Err(ContentTypeError::UnknownEncoding) Err(ContentTypeError::UnknownEncoding)
@ -146,7 +144,7 @@ mod tests {
.finish(); .finish();
assert_eq!(req.content_type(), "text/plain"); assert_eq!(req.content_type(), "text/plain");
let req = TestRequest::default() let req = TestRequest::default()
.insert_header(("content-type", "application/json; charset=utf=8")) .insert_header(("content-type", "application/json; charset=utf-8"))
.finish(); .finish();
assert_eq!(req.content_type(), "application/json"); assert_eq!(req.content_type(), "application/json");
let req = TestRequest::default().finish(); let req = TestRequest::default().finish();

View file

@ -28,8 +28,7 @@
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub use ::http::{uri, uri::Uri}; pub use ::http::{uri, uri::Uri, Method, StatusCode, Version};
pub use ::http::{Method, StatusCode, Version};
pub mod body; pub mod body;
mod builder; mod builder;
@ -57,22 +56,24 @@ pub mod test;
#[cfg(feature = "ws")] #[cfg(feature = "ws")]
pub mod ws; pub mod ws;
pub use self::builder::HttpServiceBuilder;
pub use self::config::ServiceConfig;
pub use self::error::Error;
pub use self::extensions::Extensions;
pub use self::header::ContentEncoding;
pub use self::http_message::HttpMessage;
pub use self::keep_alive::KeepAlive;
pub use self::message::ConnectionType;
pub use self::message::Message;
#[allow(deprecated)] #[allow(deprecated)]
pub use self::payload::{BoxedPayloadStream, Payload, PayloadStream}; pub use self::payload::PayloadStream;
pub use self::requests::{Request, RequestHead, RequestHeadType}; #[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))]
pub use self::responses::{Response, ResponseBuilder, ResponseHead};
pub use self::service::HttpService;
#[cfg(any(feature = "openssl", feature = "rustls"))]
pub use self::service::TlsAcceptorConfig; pub use self::service::TlsAcceptorConfig;
pub use self::{
builder::HttpServiceBuilder,
config::ServiceConfig,
error::Error,
extensions::Extensions,
header::ContentEncoding,
http_message::HttpMessage,
keep_alive::KeepAlive,
message::{ConnectionType, Message},
payload::{BoxedPayloadStream, Payload},
requests::{Request, RequestHead, RequestHeadType},
responses::{Response, ResponseBuilder, ResponseHead},
service::HttpService,
};
/// A major HTTP protocol version. /// A major HTTP protocol version.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]

View file

@ -3,5 +3,7 @@
mod head; mod head;
mod request; mod request;
pub use self::head::{RequestHead, RequestHeadType}; pub use self::{
pub use self::request::Request; head::{RequestHead, RequestHeadType},
request::Request,
};

View file

@ -10,8 +10,7 @@ use std::{
use http::{header, Method, Uri, Version}; use http::{header, Method, Uri, Version};
use crate::{ use crate::{
header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Message, Payload, header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Message, Payload, RequestHead,
RequestHead,
}; };
/// An HTTP request. /// An HTTP request.
@ -234,7 +233,6 @@ impl<P> fmt::Debug for Request<P> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::convert::TryFrom;
#[test] #[test]
fn test_basics() { fn test_basics() {

View file

@ -93,7 +93,7 @@ impl ResponseBuilder {
Ok((key, value)) => { Ok((key, value)) => {
parts.headers.insert(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() { if let Some(parts) = self.inner() {
match header.try_into_pair() { match header.try_into_pair() {
Ok((key, value)) => parts.headers.append(key, value), 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) => { Ok(value) => {
parts.headers.insert(header::CONTENT_TYPE, value); parts.headers.insert(header::CONTENT_TYPE, value);
} }
Err(e) => self.err = Some(e.into()), Err(err) => self.err = Some(err.into()),
}; };
} }
self self

View file

@ -5,7 +5,5 @@ mod head;
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]
mod response; mod response;
pub use self::builder::ResponseBuilder;
pub(crate) use self::head::BoxedResponseHead; pub(crate) use self::head::BoxedResponseHead;
pub use self::head::ResponseHead; pub use self::{builder::ResponseBuilder, head::ResponseHead, response::Response};
pub use self::response::Response;

View file

@ -30,9 +30,9 @@ use crate::{
/// ///
/// # Automatic HTTP Version Selection /// # Automatic HTTP Version Selection
/// There are two ways to select the HTTP version of an incoming connection: /// There are two ways to select the HTTP version of an incoming connection:
/// - One is to rely on the ALPN information that is provided when using a TLS (HTTPS); both /// - One is to rely on the ALPN information that is provided when using TLS (HTTPS); both versions
/// versions are supported automatically when using either of the `.rustls()` or `.openssl()` /// are supported automatically when using either of the `.rustls()` or `.openssl()` finalizing
/// finalizing methods. /// methods.
/// - The other is to read the first few bytes of the TCP stream. This is the only viable approach /// - The other is to read the first few bytes of the TCP stream. This is the only viable approach
/// for supporting H2C, which allows the HTTP/2 protocol to work over plaintext connections. Use /// for supporting H2C, which allows the HTTP/2 protocol to work over plaintext connections. Use
/// the `.tcp_auto_h2c()` finalizing method to enable this behavior. /// the `.tcp_auto_h2c()` finalizing method to enable this behavior.
@ -200,13 +200,8 @@ where
/// The resulting service only supports HTTP/1.x. /// The resulting service only supports HTTP/1.x.
pub fn tcp( pub fn tcp(
self, self,
) -> impl ServiceFactory< ) -> impl ServiceFactory<TcpStream, Config = (), Response = (), Error = DispatchError, InitError = ()>
TcpStream, {
Config = (),
Response = (),
Error = DispatchError,
InitError = (),
> {
fn_service(|io: TcpStream| async { fn_service(|io: TcpStream| async {
let peer_addr = io.peer_addr().ok(); let peer_addr = io.peer_addr().ok();
Ok((io, Protocol::Http1, peer_addr)) Ok((io, Protocol::Http1, peer_addr))
@ -219,13 +214,8 @@ where
#[cfg(feature = "http2")] #[cfg(feature = "http2")]
pub fn tcp_auto_h2c( pub fn tcp_auto_h2c(
self, self,
) -> impl ServiceFactory< ) -> impl ServiceFactory<TcpStream, Config = (), Response = (), Error = DispatchError, InitError = ()>
TcpStream, {
Config = (),
Response = (),
Error = DispatchError,
InitError = (),
> {
fn_service(move |io: TcpStream| async move { fn_service(move |io: TcpStream| async move {
// subset of HTTP/2 preface defined by RFC 9113 §3.4 // subset of HTTP/2 preface defined by RFC 9113 §3.4
// this subset was chosen to maximize likelihood that peeking only once will allow us to // this subset was chosen to maximize likelihood that peeking only once will allow us to
@ -251,13 +241,13 @@ where
} }
/// Configuration options used when accepting TLS connection. /// 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"))]
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct TlsAcceptorConfig { pub struct TlsAcceptorConfig {
pub(crate) handshake_timeout: Option<std::time::Duration>, pub(crate) handshake_timeout: Option<std::time::Duration>,
} }
#[cfg(any(feature = "openssl", feature = "rustls"))] #[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))]
impl TlsAcceptorConfig { impl TlsAcceptorConfig {
/// Set TLS handshake timeout duration. /// Set TLS handshake timeout duration.
pub fn handshake_timeout(self, dur: std::time::Duration) -> Self { pub fn handshake_timeout(self, dur: std::time::Duration) -> Self {
@ -362,8 +352,8 @@ mod openssl {
} }
} }
#[cfg(feature = "rustls")] #[cfg(feature = "rustls-0_20")]
mod rustls { mod rustls_020 {
use std::io; use std::io;
use actix_service::ServiceFactoryExt as _; use actix_service::ServiceFactoryExt as _;
@ -458,6 +448,102 @@ mod rustls {
} }
} }
#[cfg(feature = "rustls-0_21")]
mod rustls_021 {
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 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 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))
}
}
}
impl<T, S, B, X, U> ServiceFactory<(T, Protocol, Option<net::SocketAddr>)> impl<T, S, B, X, U> ServiceFactory<(T, Protocol, Option<net::SocketAddr>)>
for HttpService<T, S, B, X, U> for HttpService<T, S, B, X, U>
where where
@ -563,10 +649,7 @@ where
} }
} }
pub(super) fn _poll_ready( pub(super) fn _poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Response<BoxBody>>> {
&self,
cx: &mut Context<'_>,
) -> Poll<Result<(), Response<BoxBody>>> {
ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?; ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?;
ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?; ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?;
@ -625,10 +708,7 @@ where
}) })
} }
fn call( fn call(&self, (io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>)) -> Self::Future {
&self,
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
) -> Self::Future {
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
match proto { match proto {

View file

@ -296,7 +296,7 @@ impl Decoder for Codec {
} }
} }
Ok(None) => Ok(None), Ok(None) => Ok(None),
Err(e) => Err(e), Err(err) => Err(err),
} }
} }
} }

View file

@ -70,15 +70,14 @@ mod inner {
task::{Context, Poll}, task::{Context, Poll},
}; };
use actix_codec::Framed;
use actix_service::{IntoService, Service}; use actix_service::{IntoService, Service};
use futures_core::stream::Stream; use futures_core::stream::Stream;
use local_channel::mpsc; use local_channel::mpsc;
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use tracing::debug;
use actix_codec::Framed;
use tokio::io::{AsyncRead, AsyncWrite}; use tokio::io::{AsyncRead, AsyncWrite};
use tokio_util::codec::{Decoder, Encoder}; use tokio_util::codec::{Decoder, Encoder};
use tracing::debug;
use crate::{body::BoxBody, Response}; use crate::{body::BoxBody, Response};
@ -413,9 +412,7 @@ mod inner {
} }
State::Error(_) => { State::Error(_) => {
// flush write buffer // flush write buffer
if !this.framed.is_write_buf_empty() if !this.framed.is_write_buf_empty() && this.framed.flush(cx).is_pending() {
&& this.framed.flush(cx).is_pending()
{
return Poll::Pending; return Poll::Pending;
} }
Poll::Ready(Err(this.state.take_error())) Poll::Ready(Err(this.state.take_error()))

View file

@ -1,5 +1,4 @@
use std::cmp::min; use std::cmp::min;
use std::convert::TryFrom;
use bytes::{Buf, BufMut, BytesMut}; use bytes::{Buf, BufMut, BytesMut};
use tracing::debug; use tracing::debug;
@ -222,9 +221,10 @@ impl Parser {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use bytes::Bytes; use bytes::Bytes;
use super::*;
struct F { struct F {
finished: bool, finished: bool,
opcode: OpCode, opcode: OpCode,

View file

@ -50,7 +50,7 @@ mod tests {
#[test] #[test]
fn test_apply_mask() { fn test_apply_mask() {
let mask = [0x6d, 0xb6, 0xb2, 0x80]; let mask = [0x6d, 0xb6, 0xb2, 0x80];
let unmasked = vec![ let unmasked = [
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9, 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9,
0x12, 0x03, 0x12, 0x03,
]; ];

View file

@ -8,8 +8,7 @@ use std::io;
use derive_more::{Display, Error, From}; use derive_more::{Display, Error, From};
use http::{header, Method, StatusCode}; use http::{header, Method, StatusCode};
use crate::body::BoxBody; use crate::{body::BoxBody, header::HeaderValue, RequestHead, Response, ResponseBuilder};
use crate::{header::HeaderValue, RequestHead, Response, ResponseBuilder};
mod codec; mod codec;
mod dispatcher; mod dispatcher;
@ -17,10 +16,12 @@ mod frame;
mod mask; mod mask;
mod proto; mod proto;
pub use self::codec::{Codec, Frame, Item, Message}; pub use self::{
pub use self::dispatcher::Dispatcher; codec::{Codec, Frame, Item, Message},
pub use self::frame::Parser; dispatcher::Dispatcher,
pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; frame::Parser,
proto::{hash_key, CloseCode, CloseReason, OpCode},
};
/// WebSocket protocol errors. /// WebSocket protocol errors.
#[derive(Debug, Display, Error, From)] #[derive(Debug, Display, Error, From)]
@ -219,10 +220,8 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{header, Method};
use super::*; use super::*;
use crate::test::TestRequest; use crate::{header, test::TestRequest, Method};
#[test] #[test]
fn test_handshake() { fn test_handshake() {

View file

@ -321,8 +321,7 @@ async fn h2_body_length() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| async { .h2(|_| async {
let body = let body = once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) });
once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) });
Ok::<_, Infallible>( Ok::<_, Infallible>(
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),

View file

@ -1,10 +1,9 @@
#![cfg(feature = "rustls")] #![cfg(feature = "rustls-0_21")]
#![allow(clippy::uninlined_format_args)]
extern crate tls_rustls as rustls; extern crate tls_rustls_021 as rustls;
use std::{ use std::{
convert::{Infallible, TryFrom}, convert::Infallible,
io::{self, BufReader, Write}, io::{self, BufReader, Write},
net::{SocketAddr, TcpStream as StdTcpStream}, net::{SocketAddr, TcpStream as StdTcpStream},
sync::Arc, sync::Arc,
@ -21,7 +20,7 @@ use actix_http::{
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_rt::pin; use actix_rt::pin;
use actix_service::{fn_factory_with_config, fn_service}; use actix_service::{fn_factory_with_config, fn_service};
use actix_tls::connect::rustls::webpki_roots_cert_store; use actix_tls::connect::rustls_0_21::webpki_roots_cert_store;
use actix_utils::future::{err, ok, poll_fn}; use actix_utils::future::{err, ok, poll_fn};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use derive_more::{Display, Error}; use derive_more::{Display, Error};
@ -90,11 +89,9 @@ pub fn get_negotiated_alpn_protocol(
config.alpn_protocols.push(client_alpn_protocol.to_vec()); config.alpn_protocols.push(client_alpn_protocol.to_vec());
let mut sess = rustls::ClientConnection::new( let mut sess =
Arc::new(config), rustls::ClientConnection::new(Arc::new(config), ServerName::try_from("localhost").unwrap())
ServerName::try_from("localhost").unwrap(), .unwrap();
)
.unwrap();
let mut sock = StdTcpStream::connect(addr).unwrap(); let mut sock = StdTcpStream::connect(addr).unwrap();
let mut stream = rustls::Stream::new(&mut sess, &mut sock); let mut stream = rustls::Stream::new(&mut sess, &mut sock);
@ -112,7 +109,7 @@ async fn h1() -> io::Result<()> {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.h1(|_| ok::<_, Error>(Response::ok())) .h1(|_| ok::<_, Error>(Response::ok()))
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -126,7 +123,7 @@ async fn h2() -> io::Result<()> {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, Error>(Response::ok())) .h2(|_| ok::<_, Error>(Response::ok()))
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -144,7 +141,7 @@ async fn h1_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_11); assert_eq!(req.version(), Version::HTTP_11);
ok::<_, Error>(Response::ok()) ok::<_, Error>(Response::ok())
}) })
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -162,7 +159,7 @@ async fn h2_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_2); assert_eq!(req.version(), Version::HTTP_2);
ok::<_, Error>(Response::ok()) ok::<_, Error>(Response::ok())
}) })
.rustls_with_config( .rustls_021_with_config(
tls_config(), tls_config(),
TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)), TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)),
) )
@ -183,7 +180,7 @@ async fn h2_body1() -> io::Result<()> {
let body = load_body(req.take_payload()).await?; let body = load_body(req.take_payload()).await?;
Ok::<_, Error>(Response::ok().set_body(body)) Ok::<_, Error>(Response::ok().set_body(body))
}) })
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -209,7 +206,7 @@ async fn h2_content_length() {
]; ];
ok::<_, Infallible>(Response::new(statuses[indx])) ok::<_, Infallible>(Response::new(statuses[indx]))
}) })
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -281,7 +278,7 @@ async fn h2_headers() {
} }
ok::<_, Infallible>(config.body(data.clone())) ok::<_, Infallible>(config.body(data.clone()))
}) })
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -320,7 +317,7 @@ async fn h2_body2() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -337,7 +334,7 @@ async fn h2_head_empty() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -363,7 +360,7 @@ async fn h2_head_binary() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -388,7 +385,7 @@ async fn h2_head_binary2() {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -414,7 +411,7 @@ async fn h2_body_length() {
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
) )
}) })
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -438,7 +435,7 @@ async fn h2_body_chunked_explicit() {
.body(BodyStream::new(body)), .body(BodyStream::new(body)),
) )
}) })
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -467,7 +464,7 @@ async fn h2_response_http_error_handling() {
) )
})) }))
})) }))
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -497,7 +494,7 @@ async fn h2_service_error() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| err::<Response<BoxBody>, _>(BadRequest)) .h2(|_| err::<Response<BoxBody>, _>(BadRequest))
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -514,7 +511,7 @@ async fn h1_service_error() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h1(|_| err::<Response<BoxBody>, _>(BadRequest)) .h1(|_| err::<Response<BoxBody>, _>(BadRequest))
.rustls(tls_config()) .rustls_021(tls_config())
}) })
.await; .await;
@ -537,7 +534,7 @@ async fn alpn_h1() -> io::Result<()> {
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
HttpService::build() HttpService::build()
.h1(|_| ok::<_, Error>(Response::ok())) .h1(|_| ok::<_, Error>(Response::ok()))
.rustls(config) .rustls_021(config)
}) })
.await; .await;
@ -559,7 +556,7 @@ async fn alpn_h2() -> io::Result<()> {
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
HttpService::build() HttpService::build()
.h2(|_| ok::<_, Error>(Response::ok())) .h2(|_| ok::<_, Error>(Response::ok()))
.rustls(config) .rustls_021(config)
}) })
.await; .await;
@ -585,7 +582,7 @@ async fn alpn_h2_1() -> io::Result<()> {
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
HttpService::build() HttpService::build()
.finish(|_| ok::<_, Error>(Response::ok())) .finish(|_| ok::<_, Error>(Response::ok()))
.rustls(config) .rustls_021(config)
}) })
.await; .await;

View file

@ -1,5 +1,3 @@
#![allow(clippy::uninlined_format_args)]
use std::{ use std::{
convert::Infallible, convert::Infallible,
io::{Read, Write}, io::{Read, Write},
@ -139,7 +137,7 @@ async fn expect_continue_h1() {
#[actix_rt::test] #[actix_rt::test]
async fn chunked_payload() { async fn chunked_payload() {
let chunk_sizes = vec![32768, 32, 32768]; let chunk_sizes = [32768, 32, 32768];
let total_size: usize = chunk_sizes.iter().sum(); let total_size: usize = chunk_sizes.iter().sum();
let mut srv = test_server(|| { let mut srv = test_server(|| {
@ -149,7 +147,7 @@ async fn chunked_payload() {
.take_payload() .take_payload()
.map(|res| match res { .map(|res| match res {
Ok(pl) => pl, 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())) .fold(0usize, |acc, chunk| ready(acc + chunk.len()))
.map(|req_size| { .map(|req_size| {
@ -166,8 +164,7 @@ async fn chunked_payload() {
for chunk_size in chunk_sizes.iter() { for chunk_size in chunk_sizes.iter() {
let mut bytes = Vec::new(); let mut bytes = Vec::new();
let random_bytes: Vec<u8> = let random_bytes: Vec<u8> = (0..*chunk_size).map(|_| rand::random::<u8>()).collect();
(0..*chunk_size).map(|_| rand::random::<u8>()).collect();
bytes.extend(format!("{:X}\r\n", chunk_size).as_bytes()); bytes.extend(format!("{:X}\r\n", chunk_size).as_bytes());
bytes.extend(&random_bytes[..]); bytes.extend(&random_bytes[..]);
@ -352,8 +349,7 @@ async fn http10_keepalive() {
.await; .await;
let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
let _ = let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n");
stream.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n");
let mut data = vec![0; 1024]; let mut data = vec![0; 1024];
let _ = stream.read(&mut data); let _ = stream.read(&mut data);
assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n");
@ -404,7 +400,7 @@ async fn content_length() {
let mut srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|req: Request| { .h1(|req: Request| {
let indx: usize = req.uri().path()[1..].parse().unwrap(); let idx: usize = req.uri().path()[1..].parse().unwrap();
let statuses = [ let statuses = [
StatusCode::NO_CONTENT, StatusCode::NO_CONTENT,
StatusCode::CONTINUE, StatusCode::CONTINUE,
@ -413,7 +409,7 @@ async fn content_length() {
StatusCode::OK, StatusCode::OK,
StatusCode::NOT_FOUND, StatusCode::NOT_FOUND,
]; ];
ok::<_, Infallible>(Response::new(statuses[indx])) ok::<_, Infallible>(Response::new(statuses[idx]))
}) })
.tcp() .tcp()
}) })
@ -795,8 +791,9 @@ async fn not_modified_spec_h1() {
.map_into_boxed_body(), .map_into_boxed_body(),
// with no content-length // with no content-length
"/body" => Response::with_body(StatusCode::NOT_MODIFIED, "1234") "/body" => {
.map_into_boxed_body(), Response::with_body(StatusCode::NOT_MODIFIED, "1234").map_into_boxed_body()
}
// with manual content-length header and specific None body // with manual content-length header and specific None body
"/cl-none" => { "/cl-none" => {

View file

@ -1,5 +1,12 @@
# Changes # Changes
## 0.6.0 - 2023-02-26 ## Unreleased
## 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
- Add `MultipartForm` derive macro. - Add `MultipartForm` derive macro.

View file

@ -1,13 +1,13 @@
[package] [package]
name = "actix-multipart-derive" name = "actix-multipart-derive"
version = "0.6.0" version = "0.6.1"
authors = ["Jacob Halsey <jacob@jhalsey.com>"] authors = ["Jacob Halsey <jacob@jhalsey.com>"]
description = "Multipart form derive macro for Actix Web" description = "Multipart form derive macro for Actix Web"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2021"
[package.metadata.docs.rs] [package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
@ -17,11 +17,11 @@ all-features = true
proc-macro = true proc-macro = true
[dependencies] [dependencies]
darling = "0.14" darling = "0.20"
parse-size = "1" parse-size = "1"
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
syn = "1" syn = "2"
[dev-dependencies] [dev-dependencies]
actix-multipart = "0.6" actix-multipart = "0.6"

View file

@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-multipart-derive?label=latest)](https://crates.io/crates/actix-multipart-derive) [![crates.io](https://img.shields.io/crates/v/actix-multipart-derive?label=latest)](https://crates.io/crates/actix-multipart-derive)
[![Documentation](https://docs.rs/actix-multipart-derive/badge.svg?version=0.5.0)](https://docs.rs/actix-multipart-derive/0.5.0) [![Documentation](https://docs.rs/actix-multipart-derive/badge.svg?version=0.5.0)](https://docs.rs/actix-multipart-derive/0.5.0)
![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart-derive.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart-derive.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-multipart-derive/0.5.0/status.svg)](https://deps.rs/crate/actix-multipart-derive/0.5.0) [![dependency status](https://deps.rs/crate/actix-multipart-derive/0.5.0/status.svg)](https://deps.rs/crate/actix-multipart-derive/0.5.0)
@ -14,4 +14,4 @@
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart-derive) - [API Documentation](https://docs.rs/actix-multipart-derive)
- Minimum Supported Rust Version (MSRV): 1.59 - Minimum Supported Rust Version (MSRV): 1.68

View file

@ -8,7 +8,7 @@
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
use std::{collections::HashSet, convert::TryFrom as _}; use std::collections::HashSet;
use darling::{FromDeriveInput, FromField, FromMeta}; use darling::{FromDeriveInput, FromField, FromMeta};
use parse_size::parse_size; use parse_size::parse_size;

View file

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

View file

@ -1,47 +1,51 @@
# Changes # Changes
## Unreleased - 2023-xx-xx ## Unreleased
## 0.6.0 - 2023-02-26 ## 0.6.1
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
## 0.6.0
- Added `MultipartForm` typed data extractor. [#2883] - Added `MultipartForm` typed data extractor. [#2883]
[#2883]: https://github.com/actix/actix-web/pull/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] - `Field::content_type()` now returns `Option<&mime::Mime>`. [#2885]
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. - Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
[#2885]: https://github.com/actix/actix-web/pull/2885 [#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`. - 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`. - 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. - 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`. - 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`. - 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] - Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463]
[#2463]: https://github.com/actix/actix-web/pull/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] - Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451]
- Added `MultipartError::NoContentDisposition` variant. [#2451] - Added `MultipartError::NoContentDisposition` variant. [#2451]
@ -52,31 +56,31 @@
[#2451]: https://github.com/actix/actix-web/pull/2451 [#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. - 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. - 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. - No notable changes.
## 0.4.0-beta.4 - 2021-04-02 ## 0.4.0-beta.4
- No notable changes. - No notable changes.
## 0.4.0-beta.3 - 2021-03-09 ## 0.4.0-beta.3
- No notable changes. - No notable changes.
## 0.4.0-beta.2 - 2021-02-10 ## 0.4.0-beta.2
- No notable changes. - No notable changes.
## 0.4.0-beta.1 - 2021-01-07 ## 0.4.0-beta.1
- Fix multipart consuming payload before header checks. [#1513] - Fix multipart consuming payload before header checks. [#1513]
- Update `bytes` to `1.0`. [#1813] - Update `bytes` to `1.0`. [#1813]
@ -84,19 +88,19 @@
[#1813]: https://github.com/actix/actix-web/pull/1813 [#1813]: https://github.com/actix/actix-web/pull/1813
[#1513]: https://github.com/actix/actix-web/pull/1513 [#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`. - 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. - 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 - 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 - Update `actix-web` to 3.0.0-alpha.3
- Bump minimum supported Rust version to 1.40 - Bump minimum supported Rust version to 1.40
@ -104,45 +108,45 @@
- Remove the unused `time` dependency - Remove the unused `time` dependency
- Fix missing `std::error::Error` implement for `MultipartError`. - Fix missing `std::error::Error` implement for `MultipartError`.
## [0.2.0] - 2019-12-20 ## 0.2.0
- Release - Release
## [0.2.0-alpha.4] - 2019-12-xx ## 0.2.0-alpha.4
- Multipart handling now handles Pending during read of boundary #1205 - 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` - 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 - 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. - Fix ring dependency from actix-web default features for #741.
## [0.1.2] - 2019-06-02 ## 0.1.2
- Fix boundary parsing #876 - Fix boundary parsing #876
## [0.1.1] - 2019-05-25 ## 0.1.1
- Fix disconnect handling #834 - Fix disconnect handling #834
## [0.1.0] - 2019-05-18 ## 0.1.0
- Release - Release
## [0.1.0-beta.4] - 2019-05-12 ## 0.1.0-beta.4
- Handle cancellation of uploads #736 - Handle cancellation of uploads #736
- Upgrade to actix-web 1.0.0-beta.4 - 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 - Do not support nested multipart

View file

@ -1,6 +1,6 @@
[package] [package]
name = "actix-multipart" name = "actix-multipart"
version = "0.6.0" version = "0.6.1"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Jacob Halsey <jacob@jhalsey.com>", "Jacob Halsey <jacob@jhalsey.com>",
@ -10,7 +10,7 @@ keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2021"
[package.metadata.docs.rs] [package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
@ -19,10 +19,10 @@ all-features = true
[features] [features]
default = ["tempfile", "derive"] default = ["tempfile", "derive"]
derive = ["actix-multipart-derive"] derive = ["actix-multipart-derive"]
tempfile = ["tempfile-dep", "tokio/fs"] tempfile = ["dep:tempfile", "tokio/fs"]
[dependencies] [dependencies]
actix-multipart-derive = { version = "=0.6.0", optional = true } actix-multipart-derive = { version = "=0.6.1", optional = true }
actix-utils = "3" actix-utils = "3"
actix-web = { version = "4", default-features = false } actix-web = { version = "4", default-features = false }
@ -38,9 +38,8 @@ mime = "0.3"
serde = "1" serde = "1"
serde_json = "1" serde_json = "1"
serde_plain = "1" serde_plain = "1"
# TODO(MSRV 1.60): replace with dep: prefix tempfile = { version = "3.4", optional = true }
tempfile-dep = { package = "tempfile", version = "3.4", optional = true } tokio = { version = "1.24.2", features = ["sync", "io-util"] }
tokio = { version = "1.24.2", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
actix-http = "3" actix-http = "3"

View file

@ -3,15 +3,15 @@
> Multipart form support for Actix Web. > Multipart form support for Actix Web.
[![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart)
[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.6.0)](https://docs.rs/actix-multipart/0.6.0) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.6.1)](https://docs.rs/actix-multipart/0.6.1)
![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-multipart/0.6.0/status.svg)](https://deps.rs/crate/actix-multipart/0.6.0) [![dependency status](https://deps.rs/crate/actix-multipart/0.6.1/status.svg)](https://deps.rs/crate/actix-multipart/0.6.1)
[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart) - [API Documentation](https://docs.rs/actix-multipart)
- Minimum Supported Rust Version (MSRV): 1.59 - Minimum Supported Rust Version (MSRV): 1.68

View file

@ -27,11 +27,7 @@ pub struct Bytes {
impl<'t> FieldReader<'t> for Bytes { impl<'t> FieldReader<'t> for Bytes {
type Future = LocalBoxFuture<'t, Result<Self, MultipartError>>; type Future = LocalBoxFuture<'t, Result<Self, MultipartError>>;
fn read_field( fn read_field(_: &'t HttpRequest, mut field: Field, limits: &'t mut Limits) -> Self::Future {
_: &'t HttpRequest,
mut field: Field,
limits: &'t mut Limits,
) -> Self::Future {
Box::pin(async move { Box::pin(async move {
let mut buf = BytesMut::with_capacity(131_072); let mut buf = BytesMut::with_capacity(131_072);

View file

@ -7,13 +7,12 @@ use derive_more::{Deref, DerefMut, Display, Error};
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use super::FieldErrorHandler;
use crate::{ use crate::{
form::{bytes::Bytes, FieldReader, Limits}, form::{bytes::Bytes, FieldReader, Limits},
Field, MultipartError, Field, MultipartError,
}; };
use super::FieldErrorHandler;
/// Deserialize from JSON. /// Deserialize from JSON.
#[derive(Debug, Deref, DerefMut)] #[derive(Debug, Deref, DerefMut)]
pub struct Json<T: DeserializeOwned>(pub T); pub struct Json<T: DeserializeOwned>(pub T);

View file

@ -429,8 +429,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_options() { async fn test_options() {
let srv = let srv = actix_test::start(|| App::new().route("/", web::post().to(test_options_route)));
actix_test::start(|| App::new().route("/", web::post().to(test_options_route)));
let mut form = multipart::Form::default(); let mut form = multipart::Form::default();
form.add_text("field1", "value"); form.add_text("field1", "value");
@ -481,9 +480,7 @@ mod tests {
field3: Text<String>, field3: Text<String>,
} }
async fn test_field_renaming_route( async fn test_field_renaming_route(form: MultipartForm<TestFieldRenaming>) -> impl Responder {
form: MultipartForm<TestFieldRenaming>,
) -> impl Responder {
assert_eq!(&*form.field1, "renamed"); assert_eq!(&*form.field1, "renamed");
assert_eq!(&*form.field2, "field1"); assert_eq!(&*form.field2, "field1");
assert_eq!(&*form.field3, "field3"); assert_eq!(&*form.field3, "field3");
@ -492,9 +489,8 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_field_renaming() { async fn test_field_renaming() {
let srv = actix_test::start(|| { let srv =
App::new().route("/", web::post().to(test_field_renaming_route)) actix_test::start(|| App::new().route("/", web::post().to(test_field_renaming_route)));
});
let mut form = multipart::Form::default(); let mut form = multipart::Form::default();
form.add_text("renamed", "renamed"); form.add_text("renamed", "renamed");
@ -623,9 +619,7 @@ mod tests {
HttpResponse::Ok().finish() HttpResponse::Ok().finish()
} }
async fn test_upload_limits_file( async fn test_upload_limits_file(form: MultipartForm<TestFileUploadLimits>) -> impl Responder {
form: MultipartForm<TestFileUploadLimits>,
) -> impl Responder {
assert!(form.field.size > 0); assert!(form.field.size > 0);
HttpResponse::Ok().finish() HttpResponse::Ok().finish()
} }

View file

@ -11,7 +11,7 @@ use derive_more::{Display, Error};
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use futures_util::TryStreamExt as _; use futures_util::TryStreamExt as _;
use mime::Mime; use mime::Mime;
use tempfile_dep::NamedTempFile; use tempfile::NamedTempFile;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use super::FieldErrorHandler; use super::FieldErrorHandler;
@ -39,23 +39,20 @@ pub struct TempFile {
impl<'t> FieldReader<'t> for TempFile { impl<'t> FieldReader<'t> for TempFile {
type Future = LocalBoxFuture<'t, Result<Self, MultipartError>>; type Future = LocalBoxFuture<'t, Result<Self, MultipartError>>;
fn read_field( fn read_field(req: &'t HttpRequest, mut field: Field, limits: &'t mut Limits) -> Self::Future {
req: &'t HttpRequest,
mut field: Field,
limits: &'t mut Limits,
) -> Self::Future {
Box::pin(async move { Box::pin(async move {
let config = TempFileConfig::from_req(req); let config = TempFileConfig::from_req(req);
let field_name = field.name().to_owned(); let field_name = field.name().to_owned();
let mut size = 0; let mut size = 0;
let file = config.create_tempfile().map_err(|err| { let file = config
config.map_error(req, &field_name, TempFileError::FileIo(err)) .create_tempfile()
})?; .map_err(|err| config.map_error(req, &field_name, TempFileError::FileIo(err)))?;
let mut file_async = tokio::fs::File::from_std(file.reopen().map_err(|err| { let mut file_async =
config.map_error(req, &field_name, TempFileError::FileIo(err)) tokio::fs::File::from_std(file.reopen().map_err(|err| {
})?); config.map_error(req, &field_name, TempFileError::FileIo(err))
})?);
while let Some(chunk) = field.try_next().await? { while let Some(chunk) = field.try_next().await? {
limits.try_consume_limits(chunk.len(), false)?; limits.try_consume_limits(chunk.len(), false)?;
@ -65,9 +62,10 @@ impl<'t> FieldReader<'t> for TempFile {
})?; })?;
} }
file_async.flush().await.map_err(|err| { file_async
config.map_error(req, &field_name, TempFileError::FileIo(err)) .flush()
})?; .await
.map_err(|err| config.map_error(req, &field_name, TempFileError::FileIo(err)))?;
Ok(TempFile { Ok(TempFile {
file, file,
@ -131,12 +129,7 @@ impl TempFileConfig {
.unwrap_or(&DEFAULT_CONFIG) .unwrap_or(&DEFAULT_CONFIG)
} }
fn map_error( fn map_error(&self, req: &HttpRequest, field_name: &str, err: TempFileError) -> MultipartError {
&self,
req: &HttpRequest,
field_name: &str,
err: TempFileError,
) -> MultipartError {
let source = if let Some(ref err_handler) = self.err_handler { let source = if let Some(ref err_handler) = self.err_handler {
(err_handler)(err, req) (err_handler)(err, req)
} else { } else {

View file

@ -17,5 +17,7 @@ mod server;
pub mod form; pub mod form;
pub use self::error::MultipartError; pub use self::{
pub use self::server::{Field, Multipart}; error::MultipartError,
server::{Field, Multipart},
};

View file

@ -2,9 +2,7 @@
use std::{ use std::{
cell::{Cell, RefCell, RefMut}, cell::{Cell, RefCell, RefMut},
cmp, cmp, fmt,
convert::TryFrom,
fmt,
marker::PhantomData, marker::PhantomData,
pin::Pin, pin::Pin,
rc::Rc, rc::Rc,
@ -163,8 +161,8 @@ impl InnerMultipart {
for h in hdrs { for h in hdrs {
let name = let name =
HeaderName::try_from(h.name).map_err(|_| ParseError::Header)?; HeaderName::try_from(h.name).map_err(|_| ParseError::Header)?;
let value = HeaderValue::try_from(h.value) let value =
.map_err(|_| ParseError::Header)?; HeaderValue::try_from(h.value).map_err(|_| ParseError::Header)?;
headers.append(name, value); headers.append(name, value);
} }
@ -224,8 +222,7 @@ impl InnerMultipart {
if chunk.len() < boundary.len() { if chunk.len() < boundary.len() {
continue; continue;
} }
if &chunk[..2] == b"--" && &chunk[2..chunk.len() - 2] == boundary.as_bytes() if &chunk[..2] == b"--" && &chunk[2..chunk.len() - 2] == boundary.as_bytes() {
{
break; break;
} else { } else {
if chunk.len() < boundary.len() + 2 { if chunk.len() < boundary.len() + 2 {
@ -255,7 +252,7 @@ impl InnerMultipart {
fn poll( fn poll(
&mut self, &mut self,
safety: &Safety, safety: &Safety,
cx: &mut Context<'_>, cx: &Context<'_>,
) -> Poll<Option<Result<Field, MultipartError>>> { ) -> Poll<Option<Result<Field, MultipartError>>> {
if self.state == InnerState::Eof { if self.state == InnerState::Eof {
Poll::Ready(None) Poll::Ready(None)
@ -270,9 +267,7 @@ impl InnerMultipart {
match field.borrow_mut().poll(safety) { match field.borrow_mut().poll(safety) {
Poll::Pending => return Poll::Pending, Poll::Pending => return Poll::Pending,
Poll::Ready(Some(Ok(_))) => continue, Poll::Ready(Some(Ok(_))) => continue,
Poll::Ready(Some(Err(err))) => { Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))),
return Poll::Ready(Some(Err(err)))
}
Poll::Ready(None) => true, Poll::Ready(None) => true,
} }
} }
@ -291,8 +286,7 @@ impl InnerMultipart {
match self.state { match self.state {
// read until first boundary // read until first boundary
InnerState::FirstBoundary => { InnerState::FirstBoundary => {
match InnerMultipart::skip_until_boundary(&mut payload, &self.boundary)? match InnerMultipart::skip_until_boundary(&mut payload, &self.boundary)? {
{
Some(eof) => { Some(eof) => {
if eof { if eof {
self.state = InnerState::Eof; self.state = InnerState::Eof;
@ -669,9 +663,7 @@ impl InnerField {
Ok(None) => Poll::Pending, Ok(None) => Poll::Pending,
Ok(Some(line)) => { Ok(Some(line)) => {
if line.as_ref() != b"\r\n" { if line.as_ref() != b"\r\n" {
log::warn!( log::warn!("multipart field did not read all the data or it is malformed");
"multipart field did not read all the data or it is malformed"
);
} }
Poll::Ready(None) Poll::Ready(None)
} }
@ -748,7 +740,7 @@ impl Safety {
self.clean.get() self.clean.get()
} }
fn clone(&self, cx: &mut Context<'_>) -> Safety { fn clone(&self, cx: &Context<'_>) -> Safety {
let payload = Rc::clone(&self.payload); let payload = Rc::clone(&self.payload);
let s = Safety { let s = Safety {
task: LocalWaker::new(), task: LocalWaker::new(),

View file

@ -1,15 +1,17 @@
# Changes # Changes
## Unreleased - 2023-xx-xx ## Unreleased
## 0.5.1 - 2022-09-19 - Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
## 0.5.1
- Correct typo in error string for `i32` deserialization. [#2876] - Correct typo in error string for `i32` deserialization. [#2876]
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. - Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
[#2876]: https://github.com/actix/actix-web/pull/2876 [#2876]: https://github.com/actix/actix-web/pull/2876
## 0.5.0 - 2022-02-22 ## 0.5.0
### Added ### Added
@ -84,7 +86,7 @@
<details> <details>
<summary>0.5.0 Pre-Releases</summary> <summary>0.5.0 Pre-Releases</summary>
## 0.5.0-rc.3 - 2022-01-31 ## 0.5.0-rc.3
- Remove unused `ResourceInfo`. [#2612] - Remove unused `ResourceInfo`. [#2612]
- Add `RouterBuilder::push`. [#2612] - Add `RouterBuilder::push`. [#2612]
@ -96,32 +98,32 @@
[#2612]: https://github.com/actix/actix-web/pull/2612 [#2612]: https://github.com/actix/actix-web/pull/2612
[#2613]: https://github.com/actix/actix-web/pull/2613 [#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] - Add `Path::as_str`. [#2590]
- Deprecate `Path::path`. [#2590] - Deprecate `Path::path`. [#2590]
[#2590]: https://github.com/actix/actix-web/pull/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` trait now have an associated type, `Path`, instead of the generic parameter. [#2568]
- `Resource` is now implemented for `&mut Path<_>` and `RefMut<Path<_>>`. [#2568] - `Resource` is now implemented for `&mut Path<_>` and `RefMut<Path<_>>`. [#2568]
[#2568]: https://github.com/actix/actix-web/pull/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] - `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#2566]
- Minimum supported Rust version (MSRV) is now 1.54. - Minimum supported Rust version (MSRV) is now 1.54.
[#2566]: https://github.com/actix/actix-net/pull/2566 [#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. - 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] - Introduce `ResourceDef::join`. [#380][net#380]
- Disallow prefix routes with tail segments. [#379][net#379] - Disallow prefix routes with tail segments. [#379][net#379]
@ -141,7 +143,7 @@
[#2355]: https://github.com/actix/actix-web/pull/2355 [#2355]: https://github.com/actix/actix-web/pull/2355
[#2356]: https://github.com/actix/actix-web/pull/2356 [#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] - 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] - Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373][net#373]
@ -171,7 +173,7 @@
</details> </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] - 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] - Path tail patterns now match new lines (`\n`) in request URL. [#360][net#360]
@ -183,70 +185,70 @@
[net#359]: https://github.com/actix/actix-net/pull/359 [net#359]: https://github.com/actix/actix-net/pull/359
[net#360]: https://github.com/actix/actix-net/pull/360 [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 - 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] - Add `Router::recognize_checked` [#247][net#247]
[net#247]: https://github.com/actix/actix-net/pull/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] - Use `bytestring` version range compatible with Bytes v1.0. [#246][net#246]
[net#246]: https://github.com/actix/actix-net/pull/246 [net#246]: https://github.com/actix/actix-net/pull/246
## 0.2.5 - 2020-09-20 ## 0.2.5
- Fix `from_hex()` method - Fix `from_hex()` method
## 0.2.4 - 2019-12-31 ## 0.2.4
- Add `ResourceDef::resource_path_named()` path generation method - Add `ResourceDef::resource_path_named()` path generation method
## 0.2.3 - 2019-12-25 ## 0.2.3
- Add impl `IntoPattern` for `&String` - Add impl `IntoPattern` for `&String`
## 0.2.2 - 2019-12-25 ## 0.2.2
- Use `IntoPattern` for `RouterBuilder::path()` - Use `IntoPattern` for `RouterBuilder::path()`
## 0.2.1 - 2019-12-25 ## 0.2.1
- Add `IntoPattern` trait - Add `IntoPattern` trait
- Add multi-pattern resources - Add multi-pattern resources
## 0.2.0 - 2019-12-07 ## 0.2.0
- Update http to 0.2 - Update http to 0.2
- Update regex to 1.3 - Update regex to 1.3
- Use bytestring instead of string - Use bytestring instead of string
## 0.1.5 - 2019-05-15 ## 0.1.5
- Remove debug prints - Remove debug prints
## 0.1.4 - 2019-05-15 ## 0.1.4
- Fix checked resource match - Fix checked resource match
## 0.1.3 - 2019-04-22 ## 0.1.3
- Added support for `remainder match` (i.e "/path/{tail}\*") - Added support for `remainder match` (i.e "/path/{tail}\*")
## 0.1.2 - 2019-04-07 ## 0.1.2
- Export `Quoter` type - Export `Quoter` type
- Allow to reset `Path` instance - Allow to reset `Path` instance
## 0.1.1 - 2019-04-03 ## 0.1.1
- Get dynamic segment by name instead of iterator. - Get dynamic segment by name instead of iterator.
## 0.1.0 - 2019-03-09 ## 0.1.0
- Initial release - Initial release

View file

@ -10,7 +10,7 @@ description = "Resource path matching and router"
keywords = ["actix", "router", "routing"] keywords = ["actix", "router", "routing"]
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2021"
[lib] [lib]
name = "actix_router" name = "actix_router"
@ -27,7 +27,7 @@ serde = "1"
tracing = { version = "0.1.30", default-features = false, features = ["log"] } tracing = { version = "0.1.30", default-features = false, features = ["log"] }
[dev-dependencies] [dev-dependencies]
criterion = { version = "0.4", features = ["html_reports"] } criterion = { version = "0.5", features = ["html_reports"] }
http = "0.2.7" http = "0.2.7"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
percent-encoding = "2.1" percent-encoding = "2.1"

View file

@ -1,16 +1,17 @@
#![allow(clippy::uninlined_format_args)] #![allow(clippy::uninlined_format_args)]
use criterion::{black_box, criterion_group, criterion_main, Criterion}; use std::{borrow::Cow, fmt::Write as _};
use std::borrow::Cow; use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn compare_quoters(c: &mut Criterion) { fn compare_quoters(c: &mut Criterion) {
let mut group = c.benchmark_group("Compare Quoters"); let mut group = c.benchmark_group("Compare Quoters");
let quoter = actix_router::Quoter::new(b"", b""); let quoter = actix_router::Quoter::new(b"", b"");
let path_quoted = (0..=0x7f) let path_quoted = (0..=0x7f).fold(String::new(), |mut buf, c| {
.map(|c| format!("%{:02X}", c)) write!(&mut buf, "%{:02X}", c).unwrap();
.collect::<String>(); buf
});
let path_unquoted = ('\u{00}'..='\u{7f}').collect::<String>(); let path_unquoted = ('\u{00}'..='\u{7f}').collect::<String>();
group.bench_function("quoter_unquoted", |b| { group.bench_function("quoter_unquoted", |b| {

View file

@ -1,10 +1,14 @@
use std::borrow::Cow; use std::borrow::Cow;
use serde::de::{self, Deserializer, Error as DeError, Visitor}; use serde::{
use serde::forward_to_deserialize_any; de::{self, Deserializer, Error as DeError, Visitor},
forward_to_deserialize_any,
};
use crate::path::{Path, PathIter}; use crate::{
use crate::{Quoter, ResourcePath}; path::{Path, PathIter},
Quoter, ResourcePath,
};
thread_local! { thread_local! {
static FULL_QUOTER: Quoter = Quoter::new(b"", b""); static FULL_QUOTER: Quoter = Quoter::new(b"", b"");
@ -486,11 +490,7 @@ impl<'de> de::VariantAccess<'de> for UnitVariant {
Err(de::value::Error::custom("not supported")) Err(de::value::Error::custom("not supported"))
} }
fn struct_variant<V>( fn struct_variant<V>(self, _: &'static [&'static str], _: V) -> Result<V::Value, Self::Error>
self,
_: &'static [&'static str],
_: V,
) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
@ -503,9 +503,7 @@ mod tests {
use serde::{de, Deserialize}; use serde::{de, Deserialize};
use super::*; use super::*;
use crate::path::Path; use crate::{path::Path, router::Router, ResourceDef};
use crate::router::Router;
use crate::ResourceDef;
#[derive(Deserialize)] #[derive(Deserialize)]
struct MyStruct { struct MyStruct {
@ -572,13 +570,11 @@ mod tests {
assert_eq!(s.key, "name"); assert_eq!(s.key, "name");
assert_eq!(s.value, 32); assert_eq!(s.value, 32);
let s: (String, u8) = let s: (String, u8) = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
assert_eq!(s.0, "name"); assert_eq!(s.0, "name");
assert_eq!(s.1, 32); assert_eq!(s.1, 32);
let res: Vec<String> = let res: Vec<String> = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
assert_eq!(res[0], "name".to_owned()); assert_eq!(res[0], "name".to_owned());
assert_eq!(res[1], "32".to_owned()); assert_eq!(res[1], "32".to_owned());
} }

View file

@ -18,13 +18,14 @@ mod router;
#[cfg(feature = "http")] #[cfg(feature = "http")]
mod url; mod url;
pub use self::de::PathDeserializer;
pub use self::path::Path;
pub use self::pattern::{IntoPatterns, Patterns};
pub use self::quoter::Quoter;
pub use self::resource::ResourceDef;
pub use self::resource_path::{Resource, ResourcePath};
pub use self::router::{ResourceId, Router, RouterBuilder};
#[cfg(feature = "http")] #[cfg(feature = "http")]
pub use self::url::Url; pub use self::url::Url;
pub use self::{
de::PathDeserializer,
path::Path,
pattern::{IntoPatterns, Patterns},
quoter::Quoter,
resource::ResourceDef,
resource_path::{Resource, ResourcePath},
router::{ResourceId, Router, RouterBuilder},
};

View file

@ -1,5 +1,7 @@
use std::borrow::Cow; use std::{
use std::ops::{DerefMut, Index}; borrow::Cow,
ops::{DerefMut, Index},
};
use serde::de; use serde::de;

View file

@ -252,7 +252,7 @@ impl ResourceDef {
/// Multi-pattern resources can be constructed by providing a slice (or vec) of patterns. /// Multi-pattern resources can be constructed by providing a slice (or vec) of patterns.
/// ///
/// # Panics /// # Panics
/// Panics if path pattern is malformed. /// Panics if any path patterns are malformed.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
@ -501,7 +501,12 @@ impl ResourceDef {
let patterns = self let patterns = self
.pattern_iter() .pattern_iter()
.flat_map(move |this| other.pattern_iter().map(move |other| (this, other))) .flat_map(move |this| other.pattern_iter().map(move |other| (this, other)))
.map(|(this, other)| [this, other].join("")) .map(|(this, other)| {
let mut pattern = String::with_capacity(this.len() + other.len());
pattern.push_str(this);
pattern.push_str(other);
pattern
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
match patterns.len() { match patterns.len() {
@ -838,6 +843,7 @@ impl ResourceDef {
fn construct<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self { fn construct<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
let patterns = paths.patterns(); let patterns = paths.patterns();
let (pat_type, segments) = match &patterns { let (pat_type, segments) = match &patterns {
Patterns::Single(pattern) => ResourceDef::parse(pattern, is_prefix, false), Patterns::Single(pattern) => ResourceDef::parse(pattern, is_prefix, false),
@ -1389,8 +1395,6 @@ mod tests {
#[cfg(feature = "http")] #[cfg(feature = "http")]
#[test] #[test]
fn parse_urlencoded_param() { fn parse_urlencoded_param() {
use std::convert::TryFrom;
let re = ResourceDef::new("/user/{id}/test"); let re = ResourceDef::new("/user/{id}/test");
let mut path = Path::new("/user/2345/test"); let mut path = Path::new("/user/2345/test");
@ -1530,7 +1534,12 @@ mod tests {
assert!(!resource.resource_path_from_iter(&mut s, &mut ["item"].iter())); assert!(!resource.resource_path_from_iter(&mut s, &mut ["item"].iter()));
let mut s = String::new(); let mut s = String::new();
assert!(resource.resource_path_from_iter(&mut s, &mut vec!["item", "item2"].iter()));
assert!(resource.resource_path_from_iter(
&mut s,
#[allow(clippy::useless_vec)]
&mut vec!["item", "item2"].iter()
));
assert_eq!(s, "/user/item/item2/"); assert_eq!(s, "/user/item/item2/");
} }
@ -1743,9 +1752,7 @@ mod tests {
ResourceDef::new("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}"); ResourceDef::new("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}");
// panics // panics
ResourceDef::new( ResourceDef::new("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}");
"/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}",
);
} }
#[test] #[test]

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