mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-30 12:06:41 +02:00
Compare commits
10 Commits
test-v0.1.
...
pr/cptrodo
Author | SHA1 | Date | |
---|---|---|---|
|
0216cb186e | ||
|
2b543437c3 | ||
|
a3ec0ccf99 | ||
|
e9a1aeebce | ||
|
14b09e35d1 | ||
|
2bfc170fb0 | ||
|
42fd6290d6 | ||
|
c96844ab14 | ||
|
68adcf657a | ||
|
99bccb74ac |
@@ -1,6 +1,6 @@
|
||||
[alias]
|
||||
lint = "clippy --workspace --all-targets -- -Dclippy::todo"
|
||||
lint-all = "clippy --workspace --all-features --all-targets -- -Dclippy::todo"
|
||||
lint = "clippy --workspace --tests --examples --bins -- -Dclippy::todo"
|
||||
lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo"
|
||||
|
||||
# lib checking
|
||||
ci-check-min = "hack --workspace check --no-default-features"
|
||||
@@ -12,3 +12,6 @@ ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=
|
||||
# testing
|
||||
ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture"
|
||||
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
||||
|
||||
# compile docs as docs.rs would
|
||||
# RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace
|
||||
|
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -1,10 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
21
.github/workflows/bench.yml
vendored
21
.github/workflows/bench.yml
vendored
@@ -2,10 +2,11 @@ name: Benchmark
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -16,13 +17,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
run: |
|
||||
rustup set profile minimal
|
||||
rustup install nightly
|
||||
rustup override set nightly
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Check benchmark
|
||||
run: cargo bench --bench=server -- --sample-size=15
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: bench
|
||||
args: --bench=server -- --sample-size=15
|
||||
|
108
.github/workflows/ci-post-merge.yml
vendored
108
.github/workflows/ci-post-merge.yml
vendored
@@ -5,7 +5,7 @@ on:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -16,53 +16,65 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# prettier-ignore
|
||||
target:
|
||||
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
|
||||
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
|
||||
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
|
||||
- { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc }
|
||||
version:
|
||||
- { name: nightly, version: nightly }
|
||||
- nightly
|
||||
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
||||
runs-on: ${{ matrix.target.os }}
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
CARGO_INCREMENTAL: 0
|
||||
VCPKGRS_DYNAMIC: 1
|
||||
CARGO_UNSTABLE_SPARSE_REGISTRY: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# 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
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
choco install openssl --version=1.1.1.2100 -y --no-progress
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
|
||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||
if: matrix.target.triple == 'x86_64-pc-windows-msvc'
|
||||
run: vcpkg install openssl:x64-windows
|
||||
|
||||
- name: Install Rust (${{ matrix.version.name }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
- name: Install ${{ matrix.version }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.version.version }}
|
||||
toolchain: ${{ matrix.version }}
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Install cargo-hack and cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.26.8
|
||||
with:
|
||||
tool: cargo-hack,cargo-ci-cache-clean
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
|
||||
- name: Generate Cargo.lock
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: generate-lockfile }
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v1.2.0
|
||||
|
||||
- name: check minimal
|
||||
run: cargo ci-check-min
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-min }
|
||||
|
||||
- name: check default
|
||||
run: cargo ci-check-default
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-default }
|
||||
|
||||
- name: tests
|
||||
timeout-minutes: 60
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
cargo test --lib --tests -p=actix-router --all-features
|
||||
cargo test --lib --tests -p=actix-http --all-features
|
||||
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,rustls-0_22,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
|
||||
cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
|
||||
cargo test --lib --tests -p=actix-web-codegen --all-features
|
||||
cargo test --lib --tests -p=awc --all-features
|
||||
cargo test --lib --tests -p=actix-http-test --all-features
|
||||
@@ -71,23 +83,31 @@ jobs:
|
||||
cargo test --lib --tests -p=actix-multipart --all-features
|
||||
cargo test --lib --tests -p=actix-web-actors --all-features
|
||||
|
||||
- name: CI cache clean
|
||||
run: cargo-ci-cache-clean
|
||||
- name: Clear the cargo caches
|
||||
run: |
|
||||
cargo install cargo-cache --version 0.8.3 --no-default-features --features ci-autoclean
|
||||
cargo-cache
|
||||
|
||||
ci_feature_powerset_check:
|
||||
name: Verify Feature Combinations
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
env:
|
||||
CI: 1
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@v2.26.8
|
||||
with:
|
||||
tool: cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
|
||||
- name: Generate Cargo.lock
|
||||
run: cargo generate-lockfile
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v1.2.0
|
||||
|
||||
- name: check feature combinations
|
||||
run: cargo ci-check-all-feature-powerset
|
||||
@@ -99,16 +119,22 @@ jobs:
|
||||
name: nextest
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
env:
|
||||
CI: 1
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@v2.26.8
|
||||
with:
|
||||
tool: nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Generate Cargo.lock
|
||||
run: cargo generate-lockfile
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v1.3.0
|
||||
|
||||
- name: Test with cargo-nextest
|
||||
run: cargo nextest run
|
||||
|
83
.github/workflows/ci.yml
vendored
83
.github/workflows/ci.yml
vendored
@@ -3,13 +3,11 @@ name: CI
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -20,63 +18,63 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# prettier-ignore
|
||||
target:
|
||||
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
|
||||
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
|
||||
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
|
||||
version:
|
||||
- { name: msrv, version: 1.68.0 }
|
||||
- { name: stable, version: stable }
|
||||
- 1.59.0 # MSRV
|
||||
- stable
|
||||
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
||||
runs-on: ${{ matrix.target.os }}
|
||||
|
||||
env: {}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
choco install openssl --version=1.1.1.2100 -y --no-progress
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
|
||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||
run: choco install openssl
|
||||
- name: Set OpenSSL dir in env
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
run: echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
|
||||
- name: Install Rust (${{ matrix.version.name }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
- name: Install Rust (${{ matrix.version }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.version.version }}
|
||||
toolchain: ${{ matrix.version }}
|
||||
|
||||
- name: Install cargo-hack and cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.26.8
|
||||
with:
|
||||
tool: cargo-hack,cargo-ci-cache-clean
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
|
||||
- name: workaround MSRV issues
|
||||
if: matrix.version.name == 'msrv'
|
||||
if: matrix.version != 'stable'
|
||||
run: |
|
||||
cargo update -p=ciborium --precise=0.2.1
|
||||
cargo update -p=ciborium-ll --precise=0.2.1
|
||||
cargo update -p=clap --precise=4.3.24
|
||||
cargo update -p=clap_lex --precise=0.5.0
|
||||
cargo update -p=anstyle --precise=1.0.2
|
||||
cargo install cargo-edit --version=0.8.0
|
||||
cargo add const-str@0.3 --dev -p=actix-web
|
||||
cargo add const-str@0.3 --dev -p=awc
|
||||
|
||||
- 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
|
||||
run: cargo ci-check-min
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-min }
|
||||
|
||||
- name: check default
|
||||
run: cargo ci-check-default
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-default }
|
||||
|
||||
- name: tests
|
||||
timeout-minutes: 60
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
cargo test --lib --tests -p=actix-router --all-features
|
||||
cargo test --lib --tests -p=actix-http --all-features
|
||||
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,rustls-0_22,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
|
||||
cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
|
||||
cargo test --lib --tests -p=actix-web-codegen --all-features
|
||||
cargo test --lib --tests -p=awc --all-features
|
||||
cargo test --lib --tests -p=actix-http-test --all-features
|
||||
@@ -85,35 +83,36 @@ jobs:
|
||||
cargo test --lib --tests -p=actix-multipart --all-features
|
||||
cargo test --lib --tests -p=actix-web-actors --all-features
|
||||
|
||||
- name: CI cache clean
|
||||
run: cargo-ci-cache-clean
|
||||
- name: Clear the cargo caches
|
||||
run: |
|
||||
cargo install cargo-cache --version 0.8.3 --no-default-features --features ci-autoclean
|
||||
cargo-cache
|
||||
|
||||
io-uring:
|
||||
name: io-uring tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { toolchain: nightly }
|
||||
|
||||
- name: tests (io-uring)
|
||||
timeout-minutes: 60
|
||||
run: >
|
||||
sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=stable cargo test --lib --tests -p=actix-files --all-features"
|
||||
|
||||
|
||||
rustdoc:
|
||||
name: doc tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { toolchain: nightly }
|
||||
|
||||
- name: doc tests
|
||||
run: cargo ci-doctest
|
||||
|
75
.github/workflows/clippy-fmt.yml
vendored
Normal file
75
.github/workflows/clippy-fmt.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
|
||||
- run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
permissions:
|
||||
checks: write # to add clippy checks to PR diffs
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { components: clippy }
|
||||
|
||||
- uses: giraffate/clippy-action@v1
|
||||
with:
|
||||
reporter: 'github-pr-check'
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
clippy_flags: --workspace --all-features --tests --examples --bins -- -Dclippy::todo
|
||||
|
||||
lint-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { components: rust-docs }
|
||||
|
||||
- name: Check for broken intra-doc links
|
||||
env: { RUSTDOCFLAGS: "-D warnings" }
|
||||
run: cargo doc --no-deps --all-features --workspace
|
||||
|
||||
public-api-diff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { toolchain: nightly }
|
||||
|
||||
- uses: taiki-e/cache-cargo-install-action@v1
|
||||
with: { tool: cargo-public-api }
|
||||
|
||||
- name: generate API diff
|
||||
run: |
|
||||
for f in $(find -mindepth 2 -maxdepth 2 -name Cargo.toml); do
|
||||
cargo public-api --manifest-path "$f" diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }}
|
||||
done
|
36
.github/workflows/coverage.yml
vendored
36
.github/workflows/coverage.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# disabled because `cargo tarpaulin` currently segfaults
|
||||
|
||||
name: Coverage
|
||||
|
||||
on:
|
||||
@@ -5,35 +7,27 @@ on:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# job currently (1st Feb 2022) segfaults
|
||||
coverage:
|
||||
name: coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { toolchain: nightly }
|
||||
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@v2.26.8
|
||||
with:
|
||||
tool: cargo-llvm-cov
|
||||
|
||||
- name: Generate code coverage
|
||||
run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4.0.0
|
||||
with:
|
||||
files: codecov.json
|
||||
fail_ci_if_error: true
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Generate coverage file
|
||||
run: |
|
||||
cargo install cargo-tarpaulin --vers "^0.13"
|
||||
cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@v1
|
||||
with: { file: cobertura.xml }
|
||||
|
93
.github/workflows/lint.yml
vendored
93
.github/workflows/lint.yml
vendored
@@ -1,93 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
|
||||
- name: Check with Rustfmt
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write # to add clippy checks to PR diffs
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Check with Clippy
|
||||
uses: giraffate/clippy-action@v1.0.1
|
||||
with:
|
||||
reporter: github-pr-check
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
clippy_flags: >-
|
||||
--workspace --all-features --tests --examples --bins --
|
||||
-A unknown_lints -D clippy::todo -D clippy::dbg_macro
|
||||
|
||||
lint-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rust-docs
|
||||
|
||||
- name: Check for broken intra-doc links
|
||||
env:
|
||||
RUSTDOCFLAGS: -D warnings
|
||||
run: cargo +nightly doc --no-deps --workspace --all-features
|
||||
|
||||
public-api-diff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout main branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
- name: Checkout PR branch
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
with:
|
||||
toolchain: nightly-2023-08-25
|
||||
|
||||
- name: Install cargo-public-api
|
||||
uses: taiki-e/install-action@v2.26.8
|
||||
with:
|
||||
tool: cargo-public-api
|
||||
|
||||
- name: Generate API diff
|
||||
run: |
|
||||
for f in $(find -mindepth 2 -maxdepth 2 -name Cargo.toml); do
|
||||
cargo public-api --manifest-path "$f" diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }}
|
||||
done
|
13
.github/workflows/upload-doc.yml
vendored
13
.github/workflows/upload-doc.yml
vendored
@@ -5,7 +5,7 @@ on:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -14,17 +14,14 @@ concurrency:
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: write
|
||||
contents: write # to push changes in repo (jamesives/github-pages-deploy-action)
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
|
||||
- name: Build Docs
|
||||
run: cargo +nightly doc --no-deps --workspace --all-features
|
||||
@@ -35,7 +32,7 @@ jobs:
|
||||
run: echo '<meta http-equiv="refresh" content="0;url=actix_web/index.html">' > target/doc/index.html
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: JamesIves/github-pages-deploy-action@v4.5.0
|
||||
uses: JamesIves/github-pages-deploy-action@v4.4.1
|
||||
with:
|
||||
folder: target/doc
|
||||
single-commit: true
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -19,7 +19,3 @@ guide/build/
|
||||
|
||||
# Configuration directory generated by VSCode
|
||||
.vscode
|
||||
|
||||
# code coverage
|
||||
/lcov.info
|
||||
/codecov.json
|
||||
|
1
.prettierrc.yaml
Normal file
1
.prettierrc.yaml
Normal file
@@ -0,0 +1 @@
|
||||
proseWrap: never
|
@@ -1,5 +0,0 @@
|
||||
overrides:
|
||||
- files: "*.md"
|
||||
options:
|
||||
printWidth: 9999
|
||||
proseWrap: never
|
@@ -1,3 +0,0 @@
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Crate"
|
||||
use_field_init_shorthand = true
|
@@ -14,11 +14,6 @@ members = [
|
||||
"awc",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.68"
|
||||
|
||||
[profile.dev]
|
||||
# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much.
|
||||
debug = 0
|
||||
|
@@ -1,17 +1,8 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased
|
||||
## Unreleased - 2023-xx-xx
|
||||
|
||||
## 0.6.5
|
||||
|
||||
- Fix handling of special characters in filenames.
|
||||
|
||||
## 0.6.4
|
||||
|
||||
- Fix handling of newlines in filenames.
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 0.6.3
|
||||
## 0.6.3 - 2023-01-21
|
||||
|
||||
- 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.
|
||||
@@ -19,14 +10,14 @@
|
||||
|
||||
[#2903]: https://github.com/actix/actix-web/pull/2903
|
||||
|
||||
## 0.6.2
|
||||
## 0.6.2 - 2022-07-23
|
||||
|
||||
- Allow partial range responses for video content to start streaming sooner. [#2817]
|
||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||
|
||||
[#2817]: https://github.com/actix/actix-web/pull/2817
|
||||
|
||||
## 0.6.1
|
||||
## 0.6.1 - 2022-06-11
|
||||
|
||||
- Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021]
|
||||
- Update `tokio-uring` dependency to `0.3`.
|
||||
@@ -36,25 +27,25 @@
|
||||
[#2021]: https://github.com/actix/actix-web/pull/2021
|
||||
[#2645]: https://github.com/actix/actix-web/pull/2645
|
||||
|
||||
## 0.6.0
|
||||
## 0.6.0 - 2022-02-25
|
||||
|
||||
- No significant changes since `0.6.0-beta.16`.
|
||||
|
||||
## 0.6.0-beta.16
|
||||
## 0.6.0-beta.16 - 2022-01-31
|
||||
|
||||
- No significant changes since `0.6.0-beta.15`.
|
||||
|
||||
## 0.6.0-beta.15
|
||||
## 0.6.0-beta.15 - 2022-01-21
|
||||
|
||||
- No significant changes since `0.6.0-beta.14`.
|
||||
|
||||
## 0.6.0-beta.14
|
||||
## 0.6.0-beta.14 - 2022-01-14
|
||||
|
||||
- The `prefer_utf8` option introduced in `0.4.0` is now true by default. [#2583]
|
||||
|
||||
[#2583]: https://github.com/actix/actix-web/pull/2583
|
||||
|
||||
## 0.6.0-beta.13
|
||||
## 0.6.0-beta.13 - 2022-01-04
|
||||
|
||||
- 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]
|
||||
@@ -62,19 +53,19 @@
|
||||
|
||||
[#2398]: https://github.com/actix/actix-web/pull/2398
|
||||
|
||||
## 0.6.0-beta.12
|
||||
## 0.6.0-beta.12 - 2021-12-29
|
||||
|
||||
- No significant changes since `0.6.0-beta.11`.
|
||||
|
||||
## 0.6.0-beta.11
|
||||
## 0.6.0-beta.11 - 2021-12-27
|
||||
|
||||
- No significant changes since `0.6.0-beta.10`.
|
||||
|
||||
## 0.6.0-beta.10
|
||||
## 0.6.0-beta.10 - 2021-12-11
|
||||
|
||||
- No significant changes since `0.6.0-beta.9`.
|
||||
|
||||
## 0.6.0-beta.9
|
||||
## 0.6.0-beta.9 - 2021-11-22
|
||||
|
||||
- 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]
|
||||
@@ -86,15 +77,15 @@
|
||||
[#2408]: https://github.com/actix/actix-web/pull/2408
|
||||
[#2453]: https://github.com/actix/actix-web/pull/2453
|
||||
|
||||
## 0.6.0-beta.8
|
||||
## 0.6.0-beta.8 - 2021-10-20
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
## 0.6.0-beta.7
|
||||
## 0.6.0-beta.7 - 2021-09-09
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
||||
## 0.6.0-beta.6
|
||||
## 0.6.0-beta.6 - 2021-06-26
|
||||
|
||||
- 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]
|
||||
@@ -102,7 +93,7 @@
|
||||
[#2274]: https://github.com/actix/actix-web/pull/2274
|
||||
[#2228]: https://github.com/actix/actix-web/pull/2228
|
||||
|
||||
## 0.6.0-beta.5
|
||||
## 0.6.0-beta.5 - 2021-06-17
|
||||
|
||||
- `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]
|
||||
@@ -114,17 +105,17 @@
|
||||
[#2225]: https://github.com/actix/actix-web/pull/2225
|
||||
[#2257]: https://github.com/actix/actix-web/pull/2257
|
||||
|
||||
## 0.6.0-beta.4
|
||||
## 0.6.0-beta.4 - 2021-04-02
|
||||
|
||||
- Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046]
|
||||
|
||||
[#2046]: https://github.com/actix/actix-web/pull/2046
|
||||
|
||||
## 0.6.0-beta.3
|
||||
## 0.6.0-beta.3 - 2021-03-09
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 0.6.0-beta.2
|
||||
## 0.6.0-beta.2 - 2021-02-10
|
||||
|
||||
- Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
|
||||
- Replace `v_htmlescape` with `askama_escape`. [#1953]
|
||||
@@ -132,39 +123,39 @@
|
||||
[#1887]: https://github.com/actix/actix-web/pull/1887
|
||||
[#1953]: https://github.com/actix/actix-web/pull/1953
|
||||
|
||||
## 0.6.0-beta.1
|
||||
## 0.6.0-beta.1 - 2021-01-07
|
||||
|
||||
- `HttpRange::parse` now has its own error type.
|
||||
- Update `bytes` to `1.0`. [#1813]
|
||||
|
||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||
|
||||
## 0.5.0
|
||||
## 0.5.0 - 2020-12-26
|
||||
|
||||
- Optionally support hidden files/directories. [#1811]
|
||||
|
||||
[#1811]: https://github.com/actix/actix-web/pull/1811
|
||||
|
||||
## 0.4.1
|
||||
## 0.4.1 - 2020-11-24
|
||||
|
||||
- Clarify order of parameters in `Files::new` and improve docs.
|
||||
|
||||
## 0.4.0
|
||||
## 0.4.0 - 2020-10-06
|
||||
|
||||
- Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714]
|
||||
|
||||
[#1714]: https://github.com/actix/actix-web/pull/1714
|
||||
|
||||
## 0.3.0
|
||||
## 0.3.0 - 2020-09-11
|
||||
|
||||
- No significant changes from 0.3.0-beta.1.
|
||||
|
||||
## 0.3.0-beta.1
|
||||
## 0.3.0-beta.1 - 2020-07-15
|
||||
|
||||
- Update `v_htmlescape` to 0.10
|
||||
- Update `actix-web` and `actix-http` dependencies to beta.1
|
||||
|
||||
## 0.3.0-alpha.1
|
||||
## 0.3.0-alpha.1 - 2020-05-23
|
||||
|
||||
- Update `actix-web` and `actix-http` dependencies to alpha
|
||||
- Fix some typos in the docs
|
||||
@@ -173,73 +164,73 @@
|
||||
|
||||
[#1384]: https://github.com/actix/actix-web/pull/1384
|
||||
|
||||
## 0.2.1
|
||||
## 0.2.1 - 2019-12-22
|
||||
|
||||
- Use the same format for file URLs regardless of platforms
|
||||
|
||||
## 0.2.0
|
||||
## 0.2.0 - 2019-12-20
|
||||
|
||||
- Fix BodyEncoding trait import #1220
|
||||
|
||||
## 0.2.0-alpha.1
|
||||
## 0.2.0-alpha.1 - 2019-12-07
|
||||
|
||||
- Migrate to `std::future`
|
||||
|
||||
## 0.1.7
|
||||
## 0.1.7 - 2019-11-06
|
||||
|
||||
- Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151)
|
||||
|
||||
## 0.1.6
|
||||
## 0.1.6 - 2019-10-14
|
||||
|
||||
- Add option to redirect to a slash-ended path `Files` #1132
|
||||
|
||||
## 0.1.5
|
||||
## 0.1.5 - 2019-10-08
|
||||
|
||||
- Bump up `mime_guess` crate version to 2.0.1
|
||||
- Bump up `percent-encoding` crate version to 2.1
|
||||
- Allow user defined request guards for `Files` #1113
|
||||
|
||||
## 0.1.4
|
||||
## 0.1.4 - 2019-07-20
|
||||
|
||||
- Allow to disable `Content-Disposition` header #686
|
||||
|
||||
## 0.1.3
|
||||
## 0.1.3 - 2019-06-28
|
||||
|
||||
- Do not set `Content-Length` header, let actix-http set it #930
|
||||
|
||||
## 0.1.2
|
||||
## 0.1.2 - 2019-06-13
|
||||
|
||||
- Content-Length is 0 for NamedFile HEAD request #914
|
||||
- Fix ring dependency from actix-web default features for #741
|
||||
|
||||
## 0.1.1
|
||||
## 0.1.1 - 2019-06-01
|
||||
|
||||
- Static files are incorrectly served as both chunked and with length #812
|
||||
|
||||
## 0.1.0
|
||||
## 0.1.0 - 2019-05-25
|
||||
|
||||
- NamedFile last-modified check always fails due to nano-seconds in file modified date #820
|
||||
|
||||
## 0.1.0-beta.4
|
||||
## 0.1.0-beta.4 - 2019-05-12
|
||||
|
||||
- Update actix-web to beta.4
|
||||
|
||||
## 0.1.0-beta.1
|
||||
## 0.1.0-beta.1 - 2019-04-20
|
||||
|
||||
- Update actix-web to beta.1
|
||||
|
||||
## 0.1.0-alpha.6
|
||||
## 0.1.0-alpha.6 - 2019-04-14
|
||||
|
||||
- Update actix-web to alpha6
|
||||
|
||||
## 0.1.0-alpha.4
|
||||
## 0.1.0-alpha.4 - 2019-04-08
|
||||
|
||||
- Update actix-web to alpha4
|
||||
|
||||
## 0.1.0-alpha.2
|
||||
## 0.1.0-alpha.2 - 2019-04-02
|
||||
|
||||
- Add default handler support
|
||||
|
||||
## 0.1.0-alpha.1
|
||||
## 0.1.0-alpha.1 - 2019-03-28
|
||||
|
||||
- Initial impl
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-files"
|
||||
version = "0.6.5"
|
||||
version = "0.6.3"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@@ -11,7 +11,7 @@ homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
categories = ["asynchronous", "web-programming::http-server"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_files"
|
||||
@@ -26,7 +26,7 @@ actix-service = "2"
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4", default-features = false }
|
||||
|
||||
bitflags = "2"
|
||||
bitflags = "1"
|
||||
bytes = "1"
|
||||
derive_more = "0.99.5"
|
||||
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
@@ -47,5 +47,4 @@ actix-server = { version = "2.2", optional = true } # ensure matching tokio-urin
|
||||
actix-rt = "2.7"
|
||||
actix-test = "0.1"
|
||||
actix-web = "4"
|
||||
env_logger = "0.10"
|
||||
tempfile = "3.2"
|
||||
|
@@ -1,32 +1,18 @@
|
||||
# `actix-files`
|
||||
# actix-files
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
> Static file serving for Actix Web
|
||||
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://docs.rs/actix-files/0.6.5)
|
||||

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

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-files/0.6.5)
|
||||
[](https://deps.rs/crate/actix-files/0.6.3)
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
## Documentation & Resources
|
||||
|
||||
<!-- cargo-rdme start -->
|
||||
|
||||
Static file serving for Actix Web.
|
||||
|
||||
Provides a non-blocking service for serving static files from disk.
|
||||
|
||||
## Examples
|
||||
|
||||
```rust
|
||||
use actix_web::App;
|
||||
use actix_files::Files;
|
||||
|
||||
let app = App::new()
|
||||
.service(Files::new("/static", ".").prefer_utf8(true));
|
||||
```
|
||||
|
||||
<!-- cargo-rdme end -->
|
||||
- [API Documentation](https://docs.rs/actix-files)
|
||||
- [Example Project](https://github.com/actix/examples/tree/master/basics/static-files)
|
||||
- Minimum Supported Rust Version (MSRV): 1.59
|
||||
|
@@ -1,33 +0,0 @@
|
||||
use actix_files::Files;
|
||||
use actix_web::{get, guard, middleware, App, HttpServer, Responder};
|
||||
|
||||
const EXAMPLES_DIR: &str = concat![env!("CARGO_MANIFEST_DIR"), "/examples"];
|
||||
|
||||
#[get("/")]
|
||||
async fn index() -> impl Responder {
|
||||
"Hello world!"
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||
|
||||
log::info!("starting HTTP server at http://localhost:8080");
|
||||
|
||||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.service(index)
|
||||
.service(
|
||||
Files::new("/assets", EXAMPLES_DIR)
|
||||
.show_files_listing()
|
||||
.guard(guard::Header("show-listing", "?1")),
|
||||
)
|
||||
.service(Files::new("/assets", EXAMPLES_DIR))
|
||||
.wrap(middleware::Compress::default())
|
||||
.wrap(middleware::Logger::default())
|
||||
})
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.workers(2)
|
||||
.run()
|
||||
.await
|
||||
}
|
@@ -7,11 +7,12 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_web::{error::Error, web::Bytes};
|
||||
#[cfg(feature = "experimental-io-uring")]
|
||||
use bytes::BytesMut;
|
||||
use futures_core::{ready, Stream};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
#[cfg(feature = "experimental-io-uring")]
|
||||
use bytes::BytesMut;
|
||||
|
||||
use super::named::File;
|
||||
|
||||
pin_project! {
|
||||
|
@@ -1,9 +1,4 @@
|
||||
use std::{
|
||||
fmt::Write,
|
||||
fs::DirEntry,
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::{fmt::Write, fs::DirEntry, io, path::Path, path::PathBuf};
|
||||
|
||||
use actix_web::{dev::ServiceResponse, HttpRequest, HttpResponse};
|
||||
use percent_encoding::{utf8_percent_encode, CONTROLS};
|
||||
|
@@ -8,7 +8,8 @@ use std::{
|
||||
use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt};
|
||||
use actix_web::{
|
||||
dev::{
|
||||
AppService, HttpServiceFactory, RequestHead, ResourceDef, ServiceRequest, ServiceResponse,
|
||||
AppService, HttpServiceFactory, RequestHead, ResourceDef, ServiceRequest,
|
||||
ServiceResponse,
|
||||
},
|
||||
error::Error,
|
||||
guard::Guard,
|
||||
@@ -235,7 +236,7 @@ impl Files {
|
||||
/// request starts being handled by the file service, it will not be able to back-out and try
|
||||
/// the next service, you will simply get a 404 (or 405) error response.
|
||||
///
|
||||
/// To allow `POST` requests to retrieve files, see [`Files::method_guard()`].
|
||||
/// To allow `POST` requests to retrieve files, see [`Files::use_guards`].
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
@@ -300,8 +301,12 @@ impl Files {
|
||||
pub fn default_handler<F, U>(mut self, f: F) -> Self
|
||||
where
|
||||
F: IntoServiceFactory<U, ServiceRequest>,
|
||||
U: ServiceFactory<ServiceRequest, Config = (), Response = ServiceResponse, Error = Error>
|
||||
+ 'static,
|
||||
U: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
> + 'static,
|
||||
{
|
||||
// create and configure default resource
|
||||
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
|
||||
@@ -417,14 +422,10 @@ mod tests {
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
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!(
|
||||
actual_path.ends_with(expected_path),
|
||||
"body {:?} does not end with {:?}",
|
||||
actual_path,
|
||||
expected_path
|
||||
body.ends_with(b"actix-files/tests/"),
|
||||
"body {:?} does not end with `actix-files/tests/`",
|
||||
body
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -13,12 +13,11 @@
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use actix_service::boxed::{BoxService, BoxServiceFactory};
|
||||
use actix_web::{
|
||||
dev::{RequestHead, ServiceRequest, ServiceResponse},
|
||||
@@ -26,6 +25,7 @@ use actix_web::{
|
||||
http::header::DispositionType,
|
||||
};
|
||||
use mime_guess::from_ext;
|
||||
use std::path::Path;
|
||||
|
||||
mod chunked;
|
||||
mod directory;
|
||||
@@ -37,15 +37,16 @@ mod path_buf;
|
||||
mod range;
|
||||
mod service;
|
||||
|
||||
pub use self::{
|
||||
chunked::ChunkedReadFile, directory::Directory, files::Files, named::NamedFile,
|
||||
range::HttpRange, service::FilesService,
|
||||
};
|
||||
use self::{
|
||||
directory::{directory_listing, DirectoryRenderer},
|
||||
error::FilesError,
|
||||
path_buf::PathBufWrap,
|
||||
};
|
||||
pub use self::chunked::ChunkedReadFile;
|
||||
pub use self::directory::Directory;
|
||||
pub use self::files::Files;
|
||||
pub use self::named::NamedFile;
|
||||
pub use self::range::HttpRange;
|
||||
pub use self::service::FilesService;
|
||||
|
||||
use self::directory::{directory_listing, DirectoryRenderer};
|
||||
use self::error::FilesError;
|
||||
use self::path_buf::PathBufWrap;
|
||||
|
||||
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
||||
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
@@ -65,7 +66,6 @@ type PathFilter = dyn Fn(&Path, &RequestHead) -> bool;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
fmt::Write as _,
|
||||
fs::{self},
|
||||
ops::Add,
|
||||
time::{Duration, SystemTime},
|
||||
@@ -554,9 +554,10 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_static_files_with_spaces() {
|
||||
let srv =
|
||||
test::init_service(App::new().service(Files::new("/", ".").index_file("Cargo.toml")))
|
||||
.await;
|
||||
let srv = test::init_service(
|
||||
App::new().service(Files::new("/", ".").index_file("Cargo.toml")),
|
||||
)
|
||||
.await;
|
||||
let request = TestRequest::get()
|
||||
.uri("/tests/test%20space.binary")
|
||||
.to_request();
|
||||
@@ -568,30 +569,6 @@ mod tests {
|
||||
assert_eq!(bytes, data);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[actix_rt::test]
|
||||
async fn test_static_files_with_special_characters() {
|
||||
// Create the file we want to test against ad-hoc. We can't check it in as otherwise
|
||||
// Windows can't even checkout this repository.
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let file_with_newlines = temp_dir.path().join("test\n\x0B\x0C\rnewline.text");
|
||||
fs::write(&file_with_newlines, "Look at my newlines").unwrap();
|
||||
|
||||
let srv = test::init_service(
|
||||
App::new().service(Files::new("/", temp_dir.path()).index_file("Cargo.toml")),
|
||||
)
|
||||
.await;
|
||||
let request = TestRequest::get()
|
||||
.uri("/test%0A%0B%0C%0Dnewline.text")
|
||||
.to_request();
|
||||
let response = test::call_service(&srv, request).await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let bytes = test::read_body(response).await;
|
||||
let data = web::Bytes::from(fs::read(file_with_newlines).unwrap());
|
||||
assert_eq!(bytes, data);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_files_not_allowed() {
|
||||
let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
|
||||
@@ -690,7 +667,8 @@ mod tests {
|
||||
#[actix_rt::test]
|
||||
async fn test_static_files() {
|
||||
let srv =
|
||||
test::init_service(App::new().service(Files::new("/", ".").show_files_listing())).await;
|
||||
test::init_service(App::new().service(Files::new("/", ".").show_files_listing()))
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/missing").to_request();
|
||||
|
||||
let resp = test::call_service(&srv, req).await;
|
||||
@@ -703,7 +681,8 @@ mod tests {
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let srv =
|
||||
test::init_service(App::new().service(Files::new("/", ".").show_files_listing())).await;
|
||||
test::init_service(App::new().service(Files::new("/", ".").show_files_listing()))
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/tests").to_request();
|
||||
let resp = test::call_service(&srv, req).await;
|
||||
assert_eq!(
|
||||
@@ -864,21 +843,19 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_percent_encoding_2() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let tmpdir = tempfile::tempdir().unwrap();
|
||||
let filename = match cfg!(unix) {
|
||||
true => "ض:?#[]{}<>()@!$&'`|*+,;= %20\n.test",
|
||||
true => "ض:?#[]{}<>()@!$&'`|*+,;= %20.test",
|
||||
false => "ض#[]{}()@!$&'`+,;= %20.test",
|
||||
};
|
||||
let filename_encoded = filename
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.fold(String::new(), |mut buf, c| {
|
||||
write!(&mut buf, "%{:02X}", c).unwrap();
|
||||
buf
|
||||
});
|
||||
std::fs::File::create(temp_dir.path().join(filename)).unwrap();
|
||||
.map(|c| format!("%{:02X}", c))
|
||||
.collect::<String>();
|
||||
std::fs::File::create(tmpdir.path().join(filename)).unwrap();
|
||||
|
||||
let srv = test::init_service(App::new().service(Files::new("/", temp_dir.path()))).await;
|
||||
let srv = test::init_service(App::new().service(Files::new("", tmpdir.path()))).await;
|
||||
|
||||
let req = TestRequest::get()
|
||||
.uri(&format!("/{}", filename_encoded))
|
||||
|
@@ -8,13 +8,13 @@ use std::{
|
||||
use actix_web::{
|
||||
body::{self, BoxBody, SizedStream},
|
||||
dev::{
|
||||
self, AppService, HttpServiceFactory, ResourceDef, Service, ServiceFactory, ServiceRequest,
|
||||
ServiceResponse,
|
||||
self, AppService, HttpServiceFactory, ResourceDef, Service, ServiceFactory,
|
||||
ServiceRequest, ServiceResponse,
|
||||
},
|
||||
http::{
|
||||
header::{
|
||||
self, Charset, ContentDisposition, ContentEncoding, DispositionParam, DispositionType,
|
||||
ExtendedValue, HeaderValue,
|
||||
self, Charset, ContentDisposition, ContentEncoding, DispositionParam,
|
||||
DispositionType, ExtendedValue, HeaderValue,
|
||||
},
|
||||
StatusCode,
|
||||
},
|
||||
@@ -24,11 +24,11 @@ use bitflags::bitflags;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use mime::Mime;
|
||||
use mime_guess::from_path;
|
||||
|
||||
use crate::{encoding::equiv_utf8_text, range::HttpRange};
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct Flags: u8 {
|
||||
const ETAG = 0b0000_0001;
|
||||
const LAST_MD = 0b0000_0010;
|
||||
@@ -84,7 +84,6 @@ pub struct NamedFile {
|
||||
|
||||
#[cfg(not(feature = "experimental-io-uring"))]
|
||||
pub(crate) use std::fs::File;
|
||||
|
||||
#[cfg(feature = "experimental-io-uring")]
|
||||
pub(crate) use tokio_uring::fs::File;
|
||||
|
||||
@@ -127,7 +126,7 @@ impl NamedFile {
|
||||
}
|
||||
};
|
||||
|
||||
let ct = mime_guess::from_path(&path).first_or_octet_stream();
|
||||
let ct = from_path(&path).first_or_octet_stream();
|
||||
|
||||
let disposition = match ct.type_() {
|
||||
mime::IMAGE | mime::TEXT | mime::AUDIO | mime::VIDEO => DispositionType::Inline,
|
||||
@@ -139,13 +138,8 @@ impl NamedFile {
|
||||
_ => DispositionType::Attachment,
|
||||
};
|
||||
|
||||
// replace special characters in filenames which could occur on some filesystems
|
||||
let filename_s = filename
|
||||
.replace('\n', "%0A") // \n line break
|
||||
.replace('\x0B', "%0B") // \v vertical tab
|
||||
.replace('\x0C', "%0C") // \f form feed
|
||||
.replace('\r', "%0D"); // \r carriage return
|
||||
let mut parameters = vec![DispositionParam::Filename(filename_s)];
|
||||
let mut parameters =
|
||||
vec![DispositionParam::Filename(String::from(filename.as_ref()))];
|
||||
|
||||
if !filename.is_ascii() {
|
||||
parameters.push(DispositionParam::FilenameExt(ExtendedValue {
|
||||
|
@@ -97,6 +97,8 @@ impl FromRequest for PathBufWrap {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::iter::FromIterator;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
@@ -48,8 +48,8 @@ impl HttpRange {
|
||||
/// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`).
|
||||
/// `size` is full size of response (file).
|
||||
pub fn parse(header: &str, size: u64) -> Result<Vec<HttpRange>, ParseRangeErr> {
|
||||
let ranges =
|
||||
http_range::HttpRange::parse(header, size).map_err(|err| ParseRangeErr(err.into()))?;
|
||||
let ranges = http_range::HttpRange::parse(header, size)
|
||||
.map_err(|err| ParseRangeErr(err.into()))?;
|
||||
|
||||
Ok(ranges
|
||||
.iter()
|
||||
|
@@ -62,7 +62,11 @@ impl FilesService {
|
||||
}
|
||||
}
|
||||
|
||||
fn serve_named_file(&self, req: ServiceRequest, mut named_file: NamedFile) -> ServiceResponse {
|
||||
fn serve_named_file(
|
||||
&self,
|
||||
req: ServiceRequest,
|
||||
mut named_file: NamedFile,
|
||||
) -> ServiceResponse {
|
||||
if let Some(ref mime_override) = self.mime_override {
|
||||
let new_disposition = mime_override(&named_file.content_type.type_());
|
||||
named_file.content_disposition.disposition = new_disposition;
|
||||
@@ -116,11 +120,13 @@ impl Service<ServiceRequest> for FilesService {
|
||||
));
|
||||
}
|
||||
|
||||
let path_on_disk =
|
||||
match PathBufWrap::parse_path(req.match_info().unprocessed(), this.hidden_files) {
|
||||
Ok(item) => item,
|
||||
Err(err) => return Ok(req.error_response(err)),
|
||||
};
|
||||
let path_on_disk = match PathBufWrap::parse_path(
|
||||
req.match_info().unprocessed(),
|
||||
this.hidden_files,
|
||||
) {
|
||||
Ok(item) => item,
|
||||
Err(err) => return Ok(req.error_response(err)),
|
||||
};
|
||||
|
||||
if let Some(filter) = &this.path_filter {
|
||||
if !filter(path_on_disk.as_ref(), req.head()) {
|
||||
@@ -171,7 +177,8 @@ impl Service<ServiceRequest> for FilesService {
|
||||
match NamedFile::open_async(&path).await {
|
||||
Ok(mut named_file) => {
|
||||
if let Some(ref mime_override) = this.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.flags = this.file_flags;
|
||||
|
@@ -24,7 +24,8 @@ async fn test_utf8_file_contents() {
|
||||
|
||||
// disable UTF-8 attribute
|
||||
let srv =
|
||||
test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(false))).await;
|
||||
test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(false)))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/utf8.txt").to_request();
|
||||
let res = test::call_service(&srv, req).await;
|
||||
|
@@ -12,7 +12,9 @@ async fn test_guard_filter() {
|
||||
let srv = test::init_service(
|
||||
App::new()
|
||||
.service(Files::new("/", "./tests/fixtures/guards/first").guard(Host("first.com")))
|
||||
.service(Files::new("/", "./tests/fixtures/guards/second").guard(Host("second.com"))),
|
||||
.service(
|
||||
Files::new("/", "./tests/fixtures/guards/second").guard(Host("second.com")),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@@ -9,7 +9,8 @@ use actix_web::{
|
||||
async fn test_directory_traversal_prevention() {
|
||||
let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await;
|
||||
|
||||
let req = TestRequest::with_uri("/../../../../../../../../../../../etc/passwd").to_request();
|
||||
let req =
|
||||
TestRequest::with_uri("/../../../../../../../../../../../etc/passwd").to_request();
|
||||
let res = test::call_service(&srv, req).await;
|
||||
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
|
@@ -1,16 +1,12 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased
|
||||
## Unreleased - 2023-xx-xx
|
||||
|
||||
## 3.2.0
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 3.1.0
|
||||
## 3.1.0 - 2023-01-21
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.59.
|
||||
|
||||
## 3.0.0
|
||||
## 3.0.0 - 2022-07-24
|
||||
|
||||
- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442]
|
||||
- Added `TestServer::client_headers` method. [#2097]
|
||||
@@ -26,41 +22,41 @@
|
||||
<details>
|
||||
<summary>3.0.0 Pre-Releases</summary>
|
||||
|
||||
## 3.0.0-beta.13
|
||||
## 3.0.0-beta.13 - 2022-02-16
|
||||
|
||||
- No significant changes since `3.0.0-beta.12`.
|
||||
|
||||
## 3.0.0-beta.12
|
||||
## 3.0.0-beta.12 - 2022-01-31
|
||||
|
||||
- No significant changes since `3.0.0-beta.11`.
|
||||
|
||||
## 3.0.0-beta.11
|
||||
## 3.0.0-beta.11 - 2022-01-04
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||
|
||||
## 3.0.0-beta.10
|
||||
## 3.0.0-beta.10 - 2021-12-27
|
||||
|
||||
- Update `actix-server` to `2.0.0-rc.2`. [#2550]
|
||||
|
||||
[#2550]: https://github.com/actix/actix-web/pull/2550
|
||||
|
||||
## 3.0.0-beta.9
|
||||
## 3.0.0-beta.9 - 2021-12-11
|
||||
|
||||
- No significant changes since `3.0.0-beta.8`.
|
||||
|
||||
## 3.0.0-beta.8
|
||||
## 3.0.0-beta.8 - 2021-11-30
|
||||
|
||||
- Update `actix-tls` to `3.0.0-rc.1`. [#2474]
|
||||
|
||||
[#2474]: https://github.com/actix/actix-web/pull/2474
|
||||
|
||||
## 3.0.0-beta.7
|
||||
## 3.0.0-beta.7 - 2021-11-22
|
||||
|
||||
- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]
|
||||
|
||||
[#2408]: https://github.com/actix/actix-web/pull/2408
|
||||
|
||||
## 3.0.0-beta.6
|
||||
## 3.0.0-beta.6 - 2021-11-15
|
||||
|
||||
- `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]
|
||||
@@ -68,25 +64,25 @@
|
||||
|
||||
[#2442]: https://github.com/actix/actix-web/pull/2442
|
||||
|
||||
## 3.0.0-beta.5
|
||||
## 3.0.0-beta.5 - 2021-09-09
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
||||
## 3.0.0-beta.4
|
||||
## 3.0.0-beta.4 - 2021-04-02
|
||||
|
||||
- Added `TestServer::client_headers` method. [#2097]
|
||||
|
||||
[#2097]: https://github.com/actix/actix-web/pull/2097
|
||||
|
||||
## 3.0.0-beta.3
|
||||
## 3.0.0-beta.3 - 2021-03-09
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 3.0.0-beta.2
|
||||
## 3.0.0-beta.2 - 2021-02-10
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 3.0.0-beta.1
|
||||
## 3.0.0-beta.1 - 2021-01-07
|
||||
|
||||
- Update `bytes` to `1.0`. [#1813]
|
||||
|
||||
@@ -94,7 +90,7 @@
|
||||
|
||||
</details>
|
||||
|
||||
## 2.1.0
|
||||
## 2.1.0 - 2020-11-25
|
||||
|
||||
- Add ability to set address for `TestServer`. [#1645]
|
||||
- Upgrade `base64` to `0.13`.
|
||||
@@ -103,11 +99,11 @@
|
||||
[#1773]: https://github.com/actix/actix-web/pull/1773
|
||||
[#1645]: https://github.com/actix/actix-web/pull/1645
|
||||
|
||||
## 2.0.0
|
||||
## 2.0.0 - 2020-09-11
|
||||
|
||||
- Update actix-codec and actix-utils dependencies.
|
||||
|
||||
## 2.0.0-alpha.1
|
||||
## 2.0.0-alpha.1 - 2020-05-23
|
||||
|
||||
- Update the `time` dependency to 0.2.7
|
||||
- Update `actix-connect` dependency to 2.0.0-alpha.2
|
||||
@@ -117,57 +113,57 @@
|
||||
- Update `base64` dependency to 0.12
|
||||
- Update `env_logger` dependency to 0.7
|
||||
|
||||
## 1.0.0
|
||||
## 1.0.0 - 2019-12-13
|
||||
|
||||
- Replaced `TestServer::start()` with `test_server()`
|
||||
|
||||
## 1.0.0-alpha.3
|
||||
## 1.0.0-alpha.3 - 2019-12-07
|
||||
|
||||
- Migrate to `std::future`
|
||||
|
||||
## 0.2.5
|
||||
## 0.2.5 - 2019-09-17
|
||||
|
||||
- Update serde_urlencoded to "0.6.1"
|
||||
- Increase TestServerRuntime timeouts from 500ms to 3000ms
|
||||
- Do not override current `System`
|
||||
|
||||
## 0.2.4
|
||||
## 0.2.4 - 2019-07-18
|
||||
|
||||
- Update actix-server to 0.6
|
||||
|
||||
## 0.2.3
|
||||
## 0.2.3 - 2019-07-16
|
||||
|
||||
- Add `delete`, `options`, `patch` methods to `TestServerRunner`
|
||||
|
||||
## 0.2.2
|
||||
## 0.2.2 - 2019-06-16
|
||||
|
||||
- Add .put() and .sput() methods
|
||||
|
||||
## 0.2.1
|
||||
## 0.2.1 - 2019-06-05
|
||||
|
||||
- Add license files
|
||||
|
||||
## 0.2.0
|
||||
## 0.2.0 - 2019-05-12
|
||||
|
||||
- Update awc and actix-http deps
|
||||
|
||||
## 0.1.1
|
||||
## 0.1.1 - 2019-04-24
|
||||
|
||||
- Always make new connection for http client
|
||||
|
||||
## 0.1.0
|
||||
## 0.1.0 - 2019-04-16
|
||||
|
||||
- No changes
|
||||
|
||||
## 0.1.0-alpha.3
|
||||
## 0.1.0-alpha.3 - 2019-04-02
|
||||
|
||||
- Request functions accept path #743
|
||||
|
||||
## 0.1.0-alpha.2
|
||||
## 0.1.0-alpha.2 - 2019-03-29
|
||||
|
||||
- Added TestServerRuntime::load_body() method
|
||||
- Update actix-http and awc libraries
|
||||
|
||||
## 0.1.0-alpha.1
|
||||
## 0.1.0-alpha.1 - 2019-03-28
|
||||
|
||||
- Initial impl
|
||||
|
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "actix-http-test"
|
||||
version = "3.2.0"
|
||||
version = "3.1.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Various helpers for Actix applications to use during testing"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
categories = [
|
||||
"network-programming",
|
||||
"asynchronous",
|
||||
@@ -13,7 +13,7 @@ categories = [
|
||||
"web-programming::websocket",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = []
|
||||
@@ -41,13 +41,14 @@ bytes = "1"
|
||||
futures-core = { version = "0.3.17", default-features = false }
|
||||
http = "0.2.7"
|
||||
log = "0.4"
|
||||
socket2 = "0.5"
|
||||
socket2 = "0.4"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
slab = "0.4"
|
||||
serde_urlencoded = "0.7"
|
||||
tls-openssl = { version = "0.10.55", package = "openssl", optional = true }
|
||||
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||
tokio = { version = "1.24.2", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = { version = "4", default-features = false, features = ["cookies"] }
|
||||
actix-http = "3"
|
||||
|
@@ -1,21 +1,17 @@
|
||||
# `actix-http-test`
|
||||
# actix-http-test
|
||||
|
||||
> Various helpers for Actix applications to use during testing.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-http-test)
|
||||
[](https://docs.rs/actix-http-test/3.2.0)
|
||||

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

|
||||

|
||||
<br>
|
||||
[](https://deps.rs/crate/actix-http-test/3.2.0)
|
||||
[](https://deps.rs/crate/actix-http-test/3.1.0)
|
||||
[](https://crates.io/crates/actix-http-test)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-http-test)
|
||||
- Minimum Supported Rust Version (MSRV): 1.68
|
||||
- Minimum Supported Rust Version (MSRV): 1.59
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
@@ -30,20 +31,24 @@ use tokio::sync::mpsc;
|
||||
/// for HTTP applications.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use actix_http::{HttpService, Response, Error, StatusCode};
|
||||
/// ```no_run
|
||||
/// use actix_http::HttpService;
|
||||
/// use actix_http_test::test_server;
|
||||
/// use actix_service::{fn_service, map_config, ServiceFactoryExt as _};
|
||||
/// use actix_service::map_config;
|
||||
/// use actix_service::ServiceFactoryExt;
|
||||
/// use actix_web::{dev::AppConfig, web, App, Error, HttpResponse};
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// # async fn hidden_test() {}
|
||||
/// async fn my_handler() -> Result<HttpResponse, Error> {
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// }
|
||||
///
|
||||
/// #[actix_web::test]
|
||||
/// async fn test_example() {
|
||||
/// let srv = test_server(|| {
|
||||
/// let app = App::new().service(web::resource("/").to(my_handler));
|
||||
///
|
||||
/// HttpService::build()
|
||||
/// .h1(fn_service(|req| async move {
|
||||
/// Ok::<_, Error>(Response::ok())
|
||||
/// }))
|
||||
/// .h1(map_config(app, |_| AppConfig::default()))
|
||||
/// .tcp()
|
||||
/// .map_err(|_| ())
|
||||
/// })
|
||||
@@ -52,9 +57,8 @@ use tokio::sync::mpsc;
|
||||
/// let req = srv.get("/");
|
||||
/// let response = req.send().await.unwrap();
|
||||
///
|
||||
/// assert_eq!(response.status(), StatusCode::OK);
|
||||
/// assert!(response.status().is_success());
|
||||
/// }
|
||||
/// # actix_rt::System::new().block_on(test_example());
|
||||
/// ```
|
||||
pub async fn test_server<F: ServerServiceFactory<TcpStream>>(factory: F) -> TestServer {
|
||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
|
@@ -1,55 +1,19 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 3.6.0
|
||||
## Unreleased - 2023-xx-xx
|
||||
|
||||
### Added
|
||||
|
||||
- Add `rustls-0_22` crate feature.
|
||||
- Add `{h1::H1Service, h2::H2Service, HttpService}::rustls_0_22()` and `HttpService::rustls_0_22_with_config()` service constructors.
|
||||
- Implement `From<&HeaderMap>` for `http::HeaderMap`.
|
||||
|
||||
## 3.5.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Prevent hang when returning zero-sized response bodies through compression layer.
|
||||
|
||||
## 3.5.0
|
||||
|
||||
### Added
|
||||
|
||||
- Implement `From<HeaderMap>` for `http::HeaderMap`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated `zstd` dependency to `0.13`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Prevent compression of zero-sized response bodies.
|
||||
|
||||
## 3.4.0
|
||||
|
||||
### Added
|
||||
|
||||
- Add `rustls-0_20` crate feature.
|
||||
- Add `{h1::H1Service, h2::H2Service, HttpService}::rustls_021()` and `HttpService::rustls_021_with_config()` service constructors.
|
||||
- Add `body::to_bytes_limited()` function.
|
||||
- Add `body::to_body_limit()` function.
|
||||
- Add `body::BodyLimitExceeded` error type.
|
||||
|
||||
### Changed
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 3.3.1
|
||||
## 3.3.1 - 2023-03-02
|
||||
|
||||
### Fixed
|
||||
|
||||
- Use correct `http` version requirement to ensure support for const `HeaderName` definitions.
|
||||
|
||||
## 3.3.0
|
||||
## 3.3.0 - 2023-01-21
|
||||
|
||||
### Added
|
||||
|
||||
@@ -85,7 +49,7 @@
|
||||
[#2956]: https://github.com/actix/actix-web/pull/2956
|
||||
[#2968]: https://github.com/actix/actix-web/pull/2968
|
||||
|
||||
## 3.2.2
|
||||
## 3.2.2 - 2022-09-11
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -97,7 +61,7 @@
|
||||
|
||||
[#2369]: https://github.com/actix/actix-web/pull/2369
|
||||
|
||||
## 3.2.1
|
||||
## 3.2.1 - 2022-07-02
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -105,7 +69,7 @@
|
||||
|
||||
[#2794]: https://github.com/actix/actix-web/pull/2794
|
||||
|
||||
## 3.2.0
|
||||
## 3.2.0 - 2022-06-30
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -119,7 +83,7 @@
|
||||
[#2790]: https://github.com/actix/actix-web/pull/2790
|
||||
[#2798]: https://github.com/actix/actix-web/pull/2798
|
||||
|
||||
## 3.1.0
|
||||
## 3.1.0 - 2022-06-11
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -133,13 +97,13 @@
|
||||
[#2357]: https://github.com/actix/actix-web/issues/2357
|
||||
[#2779]: https://github.com/actix/actix-web/pull/2779
|
||||
|
||||
## 3.0.4
|
||||
## 3.0.4 - 2022-03-09
|
||||
|
||||
### Fixed
|
||||
|
||||
- Document on docs.rs with `ws` feature enabled.
|
||||
|
||||
## 3.0.3
|
||||
## 3.0.3 - 2022-03-08
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -147,7 +111,7 @@
|
||||
|
||||
[#2684]: https://github.com/actix/actix-web/pull/2684
|
||||
|
||||
## 3.0.2
|
||||
## 3.0.2 - 2022-03-05
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -155,13 +119,13 @@
|
||||
|
||||
[#2683]: https://github.com/actix/actix-web/pull/2683
|
||||
|
||||
## 3.0.1
|
||||
## 3.0.1 - 2022-03-04
|
||||
|
||||
- Fix panic in H1 dispatcher when pipelining is used with keep-alive. [#2678]
|
||||
|
||||
[#2678]: https://github.com/actix/actix-web/issues/2678
|
||||
|
||||
## 3.0.0
|
||||
## 3.0.0 - 2022-02-25
|
||||
|
||||
### Dependencies
|
||||
|
||||
@@ -449,7 +413,7 @@
|
||||
<details>
|
||||
<summary>3.0.0 Pre-Releases</summary>
|
||||
|
||||
## 3.0.0-rc.4
|
||||
## 3.0.0-rc.4 - 2022-02-22
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -457,11 +421,11 @@
|
||||
|
||||
[1ce58ecb]: https://github.com/actix/actix-web/commit/1ce58ecb305c60e51db06e6c913b7a1344e229ca
|
||||
|
||||
## 3.0.0-rc.3
|
||||
## 3.0.0-rc.3 - 2022-02-16
|
||||
|
||||
- No significant changes since `3.0.0-rc.2`.
|
||||
|
||||
## 3.0.0-rc.2
|
||||
## 3.0.0-rc.2 - 2022-02-08
|
||||
|
||||
### Added
|
||||
|
||||
@@ -478,7 +442,7 @@
|
||||
[#2624]: https://github.com/actix/actix-web/pull/2624
|
||||
[#2625]: https://github.com/actix/actix-web/pull/2625
|
||||
|
||||
## 3.0.0-rc.1
|
||||
## 3.0.0-rc.1 - 2022-01-31
|
||||
|
||||
### Added
|
||||
|
||||
@@ -514,7 +478,7 @@
|
||||
[#2611]: https://github.com/actix/actix-web/pull/2611
|
||||
[#2618]: https://github.com/actix/actix-web/pull/2618
|
||||
|
||||
## 3.0.0-beta.19
|
||||
## 3.0.0-beta.19 - 2022-01-21
|
||||
|
||||
### Added
|
||||
|
||||
@@ -534,7 +498,7 @@
|
||||
[#2585]: https://github.com/actix/actix-web/pull/2585
|
||||
[#2587]: https://github.com/actix/actix-web/pull/2587
|
||||
|
||||
## 3.0.0-beta.18
|
||||
## 3.0.0-beta.18 - 2022-01-04
|
||||
|
||||
### Added
|
||||
|
||||
@@ -565,7 +529,7 @@
|
||||
[#2501]: https://github.com/actix/actix-web/pull/2501
|
||||
[#2565]: https://github.com/actix/actix-web/pull/2565
|
||||
|
||||
## 3.0.0-beta.17
|
||||
## 3.0.0-beta.17 - 2021-12-27
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -583,7 +547,7 @@
|
||||
[#2527]: https://github.com/actix/actix-web/pull/2527
|
||||
[#2545]: https://github.com/actix/actix-web/pull/2545
|
||||
|
||||
## 3.0.0-beta.16
|
||||
## 3.0.0-beta.16 - 2021-12-17
|
||||
|
||||
### Added
|
||||
|
||||
@@ -602,7 +566,7 @@
|
||||
[#2510]: https://github.com/actix/actix-web/pull/2510
|
||||
[#2522]: https://github.com/actix/actix-web/pull/2522
|
||||
|
||||
## 3.0.0-beta.15
|
||||
## 3.0.0-beta.15 - 2021-12-11
|
||||
|
||||
### Added
|
||||
|
||||
@@ -656,7 +620,7 @@
|
||||
[#2497]: https://github.com/actix/actix-web/pull/2497
|
||||
[#2520]: https://github.com/actix/actix-web/pull/2520
|
||||
|
||||
## 3.0.0-beta.14
|
||||
## 3.0.0-beta.14 - 2021-11-30
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -669,7 +633,7 @@
|
||||
[#2470]: https://github.com/actix/actix-web/pull/2470
|
||||
[#2474]: https://github.com/actix/actix-web/pull/2474
|
||||
|
||||
## 3.0.0-beta.13
|
||||
## 3.0.0-beta.13 - 2021-11-22
|
||||
|
||||
### Added
|
||||
|
||||
@@ -698,7 +662,7 @@
|
||||
[#2448]: https://github.com/actix/actix-web/pull/2448
|
||||
[#2456]: https://github.com/actix/actix-web/pull/2456
|
||||
|
||||
## 3.0.0-beta.12
|
||||
## 3.0.0-beta.12 - 2021-11-15
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -712,7 +676,7 @@
|
||||
[#2425]: https://github.com/actix/actix-web/pull/2425
|
||||
[#2442]: https://github.com/actix/actix-web/pull/2442
|
||||
|
||||
## 3.0.0-beta.11
|
||||
## 3.0.0-beta.11 - 2021-10-20
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -721,7 +685,7 @@
|
||||
|
||||
[#2414]: https://github.com/actix/actix-web/pull/2414
|
||||
|
||||
## 3.0.0-beta.10
|
||||
## 3.0.0-beta.10 - 2021-09-09
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -739,13 +703,13 @@
|
||||
[#2344]: https://github.com/actix/actix-web/pull/2344
|
||||
[#2377]: https://github.com/actix/actix-web/pull/2377
|
||||
|
||||
## 3.0.0-beta.9
|
||||
## 3.0.0-beta.9 - 2021-08-09
|
||||
|
||||
### Fixed
|
||||
|
||||
- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
|
||||
|
||||
## 3.0.0-beta.8
|
||||
## 3.0.0-beta.8 - 2021-06-26
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -758,7 +722,7 @@
|
||||
[#2291]: https://github.com/actix/actix-web/pull/2291
|
||||
[#2250]: https://github.com/actix/actix-web/pull/2250
|
||||
|
||||
## 3.0.0-beta.7
|
||||
## 3.0.0-beta.7 - 2021-06-17
|
||||
|
||||
### Added
|
||||
|
||||
@@ -805,7 +769,7 @@
|
||||
[#2253]: https://github.com/actix/actix-web/pull/2253
|
||||
[#2244]: https://github.com/actix/actix-web/pull/2244
|
||||
|
||||
## 3.0.0-beta.6
|
||||
## 3.0.0-beta.6 - 2021-04-17
|
||||
|
||||
### Added
|
||||
|
||||
@@ -840,7 +804,7 @@
|
||||
[#2158]: https://github.com/actix/actix-web/pull/2158
|
||||
[#2161]: https://github.com/actix/actix-web/pull/2161
|
||||
|
||||
## 3.0.0-beta.5
|
||||
## 3.0.0-beta.5 - 2021-04-02
|
||||
|
||||
### Added
|
||||
|
||||
@@ -862,7 +826,7 @@
|
||||
[#2094]: https://github.com/actix/actix-web/pull/2094
|
||||
[#2127]: https://github.com/actix/actix-web/pull/2127
|
||||
|
||||
## 3.0.0-beta.4
|
||||
## 3.0.0-beta.4 - 2021-03-08
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -880,11 +844,11 @@
|
||||
[#2035]: https://github.com/actix/actix-web/pull/2035
|
||||
[#2052]: https://github.com/actix/actix-web/pull/2052
|
||||
|
||||
## 3.0.0-beta.3
|
||||
## 3.0.0-beta.3 - 2021-02-10
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 3.0.0-beta.2
|
||||
## 3.0.0-beta.2 - 2021-02-10
|
||||
|
||||
### Added
|
||||
|
||||
@@ -936,7 +900,7 @@
|
||||
[#1964]: https://github.com/actix/actix-web/pull/1964
|
||||
[#1969]: https://github.com/actix/actix-web/pull/1969
|
||||
|
||||
## 3.0.0-beta.1
|
||||
## 3.0.0-beta.1 - 2021-01-07
|
||||
|
||||
### Added
|
||||
|
||||
@@ -964,7 +928,7 @@
|
||||
|
||||
</details>
|
||||
|
||||
## 2.2.2
|
||||
## 2.2.2 - 2022-01-21
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -972,13 +936,13 @@
|
||||
|
||||
[ad7e3c06]: https://github.com/actix/actix-web/commit/ad7e3c06
|
||||
|
||||
## 2.2.1
|
||||
## 2.2.1 - 2021-08-09
|
||||
|
||||
### Fixed
|
||||
|
||||
- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
|
||||
|
||||
## 2.2.0
|
||||
## 2.2.0 - 2020-11-25
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1000,7 +964,7 @@
|
||||
[#1793]: https://github.com/actix/actix-web/pull/1793
|
||||
[#1797]: https://github.com/actix/actix-web/pull/1797
|
||||
|
||||
## 2.1.0
|
||||
## 2.1.0 - 2020-10-30
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1017,18 +981,18 @@
|
||||
[#1733]: https://github.com/actix/actix-web/pull/1733
|
||||
[#1744]: https://github.com/actix/actix-web/pull/1744
|
||||
|
||||
## 2.0.0
|
||||
## 2.0.0 - 2020-09-11
|
||||
|
||||
- No significant changes from `2.0.0-beta.4`.
|
||||
|
||||
## 2.0.0-beta.4
|
||||
## 2.0.0-beta.4 - 2020-09-09
|
||||
|
||||
### Changed
|
||||
|
||||
- Update actix-codec and actix-utils dependencies.
|
||||
- Update actix-connect and actix-tls dependencies.
|
||||
|
||||
## 2.0.0-beta.3
|
||||
## 2.0.0-beta.3 - 2020-08-14
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1036,7 +1000,7 @@
|
||||
|
||||
[#1626]: https://github.com/actix/actix-web/pull/1626
|
||||
|
||||
## 2.0.0-beta.2
|
||||
## 2.0.0-beta.2 - 2020-07-21
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1049,7 +1013,7 @@
|
||||
[#1614]: https://github.com/actix/actix-web/pull/1614
|
||||
[#1615]: https://github.com/actix/actix-web/pull/1615
|
||||
|
||||
## 2.0.0-beta.1
|
||||
## 2.0.0-beta.1 - 2020-07-11
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1062,7 +1026,7 @@
|
||||
[#1586]: https://github.com/actix/actix-web/pull/1586
|
||||
[#1580]: https://github.com/actix/actix-web/pull/1580
|
||||
|
||||
## 2.0.0-alpha.4
|
||||
## 2.0.0-alpha.4 - 2020-05-21
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1078,7 +1042,7 @@
|
||||
[#1439]: https://github.com/actix/actix-web/pull/1439
|
||||
[#1503]: https://github.com/actix/actix-web/pull/1503
|
||||
|
||||
## 2.0.0-alpha.3
|
||||
## 2.0.0-alpha.3 - 2020-05-08
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1093,7 +1057,7 @@
|
||||
[#1422]: https://github.com/actix/actix-web/pull/1422
|
||||
[#1487]: https://github.com/actix/actix-web/pull/1487
|
||||
|
||||
## 2.0.0-alpha.2
|
||||
## 2.0.0-alpha.2 - 2020-03-07
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1105,7 +1069,7 @@
|
||||
[#1394]: https://github.com/actix/actix-web/pull/1394
|
||||
[#1395]: https://github.com/actix/actix-web/pull/1395
|
||||
|
||||
## 2.0.0-alpha.1
|
||||
## 2.0.0-alpha.1 - 2020-02-27
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1118,14 +1082,14 @@
|
||||
|
||||
- Allow `SameSite=None` cookies to be sent in a response.
|
||||
|
||||
## 1.0.1
|
||||
## 1.0.1 - 2019-12-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- Poll upgrade service's readiness from HTTP service handlers
|
||||
- Replace brotli with brotli2 #1224
|
||||
|
||||
## 1.0.0
|
||||
## 1.0.0 - 2019-12-13
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1135,7 +1099,7 @@
|
||||
|
||||
- Replace `flate2-xxx` features with `compress`
|
||||
|
||||
## 1.0.0-alpha.5
|
||||
## 1.0.0-alpha.5 - 2019-12-09
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1146,7 +1110,7 @@
|
||||
|
||||
- Websockets: Ping and Pong should have binary data #1049
|
||||
|
||||
## 1.0.0-alpha.4
|
||||
## 1.0.0-alpha.4 - 2019-12-08
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1156,14 +1120,14 @@
|
||||
|
||||
- Use rust based brotli compression library
|
||||
|
||||
## 1.0.0-alpha.3
|
||||
## 1.0.0-alpha.3 - 2019-12-07
|
||||
|
||||
### Changed
|
||||
|
||||
- Migrate to tokio 0.2
|
||||
- Migrate to `std::future`
|
||||
|
||||
## 0.2.11
|
||||
## 0.2.11 - 2019-11-06
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1177,7 +1141,7 @@
|
||||
|
||||
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||
|
||||
## 0.2.10
|
||||
## 0.2.10 - 2019-09-11
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1188,7 +1152,7 @@
|
||||
- h2 will use error response #1080
|
||||
- on_connect result isn't added to request extensions for http2 requests #1009
|
||||
|
||||
## 0.2.9
|
||||
## 0.2.9 - 2019-08-13
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1200,7 +1164,7 @@
|
||||
|
||||
- Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031)
|
||||
|
||||
## 0.2.8
|
||||
## 0.2.8 - 2019-08-01
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1212,20 +1176,20 @@
|
||||
- awc client panic #1016
|
||||
- Invalid response with compression middleware enabled, but compression-related features disabled #997
|
||||
|
||||
## 0.2.7
|
||||
## 0.2.7 - 2019-07-18
|
||||
|
||||
### Added
|
||||
|
||||
- Add support for downcasting response errors #986
|
||||
|
||||
## 0.2.6
|
||||
## 0.2.6 - 2019-07-17
|
||||
|
||||
### Changed
|
||||
|
||||
- Replace `ClonableService` with local copy
|
||||
- Upgrade `rand` dependency version to 0.7
|
||||
|
||||
## 0.2.5
|
||||
## 0.2.5 - 2019-06-28
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1236,13 +1200,13 @@
|
||||
- Use `encoding_rs` crate instead of unmaintained `encoding` crate
|
||||
- Add `Copy` and `Clone` impls for `ws::Codec`
|
||||
|
||||
## 0.2.4
|
||||
## 0.2.4 - 2019-06-16
|
||||
|
||||
### Fixed
|
||||
|
||||
- Do not compress NoContent (204) responses #918
|
||||
|
||||
## 0.2.3
|
||||
## 0.2.3 - 2019-06-02
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1253,19 +1217,19 @@
|
||||
|
||||
- SizedStream uses u64
|
||||
|
||||
## 0.2.2
|
||||
## 0.2.2 - 2019-05-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Parse incoming stream before closing stream on disconnect #868
|
||||
|
||||
## 0.2.1
|
||||
## 0.2.1 - 2019-05-25
|
||||
|
||||
### Fixed
|
||||
|
||||
- Handle socket read disconnect
|
||||
|
||||
## 0.2.0
|
||||
## 0.2.0 - 2019-05-12
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1276,13 +1240,13 @@
|
||||
|
||||
- `OneRequest` service
|
||||
|
||||
## 0.1.5
|
||||
## 0.1.5 - 2019-05-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- Clean up response extensions in response pool #817
|
||||
|
||||
## 0.1.4
|
||||
## 0.1.4 - 2019-04-24
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1292,20 +1256,20 @@
|
||||
|
||||
- Read until eof for http/1.0 responses #771
|
||||
|
||||
## 0.1.3
|
||||
## 0.1.3 - 2019-04-23
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix http client pool management
|
||||
- Fix http client wait queue management #794
|
||||
|
||||
## 0.1.2
|
||||
## 0.1.2 - 2019-04-23
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix BorrowMutError panic in client connector #793
|
||||
|
||||
## 0.1.1
|
||||
## 0.1.1 - 2019-04-19
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1313,7 +1277,7 @@
|
||||
- Cookie::max_age_time() accepts value in time::Duration
|
||||
- Allow to specify server address for client connector
|
||||
|
||||
## 0.1.0
|
||||
## 0.1.0 - 2019-04-16
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1324,7 +1288,7 @@
|
||||
- `actix_http::encoding` always available
|
||||
- use trust-dns-resolver 0.11.0
|
||||
|
||||
## 0.1.0-alpha.5
|
||||
## 0.1.0-alpha.5 - 2019-04-12
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1336,7 +1300,7 @@
|
||||
- MessageBody::length() renamed to MessageBody::size() for consistency
|
||||
- ws handshake verification functions take RequestHead instead of Request
|
||||
|
||||
## 0.1.0-alpha.4
|
||||
## 0.1.0-alpha.4 - 2019-04-08
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1353,7 +1317,7 @@
|
||||
|
||||
- Removed PayloadBuffer
|
||||
|
||||
## 0.1.0-alpha.3
|
||||
## 0.1.0-alpha.3 - 2019-04-02
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1365,7 +1329,7 @@
|
||||
- Preallocate read buffer for h1 codec
|
||||
- Detect socket disconnection during protocol selection
|
||||
|
||||
## 0.1.0-alpha.2
|
||||
## 0.1.0-alpha.2 - 2019-03-29
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1375,6 +1339,6 @@
|
||||
|
||||
- Do not use thread pool for decompression if chunk size is smaller than 2048.
|
||||
|
||||
## 0.1.0-alpha.1
|
||||
## 0.1.0-alpha.1 - 2019-03-28
|
||||
|
||||
- Initial impl
|
||||
|
@@ -1,37 +1,26 @@
|
||||
[package]
|
||||
name = "actix-http"
|
||||
version = "3.6.0"
|
||||
version = "3.3.1"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
description = "HTTP types and services for the Actix ecosystem"
|
||||
description = "HTTP primitives for the Actix ecosystem"
|
||||
keywords = ["actix", "http", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
categories = [
|
||||
"network-programming",
|
||||
"asynchronous",
|
||||
"web-programming::http-server",
|
||||
"web-programming::websocket",
|
||||
]
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
features = [
|
||||
"http2",
|
||||
"ws",
|
||||
"openssl",
|
||||
"rustls-0_20",
|
||||
"rustls-0_21",
|
||||
"rustls-0_22",
|
||||
"compress-brotli",
|
||||
"compress-gzip",
|
||||
"compress-zstd",
|
||||
]
|
||||
# features that docs.rs will build with
|
||||
features = ["http2", "ws", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"]
|
||||
|
||||
[lib]
|
||||
name = "actix_http"
|
||||
@@ -54,24 +43,15 @@ ws = [
|
||||
# TLS via OpenSSL
|
||||
openssl = ["actix-tls/accept", "actix-tls/openssl"]
|
||||
|
||||
# TLS via Rustls v0.20
|
||||
rustls = ["rustls-0_20"]
|
||||
|
||||
# TLS via Rustls v0.20
|
||||
rustls-0_20 = ["actix-tls/accept", "actix-tls/rustls-0_20"]
|
||||
|
||||
# TLS via Rustls v0.21
|
||||
rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"]
|
||||
|
||||
# TLS via Rustls v0.22
|
||||
rustls-0_22 = ["actix-tls/accept", "actix-tls/rustls-0_22"]
|
||||
# TLS via Rustls
|
||||
rustls = ["actix-tls/accept", "actix-tls/rustls"]
|
||||
|
||||
# Compression codecs
|
||||
compress-brotli = ["__compress", "brotli"]
|
||||
compress-gzip = ["__compress", "flate2"]
|
||||
compress-zstd = ["__compress", "zstd"]
|
||||
|
||||
# Internal (PRIVATE!) features used to aid testing and checking feature status.
|
||||
# Internal (PRIVATE!) features used to aid testing and cheking feature status.
|
||||
# Don't rely on these whatsoever. They are semver-exempt and may disappear at anytime.
|
||||
__compress = []
|
||||
|
||||
@@ -82,7 +62,7 @@ actix-utils = "3"
|
||||
actix-rt = { version = "2.2", default-features = false }
|
||||
|
||||
ahash = "0.8"
|
||||
bitflags = "2"
|
||||
bitflags = "1.2"
|
||||
bytes = "1"
|
||||
bytestring = "1"
|
||||
derive_more = "0.99.5"
|
||||
@@ -102,7 +82,7 @@ tokio-util = { version = "0.7", features = ["io", "codec"] }
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
# http2
|
||||
h2 = { version = "0.3.24", optional = true }
|
||||
h2 = { version = "0.3.9", optional = true }
|
||||
|
||||
# websockets
|
||||
local-channel = { version = "0.1", optional = true }
|
||||
@@ -111,43 +91,39 @@ rand = { version = "0.8", optional = true }
|
||||
sha1 = { version = "0.10", optional = true }
|
||||
|
||||
# openssl/rustls
|
||||
actix-tls = { version = "3.3", default-features = false, optional = true }
|
||||
actix-tls = { version = "3", default-features = false, optional = true }
|
||||
|
||||
# compress-*
|
||||
brotli = { version = "3.3.3", optional = true }
|
||||
flate2 = { version = "1.0.13", optional = true }
|
||||
zstd = { version = "0.13", optional = true }
|
||||
zstd = { version = "0.12", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http-test = { version = "3", features = ["openssl"] }
|
||||
actix-server = "2"
|
||||
actix-tls = { version = "3.3", features = ["openssl", "rustls-0_22-webpki-roots"] }
|
||||
actix-tls = { version = "3", features = ["openssl"] }
|
||||
actix-web = "4"
|
||||
|
||||
async-stream = "0.3"
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
env_logger = "0.10"
|
||||
criterion = { version = "0.4", features = ["html_reports"] }
|
||||
env_logger = "0.9"
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
memchr = "2.4"
|
||||
once_cell = "1.9"
|
||||
rcgen = "0.12"
|
||||
rcgen = "0.9"
|
||||
regex = "1.3"
|
||||
rustversion = "1"
|
||||
rustls-pemfile = "2"
|
||||
rustls-pemfile = "1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
static_assertions = "1"
|
||||
tls-openssl = { package = "openssl", version = "0.10.55" }
|
||||
tls-rustls_022 = { package = "rustls", version = "0.22" }
|
||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
||||
tls-rustls = { package = "rustls", version = "0.20.0" }
|
||||
tokio = { version = "1.24.2", features = ["net", "rt", "macros"] }
|
||||
|
||||
[[example]]
|
||||
name = "ws"
|
||||
required-features = ["ws", "rustls-0_22"]
|
||||
|
||||
[[example]]
|
||||
name = "tls_rustls"
|
||||
required-features = ["http2", "rustls-0_22"]
|
||||
required-features = ["ws", "rustls"]
|
||||
|
||||
[[bench]]
|
||||
name = "response-body-compression"
|
||||
|
@@ -1,26 +1,22 @@
|
||||
# `actix-http`
|
||||
# actix-http
|
||||
|
||||
> HTTP types and services for the Actix ecosystem.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
> HTTP primitives for the Actix ecosystem.
|
||||
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://docs.rs/actix-http/3.6.0)
|
||||

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

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-http/3.6.0)
|
||||
[](https://deps.rs/crate/actix-http/3.3.1)
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-http)
|
||||
- Minimum Supported Rust Version (MSRV): 1.68
|
||||
- Minimum Supported Rust Version (MSRV): 1.59
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
```rust
|
||||
use std::{env, io};
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use std::convert::Infallible;
|
||||
|
||||
use actix_http::{encoding::Encoder, ContentEncoding, Request, Response, StatusCode};
|
||||
|
@@ -23,7 +23,10 @@ async fn main() -> io::Result<()> {
|
||||
res.insert_header(("x-head", HeaderValue::from_static("dummy value!")));
|
||||
|
||||
let forty_two = req.conn_data::<u32>().unwrap().to_string();
|
||||
res.insert_header(("x-forty-two", HeaderValue::from_str(&forty_two).unwrap()));
|
||||
res.insert_header((
|
||||
"x-forty-two",
|
||||
HeaderValue::from_str(&forty_two).unwrap(),
|
||||
));
|
||||
|
||||
Ok::<_, Infallible>(res.body("Hello world!"))
|
||||
})
|
||||
|
@@ -1,75 +0,0 @@
|
||||
//! 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_022 as rustls;
|
||||
|
||||
use std::io;
|
||||
|
||||
use actix_http::{Error, HttpService, Request, Response};
|
||||
use actix_utils::future::ok;
|
||||
|
||||
#[actix_rt::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||
|
||||
tracing::info!("starting HTTP server at https://127.0.0.1:8443");
|
||||
|
||||
actix_server::Server::build()
|
||||
.bind("echo", ("127.0.0.1", 8443), || {
|
||||
HttpService::build()
|
||||
.finish(|req: Request| {
|
||||
let body = format!(
|
||||
"Hello World!\n\
|
||||
Protocol: {:?}",
|
||||
req.head().version
|
||||
);
|
||||
ok::<_, Error>(Response::ok().set_body(body))
|
||||
})
|
||||
.rustls_0_22(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)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
let mut keys = rustls_pemfile::pkcs8_private_keys(key_file)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
|
||||
let mut config = rustls::ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(
|
||||
cert_chain,
|
||||
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
const H1_ALPN: &[u8] = b"http/1.1";
|
||||
const H2_ALPN: &[u8] = b"h2";
|
||||
|
||||
config.alpn_protocols.push(H2_ALPN.to_vec());
|
||||
config.alpn_protocols.push(H1_ALPN.to_vec());
|
||||
|
||||
config
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
//! Sets up a WebSocket server over TCP and TLS.
|
||||
//! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames.
|
||||
|
||||
extern crate tls_rustls_022 as rustls;
|
||||
extern crate tls_rustls as rustls;
|
||||
|
||||
use std::{
|
||||
io,
|
||||
@@ -28,9 +28,7 @@ async fn main() -> io::Result<()> {
|
||||
HttpService::build().h1(handler).tcp()
|
||||
})?
|
||||
.bind("tls", ("127.0.0.1", 8443), || {
|
||||
HttpService::build()
|
||||
.finish(handler)
|
||||
.rustls_0_22(tls_config())
|
||||
HttpService::build().finish(handler).rustls(tls_config())
|
||||
})?
|
||||
.run()
|
||||
.await
|
||||
@@ -85,6 +83,7 @@ impl Stream for Heartbeat {
|
||||
fn tls_config() -> rustls::ServerConfig {
|
||||
use std::io::BufReader;
|
||||
|
||||
use rustls::{Certificate, PrivateKey};
|
||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
@@ -94,17 +93,17 @@ fn tls_config() -> rustls::ServerConfig {
|
||||
let cert_file = &mut BufReader::new(cert_file.as_bytes());
|
||||
let key_file = &mut BufReader::new(key_file.as_bytes());
|
||||
|
||||
let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap();
|
||||
let mut keys = pkcs8_private_keys(key_file)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
let cert_chain = certs(cert_file)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
let mut keys = 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::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
|
||||
)
|
||||
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
|
||||
.unwrap();
|
||||
|
||||
config.alpn_protocols.push(b"http/1.1".to_vec());
|
||||
|
@@ -47,8 +47,9 @@ where
|
||||
|
||||
/// Attempts to pull out the next value of the underlying [`Stream`].
|
||||
///
|
||||
/// Empty values are skipped to prevent [`BodyStream`]'s transmission being ended on a
|
||||
/// zero-length chunk, but rather proceed until the underlying [`Stream`] ends.
|
||||
/// Empty values are skipped to prevent [`BodyStream`]'s transmission being
|
||||
/// ended on a zero-length chunk, but rather proceed until the underlying
|
||||
/// [`Stream`] ends.
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
|
@@ -77,8 +77,12 @@ impl MessageBody for BoxBody {
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
match &mut self.0 {
|
||||
BoxBodyInner::None(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::None(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),
|
||||
}
|
||||
}
|
||||
@@ -100,6 +104,7 @@ impl MessageBody for BoxBody {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use static_assertions::{assert_impl_all, assert_not_impl_any};
|
||||
|
||||
use super::*;
|
||||
|
@@ -555,7 +555,6 @@ mod tests {
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_allocation)] // triggered by `Box::new(()).size()`
|
||||
#[actix_rt::test]
|
||||
async fn boxing_equivalence() {
|
||||
assert_eq!(().size(), BodySize::Sized(0));
|
||||
|
@@ -14,14 +14,12 @@ mod size;
|
||||
mod sized_stream;
|
||||
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 use self::{
|
||||
body_stream::BodyStream,
|
||||
boxed::BoxBody,
|
||||
either::EitherBody,
|
||||
message_body::MessageBody,
|
||||
none::None,
|
||||
size::BodySize,
|
||||
sized_stream::SizedStream,
|
||||
utils::{to_bytes, to_bytes_limited, BodyLimitExceeded},
|
||||
};
|
||||
pub use self::none::None;
|
||||
pub use self::size::BodySize;
|
||||
pub use self::sized_stream::SizedStream;
|
||||
pub use self::utils::{to_bytes, to_bytes_limited, BodyLimitExceeded};
|
||||
|
@@ -132,15 +132,15 @@ impl ServiceConfig {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{date::DATE_VALUE_LENGTH, notify_on_drop};
|
||||
|
||||
use actix_rt::{
|
||||
task::yield_now,
|
||||
time::{sleep, sleep_until},
|
||||
};
|
||||
use memchr::memmem;
|
||||
|
||||
use super::*;
|
||||
use crate::{date::DATE_VALUE_LENGTH, notify_on_drop};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_date_service_update() {
|
||||
let settings =
|
||||
|
@@ -9,9 +9,11 @@ use std::{
|
||||
|
||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||
use bytes::Bytes;
|
||||
use futures_core::{ready, Stream};
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
use flate2::write::{GzDecoder, ZlibDecoder};
|
||||
use futures_core::{ready, Stream};
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
use zstd::stream::write::Decoder as ZstdDecoder;
|
||||
|
||||
@@ -47,9 +49,9 @@ where
|
||||
))),
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(ZlibDecoder::new(
|
||||
Writer::new(),
|
||||
)))),
|
||||
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
||||
ZlibDecoder::new(Writer::new()),
|
||||
))),
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new(
|
||||
@@ -191,7 +193,7 @@ impl ContentDecoder {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
@@ -205,7 +207,7 @@ impl ContentDecoder {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
@@ -218,7 +220,7 @@ impl ContentDecoder {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
@@ -231,7 +233,7 @@ impl ContentDecoder {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -250,7 +252,7 @@ impl ContentDecoder {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
@@ -265,7 +267,7 @@ impl ContentDecoder {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
@@ -280,7 +282,7 @@ impl ContentDecoder {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
@@ -295,7 +297,7 @@ impl ContentDecoder {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -11,10 +11,12 @@ use std::{
|
||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||
use bytes::Bytes;
|
||||
use derive_more::Display;
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||
use futures_core::ready;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||
|
||||
use tracing::trace;
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
use zstd::stream::write::Encoder as ZstdEncoder;
|
||||
@@ -50,21 +52,10 @@ impl<B: MessageBody> Encoder<B> {
|
||||
}
|
||||
}
|
||||
|
||||
fn empty() -> Self {
|
||||
Encoder {
|
||||
body: EncoderBody::Full { body: Bytes::new() },
|
||||
encoder: None,
|
||||
fut: None,
|
||||
eof: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, body: B) -> Self {
|
||||
// no need to compress empty bodies
|
||||
match body.size() {
|
||||
BodySize::None => return Self::none(),
|
||||
BodySize::Sized(0) => return Self::empty(),
|
||||
_ => {}
|
||||
// no need to compress an empty body
|
||||
if matches!(body.size(), BodySize::None) {
|
||||
return Self::none();
|
||||
}
|
||||
|
||||
let should_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|
||||
|
@@ -7,12 +7,13 @@ use bytes::{Bytes, BytesMut};
|
||||
mod decoder;
|
||||
mod encoder;
|
||||
|
||||
pub use self::{decoder::Decoder, encoder::Encoder};
|
||||
pub use self::decoder::Decoder;
|
||||
pub use self::encoder::Encoder;
|
||||
|
||||
/// Special-purpose writer for streaming (de-)compression.
|
||||
///
|
||||
/// Pre-allocates 8KiB of capacity.
|
||||
struct Writer {
|
||||
pub(self) struct Writer {
|
||||
buf: BytesMut,
|
||||
}
|
||||
|
||||
|
@@ -3,11 +3,12 @@
|
||||
use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};
|
||||
|
||||
use derive_more::{Display, Error, From};
|
||||
pub use http::Error as HttpError;
|
||||
use http::{uri::InvalidUri, StatusCode};
|
||||
|
||||
use crate::{body::BoxBody, Response};
|
||||
|
||||
pub use http::Error as HttpError;
|
||||
|
||||
pub struct Error {
|
||||
inner: Box<ErrorInner>,
|
||||
}
|
||||
|
@@ -16,7 +16,6 @@ use crate::{
|
||||
};
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Flags: u8 {
|
||||
const HEAD = 0b0000_0001;
|
||||
const KEEP_ALIVE_ENABLED = 0b0000_1000;
|
||||
|
@@ -9,10 +9,11 @@ use super::{
|
||||
decoder::{self, PayloadDecoder, PayloadItem, PayloadType},
|
||||
encoder, Message, MessageType,
|
||||
};
|
||||
use crate::{body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig};
|
||||
use crate::{
|
||||
body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig,
|
||||
};
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Flags: u8 {
|
||||
const HEAD = 0b0000_0001;
|
||||
const KEEP_ALIVE_ENABLED = 0b0000_0010;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use std::{io, marker::PhantomData, mem::MaybeUninit, task::Poll};
|
||||
use std::{convert::TryFrom, io, marker::PhantomData, mem::MaybeUninit, task::Poll};
|
||||
|
||||
use actix_codec::Decoder;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
@@ -94,7 +94,9 @@ pub(crate) trait MessageType: Sized {
|
||||
// SAFETY: httparse already checks header value is only visible ASCII bytes
|
||||
// from_maybe_shared_unchecked contains debug assertions so they are omitted here
|
||||
let value = unsafe {
|
||||
HeaderValue::from_maybe_shared_unchecked(slice.slice(idx.value.0..idx.value.1))
|
||||
HeaderValue::from_maybe_shared_unchecked(
|
||||
slice.slice(idx.value.0..idx.value.1),
|
||||
)
|
||||
};
|
||||
|
||||
match name {
|
||||
@@ -273,7 +275,8 @@ impl MessageType for Request {
|
||||
let mut msg = Request::new();
|
||||
|
||||
// convert headers
|
||||
let mut length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?;
|
||||
let mut length =
|
||||
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
|
||||
// see https://datatracker.ietf.org/doc/html/rfc1945#section-7.2.2
|
||||
@@ -353,8 +356,8 @@ impl MessageType for ResponseHead {
|
||||
Version::HTTP_10
|
||||
};
|
||||
|
||||
let status =
|
||||
StatusCode::from_u16(res.code.unwrap()).map_err(|_| ParseError::Status)?;
|
||||
let status = StatusCode::from_u16(res.code.unwrap())
|
||||
.map_err(|_| ParseError::Status)?;
|
||||
HeaderIndex::record(src, res.headers, &mut headers);
|
||||
|
||||
(len, version, status, res.headers.len())
|
||||
@@ -375,7 +378,8 @@ impl MessageType for ResponseHead {
|
||||
msg.version = ver;
|
||||
|
||||
// convert headers
|
||||
let mut length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?;
|
||||
let mut length =
|
||||
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.
|
||||
// Protects against some request smuggling attacks.
|
||||
@@ -532,7 +536,7 @@ impl Decoder for PayloadDecoder {
|
||||
*state = match state.step(src, size, &mut buf) {
|
||||
Poll::Pending => return Ok(None),
|
||||
Poll::Ready(Ok(state)) => state,
|
||||
Poll::Ready(Err(err)) => return Err(err),
|
||||
Poll::Ready(Err(e)) => return Err(e),
|
||||
};
|
||||
|
||||
if *state == ChunkedState::End {
|
||||
|
@@ -19,13 +19,6 @@ use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_util::codec::{Decoder as _, Encoder as _};
|
||||
use tracing::{error, trace};
|
||||
|
||||
use super::{
|
||||
codec::Codec,
|
||||
decoder::MAX_BUFFER_SIZE,
|
||||
payload::{Payload, PayloadSender, PayloadStatus},
|
||||
timer::TimerState,
|
||||
Message, MessageType,
|
||||
};
|
||||
use crate::{
|
||||
body::{BodySize, BoxBody, MessageBody},
|
||||
config::ServiceConfig,
|
||||
@@ -34,12 +27,19 @@ use crate::{
|
||||
Error, Extensions, OnConnectData, Request, Response, StatusCode,
|
||||
};
|
||||
|
||||
use super::{
|
||||
codec::Codec,
|
||||
decoder::MAX_BUFFER_SIZE,
|
||||
payload::{Payload, PayloadSender, PayloadStatus},
|
||||
timer::TimerState,
|
||||
Message, MessageType,
|
||||
};
|
||||
|
||||
const LW_BUFFER_SIZE: usize = 1024;
|
||||
const HW_BUFFER_SIZE: usize = 1024 * 8;
|
||||
const MAX_PIPELINED_MESSAGES: usize = 16;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Flags: u8 {
|
||||
/// Set when stream is read for first time.
|
||||
const STARTED = 0b0000_0001;
|
||||
@@ -212,7 +212,9 @@ where
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::None => write!(f, "State::None"),
|
||||
Self::ExpectCall { .. } => f.debug_struct("State::ExpectCall").finish_non_exhaustive(),
|
||||
Self::ExpectCall { .. } => {
|
||||
f.debug_struct("State::ExpectCall").finish_non_exhaustive()
|
||||
}
|
||||
Self::ServiceCall { .. } => {
|
||||
f.debug_struct("State::ServiceCall").finish_non_exhaustive()
|
||||
}
|
||||
@@ -273,7 +275,9 @@ where
|
||||
|
||||
head_timer: TimerState::new(config.client_request_deadline().is_some()),
|
||||
ka_timer: TimerState::new(config.keep_alive().enabled()),
|
||||
shutdown_timer: TimerState::new(config.client_disconnect_deadline().is_some()),
|
||||
shutdown_timer: TimerState::new(
|
||||
config.client_disconnect_deadline().is_some(),
|
||||
),
|
||||
|
||||
io: Some(io),
|
||||
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||
@@ -451,7 +455,9 @@ where
|
||||
}
|
||||
|
||||
// return with upgrade request and poll it exclusively
|
||||
Some(DispatcherMessage::Upgrade(req)) => return Ok(PollResponse::Upgrade(req)),
|
||||
Some(DispatcherMessage::Upgrade(req)) => {
|
||||
return Ok(PollResponse::Upgrade(req))
|
||||
}
|
||||
|
||||
// all messages are dealt with
|
||||
None => {
|
||||
@@ -668,7 +674,9 @@ where
|
||||
}
|
||||
|
||||
_ => {
|
||||
unreachable!("State must be set to ServiceCall or ExceptCall in handle_request")
|
||||
unreachable!(
|
||||
"State must be set to ServiceCall or ExceptCall in handle_request"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -677,7 +685,10 @@ where
|
||||
/// Process one incoming request.
|
||||
///
|
||||
/// Returns true if any meaningful work was done.
|
||||
fn poll_request(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Result<bool, DispatchError> {
|
||||
fn poll_request(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Result<bool, DispatchError> {
|
||||
let pipeline_queue_full = self.messages.len() >= MAX_PIPELINED_MESSAGES;
|
||||
let can_not_read = !self.can_read(cx);
|
||||
|
||||
@@ -847,7 +858,10 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_ka_timer(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Result<(), DispatchError> {
|
||||
fn poll_ka_timer(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Result<(), DispatchError> {
|
||||
let this = self.as_mut().project();
|
||||
if let TimerState::Active { timer } = this.ka_timer {
|
||||
debug_assert!(
|
||||
@@ -912,7 +926,10 @@ where
|
||||
}
|
||||
|
||||
/// Poll head, keep-alive, and disconnect timer.
|
||||
fn poll_timers(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Result<(), DispatchError> {
|
||||
fn poll_timers(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Result<(), DispatchError> {
|
||||
self.as_mut().poll_head_timer(cx)?;
|
||||
self.as_mut().poll_ka_timer(cx)?;
|
||||
self.as_mut().poll_shutdown_timer(cx)?;
|
||||
@@ -926,7 +943,10 @@ where
|
||||
/// - `std::io::ErrorKind::ConnectionReset` after partial read;
|
||||
/// - all data read done.
|
||||
#[inline(always)] // TODO: bench this inline
|
||||
fn read_available(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Result<bool, DispatchError> {
|
||||
fn read_available(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Result<bool, DispatchError> {
|
||||
let this = self.project();
|
||||
|
||||
if this.flags.contains(Flags::READ_DISCONNECT) {
|
||||
|
@@ -1,12 +1,15 @@
|
||||
use std::{future::Future, str, task::Poll, time::Duration};
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_rt::{pin, time::sleep};
|
||||
use actix_service::{fn_service, Service};
|
||||
use actix_service::fn_service;
|
||||
use actix_utils::future::{ready, Ready};
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use bytes::Bytes;
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_service::Service;
|
||||
use bytes::{Buf, BytesMut};
|
||||
|
||||
use super::dispatcher::{Dispatcher, DispatcherState, DispatcherStateProj, Flags};
|
||||
use crate::{
|
||||
body::MessageBody,
|
||||
@@ -40,8 +43,8 @@ fn status_service(
|
||||
fn_service(move |_req: Request| ready(Ok::<_, Error>(Response::new(status))))
|
||||
}
|
||||
|
||||
fn echo_path_service() -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
|
||||
{
|
||||
fn echo_path_service(
|
||||
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
|
||||
fn_service(|req: Request| {
|
||||
let path = req.path().as_bytes();
|
||||
ready(Ok::<_, Error>(
|
||||
@@ -50,8 +53,8 @@ fn echo_path_service() -> impl Service<Request, Response = Response<impl Message
|
||||
})
|
||||
}
|
||||
|
||||
fn drop_payload_service() -> impl Service<Request, Response = Response<&'static str>, Error = Error>
|
||||
{
|
||||
fn drop_payload_service(
|
||||
) -> impl Service<Request, Response = Response<&'static str>, Error = Error> {
|
||||
fn_service(|mut req: Request| async move {
|
||||
let _ = req.take_payload();
|
||||
Ok::<_, Error>(Response::with_body(StatusCode::OK, "payload dropped"))
|
||||
|
@@ -17,16 +17,14 @@ mod timer;
|
||||
mod upgrade;
|
||||
mod utils;
|
||||
|
||||
pub use self::{
|
||||
client::{ClientCodec, ClientPayloadCodec},
|
||||
codec::Codec,
|
||||
dispatcher::Dispatcher,
|
||||
expect::ExpectHandler,
|
||||
payload::Payload,
|
||||
service::{H1Service, H1ServiceHandler},
|
||||
upgrade::UpgradeHandler,
|
||||
utils::SendResponse,
|
||||
};
|
||||
pub use self::client::{ClientCodec, ClientPayloadCodec};
|
||||
pub use self::codec::Codec;
|
||||
pub use self::dispatcher::Dispatcher;
|
||||
pub use self::expect::ExpectHandler;
|
||||
pub use self::payload::Payload;
|
||||
pub use self::service::{H1Service, H1ServiceHandler};
|
||||
pub use self::upgrade::UpgradeHandler;
|
||||
pub use self::utils::SendResponse;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Codec message
|
||||
|
@@ -117,7 +117,6 @@ impl PayloadSender {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_ref_mut)]
|
||||
#[inline]
|
||||
pub fn need_read(&self, cx: &mut Context<'_>) -> PayloadStatus {
|
||||
// we check need_read only if Payload (other side) is alive,
|
||||
@@ -175,7 +174,7 @@ impl Inner {
|
||||
|
||||
/// Register future waiting data from payload.
|
||||
/// Waker would be used in `Inner::wake`
|
||||
fn register(&mut self, cx: &Context<'_>) {
|
||||
fn register(&mut self, cx: &mut Context<'_>) {
|
||||
if self
|
||||
.task
|
||||
.as_ref()
|
||||
@@ -187,7 +186,7 @@ impl Inner {
|
||||
|
||||
// Register future feeding data to payload.
|
||||
/// Waker would be used in `Inner::wake_io`
|
||||
fn register_io(&mut self, cx: &Context<'_>) {
|
||||
fn register_io(&mut self, cx: &mut Context<'_>) {
|
||||
if self
|
||||
.io_task
|
||||
.as_ref()
|
||||
@@ -222,7 +221,7 @@ impl Inner {
|
||||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &Context<'_>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, PayloadError>>> {
|
||||
if let Some(data) = self.items.pop_front() {
|
||||
self.len -= data.len();
|
||||
|
@@ -15,7 +15,6 @@ use actix_utils::future::ready;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use tracing::error;
|
||||
|
||||
use super::{codec::Codec, dispatcher::Dispatcher, ExpectHandler, UpgradeHandler};
|
||||
use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
config::ServiceConfig,
|
||||
@@ -24,6 +23,8 @@ use crate::{
|
||||
ConnectCallback, OnConnectData, Request, Response,
|
||||
};
|
||||
|
||||
use super::{codec::Codec, dispatcher::Dispatcher, ExpectHandler, UpgradeHandler};
|
||||
|
||||
/// `ServiceFactory` implementation for HTTP1 transport
|
||||
pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> {
|
||||
srv: S,
|
||||
@@ -81,8 +82,13 @@ where
|
||||
/// Create simple tcp stream service
|
||||
pub fn tcp(
|
||||
self,
|
||||
) -> impl ServiceFactory<TcpStream, Config = (), Response = (), Error = DispatchError, InitError = ()>
|
||||
{
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = DispatchError,
|
||||
InitError = (),
|
||||
> {
|
||||
fn_service(|io: TcpStream| {
|
||||
let peer_addr = io.peer_addr().ok();
|
||||
ready(Ok((io, peer_addr)))
|
||||
@@ -93,6 +99,8 @@ where
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
mod openssl {
|
||||
use super::*;
|
||||
|
||||
use actix_tls::accept::{
|
||||
openssl::{
|
||||
reexports::{Error as SslError, SslAcceptor},
|
||||
@@ -101,8 +109,6 @@ mod openssl {
|
||||
TlsError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
@@ -152,13 +158,14 @@ mod openssl {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_20")]
|
||||
mod rustls_0_20 {
|
||||
#[cfg(feature = "rustls")]
|
||||
mod rustls {
|
||||
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_20::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
rustls::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
@@ -188,7 +195,7 @@ mod rustls_0_20 {
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create Rustls v0.20 based service.
|
||||
/// Create Rustls based service.
|
||||
pub fn rustls(
|
||||
self,
|
||||
config: ServerConfig,
|
||||
@@ -213,128 +220,6 @@ mod rustls_0_20 {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_21")]
|
||||
mod rustls_0_21 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_21::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
|
||||
B: MessageBody,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<
|
||||
(Request, Framed<TlsStream<TcpStream>, Codec>),
|
||||
Config = (),
|
||||
Response = (),
|
||||
>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create Rustls v0.21 based service.
|
||||
pub fn rustls_021(
|
||||
self,
|
||||
config: ServerConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
Acceptor::new(config)
|
||||
.map_init_err(|_| {
|
||||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.map(|io: TlsStream<TcpStream>| {
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
(io, peer_addr)
|
||||
})
|
||||
.and_then(self.map_err(TlsError::Service))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
mod rustls_0_22 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
|
||||
B: MessageBody,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<
|
||||
(Request, Framed<TlsStream<TcpStream>, Codec>),
|
||||
Config = (),
|
||||
Response = (),
|
||||
>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create Rustls v0.22 based service.
|
||||
pub fn rustls_0_22(
|
||||
self,
|
||||
config: ServerConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
Acceptor::new(config)
|
||||
.map_init_err(|_| {
|
||||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.map(|io: TlsStream<TcpStream>| {
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
(io, peer_addr)
|
||||
})
|
||||
.and_then(self.map_err(TlsError::Service))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
|
@@ -23,7 +23,8 @@ use crate::{
|
||||
mod dispatcher;
|
||||
mod service;
|
||||
|
||||
pub use self::{dispatcher::Dispatcher, service::H2Service};
|
||||
pub use self::dispatcher::Dispatcher;
|
||||
pub use self::service::H2Service;
|
||||
|
||||
/// HTTP/2 peer stream.
|
||||
pub struct Payload {
|
||||
@@ -57,7 +58,10 @@ impl Stream for Payload {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handshake_with_timeout<T>(io: T, config: &ServiceConfig) -> HandshakeWithTimeout<T>
|
||||
pub(crate) fn handshake_with_timeout<T>(
|
||||
io: T,
|
||||
config: &ServiceConfig,
|
||||
) -> HandshakeWithTimeout<T>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
|
@@ -16,7 +16,6 @@ use actix_utils::future::ready;
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use tracing::{error, trace};
|
||||
|
||||
use super::{dispatcher::Dispatcher, handshake_with_timeout, HandshakeWithTimeout};
|
||||
use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
config::ServiceConfig,
|
||||
@@ -25,6 +24,8 @@ use crate::{
|
||||
ConnectCallback, OnConnectData, Request, Response,
|
||||
};
|
||||
|
||||
use super::{dispatcher::Dispatcher, handshake_with_timeout, HandshakeWithTimeout};
|
||||
|
||||
/// `ServiceFactory` implementation for HTTP/2 transport
|
||||
pub struct H2Service<T, S, B> {
|
||||
srv: S,
|
||||
@@ -140,8 +141,8 @@ mod openssl {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_20")]
|
||||
mod rustls_0_20 {
|
||||
#[cfg(feature = "rustls")]
|
||||
mod rustls {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
@@ -162,7 +163,7 @@ mod rustls_0_20 {
|
||||
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
/// Create Rustls v0.20 based service.
|
||||
/// Create Rustls based service.
|
||||
pub fn rustls(
|
||||
self,
|
||||
mut config: ServerConfig,
|
||||
@@ -191,108 +192,6 @@ mod rustls_0_20 {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_21")]
|
||||
mod rustls_0_21 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_21::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
/// Create Rustls v0.21 based service.
|
||||
pub fn rustls_021(
|
||||
self,
|
||||
mut config: ServerConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = S::InitError,
|
||||
> {
|
||||
let mut protos = vec![b"h2".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
||||
Acceptor::new(config)
|
||||
.map_init_err(|_| {
|
||||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.map(|io: TlsStream<TcpStream>| {
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
(io, peer_addr)
|
||||
})
|
||||
.and_then(self.map_err(TlsError::Service))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
mod rustls_0_22 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
/// Create Rustls v0.22 based service.
|
||||
pub fn rustls_0_22(
|
||||
self,
|
||||
mut config: ServerConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = S::InitError,
|
||||
> {
|
||||
let mut protos = vec![b"h2".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
||||
Acceptor::new(config)
|
||||
.map_init_err(|_| {
|
||||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.map(|io: TlsStream<TcpStream>| {
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
(io, peer_addr)
|
||||
})
|
||||
.and_then(self.map_err(TlsError::Service))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||
|
@@ -1,5 +1,7 @@
|
||||
//! [`TryIntoHeaderPair`] trait and implementations.
|
||||
|
||||
use std::convert::TryFrom as _;
|
||||
|
||||
use super::{
|
||||
Header, HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, TryIntoHeaderValue,
|
||||
};
|
||||
|
@@ -1,5 +1,7 @@
|
||||
//! [`TryIntoHeaderValue`] trait and implementations.
|
||||
|
||||
use std::convert::TryFrom as _;
|
||||
|
||||
use bytes::Bytes;
|
||||
use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue};
|
||||
use mime::Mime;
|
||||
|
@@ -636,24 +636,10 @@ impl<'a> IntoIterator for &'a HeaderMap {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a `http::HeaderMap` to our `HeaderMap`.
|
||||
/// Convert `http::HeaderMap` to our `HeaderMap`.
|
||||
impl From<http::HeaderMap> for HeaderMap {
|
||||
fn from(mut map: http::HeaderMap) -> Self {
|
||||
Self::from_drain(map.drain())
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert our `HeaderMap` to a `http::HeaderMap`.
|
||||
impl From<HeaderMap> for http::HeaderMap {
|
||||
fn from(map: HeaderMap) -> Self {
|
||||
Self::from_iter(map)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert our `&HeaderMap` to a `http::HeaderMap`.
|
||||
impl From<&HeaderMap> for http::HeaderMap {
|
||||
fn from(map: &HeaderMap) -> Self {
|
||||
map.to_owned().into()
|
||||
fn from(mut map: http::HeaderMap) -> HeaderMap {
|
||||
HeaderMap::from_drain(map.drain())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1134,7 +1120,9 @@ mod tests {
|
||||
assert!(vals.next().is_none());
|
||||
}
|
||||
|
||||
fn owned_pair<'a>((name, val): (&'a HeaderName, &'a HeaderValue)) -> (HeaderName, HeaderValue) {
|
||||
fn owned_pair<'a>(
|
||||
(name, val): (&'a HeaderName, &'a HeaderValue),
|
||||
) -> (HeaderName, HeaderValue) {
|
||||
(name.clone(), val.clone())
|
||||
}
|
||||
}
|
||||
|
@@ -3,30 +3,33 @@
|
||||
// declaring new header consts will yield this error
|
||||
#![allow(clippy::declare_interior_mutable_const)]
|
||||
|
||||
use percent_encoding::{AsciiSet, CONTROLS};
|
||||
|
||||
// re-export from http except header map related items
|
||||
pub use ::http::header::{
|
||||
HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, ToStrError,
|
||||
};
|
||||
|
||||
// re-export const header names, list is explicit so that any updates to `common` module do not
|
||||
// conflict with this set
|
||||
pub use ::http::header::{
|
||||
ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES,
|
||||
ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS,
|
||||
ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_MAX_AGE,
|
||||
ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE, ALLOW, ALT_SVC,
|
||||
AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, CONTENT_ENCODING,
|
||||
CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE, CONTENT_SECURITY_POLICY,
|
||||
CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, DATE, DNT, ETAG, EXPECT, EXPIRES,
|
||||
FORWARDED, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE, IF_NONE_MATCH, IF_RANGE,
|
||||
IF_UNMODIFIED_SINCE, LAST_MODIFIED, LINK, LOCATION, MAX_FORWARDS, ORIGIN, PRAGMA,
|
||||
PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, PUBLIC_KEY_PINS, PUBLIC_KEY_PINS_REPORT_ONLY, RANGE,
|
||||
REFERER, REFERRER_POLICY, REFRESH, RETRY_AFTER, SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS,
|
||||
SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL, SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE,
|
||||
STRICT_TRANSPORT_SECURITY, TE, TRAILER, TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS,
|
||||
USER_AGENT, VARY, VIA, WARNING, WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS,
|
||||
X_DNS_PREFETCH_CONTROL, X_FRAME_OPTIONS, X_XSS_PROTECTION,
|
||||
ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS,
|
||||
ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS,
|
||||
ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE,
|
||||
ALLOW, ALT_SVC, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION,
|
||||
CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE,
|
||||
CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, DATE,
|
||||
DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE,
|
||||
IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED, LINK, LOCATION, MAX_FORWARDS,
|
||||
ORIGIN, PRAGMA, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, PUBLIC_KEY_PINS,
|
||||
PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER, REFERRER_POLICY, REFRESH, RETRY_AFTER,
|
||||
SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL,
|
||||
SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE, STRICT_TRANSPORT_SECURITY, TE, TRAILER,
|
||||
TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA, WARNING,
|
||||
WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL, X_FRAME_OPTIONS,
|
||||
X_XSS_PROTECTION,
|
||||
};
|
||||
use percent_encoding::{AsciiSet, CONTROLS};
|
||||
|
||||
use crate::{error::ParseError, HttpMessage};
|
||||
|
||||
@@ -40,22 +43,23 @@ mod utils;
|
||||
|
||||
pub use self::{
|
||||
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_value::TryIntoHeaderValue,
|
||||
map::HeaderMap,
|
||||
shared::{
|
||||
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag,
|
||||
Quality, QualityItem,
|
||||
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate,
|
||||
LanguageTag, Quality, QualityItem,
|
||||
},
|
||||
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.
|
||||
pub trait Header: TryIntoHeaderValue {
|
||||
/// Returns the name of the header field.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use std::str::FromStr;
|
||||
use std::{convert::TryFrom, str::FromStr};
|
||||
|
||||
use derive_more::{Display, Error};
|
||||
use http::header::InvalidHeaderValue;
|
||||
|
@@ -1,7 +1,5 @@
|
||||
//! Originally taken from `hyper::header::shared`.
|
||||
|
||||
pub use language_tags::LanguageTag;
|
||||
|
||||
mod charset;
|
||||
mod content_encoding;
|
||||
mod extended;
|
||||
@@ -9,11 +7,10 @@ mod http_date;
|
||||
mod quality;
|
||||
mod quality_item;
|
||||
|
||||
pub use self::{
|
||||
charset::Charset,
|
||||
content_encoding::ContentEncoding,
|
||||
extended::{parse_extended_value, ExtendedValue},
|
||||
http_date::HttpDate,
|
||||
quality::{q, Quality},
|
||||
quality_item::QualityItem,
|
||||
};
|
||||
pub use self::charset::Charset;
|
||||
pub use self::content_encoding::ContentEncoding;
|
||||
pub use self::extended::{parse_extended_value, ExtendedValue};
|
||||
pub use self::http_date::HttpDate;
|
||||
pub use self::quality::{q, Quality};
|
||||
pub use self::quality_item::QualityItem;
|
||||
pub use language_tags::LanguageTag;
|
||||
|
@@ -1,4 +1,7 @@
|
||||
use std::fmt;
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt,
|
||||
};
|
||||
|
||||
use derive_more::{Display, Error};
|
||||
|
||||
|
@@ -1,7 +1,8 @@
|
||||
use std::{cmp, fmt, str};
|
||||
use std::{cmp, convert::TryFrom as _, fmt, str};
|
||||
|
||||
use crate::error::ParseError;
|
||||
|
||||
use super::Quality;
|
||||
use crate::error::ParseError;
|
||||
|
||||
/// 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).
|
||||
|
@@ -61,7 +61,9 @@ pub trait HttpMessage: Sized {
|
||||
fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> {
|
||||
if let Some(mime_type) = self.mime_type()? {
|
||||
if let Some(charset) = mime_type.get_param("charset") {
|
||||
if let Some(enc) = Encoding::for_label_no_replacement(charset.as_str().as_bytes()) {
|
||||
if let Some(enc) =
|
||||
Encoding::for_label_no_replacement(charset.as_str().as_bytes())
|
||||
{
|
||||
Ok(enc)
|
||||
} else {
|
||||
Err(ContentTypeError::UnknownEncoding)
|
||||
@@ -144,7 +146,7 @@ mod tests {
|
||||
.finish();
|
||||
assert_eq!(req.content_type(), "text/plain");
|
||||
let req = TestRequest::default()
|
||||
.insert_header(("content-type", "application/json; charset=utf-8"))
|
||||
.insert_header(("content-type", "application/json; charset=utf=8"))
|
||||
.finish();
|
||||
assert_eq!(req.content_type(), "application/json");
|
||||
let req = TestRequest::default().finish();
|
||||
|
@@ -1,7 +1,6 @@
|
||||
//! HTTP types and services for the Actix ecosystem.
|
||||
//! HTTP primitives for the Actix ecosystem.
|
||||
//!
|
||||
//! ## Crate Features
|
||||
//!
|
||||
//! | Feature | Functionality |
|
||||
//! | ------------------- | ------------------------------------------- |
|
||||
//! | `http2` | HTTP/2 support via [h2]. |
|
||||
@@ -22,13 +21,15 @@
|
||||
#![allow(
|
||||
clippy::type_complexity,
|
||||
clippy::too_many_arguments,
|
||||
clippy::borrow_interior_mutable_const
|
||||
clippy::borrow_interior_mutable_const,
|
||||
clippy::uninlined_format_args
|
||||
)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
||||
pub use ::http::{uri, uri::Uri, Method, StatusCode, Version};
|
||||
pub use ::http::{uri, uri::Uri};
|
||||
pub use ::http::{Method, StatusCode, Version};
|
||||
|
||||
pub mod body;
|
||||
mod builder;
|
||||
@@ -56,29 +57,22 @@ pub mod test;
|
||||
#[cfg(feature = "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)]
|
||||
pub use self::payload::PayloadStream;
|
||||
#[cfg(any(
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
))]
|
||||
pub use self::payload::{BoxedPayloadStream, Payload, PayloadStream};
|
||||
pub use self::requests::{Request, RequestHead, RequestHeadType};
|
||||
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::{
|
||||
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.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
|
@@ -16,7 +16,6 @@ pub enum ConnectionType {
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct Flags: u8 {
|
||||
const CLOSE = 0b0000_0001;
|
||||
const KEEP_ALIVE = 0b0000_0010;
|
||||
|
@@ -16,10 +16,7 @@ pub struct RequestHead {
|
||||
pub uri: Uri,
|
||||
pub version: Version,
|
||||
pub headers: HeaderMap,
|
||||
|
||||
/// Will only be None when called in unit tests unless set manually.
|
||||
pub peer_addr: Option<net::SocketAddr>,
|
||||
|
||||
flags: Flags,
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,5 @@
|
||||
mod head;
|
||||
mod request;
|
||||
|
||||
pub use self::{
|
||||
head::{RequestHead, RequestHeadType},
|
||||
request::Request,
|
||||
};
|
||||
pub use self::head::{RequestHead, RequestHeadType};
|
||||
pub use self::request::Request;
|
||||
|
@@ -10,7 +10,8 @@ use std::{
|
||||
use http::{header, Method, Uri, Version};
|
||||
|
||||
use crate::{
|
||||
header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Message, Payload, RequestHead,
|
||||
header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Message, Payload,
|
||||
RequestHead,
|
||||
};
|
||||
|
||||
/// An HTTP request.
|
||||
@@ -173,7 +174,7 @@ impl<P> Request<P> {
|
||||
/// Peer address is the directly connected peer's socket address. If a proxy is used in front of
|
||||
/// the Actix Web server, then it would be address of this proxy.
|
||||
///
|
||||
/// Will only return None when called in unit tests unless set manually.
|
||||
/// Will only return None when called in unit tests.
|
||||
#[inline]
|
||||
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||
self.head().peer_addr
|
||||
@@ -233,6 +234,7 @@ impl<P> fmt::Debug for Request<P> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[test]
|
||||
fn test_basics() {
|
||||
|
@@ -93,7 +93,7 @@ impl ResponseBuilder {
|
||||
Ok((key, value)) => {
|
||||
parts.headers.insert(key, value);
|
||||
}
|
||||
Err(err) => self.err = Some(err.into()),
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ impl ResponseBuilder {
|
||||
if let Some(parts) = self.inner() {
|
||||
match header.try_into_pair() {
|
||||
Ok((key, value)) => parts.headers.append(key, value),
|
||||
Err(err) => self.err = Some(err.into()),
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ impl ResponseBuilder {
|
||||
Ok(value) => {
|
||||
parts.headers.insert(header::CONTENT_TYPE, value);
|
||||
}
|
||||
Err(err) => self.err = Some(err.into()),
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
};
|
||||
}
|
||||
self
|
||||
|
@@ -5,5 +5,7 @@ mod head;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod response;
|
||||
|
||||
pub use self::builder::ResponseBuilder;
|
||||
pub(crate) use self::head::BoxedResponseHead;
|
||||
pub use self::{builder::ResponseBuilder, head::ResponseHead, response::Response};
|
||||
pub use self::head::ResponseHead;
|
||||
pub use self::response::Response;
|
||||
|
@@ -30,9 +30,9 @@ use crate::{
|
||||
///
|
||||
/// # Automatic HTTP Version Selection
|
||||
/// 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 TLS (HTTPS); both versions
|
||||
/// are supported automatically when using either of the `.rustls()` or `.openssl()` finalizing
|
||||
/// methods.
|
||||
/// - One is to rely on the ALPN information that is provided when using a TLS (HTTPS); both
|
||||
/// versions are supported automatically when using either of the `.rustls()` or `.openssl()`
|
||||
/// finalizing methods.
|
||||
/// - 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
|
||||
/// the `.tcp_auto_h2c()` finalizing method to enable this behavior.
|
||||
@@ -200,8 +200,13 @@ where
|
||||
/// The resulting service only supports HTTP/1.x.
|
||||
pub fn tcp(
|
||||
self,
|
||||
) -> impl ServiceFactory<TcpStream, Config = (), Response = (), Error = DispatchError, InitError = ()>
|
||||
{
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = DispatchError,
|
||||
InitError = (),
|
||||
> {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let peer_addr = io.peer_addr().ok();
|
||||
Ok((io, Protocol::Http1, peer_addr))
|
||||
@@ -214,8 +219,13 @@ where
|
||||
#[cfg(feature = "http2")]
|
||||
pub fn tcp_auto_h2c(
|
||||
self,
|
||||
) -> impl ServiceFactory<TcpStream, Config = (), Response = (), Error = DispatchError, InitError = ()>
|
||||
{
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = DispatchError,
|
||||
InitError = (),
|
||||
> {
|
||||
fn_service(move |io: TcpStream| async move {
|
||||
// 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
|
||||
@@ -241,23 +251,13 @@ where
|
||||
}
|
||||
|
||||
/// Configuration options used when accepting TLS connection.
|
||||
#[cfg(any(
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
))]
|
||||
#[cfg(any(feature = "openssl", feature = "rustls"))]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TlsAcceptorConfig {
|
||||
pub(crate) handshake_timeout: Option<std::time::Duration>,
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
))]
|
||||
#[cfg(any(feature = "openssl", feature = "rustls"))]
|
||||
impl TlsAcceptorConfig {
|
||||
/// Set TLS handshake timeout duration.
|
||||
pub fn handshake_timeout(self, dur: std::time::Duration) -> Self {
|
||||
@@ -362,13 +362,13 @@ mod openssl {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_20")]
|
||||
mod rustls_0_20 {
|
||||
#[cfg(feature = "rustls")]
|
||||
mod rustls {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_20::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
rustls::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
@@ -399,7 +399,7 @@ mod rustls_0_20 {
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create Rustls v0.20 based service.
|
||||
/// Create Rustls based service.
|
||||
pub fn rustls(
|
||||
self,
|
||||
config: ServerConfig,
|
||||
@@ -413,7 +413,7 @@ mod rustls_0_20 {
|
||||
self.rustls_with_config(config, TlsAcceptorConfig::default())
|
||||
}
|
||||
|
||||
/// Create Rustls v0.20 based service with custom TLS acceptor configuration.
|
||||
/// Create Rustls based service with custom TLS acceptor configuration.
|
||||
pub fn rustls_with_config(
|
||||
self,
|
||||
mut config: ServerConfig,
|
||||
@@ -458,198 +458,6 @@ mod rustls_0_20 {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_21")]
|
||||
mod rustls_0_21 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_21::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<
|
||||
(Request, Framed<TlsStream<TcpStream>, h1::Codec>),
|
||||
Config = (),
|
||||
Response = (),
|
||||
>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create Rustls v0.21 based service.
|
||||
pub fn rustls_021(
|
||||
self,
|
||||
config: ServerConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
self.rustls_021_with_config(config, TlsAcceptorConfig::default())
|
||||
}
|
||||
|
||||
/// Create Rustls v0.21 based service with custom TLS acceptor configuration.
|
||||
pub fn rustls_021_with_config(
|
||||
self,
|
||||
mut config: ServerConfig,
|
||||
tls_acceptor_config: TlsAcceptorConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
||||
let mut acceptor = Acceptor::new(config);
|
||||
|
||||
if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout {
|
||||
acceptor.set_handshake_timeout(handshake_timeout);
|
||||
}
|
||||
|
||||
acceptor
|
||||
.map_init_err(|_| {
|
||||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.and_then(|io: TlsStream<TcpStream>| async {
|
||||
let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
|
||||
if protos.windows(2).any(|window| window == b"h2") {
|
||||
Protocol::Http2
|
||||
} else {
|
||||
Protocol::Http1
|
||||
}
|
||||
} else {
|
||||
Protocol::Http1
|
||||
};
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
Ok((io, proto, peer_addr))
|
||||
})
|
||||
.and_then(self.map_err(TlsError::Service))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
mod rustls_0_22 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<
|
||||
(Request, Framed<TlsStream<TcpStream>, h1::Codec>),
|
||||
Config = (),
|
||||
Response = (),
|
||||
>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create Rustls v0.22 based service.
|
||||
pub fn rustls_0_22(
|
||||
self,
|
||||
config: ServerConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
self.rustls_0_22_with_config(config, TlsAcceptorConfig::default())
|
||||
}
|
||||
|
||||
/// Create Rustls v0.22 based service with custom TLS acceptor configuration.
|
||||
pub fn rustls_0_22_with_config(
|
||||
self,
|
||||
mut config: ServerConfig,
|
||||
tls_acceptor_config: TlsAcceptorConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
||||
let mut acceptor = Acceptor::new(config);
|
||||
|
||||
if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout {
|
||||
acceptor.set_handshake_timeout(handshake_timeout);
|
||||
}
|
||||
|
||||
acceptor
|
||||
.map_init_err(|_| {
|
||||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.and_then(|io: TlsStream<TcpStream>| async {
|
||||
let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
|
||||
if protos.windows(2).any(|window| window == b"h2") {
|
||||
Protocol::Http2
|
||||
} else {
|
||||
Protocol::Http1
|
||||
}
|
||||
} else {
|
||||
Protocol::Http1
|
||||
};
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
Ok((io, proto, peer_addr))
|
||||
})
|
||||
.and_then(self.map_err(TlsError::Service))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, B, X, U> ServiceFactory<(T, Protocol, Option<net::SocketAddr>)>
|
||||
for HttpService<T, S, B, X, U>
|
||||
where
|
||||
@@ -755,7 +563,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn _poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Response<BoxBody>>> {
|
||||
pub(super) fn _poll_ready(
|
||||
&self,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), Response<BoxBody>>> {
|
||||
ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?;
|
||||
|
||||
ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?;
|
||||
@@ -814,7 +625,10 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn call(&self, (io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>)) -> Self::Future {
|
||||
fn call(
|
||||
&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());
|
||||
|
||||
match proto {
|
||||
|
@@ -74,7 +74,6 @@ pub struct Codec {
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Flags: u8 {
|
||||
const SERVER = 0b0000_0001;
|
||||
const CONTINUATION = 0b0000_0010;
|
||||
@@ -296,7 +295,7 @@ impl Decoder for Codec {
|
||||
}
|
||||
}
|
||||
Ok(None) => Ok(None),
|
||||
Err(err) => Err(err),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -70,14 +70,15 @@ mod inner {
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_service::{IntoService, Service};
|
||||
use futures_core::stream::Stream;
|
||||
use local_channel::mpsc;
|
||||
use pin_project_lite::pin_project;
|
||||
use tracing::debug;
|
||||
|
||||
use actix_codec::Framed;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_util::codec::{Decoder, Encoder};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{body::BoxBody, Response};
|
||||
|
||||
@@ -412,7 +413,9 @@ mod inner {
|
||||
}
|
||||
State::Error(_) => {
|
||||
// flush write buffer
|
||||
if !this.framed.is_write_buf_empty() && this.framed.flush(cx).is_pending() {
|
||||
if !this.framed.is_write_buf_empty()
|
||||
&& this.framed.flush(cx).is_pending()
|
||||
{
|
||||
return Poll::Pending;
|
||||
}
|
||||
Poll::Ready(Err(this.state.take_error()))
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use std::cmp::min;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
use tracing::debug;
|
||||
@@ -96,10 +96,6 @@ impl Parser {
|
||||
|
||||
// not enough data
|
||||
if src.len() < idx + length {
|
||||
let min_length = min(length, max_size);
|
||||
if src.capacity() < idx + min_length {
|
||||
src.reserve(idx + min_length - src.capacity());
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
@@ -221,9 +217,8 @@ impl Parser {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bytes::Bytes;
|
||||
|
||||
use super::*;
|
||||
use bytes::Bytes;
|
||||
|
||||
struct F {
|
||||
finished: bool,
|
||||
|
@@ -50,7 +50,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_apply_mask() {
|
||||
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
||||
let unmasked = [
|
||||
let unmasked = vec![
|
||||
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9,
|
||||
0x12, 0x03,
|
||||
];
|
||||
|
@@ -8,7 +8,8 @@ use std::io;
|
||||
use derive_more::{Display, Error, From};
|
||||
use http::{header, Method, StatusCode};
|
||||
|
||||
use crate::{body::BoxBody, header::HeaderValue, RequestHead, Response, ResponseBuilder};
|
||||
use crate::body::BoxBody;
|
||||
use crate::{header::HeaderValue, RequestHead, Response, ResponseBuilder};
|
||||
|
||||
mod codec;
|
||||
mod dispatcher;
|
||||
@@ -16,12 +17,10 @@ mod frame;
|
||||
mod mask;
|
||||
mod proto;
|
||||
|
||||
pub use self::{
|
||||
codec::{Codec, Frame, Item, Message},
|
||||
dispatcher::Dispatcher,
|
||||
frame::Parser,
|
||||
proto::{hash_key, CloseCode, CloseReason, OpCode},
|
||||
};
|
||||
pub use self::codec::{Codec, Frame, Item, Message};
|
||||
pub use self::dispatcher::Dispatcher;
|
||||
pub use self::frame::Parser;
|
||||
pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode};
|
||||
|
||||
/// WebSocket protocol errors.
|
||||
#[derive(Debug, Display, Error, From)]
|
||||
@@ -220,8 +219,10 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{header, Method};
|
||||
|
||||
use super::*;
|
||||
use crate::{header, test::TestRequest, Method};
|
||||
use crate::test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_handshake() {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
#![cfg(feature = "openssl")]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
extern crate tls_openssl as openssl;
|
||||
|
||||
@@ -320,7 +321,8 @@ async fn h2_body_length() {
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| async {
|
||||
let body = once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) });
|
||||
let body =
|
||||
once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) });
|
||||
|
||||
Ok::<_, Infallible>(
|
||||
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
|
||||
|
@@ -1,9 +1,10 @@
|
||||
#![cfg(feature = "rustls-0_22")]
|
||||
#![cfg(feature = "rustls")]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
extern crate tls_rustls_022 as rustls;
|
||||
extern crate tls_rustls as rustls;
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
convert::{Infallible, TryFrom},
|
||||
io::{self, BufReader, Write},
|
||||
net::{SocketAddr, TcpStream as StdTcpStream},
|
||||
sync::Arc,
|
||||
@@ -20,13 +21,13 @@ use actix_http::{
|
||||
use actix_http_test::test_server;
|
||||
use actix_rt::pin;
|
||||
use actix_service::{fn_factory_with_config, fn_service};
|
||||
use actix_tls::connect::rustls_0_22::webpki_roots_cert_store;
|
||||
use actix_tls::connect::rustls::webpki_roots_cert_store;
|
||||
use actix_utils::future::{err, ok, poll_fn};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use derive_more::{Display, Error};
|
||||
use futures_core::{ready, Stream};
|
||||
use futures_util::stream::once;
|
||||
use rustls::{pki_types::ServerName, ServerConfig as RustlsServerConfig};
|
||||
use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName};
|
||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
|
||||
async fn load_body<S>(stream: S) -> Result<BytesMut, PayloadError>
|
||||
@@ -59,17 +60,17 @@ fn tls_config() -> RustlsServerConfig {
|
||||
let cert_file = &mut BufReader::new(cert_file.as_bytes());
|
||||
let key_file = &mut BufReader::new(key_file.as_bytes());
|
||||
|
||||
let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap();
|
||||
let mut keys = pkcs8_private_keys(key_file)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
let cert_chain = certs(cert_file)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
let mut keys = pkcs8_private_keys(key_file).unwrap();
|
||||
|
||||
let mut config = RustlsServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(
|
||||
cert_chain,
|
||||
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
|
||||
)
|
||||
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
|
||||
.unwrap();
|
||||
|
||||
config.alpn_protocols.push(HTTP1_1_ALPN_PROTOCOL.to_vec());
|
||||
@@ -83,14 +84,17 @@ pub fn get_negotiated_alpn_protocol(
|
||||
client_alpn_protocol: &[u8],
|
||||
) -> Option<Vec<u8>> {
|
||||
let mut config = rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(webpki_roots_cert_store())
|
||||
.with_no_client_auth();
|
||||
|
||||
config.alpn_protocols.push(client_alpn_protocol.to_vec());
|
||||
|
||||
let mut sess =
|
||||
rustls::ClientConnection::new(Arc::new(config), ServerName::try_from("localhost").unwrap())
|
||||
.unwrap();
|
||||
let mut sess = rustls::ClientConnection::new(
|
||||
Arc::new(config),
|
||||
ServerName::try_from("localhost").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut sock = StdTcpStream::connect(addr).unwrap();
|
||||
let mut stream = rustls::Stream::new(&mut sess, &mut sock);
|
||||
@@ -108,7 +112,7 @@ async fn h1() -> io::Result<()> {
|
||||
let srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h1(|_| ok::<_, Error>(Response::ok()))
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -122,7 +126,7 @@ async fn h2() -> io::Result<()> {
|
||||
let srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| ok::<_, Error>(Response::ok()))
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -140,7 +144,7 @@ async fn h1_1() -> io::Result<()> {
|
||||
assert_eq!(req.version(), Version::HTTP_11);
|
||||
ok::<_, Error>(Response::ok())
|
||||
})
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -158,7 +162,7 @@ async fn h2_1() -> io::Result<()> {
|
||||
assert_eq!(req.version(), Version::HTTP_2);
|
||||
ok::<_, Error>(Response::ok())
|
||||
})
|
||||
.rustls_0_22_with_config(
|
||||
.rustls_with_config(
|
||||
tls_config(),
|
||||
TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)),
|
||||
)
|
||||
@@ -179,7 +183,7 @@ async fn h2_body1() -> io::Result<()> {
|
||||
let body = load_body(req.take_payload()).await?;
|
||||
Ok::<_, Error>(Response::ok().set_body(body))
|
||||
})
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -205,7 +209,7 @@ async fn h2_content_length() {
|
||||
];
|
||||
ok::<_, Infallible>(Response::new(statuses[indx]))
|
||||
})
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -277,7 +281,7 @@ async fn h2_headers() {
|
||||
}
|
||||
ok::<_, Infallible>(config.body(data.clone()))
|
||||
})
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -316,7 +320,7 @@ async fn h2_body2() {
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -333,7 +337,7 @@ async fn h2_head_empty() {
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -359,7 +363,7 @@ async fn h2_head_binary() {
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -384,7 +388,7 @@ async fn h2_head_binary2() {
|
||||
let srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -410,7 +414,7 @@ async fn h2_body_length() {
|
||||
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
|
||||
)
|
||||
})
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -434,7 +438,7 @@ async fn h2_body_chunked_explicit() {
|
||||
.body(BodyStream::new(body)),
|
||||
)
|
||||
})
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -463,7 +467,7 @@ async fn h2_response_http_error_handling() {
|
||||
)
|
||||
}))
|
||||
}))
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -493,7 +497,7 @@ async fn h2_service_error() {
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| err::<Response<BoxBody>, _>(BadRequest))
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -510,7 +514,7 @@ async fn h1_service_error() {
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h1(|_| err::<Response<BoxBody>, _>(BadRequest))
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -533,7 +537,7 @@ async fn alpn_h1() -> io::Result<()> {
|
||||
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
|
||||
HttpService::build()
|
||||
.h1(|_| ok::<_, Error>(Response::ok()))
|
||||
.rustls_0_22(config)
|
||||
.rustls(config)
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -555,7 +559,7 @@ async fn alpn_h2() -> io::Result<()> {
|
||||
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
|
||||
HttpService::build()
|
||||
.h2(|_| ok::<_, Error>(Response::ok()))
|
||||
.rustls_0_22(config)
|
||||
.rustls(config)
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -581,7 +585,7 @@ async fn alpn_h2_1() -> io::Result<()> {
|
||||
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
|
||||
HttpService::build()
|
||||
.finish(|_| ok::<_, Error>(Response::ok()))
|
||||
.rustls_0_22(config)
|
||||
.rustls(config)
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
io::{Read, Write},
|
||||
@@ -137,7 +139,7 @@ async fn expect_continue_h1() {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn chunked_payload() {
|
||||
let chunk_sizes = [32768, 32, 32768];
|
||||
let chunk_sizes = vec![32768, 32, 32768];
|
||||
let total_size: usize = chunk_sizes.iter().sum();
|
||||
|
||||
let mut srv = test_server(|| {
|
||||
@@ -147,7 +149,7 @@ async fn chunked_payload() {
|
||||
.take_payload()
|
||||
.map(|res| match res {
|
||||
Ok(pl) => pl,
|
||||
Err(err) => panic!("Error reading payload: {err}"),
|
||||
Err(e) => panic!("Error reading payload: {}", e),
|
||||
})
|
||||
.fold(0usize, |acc, chunk| ready(acc + chunk.len()))
|
||||
.map(|req_size| {
|
||||
@@ -164,7 +166,8 @@ async fn chunked_payload() {
|
||||
|
||||
for chunk_size in chunk_sizes.iter() {
|
||||
let mut bytes = Vec::new();
|
||||
let random_bytes: Vec<u8> = (0..*chunk_size).map(|_| rand::random::<u8>()).collect();
|
||||
let random_bytes: Vec<u8> =
|
||||
(0..*chunk_size).map(|_| rand::random::<u8>()).collect();
|
||||
|
||||
bytes.extend(format!("{:X}\r\n", chunk_size).as_bytes());
|
||||
bytes.extend(&random_bytes[..]);
|
||||
@@ -349,7 +352,8 @@ async fn http10_keepalive() {
|
||||
.await;
|
||||
|
||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||
let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n");
|
||||
let _ =
|
||||
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 _ = stream.read(&mut data);
|
||||
assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n");
|
||||
@@ -400,7 +404,7 @@ async fn content_length() {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.h1(|req: Request| {
|
||||
let idx: usize = req.uri().path()[1..].parse().unwrap();
|
||||
let indx: usize = req.uri().path()[1..].parse().unwrap();
|
||||
let statuses = [
|
||||
StatusCode::NO_CONTENT,
|
||||
StatusCode::CONTINUE,
|
||||
@@ -409,7 +413,7 @@ async fn content_length() {
|
||||
StatusCode::OK,
|
||||
StatusCode::NOT_FOUND,
|
||||
];
|
||||
ok::<_, Infallible>(Response::new(statuses[idx]))
|
||||
ok::<_, Infallible>(Response::new(statuses[indx]))
|
||||
})
|
||||
.tcp()
|
||||
})
|
||||
@@ -791,9 +795,8 @@ async fn not_modified_spec_h1() {
|
||||
.map_into_boxed_body(),
|
||||
|
||||
// with no content-length
|
||||
"/body" => {
|
||||
Response::with_body(StatusCode::NOT_MODIFIED, "1234").map_into_boxed_body()
|
||||
}
|
||||
"/body" => Response::with_body(StatusCode::NOT_MODIFIED, "1234")
|
||||
.map_into_boxed_body(),
|
||||
|
||||
// with manual content-length header and specific None body
|
||||
"/cl-none" => {
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use std::{
|
||||
cell::Cell,
|
||||
convert::Infallible,
|
||||
|
@@ -1,12 +1,5 @@
|
||||
# Changes
|
||||
|
||||
## 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
|
||||
## 0.6.0 - 2023-02-26
|
||||
|
||||
- Add `MultipartForm` derive macro.
|
||||
|
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "actix-multipart-derive"
|
||||
version = "0.6.1"
|
||||
version = "0.6.0"
|
||||
authors = ["Jacob Halsey <jacob@jhalsey.com>"]
|
||||
description = "Multipart form derive macro for Actix Web"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
@@ -17,11 +17,11 @@ all-features = true
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
darling = "0.20"
|
||||
darling = "0.14"
|
||||
parse-size = "1"
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = "2"
|
||||
syn = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-multipart = "0.6"
|
||||
|
@@ -1,21 +1,17 @@
|
||||
# `actix-multipart-derive`
|
||||
# actix-multipart-derive
|
||||
|
||||
> The derive macro implementation for actix-multipart-derive.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-multipart-derive)
|
||||
[](https://docs.rs/actix-multipart-derive/0.6.1)
|
||||

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

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-multipart-derive/0.6.1)
|
||||
[](https://deps.rs/crate/actix-multipart-derive/0.5.0)
|
||||
[](https://crates.io/crates/actix-multipart-derive)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-multipart-derive)
|
||||
- Minimum Supported Rust Version (MSRV): 1.68
|
||||
- Minimum Supported Rust Version (MSRV): 1.59
|
||||
|
@@ -8,7 +8,7 @@
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::{collections::HashSet, convert::TryFrom as _};
|
||||
|
||||
use darling::{FromDeriveInput, FromField, FromMeta};
|
||||
use parse_size::parse_size;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#[rustversion::stable(1.68)] // MSRV
|
||||
#[rustversion::stable(1.59)] // MSRV
|
||||
#[test]
|
||||
fn compile_macros() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
@@ -1,51 +1,47 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased
|
||||
## Unreleased - 2023-xx-xx
|
||||
|
||||
## 0.6.1
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 0.6.0
|
||||
## 0.6.0 - 2023-02-26
|
||||
|
||||
- Added `MultipartForm` typed data extractor. [#2883]
|
||||
|
||||
[#2883]: https://github.com/actix/actix-web/pull/2883
|
||||
|
||||
## 0.5.0
|
||||
## 0.5.0 - 2023-01-21
|
||||
|
||||
- `Field::content_type()` now returns `Option<&mime::Mime>`. [#2885]
|
||||
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
|
||||
|
||||
[#2885]: https://github.com/actix/actix-web/pull/2885
|
||||
|
||||
## 0.4.0
|
||||
## 0.4.0 - 2022-02-25
|
||||
|
||||
- No significant changes since `0.4.0-beta.13`.
|
||||
|
||||
## 0.4.0-beta.13
|
||||
## 0.4.0-beta.13 - 2022-01-31
|
||||
|
||||
- No significant changes since `0.4.0-beta.12`.
|
||||
|
||||
## 0.4.0-beta.12
|
||||
## 0.4.0-beta.12 - 2022-01-04
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||
|
||||
## 0.4.0-beta.11
|
||||
## 0.4.0-beta.11 - 2021-12-27
|
||||
|
||||
- No significant changes since `0.4.0-beta.10`.
|
||||
|
||||
## 0.4.0-beta.10
|
||||
## 0.4.0-beta.10 - 2021-12-11
|
||||
|
||||
- No significant changes since `0.4.0-beta.9`.
|
||||
|
||||
## 0.4.0-beta.9
|
||||
## 0.4.0-beta.9 - 2021-12-01
|
||||
|
||||
- Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463]
|
||||
|
||||
[#2463]: https://github.com/actix/actix-web/pull/2463
|
||||
|
||||
## 0.4.0-beta.8
|
||||
## 0.4.0-beta.8 - 2021-11-22
|
||||
|
||||
- Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451]
|
||||
- Added `MultipartError::NoContentDisposition` variant. [#2451]
|
||||
@@ -56,31 +52,31 @@
|
||||
|
||||
[#2451]: https://github.com/actix/actix-web/pull/2451
|
||||
|
||||
## 0.4.0-beta.7
|
||||
## 0.4.0-beta.7 - 2021-10-20
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
## 0.4.0-beta.6
|
||||
## 0.4.0-beta.6 - 2021-09-09
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
||||
## 0.4.0-beta.5
|
||||
## 0.4.0-beta.5 - 2021-06-17
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 0.4.0-beta.4
|
||||
## 0.4.0-beta.4 - 2021-04-02
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 0.4.0-beta.3
|
||||
## 0.4.0-beta.3 - 2021-03-09
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 0.4.0-beta.2
|
||||
## 0.4.0-beta.2 - 2021-02-10
|
||||
|
||||
- No notable changes.
|
||||
|
||||
## 0.4.0-beta.1
|
||||
## 0.4.0-beta.1 - 2021-01-07
|
||||
|
||||
- Fix multipart consuming payload before header checks. [#1513]
|
||||
- Update `bytes` to `1.0`. [#1813]
|
||||
@@ -88,19 +84,19 @@
|
||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||
[#1513]: https://github.com/actix/actix-web/pull/1513
|
||||
|
||||
## 0.3.0
|
||||
## 0.3.0 - 2020-09-11
|
||||
|
||||
- No significant changes from `0.3.0-beta.2`.
|
||||
|
||||
## 0.3.0-beta.2
|
||||
## 0.3.0-beta.2 - 2020-09-10
|
||||
|
||||
- Update `actix-*` dependencies to latest versions.
|
||||
|
||||
## 0.3.0-beta.1
|
||||
## 0.3.0-beta.1 - 2020-07-15
|
||||
|
||||
- Update `actix-web` to 3.0.0-beta.1
|
||||
|
||||
## 0.3.0-alpha.1
|
||||
## 0.3.0-alpha.1 - 2020-05-25
|
||||
|
||||
- Update `actix-web` to 3.0.0-alpha.3
|
||||
- Bump minimum supported Rust version to 1.40
|
||||
@@ -108,45 +104,45 @@
|
||||
- Remove the unused `time` dependency
|
||||
- Fix missing `std::error::Error` implement for `MultipartError`.
|
||||
|
||||
## 0.2.0
|
||||
## [0.2.0] - 2019-12-20
|
||||
|
||||
- Release
|
||||
|
||||
## 0.2.0-alpha.4
|
||||
## [0.2.0-alpha.4] - 2019-12-xx
|
||||
|
||||
- Multipart handling now handles Pending during read of boundary #1205
|
||||
|
||||
## 0.2.0-alpha.2
|
||||
## [0.2.0-alpha.2] - 2019-12-03
|
||||
|
||||
- Migrate to `std::future`
|
||||
|
||||
## 0.1.4
|
||||
## [0.1.4] - 2019-09-12
|
||||
|
||||
- Multipart handling now parses requests which do not end in CRLF #1038
|
||||
|
||||
## 0.1.3
|
||||
## [0.1.3] - 2019-08-18
|
||||
|
||||
- Fix ring dependency from actix-web default features for #741.
|
||||
|
||||
## 0.1.2
|
||||
## [0.1.2] - 2019-06-02
|
||||
|
||||
- Fix boundary parsing #876
|
||||
|
||||
## 0.1.1
|
||||
## [0.1.1] - 2019-05-25
|
||||
|
||||
- Fix disconnect handling #834
|
||||
|
||||
## 0.1.0
|
||||
## [0.1.0] - 2019-05-18
|
||||
|
||||
- Release
|
||||
|
||||
## 0.1.0-beta.4
|
||||
## [0.1.0-beta.4] - 2019-05-12
|
||||
|
||||
- Handle cancellation of uploads #736
|
||||
|
||||
- Upgrade to actix-web 1.0.0-beta.4
|
||||
|
||||
## 0.1.0-beta.1
|
||||
## [0.1.0-beta.1] - 2019-04-21
|
||||
|
||||
- Do not support nested multipart
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-multipart"
|
||||
version = "0.6.1"
|
||||
version = "0.6.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Jacob Halsey <jacob@jhalsey.com>",
|
||||
@@ -8,9 +8,9 @@ authors = [
|
||||
description = "Multipart form support for Actix Web"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
@@ -19,10 +19,10 @@ all-features = true
|
||||
[features]
|
||||
default = ["tempfile", "derive"]
|
||||
derive = ["actix-multipart-derive"]
|
||||
tempfile = ["dep:tempfile", "tokio/fs"]
|
||||
tempfile = ["tempfile-dep", "tokio/fs"]
|
||||
|
||||
[dependencies]
|
||||
actix-multipart-derive = { version = "=0.6.1", optional = true }
|
||||
actix-multipart-derive = { version = "=0.6.0", optional = true }
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4", default-features = false }
|
||||
|
||||
@@ -38,8 +38,9 @@ mime = "0.3"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
serde_plain = "1"
|
||||
tempfile = { version = "3.4", optional = true }
|
||||
tokio = { version = "1.24.2", features = ["sync", "io-util"] }
|
||||
# TODO(MSRV 1.60): replace with dep: prefix
|
||||
tempfile-dep = { package = "tempfile", version = "3.4", optional = true }
|
||||
tokio = { version = "1.24.2", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http = "3"
|
||||
|
@@ -1,21 +1,17 @@
|
||||
# `actix-multipart`
|
||||
# actix-multipart
|
||||
|
||||
> Multipart form support for Actix Web.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
[](https://docs.rs/actix-multipart/0.6.1)
|
||||

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

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-multipart/0.6.1)
|
||||
[](https://deps.rs/crate/actix-multipart/0.6.0)
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-multipart)
|
||||
- Minimum Supported Rust Version (MSRV): 1.68
|
||||
- Minimum Supported Rust Version (MSRV): 1.59
|
||||
|
@@ -27,7 +27,11 @@ pub struct Bytes {
|
||||
impl<'t> FieldReader<'t> for Bytes {
|
||||
type Future = LocalBoxFuture<'t, Result<Self, MultipartError>>;
|
||||
|
||||
fn read_field(_: &'t HttpRequest, mut field: Field, limits: &'t mut Limits) -> Self::Future {
|
||||
fn read_field(
|
||||
_: &'t HttpRequest,
|
||||
mut field: Field,
|
||||
limits: &'t mut Limits,
|
||||
) -> Self::Future {
|
||||
Box::pin(async move {
|
||||
let mut buf = BytesMut::with_capacity(131_072);
|
||||
|
||||
|
@@ -7,12 +7,13 @@ use derive_more::{Deref, DerefMut, Display, Error};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use super::FieldErrorHandler;
|
||||
use crate::{
|
||||
form::{bytes::Bytes, FieldReader, Limits},
|
||||
Field, MultipartError,
|
||||
};
|
||||
|
||||
use super::FieldErrorHandler;
|
||||
|
||||
/// Deserialize from JSON.
|
||||
#[derive(Debug, Deref, DerefMut)]
|
||||
pub struct Json<T: DeserializeOwned>(pub T);
|
||||
|
@@ -429,7 +429,8 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_options() {
|
||||
let srv = actix_test::start(|| App::new().route("/", web::post().to(test_options_route)));
|
||||
let srv =
|
||||
actix_test::start(|| App::new().route("/", web::post().to(test_options_route)));
|
||||
|
||||
let mut form = multipart::Form::default();
|
||||
form.add_text("field1", "value");
|
||||
@@ -480,7 +481,9 @@ mod tests {
|
||||
field3: Text<String>,
|
||||
}
|
||||
|
||||
async fn test_field_renaming_route(form: MultipartForm<TestFieldRenaming>) -> impl Responder {
|
||||
async fn test_field_renaming_route(
|
||||
form: MultipartForm<TestFieldRenaming>,
|
||||
) -> impl Responder {
|
||||
assert_eq!(&*form.field1, "renamed");
|
||||
assert_eq!(&*form.field2, "field1");
|
||||
assert_eq!(&*form.field3, "field3");
|
||||
@@ -489,8 +492,9 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_field_renaming() {
|
||||
let srv =
|
||||
actix_test::start(|| App::new().route("/", web::post().to(test_field_renaming_route)));
|
||||
let srv = actix_test::start(|| {
|
||||
App::new().route("/", web::post().to(test_field_renaming_route))
|
||||
});
|
||||
|
||||
let mut form = multipart::Form::default();
|
||||
form.add_text("renamed", "renamed");
|
||||
@@ -619,7 +623,9 @@ mod tests {
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
||||
async fn test_upload_limits_file(form: MultipartForm<TestFileUploadLimits>) -> impl Responder {
|
||||
async fn test_upload_limits_file(
|
||||
form: MultipartForm<TestFileUploadLimits>,
|
||||
) -> impl Responder {
|
||||
assert!(form.field.size > 0);
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ use derive_more::{Display, Error};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use futures_util::TryStreamExt as _;
|
||||
use mime::Mime;
|
||||
use tempfile::NamedTempFile;
|
||||
use tempfile_dep::NamedTempFile;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
use super::FieldErrorHandler;
|
||||
@@ -39,20 +39,23 @@ pub struct TempFile {
|
||||
impl<'t> FieldReader<'t> for TempFile {
|
||||
type Future = LocalBoxFuture<'t, Result<Self, MultipartError>>;
|
||||
|
||||
fn read_field(req: &'t HttpRequest, mut field: Field, limits: &'t mut Limits) -> Self::Future {
|
||||
fn read_field(
|
||||
req: &'t HttpRequest,
|
||||
mut field: Field,
|
||||
limits: &'t mut Limits,
|
||||
) -> Self::Future {
|
||||
Box::pin(async move {
|
||||
let config = TempFileConfig::from_req(req);
|
||||
let field_name = field.name().to_owned();
|
||||
let mut size = 0;
|
||||
|
||||
let file = config
|
||||
.create_tempfile()
|
||||
.map_err(|err| config.map_error(req, &field_name, TempFileError::FileIo(err)))?;
|
||||
let file = config.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| {
|
||||
config.map_error(req, &field_name, TempFileError::FileIo(err))
|
||||
})?);
|
||||
let mut file_async = 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? {
|
||||
limits.try_consume_limits(chunk.len(), false)?;
|
||||
@@ -62,10 +65,9 @@ impl<'t> FieldReader<'t> for TempFile {
|
||||
})?;
|
||||
}
|
||||
|
||||
file_async
|
||||
.flush()
|
||||
.await
|
||||
.map_err(|err| config.map_error(req, &field_name, TempFileError::FileIo(err)))?;
|
||||
file_async.flush().await.map_err(|err| {
|
||||
config.map_error(req, &field_name, TempFileError::FileIo(err))
|
||||
})?;
|
||||
|
||||
Ok(TempFile {
|
||||
file,
|
||||
@@ -129,7 +131,12 @@ impl TempFileConfig {
|
||||
.unwrap_or(&DEFAULT_CONFIG)
|
||||
}
|
||||
|
||||
fn map_error(&self, req: &HttpRequest, field_name: &str, err: TempFileError) -> MultipartError {
|
||||
fn map_error(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
field_name: &str,
|
||||
err: TempFileError,
|
||||
) -> MultipartError {
|
||||
let source = if let Some(ref err_handler) = self.err_handler {
|
||||
(err_handler)(err, req)
|
||||
} else {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![allow(clippy::borrow_interior_mutable_const)]
|
||||
#![allow(clippy::borrow_interior_mutable_const, clippy::uninlined_format_args)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
@@ -17,7 +17,5 @@ mod server;
|
||||
|
||||
pub mod form;
|
||||
|
||||
pub use self::{
|
||||
error::MultipartError,
|
||||
server::{Field, Multipart},
|
||||
};
|
||||
pub use self::error::MultipartError;
|
||||
pub use self::server::{Field, Multipart};
|
||||
|
@@ -2,7 +2,9 @@
|
||||
|
||||
use std::{
|
||||
cell::{Cell, RefCell, RefMut},
|
||||
cmp, fmt,
|
||||
cmp,
|
||||
convert::TryFrom,
|
||||
fmt,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
@@ -161,8 +163,8 @@ impl InnerMultipart {
|
||||
for h in hdrs {
|
||||
let name =
|
||||
HeaderName::try_from(h.name).map_err(|_| ParseError::Header)?;
|
||||
let value =
|
||||
HeaderValue::try_from(h.value).map_err(|_| ParseError::Header)?;
|
||||
let value = HeaderValue::try_from(h.value)
|
||||
.map_err(|_| ParseError::Header)?;
|
||||
headers.append(name, value);
|
||||
}
|
||||
|
||||
@@ -222,7 +224,8 @@ impl InnerMultipart {
|
||||
if chunk.len() < boundary.len() {
|
||||
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;
|
||||
} else {
|
||||
if chunk.len() < boundary.len() + 2 {
|
||||
@@ -252,7 +255,7 @@ impl InnerMultipart {
|
||||
fn poll(
|
||||
&mut self,
|
||||
safety: &Safety,
|
||||
cx: &Context<'_>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Field, MultipartError>>> {
|
||||
if self.state == InnerState::Eof {
|
||||
Poll::Ready(None)
|
||||
@@ -267,7 +270,9 @@ impl InnerMultipart {
|
||||
match field.borrow_mut().poll(safety) {
|
||||
Poll::Pending => return Poll::Pending,
|
||||
Poll::Ready(Some(Ok(_))) => continue,
|
||||
Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))),
|
||||
Poll::Ready(Some(Err(err))) => {
|
||||
return Poll::Ready(Some(Err(err)))
|
||||
}
|
||||
Poll::Ready(None) => true,
|
||||
}
|
||||
}
|
||||
@@ -286,7 +291,8 @@ impl InnerMultipart {
|
||||
match self.state {
|
||||
// read until first boundary
|
||||
InnerState::FirstBoundary => {
|
||||
match InnerMultipart::skip_until_boundary(&mut payload, &self.boundary)? {
|
||||
match InnerMultipart::skip_until_boundary(&mut payload, &self.boundary)?
|
||||
{
|
||||
Some(eof) => {
|
||||
if eof {
|
||||
self.state = InnerState::Eof;
|
||||
@@ -663,7 +669,9 @@ impl InnerField {
|
||||
Ok(None) => Poll::Pending,
|
||||
Ok(Some(line)) => {
|
||||
if line.as_ref() != b"\r\n" {
|
||||
log::warn!("multipart field did not read all the data or it is malformed");
|
||||
log::warn!(
|
||||
"multipart field did not read all the data or it is malformed"
|
||||
);
|
||||
}
|
||||
Poll::Ready(None)
|
||||
}
|
||||
@@ -740,7 +748,7 @@ impl Safety {
|
||||
self.clean.get()
|
||||
}
|
||||
|
||||
fn clone(&self, cx: &Context<'_>) -> Safety {
|
||||
fn clone(&self, cx: &mut Context<'_>) -> Safety {
|
||||
let payload = Rc::clone(&self.payload);
|
||||
let s = Safety {
|
||||
task: LocalWaker::new(),
|
||||
|
@@ -1,19 +1,15 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased
|
||||
## Unreleased - 2023-xx-xx
|
||||
|
||||
## 0.5.2
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
|
||||
|
||||
## 0.5.1
|
||||
## 0.5.1 - 2022-09-19
|
||||
|
||||
- Correct typo in error string for `i32` deserialization. [#2876]
|
||||
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
|
||||
|
||||
[#2876]: https://github.com/actix/actix-web/pull/2876
|
||||
|
||||
## 0.5.0
|
||||
## 0.5.0 - 2022-02-22
|
||||
|
||||
### Added
|
||||
|
||||
@@ -88,7 +84,7 @@
|
||||
<details>
|
||||
<summary>0.5.0 Pre-Releases</summary>
|
||||
|
||||
## 0.5.0-rc.3
|
||||
## 0.5.0-rc.3 - 2022-01-31
|
||||
|
||||
- Remove unused `ResourceInfo`. [#2612]
|
||||
- Add `RouterBuilder::push`. [#2612]
|
||||
@@ -100,32 +96,32 @@
|
||||
[#2612]: https://github.com/actix/actix-web/pull/2612
|
||||
[#2613]: https://github.com/actix/actix-web/pull/2613
|
||||
|
||||
## 0.5.0-rc.2
|
||||
## 0.5.0-rc.2 - 2022-01-21
|
||||
|
||||
- Add `Path::as_str`. [#2590]
|
||||
- Deprecate `Path::path`. [#2590]
|
||||
|
||||
[#2590]: https://github.com/actix/actix-web/pull/2590
|
||||
|
||||
## 0.5.0-rc.1
|
||||
## 0.5.0-rc.1 - 2022-01-14
|
||||
|
||||
- `Resource` trait now have an associated type, `Path`, instead of the generic parameter. [#2568]
|
||||
- `Resource` is now implemented for `&mut Path<_>` and `RefMut<Path<_>>`. [#2568]
|
||||
|
||||
[#2568]: https://github.com/actix/actix-web/pull/2568
|
||||
|
||||
## 0.5.0-beta.4
|
||||
## 0.5.0-beta.4 - 2022-01-04
|
||||
|
||||
- `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#2566]
|
||||
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||
|
||||
[#2566]: https://github.com/actix/actix-net/pull/2566
|
||||
|
||||
## 0.5.0-beta.3
|
||||
## 0.5.0-beta.3 - 2021-12-17
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
## 0.5.0-beta.2
|
||||
## 0.5.0-beta.2 - 2021-09-09
|
||||
|
||||
- Introduce `ResourceDef::join`. [#380][net#380]
|
||||
- Disallow prefix routes with tail segments. [#379][net#379]
|
||||
@@ -145,7 +141,7 @@
|
||||
[#2355]: https://github.com/actix/actix-web/pull/2355
|
||||
[#2356]: https://github.com/actix/actix-web/pull/2356
|
||||
|
||||
## 0.5.0-beta.1
|
||||
## 0.5.0-beta.1 - 2021-07-20
|
||||
|
||||
- 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]
|
||||
@@ -175,7 +171,7 @@
|
||||
|
||||
</details>
|
||||
|
||||
## 0.4.0
|
||||
## 0.4.0 - 2021-06-06
|
||||
|
||||
- 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]
|
||||
@@ -187,70 +183,70 @@
|
||||
[net#359]: https://github.com/actix/actix-net/pull/359
|
||||
[net#360]: https://github.com/actix/actix-net/pull/360
|
||||
|
||||
## 0.3.0
|
||||
## 0.3.0 - 2019-12-31
|
||||
|
||||
- Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0
|
||||
|
||||
## 0.2.7
|
||||
## 0.2.7 - 2021-02-06
|
||||
|
||||
- Add `Router::recognize_checked` [#247][net#247]
|
||||
|
||||
[net#247]: https://github.com/actix/actix-net/pull/247
|
||||
|
||||
## 0.2.6
|
||||
## 0.2.6 - 2021-01-09
|
||||
|
||||
- Use `bytestring` version range compatible with Bytes v1.0. [#246][net#246]
|
||||
|
||||
[net#246]: https://github.com/actix/actix-net/pull/246
|
||||
|
||||
## 0.2.5
|
||||
## 0.2.5 - 2020-09-20
|
||||
|
||||
- Fix `from_hex()` method
|
||||
|
||||
## 0.2.4
|
||||
## 0.2.4 - 2019-12-31
|
||||
|
||||
- Add `ResourceDef::resource_path_named()` path generation method
|
||||
|
||||
## 0.2.3
|
||||
## 0.2.3 - 2019-12-25
|
||||
|
||||
- Add impl `IntoPattern` for `&String`
|
||||
|
||||
## 0.2.2
|
||||
## 0.2.2 - 2019-12-25
|
||||
|
||||
- Use `IntoPattern` for `RouterBuilder::path()`
|
||||
|
||||
## 0.2.1
|
||||
## 0.2.1 - 2019-12-25
|
||||
|
||||
- Add `IntoPattern` trait
|
||||
- Add multi-pattern resources
|
||||
|
||||
## 0.2.0
|
||||
## 0.2.0 - 2019-12-07
|
||||
|
||||
- Update http to 0.2
|
||||
- Update regex to 1.3
|
||||
- Use bytestring instead of string
|
||||
|
||||
## 0.1.5
|
||||
## 0.1.5 - 2019-05-15
|
||||
|
||||
- Remove debug prints
|
||||
|
||||
## 0.1.4
|
||||
## 0.1.4 - 2019-05-15
|
||||
|
||||
- Fix checked resource match
|
||||
|
||||
## 0.1.3
|
||||
## 0.1.3 - 2019-04-22
|
||||
|
||||
- Added support for `remainder match` (i.e "/path/{tail}\*")
|
||||
|
||||
## 0.1.2
|
||||
## 0.1.2 - 2019-04-07
|
||||
|
||||
- Export `Quoter` type
|
||||
- Allow to reset `Path` instance
|
||||
|
||||
## 0.1.1
|
||||
## 0.1.1 - 2019-04-03
|
||||
|
||||
- Get dynamic segment by name instead of iterator.
|
||||
|
||||
## 0.1.0
|
||||
## 0.1.0 - 2019-03-09
|
||||
|
||||
- Initial release
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user