mirror of https://github.com/fafhrd91/actix-web
Merge branch 'main' into feat/http-response-take-error
This commit is contained in:
commit
7ed55c4351
|
|
@ -4,8 +4,12 @@ updates:
|
||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 3
|
||||||
- package-ecosystem: cargo
|
- package-ecosystem: cargo
|
||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 3
|
||||||
versioning-strategy: lockfile-only
|
versioning-strategy: lockfile-only
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install nasm
|
- name: Install nasm
|
||||||
if: matrix.target.os == 'windows-latest'
|
if: matrix.target.os == 'windows-latest'
|
||||||
|
|
@ -49,7 +51,7 @@ jobs:
|
||||||
toolchain: ${{ matrix.version.version }}
|
toolchain: ${{ matrix.version.version }}
|
||||||
|
|
||||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||||
uses: taiki-e/install-action@055f5df8c3f65ea01cd41e9dc855becd88953486 # v2.75.18
|
uses: taiki-e/install-action@db5fb34fa772531a3ece57ca434f579eb334e0fb # v2.75.30
|
||||||
with:
|
with:
|
||||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||||
|
|
||||||
|
|
@ -72,18 +74,20 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Free Disk Space
|
- name: Free Disk Space
|
||||||
run: ./scripts/free-disk-space.sh
|
run: ./scripts/free-disk-space.sh
|
||||||
|
|
||||||
- name: Setup mold linker
|
- name: Setup mold linker
|
||||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
||||||
|
|
||||||
- name: Install just, cargo-hack
|
- name: Install just, cargo-hack
|
||||||
uses: taiki-e/install-action@055f5df8c3f65ea01cd41e9dc855becd88953486 # v2.75.18
|
uses: taiki-e/install-action@db5fb34fa772531a3ece57ca434f579eb334e0fb # v2.75.30
|
||||||
with:
|
with:
|
||||||
tool: just,cargo-hack
|
tool: just,cargo-hack
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install nasm
|
- name: Install nasm
|
||||||
if: matrix.target.os == 'windows-latest'
|
if: matrix.target.os == 'windows-latest'
|
||||||
|
|
@ -56,7 +58,7 @@ jobs:
|
||||||
|
|
||||||
- name: Setup mold linker
|
- name: Setup mold linker
|
||||||
if: matrix.target.os == 'ubuntu-latest'
|
if: matrix.target.os == 'ubuntu-latest'
|
||||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
|
||||||
|
|
||||||
- name: Install Rust (${{ matrix.version.name }})
|
- name: Install Rust (${{ matrix.version.name }})
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
||||||
|
|
@ -64,7 +66,7 @@ jobs:
|
||||||
toolchain: ${{ matrix.version.version }}
|
toolchain: ${{ matrix.version.version }}
|
||||||
|
|
||||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||||
uses: taiki-e/install-action@055f5df8c3f65ea01cd41e9dc855becd88953486 # v2.75.18
|
uses: taiki-e/install-action@db5fb34fa772531a3ece57ca434f579eb334e0fb # v2.75.30
|
||||||
with:
|
with:
|
||||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||||
|
|
||||||
|
|
@ -94,6 +96,8 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
||||||
|
|
@ -110,6 +114,8 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install Rust (nightly)
|
- name: Install Rust (nightly)
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
||||||
|
|
@ -117,7 +123,7 @@ jobs:
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
|
|
||||||
- name: Install just
|
- name: Install just
|
||||||
uses: taiki-e/install-action@055f5df8c3f65ea01cd41e9dc855becd88953486 # v2.75.18
|
uses: taiki-e/install-action@db5fb34fa772531a3ece57ca434f579eb334e0fb # v2.75.30
|
||||||
with:
|
with:
|
||||||
tool: just
|
tool: just
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install Rust (nightly)
|
- name: Install Rust (nightly)
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
||||||
|
|
@ -24,7 +26,7 @@ jobs:
|
||||||
components: llvm-tools
|
components: llvm-tools
|
||||||
|
|
||||||
- name: Install just, cargo-llvm-cov, cargo-nextest
|
- name: Install just, cargo-llvm-cov, cargo-nextest
|
||||||
uses: taiki-e/install-action@055f5df8c3f65ea01cd41e9dc855becd88953486 # v2.75.18
|
uses: taiki-e/install-action@db5fb34fa772531a3ece57ca434f579eb334e0fb # v2.75.30
|
||||||
with:
|
with:
|
||||||
tool: just,cargo-llvm-cov,cargo-nextest
|
tool: just,cargo-llvm-cov,cargo-nextest
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,6 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,30 @@ concurrency:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
zizmor:
|
||||||
|
name: zizmor
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
||||||
|
with:
|
||||||
|
advanced-security: false
|
||||||
|
annotations: true
|
||||||
|
version: v1.24.1
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install Rust (nightly)
|
- name: Install Rust (nightly)
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
||||||
|
|
@ -34,6 +54,8 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
||||||
|
|
@ -53,6 +75,8 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install Rust (nightly)
|
- name: Install Rust (nightly)
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
||||||
|
|
@ -70,6 +94,8 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install Rust (${{ vars.RUST_VERSION_EXTERNAL_TYPES }})
|
- name: Install Rust (${{ vars.RUST_VERSION_EXTERNAL_TYPES }})
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
||||||
|
|
@ -77,14 +103,16 @@ jobs:
|
||||||
toolchain: ${{ vars.RUST_VERSION_EXTERNAL_TYPES }}
|
toolchain: ${{ vars.RUST_VERSION_EXTERNAL_TYPES }}
|
||||||
|
|
||||||
- name: Install just
|
- name: Install just
|
||||||
uses: taiki-e/install-action@055f5df8c3f65ea01cd41e9dc855becd88953486 # v2.75.18
|
uses: taiki-e/install-action@db5fb34fa772531a3ece57ca434f579eb334e0fb # v2.75.30
|
||||||
with:
|
with:
|
||||||
tool: just
|
tool: just
|
||||||
|
|
||||||
- name: Install cargo-check-external-types
|
- name: Install cargo-check-external-types
|
||||||
uses: taiki-e/cache-cargo-install-action@f9eed3e4680f27610dc6d8c67be1b88593f7dade # v3.0.6
|
uses: taiki-e/cache-cargo-install-action@417450f3c33ee20393705369577571770643d4c7 # v3.0.7
|
||||||
with:
|
with:
|
||||||
tool: cargo-check-external-types
|
tool: cargo-check-external-types
|
||||||
|
|
||||||
- name: check external types
|
- name: check external types
|
||||||
run: just check-external-types-all +${{ vars.RUST_VERSION_EXTERNAL_TYPES }}
|
run: just check-external-types-all +"${RUST_VERSION_EXTERNAL_TYPES}"
|
||||||
|
env:
|
||||||
|
RUST_VERSION_EXTERNAL_TYPES: ${{ vars.RUST_VERSION_EXTERNAL_TYPES }}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ jobs:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0
|
||||||
|
|
@ -20,7 +21,7 @@ jobs:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
|
|
||||||
- name: Install cargo-semver-checks
|
- name: Install cargo-semver-checks
|
||||||
uses: taiki-e/install-action@055f5df8c3f65ea01cd41e9dc855becd88953486 # v2.75.18
|
uses: taiki-e/install-action@db5fb34fa772531a3ece57ca434f579eb334e0fb # v2.75.30
|
||||||
with:
|
with:
|
||||||
tool: cargo-semver-checks
|
tool: cargo-semver-checks
|
||||||
|
|
||||||
|
|
@ -59,16 +60,22 @@ jobs:
|
||||||
- name: Summarize cargo semver-checks output
|
- name: Summarize cargo semver-checks output
|
||||||
if: always() && steps.semver.outcome != 'skipped'
|
if: always() && steps.semver.outcome != 'skipped'
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||||
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
|
SEMVER_TYPE: ${{ steps.semver.outputs.semver_type }}
|
||||||
|
STATUS: ${{ steps.semver.outputs.exit_code }}
|
||||||
|
SUMMARY_FILE: ${{ steps.semver.outputs.output_file }}
|
||||||
run: |
|
run: |
|
||||||
summary_file="${{ steps.semver.outputs.output_file }}"
|
summary_file="$SUMMARY_FILE"
|
||||||
status="${{ steps.semver.outputs.exit_code }}"
|
status="$STATUS"
|
||||||
|
|
||||||
{
|
{
|
||||||
echo "## cargo semver-checks"
|
echo "## cargo semver-checks"
|
||||||
echo
|
echo
|
||||||
echo "- Base SHA: \`${{ github.event.pull_request.base.sha }}\`"
|
echo "- Base SHA: \`${BASE_SHA}\`"
|
||||||
echo "- Head SHA: \`${{ github.event.pull_request.head.sha }}\`"
|
echo "- Head SHA: \`${HEAD_SHA}\`"
|
||||||
echo "- Required release: \`${{ steps.semver.outputs.semver_type }}\`"
|
echo "- Required release: \`${SEMVER_TYPE}\`"
|
||||||
echo "- cargo semver-checks exit code: \`$status\`"
|
echo "- cargo semver-checks exit code: \`$status\`"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|
|
||||||
|
|
@ -294,7 +294,7 @@ dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"http 0.2.12",
|
"http 0.2.12",
|
||||||
"http 1.4.0",
|
"http 1.4.0",
|
||||||
"impl-more",
|
"impl-more 0.1.9",
|
||||||
"openssl",
|
"openssl",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustls-native-certs",
|
"rustls-native-certs",
|
||||||
|
|
@ -354,7 +354,7 @@ dependencies = [
|
||||||
"foldhash 0.2.0",
|
"foldhash 0.2.0",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"impl-more",
|
"impl-more 0.3.1",
|
||||||
"itoa",
|
"itoa",
|
||||||
"language-tags",
|
"language-tags",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -465,6 +465,15 @@ dependencies = [
|
||||||
"alloc-no-stdlib",
|
"alloc-no-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alloca"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anes"
|
name = "anes"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
|
@ -726,9 +735,9 @@ checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytestring"
|
name = "bytestring"
|
||||||
version = "1.5.0"
|
version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289"
|
checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
]
|
]
|
||||||
|
|
@ -846,6 +855,16 @@ version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "combine"
|
||||||
|
version = "4.6.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "common-multipart-rfc7578"
|
name = "common-multipart-rfc7578"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
|
@ -907,6 +926,16 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
|
@ -963,25 +992,24 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "criterion"
|
name = "criterion"
|
||||||
version = "0.5.1"
|
version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
|
checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"alloca",
|
||||||
"anes",
|
"anes",
|
||||||
"cast",
|
"cast",
|
||||||
"ciborium",
|
"ciborium",
|
||||||
"clap",
|
"clap",
|
||||||
"criterion-plot",
|
"criterion-plot",
|
||||||
"is-terminal",
|
|
||||||
"itertools",
|
"itertools",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
|
||||||
"oorandom",
|
"oorandom",
|
||||||
|
"page_size",
|
||||||
"plotters",
|
"plotters",
|
||||||
"rayon",
|
"rayon",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tinytemplate",
|
"tinytemplate",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
|
|
@ -989,9 +1017,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "criterion-plot"
|
name = "criterion-plot"
|
||||||
version = "0.5.0"
|
version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cast",
|
"cast",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
|
@ -1074,9 +1102,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.20.11"
|
version = "0.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"darling_macro",
|
"darling_macro",
|
||||||
|
|
@ -1084,11 +1112,10 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_core"
|
name = "darling_core"
|
||||||
version = "0.20.11"
|
version = "0.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fnv",
|
|
||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1098,9 +1125,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.20.11"
|
version = "0.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1224,18 +1251,6 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "enum-as-inner"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
|
|
||||||
dependencies = [
|
|
||||||
"heck",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_filter"
|
name = "env_filter"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
@ -1435,6 +1450,7 @@ checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
|
@ -1563,23 +1579,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hickory-proto"
|
name = "hickory-net"
|
||||||
version = "0.25.2"
|
version = "0.26.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502"
|
checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"enum-as-inner",
|
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"hickory-proto",
|
||||||
"idna",
|
"idna",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"once_cell",
|
"jni",
|
||||||
"rand 0.9.4",
|
"rand 0.10.1",
|
||||||
"ring 0.17.14",
|
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -1588,21 +1603,46 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hickory-resolver"
|
name = "hickory-proto"
|
||||||
version = "0.25.2"
|
version = "0.26.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a"
|
checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643"
|
||||||
|
dependencies = [
|
||||||
|
"data-encoding",
|
||||||
|
"idna",
|
||||||
|
"ipnet",
|
||||||
|
"jni",
|
||||||
|
"once_cell",
|
||||||
|
"prefix-trie",
|
||||||
|
"rand 0.10.1",
|
||||||
|
"ring 0.17.14",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tinyvec",
|
||||||
|
"tracing",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hickory-resolver"
|
||||||
|
version = "0.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"hickory-net",
|
||||||
"hickory-proto",
|
"hickory-proto",
|
||||||
"ipconfig",
|
"ipconfig",
|
||||||
|
"ipnet",
|
||||||
|
"jni",
|
||||||
"moka",
|
"moka",
|
||||||
|
"ndk-context",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rand 0.9.4",
|
"rand 0.10.1",
|
||||||
"resolv-conf",
|
"resolv-conf",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"system-configuration",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -1795,6 +1835,12 @@ version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
|
checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "impl-more"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.14.0"
|
version = "2.14.0"
|
||||||
|
|
@ -1844,16 +1890,8 @@ name = "ipnet"
|
||||||
version = "2.12.0"
|
version = "2.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is-terminal"
|
|
||||||
version = "0.4.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"serde",
|
||||||
"libc",
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1864,9 +1902,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
@ -1901,6 +1939,55 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni"
|
||||||
|
version = "0.22.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"combine",
|
||||||
|
"jni-macros",
|
||||||
|
"jni-sys",
|
||||||
|
"log",
|
||||||
|
"simd_cesu8",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"walkdir",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni-macros"
|
||||||
|
version = "0.22.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustc_version",
|
||||||
|
"simd_cesu8",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni-sys"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
|
||||||
|
dependencies = [
|
||||||
|
"jni-sys-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni-sys-macros"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.34"
|
version = "0.1.34"
|
||||||
|
|
@ -2073,6 +2160,12 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ndk-context"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
@ -2128,15 +2221,14 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.78"
|
version = "0.10.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222"
|
checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
|
||||||
"openssl-macros",
|
"openssl-macros",
|
||||||
"openssl-sys",
|
"openssl-sys",
|
||||||
]
|
]
|
||||||
|
|
@ -2160,9 +2252,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.114"
|
version = "0.9.115"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6"
|
checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -2170,6 +2262,16 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "page_size"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
|
|
@ -2326,6 +2428,17 @@ dependencies = [
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prefix-trie"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23370be78b7e5bcbb0cab4a02047eb040279a693c78daad04c2c5f1c24a83503"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"ipnet",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.37"
|
version = "0.2.37"
|
||||||
|
|
@ -2373,20 +2486,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha 0.3.1",
|
"rand_chacha",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.9.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
|
||||||
dependencies = [
|
|
||||||
"rand_chacha 0.9.0",
|
|
||||||
"rand_core 0.9.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
|
@ -2408,16 +2511,6 @@ dependencies = [
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_chacha"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
|
||||||
dependencies = [
|
|
||||||
"ppv-lite86",
|
|
||||||
"rand_core 0.9.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
|
|
@ -2427,15 +2520,6 @@ dependencies = [
|
||||||
"getrandom 0.2.17",
|
"getrandom 0.2.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.9.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom 0.3.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
|
@ -2652,9 +2736,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pki-types"
|
name = "rustls-pki-types"
|
||||||
version = "1.14.0"
|
version = "1.14.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
|
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
@ -2751,7 +2835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
|
|
@ -2890,6 +2974,22 @@ version = "0.3.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd_cesu8"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
|
||||||
|
dependencies = [
|
||||||
|
"rustc_version",
|
||||||
|
"simdutf8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simdutf8"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
|
|
@ -2990,6 +3090,27 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"system-configuration-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration-sys"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tagptr"
|
name = "tagptr"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -3434,10 +3555,19 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "v_htmlescape"
|
name = "v_escape-base"
|
||||||
version = "0.15.8"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c"
|
checksum = "f1212fce830b75af194b578e55b3db9049f2c8c45f58d397fb25602fdb50fb3d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "v_htmlescape"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "befb3d53c9e3ec641417685896cbc8cc5bd264d6a2e190c56aaef1af24740d99"
|
||||||
|
dependencies = [
|
||||||
|
"v_escape-base",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,14 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Add support for passing multiple root directories to `Files::new`. [#3402]
|
||||||
- Add `Files::try_compressed()` to support serving pre-compressed static files [#2615]
|
- Add `Files::try_compressed()` to support serving pre-compressed static files [#2615]
|
||||||
- Fix handling of `bytes=0-`
|
- Fix handling of `bytes=0-`
|
||||||
- Fix `NamedFile` panic when serving files with pre-UNIX epoch modification times. [#2748]
|
- Fix `NamedFile` panic when serving files with pre-UNIX epoch modification times. [#2748]
|
||||||
- Fix invalid `Content-Encoding: identity` header in `NamedFile` range responses. [#3191]
|
- Fix invalid `Content-Encoding: identity` header in `NamedFile` range responses. [#3191]
|
||||||
|
- Update `v_htmlescape` dependency to `0.17`.
|
||||||
|
|
||||||
|
[#3402]: https://github.com/actix/actix-web/issues/3402
|
||||||
[#2615]: https://github.com/actix/actix-web/pull/2615
|
[#2615]: https://github.com/actix/actix-web/pull/2615
|
||||||
[#2748]: https://github.com/actix/actix-web/issues/2748
|
[#2748]: https://github.com/actix/actix-web/issues/2748
|
||||||
[#3191]: https://github.com/actix/actix-web/issues/3191
|
[#3191]: https://github.com/actix/actix-web/issues/3191
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ mime = "0.3.9"
|
||||||
mime_guess = "2.0.1"
|
mime_guess = "2.0.1"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
pin-project-lite = "0.2.7"
|
pin-project-lite = "0.2.7"
|
||||||
v_htmlescape = "0.15.5"
|
v_htmlescape = "0.17"
|
||||||
|
|
||||||
# experimental-io-uring
|
# experimental-io-uring
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
|
|
||||||
use actix_web::{dev::ServiceResponse, HttpRequest, HttpResponse};
|
use actix_web::{dev::ServiceResponse, HttpRequest, HttpResponse};
|
||||||
use percent_encoding::{utf8_percent_encode, CONTROLS};
|
use percent_encoding::{utf8_percent_encode, CONTROLS};
|
||||||
use v_htmlescape::escape as escape_html_entity;
|
use v_htmlescape::escape_fmt;
|
||||||
|
|
||||||
/// A directory; responds with the generated directory listing.
|
/// A directory; responds with the generated directory listing.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -64,7 +64,7 @@ macro_rules! encode_file_url {
|
||||||
/// ```
|
/// ```
|
||||||
macro_rules! encode_file_name {
|
macro_rules! encode_file_name {
|
||||||
($entry:ident) => {
|
($entry:ident) => {
|
||||||
escape_html_entity(&$entry.file_name().to_string_lossy())
|
escape_fmt(&$entry.file_name().to_string_lossy())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
fmt, io,
|
fmt, io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
|
@ -37,7 +39,7 @@ use crate::{
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Files {
|
pub struct Files {
|
||||||
mount_path: String,
|
mount_path: String,
|
||||||
directory: PathBuf,
|
directories: Vec<PathBuf>,
|
||||||
index: Option<String>,
|
index: Option<String>,
|
||||||
show_index: bool,
|
show_index: bool,
|
||||||
redirect_to_slash: bool,
|
redirect_to_slash: bool,
|
||||||
|
|
@ -63,7 +65,7 @@ impl fmt::Debug for Files {
|
||||||
impl Clone for Files {
|
impl Clone for Files {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
directory: self.directory.clone(),
|
directories: self.directories.clone(),
|
||||||
index: self.index.clone(),
|
index: self.index.clone(),
|
||||||
show_index: self.show_index,
|
show_index: self.show_index,
|
||||||
redirect_to_slash: self.redirect_to_slash,
|
redirect_to_slash: self.redirect_to_slash,
|
||||||
|
|
@ -83,6 +85,131 @@ impl Clone for Files {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// File serving root directories for [`Files`].
|
||||||
|
///
|
||||||
|
/// This type is used by [`Files::new`] to accept either one root directory or an ordered
|
||||||
|
/// collection of root directories.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FilesDirs(Vec<PathBuf>);
|
||||||
|
|
||||||
|
impl FilesDirs {
|
||||||
|
fn canonicalize(self) -> Vec<PathBuf> {
|
||||||
|
self.0
|
||||||
|
.into_iter()
|
||||||
|
.map(|orig_dir| match orig_dir.canonicalize() {
|
||||||
|
Ok(canon_dir) => canon_dir,
|
||||||
|
Err(_) => {
|
||||||
|
log::error!("Specified path is not a directory: {:?}", orig_dir);
|
||||||
|
// Preserve original path so requests don't fall back to CWD.
|
||||||
|
orig_dir
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Path> for FilesDirs {
|
||||||
|
fn from(dir: &Path) -> Self {
|
||||||
|
Self(vec![dir.into()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&PathBuf> for FilesDirs {
|
||||||
|
fn from(dir: &PathBuf) -> Self {
|
||||||
|
Self(vec![dir.into()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PathBuf> for FilesDirs {
|
||||||
|
fn from(dir: PathBuf) -> Self {
|
||||||
|
Self(vec![dir])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for FilesDirs {
|
||||||
|
fn from(dir: &str) -> Self {
|
||||||
|
Self(vec![dir.into()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&String> for FilesDirs {
|
||||||
|
fn from(dir: &String) -> Self {
|
||||||
|
Self(vec![dir.into()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for FilesDirs {
|
||||||
|
fn from(dir: String) -> Self {
|
||||||
|
Self(vec![dir.into()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&OsStr> for FilesDirs {
|
||||||
|
fn from(dir: &OsStr) -> Self {
|
||||||
|
Self(vec![dir.into()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<OsString> for FilesDirs {
|
||||||
|
fn from(dir: OsString) -> Self {
|
||||||
|
Self(vec![dir.into()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&OsString> for FilesDirs {
|
||||||
|
fn from(dir: &OsString) -> Self {
|
||||||
|
Self(vec![dir.into()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Box<Path>> for FilesDirs {
|
||||||
|
fn from(dir: Box<Path>) -> Self {
|
||||||
|
Self(vec![dir.into()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Cow<'_, Path>> for FilesDirs {
|
||||||
|
fn from(dir: Cow<'_, Path>) -> Self {
|
||||||
|
Self(vec![dir.into()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P, const N: usize> From<[P; N]> for FilesDirs
|
||||||
|
where
|
||||||
|
P: Into<PathBuf>,
|
||||||
|
{
|
||||||
|
fn from(dirs: [P; N]) -> Self {
|
||||||
|
Self(dirs.into_iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P, const N: usize> From<&[P; N]> for FilesDirs
|
||||||
|
where
|
||||||
|
P: Clone + Into<PathBuf>,
|
||||||
|
{
|
||||||
|
fn from(dirs: &[P; N]) -> Self {
|
||||||
|
Self(dirs.iter().cloned().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> From<&[P]> for FilesDirs
|
||||||
|
where
|
||||||
|
P: Clone + Into<PathBuf>,
|
||||||
|
{
|
||||||
|
fn from(dirs: &[P]) -> Self {
|
||||||
|
Self(dirs.iter().cloned().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> From<Vec<P>> for FilesDirs
|
||||||
|
where
|
||||||
|
P: Into<PathBuf>,
|
||||||
|
{
|
||||||
|
fn from(dirs: Vec<P>) -> Self {
|
||||||
|
Self(dirs.into_iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Files {
|
impl Files {
|
||||||
/// Create new `Files` instance for a specified base directory.
|
/// Create new `Files` instance for a specified base directory.
|
||||||
///
|
///
|
||||||
|
|
@ -90,34 +217,34 @@ impl Files {
|
||||||
/// The first argument (`mount_path`) is the root URL at which the static files are served.
|
/// The first argument (`mount_path`) is the root URL at which the static files are served.
|
||||||
/// For example, `/assets` will serve files at `example.com/assets/...`.
|
/// For example, `/assets` will serve files at `example.com/assets/...`.
|
||||||
///
|
///
|
||||||
/// The second argument (`serve_from`) is the location on disk at which files are loaded.
|
/// The second argument (`serve_from`) is the location on disk that files are served from. This
|
||||||
/// This can be a relative path. For example, `./` would serve files from the current
|
/// can be a single path or an ordered collection of paths. Relative paths are resolved from the
|
||||||
/// working directory.
|
/// current working directory.
|
||||||
|
///
|
||||||
|
/// When multiple directories are provided, they are checked in order. The first directory that
|
||||||
|
/// can serve the requested path is used.
|
||||||
|
///
|
||||||
|
/// Directory listings are generated from the first matching directory and are not merged across
|
||||||
|
/// roots. When [`Files::index_file()`] is configured, later roots are searched if an earlier
|
||||||
|
/// matching directory does not contain the index file.
|
||||||
|
///
|
||||||
|
/// Empty root collections never match files; requests fall through to the default handler, or
|
||||||
|
/// return `404 Not Found` if none is configured.
|
||||||
///
|
///
|
||||||
/// # Implementation Notes
|
/// # Implementation Notes
|
||||||
/// If the mount path is set as the root path `/`, services registered after this one will
|
/// If the mount path is set as the root path `/`, services registered after this one will
|
||||||
/// be inaccessible. Register more specific handlers and services first.
|
/// be inaccessible. Register more specific handlers and services first.
|
||||||
///
|
///
|
||||||
/// If `serve_from` cannot be canonicalized at startup, an error is logged and the original
|
/// If a `serve_from` path cannot be canonicalized at startup, an error is logged and the
|
||||||
/// path is preserved. Requests will return `404 Not Found` until the path exists.
|
/// original path is preserved. Requests will return `404 Not Found` until the path exists.
|
||||||
///
|
///
|
||||||
/// `Files` utilizes the existing Tokio thread-pool for blocking filesystem operations.
|
/// `Files` utilizes the existing Tokio thread-pool for blocking filesystem operations.
|
||||||
/// The number of running threads is adjusted over time as needed, up to a maximum of 512 times
|
/// The number of running threads is adjusted over time as needed, up to a maximum of 512 times
|
||||||
/// the number of server [workers](actix_web::HttpServer::workers), by default.
|
/// the number of server [workers](actix_web::HttpServer::workers), by default.
|
||||||
pub fn new<T: Into<PathBuf>>(mount_path: &str, serve_from: T) -> Files {
|
pub fn new<T: Into<FilesDirs>>(mount_path: &str, serve_from: T) -> Files {
|
||||||
let orig_dir = serve_from.into();
|
|
||||||
let dir = match orig_dir.canonicalize() {
|
|
||||||
Ok(canon_dir) => canon_dir,
|
|
||||||
Err(_) => {
|
|
||||||
log::error!("Specified path is not a directory: {:?}", orig_dir);
|
|
||||||
// Preserve original path so requests don't fall back to CWD.
|
|
||||||
orig_dir
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Files {
|
Files {
|
||||||
mount_path: mount_path.trim_end_matches('/').to_owned(),
|
mount_path: mount_path.trim_end_matches('/').to_owned(),
|
||||||
directory: dir,
|
directories: serve_from.into().canonicalize(),
|
||||||
index: None,
|
index: None,
|
||||||
show_index: false,
|
show_index: false,
|
||||||
redirect_to_slash: false,
|
redirect_to_slash: false,
|
||||||
|
|
@ -149,6 +276,9 @@ impl Files {
|
||||||
/// Redirects to a slash-ended path when browsing a directory.
|
/// Redirects to a slash-ended path when browsing a directory.
|
||||||
///
|
///
|
||||||
/// By default never redirect.
|
/// By default never redirect.
|
||||||
|
///
|
||||||
|
/// When multiple root directories are configured, a matching directory in an earlier root can
|
||||||
|
/// trigger a redirect before later roots are checked for a file at the same path.
|
||||||
pub fn redirect_to_slash_directory(mut self) -> Self {
|
pub fn redirect_to_slash_directory(mut self) -> Self {
|
||||||
self.redirect_to_slash = true;
|
self.redirect_to_slash = true;
|
||||||
self
|
self
|
||||||
|
|
@ -407,7 +537,7 @@ impl ServiceFactory<ServiceRequest> for Files {
|
||||||
|
|
||||||
fn new_service(&self, _: ()) -> Self::Future {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
let mut inner = FilesServiceInner {
|
let mut inner = FilesServiceInner {
|
||||||
directory: self.directory.clone(),
|
directories: self.directories.clone(),
|
||||||
index: self.index.clone(),
|
index: self.index.clone(),
|
||||||
show_index: self.show_index,
|
show_index: self.show_index,
|
||||||
redirect_to_slash: self.redirect_to_slash,
|
redirect_to_slash: self.redirect_to_slash,
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,14 @@ mod range;
|
||||||
mod service;
|
mod service;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
chunked::ChunkedReadFile, directory::Directory, error::UriSegmentError, files::Files,
|
chunked::ChunkedReadFile,
|
||||||
named::NamedFile, path_buf::PathBufWrap, range::HttpRange, service::FilesService,
|
directory::Directory,
|
||||||
|
error::UriSegmentError,
|
||||||
|
files::{Files, FilesDirs},
|
||||||
|
named::NamedFile,
|
||||||
|
path_buf::PathBufWrap,
|
||||||
|
range::HttpRange,
|
||||||
|
service::FilesService,
|
||||||
};
|
};
|
||||||
use self::{
|
use self::{
|
||||||
directory::{directory_listing, DirectoryRenderer},
|
directory::{directory_listing, DirectoryRenderer},
|
||||||
|
|
@ -63,9 +69,11 @@ type PathFilter = dyn Fn(&Path, &RequestHead) -> bool;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{
|
use std::{
|
||||||
|
ffi::OsString,
|
||||||
fmt::Write as _,
|
fmt::Write as _,
|
||||||
fs::{self},
|
fs::{self},
|
||||||
ops::Add,
|
ops::Add,
|
||||||
|
path::PathBuf,
|
||||||
time::{Duration, SystemTime},
|
time::{Duration, SystemTime},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -832,6 +840,243 @@ mod tests {
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_static_files_accepts_borrowed_os_string_directory() {
|
||||||
|
let dir = OsString::from(".");
|
||||||
|
let service = Files::new("/", &dir).new_service(()).await.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
|
||||||
|
let resp = test::call_service(&service, req).await;
|
||||||
|
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_static_files_empty_directories() {
|
||||||
|
let service = Files::new("/", Vec::<PathBuf>::new())
|
||||||
|
.new_service(())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
|
||||||
|
let resp = test::call_service(&service, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let service = Files::new("/", Vec::<PathBuf>::new())
|
||||||
|
.default_handler(|req: ServiceRequest| async {
|
||||||
|
Ok(req.into_response(HttpResponse::Ok().body("default content")))
|
||||||
|
})
|
||||||
|
.new_service(())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
|
||||||
|
let resp = test::call_service(&service, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
test::read_body(resp).await,
|
||||||
|
Bytes::from_static(b"default content")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_static_files_multiple_directories() {
|
||||||
|
let first_dir = tempfile::tempdir().unwrap();
|
||||||
|
let second_dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
fs::write(first_dir.path().join("shared.txt"), "first").unwrap();
|
||||||
|
fs::write(second_dir.path().join("shared.txt"), "second").unwrap();
|
||||||
|
fs::write(second_dir.path().join("fallback.txt"), "fallback").unwrap();
|
||||||
|
|
||||||
|
let service = Files::new("/", [first_dir.path(), second_dir.path()])
|
||||||
|
.new_service(())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/shared.txt").to_srv_request();
|
||||||
|
let resp = test::call_service(&service, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(test::read_body(resp).await, Bytes::from_static(b"first"));
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/fallback.txt").to_srv_request();
|
||||||
|
let resp = test::call_service(&service, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(test::read_body(resp).await, Bytes::from_static(b"fallback"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_static_files_multiple_directories_file_as_parent_falls_back() {
|
||||||
|
let first_dir = tempfile::tempdir().unwrap();
|
||||||
|
let second_dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
fs::write(first_dir.path().join("assets"), "file").unwrap();
|
||||||
|
fs::create_dir(second_dir.path().join("assets")).unwrap();
|
||||||
|
fs::write(
|
||||||
|
second_dir.path().join("assets").join("fallback.txt"),
|
||||||
|
"fallback",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let service = Files::new("/", [first_dir.path(), second_dir.path()])
|
||||||
|
.new_service(())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/assets/fallback.txt").to_srv_request();
|
||||||
|
let resp = test::call_service(&service, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(test::read_body(resp).await, Bytes::from_static(b"fallback"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_static_files_multiple_directories_default_handler() {
|
||||||
|
let first_dir = tempfile::tempdir().unwrap();
|
||||||
|
let second_dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
fs::write(second_dir.path().join("fallback.txt"), "fallback").unwrap();
|
||||||
|
|
||||||
|
let service = Files::new("/", vec![first_dir.path(), second_dir.path()])
|
||||||
|
.default_handler(|req: ServiceRequest| async {
|
||||||
|
Ok(req.into_response(HttpResponse::Ok().body("default content")))
|
||||||
|
})
|
||||||
|
.new_service(())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/fallback.txt").to_srv_request();
|
||||||
|
let resp = test::call_service(&service, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(test::read_body(resp).await, Bytes::from_static(b"fallback"));
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/missing.txt").to_srv_request();
|
||||||
|
let resp = test::call_service(&service, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
test::read_body(resp).await,
|
||||||
|
Bytes::from_static(b"default content")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_static_files_multiple_directories_index_file() {
|
||||||
|
let first_dir = tempfile::tempdir().unwrap();
|
||||||
|
let second_dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
fs::create_dir(first_dir.path().join("nested")).unwrap();
|
||||||
|
fs::create_dir(second_dir.path().join("nested")).unwrap();
|
||||||
|
fs::write(
|
||||||
|
second_dir.path().join("nested").join("index.html"),
|
||||||
|
"second index",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let service = Files::new("/", [first_dir.path(), second_dir.path()])
|
||||||
|
.index_file("index.html")
|
||||||
|
.new_service(())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/nested/").to_srv_request();
|
||||||
|
let resp = test::call_service(&service, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
test::read_body(resp).await,
|
||||||
|
Bytes::from_static(b"second index")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_static_files_multiple_directories_index_file_as_parent_falls_back() {
|
||||||
|
let first_dir = tempfile::tempdir().unwrap();
|
||||||
|
let second_dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
fs::create_dir(first_dir.path().join("nested")).unwrap();
|
||||||
|
fs::write(first_dir.path().join("nested").join("index.html"), "file").unwrap();
|
||||||
|
fs::create_dir(second_dir.path().join("nested")).unwrap();
|
||||||
|
fs::create_dir(second_dir.path().join("nested").join("index.html")).unwrap();
|
||||||
|
fs::write(
|
||||||
|
second_dir
|
||||||
|
.path()
|
||||||
|
.join("nested")
|
||||||
|
.join("index.html")
|
||||||
|
.join("fallback.txt"),
|
||||||
|
"fallback",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let service = Files::new("/", [first_dir.path(), second_dir.path()])
|
||||||
|
.index_file("index.html/fallback.txt")
|
||||||
|
.new_service(())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/nested/").to_srv_request();
|
||||||
|
let resp = test::call_service(&service, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(test::read_body(resp).await, Bytes::from_static(b"fallback"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_static_files_index_file_error_falls_back_to_listing() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
fs::write(dir.path().join("listed.txt"), "listed").unwrap();
|
||||||
|
|
||||||
|
let service = Files::new("/", dir.path())
|
||||||
|
.index_file("index.html\0")
|
||||||
|
.show_files_listing()
|
||||||
|
.new_service(())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/").to_srv_request();
|
||||||
|
let resp = test::call_service(&service, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
let bytes = test::read_body(resp).await;
|
||||||
|
assert!(format!("{bytes:?}").contains("listed.txt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_static_files_multiple_directories_show_files_listing() {
|
||||||
|
let first_dir = tempfile::tempdir().unwrap();
|
||||||
|
let second_dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
fs::write(first_dir.path().join("listed.txt"), "listed").unwrap();
|
||||||
|
|
||||||
|
let service = Files::new("/", [first_dir.path(), second_dir.path()])
|
||||||
|
.show_files_listing()
|
||||||
|
.new_service(())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/").to_srv_request();
|
||||||
|
let resp = test::call_service(&service, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
let bytes = test::read_body(resp).await;
|
||||||
|
assert!(format!("{bytes:?}").contains("listed.txt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_static_files_multiple_directories_redirect_precedence() {
|
||||||
|
let first_dir = tempfile::tempdir().unwrap();
|
||||||
|
let second_dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
fs::create_dir(first_dir.path().join("item")).unwrap();
|
||||||
|
fs::write(second_dir.path().join("item"), "file").unwrap();
|
||||||
|
|
||||||
|
let service = Files::new("/", [first_dir.path(), second_dir.path()])
|
||||||
|
.show_files_listing()
|
||||||
|
.redirect_to_slash_directory()
|
||||||
|
.new_service(())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/item").to_srv_request();
|
||||||
|
let resp = test::call_service(&service, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::TEMPORARY_REDIRECT);
|
||||||
|
assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/item/");
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_default_handler_file_missing() {
|
async fn test_default_handler_file_missing() {
|
||||||
let st = Files::new("/", ".")
|
let st = Files::new("/", ".")
|
||||||
|
|
|
||||||
|
|
@ -117,14 +117,35 @@ pub(crate) fn get_content_type_and_disposition(
|
||||||
};
|
};
|
||||||
|
|
||||||
// replace special characters in filenames which could occur on some filesystems
|
// replace special characters in filenames which could occur on some filesystems
|
||||||
let filename_s = filename
|
let mut escaped_len = filename.len();
|
||||||
.replace('\n', "%0A") // \n line break
|
for byte in filename.bytes() {
|
||||||
.replace('\x0B', "%0B") // \v vertical tab
|
if matches!(byte, b'\n' | b'\x0B' | b'\x0C' | b'\r') {
|
||||||
.replace('\x0C', "%0C") // \f form feed
|
escaped_len += 2;
|
||||||
.replace('\r', "%0D"); // \r carriage return
|
}
|
||||||
let mut parameters = vec![DispositionParam::Filename(filename_s)];
|
}
|
||||||
|
|
||||||
if !filename.is_ascii() {
|
let filename_s = if escaped_len == filename.len() {
|
||||||
|
filename.to_string()
|
||||||
|
} else {
|
||||||
|
let mut escaped = String::with_capacity(escaped_len);
|
||||||
|
for ch in filename.chars() {
|
||||||
|
match ch {
|
||||||
|
'\n' => escaped.push_str("%0A"), // \n line break
|
||||||
|
'\x0B' => escaped.push_str("%0B"), // \v vertical tab
|
||||||
|
'\x0C' => escaped.push_str("%0C"), // \f form feed
|
||||||
|
'\r' => escaped.push_str("%0D"), // \r carriage return
|
||||||
|
ch => escaped.push(ch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
escaped
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_ascii = filename.is_ascii();
|
||||||
|
|
||||||
|
let mut parameters = Vec::with_capacity(if is_ascii { 1 } else { 2 });
|
||||||
|
parameters.push(DispositionParam::Filename(filename_s));
|
||||||
|
|
||||||
|
if !is_ascii {
|
||||||
parameters.push(DispositionParam::FilenameExt(ExtendedValue {
|
parameters.push(DispositionParam::FilenameExt(ExtendedValue {
|
||||||
charset: Charset::Ext(String::from("UTF-8")),
|
charset: Charset::Ext(String::from("UTF-8")),
|
||||||
language_tag: None,
|
language_tag: None,
|
||||||
|
|
@ -735,4 +756,15 @@ mod tests {
|
||||||
let (_ct, cd) = get_content_type_and_disposition(Path::new("sound.mp3")).unwrap();
|
let (_ct, cd) = get_content_type_and_disposition(Path::new("sound.mp3")).unwrap();
|
||||||
assert_eq!(cd.disposition, DispositionType::Inline);
|
assert_eq!(cd.disposition, DispositionType::Inline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn special_chars_are_escaped_in_content_disposition_filename() {
|
||||||
|
let (_ct, cd) =
|
||||||
|
get_content_type_and_disposition(Path::new("test\n\x0B\x0C\rnewline.text")).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
cd.to_string(),
|
||||||
|
"inline; filename=\"test%0A%0B%0C%0Dnewline.text\"",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
path::{Component, Path, PathBuf},
|
path::{Component, Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
@ -70,8 +71,10 @@ impl PathBufWrap {
|
||||||
.map_err(|_| UriSegmentError::NotValidUtf8)?;
|
.map_err(|_| UriSegmentError::NotValidUtf8)?;
|
||||||
|
|
||||||
// disallow decoding `%2F` into `/`
|
// disallow decoding `%2F` into `/`
|
||||||
if segment_count != path.matches('/').count() + 1 {
|
if let Cow::Owned(ref path) = path {
|
||||||
return Err(UriSegmentError::BadChar('/'));
|
if segment_count != path.matches('/').count() + 1 {
|
||||||
|
return Err(UriSegmentError::BadChar('/'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for segment in path.split('/') {
|
for segment in path.split('/') {
|
||||||
|
|
@ -199,6 +202,14 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encoded_slash_is_rejected() {
|
||||||
|
assert_eq!(
|
||||||
|
PathBufWrap::parse_path("/test%2Ffile.txt", false),
|
||||||
|
Err(UriSegmentError::BadChar('/'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(windows, should_panic)]
|
#[cfg_attr(windows, should_panic)]
|
||||||
fn windows_drive_traversal() {
|
fn windows_drive_traversal() {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ impl Deref for FilesService {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FilesServiceInner {
|
pub struct FilesServiceInner {
|
||||||
pub(crate) directory: PathBuf,
|
pub(crate) directories: Vec<PathBuf>,
|
||||||
pub(crate) index: Option<String>,
|
pub(crate) index: Option<String>,
|
||||||
pub(crate) show_index: bool,
|
pub(crate) show_index: bool,
|
||||||
pub(crate) redirect_to_slash: bool,
|
pub(crate) redirect_to_slash: bool,
|
||||||
|
|
@ -113,8 +113,8 @@ impl FilesService {
|
||||||
self.serve_named_file_with_encoding(req, named_file, header::ContentEncoding::Identity)
|
self.serve_named_file_with_encoding(req, named_file, header::ContentEncoding::Identity)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_index(&self, req: ServiceRequest, path: PathBuf) -> ServiceResponse {
|
fn show_index(&self, req: ServiceRequest, base: PathBuf, path: PathBuf) -> ServiceResponse {
|
||||||
let dir = Directory::new(self.directory.clone(), path);
|
let dir = Directory::new(base, path);
|
||||||
|
|
||||||
let (req, _) = req.into_parts();
|
let (req, _) = req.into_parts();
|
||||||
|
|
||||||
|
|
@ -171,70 +171,124 @@ impl Service<ServiceRequest> for FilesService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// full file path
|
let mut last_miss = None;
|
||||||
let path = this.directory.join(&path_on_disk);
|
let mut first_index_listing = None;
|
||||||
|
let mut found_unrenderable_dir = false;
|
||||||
|
|
||||||
// Try serving pre-compressed file even if the uncompressed file doesn't exist yet.
|
for directory in &this.directories {
|
||||||
// Still handle directories (index/listing) through the normal branch below.
|
// full file path
|
||||||
if this.try_compressed && !path.is_dir() {
|
let path = directory.join(&path_on_disk);
|
||||||
if let Some((named_file, encoding)) = find_compressed(&req, &path).await {
|
|
||||||
return Ok(this.serve_named_file_with_encoding(req, named_file, encoding));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(err) = path.canonicalize() {
|
// Try serving pre-compressed file even if the uncompressed file doesn't exist yet.
|
||||||
return this.handle_err(err, req).await;
|
// Still handle directories (index/listing) through the normal branch below.
|
||||||
}
|
if this.try_compressed && !path.is_dir() {
|
||||||
|
if let Some((named_file, encoding)) = find_compressed(&req, &path).await {
|
||||||
if path.is_dir() {
|
return Ok(this.serve_named_file_with_encoding(req, named_file, encoding));
|
||||||
if this.redirect_to_slash
|
|
||||||
&& !req.path().ends_with('/')
|
|
||||||
&& (this.index.is_some() || this.show_index)
|
|
||||||
{
|
|
||||||
let redirect_to = format!("{}/", req.path());
|
|
||||||
|
|
||||||
let response = if this.with_permanent_redirect {
|
|
||||||
HttpResponse::PermanentRedirect()
|
|
||||||
} else {
|
|
||||||
HttpResponse::TemporaryRedirect()
|
|
||||||
}
|
}
|
||||||
.insert_header((header::LOCATION, redirect_to))
|
|
||||||
.finish();
|
|
||||||
|
|
||||||
return Ok(req.into_response(response));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match this.index {
|
if let Err(err) = path.canonicalize() {
|
||||||
Some(ref index) => {
|
if matches!(
|
||||||
let named_path = path.join(index);
|
err.kind(),
|
||||||
if this.try_compressed {
|
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
|
||||||
if let Some((named_file, encoding)) =
|
) {
|
||||||
find_compressed(&req, &named_path).await
|
last_miss = Some(err);
|
||||||
{
|
continue;
|
||||||
return Ok(
|
}
|
||||||
this.serve_named_file_with_encoding(req, named_file, encoding)
|
|
||||||
);
|
return this.handle_err(err, req).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.is_dir() {
|
||||||
|
if this.redirect_to_slash
|
||||||
|
&& !req.path().ends_with('/')
|
||||||
|
&& (this.index.is_some() || this.show_index)
|
||||||
|
{
|
||||||
|
let redirect_to = format!("{}/", req.path());
|
||||||
|
|
||||||
|
let response = if this.with_permanent_redirect {
|
||||||
|
HttpResponse::PermanentRedirect()
|
||||||
|
} else {
|
||||||
|
HttpResponse::TemporaryRedirect()
|
||||||
|
}
|
||||||
|
.insert_header((header::LOCATION, redirect_to))
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
return Ok(req.into_response(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
match &this.index {
|
||||||
|
Some(index) => {
|
||||||
|
let named_path = path.join(index);
|
||||||
|
if this.try_compressed {
|
||||||
|
if let Some((named_file, encoding)) =
|
||||||
|
find_compressed(&req, &named_path).await
|
||||||
|
{
|
||||||
|
return Ok(this.serve_named_file_with_encoding(
|
||||||
|
req, named_file, encoding,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fallback to the uncompressed version
|
||||||
|
match NamedFile::open_async(named_path).await {
|
||||||
|
Ok(named_file) => return Ok(this.serve_named_file(req, named_file)),
|
||||||
|
Err(err)
|
||||||
|
if matches!(
|
||||||
|
err.kind(),
|
||||||
|
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
if this.show_index && first_index_listing.is_none() {
|
||||||
|
first_index_listing =
|
||||||
|
Some((directory.to_path_buf(), path.clone()));
|
||||||
|
}
|
||||||
|
last_miss = Some(err);
|
||||||
|
}
|
||||||
|
Err(_) if this.show_index => {
|
||||||
|
if first_index_listing.is_none() {
|
||||||
|
first_index_listing =
|
||||||
|
Some((directory.to_path_buf(), path.clone()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(err) => return this.handle_err(err, req).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fallback to the uncompressed version
|
None if this.show_index => {
|
||||||
match NamedFile::open_async(named_path).await {
|
return Ok(this.show_index(req, directory.to_path_buf(), path));
|
||||||
Ok(named_file) => Ok(this.serve_named_file(req, named_file)),
|
|
||||||
Err(_) if this.show_index => Ok(this.show_index(req, path)),
|
|
||||||
Err(err) => this.handle_err(err, req).await,
|
|
||||||
}
|
}
|
||||||
|
None => found_unrenderable_dir = true,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match NamedFile::open_async(&path).await {
|
||||||
|
Ok(named_file) => return Ok(this.serve_named_file(req, named_file)),
|
||||||
|
Err(err)
|
||||||
|
if matches!(
|
||||||
|
err.kind(),
|
||||||
|
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
last_miss = Some(err);
|
||||||
|
}
|
||||||
|
Err(err) => return this.handle_err(err, req).await,
|
||||||
}
|
}
|
||||||
None if this.show_index => Ok(this.show_index(req, path)),
|
|
||||||
None => Ok(ServiceResponse::from_err(
|
|
||||||
FilesError::IsDirectory,
|
|
||||||
req.into_parts().0,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match NamedFile::open_async(&path).await {
|
|
||||||
Ok(named_file) => Ok(this.serve_named_file(req, named_file)),
|
|
||||||
Err(err) => this.handle_err(err, req).await,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some((base, path)) = first_index_listing {
|
||||||
|
return Ok(this.show_index(req, base, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
if found_unrenderable_dir {
|
||||||
|
return Ok(ServiceResponse::from_err(
|
||||||
|
FilesError::IsDirectory,
|
||||||
|
req.into_parts().0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let err = last_miss
|
||||||
|
.unwrap_or_else(|| io::Error::new(io::ErrorKind::NotFound, "No such file"));
|
||||||
|
this.handle_err(err, req).await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,44 @@ async fn test_compression_encodings() {
|
||||||
assert_eq!(res.headers().get(header::CONTENT_ENCODING), None);
|
assert_eq!(res.headers().get(header::CONTENT_ENCODING), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_compression_encodings_multiple_directories() {
|
||||||
|
use actix_web::body::MessageBody;
|
||||||
|
|
||||||
|
let first_dir = tempfile::tempdir().unwrap();
|
||||||
|
let second_dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
let compressed_path = second_dir.path().join("fallback.txt.gz");
|
||||||
|
std::fs::write(&compressed_path, b"compressed").unwrap();
|
||||||
|
let compressed_len = std::fs::metadata(compressed_path).unwrap().len();
|
||||||
|
|
||||||
|
let srv = test::init_service(
|
||||||
|
App::new().service(Files::new("/", [first_dir.path(), second_dir.path()]).try_compressed()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut req = TestRequest::with_uri("/fallback.txt").to_request();
|
||||||
|
req.headers_mut().insert(
|
||||||
|
header::ACCEPT_ENCODING,
|
||||||
|
header::HeaderValue::from_static("gzip"),
|
||||||
|
);
|
||||||
|
let res = test::call_service(&srv, req).await;
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
res.headers().get(header::CONTENT_TYPE),
|
||||||
|
Some(&HeaderValue::from_static("text/plain; charset=utf-8")),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
res.headers().get(header::CONTENT_ENCODING),
|
||||||
|
Some(&HeaderValue::from_static("gzip")),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
res.into_body().size(),
|
||||||
|
actix_web::body::BodySize::Sized(compressed_len),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web::test]
|
#[actix_web::test]
|
||||||
async fn partial_range_response_encoding() {
|
async fn partial_range_response_encoding() {
|
||||||
let srv = test::init_service(App::new().default_service(web::to(|| async {
|
let srv = test::init_service(App::new().default_service(web::to(|| async {
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
- When configured, gracefully close HTTP/1 connections after early responses to unread request bodies. [#3967]
|
- When configured, gracefully close HTTP/1 connections after early responses to unread request bodies. [#3967]
|
||||||
|
- Wake HTTP/1 payload receivers with an incomplete-payload error when the sender is dropped before EOF. [#3100]
|
||||||
- Update `foldhash` dependency to `0.2`.
|
- Update `foldhash` dependency to `0.2`.
|
||||||
|
|
||||||
[#3967]: https://github.com/actix/actix-web/issues/3967
|
[#3967]: https://github.com/actix/actix-web/issues/3967
|
||||||
|
[#3100]: https://github.com/actix/actix-web/issues/3100
|
||||||
|
|
||||||
## 3.12.1
|
## 3.12.1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ actix-web = "4"
|
||||||
awc = { version = "3", default-features = false, features = ["openssl"] }
|
awc = { version = "3", default-features = false, features = ["openssl"] }
|
||||||
|
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
criterion = { version = "0.5", features = ["html_reports"] }
|
criterion = { version = "0.8", features = ["html_reports"] }
|
||||||
divan = "0.1.8"
|
divan = "0.1.8"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use std::convert::Infallible;
|
use std::{convert::Infallible, hint::black_box};
|
||||||
|
|
||||||
use actix_http::{encoding::Encoder, ContentEncoding, Request, Response, StatusCode};
|
use actix_http::{encoding::Encoder, ContentEncoding, Request, Response, StatusCode};
|
||||||
use actix_service::{fn_service, Service as _};
|
use actix_service::{fn_service, Service as _};
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
|
||||||
static BODY: &[u8] = include_bytes!("../Cargo.toml");
|
static BODY: &[u8] = include_bytes!("../Cargo.toml");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -366,10 +366,9 @@ where
|
||||||
io.poll_flush(cx)
|
io.poll_flush(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter_linger(mut self: Pin<&mut Self>) {
|
fn enter_linger(flags: &mut Flags) {
|
||||||
let this = self.as_mut().project();
|
flags.remove(Flags::KEEP_ALIVE);
|
||||||
this.flags.remove(Flags::KEEP_ALIVE);
|
flags.insert(Flags::LINGER | Flags::FINISHED);
|
||||||
this.flags.insert(Flags::LINGER | Flags::FINISHED);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_linger_timer(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool {
|
fn ensure_linger_timer(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool {
|
||||||
|
|
@ -464,23 +463,19 @@ where
|
||||||
let size = self.as_mut().send_response_inner(res, &body)?;
|
let size = self.as_mut().send_response_inner(res, &body)?;
|
||||||
match size {
|
match size {
|
||||||
BodySize::None | BodySize::Sized(0) => {
|
BodySize::None | BodySize::Sized(0) => {
|
||||||
let this = self.as_mut().project();
|
let mut this = self.as_mut().project();
|
||||||
|
|
||||||
if close_after_response {
|
if close_after_response {
|
||||||
if this.config.client_disconnect_deadline().is_some() {
|
if this.config.client_disconnect_deadline().is_some() {
|
||||||
drop(this);
|
Self::enter_linger(this.flags);
|
||||||
self.as_mut().enter_linger();
|
|
||||||
} else {
|
} else {
|
||||||
self.as_mut()
|
this.flags.insert(Flags::SHUTDOWN | Flags::FINISHED);
|
||||||
.project()
|
|
||||||
.flags
|
|
||||||
.insert(Flags::SHUTDOWN | Flags::FINISHED);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.flags.insert(Flags::FINISHED);
|
this.flags.insert(Flags::FINISHED);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.as_mut().project().state.set(State::None);
|
this.state.set(State::None);
|
||||||
}
|
}
|
||||||
_ => self
|
_ => self
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
|
@ -509,23 +504,19 @@ where
|
||||||
let size = self.as_mut().send_response_inner(res, &body)?;
|
let size = self.as_mut().send_response_inner(res, &body)?;
|
||||||
match size {
|
match size {
|
||||||
BodySize::None | BodySize::Sized(0) => {
|
BodySize::None | BodySize::Sized(0) => {
|
||||||
let this = self.as_mut().project();
|
let mut this = self.as_mut().project();
|
||||||
|
|
||||||
if close_after_response {
|
if close_after_response {
|
||||||
if this.config.client_disconnect_deadline().is_some() {
|
if this.config.client_disconnect_deadline().is_some() {
|
||||||
drop(this);
|
Self::enter_linger(this.flags);
|
||||||
self.as_mut().enter_linger();
|
|
||||||
} else {
|
} else {
|
||||||
self.as_mut()
|
this.flags.insert(Flags::SHUTDOWN | Flags::FINISHED);
|
||||||
.project()
|
|
||||||
.flags
|
|
||||||
.insert(Flags::SHUTDOWN | Flags::FINISHED);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.flags.insert(Flags::FINISHED);
|
this.flags.insert(Flags::FINISHED);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.as_mut().project().state.set(State::None);
|
this.state.set(State::None);
|
||||||
}
|
}
|
||||||
_ => self
|
_ => self
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
|
@ -646,13 +637,9 @@ where
|
||||||
|
|
||||||
if not_pipelined && close_after_response {
|
if not_pipelined && close_after_response {
|
||||||
if this.config.client_disconnect_deadline().is_some() {
|
if this.config.client_disconnect_deadline().is_some() {
|
||||||
drop(this);
|
Self::enter_linger(this.flags);
|
||||||
self.as_mut().enter_linger();
|
|
||||||
} else {
|
} else {
|
||||||
self.as_mut()
|
this.flags.insert(Flags::SHUTDOWN | Flags::FINISHED);
|
||||||
.project()
|
|
||||||
.flags
|
|
||||||
.insert(Flags::SHUTDOWN | Flags::FINISHED);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.flags.insert(Flags::FINISHED);
|
this.flags.insert(Flags::FINISHED);
|
||||||
|
|
@ -708,13 +695,9 @@ where
|
||||||
|
|
||||||
if not_pipelined && close_after_response {
|
if not_pipelined && close_after_response {
|
||||||
if this.config.client_disconnect_deadline().is_some() {
|
if this.config.client_disconnect_deadline().is_some() {
|
||||||
drop(this);
|
Self::enter_linger(this.flags);
|
||||||
self.as_mut().enter_linger();
|
|
||||||
} else {
|
} else {
|
||||||
self.as_mut()
|
this.flags.insert(Flags::SHUTDOWN | Flags::FINISHED);
|
||||||
.project()
|
|
||||||
.flags
|
|
||||||
.insert(Flags::SHUTDOWN | Flags::FINISHED);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.flags.insert(Flags::FINISHED);
|
this.flags.insert(Flags::FINISHED);
|
||||||
|
|
|
||||||
|
|
@ -140,11 +140,20 @@ impl PayloadSender {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for PayloadSender {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(shared) = self.inner.upgrade() {
|
||||||
|
shared.borrow_mut().close_sender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Inner {
|
struct Inner {
|
||||||
len: usize,
|
len: usize,
|
||||||
eof: bool,
|
eof: bool,
|
||||||
err: Option<PayloadError>,
|
err: Option<PayloadError>,
|
||||||
|
sender_closed: bool,
|
||||||
need_read: bool,
|
need_read: bool,
|
||||||
items: VecDeque<Bytes>,
|
items: VecDeque<Bytes>,
|
||||||
task: Option<Waker>,
|
task: Option<Waker>,
|
||||||
|
|
@ -157,6 +166,7 @@ impl Inner {
|
||||||
eof,
|
eof,
|
||||||
len: 0,
|
len: 0,
|
||||||
err: None,
|
err: None,
|
||||||
|
sender_closed: eof,
|
||||||
items: VecDeque::new(),
|
items: VecDeque::new(),
|
||||||
need_read: true,
|
need_read: true,
|
||||||
task: None,
|
task: None,
|
||||||
|
|
@ -200,12 +210,21 @@ impl Inner {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn set_error(&mut self, err: PayloadError) {
|
fn set_error(&mut self, err: PayloadError) {
|
||||||
|
self.sender_closed = true;
|
||||||
self.err = Some(err);
|
self.err = Some(err);
|
||||||
self.wake();
|
self.wake();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn close_sender(&mut self) {
|
||||||
|
if !self.sender_closed {
|
||||||
|
self.sender_closed = true;
|
||||||
|
self.set_error(PayloadError::Incomplete(None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn feed_eof(&mut self) {
|
fn feed_eof(&mut self) {
|
||||||
|
self.sender_closed = true;
|
||||||
self.eof = true;
|
self.eof = true;
|
||||||
self.wake();
|
self.wake();
|
||||||
}
|
}
|
||||||
|
|
@ -332,6 +351,16 @@ mod tests {
|
||||||
timeout(WAKE_TIMEOUT, handle).await.unwrap().unwrap();
|
timeout(WAKE_TIMEOUT, handle).await.unwrap().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn wake_on_sender_drop() {
|
||||||
|
let (sender, payload) = Payload::create(false);
|
||||||
|
let (rx, handle) = prepare_waking_test(payload, Some(Err(())));
|
||||||
|
|
||||||
|
rx.await.unwrap();
|
||||||
|
drop(sender);
|
||||||
|
timeout(WAKE_TIMEOUT, handle).await.unwrap().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_unread_data() {
|
async fn test_unread_data() {
|
||||||
let (_, mut payload) = Payload::create(false);
|
let (_, mut payload) = Payload::create(false);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
## 0.8.0
|
## 0.8.0
|
||||||
|
|
||||||
- Minimum supported Rust version (MSRV) is now 1.88.
|
- Minimum supported Rust version (MSRV) is now 1.88.
|
||||||
|
- Update `darling` dependency to `0.23`.
|
||||||
|
|
||||||
## 0.7.0
|
## 0.7.0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytesize = "2"
|
bytesize = "2"
|
||||||
darling = "0.20"
|
darling = "0.23"
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
syn = "2"
|
syn = "2"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
error: Unknown literal value `no`
|
error: Unknown value: `no`. Available values: `deny`, `ignore`, `replace`
|
||||||
--> tests/trybuild/deny-parse-fail.rs:4:31
|
--> tests/trybuild/deny-parse-fail.rs:4:31
|
||||||
|
|
|
|
||||||
4 | #[multipart(duplicate_field = "no")]
|
4 | #[multipart(duplicate_field = "no")]
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ enum Flow {
|
||||||
/// [`Multipart`] extractor configuration.
|
/// [`Multipart`] extractor configuration.
|
||||||
///
|
///
|
||||||
/// Add to your app data to have it picked up by [`Multipart`] extractors.
|
/// Add to your app data to have it picked up by [`Multipart`] extractors.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct MultipartConfig {
|
pub struct MultipartConfig {
|
||||||
buffer_limit: usize,
|
buffer_limit: usize,
|
||||||
|
|
@ -74,7 +74,7 @@ impl MultipartConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_CONFIG: MultipartConfig = MultipartConfig {
|
const DEFAULT_CONFIG: MultipartConfig = MultipartConfig {
|
||||||
buffer_limit: DEFAULT_BUFFER_LIMIT,
|
buffer_limit: DEFAULT_BUFFER_LIMIT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1013,7 +1013,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_multipart_payload_consumption() {
|
async fn test_multipart_payload_consumption() {
|
||||||
// with sample payload and HttpRequest with no headers
|
// with sample payload and HttpRequest with no headers
|
||||||
let (_, inner_payload) = h1::Payload::create(false);
|
let (_sender, inner_payload) = h1::Payload::create(false);
|
||||||
let mut payload = actix_web::dev::Payload::from(inner_payload);
|
let mut payload = actix_web::dev::Payload::from(inner_payload);
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn basic() {
|
async fn basic() {
|
||||||
let (_, payload) = h1::Payload::create(false);
|
let (_sender, payload) = h1::Payload::create(false);
|
||||||
let mut payload = PayloadBuffer::new_with_limit(payload, DEFAULT_BUFFER_LIMIT);
|
let mut payload = PayloadBuffer::new_with_limit(payload, DEFAULT_BUFFER_LIMIT);
|
||||||
|
|
||||||
assert_eq!(payload.buf.len(), 0);
|
assert_eq!(payload.buf.len(), 0);
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ serde = "1"
|
||||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.5", features = ["html_reports"] }
|
criterion = { version = "0.8", features = ["html_reports"] }
|
||||||
http = "0.2.7"
|
http = "0.2.7"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{borrow::Cow, fmt::Write as _};
|
use std::{borrow::Cow, fmt::Write as _, hint::black_box};
|
||||||
|
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
|
||||||
fn compare_quoters(c: &mut Criterion) {
|
fn compare_quoters(c: &mut Criterion) {
|
||||||
let mut group = c.benchmark_group("Compare Quoters");
|
let mut group = c.benchmark_group("Compare Quoters");
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
//! Based on https://github.com/ibraheemdev/matchit/blob/master/benches/bench.rs
|
//! Based on https://github.com/ibraheemdev/matchit/blob/master/benches/bench.rs
|
||||||
|
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
use std::hint::black_box;
|
||||||
|
|
||||||
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
|
||||||
macro_rules! register {
|
macro_rules! register {
|
||||||
(colon) => {{
|
(colon) => {{
|
||||||
|
|
|
||||||
|
|
@ -513,20 +513,17 @@ pub(crate) fn with_methods(input: TokenStream) -> TokenStream {
|
||||||
Err(err) => return input_and_compile_error(input, err),
|
Err(err) => return input_and_compile_error(input, err),
|
||||||
};
|
};
|
||||||
|
|
||||||
let (methods, others) = ast
|
let mut methods = Vec::new();
|
||||||
.attrs
|
|
||||||
.into_iter()
|
|
||||||
.map(|attr| match MethodType::from_path(attr.path()) {
|
|
||||||
Ok(method) => Ok((method, attr)),
|
|
||||||
Err(_) => Err(attr),
|
|
||||||
})
|
|
||||||
.partition::<Vec<_>, _>(Result::is_ok);
|
|
||||||
|
|
||||||
ast.attrs = others.into_iter().map(Result::unwrap_err).collect();
|
for attr in std::mem::take(&mut ast.attrs) {
|
||||||
|
match MethodType::from_path(attr.path()) {
|
||||||
|
Ok(method) => methods.push((method, attr)),
|
||||||
|
Err(_) => ast.attrs.push(attr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let methods = match methods
|
let methods = match methods
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Result::unwrap)
|
|
||||||
.map(|(method, attr)| {
|
.map(|(method, attr)| {
|
||||||
attr.parse_args()
|
attr.parse_args()
|
||||||
.and_then(|args| Args::new(args, Some(method)))
|
.and_then(|args| Args::new(args, Some(method)))
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,16 @@
|
||||||
- Panic when calling `Route::to()` or `Route::service()` after `Route::wrap()` to prevent silently dropping route middleware. [#3944]
|
- Panic when calling `Route::to()` or `Route::service()` after `Route::wrap()` to prevent silently dropping route middleware. [#3944]
|
||||||
- Fix `HttpRequest::{match_pattern,match_name}` reporting path-only matches when route guards disambiguate overlapping resources. [#3346]
|
- Fix `HttpRequest::{match_pattern,match_name}` reporting path-only matches when route guards disambiguate overlapping resources. [#3346]
|
||||||
- Fix `Readlines` handling of lines split across payload chunks so combined line limits are enforced and complete lines are yielded.
|
- Fix `Readlines` handling of lines split across payload chunks so combined line limits are enforced and complete lines are yielded.
|
||||||
|
- Fix app data being retained after graceful shutdown with in-flight slow request bodies. [#3100]
|
||||||
- Update `foldhash` dependency to `0.2`.
|
- Update `foldhash` dependency to `0.2`.
|
||||||
- Update `rand` dependency to `0.10`.
|
- Update `rand` dependency to `0.10`.
|
||||||
|
- Update `impl-more` dependency to `0.3`.
|
||||||
- Add `HttpServer::h1_write_buffer_size()`.
|
- Add `HttpServer::h1_write_buffer_size()`.
|
||||||
|
|
||||||
[#3944]: https://github.com/actix/actix-web/pull/3944
|
[#3944]: https://github.com/actix/actix-web/pull/3944
|
||||||
[#3346]: https://github.com/actix/actix-web/issues/3346
|
[#3346]: https://github.com/actix/actix-web/issues/3346
|
||||||
[#3542]: https://github.com/actix/actix-web/issues/3542
|
[#3542]: https://github.com/actix/actix-web/issues/3542
|
||||||
|
[#3100]: https://github.com/actix/actix-web/issues/3100
|
||||||
|
|
||||||
## 4.13.0
|
## 4.13.0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ encoding_rs = "0.8"
|
||||||
foldhash = "0.2"
|
foldhash = "0.2"
|
||||||
futures-core = { version = "0.3.17", default-features = false }
|
futures-core = { version = "0.3.17", default-features = false }
|
||||||
futures-util = { version = "0.3.17", default-features = false }
|
futures-util = { version = "0.3.17", default-features = false }
|
||||||
impl-more = "0.1.4"
|
impl-more = "0.3.1"
|
||||||
itoa = "1"
|
itoa = "1"
|
||||||
language-tags = "0.3"
|
language-tags = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
@ -176,7 +176,7 @@ awc = { version = "3", features = ["openssl"] }
|
||||||
brotli = "8"
|
brotli = "8"
|
||||||
const-str = "1.1"
|
const-str = "1.1"
|
||||||
core_affinity = "0.8"
|
core_affinity = "0.8"
|
||||||
criterion = { version = "0.5", features = ["html_reports"] }
|
criterion = { version = "0.8", features = ["html_reports"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
flate2 = "1.0.13"
|
flate2 = "1.0.13"
|
||||||
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ where
|
||||||
T: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
T: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
{
|
{
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.app_state.pool().clear();
|
self.app_state.pool().disable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefCell, RefMut},
|
cell::{Cell, Ref, RefCell, RefMut},
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt,
|
fmt,
|
||||||
hash::{BuildHasher, Hash},
|
hash::{BuildHasher, Hash},
|
||||||
|
|
@ -669,6 +669,7 @@ impl fmt::Debug for HttpRequest {
|
||||||
/// The pool's default capacity is 128 items.
|
/// The pool's default capacity is 128 items.
|
||||||
pub(crate) struct HttpRequestPool {
|
pub(crate) struct HttpRequestPool {
|
||||||
inner: RefCell<Vec<Rc<HttpRequestInner>>>,
|
inner: RefCell<Vec<Rc<HttpRequestInner>>>,
|
||||||
|
enabled: Cell<bool>,
|
||||||
cap: usize,
|
cap: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -682,6 +683,7 @@ impl HttpRequestPool {
|
||||||
pub(crate) fn with_capacity(cap: usize) -> Self {
|
pub(crate) fn with_capacity(cap: usize) -> Self {
|
||||||
HttpRequestPool {
|
HttpRequestPool {
|
||||||
inner: RefCell::new(Vec::with_capacity(cap)),
|
inner: RefCell::new(Vec::with_capacity(cap)),
|
||||||
|
enabled: Cell::new(true),
|
||||||
cap,
|
cap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -698,7 +700,7 @@ impl HttpRequestPool {
|
||||||
/// Check if the pool still has capacity for request storage.
|
/// Check if the pool still has capacity for request storage.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn is_available(&self) -> bool {
|
pub(crate) fn is_available(&self) -> bool {
|
||||||
self.inner.borrow_mut().len() < self.cap
|
self.enabled.get() && self.inner.borrow().len() < self.cap
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a request to pool.
|
/// Push a request to pool.
|
||||||
|
|
@ -707,15 +709,16 @@ impl HttpRequestPool {
|
||||||
self.inner.borrow_mut().push(req);
|
self.inner.borrow_mut().push(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears all allocated HttpRequest objects.
|
/// Prevents future requests from being returned to the pool and clears existing entries.
|
||||||
pub(crate) fn clear(&self) {
|
pub(crate) fn disable(&self) {
|
||||||
self.inner.borrow_mut().clear()
|
self.enabled.set(false);
|
||||||
|
self.inner.borrow_mut().clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
|
@ -993,6 +996,41 @@ mod tests {
|
||||||
assert_eq!(resp.headers().get("pool_cap").unwrap(), "128");
|
assert_eq!(resp.headers().get("pool_cap").unwrap(), "128");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_request_dropped_after_service_does_not_reenter_pool() {
|
||||||
|
struct State {
|
||||||
|
_data: Arc<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let (weak_data, app_data) = {
|
||||||
|
let data = Arc::new("data".to_owned());
|
||||||
|
(Arc::downgrade(&data), web::Data::new(State { _data: data }))
|
||||||
|
};
|
||||||
|
|
||||||
|
let held_req = Rc::new(RefCell::new(None));
|
||||||
|
|
||||||
|
{
|
||||||
|
let held_req = Rc::clone(&held_req);
|
||||||
|
let srv = init_service(App::new().app_data(app_data).service(web::resource("/").to(
|
||||||
|
move |req: HttpRequest| {
|
||||||
|
*held_req.borrow_mut() = Some(req.clone());
|
||||||
|
HttpResponse::Ok()
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let resp = call_service(&srv, TestRequest::default().to_request()).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
drop(resp);
|
||||||
|
drop(srv);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(weak_data.upgrade().is_some());
|
||||||
|
drop(held_req.borrow_mut().take());
|
||||||
|
assert!(weak_data.upgrade().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_data() {
|
async fn test_data() {
|
||||||
let srv = init_service(App::new().app_data(10usize).service(web::resource("/").to(
|
let srv = init_service(App::new().app_data(10usize).service(web::resource("/").to(
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
extern crate tls_openssl as openssl;
|
extern crate tls_openssl as openssl;
|
||||||
|
|
||||||
use std::{sync::mpsc, thread, time::Duration};
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
|
sync::{mpsc, Arc},
|
||||||
|
thread,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
|
use actix_web::{rt::time::sleep, web, App, HttpRequest, HttpResponse, HttpServer};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures_util::stream;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_start() {
|
async fn test_start() {
|
||||||
|
|
@ -74,6 +81,74 @@ async fn test_start() {
|
||||||
srv.stop(false).await;
|
srv.stop(false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_app_data_dropped_after_graceful_shutdown_with_slow_request() {
|
||||||
|
struct State {
|
||||||
|
_data: Arc<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn echo(_body: web::Json<String>) -> HttpResponse {
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
let (weak_data, app_data) = {
|
||||||
|
let data = Arc::new("data".to_owned());
|
||||||
|
(Arc::downgrade(&data), web::Data::new(State { _data: data }))
|
||||||
|
};
|
||||||
|
|
||||||
|
let server = HttpServer::new(move || {
|
||||||
|
App::new()
|
||||||
|
.app_data(app_data.clone())
|
||||||
|
.service(web::resource("/echo").route(web::post().to(echo)))
|
||||||
|
})
|
||||||
|
.workers(1)
|
||||||
|
.shutdown_timeout(1)
|
||||||
|
.bind(("127.0.0.1", 0))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let addr = server.addrs()[0];
|
||||||
|
let server = server.run();
|
||||||
|
let server_handle = server.handle();
|
||||||
|
|
||||||
|
let send_request = async move {
|
||||||
|
sleep(Duration::from_millis(100)).await;
|
||||||
|
|
||||||
|
let slow_body = stream::unfold(0, |idx| async move {
|
||||||
|
if idx < 8 {
|
||||||
|
sleep(Duration::from_millis(200)).await;
|
||||||
|
Some((Ok::<_, Infallible>(Bytes::from_static(b" ")), idx + 1))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = awc::Client::default();
|
||||||
|
let _ = client
|
||||||
|
.post(format!("http://{addr}/echo"))
|
||||||
|
.insert_header(("content-type", "application/json"))
|
||||||
|
.send_stream(slow_body)
|
||||||
|
.await;
|
||||||
|
};
|
||||||
|
|
||||||
|
let graceful_stop = async move {
|
||||||
|
sleep(Duration::from_millis(300)).await;
|
||||||
|
server_handle.stop(true).await;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (server_res, (), ()) = tokio::join!(server, send_request, graceful_stop);
|
||||||
|
server_res.unwrap();
|
||||||
|
|
||||||
|
for _ in 0..20 {
|
||||||
|
sleep(Duration::from_millis(100)).await;
|
||||||
|
|
||||||
|
if weak_data.upgrade().is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("app data still referenced after graceful shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
fn ssl_acceptor() -> openssl::ssl::SslAcceptorBuilder {
|
fn ssl_acceptor() -> openssl::ssl::SslAcceptorBuilder {
|
||||||
use openssl::{
|
use openssl::{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
- Add camel-case header controls to `WebsocketsRequest` via `camel_case_headers()` and `set_camel_case_headers()`. [#3953]
|
- Add camel-case header controls to `WebsocketsRequest` via `camel_case_headers()` and `set_camel_case_headers()`. [#3953]
|
||||||
|
- Update `hickory-resolver` dependency to `0.26.1`.
|
||||||
- Update `rand` dependency to `0.10`.
|
- Update `rand` dependency to `0.10`.
|
||||||
|
|
||||||
[#3953]: https://github.com/actix/actix-web/pull/3953
|
[#3953]: https://github.com/actix/actix-web/pull/3953
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ tls-rustls-0_21 = { package = "rustls", version = "0.21", optional = true, featu
|
||||||
tls-rustls-0_22 = { package = "rustls", version = "0.22", optional = true }
|
tls-rustls-0_22 = { package = "rustls", version = "0.22", optional = true }
|
||||||
tls-rustls-0_23 = { package = "rustls", version = "0.23", optional = true, default-features = false }
|
tls-rustls-0_23 = { package = "rustls", version = "0.23", optional = true, default-features = false }
|
||||||
|
|
||||||
hickory-resolver = { version = "0.25", optional = true, features = ["system-config", "tokio"] }
|
hickory-resolver = { version = "0.26.1", optional = true, features = ["system-config", "tokio"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-http = { version = "3.12", features = ["openssl"] }
|
actix-http = { version = "3.12", features = ["openssl"] }
|
||||||
|
|
|
||||||
|
|
@ -1053,7 +1053,7 @@ mod resolver {
|
||||||
use actix_tls::connect::Resolve;
|
use actix_tls::connect::Resolve;
|
||||||
use hickory_resolver::{
|
use hickory_resolver::{
|
||||||
config::{ResolverConfig, ResolverOpts},
|
config::{ResolverConfig, ResolverOpts},
|
||||||
name_server::TokioConnectionProvider,
|
net::runtime::TokioRuntimeProvider,
|
||||||
system_conf::read_system_conf,
|
system_conf::read_system_conf,
|
||||||
TokioResolver,
|
TokioResolver,
|
||||||
};
|
};
|
||||||
|
|
@ -1102,9 +1102,10 @@ mod resolver {
|
||||||
};
|
};
|
||||||
|
|
||||||
let resolver =
|
let resolver =
|
||||||
TokioResolver::builder_with_config(cfg, TokioConnectionProvider::default())
|
TokioResolver::builder_with_config(cfg, TokioRuntimeProvider::default())
|
||||||
.with_options(opts)
|
.with_options(opts)
|
||||||
.build();
|
.build()
|
||||||
|
.expect("failed to build Hickory DNS resolver");
|
||||||
|
|
||||||
Resolver::custom(HickoryDnsResolver(resolver))
|
Resolver::custom(HickoryDnsResolver(resolver))
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,24 @@
|
||||||
|
|
||||||
extern crate tls_openssl as openssl;
|
extern crate tls_openssl as openssl;
|
||||||
|
|
||||||
use std::sync::{
|
use std::{
|
||||||
atomic::{AtomicUsize, Ordering},
|
convert::Infallible,
|
||||||
Arc,
|
sync::{
|
||||||
|
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::HttpService;
|
use actix_http::{HttpService, Request, Response};
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
use actix_service::{fn_service, map_config, ServiceFactoryExt};
|
use actix_service::{fn_service, map_config, ServiceFactoryExt};
|
||||||
use actix_utils::future::ok;
|
use actix_utils::future::ok;
|
||||||
use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse};
|
use actix_web::{
|
||||||
|
dev::AppConfig,
|
||||||
|
http::{header, Version},
|
||||||
|
web, App, HttpResponse,
|
||||||
|
};
|
||||||
|
use futures_util::stream;
|
||||||
use openssl::{
|
use openssl::{
|
||||||
pkey::PKey,
|
pkey::PKey,
|
||||||
ssl::{SslAcceptor, SslConnector, SslMethod, SslVerifyMode},
|
ssl::{SslAcceptor, SslConnector, SslMethod, SslVerifyMode},
|
||||||
|
|
@ -92,3 +100,55 @@ async fn test_connection_reuse_h2() {
|
||||||
// one connection
|
// one connection
|
||||||
assert_eq!(num.load(Ordering::Relaxed), 1);
|
assert_eq!(num.load(Ordering::Relaxed), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression test for https://github.com/actix/actix-web/issues/2305.
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn h2_streaming_body_does_not_send_transfer_encoding() {
|
||||||
|
let has_transfer_encoding = Arc::new(AtomicBool::new(false));
|
||||||
|
let has_transfer_encoding2 = Arc::clone(&has_transfer_encoding);
|
||||||
|
|
||||||
|
let srv = test_server(move || {
|
||||||
|
let has_transfer_encoding = Arc::clone(&has_transfer_encoding2);
|
||||||
|
|
||||||
|
HttpService::build()
|
||||||
|
.h2(move |req: Request| {
|
||||||
|
let has_transfer_encoding = Arc::clone(&has_transfer_encoding);
|
||||||
|
|
||||||
|
async move {
|
||||||
|
has_transfer_encoding.store(
|
||||||
|
req.head().headers.contains_key(header::TRANSFER_ENCODING),
|
||||||
|
Ordering::Relaxed,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok::<_, Infallible>(Response::ok())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.openssl(tls_config())
|
||||||
|
.map_err(|_| ())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// disable ssl verification
|
||||||
|
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||||
|
builder.set_verify(SslVerifyMode::NONE);
|
||||||
|
let _ = builder
|
||||||
|
.set_alpn_protos(b"\x02h2\x08http/1.1")
|
||||||
|
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
|
||||||
|
|
||||||
|
let client = awc::Client::builder()
|
||||||
|
.connector(awc::Connector::new().openssl(builder.build()))
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.post(srv.surl("/"))
|
||||||
|
.version(Version::HTTP_2)
|
||||||
|
.send_stream(stream::once(async {
|
||||||
|
Ok::<_, Infallible>(bytes::Bytes::from_static(b"hello"))
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
assert_eq!(response.version(), Version::HTTP_2);
|
||||||
|
assert!(!has_transfer_encoding.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
rules:
|
||||||
|
dangerous-triggers:
|
||||||
|
ignore:
|
||||||
|
# Required for labeling PRs from forks; does not check out PR head.
|
||||||
|
- labeler.yml:3
|
||||||
|
dependabot-cooldown:
|
||||||
|
config:
|
||||||
|
days: 3
|
||||||
Loading…
Reference in New Issue