mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-12 13:33:41 +02:00
Compare commits
115 Commits
web-v4.0.0
...
http-v3.1.
Author | SHA1 | Date | |
---|---|---|---|
43671ae4aa | |||
264a703d94 | |||
498fb954b3 | |||
2253eae2bb | |||
8e76a1c775 | |||
dce57a79c9 | |||
6a5b370206 | |||
b1c85ba85b | |||
9aab911600 | |||
017e40f733 | |||
45592b37b6 | |||
8abcb94512 | |||
f2cacc4c9d | |||
56b9c0d08e | |||
de9e41484a | |||
2fed978597 | |||
40048a5811 | |||
e942d3e3b1 | |||
09cffc093c | |||
c58f287044 | |||
7b27493e4c | |||
478b33b8a3 | |||
592b40f914 | |||
fe5279c77a | |||
80d222aa78 | |||
a03a2a0076 | |||
745e738955 | |||
1fd90f0b10 | |||
a35804b89f | |||
5611b98c0d | |||
dce9438518 | |||
be986d96b3 | |||
8ddb24b49b | |||
87f627cd5d | |||
03456b8a33 | |||
8c2fad3164 | |||
62fbd225bc | |||
0fa4d999d9 | |||
da4c849f62 | |||
49cd303c3b | |||
955c3ac0c4 | |||
56e5c19b85 | |||
3f03af1c59 | |||
25c0673278 | |||
e7a05f9892 | |||
2f13e5f675 | |||
9f964751f6 | |||
fcca515387 | |||
075932d823 | |||
cb379c0e0c | |||
d4a5d450de | |||
542200cbc2 | |||
d0c08dbb7d | |||
d0b5fb18d2 | |||
12fb3412a5 | |||
2665357a0c | |||
693271e571 | |||
10ef9b0751 | |||
ce00c88963 | |||
75e6ffb057 | |||
ad38973767 | |||
1c1d6477ef | |||
53509a5361 | |||
a6f27baff1 | |||
218e34ee17 | |||
11bfa84926 | |||
5aa6f713c7 | |||
151a15da74 | |||
1ce58ecb30 | |||
f940653981 | |||
b291e29882 | |||
f843776f36 | |||
52f7d96358 | |||
51e573b888 | |||
38e015432b | |||
f5895d5eff | |||
a0c4bf8d1b | |||
594e3a6ef1 | |||
a808a26d8c | |||
de62e8b025 | |||
3486edabcf | |||
4c59a34513 | |||
1b706b3069 | |||
a9f445875a | |||
e0f02c1d9e | |||
092dbba5b9 | |||
ff4b2d251f | |||
98faa61afe | |||
3f2db9e75c | |||
074d18209d | |||
593fbde46a | |||
161861997c | |||
3d621677a5 | |||
0c144054cb | |||
b0fbe0dfd8 | |||
b653bf557f | |||
1d1a65282f | |||
b0a363a7ae | |||
b4d3c2394d | |||
5ca42df89a | |||
fc5ecdc30b | |||
7fe800c3ff | |||
075df88a07 | |||
391d8a744a | |||
5b6cb681b9 | |||
0957ec40b4 | |||
ccf430d74a | |||
c84c1f0f15 | |||
e9279dfbb8 | |||
a68239adaa | |||
40a4b1ccd5 | |||
7f5a8c0851 | |||
bcdde1d4ea | |||
30aa64ea32 | |||
5469b02638 |
@ -6,9 +6,12 @@ lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dcli
|
|||||||
ci-check-min = "hack --workspace check --no-default-features"
|
ci-check-min = "hack --workspace check --no-default-features"
|
||||||
ci-check-default = "hack --workspace check"
|
ci-check-default = "hack --workspace check"
|
||||||
ci-check-default-tests = "check --workspace --tests"
|
ci-check-default-tests = "check --workspace --tests"
|
||||||
ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,io-uring check"
|
ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,experimental-io-uring check"
|
||||||
ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=__compress check"
|
ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=__compress check"
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture"
|
ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture"
|
||||||
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
||||||
|
|
||||||
|
# compile docs as docs.rs would
|
||||||
|
# RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
name: CI (master only)
|
name: CI (post-merge)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -78,15 +78,6 @@ jobs:
|
|||||||
cargo test --lib --tests -p=actix-multipart --all-features
|
cargo test --lib --tests -p=actix-multipart --all-features
|
||||||
cargo test --lib --tests -p=actix-web-actors --all-features
|
cargo test --lib --tests -p=actix-web-actors --all-features
|
||||||
|
|
||||||
- name: tests (io-uring)
|
|
||||||
if: matrix.target.os == 'ubuntu-latest'
|
|
||||||
timeout-minutes: 60
|
|
||||||
run: >
|
|
||||||
sudo bash -c "ulimit -Sl 512
|
|
||||||
&& ulimit -Hl 512
|
|
||||||
&& PATH=$PATH:/usr/share/rust/.cargo/bin
|
|
||||||
&& RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo test --lib --tests -p=actix-files --all-features"
|
|
||||||
|
|
||||||
- name: Clear the cargo caches
|
- name: Clear the cargo caches
|
||||||
run: |
|
run: |
|
||||||
cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean
|
cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean
|
||||||
@ -125,16 +116,16 @@ jobs:
|
|||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with: { command: ci-check-all-feature-powerset-linux }
|
with: { command: ci-check-all-feature-powerset-linux }
|
||||||
|
|
||||||
coverage:
|
nextest:
|
||||||
name: coverage
|
name: nextest
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install stable
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: stable-x86_64-unknown-linux-gnu
|
toolchain: stable
|
||||||
profile: minimal
|
profile: minimal
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
@ -142,12 +133,16 @@ jobs:
|
|||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with: { command: generate-lockfile }
|
with: { command: generate-lockfile }
|
||||||
- name: Cache Dependencies
|
- name: Cache Dependencies
|
||||||
uses: Swatinem/rust-cache@v1.2.0
|
uses: Swatinem/rust-cache@v1.3.0
|
||||||
|
|
||||||
- name: Generate coverage file
|
- name: Install cargo-nextest
|
||||||
run: |
|
uses: actions-rs/cargo@v1
|
||||||
cargo install cargo-tarpaulin --vers "^0.13"
|
with:
|
||||||
cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose
|
command: install
|
||||||
- name: Upload to Codecov
|
args: cargo-nextest
|
||||||
uses: codecov/codecov-action@v1
|
|
||||||
with: { file: cobertura.xml }
|
- name: Test with cargo-nextest
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: nextest
|
||||||
|
args: run
|
34
.github/workflows/ci.yml
vendored
34
.github/workflows/ci.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
|
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
|
||||||
- { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc }
|
- { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc }
|
||||||
version:
|
version:
|
||||||
- 1.54.0 # MSRV
|
- 1.56.0 # MSRV
|
||||||
- stable
|
- stable
|
||||||
|
|
||||||
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
||||||
@ -81,19 +81,37 @@ jobs:
|
|||||||
cargo test --lib --tests -p=actix-multipart --all-features
|
cargo test --lib --tests -p=actix-multipart --all-features
|
||||||
cargo test --lib --tests -p=actix-web-actors --all-features
|
cargo test --lib --tests -p=actix-web-actors --all-features
|
||||||
|
|
||||||
|
- name: Clear the cargo caches
|
||||||
|
run: |
|
||||||
|
cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean
|
||||||
|
cargo-cache
|
||||||
|
|
||||||
|
io-uring:
|
||||||
|
name: io-uring tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable-x86_64-unknown-linux-gnu
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Generate Cargo.lock
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with: { command: generate-lockfile }
|
||||||
|
- name: Cache Dependencies
|
||||||
|
uses: Swatinem/rust-cache@v1.3.0
|
||||||
|
|
||||||
- name: tests (io-uring)
|
- name: tests (io-uring)
|
||||||
if: matrix.target.os == 'ubuntu-latest'
|
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
run: >
|
run: >
|
||||||
sudo bash -c "ulimit -Sl 512
|
sudo bash -c "ulimit -Sl 512
|
||||||
&& ulimit -Hl 512
|
&& ulimit -Hl 512
|
||||||
&& PATH=$PATH:/usr/share/rust/.cargo/bin
|
&& PATH=$PATH:/usr/share/rust/.cargo/bin
|
||||||
&& RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo test --lib --tests -p=actix-files --all-features"
|
&& RUSTUP_TOOLCHAIN=stable cargo test --lib --tests -p=actix-files --all-features"
|
||||||
|
|
||||||
- name: Clear the cargo caches
|
|
||||||
run: |
|
|
||||||
cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean
|
|
||||||
cargo-cache
|
|
||||||
|
|
||||||
rustdoc:
|
rustdoc:
|
||||||
name: doc tests
|
name: doc tests
|
||||||
|
36
.github/workflows/coverage.yml
vendored
Normal file
36
.github/workflows/coverage.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# disabled because `cargo tarpaulin` currently segfaults
|
||||||
|
|
||||||
|
name: Coverage
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# job currently (1st Feb 2022) segfaults
|
||||||
|
coverage:
|
||||||
|
name: coverage
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install stable
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable-x86_64-unknown-linux-gnu
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Generate Cargo.lock
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with: { command: generate-lockfile }
|
||||||
|
- name: Cache Dependencies
|
||||||
|
uses: Swatinem/rust-cache@v1.2.0
|
||||||
|
|
||||||
|
- name: Generate coverage file
|
||||||
|
run: |
|
||||||
|
cargo install cargo-tarpaulin --vers "^0.13"
|
||||||
|
cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose
|
||||||
|
- name: Upload to Codecov
|
||||||
|
uses: codecov/codecov-action@v1
|
||||||
|
with: { file: cobertura.xml }
|
3
.prettierrc.json
Normal file
3
.prettierrc.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"proseWrap": "never"
|
||||||
|
}
|
1033
CHANGES.md
1033
CHANGES.md
File diff suppressed because it is too large
Load Diff
149
Cargo.toml
149
Cargo.toml
@ -1,36 +1,6 @@
|
|||||||
[package]
|
|
||||||
name = "actix-web"
|
|
||||||
version = "4.0.0-rc.1"
|
|
||||||
authors = [
|
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
|
||||||
]
|
|
||||||
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
|
||||||
keywords = ["actix", "http", "web", "framework", "async"]
|
|
||||||
categories = [
|
|
||||||
"network-programming",
|
|
||||||
"asynchronous",
|
|
||||||
"web-programming::http-server",
|
|
||||||
"web-programming::websocket"
|
|
||||||
]
|
|
||||||
homepage = "https://actix.rs"
|
|
||||||
repository = "https://github.com/actix/actix-web.git"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
# features that docs.rs will build with
|
|
||||||
features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"]
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "actix_web"
|
|
||||||
path = "src/lib.rs"
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
".",
|
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-http-test",
|
"actix-http-test",
|
||||||
"actix-http",
|
"actix-http",
|
||||||
@ -39,93 +9,10 @@ members = [
|
|||||||
"actix-test",
|
"actix-test",
|
||||||
"actix-web-actors",
|
"actix-web-actors",
|
||||||
"actix-web-codegen",
|
"actix-web-codegen",
|
||||||
|
"actix-web",
|
||||||
"awc",
|
"awc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
|
|
||||||
|
|
||||||
# Brotli algorithm content-encoding support
|
|
||||||
compress-brotli = ["actix-http/compress-brotli", "__compress"]
|
|
||||||
# Gzip and deflate algorithms content-encoding support
|
|
||||||
compress-gzip = ["actix-http/compress-gzip", "__compress"]
|
|
||||||
# Zstd algorithm content-encoding support
|
|
||||||
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
|
||||||
|
|
||||||
# support for cookies
|
|
||||||
cookies = ["cookie"]
|
|
||||||
|
|
||||||
# secure cookies feature
|
|
||||||
secure-cookies = ["cookie/secure"]
|
|
||||||
|
|
||||||
# openssl
|
|
||||||
openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"]
|
|
||||||
|
|
||||||
# rustls
|
|
||||||
rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"]
|
|
||||||
|
|
||||||
# Internal (PRIVATE!) features used to aid testing and checking feature status.
|
|
||||||
# Don't rely on these whatsoever. They may disappear at anytime.
|
|
||||||
__compress = []
|
|
||||||
|
|
||||||
# io-uring feature only avaiable for Linux OSes.
|
|
||||||
experimental-io-uring = ["actix-server/io-uring"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
actix-codec = "0.4.1"
|
|
||||||
actix-macros = "0.2.3"
|
|
||||||
actix-rt = "2.6"
|
|
||||||
actix-server = "2"
|
|
||||||
actix-service = "2.0.0"
|
|
||||||
actix-utils = "3.0.0"
|
|
||||||
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
|
||||||
|
|
||||||
actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] }
|
|
||||||
actix-router = "0.5.0-rc.3"
|
|
||||||
actix-web-codegen = "0.5.0-rc.1"
|
|
||||||
|
|
||||||
ahash = "0.7"
|
|
||||||
bytes = "1"
|
|
||||||
cfg-if = "1"
|
|
||||||
cookie = { version = "0.16", features = ["percent-encode"], optional = true }
|
|
||||||
derive_more = "0.99.5"
|
|
||||||
encoding_rs = "0.8"
|
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
|
||||||
itoa = "1"
|
|
||||||
language-tags = "0.3"
|
|
||||||
once_cell = "1.5"
|
|
||||||
log = "0.4"
|
|
||||||
mime = "0.3"
|
|
||||||
pin-project-lite = "0.2.7"
|
|
||||||
regex = "1.4"
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_urlencoded = "0.7"
|
|
||||||
smallvec = "1.6.1"
|
|
||||||
socket2 = "0.4.0"
|
|
||||||
time = { version = "0.3", default-features = false, features = ["formatting"] }
|
|
||||||
url = "2.1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
actix-files = "0.6.0-beta.16"
|
|
||||||
actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] }
|
|
||||||
awc = { version = "3.0.0-beta.20", features = ["openssl"] }
|
|
||||||
|
|
||||||
brotli = "3.3.3"
|
|
||||||
const-str = "0.3"
|
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
|
||||||
env_logger = "0.9"
|
|
||||||
flate2 = "1.0.13"
|
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["std"] }
|
|
||||||
rand = "0.8"
|
|
||||||
rcgen = "0.8"
|
|
||||||
rustls-pemfile = "0.2"
|
|
||||||
static_assertions = "1"
|
|
||||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
|
||||||
tls-rustls = { package = "rustls", version = "0.20.0" }
|
|
||||||
zstd = "0.9"
|
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much.
|
# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much.
|
||||||
debug = 0
|
debug = 0
|
||||||
@ -142,7 +29,7 @@ actix-http-test = { path = "actix-http-test" }
|
|||||||
actix-multipart = { path = "actix-multipart" }
|
actix-multipart = { path = "actix-multipart" }
|
||||||
actix-router = { path = "actix-router" }
|
actix-router = { path = "actix-router" }
|
||||||
actix-test = { path = "actix-test" }
|
actix-test = { path = "actix-test" }
|
||||||
actix-web = { path = "." }
|
actix-web = { path = "actix-web" }
|
||||||
actix-web-actors = { path = "actix-web-actors" }
|
actix-web-actors = { path = "actix-web-actors" }
|
||||||
actix-web-codegen = { path = "actix-web-codegen" }
|
actix-web-codegen = { path = "actix-web-codegen" }
|
||||||
awc = { path = "awc" }
|
awc = { path = "awc" }
|
||||||
@ -155,35 +42,3 @@ awc = { path = "awc" }
|
|||||||
# actix-utils = { path = "../actix-net/actix-utils" }
|
# actix-utils = { path = "../actix-net/actix-utils" }
|
||||||
# actix-tls = { path = "../actix-net/actix-tls" }
|
# actix-tls = { path = "../actix-net/actix-tls" }
|
||||||
# actix-server = { path = "../actix-net/actix-server" }
|
# actix-server = { path = "../actix-net/actix-server" }
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "test_server"
|
|
||||||
required-features = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "compression"
|
|
||||||
required-features = ["compress-brotli", "compress-gzip", "compress-zstd"]
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "basic"
|
|
||||||
required-features = ["compress-gzip"]
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "uds"
|
|
||||||
required-features = ["compress-gzip"]
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "on-connect"
|
|
||||||
required-features = []
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "server"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "service"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "responder"
|
|
||||||
harness = false
|
|
||||||
|
677
MIGRATION.md
677
MIGRATION.md
@ -1,677 +0,0 @@
|
|||||||
## Unreleased
|
|
||||||
|
|
||||||
- The default `NormalizePath` behavior now strips trailing slashes by default. This was
|
|
||||||
previously documented to be the case in v3 but the behavior now matches. The effect is that
|
|
||||||
routes defined with trailing slashes will become inaccessible when
|
|
||||||
using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning.
|
|
||||||
It is advised that the `new` method be used instead.
|
|
||||||
|
|
||||||
Before: `#[get("/test/")]`
|
|
||||||
After: `#[get("/test")]`
|
|
||||||
|
|
||||||
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
|
|
||||||
|
|
||||||
- The `type Config` of `FromRequest` was removed.
|
|
||||||
|
|
||||||
- Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd).
|
|
||||||
By default all compression algorithms are enabled.
|
|
||||||
To select algorithm you want to include with `middleware::Compress` use following flags:
|
|
||||||
- `compress-brotli`
|
|
||||||
- `compress-gzip`
|
|
||||||
- `compress-zstd`
|
|
||||||
If you have set in your `Cargo.toml` dedicated `actix-web` features and you still want
|
|
||||||
to have compression enabled. Please change features selection like bellow:
|
|
||||||
|
|
||||||
Before: `"compress"`
|
|
||||||
After: `"compress-brotli", "compress-gzip", "compress-zstd"`
|
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0
|
|
||||||
|
|
||||||
- The return type for `ServiceRequest::app_data::<T>()` was changed from returning a `Data<T>` to
|
|
||||||
simply a `T`. To access a `Data<T>` use `ServiceRequest::app_data::<Data<T>>()`.
|
|
||||||
|
|
||||||
- Cookie handling has been offloaded to the `cookie` crate:
|
|
||||||
* `USERINFO_ENCODE_SET` is no longer exposed. Percent-encoding is still supported; check docs.
|
|
||||||
* Some types now require lifetime parameters.
|
|
||||||
|
|
||||||
- The time crate was updated to `v0.2`, a major breaking change to the time crate, which affects
|
|
||||||
any `actix-web` method previously expecting a time v0.1 input.
|
|
||||||
|
|
||||||
- Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now
|
|
||||||
result in `SameSite=None` being sent with the response Set-Cookie header.
|
|
||||||
To create a cookie without a SameSite attribute, remove any calls setting same_site.
|
|
||||||
|
|
||||||
- actix-http support for Actors messages was moved to actix-http crate and is enabled
|
|
||||||
with feature `actors`
|
|
||||||
|
|
||||||
- content_length function is removed from actix-http.
|
|
||||||
You can set Content-Length by normally setting the response body or calling no_chunking function.
|
|
||||||
|
|
||||||
- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
|
|
||||||
`u64` instead of a `usize`.
|
|
||||||
|
|
||||||
- Code that was using `path.<index>` to access a `web::Path<(A, B, C)>`s elements now needs to use
|
|
||||||
destructuring or `.into_inner()`. For example:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Previously:
|
|
||||||
async fn some_route(path: web::Path<(String, String)>) -> String {
|
|
||||||
format!("Hello, {} {}", path.0, path.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now (this also worked before):
|
|
||||||
async fn some_route(path: web::Path<(String, String)>) -> String {
|
|
||||||
let (first_name, last_name) = path.into_inner();
|
|
||||||
format!("Hello, {} {}", first_name, last_name)
|
|
||||||
}
|
|
||||||
// Or (this wasn't previously supported):
|
|
||||||
async fn some_route(web::Path((first_name, last_name)): web::Path<(String, String)>) -> String {
|
|
||||||
format!("Hello, {} {}", first_name, last_name)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one.
|
|
||||||
It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`,
|
|
||||||
or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly))`.
|
|
||||||
|
|
||||||
- `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`.
|
|
||||||
|
|
||||||
- `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`.
|
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0
|
|
||||||
|
|
||||||
- `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to
|
|
||||||
`.await` on `run` method result, in that case it awaits server exit.
|
|
||||||
|
|
||||||
- `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`.
|
|
||||||
Stored data is available via `HttpRequest::app_data()` method at runtime.
|
|
||||||
|
|
||||||
- Extractor configuration must be registered with `App::app_data()` instead of `App::data()`
|
|
||||||
|
|
||||||
- Sync handlers has been removed. `.to_async()` method has been renamed to `.to()`
|
|
||||||
replace `fn` with `async fn` to convert sync handler to async
|
|
||||||
|
|
||||||
- `actix_http_test::TestServer` moved to `actix_web::test` module. To start
|
|
||||||
test server use `test::start()` or `test_start_with_config()` methods
|
|
||||||
|
|
||||||
- `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders
|
|
||||||
http response.
|
|
||||||
|
|
||||||
- Feature `rust-tls` renamed to `rustls`
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
actix-web = { version = "2.0.0", features = ["rust-tls"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
use
|
|
||||||
|
|
||||||
```rust
|
|
||||||
actix-web = { version = "2.0.0", features = ["rustls"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
- Feature `ssl` renamed to `openssl`
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
actix-web = { version = "2.0.0", features = ["ssl"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
use
|
|
||||||
|
|
||||||
```rust
|
|
||||||
actix-web = { version = "2.0.0", features = ["openssl"] }
|
|
||||||
```
|
|
||||||
- `Cors` builder now requires that you call `.finish()` to construct the middleware
|
|
||||||
|
|
||||||
## 1.0.1
|
|
||||||
|
|
||||||
- Cors middleware has been moved to `actix-cors` crate
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use actix_web::middleware::cors::Cors;
|
|
||||||
```
|
|
||||||
|
|
||||||
use
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use actix_cors::Cors;
|
|
||||||
```
|
|
||||||
|
|
||||||
- Identity middleware has been moved to `actix-identity` crate
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use actix_web::middleware::identity::{Identity, CookieIdentityPolicy, IdentityService};
|
|
||||||
```
|
|
||||||
|
|
||||||
use
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use actix_identity::{Identity, CookieIdentityPolicy, IdentityService};
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## 1.0.0
|
|
||||||
|
|
||||||
- Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct ExtractorConfig {
|
|
||||||
config: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromRequest for YourExtractor {
|
|
||||||
type Config = ExtractorConfig;
|
|
||||||
type Result = Result<YourExtractor, Error>;
|
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result {
|
|
||||||
println!("use the config: {:?}", cfg.config);
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
App::new().resource("/route_with_config", |r| {
|
|
||||||
r.post().with_config(handler_fn, |cfg| {
|
|
||||||
cfg.0.config = "test".to_string();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
use the HttpRequest to get the configuration like any other `Data` with `req.app_data::<C>()` and set it with the `data()` method on the `resource`
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[derive(Default)]
|
|
||||||
struct ExtractorConfig {
|
|
||||||
config: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromRequest for YourExtractor {
|
|
||||||
type Error = Error;
|
|
||||||
type Future = Result<Self, Self::Error>;
|
|
||||||
type Config = ExtractorConfig;
|
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
|
||||||
let cfg = req.app_data::<ExtractorConfig>();
|
|
||||||
println!("config data?: {:?}", cfg.unwrap().role);
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
App::new().service(
|
|
||||||
resource("/route_with_config")
|
|
||||||
.data(ExtractorConfig {
|
|
||||||
config: "test".to_string(),
|
|
||||||
})
|
|
||||||
.route(post().to(handler_fn)),
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
- Resource registration. 1.0 version uses generalized resource
|
|
||||||
registration via `.service()` method.
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
App.new().resource("/welcome", |r| r.f(welcome))
|
|
||||||
```
|
|
||||||
|
|
||||||
use App's or Scope's `.service()` method. `.service()` method accepts
|
|
||||||
object that implements `HttpServiceFactory` trait. By default
|
|
||||||
actix-web provides `Resource` and `Scope` services.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
App.new().service(
|
|
||||||
web::resource("/welcome")
|
|
||||||
.route(web::get().to(welcome))
|
|
||||||
.route(web::post().to(post_handler))
|
|
||||||
```
|
|
||||||
|
|
||||||
- Scope registration.
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let app = App::new().scope("/{project_id}", |scope| {
|
|
||||||
scope
|
|
||||||
.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
|
||||||
.resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
|
||||||
.resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
use `.service()` for registration and `web::scope()` as scope object factory.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let app = App::new().service(
|
|
||||||
web::scope("/{project_id}")
|
|
||||||
.service(web::resource("/path1").to(|| HttpResponse::Ok()))
|
|
||||||
.service(web::resource("/path2").to(|| HttpResponse::Ok()))
|
|
||||||
.service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed()))
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
- `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`.
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
App.new().resource("/welcome", |r| r.with(welcome))
|
|
||||||
```
|
|
||||||
|
|
||||||
use `.to()` or `.to_async()` methods
|
|
||||||
|
|
||||||
```rust
|
|
||||||
App.new().service(web::resource("/welcome").to(welcome))
|
|
||||||
```
|
|
||||||
|
|
||||||
- Passing arguments to handler with extractors, multiple arguments are allowed
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn welcome((body, req): (Bytes, HttpRequest)) -> ... {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
use multiple arguments
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn welcome(body: Bytes, req: HttpRequest) -> ... {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `.f()`, `.a()` and `.h()` handler registration methods have been removed.
|
|
||||||
Use `.to()` for handlers and `.to_async()` for async handlers. Handler function
|
|
||||||
must use extractors.
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
App.new().resource("/welcome", |r| r.f(welcome))
|
|
||||||
```
|
|
||||||
|
|
||||||
use App's `to()` or `to_async()` methods
|
|
||||||
|
|
||||||
```rust
|
|
||||||
App.new().service(web::resource("/welcome").to(welcome))
|
|
||||||
```
|
|
||||||
|
|
||||||
- `HttpRequest` does not provide access to request's payload stream.
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn index(req: &HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
req
|
|
||||||
.payload()
|
|
||||||
.from_err()
|
|
||||||
.fold((), |_, chunk| {
|
|
||||||
...
|
|
||||||
})
|
|
||||||
.map(|_| HttpResponse::Ok().finish())
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
use `Payload` extractor
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn index(stream: web::Payload) -> impl Future<Item=HttpResponse, Error=Error> {
|
|
||||||
stream
|
|
||||||
.from_err()
|
|
||||||
.fold((), |_, chunk| {
|
|
||||||
...
|
|
||||||
})
|
|
||||||
.map(|_| HttpResponse::Ok().finish())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `State` is now `Data`. You register Data during the App initialization process
|
|
||||||
and then access it from handlers either using a Data extractor or using
|
|
||||||
HttpRequest's api.
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
App.with_state(T)
|
|
||||||
```
|
|
||||||
|
|
||||||
use App's `data` method
|
|
||||||
|
|
||||||
```rust
|
|
||||||
App.new()
|
|
||||||
.data(T)
|
|
||||||
```
|
|
||||||
|
|
||||||
and either use the Data extractor within your handler
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use actix_web::web::Data;
|
|
||||||
|
|
||||||
fn endpoint_handler(Data<T>)){
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
.. or access your Data element from the HttpRequest
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn endpoint_handler(req: HttpRequest) {
|
|
||||||
let data: Option<Data<T>> = req.app_data::<T>();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
- AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type.
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use actix_web::AsyncResponder;
|
|
||||||
|
|
||||||
fn endpoint_handler(...) -> impl Future<Item=HttpResponse, Error=Error>{
|
|
||||||
...
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
.. simply omit AsyncResponder and the corresponding responder() finish method
|
|
||||||
|
|
||||||
|
|
||||||
- Middleware
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let app = App::new()
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
```
|
|
||||||
|
|
||||||
use `.wrap()` method
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let app = App::new()
|
|
||||||
.wrap(middleware::Logger::default())
|
|
||||||
.route("/index.html", web::get().to(index));
|
|
||||||
```
|
|
||||||
|
|
||||||
- `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()`
|
|
||||||
method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead.
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn index(req: &HttpRequest) -> Responder {
|
|
||||||
req.body()
|
|
||||||
.and_then(|body| {
|
|
||||||
...
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
use
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn index(body: Bytes) -> Responder {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type
|
|
||||||
|
|
||||||
- StaticFiles and NamedFile have been moved to a separate crate.
|
|
||||||
|
|
||||||
instead of `use actix_web::fs::StaticFile`
|
|
||||||
|
|
||||||
use `use actix_files::Files`
|
|
||||||
|
|
||||||
instead of `use actix_web::fs::Namedfile`
|
|
||||||
|
|
||||||
use `use actix_files::NamedFile`
|
|
||||||
|
|
||||||
- Multipart has been moved to a separate crate.
|
|
||||||
|
|
||||||
instead of `use actix_web::multipart::Multipart`
|
|
||||||
|
|
||||||
use `use actix_multipart::Multipart`
|
|
||||||
|
|
||||||
- Response compression is not enabled by default.
|
|
||||||
To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`.
|
|
||||||
|
|
||||||
- Session middleware moved to actix-session crate
|
|
||||||
|
|
||||||
- Actors support have been moved to `actix-web-actors` crate
|
|
||||||
|
|
||||||
- Custom Error
|
|
||||||
|
|
||||||
Instead of error_response method alone, ResponseError now provides two methods: error_response and render_response respectively. Where, error_response creates the error response and render_response returns the error response to the caller.
|
|
||||||
|
|
||||||
Simplest migration from 0.7 to 1.0 shall include below method to the custom implementation of ResponseError:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn render_response(&self) -> HttpResponse {
|
|
||||||
self.error_response()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 0.7.15
|
|
||||||
|
|
||||||
- The `' '` character is not percent decoded anymore before matching routes. If you need to use it in
|
|
||||||
your routes, you should use `%20`.
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn main() {
|
|
||||||
let app = App::new().resource("/my index", |r| {
|
|
||||||
r.method(http::Method::GET)
|
|
||||||
.with(index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
use
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn main() {
|
|
||||||
let app = App::new().resource("/my%20index", |r| {
|
|
||||||
r.method(http::Method::GET)
|
|
||||||
.with(index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- If you used `AsyncResult::async` you need to replace it with `AsyncResult::future`
|
|
||||||
|
|
||||||
|
|
||||||
## 0.7.4
|
|
||||||
|
|
||||||
- `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
|
|
||||||
even for handler with one parameter.
|
|
||||||
|
|
||||||
|
|
||||||
## 0.7
|
|
||||||
|
|
||||||
- `HttpRequest` does not implement `Stream` anymore. If you need to read request payload
|
|
||||||
use `HttpMessage::payload()` method.
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn index(req: HttpRequest) -> impl Responder {
|
|
||||||
req
|
|
||||||
.from_err()
|
|
||||||
.fold(...)
|
|
||||||
....
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
use `.payload()`
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn index(req: HttpRequest) -> impl Responder {
|
|
||||||
req
|
|
||||||
.payload() // <- get request payload stream
|
|
||||||
.from_err()
|
|
||||||
.fold(...)
|
|
||||||
....
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html)
|
|
||||||
trait uses `&HttpRequest` instead of `&mut HttpRequest`.
|
|
||||||
|
|
||||||
- Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead.
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn index(query: Query<..>, info: Json<MyStruct) -> impl Responder {}
|
|
||||||
```
|
|
||||||
|
|
||||||
use tuple of extractors and use `.with()` for registration:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn index((query, json): (Query<..>, Json<MyStruct)) -> impl Responder {}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `Handler::handle()` uses `&self` instead of `&mut self`
|
|
||||||
|
|
||||||
- `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value
|
|
||||||
|
|
||||||
- Removed deprecated `HttpServer::threads()`, use
|
|
||||||
[HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead.
|
|
||||||
|
|
||||||
- Renamed `client::ClientConnectorError::Connector` to
|
|
||||||
`client::ClientConnectorError::Resolver`
|
|
||||||
|
|
||||||
- `Route::with()` does not return `ExtractorConfig`, to configure
|
|
||||||
extractor use `Route::with_config()`
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn main() {
|
|
||||||
let app = App::new().resource("/index.html", |r| {
|
|
||||||
r.method(http::Method::GET)
|
|
||||||
.with(index)
|
|
||||||
.limit(4096); // <- limit size of the payload
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
use
|
|
||||||
|
|
||||||
```rust
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let app = App::new().resource("/index.html", |r| {
|
|
||||||
r.method(http::Method::GET)
|
|
||||||
.with_config(index, |cfg| { // <- register handler
|
|
||||||
cfg.limit(4096); // <- limit size of the payload
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `Route::with_async()` does not return `ExtractorConfig`, to configure
|
|
||||||
extractor use `Route::with_async_config()`
|
|
||||||
|
|
||||||
|
|
||||||
## 0.6
|
|
||||||
|
|
||||||
- `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
|
|
||||||
|
|
||||||
- `ws::Message::Close` now includes optional close reason.
|
|
||||||
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
|
|
||||||
|
|
||||||
- `HttpServer::threads()` renamed to `HttpServer::workers()`.
|
|
||||||
|
|
||||||
- `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated.
|
|
||||||
Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead.
|
|
||||||
|
|
||||||
- `HttpRequest::extensions()` returns read only reference to the request's Extension
|
|
||||||
`HttpRequest::extensions_mut()` returns mutable reference.
|
|
||||||
|
|
||||||
- Instead of
|
|
||||||
|
|
||||||
`use actix_web::middleware::{
|
|
||||||
CookieSessionBackend, CookieSessionError, RequestSession,
|
|
||||||
Session, SessionBackend, SessionImpl, SessionStorage};`
|
|
||||||
|
|
||||||
use `actix_web::middleware::session`
|
|
||||||
|
|
||||||
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,
|
|
||||||
RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
|
|
||||||
|
|
||||||
- `FromRequest::from_request()` accepts mutable reference to a request
|
|
||||||
|
|
||||||
- `FromRequest::Result` has to implement `Into<Reply<Self>>`
|
|
||||||
|
|
||||||
- [`Responder::respond_to()`](
|
|
||||||
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
|
|
||||||
is generic over `S`
|
|
||||||
|
|
||||||
- Use `Query` extractor instead of HttpRequest::query()`.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let q = Query::<HashMap<String, String>>::extract(req);
|
|
||||||
```
|
|
||||||
|
|
||||||
- Websocket operations are implemented as `WsWriter` trait.
|
|
||||||
you need to use `use actix_web::ws::WsWriter`
|
|
||||||
|
|
||||||
|
|
||||||
## 0.5
|
|
||||||
|
|
||||||
- `HttpResponseBuilder::body()`, `.finish()`, `.json()`
|
|
||||||
methods return `HttpResponse` instead of `Result<HttpResponse>`
|
|
||||||
|
|
||||||
- `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version`
|
|
||||||
moved to `actix_web::http` module
|
|
||||||
|
|
||||||
- `actix_web::header` moved to `actix_web::http::header`
|
|
||||||
|
|
||||||
- `NormalizePath` moved to `actix_web::http` module
|
|
||||||
|
|
||||||
- `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function,
|
|
||||||
shortcut for `actix_web::server::HttpServer::new()`
|
|
||||||
|
|
||||||
- `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself
|
|
||||||
|
|
||||||
- `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead.
|
|
||||||
|
|
||||||
- `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type
|
|
||||||
|
|
||||||
- `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()`
|
|
||||||
functions should be used instead
|
|
||||||
|
|
||||||
- `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>`
|
|
||||||
instead of `Result<_, http::Error>`
|
|
||||||
|
|
||||||
- `Application` renamed to a `App`
|
|
||||||
|
|
||||||
- `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev`
|
|
105
README.md
105
README.md
@ -1,105 +0,0 @@
|
|||||||
<div align="center">
|
|
||||||
<h1>Actix Web</h1>
|
|
||||||
<p>
|
|
||||||
<strong>Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust</strong>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web)
|
|
||||||
[](https://docs.rs/actix-web/4.0.0-rc.1)
|
|
||||||

|
|
||||||

|
|
||||||
[](https://deps.rs/crate/actix-web/4.0.0-rc.1)
|
|
||||||
<br />
|
|
||||||
[](https://github.com/actix/actix-web/actions/workflows/ci.yml)
|
|
||||||
[](https://codecov.io/gh/actix/actix-web)
|
|
||||||

|
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
|
||||||
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Supports _HTTP/1.x_ and _HTTP/2_
|
|
||||||
- Streaming and pipelining
|
|
||||||
- Powerful [request routing](https://actix.rs/docs/url-dispatch/) with optional macros
|
|
||||||
- Full [Tokio](https://tokio.rs) compatibility
|
|
||||||
- Keep-alive and slow requests handling
|
|
||||||
- Client/server [WebSockets](https://actix.rs/docs/websockets/) support
|
|
||||||
- Transparent content compression/decompression (br, gzip, deflate, zstd)
|
|
||||||
- Multipart streams
|
|
||||||
- Static assets
|
|
||||||
- SSL support using OpenSSL or Rustls
|
|
||||||
- Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
|
|
||||||
- Includes an async [HTTP client](https://docs.rs/awc/)
|
|
||||||
- Runs on stable Rust 1.54+
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
- [Website & User Guide](https://actix.rs)
|
|
||||||
- [Examples Repository](https://github.com/actix/examples)
|
|
||||||
- [API Documentation](https://docs.rs/actix-web)
|
|
||||||
- [API Documentation (master branch)](https://actix.rs/actix-web/actix_web)
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
actix-web = "4.0.0-rc.1"
|
|
||||||
```
|
|
||||||
|
|
||||||
Code:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use actix_web::{get, web, App, HttpServer, Responder};
|
|
||||||
|
|
||||||
#[get("/{id}/{name}/index.html")]
|
|
||||||
async fn index(params: web::Path<(u32, String)>) -> impl Responder {
|
|
||||||
let (id, name) = params.into_inner();
|
|
||||||
format!("Hello {}! id:{}", name, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::main] // or #[tokio::main]
|
|
||||||
async fn main() -> std::io::Result<()> {
|
|
||||||
HttpServer::new(|| App::new().service(index))
|
|
||||||
.bind(("127.0.0.1", 8080))?
|
|
||||||
.run()
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### More examples
|
|
||||||
|
|
||||||
- [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/)
|
|
||||||
- [Application State](https://github.com/actix/examples/tree/master/basics/state/)
|
|
||||||
- [JSON Handling](https://github.com/actix/examples/tree/master/json/json/)
|
|
||||||
- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/)
|
|
||||||
- [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/)
|
|
||||||
- [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/)
|
|
||||||
- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/)
|
|
||||||
- [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/)
|
|
||||||
- [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/)
|
|
||||||
- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/)
|
|
||||||
- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/)
|
|
||||||
- [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/)
|
|
||||||
|
|
||||||
You may consider checking out [this directory](https://github.com/actix/examples/tree/master/) for more examples.
|
|
||||||
|
|
||||||
## Benchmarks
|
|
||||||
|
|
||||||
One of the fastest web frameworks available according to the [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r20&test=composite).
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This project is licensed under either of the following licenses, at your option:
|
|
||||||
|
|
||||||
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0])
|
|
||||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT])
|
|
||||||
|
|
||||||
## Code of Conduct
|
|
||||||
|
|
||||||
Contribution to the actix-web repo is organized under the terms of the Contributor Covenant.
|
|
||||||
The Actix team promises to intervene to uphold that code of conduct.
|
|
@ -3,6 +3,20 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.1 - 2022-06-11
|
||||||
|
- Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021]
|
||||||
|
- Update `tokio-uring` dependency to `0.3`.
|
||||||
|
- Audio files now use `Content-Disposition: inline` instead of `attachment`. [#2645]
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency.
|
||||||
|
|
||||||
|
[#2021]: https://github.com/actix/actix-web/pull/2021
|
||||||
|
[#2645]: https://github.com/actix/actix-web/pull/2645
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0 - 2022-02-25
|
||||||
|
- No significant changes since `0.6.0-beta.16`.
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.16 - 2022-01-31
|
## 0.6.0-beta.16 - 2022-01-31
|
||||||
- No significant changes since `0.6.0-beta.15`.
|
- No significant changes since `0.6.0-beta.15`.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.6.0-beta.16"
|
version = "0.6.1"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"fakeshadow <24548779@qq.com>",
|
"fakeshadow <24548779@qq.com>",
|
||||||
@ -22,10 +22,10 @@ path = "src/lib.rs"
|
|||||||
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
|
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-http = "3.0.0-rc.1"
|
actix-http = "3"
|
||||||
actix-service = "2"
|
actix-service = "2"
|
||||||
actix-utils = "3"
|
actix-utils = "3"
|
||||||
actix-web = { version = "4.0.0-rc.1", default-features = false }
|
actix-web = { version = "4", default-features = false }
|
||||||
|
|
||||||
askama_escape = "0.10"
|
askama_escape = "0.10"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
@ -39,10 +39,13 @@ 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"
|
||||||
|
|
||||||
tokio-uring = { version = "0.2", optional = true, features = ["bytes"] }
|
# experimental-io-uring
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
tokio-uring = { version = "0.3", optional = true, features = ["bytes"] }
|
||||||
|
actix-server = { version = "2.1", optional = true } # ensure matching tokio-uring versions
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.7"
|
||||||
actix-test = "0.1.0-beta.12"
|
actix-test = "0.1.0-beta.13"
|
||||||
actix-web = "4.0.0-rc.1"
|
actix-web = "4"
|
||||||
tempfile = "3.2"
|
tempfile = "3.2"
|
||||||
|
@ -3,16 +3,16 @@
|
|||||||
> Static file serving for Actix Web
|
> Static file serving for Actix Web
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://docs.rs/actix-files/0.6.0-beta.16)
|
[](https://docs.rs/actix-files/0.6.1)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-files/0.6.0-beta.16)
|
[](https://deps.rs/crate/actix-files/0.6.1)
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
||||||
- [API Documentation](https://docs.rs/actix-files/)
|
- [API Documentation](https://docs.rs/actix-files)
|
||||||
- [Example Project](https://github.com/actix/examples/tree/master/basics/static_index)
|
- [Example Project](https://github.com/actix/examples/tree/master/basics/static-files)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.54
|
- Minimum Supported Rust Version (MSRV): 1.54
|
||||||
|
@ -81,7 +81,7 @@ async fn chunked_read_file_callback(
|
|||||||
) -> Result<(File, Bytes), Error> {
|
) -> Result<(File, Bytes), Error> {
|
||||||
use io::{Read as _, Seek as _};
|
use io::{Read as _, Seek as _};
|
||||||
|
|
||||||
let res = actix_web::rt::task::spawn_blocking(move || {
|
let res = actix_web::web::block(move || {
|
||||||
let mut buf = Vec::with_capacity(max_bytes);
|
let mut buf = Vec::with_capacity(max_bytes);
|
||||||
|
|
||||||
file.seek(io::SeekFrom::Start(offset))?;
|
file.seek(io::SeekFrom::Start(offset))?;
|
||||||
@ -94,8 +94,7 @@ async fn chunked_read_file_callback(
|
|||||||
Ok((file, Bytes::from(buf)))
|
Ok((file, Bytes::from(buf)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await
|
.await??;
|
||||||
.map_err(|_| actix_web::error::BlockingError)??;
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ pub(crate) fn directory_listing(
|
|||||||
if dir.is_visible(&entry) {
|
if dir.is_visible(&entry) {
|
||||||
let entry = entry.unwrap();
|
let entry = entry.unwrap();
|
||||||
let p = match entry.path().strip_prefix(&dir.path) {
|
let p = match entry.path().strip_prefix(&dir.path) {
|
||||||
Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace("\\", "/"),
|
Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace('\\', "/"),
|
||||||
Ok(p) => base.join(p).to_string_lossy().into_owned(),
|
Ok(p) => base.join(p).to_string_lossy().into_owned(),
|
||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
|
@ -364,20 +364,43 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_named_file_status_code_text() {
|
async fn status_code_customize_same_output() {
|
||||||
let mut file = NamedFile::open_async("Cargo.toml")
|
let file1 = NamedFile::open_async("Cargo.toml")
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.set_status_code(StatusCode::NOT_FOUND);
|
.set_status_code(StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let file2 = NamedFile::open_async("Cargo.toml")
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.customize()
|
||||||
|
.with_status(StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let req = TestRequest::default().to_http_request();
|
||||||
|
let res1 = file1.respond_to(&req);
|
||||||
|
let res2 = file2.respond_to(&req);
|
||||||
|
|
||||||
|
assert_eq!(res1.status(), StatusCode::NOT_FOUND);
|
||||||
|
assert_eq!(res2.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_named_file_status_code_text() {
|
||||||
|
let mut file = NamedFile::open_async("Cargo.toml").await.unwrap();
|
||||||
|
|
||||||
{
|
{
|
||||||
file.file();
|
file.file();
|
||||||
let _f: &File = &file;
|
let _f: &File = &file;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let _f: &mut File = &mut file;
|
let _f: &mut File = &mut file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let file = file.customize().with_status(StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let resp = file.respond_to(&req);
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -23,6 +23,7 @@ use actix_web::{
|
|||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
use mime::Mime;
|
||||||
use mime_guess::from_path;
|
use mime_guess::from_path;
|
||||||
|
|
||||||
use crate::{encoding::equiv_utf8_text, range::HttpRange};
|
use crate::{encoding::equiv_utf8_text, range::HttpRange};
|
||||||
@ -76,8 +77,8 @@ pub struct NamedFile {
|
|||||||
pub(crate) md: Metadata,
|
pub(crate) md: Metadata,
|
||||||
pub(crate) flags: Flags,
|
pub(crate) flags: Flags,
|
||||||
pub(crate) status_code: StatusCode,
|
pub(crate) status_code: StatusCode,
|
||||||
pub(crate) content_type: mime::Mime,
|
pub(crate) content_type: Mime,
|
||||||
pub(crate) content_disposition: header::ContentDisposition,
|
pub(crate) content_disposition: ContentDisposition,
|
||||||
pub(crate) encoding: Option<ContentEncoding>,
|
pub(crate) encoding: Option<ContentEncoding>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,18 +97,18 @@ impl NamedFile {
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
|
/// use std::{
|
||||||
|
/// io::{self, Write as _},
|
||||||
|
/// env,
|
||||||
|
/// fs::File
|
||||||
|
/// };
|
||||||
/// use actix_files::NamedFile;
|
/// use actix_files::NamedFile;
|
||||||
/// use std::io::{self, Write};
|
|
||||||
/// use std::env;
|
|
||||||
/// use std::fs::File;
|
|
||||||
///
|
///
|
||||||
/// fn main() -> io::Result<()> {
|
|
||||||
/// let mut file = File::create("foo.txt")?;
|
/// let mut file = File::create("foo.txt")?;
|
||||||
/// file.write_all(b"Hello, world!")?;
|
/// file.write_all(b"Hello, world!")?;
|
||||||
/// let named_file = NamedFile::from_file(file, "bar.txt")?;
|
/// let named_file = NamedFile::from_file(file, "bar.txt")?;
|
||||||
/// # std::fs::remove_file("foo.txt");
|
/// # std::fs::remove_file("foo.txt");
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn from_file<P: AsRef<Path>>(file: File, path: P) -> io::Result<NamedFile> {
|
pub fn from_file<P: AsRef<Path>>(file: File, path: P) -> io::Result<NamedFile> {
|
||||||
let path = path.as_ref().to_path_buf();
|
let path = path.as_ref().to_path_buf();
|
||||||
@ -128,7 +129,7 @@ impl NamedFile {
|
|||||||
let ct = from_path(&path).first_or_octet_stream();
|
let ct = from_path(&path).first_or_octet_stream();
|
||||||
|
|
||||||
let disposition = match ct.type_() {
|
let disposition = match ct.type_() {
|
||||||
mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
|
mime::IMAGE | mime::TEXT | mime::AUDIO | mime::VIDEO => DispositionType::Inline,
|
||||||
mime::APPLICATION => match ct.subtype() {
|
mime::APPLICATION => match ct.subtype() {
|
||||||
mime::JAVASCRIPT | mime::JSON => DispositionType::Inline,
|
mime::JAVASCRIPT | mime::JSON => DispositionType::Inline,
|
||||||
name if name == "wasm" => DispositionType::Inline,
|
name if name == "wasm" => DispositionType::Inline,
|
||||||
@ -209,11 +210,10 @@ impl NamedFile {
|
|||||||
Self::from_file(file, path)
|
Self::from_file(file, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(rustdoc::broken_intra_doc_links)]
|
|
||||||
/// Attempts to open a file asynchronously in read-only mode.
|
/// Attempts to open a file asynchronously in read-only mode.
|
||||||
///
|
///
|
||||||
/// When the `experimental-io-uring` crate feature is enabled, this will be async.
|
/// When the `experimental-io-uring` crate feature is enabled, this will be async. Otherwise, it
|
||||||
/// Otherwise, it will be just like [`open`][Self::open].
|
/// will behave just like `open`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
@ -238,13 +238,13 @@ impl NamedFile {
|
|||||||
Self::from_file(file, path)
|
Self::from_file(file, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns reference to the underlying `File` object.
|
/// Returns reference to the underlying file object.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn file(&self) -> &File {
|
pub fn file(&self) -> &File {
|
||||||
&self.file
|
&self.file
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the path of this file.
|
/// Returns the filesystem path to this file.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
@ -262,16 +262,53 @@ impl NamedFile {
|
|||||||
self.path.as_path()
|
self.path.as_path()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set response **Status Code**
|
/// Returns the time the file was last modified.
|
||||||
|
///
|
||||||
|
/// Returns `None` only on unsupported platforms; see [`std::fs::Metadata::modified()`].
|
||||||
|
/// Therefore, it is usually safe to unwrap this.
|
||||||
|
#[inline]
|
||||||
|
pub fn modified(&self) -> Option<SystemTime> {
|
||||||
|
self.modified
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the filesystem metadata associated with this file.
|
||||||
|
#[inline]
|
||||||
|
pub fn metadata(&self) -> &Metadata {
|
||||||
|
&self.md
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `Content-Type` header that will be used when serving this file.
|
||||||
|
#[inline]
|
||||||
|
pub fn content_type(&self) -> &Mime {
|
||||||
|
&self.content_type
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `Content-Disposition` that will be used when serving this file.
|
||||||
|
#[inline]
|
||||||
|
pub fn content_disposition(&self) -> &ContentDisposition {
|
||||||
|
&self.content_disposition
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `Content-Encoding` that will be used when serving this file.
|
||||||
|
///
|
||||||
|
/// A return value of `None` indicates that the content is not already using a compressed
|
||||||
|
/// representation and may be subject to compression downstream.
|
||||||
|
#[inline]
|
||||||
|
pub fn content_encoding(&self) -> Option<ContentEncoding> {
|
||||||
|
self.encoding
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set response status code.
|
||||||
|
#[deprecated(since = "0.7.0", note = "Prefer `Responder::customize()`.")]
|
||||||
pub fn set_status_code(mut self, status: StatusCode) -> Self {
|
pub fn set_status_code(mut self, status: StatusCode) -> Self {
|
||||||
self.status_code = status;
|
self.status_code = status;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the MIME Content-Type for serving this file. By default the Content-Type is inferred
|
/// Sets the `Content-Type` header that will be used when serving this file. By default the
|
||||||
/// from the filename extension.
|
/// `Content-Type` is inferred from the filename extension.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self {
|
pub fn set_content_type(mut self, mime_type: Mime) -> Self {
|
||||||
self.content_type = mime_type;
|
self.content_type = mime_type;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -284,15 +321,15 @@ impl NamedFile {
|
|||||||
/// filename is taken from the path provided in the `open` method after converting it to UTF-8
|
/// filename is taken from the path provided in the `open` method after converting it to UTF-8
|
||||||
/// (using `to_string_lossy`).
|
/// (using `to_string_lossy`).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self {
|
pub fn set_content_disposition(mut self, cd: ContentDisposition) -> Self {
|
||||||
self.content_disposition = cd;
|
self.content_disposition = cd;
|
||||||
self.flags.insert(Flags::CONTENT_DISPOSITION);
|
self.flags.insert(Flags::CONTENT_DISPOSITION);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disable `Content-Disposition` header.
|
/// Disables `Content-Disposition` header.
|
||||||
///
|
///
|
||||||
/// By default Content-Disposition` header is enabled.
|
/// By default, the `Content-Disposition` header is sent.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn disable_content_disposition(mut self) -> Self {
|
pub fn disable_content_disposition(mut self) -> Self {
|
||||||
self.flags.remove(Flags::CONTENT_DISPOSITION);
|
self.flags.remove(Flags::CONTENT_DISPOSITION);
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency.
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.13 - 2022-02-16
|
||||||
|
- No significant changes since `3.0.0-beta.12`.
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.12 - 2022-01-31
|
## 3.0.0-beta.12 - 2022-01-31
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-http-test"
|
name = "actix-http-test"
|
||||||
version = "3.0.0-beta.12"
|
version = "3.0.0-beta.13"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Various helpers for Actix applications to use during testing"
|
description = "Various helpers for Actix applications to use during testing"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
@ -29,13 +29,13 @@ default = []
|
|||||||
openssl = ["tls-openssl", "awc/openssl"]
|
openssl = ["tls-openssl", "awc/openssl"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "2.0.0"
|
actix-service = "2"
|
||||||
actix-codec = "0.4.1"
|
actix-codec = "0.5"
|
||||||
actix-tls = "3.0.0"
|
actix-tls = "3"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3"
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-server = "2"
|
actix-server = "2"
|
||||||
awc = { version = "3.0.0-beta.20", default-features = false }
|
awc = { version = "3", default-features = false }
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
|||||||
tokio = { version = "1.8.4", features = ["sync"] }
|
tokio = { version = "1.8.4", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-web = { version = "4.0.0-rc.1", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4", default-features = false, features = ["cookies"] }
|
||||||
actix-http = "3.0.0-rc.1"
|
actix-http = "3"
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
> Various helpers for Actix applications to use during testing.
|
> Various helpers for Actix applications to use during testing.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http-test)
|
[](https://crates.io/crates/actix-http-test)
|
||||||
[](https://docs.rs/actix-http-test/3.0.0-beta.12)
|
[](https://docs.rs/actix-http-test/3.0.0-beta.13)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|

|
||||||

|

|
||||||
<br>
|
<br>
|
||||||
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.12)
|
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.13)
|
||||||
[](https://crates.io/crates/actix-http-test)
|
[](https://crates.io/crates/actix-http-test)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -3,6 +3,351 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.1.0 - 2022-06-11
|
||||||
|
### Changed
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Revert broken fix in [#2624] that caused erroneous 500 error responses. Temporarily re-introduces [#2357] bug. [#2779]
|
||||||
|
|
||||||
|
[#2357]: https://github.com/actix/actix-web/issues/2357
|
||||||
|
[#2624]: https://github.com/actix/actix-web/issues/2624
|
||||||
|
[#2779]: https://github.com/actix/actix-web/issues/2779
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.4 - 2022-03-09
|
||||||
|
### Fixed
|
||||||
|
- Document on docs.rs with `ws` feature enabled.
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.3 - 2022-03-08
|
||||||
|
### Fixed
|
||||||
|
- Allow spaces between header name and colon when parsing responses. [#2684]
|
||||||
|
|
||||||
|
[#2684]: https://github.com/actix/actix-web/issues/2684
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.2 - 2022-03-05
|
||||||
|
### Fixed
|
||||||
|
- Fix encoding camel-case header names with more than one hyphen. [#2683]
|
||||||
|
|
||||||
|
[#2683]: https://github.com/actix/actix-web/issues/2683
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.1 - 2022-03-04
|
||||||
|
- Fix panic in H1 dispatcher when pipelining is used with keep-alive. [#2678]
|
||||||
|
|
||||||
|
[#2678]: https://github.com/actix/actix-web/issues/2678
|
||||||
|
|
||||||
|
## 3.0.0 - 2022-02-25
|
||||||
|
### Dependencies
|
||||||
|
- Updated `actix-*` to Tokio v1-based versions. [#1813]
|
||||||
|
- Updated `bytes` to `1.0`. [#1813]
|
||||||
|
- Updated `h2` to `0.3`. [#1813]
|
||||||
|
- Updated `rustls` to `0.20.0`. [#2414]
|
||||||
|
- Updated `language-tags` to `0.3`.
|
||||||
|
- Updated `tokio` to `1`.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Crate Features:
|
||||||
|
- `ws`; disabled by default. [#2618]
|
||||||
|
- `http2`; disabled by default. [#2618]
|
||||||
|
- `compress-brotli`; disabled by default. [#2618]
|
||||||
|
- `compress-gzip`; disabled by default. [#2618]
|
||||||
|
- `compress-zstd`; disabled by default. [#2618]
|
||||||
|
- Functions:
|
||||||
|
- `body::to_bytes` for async collecting message body into Bytes. [#2158]
|
||||||
|
- Traits:
|
||||||
|
- `TryIntoHeaderPair`; allows using typed and untyped headers in the same methods. [#1869]
|
||||||
|
- Types:
|
||||||
|
- `body::BoxBody`; a boxed message body with boxed errors. [#2183]
|
||||||
|
- `body::EitherBody` enum. [#2468]
|
||||||
|
- `body::None` struct. [#2468]
|
||||||
|
- Re-export `http` crate's `Error` type as `error::HttpError`. [#2171]
|
||||||
|
- Variants:
|
||||||
|
- `ContentEncoding::Zstd` along with . [#2244]
|
||||||
|
- `Protocol::Http3` for future compatibility and also mark `#[non_exhaustive]`. [00ba8d55]
|
||||||
|
- Methods:
|
||||||
|
- `ContentEncoding::to_header_value()`. [#2501]
|
||||||
|
- `header::QualityItem::{max, min}()`. [#2486]
|
||||||
|
- `header::QualityItem::zero()` that uses `Quality::ZERO`. [#2501]
|
||||||
|
- `HeaderMap::drain()` as an efficient draining iterator. [#1964]
|
||||||
|
- `HeaderMap::len_keys()` has the behavior of the old `len` method. [#1964]
|
||||||
|
- `MessageBody::boxed` trait method for wrapping boxing types efficiently. [#2520]
|
||||||
|
- `MessageBody::try_into_bytes` trait method, with default implementation, for optimizations on body types that complete in exactly one poll. [#2522]
|
||||||
|
- `Request::conn_data()`. [#2491]
|
||||||
|
- `Request::take_conn_data()`. [#2491]
|
||||||
|
- `Request::take_req_data()`. [#2487]
|
||||||
|
- `Response::{ok, bad_request, not_found, internal_server_error}()`. [#2159]
|
||||||
|
- `Response::into_body()` that consumes response and returns body type. [#2201]
|
||||||
|
- `Response::map_into_boxed_body()`. [#2468]
|
||||||
|
- `ResponseBuilder::append_header()` method which allows using typed and untyped headers. [#1869]
|
||||||
|
- `ResponseBuilder::insert_header()` method which allows using typed and untyped headers. [#1869]
|
||||||
|
- `ResponseHead::set_camel_case_headers()`. [#2587]
|
||||||
|
- `TestRequest::insert_header()` method which allows using typed and untyped headers. [#1869]
|
||||||
|
- Implementations:
|
||||||
|
- Implement `Clone for ws::HandshakeError`. [#2468]
|
||||||
|
- Implement `Clone` for `body::AnyBody<S> where S: Clone`. [#2448]
|
||||||
|
- Implement `Clone` for `RequestHead`. [#2487]
|
||||||
|
- Implement `Clone` for `ResponseHead`. [#2585]
|
||||||
|
- Implement `Copy` for `QualityItem<T> where T: Copy`. [#2501]
|
||||||
|
- Implement `Default` for `ContentEncoding`. [#1912]
|
||||||
|
- Implement `Default` for `HttpServiceBuilder`. [#2611]
|
||||||
|
- Implement `Default` for `KeepAlive`. [#2611]
|
||||||
|
- Implement `Default` for `Response`. [#2201]
|
||||||
|
- Implement `Default` for `ws::Codec`. [#1920]
|
||||||
|
- Implement `Display` for `header::Quality`. [#2486]
|
||||||
|
- Implement `Eq` for `header::ContentEncoding`. [#2501]
|
||||||
|
- Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470]
|
||||||
|
- Implement `From<Duration>` for `KeepAlive`. [#2611]
|
||||||
|
- Implement `From<Option<Duration>>` for `KeepAlive`. [#2611]
|
||||||
|
- Implement `From<Vec<u8>>` for `Response<Vec<u8>>`. [#2625]
|
||||||
|
- Implement `FromStr` for `ContentEncoding`. [#1912]
|
||||||
|
- Implement `Header` for `ContentEncoding`. [#1912]
|
||||||
|
- Implement `IntoHeaderValue` for `ContentEncoding`. [#1912]
|
||||||
|
- Implement `IntoIterator` for `HeaderMap`. [#1964]
|
||||||
|
- Implement `MessageBody` for `bytestring::ByteString`. [#2468]
|
||||||
|
- Implement `MessageBody` for `Pin<Box<T>> where T: MessageBody`. [#2152]
|
||||||
|
- Misc:
|
||||||
|
- Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171]
|
||||||
|
- Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171]
|
||||||
|
- `Quality::ZERO` associated constant equivalent to `q=0`. [#2501]
|
||||||
|
- `header::Quality::{MAX, MIN}` associated constants equivalent to `q=1` and `q=0.001`, respectively. [#2486]
|
||||||
|
- Timeout for canceling HTTP/2 server side connection handshake. Configurable with `ServiceConfig::client_timeout`; defaults to 5 seconds. [#2483]
|
||||||
|
- `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Traits:
|
||||||
|
- Rename `IntoHeaderValue => TryIntoHeaderValue`. [#2510]
|
||||||
|
- `MessageBody` now has an associated `Error` type. [#2183]
|
||||||
|
- Types:
|
||||||
|
- `Protocol` enum is now marked `#[non_exhaustive]`.
|
||||||
|
- `error::DispatcherError` enum is now marked `#[non_exhaustive]`. [#2624]
|
||||||
|
- `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377]
|
||||||
|
- Error enums are marked `#[non_exhaustive]`. [#2161]
|
||||||
|
- Rename `PayloadStream` to `BoxedPayloadStream`. [#2545]
|
||||||
|
- The body type parameter of `Response` no longer has a default. [#2152]
|
||||||
|
- Enum Variants:
|
||||||
|
- Rename `ContentEncoding::{Br => Brotli}`. [#2501]
|
||||||
|
- `Payload` inner fields are now named. [#2545]
|
||||||
|
- `ws::Message::Text` now contains a `bytestring::ByteString`. [#1864]
|
||||||
|
- Methods:
|
||||||
|
- Rename `ServiceConfig::{client_timer_expire => client_request_deadline}`. [#2611]
|
||||||
|
- Rename `ServiceConfig::{client_disconnect_timer => client_disconnect_deadline}`. [#2611]
|
||||||
|
- Rename `h1::Codec::{keepalive => keep_alive}`. [#2611]
|
||||||
|
- Rename `h1::Codec::{keepalive_enabled => keep_alive_enabled}`. [#2611]
|
||||||
|
- Rename `h1::ClientCodec::{keepalive => keep_alive}`. [#2611]
|
||||||
|
- Rename `h1::ClientPayloadCodec::{keepalive => keep_alive}`. [#2611]
|
||||||
|
- Rename `header::EntityTag::{weak => new_weak, strong => new_strong}`. [#2565]
|
||||||
|
- Rename `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894]
|
||||||
|
- Deadline methods in `ServiceConfig` now return `std::time::Instant`s instead of Tokio's wrapper type. [#2611]
|
||||||
|
- Places in `Response` where `ResponseBody<B>` was received or returned now simply use `B`. [#2201]
|
||||||
|
- `encoding::Encoder::response` now returns `AnyBody<Encoder<B>>`. [#2448]
|
||||||
|
- `Extensions::insert` returns replaced item. [#1904]
|
||||||
|
- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527]
|
||||||
|
- `HeaderMap::insert` now returns iterator of removed values. [#1964]
|
||||||
|
- `HeaderMap::len` now returns number of values instead of number of keys. [#1964]
|
||||||
|
- `HeaderMap::remove` now returns iterator of removed values. [#1964]
|
||||||
|
- `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468]
|
||||||
|
- `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed `mime` types. [#1894]
|
||||||
|
- `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468]
|
||||||
|
- `ResponseBuilder::json` now takes `impl Serialize`. [#2052]
|
||||||
|
- `ResponseBuilder::message_body` now returns a `Result`. [#2201]∑
|
||||||
|
- `ServiceConfig::keep_alive` now returns a `KeepAlive`. [#2611]
|
||||||
|
- `ws::hash_key` now returns array. [#2035]
|
||||||
|
- Trait Implementations:
|
||||||
|
- Implementation of `Stream` for `Payload` no longer requires the `Stream` variant be `Unpin`. [#2545]
|
||||||
|
- Implementation of `Future` for `h1::SendResponse` no longer requires the body type be `Unpin`. [#2545]
|
||||||
|
- Implementation of `Stream` for `encoding::Decoder` no longer requires the stream type be `Unpin`. [#2545]
|
||||||
|
- Implementation of `From` for error types now return a `Response<BoxBody>`. [#2468]
|
||||||
|
- Misc:
|
||||||
|
- `header` module is now public. [#2171]
|
||||||
|
- `uri` module is now public. [#2171]
|
||||||
|
- Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487]
|
||||||
|
- All error trait bounds in server service builders have changed from `Into<Error>` to `Into<Response<BoxBody>>`. [#2253]
|
||||||
|
- All error trait bounds in message body and stream impls changed from `Into<Error>` to `Into<Box<dyn std::error::Error>>`. [#2253]
|
||||||
|
- Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467]
|
||||||
|
- Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491]
|
||||||
|
- Brotli (de)compression support is now provided by the `brotli` crate. [#2538]
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- A `Vary` header is now correctly sent along with compressed content. [#2501]
|
||||||
|
- HTTP/1.1 dispatcher correctly uses client request timeout. [#2611]
|
||||||
|
- Fixed issue where handlers that took payload but then dropped without reading it to EOF it would cause keep-alive connections to become stuck. [#2624]
|
||||||
|
- `ContentEncoding`'s `Identity` variant can now be parsed from a string. [#2501]
|
||||||
|
- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuration parameter. [#2226]
|
||||||
|
- Remove unnecessary `Into<Error>` bound on `Encoder` body types. [#2375]
|
||||||
|
- Remove unnecessary `Unpin` bound on `ResponseBuilder::streaming`. [#2253]
|
||||||
|
- `BodyStream` and `SizedStream` are no longer restricted to `Unpin` types. [#2152]
|
||||||
|
- Fixed slice creation pointing to potential uninitialized data on h1 encoder. [#2364]
|
||||||
|
- Fixed quality parse error in Accept-Encoding header. [#2344]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Crate Features:
|
||||||
|
- `compress` feature. [#2065]
|
||||||
|
- `cookies` feature. [#2065]
|
||||||
|
- `trust-dns` feature. [#2425]
|
||||||
|
- `actors` optional feature and trait implementation for `actix` types. [#1969]
|
||||||
|
- Functions:
|
||||||
|
- `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486]
|
||||||
|
- Types:
|
||||||
|
- `body::Body`; replaced with `EitherBody` and `BoxBody`. [#2468]
|
||||||
|
- `body::ResponseBody`. [#2446]
|
||||||
|
- `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. Due to the removal of this type from `tokio-openssl` crate. OpenSSL handshake error now returns `ConnectError::SslError`. [#1813]
|
||||||
|
- `error::Canceled` re-export. [#1994]
|
||||||
|
- `error::Result` type alias. [#2201]
|
||||||
|
- `error::BlockingError` [#2660]
|
||||||
|
- `InternalError` and all the error types it constructed were moved up to `actix-web`. [#2215]
|
||||||
|
- Typed HTTP headers; they have moved up to `actix-web`. [2094]
|
||||||
|
- Re-export of `http` crate's `HeaderMap` types in addition to ours. [#2171]
|
||||||
|
- Enum Variants:
|
||||||
|
- `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446]
|
||||||
|
- `ContentEncoding::Auto`. [#2501]
|
||||||
|
- `EncoderError::Boxed`. [#2446]
|
||||||
|
- Methods:
|
||||||
|
- `ContentEncoding::is_compression()`. [#2501]
|
||||||
|
- `h1::Payload::readany()`. [#2545]
|
||||||
|
- `HttpMessage::cookie[s]()` trait methods. [#2065]
|
||||||
|
- `HttpServiceBuilder::new()`; use `default` instead. [#2611]
|
||||||
|
- `on_connect` (previously deprecated) methods have been removed; use `on_connect_ext`. [#1857]
|
||||||
|
- `Response::build_from()`. [#2159]
|
||||||
|
- `Response::error()` [#2205]
|
||||||
|
- `Response::take_body()` and old `Response::into_body()` method that casted body type. [#2201]
|
||||||
|
- `Response`'s status code builders. [#2159]
|
||||||
|
- `ResponseBuilder::{if_true, if_some}()` (previously deprecated). [#2148]
|
||||||
|
- `ResponseBuilder::{set, set_header}()`; use `ResponseBuilder::insert_header()`. [#1869]
|
||||||
|
- `ResponseBuilder::extensions[_mut]()`. [#2585]
|
||||||
|
- `ResponseBuilder::header()`; use `ResponseBuilder::append_header()`. [#1869]
|
||||||
|
- `ResponseBuilder::json()`. [#2148]
|
||||||
|
- `ResponseBuilder::json2()`. [#1903]
|
||||||
|
- `ResponseBuilder::streaming()`. [#2468]
|
||||||
|
- `ResponseHead::extensions[_mut]()`. [#2585]
|
||||||
|
- `ServiceConfig::{client_timer, keep_alive_timer}()`. [#2611]
|
||||||
|
- `TestRequest::with_hdr()`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
|
- `TestRequest::with_header()`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
|
- Trait implementations:
|
||||||
|
- Implementation of `Copy` for `ws::Codec`. [#1920]
|
||||||
|
- Implementation of `From<Option<usize>> for KeepAlive`; use `Duration`s instead. [#2611]
|
||||||
|
- Implementation of `From<serde_json::Value>` for `Body`. [#2148]
|
||||||
|
- Implementation of `From<usize> for KeepAlive`; use `Duration`s instead. [#2611]
|
||||||
|
- Implementation of `Future` for `Response`. [#2201]
|
||||||
|
- Implementation of `Future` for `ResponseBuilder`. [#2468]
|
||||||
|
- Implementation of `Into<Error>` for `Response<Body>`. [#2215]
|
||||||
|
- Implementation of `Into<Error>` for `ResponseBuilder`. [#2215]
|
||||||
|
- Implementation of `ResponseError` for `actix_utils::timeout::TimeoutError`. [#2127]
|
||||||
|
- Implementation of `ResponseError` for `CookieParseError`. [#2065]
|
||||||
|
- Implementation of `TryFrom<u16>` for `header::Quality`. [#2486]
|
||||||
|
- Misc:
|
||||||
|
- `http` module; most everything it contained is exported at the crate root. [#2488]
|
||||||
|
- `cookies` module (re-export). [#2065]
|
||||||
|
- `client` module. Connector types now live in `awc`. [#2425]
|
||||||
|
- `error` field from `Response`. [#2205]
|
||||||
|
- `downcast` and `downcast_get_type_id` macros. [#2291]
|
||||||
|
- Down-casting for `MessageBody` types; use standard `Any` trait. [#2183]
|
||||||
|
|
||||||
|
|
||||||
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
|
[#1845]: https://github.com/actix/actix-web/pull/1845
|
||||||
|
[#1857]: https://github.com/actix/actix-web/pull/1857
|
||||||
|
[#1864]: https://github.com/actix/actix-web/pull/1864
|
||||||
|
[#1869]: https://github.com/actix/actix-web/pull/1869
|
||||||
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
[#1894]: https://github.com/actix/actix-web/pull/1894
|
||||||
|
[#1903]: https://github.com/actix/actix-web/pull/1903
|
||||||
|
[#1904]: https://github.com/actix/actix-web/pull/1904
|
||||||
|
[#1912]: https://github.com/actix/actix-web/pull/1912
|
||||||
|
[#1920]: https://github.com/actix/actix-web/pull/1920
|
||||||
|
[#1964]: https://github.com/actix/actix-web/pull/1964
|
||||||
|
[#1969]: https://github.com/actix/actix-web/pull/1969
|
||||||
|
[#1981]: https://github.com/actix/actix-web/pull/1981
|
||||||
|
[#1994]: https://github.com/actix/actix-web/pull/1994
|
||||||
|
[#2035]: https://github.com/actix/actix-web/pull/2035
|
||||||
|
[#2052]: https://github.com/actix/actix-web/pull/2052
|
||||||
|
[#2065]: https://github.com/actix/actix-web/pull/2065
|
||||||
|
[#2094]: https://github.com/actix/actix-web/pull/2094
|
||||||
|
[#2127]: https://github.com/actix/actix-web/pull/2127
|
||||||
|
[#2148]: https://github.com/actix/actix-web/pull/2148
|
||||||
|
[#2152]: https://github.com/actix/actix-web/pull/2152
|
||||||
|
[#2158]: https://github.com/actix/actix-web/pull/2158
|
||||||
|
[#2159]: https://github.com/actix/actix-web/pull/2159
|
||||||
|
[#2161]: https://github.com/actix/actix-web/pull/2161
|
||||||
|
[#2171]: https://github.com/actix/actix-web/pull/2171
|
||||||
|
[#2183]: https://github.com/actix/actix-web/pull/2183
|
||||||
|
[#2196]: https://github.com/actix/actix-web/pull/2196
|
||||||
|
[#2201]: https://github.com/actix/actix-web/pull/2201
|
||||||
|
[#2205]: https://github.com/actix/actix-web/pull/2205
|
||||||
|
[#2215]: https://github.com/actix/actix-web/pull/2215
|
||||||
|
[#2244]: https://github.com/actix/actix-web/pull/2244
|
||||||
|
[#2250]: https://github.com/actix/actix-web/pull/2250
|
||||||
|
[#2253]: https://github.com/actix/actix-web/pull/2253
|
||||||
|
[#2291]: https://github.com/actix/actix-web/pull/2291
|
||||||
|
[#2344]: https://github.com/actix/actix-web/pull/2344
|
||||||
|
[#2364]: https://github.com/actix/actix-web/pull/2364
|
||||||
|
[#2375]: https://github.com/actix/actix-web/pull/2375
|
||||||
|
[#2377]: https://github.com/actix/actix-web/pull/2377
|
||||||
|
[#2414]: https://github.com/actix/actix-web/pull/2414
|
||||||
|
[#2425]: https://github.com/actix/actix-web/pull/2425
|
||||||
|
[#2442]: https://github.com/actix/actix-web/pull/2442
|
||||||
|
[#2446]: https://github.com/actix/actix-web/pull/2446
|
||||||
|
[#2448]: https://github.com/actix/actix-web/pull/2448
|
||||||
|
[#2456]: https://github.com/actix/actix-web/pull/2456
|
||||||
|
[#2467]: https://github.com/actix/actix-web/pull/2467
|
||||||
|
[#2468]: https://github.com/actix/actix-web/pull/2468
|
||||||
|
[#2470]: https://github.com/actix/actix-web/pull/2470
|
||||||
|
[#2474]: https://github.com/actix/actix-web/pull/2474
|
||||||
|
[#2483]: https://github.com/actix/actix-web/pull/2483
|
||||||
|
[#2486]: https://github.com/actix/actix-web/pull/2486
|
||||||
|
[#2487]: https://github.com/actix/actix-web/pull/2487
|
||||||
|
[#2488]: https://github.com/actix/actix-web/pull/2488
|
||||||
|
[#2491]: https://github.com/actix/actix-web/pull/2491
|
||||||
|
[#2497]: https://github.com/actix/actix-web/pull/2497
|
||||||
|
[#2501]: https://github.com/actix/actix-web/pull/2501
|
||||||
|
[#2510]: https://github.com/actix/actix-web/pull/2510
|
||||||
|
[#2520]: https://github.com/actix/actix-web/pull/2520
|
||||||
|
[#2522]: https://github.com/actix/actix-web/pull/2522
|
||||||
|
[#2527]: https://github.com/actix/actix-web/pull/2527
|
||||||
|
[#2538]: https://github.com/actix/actix-web/pull/2538
|
||||||
|
[#2545]: https://github.com/actix/actix-web/pull/2545
|
||||||
|
[#2565]: https://github.com/actix/actix-web/pull/2565
|
||||||
|
[#2585]: https://github.com/actix/actix-web/pull/2585
|
||||||
|
[#2587]: https://github.com/actix/actix-web/pull/2587
|
||||||
|
[#2611]: https://github.com/actix/actix-web/pull/2611
|
||||||
|
[#2618]: https://github.com/actix/actix-web/pull/2618
|
||||||
|
[#2624]: https://github.com/actix/actix-web/pull/2624
|
||||||
|
[#2625]: https://github.com/actix/actix-web/pull/2625
|
||||||
|
[#2660]: https://github.com/actix/actix-web/pull/2660
|
||||||
|
[00ba8d55]: https://github.com/actix/actix-web/commit/00ba8d55492284581695d824648590715a8bd386
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>3.0.0 Pre-Releases</summary>
|
||||||
|
|
||||||
|
## 3.0.0-rc.4 - 2022-02-22
|
||||||
|
### Fixed
|
||||||
|
- Fix h1 dispatcher panic. [1ce58ecb]
|
||||||
|
|
||||||
|
[1ce58ecb]: https://github.com/actix/actix-web/commit/1ce58ecb305c60e51db06e6c913b7a1344e229ca
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-rc.3 - 2022-02-16
|
||||||
|
- No significant changes since `3.0.0-rc.2`.
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-rc.2 - 2022-02-08
|
||||||
|
### Added
|
||||||
|
- Implement `From<Vec<u8>>` for `Response<Vec<u8>>`. [#2625]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `error::DispatcherError` enum is now marked `#[non_exhaustive]`. [#2624]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Issue where handlers that took payload but then dropped without reading it to EOF it would cause keep-alive connections to become stuck. [#2624]
|
||||||
|
|
||||||
|
[#2624]: https://github.com/actix/actix-web/pull/2624
|
||||||
|
[#2625]: https://github.com/actix/actix-web/pull/2625
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-rc.1 - 2022-01-31
|
## 3.0.0-rc.1 - 2022-01-31
|
||||||
### Added
|
### Added
|
||||||
- Implement `Default` for `KeepAlive`. [#2611]
|
- Implement `Default` for `KeepAlive`. [#2611]
|
||||||
@ -80,7 +425,7 @@
|
|||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.17 - 2021-12-27
|
## 3.0.0-beta.17 - 2021-12-27
|
||||||
### Changes
|
### Changed
|
||||||
- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527]
|
- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527]
|
||||||
- `Payload` inner fields are now named. [#2545]
|
- `Payload` inner fields are now named. [#2545]
|
||||||
- `impl Stream` for `Payload` no longer requires the `Stream` variant be `Unpin`. [#2545]
|
- `impl Stream` for `Payload` no longer requires the `Stream` variant be `Unpin`. [#2545]
|
||||||
@ -303,7 +648,7 @@
|
|||||||
- `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159]
|
- `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159]
|
||||||
- Helper `body::to_bytes` for async collecting message body into Bytes. [#2158]
|
- Helper `body::to_bytes` for async collecting message body into Bytes. [#2158]
|
||||||
|
|
||||||
### Changes
|
### Changed
|
||||||
- The type parameter of `Response` no longer has a default. [#2152]
|
- The type parameter of `Response` no longer has a default. [#2152]
|
||||||
- The `Message` variant of `body::Body` is now `Pin<Box<dyn MessageBody>>`. [#2152]
|
- The `Message` variant of `body::Body` is now `Pin<Box<dyn MessageBody>>`. [#2152]
|
||||||
- `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152]
|
- `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152]
|
||||||
@ -447,6 +792,8 @@
|
|||||||
[#1864]: https://github.com/actix/actix-web/pull/1864
|
[#1864]: https://github.com/actix/actix-web/pull/1864
|
||||||
[#1878]: https://github.com/actix/actix-web/pull/1878
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## 2.2.2 - 2022-01-21
|
## 2.2.2 - 2022-01-21
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "3.0.0-rc.1"
|
version = "3.1.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
@ -20,7 +20,7 @@ edition = "2018"
|
|||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
# features that docs.rs will build with
|
# features that docs.rs will build with
|
||||||
features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"]
|
features = ["http2", "ws", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "actix_http"
|
name = "actix_http"
|
||||||
@ -37,7 +37,7 @@ ws = [
|
|||||||
"local-channel",
|
"local-channel",
|
||||||
"base64",
|
"base64",
|
||||||
"rand",
|
"rand",
|
||||||
"sha-1",
|
"sha1",
|
||||||
]
|
]
|
||||||
|
|
||||||
# TLS via OpenSSL
|
# TLS via OpenSSL
|
||||||
@ -57,7 +57,7 @@ __compress = []
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "2"
|
actix-service = "2"
|
||||||
actix-codec = "0.4.1"
|
actix-codec = "0.5"
|
||||||
actix-utils = "3"
|
actix-utils = "3"
|
||||||
actix-rt = { version = "2.2", default-features = false }
|
actix-rt = { version = "2.2", default-features = false }
|
||||||
|
|
||||||
@ -73,11 +73,11 @@ httparse = "1.5.1"
|
|||||||
httpdate = "1.0.1"
|
httpdate = "1.0.1"
|
||||||
itoa = "1"
|
itoa = "1"
|
||||||
language-tags = "0.3"
|
language-tags = "0.3"
|
||||||
log = "0.4"
|
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
pin-project-lite = "0.2"
|
pin-project-lite = "0.2"
|
||||||
smallvec = "1.6.1"
|
smallvec = "1.6.1"
|
||||||
|
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||||
|
|
||||||
# http2
|
# http2
|
||||||
h2 = { version = "0.3.9", optional = true }
|
h2 = { version = "0.3.9", optional = true }
|
||||||
@ -86,21 +86,21 @@ h2 = { version = "0.3.9", optional = true }
|
|||||||
local-channel = { version = "0.1", optional = true }
|
local-channel = { version = "0.1", optional = true }
|
||||||
base64 = { version = "0.13", optional = true }
|
base64 = { version = "0.13", optional = true }
|
||||||
rand = { version = "0.8", optional = true }
|
rand = { version = "0.8", optional = true }
|
||||||
sha-1 = { version = "0.10", optional = true }
|
sha1 = { version = "0.10", optional = true }
|
||||||
|
|
||||||
# openssl/rustls
|
# openssl/rustls
|
||||||
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
actix-tls = { version = "3", default-features = false, optional = true }
|
||||||
|
|
||||||
# compress-*
|
# compress-*
|
||||||
brotli = { version = "3.3.3", optional = true }
|
brotli = { version = "3.3.3", optional = true }
|
||||||
flate2 = { version = "1.0.13", optional = true }
|
flate2 = { version = "1.0.13", optional = true }
|
||||||
zstd = { version = "0.9", optional = true }
|
zstd = { version = "0.11", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] }
|
||||||
actix-server = "2"
|
actix-server = "2"
|
||||||
actix-tls = { version = "3.0.0", features = ["openssl"] }
|
actix-tls = { version = "3", features = ["openssl"] }
|
||||||
actix-web = "4.0.0-rc.1"
|
actix-web = "4"
|
||||||
|
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
@ -110,6 +110,7 @@ memchr = "2.4"
|
|||||||
once_cell = "1.9"
|
once_cell = "1.9"
|
||||||
rcgen = "0.8"
|
rcgen = "0.8"
|
||||||
regex = "1.3"
|
regex = "1.3"
|
||||||
|
rustversion = "1"
|
||||||
rustls-pemfile = "0.2"
|
rustls-pemfile = "0.2"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
@ -120,7 +121,7 @@ tokio = { version = "1.8.4", features = ["net", "rt", "macros"] }
|
|||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "ws"
|
name = "ws"
|
||||||
required-features = ["rustls"]
|
required-features = ["ws", "rustls"]
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "write-camel-case"
|
name = "write-camel-case"
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
> HTTP primitives for the Actix ecosystem.
|
> HTTP primitives for the Actix ecosystem.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http)
|
[](https://crates.io/crates/actix-http)
|
||||||
[](https://docs.rs/actix-http/3.0.0-rc.1)
|
[](https://docs.rs/actix-http/3.1.0)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-http/3.0.0-rc.1)
|
[](https://deps.rs/crate/actix-http/3.1.0)
|
||||||
[](https://crates.io/crates/actix-http)
|
[](https://crates.io/crates/actix-http)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ use actix_http::{HttpService, Response};
|
|||||||
use actix_server::Server;
|
use actix_server::Server;
|
||||||
use futures_util::future;
|
use futures_util::future;
|
||||||
use http::header::HeaderValue;
|
use http::header::HeaderValue;
|
||||||
use log::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
|
@ -114,11 +114,12 @@ mod _original {
|
|||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
pub fn parse_headers(src: &mut BytesMut) -> usize {
|
pub fn parse_headers(src: &mut BytesMut) -> usize {
|
||||||
#![allow(clippy::uninit_assumed_init)]
|
#![allow(invalid_value, clippy::uninit_assumed_init)]
|
||||||
|
|
||||||
let mut headers: [HeaderIndex; MAX_HEADERS] =
|
let mut headers: [HeaderIndex; MAX_HEADERS] =
|
||||||
unsafe { MaybeUninit::uninit().assume_init() };
|
unsafe { MaybeUninit::uninit().assume_init() };
|
||||||
|
|
||||||
|
#[allow(invalid_value)]
|
||||||
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] =
|
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] =
|
||||||
unsafe { MaybeUninit::uninit().assume_init() };
|
unsafe { MaybeUninit::uninit().assume_init() };
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@ async fn main() -> std::io::Result<()> {
|
|||||||
HttpService::build()
|
HttpService::build()
|
||||||
// pass the app to service builder
|
// pass the app to service builder
|
||||||
// map_config is used to map App's configuration to ServiceBuilder
|
// map_config is used to map App's configuration to ServiceBuilder
|
||||||
.finish(map_config(app, |_| AppConfig::default()))
|
// h1 will configure server to only use HTTP/1.1
|
||||||
|
.h1(map_config(app, |_| AppConfig::default()))
|
||||||
.tcp()
|
.tcp()
|
||||||
})?
|
})?
|
||||||
.run()
|
.run()
|
||||||
|
@ -5,6 +5,7 @@ use actix_server::Server;
|
|||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use futures_util::StreamExt as _;
|
use futures_util::StreamExt as _;
|
||||||
use http::header::HeaderValue;
|
use http::header::HeaderValue;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
@ -22,7 +23,7 @@ async fn main() -> io::Result<()> {
|
|||||||
body.extend_from_slice(&item?);
|
body.extend_from_slice(&item?);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("request body: {:?}", body);
|
info!("request body: {:?}", body);
|
||||||
|
|
||||||
let res = Response::build(StatusCode::OK)
|
let res = Response::build(StatusCode::OK)
|
||||||
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
|
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use std::{convert::Infallible, io, time::Duration};
|
use std::{convert::Infallible, io, time::Duration};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{header::HeaderValue, HttpService, Request, Response, StatusCode};
|
||||||
header::HeaderValue, HttpMessage, HttpService, Request, Response, StatusCode,
|
|
||||||
};
|
|
||||||
use actix_server::Server;
|
use actix_server::Server;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
@ -18,12 +17,12 @@ async fn main() -> io::Result<()> {
|
|||||||
ext.insert(42u32);
|
ext.insert(42u32);
|
||||||
})
|
})
|
||||||
.finish(|req: Request| async move {
|
.finish(|req: Request| async move {
|
||||||
log::info!("{:?}", req);
|
info!("{:?}", req);
|
||||||
|
|
||||||
let mut res = Response::build(StatusCode::OK);
|
let mut res = Response::build(StatusCode::OK);
|
||||||
res.insert_header(("x-head", HeaderValue::from_static("dummy value!")));
|
res.insert_header(("x-head", HeaderValue::from_static("dummy value!")));
|
||||||
|
|
||||||
let forty_two = req.extensions().get::<u32>().unwrap().to_string();
|
let forty_two = req.conn_data::<u32>().unwrap().to_string();
|
||||||
res.insert_header((
|
res.insert_header((
|
||||||
"x-forty-two",
|
"x-forty-two",
|
||||||
HeaderValue::from_str(&forty_two).unwrap(),
|
HeaderValue::from_str(&forty_two).unwrap(),
|
||||||
|
@ -12,6 +12,7 @@ use actix_http::{body::BodyStream, HttpService, Response};
|
|||||||
use actix_server::Server;
|
use actix_server::Server;
|
||||||
use async_stream::stream;
|
use async_stream::stream;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
@ -21,7 +22,7 @@ async fn main() -> io::Result<()> {
|
|||||||
.bind("streaming-error", ("127.0.0.1", 8080), || {
|
.bind("streaming-error", ("127.0.0.1", 8080), || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.finish(|req| async move {
|
.finish(|req| async move {
|
||||||
log::info!("{:?}", req);
|
info!("{:?}", req);
|
||||||
let res = Response::ok();
|
let res = Response::ok();
|
||||||
|
|
||||||
Ok::<_, Infallible>(res.set_body(BodyStream::new(stream! {
|
Ok::<_, Infallible>(res.set_body(BodyStream::new(stream! {
|
||||||
|
@ -17,6 +17,7 @@ use actix_server::Server;
|
|||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use bytestring::ByteString;
|
use bytestring::ByteString;
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
|
use tracing::{info, trace};
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
@ -34,13 +35,13 @@ async fn main() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handler(req: Request) -> Result<Response<BodyStream<Heartbeat>>, Error> {
|
async fn handler(req: Request) -> Result<Response<BodyStream<Heartbeat>>, Error> {
|
||||||
log::info!("handshaking");
|
info!("handshaking");
|
||||||
let mut res = ws::handshake(req.head())?;
|
let mut res = ws::handshake(req.head())?;
|
||||||
|
|
||||||
// handshake will always fail under HTTP/2
|
// handshake will always fail under HTTP/2
|
||||||
|
|
||||||
log::info!("responding");
|
info!("responding");
|
||||||
Ok(res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))?)
|
res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Heartbeat {
|
struct Heartbeat {
|
||||||
@ -61,7 +62,7 @@ impl Stream for Heartbeat {
|
|||||||
type Item = Result<Bytes, Error>;
|
type Item = Result<Bytes, Error>;
|
||||||
|
|
||||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
log::trace!("poll");
|
trace!("poll");
|
||||||
|
|
||||||
ready!(self.as_mut().interval.poll_tick(cx));
|
ready!(self.as_mut().interval.poll_tick(cx));
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ mod tests {
|
|||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use futures_util::{stream, FutureExt as _};
|
use futures_util::{stream, FutureExt as _};
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
use static_assertions::{assert_impl_all, assert_not_impl_any};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::body::to_bytes;
|
use crate::body::to_bytes;
|
||||||
@ -91,10 +91,10 @@ mod tests {
|
|||||||
assert_impl_all!(BodyStream<stream::Empty<Result<Bytes, Infallible>>>: MessageBody);
|
assert_impl_all!(BodyStream<stream::Empty<Result<Bytes, Infallible>>>: MessageBody);
|
||||||
assert_impl_all!(BodyStream<stream::Repeat<Result<Bytes, Infallible>>>: MessageBody);
|
assert_impl_all!(BodyStream<stream::Repeat<Result<Bytes, Infallible>>>: MessageBody);
|
||||||
|
|
||||||
assert_not_impl_all!(BodyStream<stream::Empty<Bytes>>: MessageBody);
|
assert_not_impl_any!(BodyStream<stream::Empty<Bytes>>: MessageBody);
|
||||||
assert_not_impl_all!(BodyStream<stream::Repeat<Bytes>>: MessageBody);
|
assert_not_impl_any!(BodyStream<stream::Repeat<Bytes>>: MessageBody);
|
||||||
// crate::Error is not Clone
|
// crate::Error is not Clone
|
||||||
assert_not_impl_all!(BodyStream<stream::Repeat<Result<Bytes, crate::Error>>>: MessageBody);
|
assert_not_impl_any!(BodyStream<stream::Repeat<Result<Bytes, crate::Error>>>: MessageBody);
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn skips_empty_chunks() {
|
async fn skips_empty_chunks() {
|
||||||
|
@ -105,14 +105,13 @@ impl MessageBody for BoxBody {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
use static_assertions::{assert_impl_all, assert_not_impl_any};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::body::to_bytes;
|
use crate::body::to_bytes;
|
||||||
|
|
||||||
assert_impl_all!(BoxBody: MessageBody, fmt::Debug, Unpin);
|
assert_impl_all!(BoxBody: fmt::Debug, MessageBody, Unpin);
|
||||||
|
assert_not_impl_any!(BoxBody: Send, Sync);
|
||||||
assert_not_impl_all!(BoxBody: Send, Sync, Unpin);
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn nested_boxed_body() {
|
async fn nested_boxed_body() {
|
||||||
|
@ -10,6 +10,17 @@ use super::{BodySize, BoxBody, MessageBody};
|
|||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
|
/// An "either" type specialized for body types.
|
||||||
|
///
|
||||||
|
/// It is common, in middleware especially, to conditionally return an inner service's unknown/
|
||||||
|
/// generic body `B` type or return early with a new response. This type's "right" variant
|
||||||
|
/// defaults to `BoxBody` since error responses are the common case.
|
||||||
|
///
|
||||||
|
/// For example, middleware will often have `type Response = ServiceResponse<EitherBody<B>>`.
|
||||||
|
/// This means that the inner service's response body type maps to the `Left` variant and the
|
||||||
|
/// middleware's own error responses use the default `Right` variant of `BoxBody`. Of course,
|
||||||
|
/// there's no reason it couldn't use `EitherBody<B, String>` instead if its alternative
|
||||||
|
/// responses have a known type.
|
||||||
#[project = EitherBodyProj]
|
#[project = EitherBodyProj]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum EitherBody<L, R = BoxBody> {
|
pub enum EitherBody<L, R = BoxBody> {
|
||||||
@ -22,7 +33,10 @@ pin_project! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<L> EitherBody<L, BoxBody> {
|
impl<L> EitherBody<L, BoxBody> {
|
||||||
/// Creates new `EitherBody` using left variant and boxed right variant.
|
/// Creates new `EitherBody` left variant with a boxed right variant.
|
||||||
|
///
|
||||||
|
/// If the expected `R` type will be inferred and is not `BoxBody` then use the
|
||||||
|
/// [`left`](Self::left) constructor instead.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(body: L) -> Self {
|
pub fn new(body: L) -> Self {
|
||||||
Self::Left { body }
|
Self::Left { body }
|
||||||
|
@ -19,7 +19,7 @@ use super::{BodySize, BoxBody};
|
|||||||
/// It is not usually necessary to create custom body types, this trait is already [implemented for
|
/// It is not usually necessary to create custom body types, this trait is already [implemented for
|
||||||
/// a large number of sensible body types](#foreign-impls) including:
|
/// a large number of sensible body types](#foreign-impls) including:
|
||||||
/// - Empty body: `()`
|
/// - Empty body: `()`
|
||||||
/// - Text-based: `String`, `&'static str`, `ByteString`.
|
/// - Text-based: `String`, `&'static str`, [`ByteString`](https://docs.rs/bytestring/1).
|
||||||
/// - Byte-based: `Bytes`, `BytesMut`, `Vec<u8>`, `&'static [u8]`;
|
/// - Byte-based: `Bytes`, `BytesMut`, `Vec<u8>`, `&'static [u8]`;
|
||||||
/// - Streams: [`BodyStream`](super::BodyStream), [`SizedStream`](super::SizedStream)
|
/// - Streams: [`BodyStream`](super::BodyStream), [`SizedStream`](super::SizedStream)
|
||||||
///
|
///
|
||||||
|
@ -76,7 +76,7 @@ mod tests {
|
|||||||
use actix_rt::pin;
|
use actix_rt::pin;
|
||||||
use actix_utils::future::poll_fn;
|
use actix_utils::future::poll_fn;
|
||||||
use futures_util::stream;
|
use futures_util::stream;
|
||||||
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
use static_assertions::{assert_impl_all, assert_not_impl_any};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::body::to_bytes;
|
use crate::body::to_bytes;
|
||||||
@ -87,10 +87,10 @@ mod tests {
|
|||||||
assert_impl_all!(SizedStream<stream::Empty<Result<Bytes, Infallible>>>: MessageBody);
|
assert_impl_all!(SizedStream<stream::Empty<Result<Bytes, Infallible>>>: MessageBody);
|
||||||
assert_impl_all!(SizedStream<stream::Repeat<Result<Bytes, Infallible>>>: MessageBody);
|
assert_impl_all!(SizedStream<stream::Repeat<Result<Bytes, Infallible>>>: MessageBody);
|
||||||
|
|
||||||
assert_not_impl_all!(SizedStream<stream::Empty<Bytes>>: MessageBody);
|
assert_not_impl_any!(SizedStream<stream::Empty<Bytes>>: MessageBody);
|
||||||
assert_not_impl_all!(SizedStream<stream::Repeat<Bytes>>: MessageBody);
|
assert_not_impl_any!(SizedStream<stream::Repeat<Bytes>>: MessageBody);
|
||||||
// crate::Error is not Clone
|
// crate::Error is not Clone
|
||||||
assert_not_impl_all!(SizedStream<stream::Repeat<Result<Bytes, crate::Error>>>: MessageBody);
|
assert_not_impl_any!(SizedStream<stream::Repeat<Result<Bytes, crate::Error>>>: MessageBody);
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn skips_empty_chunks() {
|
async fn skips_empty_chunks() {
|
||||||
|
@ -104,8 +104,13 @@ impl ServiceConfig {
|
|||||||
self.0.date_service.now()
|
self.0.date_service.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) {
|
/// Writes date header to `dst` buffer.
|
||||||
let mut buf: [u8; 39] = [0; 39];
|
///
|
||||||
|
/// Low-level method that utilizes the built-in efficient date service, requiring fewer syscalls
|
||||||
|
/// than normal. Note that a CRLF (`\r\n`) is included in what is written.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) {
|
||||||
|
let mut buf: [u8; 37] = [0; 37];
|
||||||
|
|
||||||
buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " });
|
buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " });
|
||||||
|
|
||||||
@ -113,7 +118,7 @@ impl ServiceConfig {
|
|||||||
.date_service
|
.date_service
|
||||||
.with_date(|date| buf[6..35].copy_from_slice(&date.bytes));
|
.with_date(|date| buf[6..35].copy_from_slice(&date.bytes));
|
||||||
|
|
||||||
buf[35..].copy_from_slice(b"\r\n\r\n");
|
buf[35..].copy_from_slice(b"\r\n");
|
||||||
dst.extend_from_slice(&buf);
|
dst.extend_from_slice(&buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ use zstd::stream::write::Decoder as ZstdDecoder;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
encoding::Writer,
|
encoding::Writer,
|
||||||
error::{BlockingError, PayloadError},
|
error::PayloadError,
|
||||||
header::{ContentEncoding, HeaderMap, CONTENT_ENCODING},
|
header::{ContentEncoding, HeaderMap, CONTENT_ENCODING},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,14 +47,17 @@ where
|
|||||||
ContentEncoding::Brotli => Some(ContentDecoder::Brotli(Box::new(
|
ContentEncoding::Brotli => Some(ContentDecoder::Brotli(Box::new(
|
||||||
brotli::DecompressorWriter::new(Writer::new(), 8_096),
|
brotli::DecompressorWriter::new(Writer::new(), 8_096),
|
||||||
))),
|
))),
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
||||||
ZlibDecoder::new(Writer::new()),
|
ZlibDecoder::new(Writer::new()),
|
||||||
))),
|
))),
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new(
|
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new(
|
||||||
Writer::new(),
|
Writer::new(),
|
||||||
)))),
|
)))),
|
||||||
|
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new(
|
ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new(
|
||||||
ZstdDecoder::new(Writer::new()).expect(
|
ZstdDecoder::new(Writer::new()).expect(
|
||||||
@ -98,8 +101,12 @@ where
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
if let Some(ref mut fut) = this.fut {
|
if let Some(ref mut fut) = this.fut {
|
||||||
let (chunk, decoder) =
|
let (chunk, decoder) = ready!(Pin::new(fut).poll(cx)).map_err(|_| {
|
||||||
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
PayloadError::Io(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Blocking task was cancelled unexpectedly",
|
||||||
|
))
|
||||||
|
})??;
|
||||||
|
|
||||||
*this.decoder = Some(decoder);
|
*this.decoder = Some(decoder);
|
||||||
this.fut.take();
|
this.fut.take();
|
||||||
@ -159,10 +166,13 @@ where
|
|||||||
enum ContentDecoder {
|
enum ContentDecoder {
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
Deflate(Box<ZlibDecoder<Writer>>),
|
Deflate(Box<ZlibDecoder<Writer>>),
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
Gzip(Box<GzDecoder<Writer>>),
|
Gzip(Box<GzDecoder<Writer>>),
|
||||||
|
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
Brotli(Box<brotli::DecompressorWriter<Writer>>),
|
Brotli(Box<brotli::DecompressorWriter<Writer>>),
|
||||||
|
|
||||||
// We need explicit 'static lifetime here because ZstdDecoder need lifetime
|
// We need explicit 'static lifetime here because ZstdDecoder need lifetime
|
||||||
// argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static`
|
// argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static`
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
|
@ -17,13 +17,13 @@ use pin_project_lite::pin_project;
|
|||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||||
|
|
||||||
|
use tracing::trace;
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
use zstd::stream::write::Encoder as ZstdEncoder;
|
use zstd::stream::write::Encoder as ZstdEncoder;
|
||||||
|
|
||||||
use super::Writer;
|
use super::Writer;
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{self, BodySize, MessageBody},
|
body::{self, BodySize, MessageBody},
|
||||||
error::BlockingError,
|
|
||||||
header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING},
|
header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING},
|
||||||
ResponseHead, StatusCode,
|
ResponseHead, StatusCode,
|
||||||
};
|
};
|
||||||
@ -173,7 +173,12 @@ where
|
|||||||
|
|
||||||
if let Some(ref mut fut) = this.fut {
|
if let Some(ref mut fut) = this.fut {
|
||||||
let mut encoder = ready!(Pin::new(fut).poll(cx))
|
let mut encoder = ready!(Pin::new(fut).poll(cx))
|
||||||
.map_err(|_| EncoderError::Blocking(BlockingError))?
|
.map_err(|_| {
|
||||||
|
EncoderError::Io(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Blocking task was cancelled unexpectedly",
|
||||||
|
))
|
||||||
|
})?
|
||||||
.map_err(EncoderError::Io)?;
|
.map_err(EncoderError::Io)?;
|
||||||
|
|
||||||
let chunk = encoder.take();
|
let chunk = encoder.take();
|
||||||
@ -352,7 +357,7 @@ impl ContentEncoder {
|
|||||||
ContentEncoder::Brotli(ref mut encoder) => match encoder.write_all(data) {
|
ContentEncoder::Brotli(ref mut encoder) => match encoder.write_all(data) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::trace!("Error decoding br encoding: {}", err);
|
trace!("Error decoding br encoding: {}", err);
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -361,7 +366,7 @@ impl ContentEncoder {
|
|||||||
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
|
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::trace!("Error decoding gzip encoding: {}", err);
|
trace!("Error decoding gzip encoding: {}", err);
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -370,7 +375,7 @@ impl ContentEncoder {
|
|||||||
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
|
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::trace!("Error decoding deflate encoding: {}", err);
|
trace!("Error decoding deflate encoding: {}", err);
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -379,7 +384,7 @@ impl ContentEncoder {
|
|||||||
ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) {
|
ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::trace!("Error decoding ztsd encoding: {}", err);
|
trace!("Error decoding ztsd encoding: {}", err);
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -400,12 +405,11 @@ fn new_brotli_compressor() -> Box<brotli::CompressorWriter<Writer>> {
|
|||||||
#[derive(Debug, Display)]
|
#[derive(Debug, Display)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum EncoderError {
|
pub enum EncoderError {
|
||||||
|
/// Wrapped body stream error.
|
||||||
#[display(fmt = "body")]
|
#[display(fmt = "body")]
|
||||||
Body(Box<dyn StdError>),
|
Body(Box<dyn StdError>),
|
||||||
|
|
||||||
#[display(fmt = "blocking")]
|
/// Generic I/O error.
|
||||||
Blocking(BlockingError),
|
|
||||||
|
|
||||||
#[display(fmt = "io")]
|
#[display(fmt = "io")]
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
}
|
}
|
||||||
@ -414,7 +418,6 @@ impl StdError for EncoderError {
|
|||||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
EncoderError::Body(err) => Some(&**err),
|
EncoderError::Body(err) => Some(&**err),
|
||||||
EncoderError::Blocking(err) => Some(err),
|
|
||||||
EncoderError::Io(err) => Some(err),
|
EncoderError::Io(err) => Some(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ impl Error {
|
|||||||
Self::new(Kind::SendResponse)
|
Self::new(Kind::SendResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)] // reserved for future use (TODO: remove allow when being used)
|
#[allow(unused)] // available for future use
|
||||||
pub(crate) fn new_io() -> Self {
|
pub(crate) fn new_io() -> Self {
|
||||||
Self::new(Kind::Io)
|
Self::new(Kind::Io)
|
||||||
}
|
}
|
||||||
@ -108,8 +108,10 @@ pub(crate) enum Kind {
|
|||||||
|
|
||||||
impl fmt::Debug for Error {
|
impl fmt::Debug for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
// TODO: more detail
|
f.debug_struct("actix_http::Error")
|
||||||
f.write_str("actix_http::Error")
|
.field("kind", &self.inner.kind)
|
||||||
|
.field("cause", &self.inner.cause)
|
||||||
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,12 +252,6 @@ impl From<ParseError> for Response<BoxBody> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A set of errors that can occur running blocking tasks in thread pool.
|
|
||||||
#[derive(Debug, Display, Error)]
|
|
||||||
#[display(fmt = "Blocking thread pool is gone")]
|
|
||||||
// TODO: non-exhaustive
|
|
||||||
pub struct BlockingError;
|
|
||||||
|
|
||||||
/// A set of errors that can occur during payload parsing.
|
/// A set of errors that can occur during payload parsing.
|
||||||
#[derive(Debug, Display)]
|
#[derive(Debug, Display)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
@ -293,13 +289,13 @@ impl std::error::Error for PayloadError {
|
|||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
PayloadError::Incomplete(None) => None,
|
PayloadError::Incomplete(None) => None,
|
||||||
PayloadError::Incomplete(Some(err)) => Some(err as &dyn std::error::Error),
|
PayloadError::Incomplete(Some(err)) => Some(err),
|
||||||
PayloadError::EncodingCorrupted => None,
|
PayloadError::EncodingCorrupted => None,
|
||||||
PayloadError::Overflow => None,
|
PayloadError::Overflow => None,
|
||||||
PayloadError::UnknownLength => None,
|
PayloadError::UnknownLength => None,
|
||||||
#[cfg(feature = "http2")]
|
#[cfg(feature = "http2")]
|
||||||
PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error),
|
PayloadError::Http2Payload(err) => Some(err),
|
||||||
PayloadError::Io(err) => Some(err as &dyn std::error::Error),
|
PayloadError::Io(err) => Some(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,15 +319,6 @@ impl From<io::Error> for PayloadError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BlockingError> for PayloadError {
|
|
||||||
fn from(_: BlockingError) -> Self {
|
|
||||||
PayloadError::Io(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"Operation is canceled",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<PayloadError> for Error {
|
impl From<PayloadError> for Error {
|
||||||
fn from(err: PayloadError) -> Self {
|
fn from(err: PayloadError) -> Self {
|
||||||
Self::new_payload().with_cause(err)
|
Self::new_payload().with_cause(err)
|
||||||
@ -340,6 +327,7 @@ impl From<PayloadError> for Error {
|
|||||||
|
|
||||||
/// A set of errors that can occur during dispatching HTTP requests.
|
/// A set of errors that can occur during dispatching HTTP requests.
|
||||||
#[derive(Debug, Display, From)]
|
#[derive(Debug, Display, From)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum DispatchError {
|
pub enum DispatchError {
|
||||||
/// Service error.
|
/// Service error.
|
||||||
#[display(fmt = "Service Error")]
|
#[display(fmt = "Service Error")]
|
||||||
@ -373,6 +361,10 @@ pub enum DispatchError {
|
|||||||
#[display(fmt = "Connection shutdown timeout")]
|
#[display(fmt = "Connection shutdown timeout")]
|
||||||
DisconnectTimeout,
|
DisconnectTimeout,
|
||||||
|
|
||||||
|
/// Handler dropped payload before reading EOF.
|
||||||
|
#[display(fmt = "Handler dropped payload before reading EOF")]
|
||||||
|
HandlerDroppedPayload,
|
||||||
|
|
||||||
/// Internal error.
|
/// Internal error.
|
||||||
#[display(fmt = "Internal error")]
|
#[display(fmt = "Internal error")]
|
||||||
InternalError,
|
InternalError,
|
||||||
@ -381,7 +373,6 @@ pub enum DispatchError {
|
|||||||
impl StdError for DispatchError {
|
impl StdError for DispatchError {
|
||||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
// TODO: error source extraction?
|
|
||||||
DispatchError::Service(_res) => None,
|
DispatchError::Service(_res) => None,
|
||||||
DispatchError::Body(err) => Some(&**err),
|
DispatchError::Body(err) => Some(&**err),
|
||||||
DispatchError::Io(err) => Some(err),
|
DispatchError::Io(err) => Some(err),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::{io, task::Poll};
|
use std::{io, task::Poll};
|
||||||
|
|
||||||
use bytes::{Buf as _, Bytes, BytesMut};
|
use bytes::{Buf as _, Bytes, BytesMut};
|
||||||
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
macro_rules! byte (
|
macro_rules! byte (
|
||||||
($rdr:ident) => ({
|
($rdr:ident) => ({
|
||||||
@ -76,7 +77,7 @@ impl ChunkedState {
|
|||||||
Poll::Ready(Ok(ChunkedState::Size))
|
Poll::Ready(Ok(ChunkedState::Size))
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::debug!("chunk size would overflow u64");
|
debug!("chunk size would overflow u64");
|
||||||
Poll::Ready(Err(io::Error::new(
|
Poll::Ready(Err(io::Error::new(
|
||||||
io::ErrorKind::InvalidInput,
|
io::ErrorKind::InvalidInput,
|
||||||
"Invalid chunk size line: Size is too big",
|
"Invalid chunk size line: Size is too big",
|
||||||
@ -124,7 +125,7 @@ impl ChunkedState {
|
|||||||
rem: &mut u64,
|
rem: &mut u64,
|
||||||
buf: &mut Option<Bytes>,
|
buf: &mut Option<Bytes>,
|
||||||
) -> Poll<Result<ChunkedState, io::Error>> {
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
log::trace!("Chunked read, remaining={:?}", rem);
|
trace!("Chunked read, remaining={:?}", rem);
|
||||||
|
|
||||||
let len = rdr.len() as u64;
|
let len = rdr.len() as u64;
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
|
@ -128,7 +128,10 @@ impl Decoder for ClientCodec {
|
|||||||
type Error = ParseError;
|
type Error = ParseError;
|
||||||
|
|
||||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||||
debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set");
|
debug_assert!(
|
||||||
|
self.inner.payload.is_none(),
|
||||||
|
"Payload decoder should not be set"
|
||||||
|
);
|
||||||
|
|
||||||
if let Some((req, payload)) = self.inner.decoder.decode(src)? {
|
if let Some((req, payload)) = self.inner.decoder.decode(src)? {
|
||||||
if let Some(conn_type) = req.conn_type() {
|
if let Some(conn_type) = req.conn_type() {
|
||||||
|
@ -125,11 +125,13 @@ impl Decoder for Codec {
|
|||||||
self.flags.set(Flags::HEAD, head.method == Method::HEAD);
|
self.flags.set(Flags::HEAD, head.method == Method::HEAD);
|
||||||
self.version = head.version;
|
self.version = head.version;
|
||||||
self.conn_type = head.connection_type();
|
self.conn_type = head.connection_type();
|
||||||
|
|
||||||
if self.conn_type == ConnectionType::KeepAlive
|
if self.conn_type == ConnectionType::KeepAlive
|
||||||
&& !self.flags.contains(Flags::KEEP_ALIVE_ENABLED)
|
&& !self.flags.contains(Flags::KEEP_ALIVE_ENABLED)
|
||||||
{
|
{
|
||||||
self.conn_type = ConnectionType::Close
|
self.conn_type = ConnectionType::Close
|
||||||
}
|
}
|
||||||
|
|
||||||
match payload {
|
match payload {
|
||||||
PayloadType::None => self.payload = None,
|
PayloadType::None => self.payload = None,
|
||||||
PayloadType::Payload(pl) => self.payload = Some(pl),
|
PayloadType::Payload(pl) => self.payload = Some(pl),
|
||||||
|
@ -6,7 +6,7 @@ use http::{
|
|||||||
header::{self, HeaderName, HeaderValue},
|
header::{self, HeaderName, HeaderValue},
|
||||||
Method, StatusCode, Uri, Version,
|
Method, StatusCode, Uri, Version,
|
||||||
};
|
};
|
||||||
use log::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
use super::chunked::ChunkedState;
|
use super::chunked::ChunkedState;
|
||||||
use crate::{error::ParseError, header::HeaderMap, ConnectionType, Request, ResponseHead};
|
use crate::{error::ParseError, header::HeaderMap, ConnectionType, Request, ResponseHead};
|
||||||
@ -209,15 +209,16 @@ impl MessageType for Request {
|
|||||||
|
|
||||||
let (len, method, uri, ver, h_len) = {
|
let (len, method, uri, ver, h_len) = {
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is
|
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is safe because the
|
||||||
// safe because the type we are claiming to have initialized here is a
|
// type we are claiming to have initialized here is a bunch of `MaybeUninit`s, which
|
||||||
// bunch of `MaybeUninit`s, which do not require initialization.
|
// do not require initialization.
|
||||||
let mut parsed = unsafe {
|
let mut parsed = unsafe {
|
||||||
MaybeUninit::<[MaybeUninit<httparse::Header<'_>>; MAX_HEADERS]>::uninit()
|
MaybeUninit::<[MaybeUninit<httparse::Header<'_>>; MAX_HEADERS]>::uninit()
|
||||||
.assume_init()
|
.assume_init()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut req = httparse::Request::new(&mut []);
|
let mut req = httparse::Request::new(&mut []);
|
||||||
|
|
||||||
match req.parse_with_uninit_headers(src, &mut parsed)? {
|
match req.parse_with_uninit_headers(src, &mut parsed)? {
|
||||||
httparse::Status::Complete(len) => {
|
httparse::Status::Complete(len) => {
|
||||||
let method = Method::from_bytes(req.method.unwrap().as_bytes())
|
let method = Method::from_bytes(req.method.unwrap().as_bytes())
|
||||||
@ -232,6 +233,7 @@ impl MessageType for Request {
|
|||||||
|
|
||||||
(len, method, uri, version, req.headers.len())
|
(len, method, uri, version, req.headers.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
httparse::Status::Partial => {
|
httparse::Status::Partial => {
|
||||||
return if src.len() >= MAX_BUFFER_SIZE {
|
return if src.len() >= MAX_BUFFER_SIZE {
|
||||||
trace!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
trace!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
||||||
@ -291,22 +293,35 @@ impl MessageType for ResponseHead {
|
|||||||
let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
|
let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
|
||||||
|
|
||||||
let (len, ver, status, h_len) = {
|
let (len, ver, status, h_len) = {
|
||||||
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY;
|
// SAFETY:
|
||||||
|
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is safe because the
|
||||||
|
// type we are claiming to have initialized here is a bunch of `MaybeUninit`s, which
|
||||||
|
// do not require initialization.
|
||||||
|
let mut parsed = unsafe {
|
||||||
|
MaybeUninit::<[MaybeUninit<httparse::Header<'_>>; MAX_HEADERS]>::uninit()
|
||||||
|
.assume_init()
|
||||||
|
};
|
||||||
|
|
||||||
let mut res = httparse::Response::new(&mut parsed);
|
let mut res = httparse::Response::new(&mut []);
|
||||||
match res.parse(src)? {
|
|
||||||
|
let mut config = httparse::ParserConfig::default();
|
||||||
|
config.allow_spaces_after_header_name_in_responses(true);
|
||||||
|
|
||||||
|
match config.parse_response_with_uninit_headers(&mut res, src, &mut parsed)? {
|
||||||
httparse::Status::Complete(len) => {
|
httparse::Status::Complete(len) => {
|
||||||
let version = if res.version.unwrap() == 1 {
|
let version = if res.version.unwrap() == 1 {
|
||||||
Version::HTTP_11
|
Version::HTTP_11
|
||||||
} else {
|
} else {
|
||||||
Version::HTTP_10
|
Version::HTTP_10
|
||||||
};
|
};
|
||||||
|
|
||||||
let status = StatusCode::from_u16(res.code.unwrap())
|
let status = StatusCode::from_u16(res.code.unwrap())
|
||||||
.map_err(|_| ParseError::Status)?;
|
.map_err(|_| ParseError::Status)?;
|
||||||
HeaderIndex::record(src, res.headers, &mut headers);
|
HeaderIndex::record(src, res.headers, &mut headers);
|
||||||
|
|
||||||
(len, version, status, res.headers.len())
|
(len, version, status, res.headers.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
httparse::Status::Partial => {
|
httparse::Status::Partial => {
|
||||||
return if src.len() >= MAX_BUFFER_SIZE {
|
return if src.len() >= MAX_BUFFER_SIZE {
|
||||||
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
||||||
@ -358,9 +373,6 @@ pub(crate) const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex {
|
|||||||
pub(crate) const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] =
|
pub(crate) const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] =
|
||||||
[EMPTY_HEADER_INDEX; MAX_HEADERS];
|
[EMPTY_HEADER_INDEX; MAX_HEADERS];
|
||||||
|
|
||||||
pub(crate) const EMPTY_HEADER_ARRAY: [httparse::Header<'static>; MAX_HEADERS] =
|
|
||||||
[httparse::EMPTY_HEADER; MAX_HEADERS];
|
|
||||||
|
|
||||||
impl HeaderIndex {
|
impl HeaderIndex {
|
||||||
pub(crate) fn record(
|
pub(crate) fn record(
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
|
@ -15,6 +15,7 @@ use bitflags::bitflags;
|
|||||||
use bytes::{Buf, BytesMut};
|
use bytes::{Buf, BytesMut};
|
||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
use tracing::{error, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BodySize, BoxBody, MessageBody},
|
body::{BodySize, BoxBody, MessageBody},
|
||||||
@ -151,7 +152,8 @@ pin_project! {
|
|||||||
error: Option<DispatchError>,
|
error: Option<DispatchError>,
|
||||||
|
|
||||||
#[pin]
|
#[pin]
|
||||||
state: State<S, B, X>,
|
pub(super) state: State<S, B, X>,
|
||||||
|
// when Some(_) dispatcher is in state of receiving request payload
|
||||||
payload: Option<PayloadSender>,
|
payload: Option<PayloadSender>,
|
||||||
messages: VecDeque<DispatcherMessage>,
|
messages: VecDeque<DispatcherMessage>,
|
||||||
|
|
||||||
@ -174,7 +176,7 @@ enum DispatcherMessage {
|
|||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
#[project = StateProj]
|
#[project = StateProj]
|
||||||
enum State<S, B, X>
|
pub(super) enum State<S, B, X>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
@ -194,7 +196,7 @@ where
|
|||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
fn is_none(&self) -> bool {
|
pub(super) fn is_none(&self) -> bool {
|
||||||
matches!(self, State::None)
|
matches!(self, State::None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -335,7 +337,7 @@ where
|
|||||||
while written < len {
|
while written < len {
|
||||||
match io.as_mut().poll_write(cx, &write_buf[written..])? {
|
match io.as_mut().poll_write(cx, &write_buf[written..])? {
|
||||||
Poll::Ready(0) => {
|
Poll::Ready(0) => {
|
||||||
log::error!("write zero; closing");
|
error!("write zero; closing");
|
||||||
return Poll::Ready(Err(io::Error::new(io::ErrorKind::WriteZero, "")));
|
return Poll::Ready(Err(io::Error::new(io::ErrorKind::WriteZero, "")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,8 +376,6 @@ where
|
|||||||
DispatchError::Io(err)
|
DispatchError::Io(err)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
this.flags.set(Flags::KEEP_ALIVE, this.codec.keep_alive());
|
|
||||||
|
|
||||||
Ok(size)
|
Ok(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,7 +458,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// all messages are dealt with
|
// all messages are dealt with
|
||||||
None => return Ok(PollResponse::DoNothing),
|
None => {
|
||||||
|
// start keep-alive if last request allowed it
|
||||||
|
this.flags.set(Flags::KEEP_ALIVE, this.codec.keep_alive());
|
||||||
|
|
||||||
|
return Ok(PollResponse::DoNothing);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
StateProj::ServiceCall { fut } => {
|
StateProj::ServiceCall { fut } => {
|
||||||
@ -564,7 +569,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
StateProj::ExpectCall { fut } => {
|
StateProj::ExpectCall { fut } => {
|
||||||
log::trace!(" calling expect service");
|
trace!(" calling expect service");
|
||||||
|
|
||||||
match fut.poll(cx) {
|
match fut.poll(cx) {
|
||||||
// expect resolved. write continue to buffer and set InnerDispatcher state
|
// expect resolved. write continue to buffer and set InnerDispatcher state
|
||||||
@ -694,6 +699,7 @@ where
|
|||||||
|
|
||||||
let mut updated = false;
|
let mut updated = false;
|
||||||
|
|
||||||
|
// decode from read buf as many full requests as possible
|
||||||
loop {
|
loop {
|
||||||
match this.codec.decode(this.read_buf) {
|
match this.codec.decode(this.read_buf) {
|
||||||
Ok(Some(msg)) => {
|
Ok(Some(msg)) => {
|
||||||
@ -746,7 +752,7 @@ where
|
|||||||
if let Some(ref mut payload) = this.payload {
|
if let Some(ref mut payload) = this.payload {
|
||||||
payload.feed_data(chunk);
|
payload.feed_data(chunk);
|
||||||
} else {
|
} else {
|
||||||
log::error!("Internal server error: unexpected payload chunk");
|
error!("Internal server error: unexpected payload chunk");
|
||||||
this.flags.insert(Flags::READ_DISCONNECT);
|
this.flags.insert(Flags::READ_DISCONNECT);
|
||||||
this.messages.push_back(DispatcherMessage::Error(
|
this.messages.push_back(DispatcherMessage::Error(
|
||||||
Response::internal_server_error().drop_body(),
|
Response::internal_server_error().drop_body(),
|
||||||
@ -760,7 +766,7 @@ where
|
|||||||
if let Some(mut payload) = this.payload.take() {
|
if let Some(mut payload) = this.payload.take() {
|
||||||
payload.feed_eof();
|
payload.feed_eof();
|
||||||
} else {
|
} else {
|
||||||
log::error!("Internal server error: unexpected eof");
|
error!("Internal server error: unexpected eof");
|
||||||
this.flags.insert(Flags::READ_DISCONNECT);
|
this.flags.insert(Flags::READ_DISCONNECT);
|
||||||
this.messages.push_back(DispatcherMessage::Error(
|
this.messages.push_back(DispatcherMessage::Error(
|
||||||
Response::internal_server_error().drop_body(),
|
Response::internal_server_error().drop_body(),
|
||||||
@ -777,7 +783,7 @@ where
|
|||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
|
|
||||||
Err(ParseError::Io(err)) => {
|
Err(ParseError::Io(err)) => {
|
||||||
log::trace!("I/O error: {}", &err);
|
trace!("I/O error: {}", &err);
|
||||||
self.as_mut().client_disconnected();
|
self.as_mut().client_disconnected();
|
||||||
this = self.as_mut().project();
|
this = self.as_mut().project();
|
||||||
*this.error = Some(DispatchError::Io(err));
|
*this.error = Some(DispatchError::Io(err));
|
||||||
@ -785,7 +791,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
Err(ParseError::TooLarge) => {
|
Err(ParseError::TooLarge) => {
|
||||||
log::trace!("request head was too big; returning 431 response");
|
trace!("request head was too big; returning 431 response");
|
||||||
|
|
||||||
if let Some(mut payload) = this.payload.take() {
|
if let Some(mut payload) = this.payload.take() {
|
||||||
payload.set_error(PayloadError::Overflow);
|
payload.set_error(PayloadError::Overflow);
|
||||||
@ -805,7 +811,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::trace!("parse error {}", &err);
|
trace!("parse error {}", &err);
|
||||||
|
|
||||||
if let Some(mut payload) = this.payload.take() {
|
if let Some(mut payload) = this.payload.take() {
|
||||||
payload.set_error(PayloadError::EncodingCorrupted);
|
payload.set_error(PayloadError::EncodingCorrupted);
|
||||||
@ -836,10 +842,7 @@ where
|
|||||||
if timer.as_mut().poll(cx).is_ready() {
|
if timer.as_mut().poll(cx).is_ready() {
|
||||||
// timeout on first request (slow request) return 408
|
// timeout on first request (slow request) return 408
|
||||||
|
|
||||||
log::trace!(
|
trace!("timed out on slow request; replying with 408 and closing connection");
|
||||||
"timed out on slow request; \
|
|
||||||
replying with 408 and closing connection"
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = self.as_mut().send_error_response(
|
let _ = self.as_mut().send_error_response(
|
||||||
Response::with_body(StatusCode::REQUEST_TIMEOUT, ()),
|
Response::with_body(StatusCode::REQUEST_TIMEOUT, ()),
|
||||||
@ -868,15 +871,21 @@ where
|
|||||||
"dispatcher should not be in keep-alive phase if state is not none: {:?}",
|
"dispatcher should not be in keep-alive phase if state is not none: {:?}",
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
debug_assert!(
|
|
||||||
this.write_buf.is_empty(),
|
// Assert removed by @robjtede on account of issue #2655. There are cases where an I/O
|
||||||
"dispatcher should not be in keep-alive phase if write_buf is not empty",
|
// flush can be pending after entering the keep-alive state causing the subsequent flush
|
||||||
);
|
// wake up to panic here. This appears to be a Linux-only problem. Leaving original code
|
||||||
|
// below for posterity because a simple and reliable test could not be found to trigger
|
||||||
|
// the behavior.
|
||||||
|
// debug_assert!(
|
||||||
|
// this.write_buf.is_empty(),
|
||||||
|
// "dispatcher should not be in keep-alive phase if write_buf is not empty",
|
||||||
|
// );
|
||||||
|
|
||||||
// keep-alive timer has timed out
|
// keep-alive timer has timed out
|
||||||
if timer.as_mut().poll(cx).is_ready() {
|
if timer.as_mut().poll(cx).is_ready() {
|
||||||
// no tasks at hand
|
// no tasks at hand
|
||||||
log::trace!("timer timed out; closing connection");
|
trace!("timer timed out; closing connection");
|
||||||
this.flags.insert(Flags::SHUTDOWN);
|
this.flags.insert(Flags::SHUTDOWN);
|
||||||
|
|
||||||
if let Some(deadline) = this.config.client_disconnect_deadline() {
|
if let Some(deadline) = this.config.client_disconnect_deadline() {
|
||||||
@ -906,7 +915,7 @@ where
|
|||||||
|
|
||||||
// timed-out during shutdown; drop connection
|
// timed-out during shutdown; drop connection
|
||||||
if timer.as_mut().poll(cx).is_ready() {
|
if timer.as_mut().poll(cx).is_ready() {
|
||||||
log::trace!("timed-out during shutdown");
|
trace!("timed-out during shutdown");
|
||||||
return Err(DispatchError::DisconnectTimeout);
|
return Err(DispatchError::DisconnectTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1065,12 +1074,12 @@ where
|
|||||||
|
|
||||||
match this.inner.project() {
|
match this.inner.project() {
|
||||||
DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| {
|
DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| {
|
||||||
log::error!("Upgrade handler error: {}", err);
|
error!("Upgrade handler error: {}", err);
|
||||||
DispatchError::Upgrade
|
DispatchError::Upgrade
|
||||||
}),
|
}),
|
||||||
|
|
||||||
DispatcherStateProj::Normal { mut inner } => {
|
DispatcherStateProj::Normal { mut inner } => {
|
||||||
log::trace!("start flags: {:?}", &inner.flags);
|
trace!("start flags: {:?}", &inner.flags);
|
||||||
|
|
||||||
trace_timer_states(
|
trace_timer_states(
|
||||||
"start",
|
"start",
|
||||||
@ -1177,7 +1186,7 @@ where
|
|||||||
|
|
||||||
// client is gone
|
// client is gone
|
||||||
if inner.flags.contains(Flags::WRITE_DISCONNECT) {
|
if inner.flags.contains(Flags::WRITE_DISCONNECT) {
|
||||||
log::trace!("client is gone; disconnecting");
|
trace!("client is gone; disconnecting");
|
||||||
return Poll::Ready(Ok(()));
|
return Poll::Ready(Ok(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1186,14 +1195,14 @@ where
|
|||||||
|
|
||||||
// read half is closed; we do not process any responses
|
// read half is closed; we do not process any responses
|
||||||
if inner_p.flags.contains(Flags::READ_DISCONNECT) && state_is_none {
|
if inner_p.flags.contains(Flags::READ_DISCONNECT) && state_is_none {
|
||||||
log::trace!("read half closed; start shutdown");
|
trace!("read half closed; start shutdown");
|
||||||
inner_p.flags.insert(Flags::SHUTDOWN);
|
inner_p.flags.insert(Flags::SHUTDOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep-alive and stream errors
|
// keep-alive and stream errors
|
||||||
if state_is_none && inner_p.write_buf.is_empty() {
|
if state_is_none && inner_p.write_buf.is_empty() {
|
||||||
if let Some(err) = inner_p.error.take() {
|
if let Some(err) = inner_p.error.take() {
|
||||||
log::error!("stream error: {}", &err);
|
error!("stream error: {}", &err);
|
||||||
return Poll::Ready(Err(err));
|
return Poll::Ready(Err(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1222,7 +1231,7 @@ where
|
|||||||
Poll::Pending
|
Poll::Pending
|
||||||
};
|
};
|
||||||
|
|
||||||
log::trace!("end flags: {:?}", &inner.flags);
|
trace!("end flags: {:?}", &inner.flags);
|
||||||
|
|
||||||
poll
|
poll
|
||||||
}
|
}
|
||||||
@ -1237,17 +1246,17 @@ fn trace_timer_states(
|
|||||||
ka_timer: &TimerState,
|
ka_timer: &TimerState,
|
||||||
shutdown_timer: &TimerState,
|
shutdown_timer: &TimerState,
|
||||||
) {
|
) {
|
||||||
log::trace!("{} timers:", label);
|
trace!("{} timers:", label);
|
||||||
|
|
||||||
if head_timer.is_enabled() {
|
if head_timer.is_enabled() {
|
||||||
log::trace!(" head {}", &head_timer);
|
trace!(" head {}", &head_timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ka_timer.is_enabled() {
|
if ka_timer.is_enabled() {
|
||||||
log::trace!(" keep-alive {}", &ka_timer);
|
trace!(" keep-alive {}", &ka_timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if shutdown_timer.is_enabled() {
|
if shutdown_timer.is_enabled() {
|
||||||
log::trace!(" shutdown {}", &shutdown_timer);
|
trace!(" shutdown {}", &shutdown_timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::{future::Future, str, task::Poll, time::Duration};
|
use std::{future::Future, str, task::Poll, time::Duration};
|
||||||
|
|
||||||
use actix_rt::time::sleep;
|
use actix_rt::{pin, time::sleep};
|
||||||
use actix_service::fn_service;
|
use actix_service::fn_service;
|
||||||
use actix_utils::future::{ready, Ready};
|
use actix_utils::future::{ready, Ready};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
@ -53,6 +53,14 @@ fn echo_path_service(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn drop_payload_service(
|
||||||
|
) -> impl Service<Request, Response = Response<&'static str>, Error = Error> {
|
||||||
|
fn_service(|mut req: Request| async move {
|
||||||
|
let _ = req.take_payload();
|
||||||
|
Ok::<_, Error>(Response::with_body(StatusCode::OK, "payload dropped"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn echo_payload_service() -> impl Service<Request, Response = Response<Bytes>, Error = Error> {
|
fn echo_payload_service() -> impl Service<Request, Response = Response<Bytes>, Error = Error> {
|
||||||
fn_service(|mut req: Request| {
|
fn_service(|mut req: Request| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
@ -89,7 +97,7 @@ async fn late_request() {
|
|||||||
None,
|
None,
|
||||||
OnConnectData::default(),
|
OnConnectData::default(),
|
||||||
);
|
);
|
||||||
actix_rt::pin!(h1);
|
pin!(h1);
|
||||||
|
|
||||||
lazy(|cx| {
|
lazy(|cx| {
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
@ -156,7 +164,7 @@ async fn oneshot_connection() {
|
|||||||
None,
|
None,
|
||||||
OnConnectData::default(),
|
OnConnectData::default(),
|
||||||
);
|
);
|
||||||
actix_rt::pin!(h1);
|
pin!(h1);
|
||||||
|
|
||||||
lazy(|cx| {
|
lazy(|cx| {
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
@ -173,13 +181,16 @@ async fn oneshot_connection() {
|
|||||||
stabilize_date_header(&mut res);
|
stabilize_date_header(&mut res);
|
||||||
let res = &res[..];
|
let res = &res[..];
|
||||||
|
|
||||||
let exp = b"\
|
let exp = http_msg(
|
||||||
HTTP/1.1 200 OK\r\n\
|
r"
|
||||||
content-length: 5\r\n\
|
HTTP/1.1 200 OK
|
||||||
connection: close\r\n\
|
content-length: 5
|
||||||
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
|
connection: close
|
||||||
/abcd\
|
date: Thu, 01 Jan 1970 12:34:56 UTC
|
||||||
";
|
|
||||||
|
/abcd
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res,
|
res,
|
||||||
@ -188,7 +199,7 @@ async fn oneshot_connection() {
|
|||||||
response: {:?}\n\
|
response: {:?}\n\
|
||||||
expected: {:?}",
|
expected: {:?}",
|
||||||
String::from_utf8_lossy(res),
|
String::from_utf8_lossy(res),
|
||||||
String::from_utf8_lossy(exp)
|
String::from_utf8_lossy(&exp)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
@ -214,7 +225,7 @@ async fn keep_alive_timeout() {
|
|||||||
None,
|
None,
|
||||||
OnConnectData::default(),
|
OnConnectData::default(),
|
||||||
);
|
);
|
||||||
actix_rt::pin!(h1);
|
pin!(h1);
|
||||||
|
|
||||||
lazy(|cx| {
|
lazy(|cx| {
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
@ -293,7 +304,7 @@ async fn keep_alive_follow_up_req() {
|
|||||||
None,
|
None,
|
||||||
OnConnectData::default(),
|
OnConnectData::default(),
|
||||||
);
|
);
|
||||||
actix_rt::pin!(h1);
|
pin!(h1);
|
||||||
|
|
||||||
lazy(|cx| {
|
lazy(|cx| {
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
@ -413,7 +424,7 @@ async fn req_parse_err() {
|
|||||||
OnConnectData::default(),
|
OnConnectData::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
actix_rt::pin!(h1);
|
pin!(h1);
|
||||||
|
|
||||||
match h1.as_mut().poll(cx) {
|
match h1.as_mut().poll(cx) {
|
||||||
Poll::Pending => panic!(),
|
Poll::Pending => panic!(),
|
||||||
@ -459,7 +470,7 @@ async fn pipelining_ok_then_ok() {
|
|||||||
OnConnectData::default(),
|
OnConnectData::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
actix_rt::pin!(h1);
|
pin!(h1);
|
||||||
|
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
@ -529,7 +540,7 @@ async fn pipelining_ok_then_bad() {
|
|||||||
OnConnectData::default(),
|
OnConnectData::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
actix_rt::pin!(h1);
|
pin!(h1);
|
||||||
|
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
@ -601,7 +612,7 @@ async fn expect_handling() {
|
|||||||
",
|
",
|
||||||
);
|
);
|
||||||
|
|
||||||
actix_rt::pin!(h1);
|
pin!(h1);
|
||||||
|
|
||||||
assert!(h1.as_mut().poll(cx).is_pending());
|
assert!(h1.as_mut().poll(cx).is_pending());
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
@ -678,7 +689,7 @@ async fn expect_eager() {
|
|||||||
",
|
",
|
||||||
);
|
);
|
||||||
|
|
||||||
actix_rt::pin!(h1);
|
pin!(h1);
|
||||||
|
|
||||||
assert!(h1.as_mut().poll(cx).is_ready());
|
assert!(h1.as_mut().poll(cx).is_ready());
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
@ -761,7 +772,7 @@ async fn upgrade_handling() {
|
|||||||
",
|
",
|
||||||
);
|
);
|
||||||
|
|
||||||
actix_rt::pin!(h1);
|
pin!(h1);
|
||||||
|
|
||||||
assert!(h1.as_mut().poll(cx).is_ready());
|
assert!(h1.as_mut().poll(cx).is_ready());
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. }));
|
assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. }));
|
||||||
@ -771,3 +782,195 @@ async fn upgrade_handling() {
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fix in #2624 reverted temporarily
|
||||||
|
// complete fix tracked in #2745
|
||||||
|
#[ignore]
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn handler_drop_payload() {
|
||||||
|
let _ = env_logger::try_init();
|
||||||
|
|
||||||
|
let mut buf = TestBuffer::new(http_msg(
|
||||||
|
r"
|
||||||
|
POST /drop-payload HTTP/1.1
|
||||||
|
Content-Length: 3
|
||||||
|
|
||||||
|
abc
|
||||||
|
",
|
||||||
|
));
|
||||||
|
|
||||||
|
let services = HttpFlow::new(
|
||||||
|
drop_payload_service(),
|
||||||
|
ExpectHandler,
|
||||||
|
None::<UpgradeHandler>,
|
||||||
|
);
|
||||||
|
|
||||||
|
let h1 = Dispatcher::new(
|
||||||
|
buf.clone(),
|
||||||
|
services,
|
||||||
|
ServiceConfig::default(),
|
||||||
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
|
);
|
||||||
|
pin!(h1);
|
||||||
|
|
||||||
|
lazy(|cx| {
|
||||||
|
assert!(h1.as_mut().poll(cx).is_pending());
|
||||||
|
|
||||||
|
// polls: manual
|
||||||
|
assert_eq!(h1.poll_count, 1);
|
||||||
|
|
||||||
|
let mut res = BytesMut::from(buf.take_write_buf().as_ref());
|
||||||
|
stabilize_date_header(&mut res);
|
||||||
|
let res = &res[..];
|
||||||
|
|
||||||
|
let exp = http_msg(
|
||||||
|
r"
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
content-length: 15
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC
|
||||||
|
|
||||||
|
payload dropped
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
exp,
|
||||||
|
"\nexpected response not in write buffer:\n\
|
||||||
|
response: {:?}\n\
|
||||||
|
expected: {:?}",
|
||||||
|
String::from_utf8_lossy(res),
|
||||||
|
String::from_utf8_lossy(&exp)
|
||||||
|
);
|
||||||
|
|
||||||
|
if let DispatcherStateProj::Normal { inner } = h1.as_mut().project().inner.project() {
|
||||||
|
assert!(inner.state.is_none());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
lazy(|cx| {
|
||||||
|
// add message that claims to have payload longer than provided
|
||||||
|
buf.extend_read_buf(http_msg(
|
||||||
|
r"
|
||||||
|
POST /drop-payload HTTP/1.1
|
||||||
|
Content-Length: 200
|
||||||
|
|
||||||
|
abc
|
||||||
|
",
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(h1.as_mut().poll(cx).is_pending());
|
||||||
|
|
||||||
|
// polls: manual => manual
|
||||||
|
assert_eq!(h1.poll_count, 2);
|
||||||
|
|
||||||
|
let mut res = BytesMut::from(buf.take_write_buf().as_ref());
|
||||||
|
stabilize_date_header(&mut res);
|
||||||
|
let res = &res[..];
|
||||||
|
|
||||||
|
// expect response immediately even though request side has not finished reading payload
|
||||||
|
let exp = http_msg(
|
||||||
|
r"
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
content-length: 15
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC
|
||||||
|
|
||||||
|
payload dropped
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
exp,
|
||||||
|
"\nexpected response not in write buffer:\n\
|
||||||
|
response: {:?}\n\
|
||||||
|
expected: {:?}",
|
||||||
|
String::from_utf8_lossy(res),
|
||||||
|
String::from_utf8_lossy(&exp)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
lazy(|cx| {
|
||||||
|
assert!(h1.as_mut().poll(cx).is_ready());
|
||||||
|
|
||||||
|
// polls: manual => manual => manual
|
||||||
|
assert_eq!(h1.poll_count, 3);
|
||||||
|
|
||||||
|
let mut res = BytesMut::from(buf.take_write_buf().as_ref());
|
||||||
|
stabilize_date_header(&mut res);
|
||||||
|
let res = &res[..];
|
||||||
|
|
||||||
|
// expect that unrequested error response is sent back since connection could not be cleaned
|
||||||
|
let exp = http_msg(
|
||||||
|
r"
|
||||||
|
HTTP/1.1 500 Internal Server Error
|
||||||
|
content-length: 0
|
||||||
|
connection: close
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC
|
||||||
|
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
exp,
|
||||||
|
"\nexpected response not in write buffer:\n\
|
||||||
|
response: {:?}\n\
|
||||||
|
expected: {:?}",
|
||||||
|
String::from_utf8_lossy(res),
|
||||||
|
String::from_utf8_lossy(&exp)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http_msg(msg: impl AsRef<str>) -> BytesMut {
|
||||||
|
let mut msg = msg
|
||||||
|
.as_ref()
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.into_iter()
|
||||||
|
.map(|line| [line.trim_start(), "\r"].concat())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
// remove trailing \r
|
||||||
|
msg.pop();
|
||||||
|
|
||||||
|
if !msg.is_empty() && !msg.contains("\r\n\r\n") {
|
||||||
|
msg.push_str("\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
BytesMut::from(msg.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn http_msg_creates_msg() {
|
||||||
|
assert_eq!(http_msg(r""), "");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
http_msg(
|
||||||
|
r"
|
||||||
|
POST / HTTP/1.1
|
||||||
|
Content-Length: 3
|
||||||
|
|
||||||
|
abc
|
||||||
|
"
|
||||||
|
),
|
||||||
|
"POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
http_msg(
|
||||||
|
r"
|
||||||
|
GET / HTTP/1.1
|
||||||
|
Content-Length: 3
|
||||||
|
|
||||||
|
"
|
||||||
|
),
|
||||||
|
"GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -210,14 +210,14 @@ pub(crate) trait MessageType: Sized {
|
|||||||
dst.advance_mut(pos);
|
dst.advance_mut(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// optimized date header, set_date writes \r\n
|
|
||||||
if !has_date {
|
if !has_date {
|
||||||
|
// optimized date header, write_date_header writes its own \r\n
|
||||||
config.write_date_header(dst, camel_case);
|
config.write_date_header(dst, camel_case);
|
||||||
} else {
|
|
||||||
// msg eof
|
|
||||||
dst.extend_from_slice(b"\r\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// end-of-headers marker
|
||||||
|
dst.extend_from_slice(b"\r\n");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,6 +517,7 @@ unsafe fn write_camel_case(value: &[u8], buf: *mut u8, len: usize) {
|
|||||||
if let Some(c @ b'a'..=b'z') = iter.next() {
|
if let Some(c @ b'a'..=b'z') = iter.next() {
|
||||||
buffer[index] = c & 0b1101_1111;
|
buffer[index] = c & 0b1101_1111;
|
||||||
}
|
}
|
||||||
|
index += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
index += 1;
|
index += 1;
|
||||||
@ -528,7 +529,7 @@ mod tests {
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::header::AUTHORIZATION;
|
use http::header::{AUTHORIZATION, UPGRADE_INSECURE_REQUESTS};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -559,6 +560,9 @@ mod tests {
|
|||||||
head.headers
|
head.headers
|
||||||
.insert(CONTENT_TYPE, HeaderValue::from_static("plain/text"));
|
.insert(CONTENT_TYPE, HeaderValue::from_static("plain/text"));
|
||||||
|
|
||||||
|
head.headers
|
||||||
|
.insert(UPGRADE_INSECURE_REQUESTS, HeaderValue::from_static("1"));
|
||||||
|
|
||||||
let mut head = RequestHeadType::Owned(head);
|
let mut head = RequestHeadType::Owned(head);
|
||||||
|
|
||||||
let _ = head.encode_headers(
|
let _ = head.encode_headers(
|
||||||
@ -574,6 +578,7 @@ mod tests {
|
|||||||
assert!(data.contains("Connection: close\r\n"));
|
assert!(data.contains("Connection: close\r\n"));
|
||||||
assert!(data.contains("Content-Type: plain/text\r\n"));
|
assert!(data.contains("Content-Type: plain/text\r\n"));
|
||||||
assert!(data.contains("Date: date\r\n"));
|
assert!(data.contains("Date: date\r\n"));
|
||||||
|
assert!(data.contains("Upgrade-Insecure-Requests: 1\r\n"));
|
||||||
|
|
||||||
let _ = head.encode_headers(
|
let _ = head.encode_headers(
|
||||||
&mut bytes,
|
&mut bytes,
|
||||||
|
@ -263,7 +263,8 @@ mod tests {
|
|||||||
assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe);
|
assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe);
|
||||||
|
|
||||||
assert_impl_all!(Inner: Unpin, Send, Sync);
|
assert_impl_all!(Inner: Unpin, Send, Sync);
|
||||||
assert_not_impl_any!(Inner: UnwindSafe, RefUnwindSafe);
|
// assertion not stable wrt rustc versions yet
|
||||||
|
// assert_impl_all!(Inner: UnwindSafe, RefUnwindSafe);
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_unread_data() {
|
async fn test_unread_data() {
|
||||||
|
@ -13,6 +13,7 @@ use actix_service::{
|
|||||||
};
|
};
|
||||||
use actix_utils::future::ready;
|
use actix_utils::future::ready;
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BoxBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
@ -305,13 +306,13 @@ where
|
|||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let expect = expect
|
let expect = expect
|
||||||
.await
|
.await
|
||||||
.map_err(|e| log::error!("Init http expect service error: {:?}", e))?;
|
.map_err(|e| error!("Init http expect service error: {:?}", e))?;
|
||||||
|
|
||||||
let upgrade = match upgrade {
|
let upgrade = match upgrade {
|
||||||
Some(upgrade) => {
|
Some(upgrade) => {
|
||||||
let upgrade = upgrade
|
let upgrade = upgrade
|
||||||
.await
|
.await
|
||||||
.map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?;
|
.map_err(|e| error!("Init http upgrade service error: {:?}", e))?;
|
||||||
Some(upgrade)
|
Some(upgrade)
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
@ -319,7 +320,7 @@ where
|
|||||||
|
|
||||||
let service = service
|
let service = service
|
||||||
.await
|
.await
|
||||||
.map_err(|e| log::error!("Init http service error: {:?}", e))?;
|
.map_err(|e| error!("Init http service error: {:?}", e))?;
|
||||||
|
|
||||||
Ok(H1ServiceHandler::new(
|
Ok(H1ServiceHandler::new(
|
||||||
cfg,
|
cfg,
|
||||||
@ -357,7 +358,7 @@ where
|
|||||||
|
|
||||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
self._poll_ready(cx).map_err(|err| {
|
self._poll_ready(cx).map_err(|err| {
|
||||||
log::error!("HTTP/1 service readiness error: {:?}", err);
|
error!("HTTP/1 service readiness error: {:?}", err);
|
||||||
DispatchError::Service(err)
|
DispatchError::Service(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::{fmt, future::Future, pin::Pin, task::Context};
|
use std::{fmt, future::Future, pin::Pin, task::Context};
|
||||||
|
|
||||||
use actix_rt::time::{Instant, Sleep};
|
use actix_rt::time::{Instant, Sleep};
|
||||||
|
use tracing::trace;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(super) enum TimerState {
|
pub(super) enum TimerState {
|
||||||
@ -24,7 +25,7 @@ impl TimerState {
|
|||||||
|
|
||||||
pub(super) fn set(&mut self, timer: Sleep, line: u32) {
|
pub(super) fn set(&mut self, timer: Sleep, line: u32) {
|
||||||
if matches!(self, Self::Disabled) {
|
if matches!(self, Self::Disabled) {
|
||||||
log::trace!("setting disabled timer from line {}", line);
|
trace!("setting disabled timer from line {}", line);
|
||||||
}
|
}
|
||||||
|
|
||||||
*self = Self::Active {
|
*self = Self::Active {
|
||||||
@ -39,11 +40,11 @@ impl TimerState {
|
|||||||
|
|
||||||
pub(super) fn clear(&mut self, line: u32) {
|
pub(super) fn clear(&mut self, line: u32) {
|
||||||
if matches!(self, Self::Disabled) {
|
if matches!(self, Self::Disabled) {
|
||||||
log::trace!("trying to clear a disabled timer from line {}", line);
|
trace!("trying to clear a disabled timer from line {}", line);
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(self, Self::Inactive) {
|
if matches!(self, Self::Inactive) {
|
||||||
log::trace!("trying to clear an inactive timer from line {}", line);
|
trace!("trying to clear an inactive timer from line {}", line);
|
||||||
}
|
}
|
||||||
|
|
||||||
*self = Self::Inactive;
|
*self = Self::Inactive;
|
||||||
|
@ -19,13 +19,15 @@ use h2::{
|
|||||||
server::{Connection, SendResponse},
|
server::{Connection, SendResponse},
|
||||||
Ping, PingPong,
|
Ping, PingPong,
|
||||||
};
|
};
|
||||||
use log::{error, trace};
|
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
use tracing::{error, trace, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BodySize, BoxBody, MessageBody},
|
body::{BodySize, BoxBody, MessageBody},
|
||||||
config::ServiceConfig,
|
config::ServiceConfig,
|
||||||
header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
|
header::{
|
||||||
|
HeaderName, HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE,
|
||||||
|
},
|
||||||
service::HttpFlow,
|
service::HttpFlow,
|
||||||
Extensions, OnConnectData, Payload, Request, Response, ResponseHead,
|
Extensions, OnConnectData, Payload, Request, Response, ResponseHead,
|
||||||
};
|
};
|
||||||
@ -141,7 +143,7 @@ where
|
|||||||
DispatchError::SendResponse(err) => {
|
DispatchError::SendResponse(err) => {
|
||||||
trace!("Error sending HTTP/2 response: {:?}", err)
|
trace!("Error sending HTTP/2 response: {:?}", err)
|
||||||
}
|
}
|
||||||
DispatchError::SendData(err) => log::warn!("{:?}", err),
|
DispatchError::SendData(err) => warn!("{:?}", err),
|
||||||
DispatchError::ResponseBody(err) => {
|
DispatchError::ResponseBody(err) => {
|
||||||
error!("Response payload stream error: {:?}", err)
|
error!("Response payload stream error: {:?}", err)
|
||||||
}
|
}
|
||||||
@ -306,13 +308,22 @@ fn prepare_response(
|
|||||||
|
|
||||||
// copy headers
|
// copy headers
|
||||||
for (key, value) in head.headers.iter() {
|
for (key, value) in head.headers.iter() {
|
||||||
match *key {
|
match key {
|
||||||
// TODO: consider skipping other headers according to:
|
// omit HTTP/1.x only headers according to:
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2
|
// https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2
|
||||||
// omit HTTP/1.x only headers
|
&CONNECTION | &TRANSFER_ENCODING | &UPGRADE => continue,
|
||||||
CONNECTION | TRANSFER_ENCODING => continue,
|
|
||||||
CONTENT_LENGTH if skip_len => continue,
|
&CONTENT_LENGTH if skip_len => continue,
|
||||||
DATE => has_date = true,
|
&DATE => has_date = true,
|
||||||
|
|
||||||
|
// omit HTTP/1.x only headers according to:
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2
|
||||||
|
hdr if hdr == HeaderName::from_static("keep-alive")
|
||||||
|
|| hdr == HeaderName::from_static("proxy-connection") =>
|
||||||
|
{
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ use actix_service::{
|
|||||||
};
|
};
|
||||||
use actix_utils::future::ready;
|
use actix_utils::future::ready;
|
||||||
use futures_core::{future::LocalBoxFuture, ready};
|
use futures_core::{future::LocalBoxFuture, ready};
|
||||||
use log::error;
|
use tracing::{error, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BoxBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
@ -355,7 +355,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::trace!("H2 handshake error: {}", err);
|
trace!("H2 handshake error: {}", err);
|
||||||
Poll::Ready(Err(err))
|
Poll::Ready(Err(err))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
//! ## Crate Features
|
//! ## Crate Features
|
||||||
//! | Feature | Functionality |
|
//! | Feature | Functionality |
|
||||||
//! | ------------------- | ------------------------------------------- |
|
//! | ------------------- | ------------------------------------------- |
|
||||||
|
//! | `http2` | HTTP/2 support via [h2]. |
|
||||||
//! | `openssl` | TLS support via [OpenSSL]. |
|
//! | `openssl` | TLS support via [OpenSSL]. |
|
||||||
//! | `rustls` | TLS support via [rustls]. |
|
//! | `rustls` | TLS support via [rustls]. |
|
||||||
//! | `compress-brotli` | Payload compression support: Brotli. |
|
//! | `compress-brotli` | Payload compression support: Brotli. |
|
||||||
@ -10,6 +11,7 @@
|
|||||||
//! | `compress-zstd` | Payload compression support: Zstd. |
|
//! | `compress-zstd` | Payload compression support: Zstd. |
|
||||||
//! | `trust-dns` | Use [trust-dns] as the client DNS resolver. |
|
//! | `trust-dns` | Use [trust-dns] as the client DNS resolver. |
|
||||||
//!
|
//!
|
||||||
|
//! [h2]: https://crates.io/crates/h2
|
||||||
//! [OpenSSL]: https://crates.io/crates/openssl
|
//! [OpenSSL]: https://crates.io/crates/openssl
|
||||||
//! [rustls]: https://crates.io/crates/rustls
|
//! [rustls]: https://crates.io/crates/rustls
|
||||||
//! [trust-dns]: https://crates.io/crates/trust-dns
|
//! [trust-dns]: https://crates.io/crates/trust-dns
|
||||||
|
@ -13,7 +13,8 @@ use crate::error::PayloadError;
|
|||||||
/// A boxed payload stream.
|
/// A boxed payload stream.
|
||||||
pub type BoxedPayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>;
|
pub type BoxedPayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>;
|
||||||
|
|
||||||
#[deprecated(since = "4.0.0", note = "Renamed to `BoxedPayloadStream`.")]
|
#[doc(hidden)]
|
||||||
|
#[deprecated(since = "3.0.0", note = "Renamed to `BoxedPayloadStream`.")]
|
||||||
pub type PayloadStream = BoxedPayloadStream;
|
pub type PayloadStream = BoxedPayloadStream;
|
||||||
|
|
||||||
#[cfg(not(feature = "http2"))]
|
#[cfg(not(feature = "http2"))]
|
||||||
|
@ -144,7 +144,7 @@ impl ResponseBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set connection type to Upgrade
|
/// Set connection type to `Upgrade`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
|
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
|
||||||
where
|
where
|
||||||
@ -161,7 +161,7 @@ impl ResponseBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force close connection, even if it is marked as keep-alive
|
/// Force-close connection, even if it is marked as keep-alive.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn force_close(&mut self) -> &mut Self {
|
pub fn force_close(&mut self) -> &mut Self {
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
|
@ -285,6 +285,24 @@ impl From<&'static [u8]> for Response<&'static [u8]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for Response<Vec<u8>> {
|
||||||
|
fn from(val: Vec<u8>) -> Self {
|
||||||
|
let mut res = Response::with_body(StatusCode::OK, val);
|
||||||
|
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
|
||||||
|
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Vec<u8>> for Response<Vec<u8>> {
|
||||||
|
fn from(val: &Vec<u8>) -> Self {
|
||||||
|
let mut res = Response::with_body(StatusCode::OK, val.clone());
|
||||||
|
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
|
||||||
|
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<String> for Response<String> {
|
impl From<String> for Response<String> {
|
||||||
fn from(val: String) -> Self {
|
fn from(val: String) -> Self {
|
||||||
let mut res = Response::with_body(StatusCode::OK, val);
|
let mut res = Response::with_body(StatusCode::OK, val);
|
||||||
|
@ -15,6 +15,7 @@ use actix_service::{
|
|||||||
};
|
};
|
||||||
use futures_core::{future::LocalBoxFuture, ready};
|
use futures_core::{future::LocalBoxFuture, ready};
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BoxBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
@ -369,13 +370,13 @@ where
|
|||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let expect = expect
|
let expect = expect
|
||||||
.await
|
.await
|
||||||
.map_err(|e| log::error!("Init http expect service error: {:?}", e))?;
|
.map_err(|e| error!("Init http expect service error: {:?}", e))?;
|
||||||
|
|
||||||
let upgrade = match upgrade {
|
let upgrade = match upgrade {
|
||||||
Some(upgrade) => {
|
Some(upgrade) => {
|
||||||
let upgrade = upgrade
|
let upgrade = upgrade
|
||||||
.await
|
.await
|
||||||
.map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?;
|
.map_err(|e| error!("Init http upgrade service error: {:?}", e))?;
|
||||||
Some(upgrade)
|
Some(upgrade)
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
@ -383,7 +384,7 @@ where
|
|||||||
|
|
||||||
let service = service
|
let service = service
|
||||||
.await
|
.await
|
||||||
.map_err(|e| log::error!("Init http service error: {:?}", e))?;
|
.map_err(|e| error!("Init http service error: {:?}", e))?;
|
||||||
|
|
||||||
Ok(HttpServiceHandler::new(
|
Ok(HttpServiceHandler::new(
|
||||||
cfg,
|
cfg,
|
||||||
@ -490,7 +491,7 @@ where
|
|||||||
|
|
||||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
self._poll_ready(cx).map_err(|err| {
|
self._poll_ready(cx).map_err(|err| {
|
||||||
log::error!("HTTP service readiness error: {:?}", err);
|
error!("HTTP service readiness error: {:?}", err);
|
||||||
DispatchError::Service(err)
|
DispatchError::Service(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -666,7 +667,7 @@ where
|
|||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::trace!("H2 handshake error: {}", err);
|
tracing::trace!("H2 handshake error: {}", err);
|
||||||
Poll::Ready(Err(err))
|
Poll::Ready(Err(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,29 +19,7 @@ use crate::{
|
|||||||
Request,
|
Request,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Test `Request` builder
|
/// Test `Request` builder.
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// # use http::{header, StatusCode};
|
|
||||||
/// # use actix_web::*;
|
|
||||||
/// use actix_web::test::TestRequest;
|
|
||||||
///
|
|
||||||
/// fn index(req: &HttpRequest) -> Response {
|
|
||||||
/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
|
|
||||||
/// Response::Ok().into()
|
|
||||||
/// } else {
|
|
||||||
/// Response::BadRequest().into()
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// let resp = TestRequest::default().insert_header("content-type", "text/plain")
|
|
||||||
/// .run(&index)
|
|
||||||
/// .unwrap();
|
|
||||||
/// assert_eq!(resp.status(), StatusCode::OK);
|
|
||||||
///
|
|
||||||
/// let resp = TestRequest::default().run(&index).unwrap();
|
|
||||||
/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
|
||||||
/// ```
|
|
||||||
pub struct TestRequest(Option<Inner>);
|
pub struct TestRequest(Option<Inner>);
|
||||||
|
|
||||||
struct Inner {
|
struct Inner {
|
||||||
|
@ -2,6 +2,7 @@ use actix_codec::{Decoder, Encoder};
|
|||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use bytestring::ByteString;
|
use bytestring::ByteString;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
frame::Parser,
|
frame::Parser,
|
||||||
@ -253,7 +254,7 @@ impl Decoder for Codec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
log::error!("Unfinished fragment {:?}", opcode);
|
error!("Unfinished fragment {:?}", opcode);
|
||||||
Err(ProtocolError::ContinuationFragment(opcode))
|
Err(ProtocolError::ContinuationFragment(opcode))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -73,8 +73,8 @@ mod inner {
|
|||||||
use actix_service::{IntoService, Service};
|
use actix_service::{IntoService, Service};
|
||||||
use futures_core::stream::Stream;
|
use futures_core::stream::Stream;
|
||||||
use local_channel::mpsc;
|
use local_channel::mpsc;
|
||||||
use log::debug;
|
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
|
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use bytes::{Buf, BufMut, BytesMut};
|
use bytes::{Buf, BufMut, BytesMut};
|
||||||
use log::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
mask::apply_mask,
|
mask::apply_mask,
|
||||||
|
@ -47,40 +47,6 @@ pub fn apply_mask_fast32(buf: &mut [u8], mask: [u8; 4]) {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
// legacy test from old apply mask test. kept for now for back compat test.
|
|
||||||
// TODO: remove it and favor the other test.
|
|
||||||
#[test]
|
|
||||||
fn test_apply_mask_legacy() {
|
|
||||||
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
|
||||||
|
|
||||||
let unmasked = vec![
|
|
||||||
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9,
|
|
||||||
0x12, 0x03,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Check masking with proper alignment.
|
|
||||||
{
|
|
||||||
let mut masked = unmasked.clone();
|
|
||||||
apply_mask_fallback(&mut masked, mask);
|
|
||||||
|
|
||||||
let mut masked_fast = unmasked.clone();
|
|
||||||
apply_mask(&mut masked_fast, mask);
|
|
||||||
|
|
||||||
assert_eq!(masked, masked_fast);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check masking without alignment.
|
|
||||||
{
|
|
||||||
let mut masked = unmasked.clone();
|
|
||||||
apply_mask_fallback(&mut masked[1..], mask);
|
|
||||||
|
|
||||||
let mut masked_fast = unmasked;
|
|
||||||
apply_mask(&mut masked_fast[1..], mask);
|
|
||||||
|
|
||||||
assert_eq!(masked, masked_fast);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_mask() {
|
fn test_apply_mask() {
|
||||||
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
||||||
|
@ -3,6 +3,8 @@ use std::{
|
|||||||
fmt,
|
fmt,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
/// Operation codes defined in [RFC 6455 §11.8].
|
/// Operation codes defined in [RFC 6455 §11.8].
|
||||||
///
|
///
|
||||||
/// [RFC 6455]: https://datatracker.ietf.org/doc/html/rfc6455#section-11.8
|
/// [RFC 6455]: https://datatracker.ietf.org/doc/html/rfc6455#section-11.8
|
||||||
@ -58,7 +60,7 @@ impl From<OpCode> for u8 {
|
|||||||
Ping => 9,
|
Ping => 9,
|
||||||
Pong => 10,
|
Pong => 10,
|
||||||
Bad => {
|
Bad => {
|
||||||
log::error!("Attempted to convert invalid opcode to u8. This is a bug.");
|
error!("Attempted to convert invalid opcode to u8. This is a bug.");
|
||||||
8 // if this somehow happens, a close frame will help us tear down quickly
|
8 // if this somehow happens, a close frame will help us tear down quickly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ fn tls_config() -> SslAcceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2() -> io::Result<()> {
|
async fn h2() -> io::Result<()> {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| ok::<_, Error>(Response::ok()))
|
.h2(|_| ok::<_, Error>(Response::ok()))
|
||||||
@ -81,7 +81,7 @@ async fn test_h2() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_1() -> io::Result<()> {
|
async fn h2_1() -> io::Result<()> {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.finish(|req: Request| {
|
.finish(|req: Request| {
|
||||||
@ -100,7 +100,7 @@ async fn test_h2_1() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_body() -> io::Result<()> {
|
async fn h2_body() -> io::Result<()> {
|
||||||
let data = "HELLOWORLD".to_owned().repeat(64 * 1024); // 640 KiB
|
let data = "HELLOWORLD".to_owned().repeat(64 * 1024); // 640 KiB
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
@ -122,7 +122,7 @@ async fn test_h2_body() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_content_length() {
|
async fn h2_content_length() {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|req: Request| {
|
.h2(|req: Request| {
|
||||||
@ -164,7 +164,7 @@ async fn test_h2_content_length() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_headers() {
|
async fn h2_headers() {
|
||||||
let data = STR.repeat(10);
|
let data = STR.repeat(10);
|
||||||
let data2 = data.clone();
|
let data2 = data.clone();
|
||||||
|
|
||||||
@ -229,7 +229,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
|
|||||||
Hello World Hello World Hello World Hello World Hello World";
|
Hello World Hello World Hello World Hello World Hello World";
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_body2() {
|
async fn h2_body2() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -247,7 +247,7 @@ async fn test_h2_body2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_head_empty() {
|
async fn h2_head_empty() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -271,7 +271,7 @@ async fn test_h2_head_empty() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_head_binary() {
|
async fn h2_head_binary() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -294,7 +294,7 @@ async fn test_h2_head_binary() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_head_binary2() {
|
async fn h2_head_binary2() {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -313,7 +313,7 @@ async fn test_h2_head_binary2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_body_length() {
|
async fn h2_body_length() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| async {
|
.h2(|_| async {
|
||||||
@ -338,7 +338,7 @@ async fn test_h2_body_length() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_body_chunked_explicit() {
|
async fn h2_body_chunked_explicit() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| {
|
.h2(|_| {
|
||||||
@ -366,7 +366,7 @@ async fn test_h2_body_chunked_explicit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_response_http_error_handling() {
|
async fn h2_response_http_error_handling() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(fn_service(|_| {
|
.h2(fn_service(|_| {
|
||||||
@ -406,7 +406,7 @@ impl From<BadRequest> for Response<BoxBody> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_service_error() {
|
async fn h2_service_error() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| err::<Response<BoxBody>, _>(BadRequest))
|
.h2(|_| err::<Response<BoxBody>, _>(BadRequest))
|
||||||
@ -424,7 +424,7 @@ async fn test_h2_service_error() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_on_connect() {
|
async fn h2_on_connect() {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.on_connect_ext(|_, data| {
|
.on_connect_ext(|_, data| {
|
||||||
|
@ -106,7 +106,7 @@ pub fn get_negotiated_alpn_protocol(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1() -> io::Result<()> {
|
async fn h1() -> io::Result<()> {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| ok::<_, Error>(Response::ok()))
|
.h1(|_| ok::<_, Error>(Response::ok()))
|
||||||
@ -120,7 +120,7 @@ async fn test_h1() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2() -> io::Result<()> {
|
async fn h2() -> io::Result<()> {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| ok::<_, Error>(Response::ok()))
|
.h2(|_| ok::<_, Error>(Response::ok()))
|
||||||
@ -134,7 +134,7 @@ async fn test_h2() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_1() -> io::Result<()> {
|
async fn h1_1() -> io::Result<()> {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|req: Request| {
|
.h1(|req: Request| {
|
||||||
@ -152,7 +152,7 @@ async fn test_h1_1() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_1() -> io::Result<()> {
|
async fn h2_1() -> io::Result<()> {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.finish(|req: Request| {
|
.finish(|req: Request| {
|
||||||
@ -170,7 +170,7 @@ async fn test_h2_1() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_body1() -> io::Result<()> {
|
async fn h2_body1() -> io::Result<()> {
|
||||||
let data = "HELLOWORLD".to_owned().repeat(64 * 1024);
|
let data = "HELLOWORLD".to_owned().repeat(64 * 1024);
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
@ -191,7 +191,7 @@ async fn test_h2_body1() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_content_length() {
|
async fn h2_content_length() {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|req: Request| {
|
.h2(|req: Request| {
|
||||||
@ -212,6 +212,7 @@ async fn test_h2_content_length() {
|
|||||||
let value = HeaderValue::from_static("0");
|
let value = HeaderValue::from_static("0");
|
||||||
|
|
||||||
{
|
{
|
||||||
|
#[allow(clippy::single_element_loop)]
|
||||||
for &i in &[0] {
|
for &i in &[0] {
|
||||||
let req = srv
|
let req = srv
|
||||||
.request(Method::HEAD, srv.surl(&format!("/{}", i)))
|
.request(Method::HEAD, srv.surl(&format!("/{}", i)))
|
||||||
@ -226,6 +227,7 @@ async fn test_h2_content_length() {
|
|||||||
// assert_eq!(response.headers().get(&header), None);
|
// assert_eq!(response.headers().get(&header), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::single_element_loop)]
|
||||||
for &i in &[1] {
|
for &i in &[1] {
|
||||||
let req = srv
|
let req = srv
|
||||||
.request(Method::GET, srv.surl(&format!("/{}", i)))
|
.request(Method::GET, srv.surl(&format!("/{}", i)))
|
||||||
@ -245,7 +247,7 @@ async fn test_h2_content_length() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_headers() {
|
async fn h2_headers() {
|
||||||
let data = STR.repeat(10);
|
let data = STR.repeat(10);
|
||||||
let data2 = data.clone();
|
let data2 = data.clone();
|
||||||
|
|
||||||
@ -309,7 +311,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
|
|||||||
Hello World Hello World Hello World Hello World Hello World";
|
Hello World Hello World Hello World Hello World Hello World";
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_body2() {
|
async fn h2_body2() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -326,7 +328,7 @@ async fn test_h2_body2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_head_empty() {
|
async fn h2_head_empty() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -352,7 +354,7 @@ async fn test_h2_head_empty() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_head_binary() {
|
async fn h2_head_binary() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -377,7 +379,7 @@ async fn test_h2_head_binary() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_head_binary2() {
|
async fn h2_head_binary2() {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -398,7 +400,7 @@ async fn test_h2_head_binary2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_body_length() {
|
async fn h2_body_length() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| {
|
.h2(|_| {
|
||||||
@ -420,7 +422,7 @@ async fn test_h2_body_length() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_body_chunked_explicit() {
|
async fn h2_body_chunked_explicit() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| {
|
.h2(|_| {
|
||||||
@ -447,7 +449,7 @@ async fn test_h2_body_chunked_explicit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_response_http_error_handling() {
|
async fn h2_response_http_error_handling() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(fn_factory_with_config(|_: ()| {
|
.h2(fn_factory_with_config(|_: ()| {
|
||||||
@ -486,7 +488,7 @@ impl From<BadRequest> for Response<BoxBody> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_service_error() {
|
async fn h2_service_error() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| err::<Response<BoxBody>, _>(BadRequest))
|
.h2(|_| err::<Response<BoxBody>, _>(BadRequest))
|
||||||
@ -503,7 +505,7 @@ async fn test_h2_service_error() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_service_error() {
|
async fn h1_service_error() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| err::<Response<BoxBody>, _>(BadRequest))
|
.h1(|_| err::<Response<BoxBody>, _>(BadRequest))
|
||||||
@ -524,7 +526,7 @@ const HTTP1_1_ALPN_PROTOCOL: &[u8] = b"http/1.1";
|
|||||||
const CUSTOM_ALPN_PROTOCOL: &[u8] = b"custom";
|
const CUSTOM_ALPN_PROTOCOL: &[u8] = b"custom";
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_alpn_h1() -> io::Result<()> {
|
async fn alpn_h1() -> io::Result<()> {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
let mut config = tls_config();
|
let mut config = tls_config();
|
||||||
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
|
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
|
||||||
@ -546,7 +548,7 @@ async fn test_alpn_h1() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_alpn_h2() -> io::Result<()> {
|
async fn alpn_h2() -> io::Result<()> {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
let mut config = tls_config();
|
let mut config = tls_config();
|
||||||
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
|
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
|
||||||
@ -572,7 +574,7 @@ async fn test_alpn_h2() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_alpn_h2_1() -> io::Result<()> {
|
async fn alpn_h2_1() -> io::Result<()> {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
let mut config = tls_config();
|
let mut config = tls_config();
|
||||||
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
|
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
|
||||||
|
@ -850,7 +850,8 @@ async fn not_modified_spec_h1() {
|
|||||||
Some(&header::HeaderValue::from_static("4")),
|
Some(&header::HeaderValue::from_static("4")),
|
||||||
);
|
);
|
||||||
// server does not prevent payload from being sent but clients may choose not to read it
|
// server does not prevent payload from being sent but clients may choose not to read it
|
||||||
// TODO: this is probably a bug, especially since CL header can differ in length from the body
|
// TODO: this is probably a bug in the client, especially since CL header can differ in length
|
||||||
|
// from the body
|
||||||
assert!(!srv.load_body(res).await.unwrap().is_empty());
|
assert!(!srv.load_body(res).await.unwrap().is_empty());
|
||||||
|
|
||||||
// TODO: add stream response tests
|
// TODO: add stream response tests
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.0 - 2022-02-25
|
||||||
|
- No significant changes since `0.4.0-beta.13`.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.13 - 2022-01-31
|
## 0.4.0-beta.13 - 2022-01-31
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-multipart"
|
name = "actix-multipart"
|
||||||
version = "0.4.0-beta.13"
|
version = "0.4.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Multipart form support for Actix Web"
|
description = "Multipart form support for Actix Web"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
@ -14,8 +14,8 @@ name = "actix_multipart"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3"
|
||||||
actix-web = { version = "4.0.0-rc.1", default-features = false }
|
actix-web = { version = "4", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
@ -28,7 +28,7 @@ twoway = "0.2"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-http = "3.0.0-rc.1"
|
actix-http = "3.0.0"
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
tokio = { version = "1.8.4", features = ["sync"] }
|
tokio = { version = "1.8.4", features = ["sync"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
> Multipart form support for Actix Web.
|
> Multipart form support for Actix Web.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-multipart)
|
[](https://crates.io/crates/actix-multipart)
|
||||||
[](https://docs.rs/actix-multipart/0.4.0-beta.13)
|
[](https://docs.rs/actix-multipart/0.4.0)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.13)
|
[](https://deps.rs/crate/actix-multipart/0.4.0)
|
||||||
[](https://crates.io/crates/actix-multipart)
|
[](https://crates.io/crates/actix-multipart)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -1,8 +1,80 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0 - 2022-02-22
|
||||||
|
### Added
|
||||||
|
- Add `Path::as_str`. [#2590]
|
||||||
|
- Add `ResourceDef::set_name`. [#373][net#373]
|
||||||
|
- Add `RouterBuilder::push`. [#2612]
|
||||||
|
- Implement `IntoPatterns` for `bytestring::ByteString`. [#372][net#372]
|
||||||
|
- Introduce `ResourceDef::join`. [#380][net#380]
|
||||||
|
- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373][net#373]
|
||||||
|
- `Resource` is now implemented for `&mut Path<_>` and `RefMut<Path<_>>`. [#2568]
|
||||||
|
- Support `build_resource_path` on multi-pattern resources. [#2356]
|
||||||
|
- Support multi-pattern prefixes and joins. [#2356]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Change signature of `ResourceDef::capture_match_info_fn` to remove `user_data` parameter. [#2612]
|
||||||
|
- Deprecate `Path::path`. [#2590]
|
||||||
|
- Disallow prefix routes with tail segments. [#379][net#379]
|
||||||
|
- Enforce path separators on dynamic prefixes. [#378][net#378]
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||||
|
- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355]
|
||||||
|
- Prefix segments with trailing slashes define a trailing empty segment. [#2355]
|
||||||
|
- `Quoter::requote` now returns `Option<Vec<u8>>`. [#2613]
|
||||||
|
- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372][net#372]
|
||||||
|
- Rename `Path::{len => segment_count}` to be more descriptive of its purpose. [#370][net#370]
|
||||||
|
- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373][net#373]
|
||||||
|
- Rename `ResourceDef::{match_path => capture_match_info}`. [#373][net#373]
|
||||||
|
- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373][net#373]
|
||||||
|
- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371][net#371]
|
||||||
|
- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371][net#371]
|
||||||
|
- Rename `Router::{*_checked => *_fn}`. [#373][net#373]
|
||||||
|
- Replace `Option<U>` with `U` in `Router` API. [#2612]
|
||||||
|
- `Resource` trait now uses an associated type, `Path`, instead of a generic parameter. [#2568]
|
||||||
|
- `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356]
|
||||||
|
- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373][net#373]
|
||||||
|
- Return type of `ResourceDef::name` is now `Option<&str>`. [#373][net#373]
|
||||||
|
- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373][net#373]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix `ResourceDef`'s `PartialEq` implementation. [#373][net#373]
|
||||||
|
- Fix segment interpolation leaving `Path` in unintended state after matching. [#368][net#368]
|
||||||
|
- Improve malformed path error message. [#384][net#384]
|
||||||
|
- `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#2566]
|
||||||
|
- Relax bounds on `Router::recognize*` and `ResourceDef::capture_match_info`. [#2612]
|
||||||
|
- Static patterns in multi-patterns are no longer interpreted as regex. [#366][net#366]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `ResourceDef::name_mut`. [#373][net#373]
|
||||||
|
- Unused `ResourceInfo`. [#2612]
|
||||||
|
|
||||||
|
[#2355]: https://github.com/actix/actix-web/pull/2355
|
||||||
|
[#2356]: https://github.com/actix/actix-web/pull/2356
|
||||||
|
[#2566]: https://github.com/actix/actix-net/pull/2566
|
||||||
|
[#2568]: https://github.com/actix/actix-web/pull/2568
|
||||||
|
[#2590]: https://github.com/actix/actix-web/pull/2590
|
||||||
|
[#2612]: https://github.com/actix/actix-web/pull/2612
|
||||||
|
[#2613]: https://github.com/actix/actix-web/pull/2613
|
||||||
|
[net#366]: https://github.com/actix/actix-net/pull/366
|
||||||
|
[net#368]: https://github.com/actix/actix-net/pull/368
|
||||||
|
[net#368]: https://github.com/actix/actix-net/pull/368
|
||||||
|
[net#370]: https://github.com/actix/actix-net/pull/370
|
||||||
|
[net#371]: https://github.com/actix/actix-net/pull/371
|
||||||
|
[net#372]: https://github.com/actix/actix-net/pull/372
|
||||||
|
[net#373]: https://github.com/actix/actix-net/pull/373
|
||||||
|
[net#378]: https://github.com/actix/actix-net/pull/378
|
||||||
|
[net#379]: https://github.com/actix/actix-net/pull/379
|
||||||
|
[net#380]: https://github.com/actix/actix-net/pull/380
|
||||||
|
[net#384]: https://github.com/actix/actix-net/pull/384
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>0.5.0 Pre-Releases</summary>
|
||||||
|
|
||||||
## 0.5.0-rc.3 - 2022-01-31
|
## 0.5.0-rc.3 - 2022-01-31
|
||||||
- Remove unused `ResourceInfo`. [#2612]
|
- Remove unused `ResourceInfo`. [#2612]
|
||||||
- Add `RouterBuilder::push`. [#2612]
|
- Add `RouterBuilder::push`. [#2612]
|
||||||
@ -41,10 +113,10 @@
|
|||||||
|
|
||||||
|
|
||||||
## 0.5.0-beta.2 - 2021-09-09
|
## 0.5.0-beta.2 - 2021-09-09
|
||||||
- Introduce `ResourceDef::join`. [#380]
|
- Introduce `ResourceDef::join`. [#380][net#380]
|
||||||
- Disallow prefix routes with tail segments. [#379]
|
- Disallow prefix routes with tail segments. [#379][net#379]
|
||||||
- Enforce path separators on dynamic prefixes. [#378]
|
- Enforce path separators on dynamic prefixes. [#378][net#378]
|
||||||
- Improve malformed path error message. [#384]
|
- Improve malformed path error message. [#384][net#384]
|
||||||
- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355]
|
- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355]
|
||||||
- Prefix segments with trailing slashes define a trailing empty segment. [#2355]
|
- Prefix segments with trailing slashes define a trailing empty segment. [#2355]
|
||||||
- Support multi-pattern prefixes and joins. [#2356]
|
- Support multi-pattern prefixes and joins. [#2356]
|
||||||
@ -52,52 +124,54 @@
|
|||||||
- Support `build_resource_path` on multi-pattern resources. [#2356]
|
- Support `build_resource_path` on multi-pattern resources. [#2356]
|
||||||
- Minimum supported Rust version (MSRV) is now 1.51.
|
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
[#378]: https://github.com/actix/actix-net/pull/378
|
[net#378]: https://github.com/actix/actix-net/pull/378
|
||||||
[#379]: https://github.com/actix/actix-net/pull/379
|
[net#379]: https://github.com/actix/actix-net/pull/379
|
||||||
[#380]: https://github.com/actix/actix-net/pull/380
|
[net#380]: https://github.com/actix/actix-net/pull/380
|
||||||
[#384]: https://github.com/actix/actix-net/pull/384
|
[net#384]: https://github.com/actix/actix-net/pull/384
|
||||||
[#2355]: https://github.com/actix/actix-web/pull/2355
|
[#2355]: https://github.com/actix/actix-web/pull/2355
|
||||||
[#2356]: https://github.com/actix/actix-web/pull/2356
|
[#2356]: https://github.com/actix/actix-web/pull/2356
|
||||||
|
|
||||||
|
|
||||||
## 0.5.0-beta.1 - 2021-07-20
|
## 0.5.0-beta.1 - 2021-07-20
|
||||||
- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366]
|
- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366][net#366]
|
||||||
- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373]
|
- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373][net#373]
|
||||||
- Fix segment interpolation leaving `Path` in unintended state after matching. [#368]
|
- Fix segment interpolation leaving `Path` in unintended state after matching. [#368][net#368]
|
||||||
- Fix `ResourceDef` `PartialEq` implementation. [#373]
|
- Fix `ResourceDef` `PartialEq` implementation. [#373][net#373]
|
||||||
- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372]
|
- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372][net#372]
|
||||||
- Implement `IntoPatterns` for `bytestring::ByteString`. [#372]
|
- Implement `IntoPatterns` for `bytestring::ByteString`. [#372][net#372]
|
||||||
- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370]
|
- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370][net#370]
|
||||||
- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371]
|
- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371][net#371]
|
||||||
- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373]
|
- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373][net#373]
|
||||||
- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371]
|
- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371][net#371]
|
||||||
- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373]
|
- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373][net#373]
|
||||||
- Rename `ResourceDef::{match_path => capture_match_info}`. [#373]
|
- Rename `ResourceDef::{match_path => capture_match_info}`. [#373][net#373]
|
||||||
- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373]
|
- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373][net#373]
|
||||||
- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373]
|
- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373][net#373]
|
||||||
- Rename `Router::{*_checked => *_fn}`. [#373]
|
- Rename `Router::{*_checked => *_fn}`. [#373][net#373]
|
||||||
- Return type of `ResourceDef::name` is now `Option<&str>`. [#373]
|
- Return type of `ResourceDef::name` is now `Option<&str>`. [#373][net#373]
|
||||||
- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373]
|
- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373][net#373]
|
||||||
|
|
||||||
[#368]: https://github.com/actix/actix-net/pull/368
|
[net#368]: https://github.com/actix/actix-net/pull/368
|
||||||
[#366]: https://github.com/actix/actix-net/pull/366
|
[net#366]: https://github.com/actix/actix-net/pull/366
|
||||||
[#368]: https://github.com/actix/actix-net/pull/368
|
[net#368]: https://github.com/actix/actix-net/pull/368
|
||||||
[#370]: https://github.com/actix/actix-net/pull/370
|
[net#370]: https://github.com/actix/actix-net/pull/370
|
||||||
[#371]: https://github.com/actix/actix-net/pull/371
|
[net#371]: https://github.com/actix/actix-net/pull/371
|
||||||
[#372]: https://github.com/actix/actix-net/pull/372
|
[net#372]: https://github.com/actix/actix-net/pull/372
|
||||||
[#373]: https://github.com/actix/actix-net/pull/373
|
[net#373]: https://github.com/actix/actix-net/pull/373
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0 - 2021-06-06
|
## 0.4.0 - 2021-06-06
|
||||||
- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357]
|
- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357][net#357]
|
||||||
- Path tail patterns now match new lines (`\n`) in request URL. [#360]
|
- Path tail patterns now match new lines (`\n`) in request URL. [#360][net#360]
|
||||||
- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359]
|
- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359][net#359]
|
||||||
- Methods `Path::{add, add_static}` now take `impl Into<Cow<'static, str>>`. [#345]
|
- Methods `Path::{add, add_static}` now take `impl Into<Cow<'static, str>>`. [#345][net#345]
|
||||||
|
|
||||||
[#345]: https://github.com/actix/actix-net/pull/345
|
[net#345]: https://github.com/actix/actix-net/pull/345
|
||||||
[#357]: https://github.com/actix/actix-net/pull/357
|
[net#357]: https://github.com/actix/actix-net/pull/357
|
||||||
[#359]: https://github.com/actix/actix-net/pull/359
|
[net#359]: https://github.com/actix/actix-net/pull/359
|
||||||
[#360]: https://github.com/actix/actix-net/pull/360
|
[net#360]: https://github.com/actix/actix-net/pull/360
|
||||||
|
|
||||||
|
|
||||||
## 0.3.0 - 2019-12-31
|
## 0.3.0 - 2019-12-31
|
||||||
@ -105,15 +179,15 @@
|
|||||||
|
|
||||||
|
|
||||||
## 0.2.7 - 2021-02-06
|
## 0.2.7 - 2021-02-06
|
||||||
- Add `Router::recognize_checked` [#247]
|
- Add `Router::recognize_checked` [#247][net#247]
|
||||||
|
|
||||||
[#247]: https://github.com/actix/actix-net/pull/247
|
[net#247]: https://github.com/actix/actix-net/pull/247
|
||||||
|
|
||||||
|
|
||||||
## 0.2.6 - 2021-01-09
|
## 0.2.6 - 2021-01-09
|
||||||
- Use `bytestring` version range compatible with Bytes v1.0. [#246]
|
- Use `bytestring` version range compatible with Bytes v1.0. [#246][net#246]
|
||||||
|
|
||||||
[#246]: https://github.com/actix/actix-net/pull/246
|
[net#246]: https://github.com/actix/actix-net/pull/246
|
||||||
|
|
||||||
|
|
||||||
## 0.2.5 - 2020-09-20
|
## 0.2.5 - 2020-09-20
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.5.0-rc.3"
|
version = "0.5.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||||
@ -21,18 +21,21 @@ default = ["http"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytestring = ">=0.1.5, <2"
|
bytestring = ">=0.1.5, <2"
|
||||||
firestorm = "0.5"
|
|
||||||
http = { version = "0.2.3", optional = true }
|
http = { version = "0.2.3", optional = true }
|
||||||
log = "0.4"
|
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
|
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
firestorm = { version = "0.5", features = ["enable_system_time"] }
|
|
||||||
http = "0.2.5"
|
http = "0.2.5"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
percent-encoding = "2.1"
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "router"
|
name = "router"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "quoter"
|
||||||
|
harness = false
|
||||||
|
52
actix-router/benches/quoter.rs
Normal file
52
actix-router/benches/quoter.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
fn compare_quoters(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("Compare Quoters");
|
||||||
|
|
||||||
|
let quoter = actix_router::Quoter::new(b"", b"");
|
||||||
|
let path_quoted = (0..=0x7f)
|
||||||
|
.map(|c| format!("%{:02X}", c))
|
||||||
|
.collect::<String>();
|
||||||
|
let path_unquoted = ('\u{00}'..='\u{7f}').collect::<String>();
|
||||||
|
|
||||||
|
group.bench_function("quoter_unquoted", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
for _ in 0..10 {
|
||||||
|
black_box(quoter.requote(path_unquoted.as_bytes()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group.bench_function("percent_encode_unquoted", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
for _ in 0..10 {
|
||||||
|
let decode = percent_encoding::percent_decode(path_unquoted.as_bytes());
|
||||||
|
black_box(Into::<Cow<'_, [u8]>>::into(decode));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group.bench_function("quoter_quoted", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
for _ in 0..10 {
|
||||||
|
black_box(quoter.requote(path_quoted.as_bytes()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group.bench_function("percent_encode_quoted", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
for _ in 0..10 {
|
||||||
|
let decode = percent_encoding::percent_decode(path_quoted.as_bytes());
|
||||||
|
black_box(Into::<Cow<'_, [u8]>>::into(decode));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, compare_quoters);
|
||||||
|
criterion_main!(benches);
|
@ -145,7 +145,8 @@ macro_rules! register {
|
|||||||
concat!("/user/keys"),
|
concat!("/user/keys"),
|
||||||
concat!("/user/keys/", $p1),
|
concat!("/user/keys/", $p1),
|
||||||
];
|
];
|
||||||
std::array::IntoIter::new(arr)
|
|
||||||
|
IntoIterator::into_iter(arr)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +159,7 @@ fn call() -> impl Iterator<Item = &'static str> {
|
|||||||
"/repos/rust-lang/rust/releases/1.51.0",
|
"/repos/rust-lang/rust/releases/1.51.0",
|
||||||
];
|
];
|
||||||
|
|
||||||
std::array::IntoIter::new(arr)
|
IntoIterator::into_iter(arr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compare_routers(c: &mut Criterion) {
|
fn compare_routers(c: &mut Criterion) {
|
||||||
|
@ -1,169 +0,0 @@
|
|||||||
macro_rules! register {
|
|
||||||
(brackets) => {{
|
|
||||||
register!(finish => "{p1}", "{p2}", "{p3}", "{p4}")
|
|
||||||
}};
|
|
||||||
(finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
|
|
||||||
let arr = [
|
|
||||||
concat!("/authorizations"),
|
|
||||||
concat!("/authorizations/", $p1),
|
|
||||||
concat!("/applications/", $p1, "/tokens/", $p2),
|
|
||||||
concat!("/events"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/events"),
|
|
||||||
concat!("/networks/", $p1, "/", $p2, "/events"),
|
|
||||||
concat!("/orgs/", $p1, "/events"),
|
|
||||||
concat!("/users/", $p1, "/received_events"),
|
|
||||||
concat!("/users/", $p1, "/received_events/public"),
|
|
||||||
concat!("/users/", $p1, "/events"),
|
|
||||||
concat!("/users/", $p1, "/events/public"),
|
|
||||||
concat!("/users/", $p1, "/events/orgs/", $p2),
|
|
||||||
concat!("/feeds"),
|
|
||||||
concat!("/notifications"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/notifications"),
|
|
||||||
concat!("/notifications/threads/", $p1),
|
|
||||||
concat!("/notifications/threads/", $p1, "/subscription"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stargazers"),
|
|
||||||
concat!("/users/", $p1, "/starred"),
|
|
||||||
concat!("/user/starred"),
|
|
||||||
concat!("/user/starred/", $p1, "/", $p2),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/subscribers"),
|
|
||||||
concat!("/users/", $p1, "/subscriptions"),
|
|
||||||
concat!("/user/subscriptions"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/subscription"),
|
|
||||||
concat!("/user/subscriptions/", $p1, "/", $p2),
|
|
||||||
concat!("/users/", $p1, "/gists"),
|
|
||||||
concat!("/gists"),
|
|
||||||
concat!("/gists/", $p1),
|
|
||||||
concat!("/gists/", $p1, "/star"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/blobs/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/commits/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/refs"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/tags/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/trees/", $p3),
|
|
||||||
concat!("/issues"),
|
|
||||||
concat!("/user/issues"),
|
|
||||||
concat!("/orgs/", $p1, "/issues"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/assignees"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/assignees/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/comments"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/events"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/labels"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/labels/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/labels"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3, "/labels"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/milestones/"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3),
|
|
||||||
concat!("/emojis"),
|
|
||||||
concat!("/gitignore/templates"),
|
|
||||||
concat!("/gitignore/templates/", $p1),
|
|
||||||
concat!("/meta"),
|
|
||||||
concat!("/rate_limit"),
|
|
||||||
concat!("/users/", $p1, "/orgs"),
|
|
||||||
concat!("/user/orgs"),
|
|
||||||
concat!("/orgs/", $p1),
|
|
||||||
concat!("/orgs/", $p1, "/members"),
|
|
||||||
concat!("/orgs/", $p1, "/members", $p2),
|
|
||||||
concat!("/orgs/", $p1, "/public_members"),
|
|
||||||
concat!("/orgs/", $p1, "/public_members/", $p2),
|
|
||||||
concat!("/orgs/", $p1, "/teams"),
|
|
||||||
concat!("/teams/", $p1),
|
|
||||||
concat!("/teams/", $p1, "/members"),
|
|
||||||
concat!("/teams/", $p1, "/members", $p2),
|
|
||||||
concat!("/teams/", $p1, "/repos"),
|
|
||||||
concat!("/teams/", $p1, "/repos/", $p2, "/", $p3),
|
|
||||||
concat!("/user/teams"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/commits"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/files"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/merge"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/comments"),
|
|
||||||
concat!("/user/repos"),
|
|
||||||
concat!("/users/", $p1, "/repos"),
|
|
||||||
concat!("/orgs/", $p1, "/repos"),
|
|
||||||
concat!("/repositories"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/contributors"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/languages"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/teams"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/tags"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/branches"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/branches/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/collaborators"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/collaborators/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/comments"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3, "/comments"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/commits"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/readme"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/keys"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/keys", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/downloads"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/downloads", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/forks"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/hooks"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/hooks", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/releases"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3, "/assets"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/contributors"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/commit_activity"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/code_frequency"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/participation"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/punch_card"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/statuses/", $p3),
|
|
||||||
concat!("/search/repositories"),
|
|
||||||
concat!("/search/code"),
|
|
||||||
concat!("/search/issues"),
|
|
||||||
concat!("/search/users"),
|
|
||||||
concat!("/legacy/issues/search/", $p1, "/", $p2, "/", $p3, "/", $p4),
|
|
||||||
concat!("/legacy/repos/search/", $p1),
|
|
||||||
concat!("/legacy/user/search/", $p1),
|
|
||||||
concat!("/legacy/user/email/", $p1),
|
|
||||||
concat!("/users/", $p1),
|
|
||||||
concat!("/user"),
|
|
||||||
concat!("/users"),
|
|
||||||
concat!("/user/emails"),
|
|
||||||
concat!("/users/", $p1, "/followers"),
|
|
||||||
concat!("/user/followers"),
|
|
||||||
concat!("/users/", $p1, "/following"),
|
|
||||||
concat!("/user/following"),
|
|
||||||
concat!("/user/following/", $p1),
|
|
||||||
concat!("/users/", $p1, "/following", $p2),
|
|
||||||
concat!("/users/", $p1, "/keys"),
|
|
||||||
concat!("/user/keys"),
|
|
||||||
concat!("/user/keys/", $p1),
|
|
||||||
];
|
|
||||||
|
|
||||||
arr.to_vec()
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
static PATHS: [&str; 5] = [
|
|
||||||
"/authorizations",
|
|
||||||
"/user/repos",
|
|
||||||
"/repos/rust-lang/rust/stargazers",
|
|
||||||
"/orgs/rust-lang/public_members/nikomatsakis",
|
|
||||||
"/repos/rust-lang/rust/releases/1.51.0",
|
|
||||||
];
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut router = actix_router::Router::<bool>::build();
|
|
||||||
|
|
||||||
for route in register!(brackets) {
|
|
||||||
router.path(route, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actix = router.finish();
|
|
||||||
|
|
||||||
if firestorm::enabled() {
|
|
||||||
firestorm::bench("target", || {
|
|
||||||
for &route in &PATHS {
|
|
||||||
let mut path = actix_router::Path::new(route);
|
|
||||||
actix.recognize(&mut path).unwrap();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,7 +7,7 @@ use crate::path::{Path, PathIter};
|
|||||||
use crate::{Quoter, ResourcePath};
|
use crate::{Quoter, ResourcePath};
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static FULL_QUOTER: Quoter = Quoter::new(b"+/%", b"");
|
static FULL_QUOTER: Quoter = Quoter::new(b"", b"");
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! unsupported_type {
|
macro_rules! unsupported_type {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ops::{DerefMut, Index};
|
use std::ops::{DerefMut, Index};
|
||||||
|
|
||||||
use firestorm::profile_method;
|
|
||||||
use serde::de;
|
use serde::de;
|
||||||
|
|
||||||
use crate::{de::PathDeserializer, Resource, ResourcePath};
|
use crate::{de::PathDeserializer, Resource, ResourcePath};
|
||||||
@ -52,7 +51,6 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
/// Returns full path as a string.
|
/// Returns full path as a string.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
profile_method!(as_str);
|
|
||||||
self.path.path()
|
self.path.path()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +59,6 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
/// Returns empty string if no more is to be processed.
|
/// Returns empty string if no more is to be processed.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn unprocessed(&self) -> &str {
|
pub fn unprocessed(&self) -> &str {
|
||||||
profile_method!(unprocessed);
|
|
||||||
// clamp skip to path length
|
// clamp skip to path length
|
||||||
let skip = (self.skip as usize).min(self.as_str().len());
|
let skip = (self.skip as usize).min(self.as_str().len());
|
||||||
&self.path.path()[skip..]
|
&self.path.path()[skip..]
|
||||||
@ -72,8 +69,6 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
#[deprecated(since = "0.6.0", note = "Use `.as_str()` or `.unprocessed()`.")]
|
#[deprecated(since = "0.6.0", note = "Use `.as_str()` or `.unprocessed()`.")]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn path(&self) -> &str {
|
pub fn path(&self) -> &str {
|
||||||
profile_method!(path);
|
|
||||||
|
|
||||||
let skip = self.skip as usize;
|
let skip = self.skip as usize;
|
||||||
let path = self.path.path();
|
let path = self.path.path();
|
||||||
if skip <= path.len() {
|
if skip <= path.len() {
|
||||||
@ -86,8 +81,6 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
/// Set new path.
|
/// Set new path.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set(&mut self, path: T) {
|
pub fn set(&mut self, path: T) {
|
||||||
profile_method!(set);
|
|
||||||
|
|
||||||
self.skip = 0;
|
self.skip = 0;
|
||||||
self.path = path;
|
self.path = path;
|
||||||
self.segments.clear();
|
self.segments.clear();
|
||||||
@ -96,8 +89,6 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
/// Reset state.
|
/// Reset state.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
profile_method!(reset);
|
|
||||||
|
|
||||||
self.skip = 0;
|
self.skip = 0;
|
||||||
self.segments.clear();
|
self.segments.clear();
|
||||||
}
|
}
|
||||||
@ -105,13 +96,10 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
/// Skip first `n` chars in path.
|
/// Skip first `n` chars in path.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn skip(&mut self, n: u16) {
|
pub fn skip(&mut self, n: u16) {
|
||||||
profile_method!(skip);
|
|
||||||
self.skip += n;
|
self.skip += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add(&mut self, name: impl Into<Cow<'static, str>>, value: PathItem) {
|
pub(crate) fn add(&mut self, name: impl Into<Cow<'static, str>>, value: PathItem) {
|
||||||
profile_method!(add);
|
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
PathItem::Static(s) => self.segments.push((name.into(), PathItem::Static(s))),
|
PathItem::Static(s) => self.segments.push((name.into(), PathItem::Static(s))),
|
||||||
PathItem::Segment(begin, end) => self.segments.push((
|
PathItem::Segment(begin, end) => self.segments.push((
|
||||||
@ -127,8 +115,6 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
name: impl Into<Cow<'static, str>>,
|
name: impl Into<Cow<'static, str>>,
|
||||||
value: impl Into<Cow<'static, str>>,
|
value: impl Into<Cow<'static, str>>,
|
||||||
) {
|
) {
|
||||||
profile_method!(add_static);
|
|
||||||
|
|
||||||
self.segments
|
self.segments
|
||||||
.push((name.into(), PathItem::Static(value.into())));
|
.push((name.into(), PathItem::Static(value.into())));
|
||||||
}
|
}
|
||||||
@ -147,8 +133,6 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
|
|
||||||
/// Get matched parameter by name without type conversion
|
/// Get matched parameter by name without type conversion
|
||||||
pub fn get(&self, name: &str) -> Option<&str> {
|
pub fn get(&self, name: &str) -> Option<&str> {
|
||||||
profile_method!(get);
|
|
||||||
|
|
||||||
for (seg_name, val) in self.segments.iter() {
|
for (seg_name, val) in self.segments.iter() {
|
||||||
if name == seg_name {
|
if name == seg_name {
|
||||||
return match val {
|
return match val {
|
||||||
@ -167,8 +151,6 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
///
|
///
|
||||||
/// If keyed parameter is not available empty string is used as default value.
|
/// If keyed parameter is not available empty string is used as default value.
|
||||||
pub fn query(&self, key: &str) -> &str {
|
pub fn query(&self, key: &str) -> &str {
|
||||||
profile_method!(query);
|
|
||||||
|
|
||||||
if let Some(s) = self.get(key) {
|
if let Some(s) = self.get(key) {
|
||||||
s
|
s
|
||||||
} else {
|
} else {
|
||||||
@ -186,7 +168,6 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
|
|
||||||
/// Try to deserialize matching parameters to a specified type `U`
|
/// Try to deserialize matching parameters to a specified type `U`
|
||||||
pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result<U, de::value::Error> {
|
pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result<U, de::value::Error> {
|
||||||
profile_method!(load);
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(self))
|
de::Deserialize::deserialize(PathDeserializer::new(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,132 +1,89 @@
|
|||||||
#[allow(dead_code)]
|
/// Partial percent-decoding.
|
||||||
const GEN_DELIMS: &[u8] = b":/?#[]@";
|
///
|
||||||
|
/// Performs percent-decoding on a slice but can selectively skip decoding certain sequences.
|
||||||
#[allow(dead_code)]
|
///
|
||||||
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
|
/// # Examples
|
||||||
|
/// ```
|
||||||
#[allow(dead_code)]
|
/// # use actix_router::Quoter;
|
||||||
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
|
/// // + is set as a protected character and will not be decoded...
|
||||||
|
/// let q = Quoter::new(&[], b"+");
|
||||||
#[allow(dead_code)]
|
///
|
||||||
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
|
/// // ...but the other encoded characters (like the hyphen below) will.
|
||||||
|
/// assert_eq!(q.requote(b"/a%2Db%2Bc").unwrap(), b"/a-b%2Bc");
|
||||||
#[allow(dead_code)]
|
/// ```
|
||||||
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
||||||
1234567890
|
|
||||||
-._~";
|
|
||||||
|
|
||||||
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
||||||
1234567890
|
|
||||||
-._~
|
|
||||||
!$'()*,";
|
|
||||||
|
|
||||||
const QS: &[u8] = b"+&=;b";
|
|
||||||
|
|
||||||
/// A quoter
|
|
||||||
pub struct Quoter {
|
pub struct Quoter {
|
||||||
/// Simple bit-map of safe values in the 0-127 ASCII range.
|
|
||||||
safe_table: [u8; 16],
|
|
||||||
|
|
||||||
/// Simple bit-map of protected values in the 0-127 ASCII range.
|
/// Simple bit-map of protected values in the 0-127 ASCII range.
|
||||||
protected_table: [u8; 16],
|
protected_table: AsciiBitmap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Quoter {
|
impl Quoter {
|
||||||
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
|
/// Constructs a new `Quoter` instance given a set of protected ASCII bytes.
|
||||||
let mut quoter = Quoter {
|
///
|
||||||
safe_table: [0; 16],
|
/// The first argument is ignored but is kept for backward compatibility.
|
||||||
protected_table: [0; 16],
|
///
|
||||||
};
|
/// # Panics
|
||||||
|
/// Panics if any of the `protected` bytes are not in the 0-127 ASCII range.
|
||||||
// prepare safe table
|
pub fn new(_: &[u8], protected: &[u8]) -> Quoter {
|
||||||
for ch in 0..128 {
|
let mut protected_table = AsciiBitmap::default();
|
||||||
if ALLOWED.contains(&ch) {
|
|
||||||
set_bit(&mut quoter.safe_table, ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
if QS.contains(&ch) {
|
|
||||||
set_bit(&mut quoter.safe_table, ch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for &ch in safe {
|
|
||||||
set_bit(&mut quoter.safe_table, ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare protected table
|
// prepare protected table
|
||||||
for &ch in protected {
|
for &ch in protected {
|
||||||
set_bit(&mut quoter.safe_table, ch);
|
protected_table.set_bit(ch);
|
||||||
set_bit(&mut quoter.protected_table, ch);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quoter
|
Quoter { protected_table }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes safe percent-encoded sequences from `val`.
|
/// Decodes the next escape sequence, if any, and advances `val`.
|
||||||
|
#[inline(always)]
|
||||||
|
fn decode_next<'a>(&self, val: &mut &'a [u8]) -> Option<(&'a [u8], u8)> {
|
||||||
|
for i in 0..val.len() {
|
||||||
|
if let (prev, [b'%', p1, p2, rem @ ..]) = val.split_at(i) {
|
||||||
|
if let Some(ch) = hex_pair_to_char(*p1, *p2)
|
||||||
|
// ignore protected ascii bytes
|
||||||
|
.filter(|&ch| !(ch < 128 && self.protected_table.bit_at(ch)))
|
||||||
|
{
|
||||||
|
*val = rem;
|
||||||
|
return Some((prev, ch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Partially percent-decodes the given bytes.
|
||||||
///
|
///
|
||||||
/// Returns `None` when no modification to the original byte string was required.
|
/// Escape sequences of the protected set are *not* decoded.
|
||||||
///
|
///
|
||||||
/// Non-ASCII bytes are accepted as valid input.
|
/// Returns `None` when no modification to the original bytes was required.
|
||||||
///
|
///
|
||||||
/// Behavior for invalid/incomplete percent-encoding sequences is unspecified and may include
|
/// Invalid/incomplete percent-encoding sequences are passed unmodified.
|
||||||
/// removing the invalid sequence from the output or passing it as-is.
|
|
||||||
pub fn requote(&self, val: &[u8]) -> Option<Vec<u8>> {
|
pub fn requote(&self, val: &[u8]) -> Option<Vec<u8>> {
|
||||||
let mut has_pct = 0;
|
let mut remaining = val;
|
||||||
let mut pct = [b'%', 0, 0];
|
|
||||||
let mut idx = 0;
|
|
||||||
let mut cloned: Option<Vec<u8>> = None;
|
|
||||||
|
|
||||||
let len = val.len();
|
// early return indicates that no percent-encoded sequences exist and we can skip allocation
|
||||||
|
let (pre, decoded_char) = self.decode_next(&mut remaining)?;
|
||||||
|
|
||||||
while idx < len {
|
// decoded output will always be shorter than the input
|
||||||
let ch = val[idx];
|
let mut decoded = Vec::<u8>::with_capacity(val.len());
|
||||||
|
|
||||||
if has_pct != 0 {
|
// push first segment and decoded char
|
||||||
pct[has_pct] = val[idx];
|
decoded.extend_from_slice(pre);
|
||||||
has_pct += 1;
|
decoded.push(decoded_char);
|
||||||
|
|
||||||
if has_pct == 3 {
|
// decode and push rest of segments and decoded chars
|
||||||
has_pct = 0;
|
while let Some((prev, ch)) = self.decode_next(&mut remaining) {
|
||||||
let buf = cloned.as_mut().unwrap();
|
// this ugly conditional achieves +50% perf in cases where this is a tight loop.
|
||||||
|
if !prev.is_empty() {
|
||||||
if let Some(ch) = hex_pair_to_char(pct[1], pct[2]) {
|
decoded.extend_from_slice(prev);
|
||||||
if ch < 128 {
|
}
|
||||||
if bit_at(&self.protected_table, ch) {
|
decoded.push(ch);
|
||||||
buf.extend_from_slice(&pct);
|
|
||||||
idx += 1;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if bit_at(&self.safe_table, ch) {
|
decoded.extend_from_slice(remaining);
|
||||||
buf.push(ch);
|
|
||||||
idx += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push(ch);
|
Some(decoded)
|
||||||
} else {
|
|
||||||
buf.extend_from_slice(&pct[..]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ch == b'%' {
|
|
||||||
has_pct = 1;
|
|
||||||
|
|
||||||
if cloned.is_none() {
|
|
||||||
let mut c = Vec::with_capacity(len);
|
|
||||||
c.extend_from_slice(&val[..idx]);
|
|
||||||
cloned = Some(c);
|
|
||||||
}
|
|
||||||
} else if let Some(ref mut cloned) = cloned {
|
|
||||||
cloned.push(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
idx += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cloned
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn requote_str_lossy(&self, val: &str) -> Option<String> {
|
pub(crate) fn requote_str_lossy(&self, val: &str) -> Option<String> {
|
||||||
@ -135,24 +92,6 @@ impl Quoter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts an ASCII character in the hex-encoded set (`0-9`, `A-F`, `a-f`) to its integer
|
|
||||||
/// representation from `0x0`–`0xF`.
|
|
||||||
///
|
|
||||||
/// - `0x30 ('0') => 0x0`
|
|
||||||
/// - `0x39 ('9') => 0x9`
|
|
||||||
/// - `0x41 ('a') => 0xA`
|
|
||||||
/// - `0x61 ('A') => 0xA`
|
|
||||||
/// - `0x46 ('f') => 0xF`
|
|
||||||
/// - `0x66 ('F') => 0xF`
|
|
||||||
fn from_ascii_hex(v: u8) -> Option<u8> {
|
|
||||||
match v {
|
|
||||||
b'0'..=b'9' => Some(v - 0x30), // ord('0') == 0x30
|
|
||||||
b'A'..=b'F' => Some(v - 0x41 + 10), // ord('A') == 0x41
|
|
||||||
b'a'..=b'f' => Some(v - 0x61 + 10), // ord('a') == 0x61
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decode a ASCII hex-encoded pair to an integer.
|
/// Decode a ASCII hex-encoded pair to an integer.
|
||||||
///
|
///
|
||||||
/// Returns `None` if either portion of the decoded pair does not evaluate to a valid hex value.
|
/// Returns `None` if either portion of the decoded pair does not evaluate to a valid hex value.
|
||||||
@ -160,64 +99,52 @@ fn from_ascii_hex(v: u8) -> Option<u8> {
|
|||||||
/// - `0x33 ('3'), 0x30 ('0') => 0x30 ('0')`
|
/// - `0x33 ('3'), 0x30 ('0') => 0x30 ('0')`
|
||||||
/// - `0x34 ('4'), 0x31 ('1') => 0x41 ('A')`
|
/// - `0x34 ('4'), 0x31 ('1') => 0x41 ('A')`
|
||||||
/// - `0x36 ('6'), 0x31 ('1') => 0x61 ('a')`
|
/// - `0x36 ('6'), 0x31 ('1') => 0x61 ('a')`
|
||||||
|
#[inline(always)]
|
||||||
fn hex_pair_to_char(d1: u8, d2: u8) -> Option<u8> {
|
fn hex_pair_to_char(d1: u8, d2: u8) -> Option<u8> {
|
||||||
let (d_high, d_low) = (from_ascii_hex(d1)?, from_ascii_hex(d2)?);
|
let d_high = char::from(d1).to_digit(16)?;
|
||||||
|
let d_low = char::from(d2).to_digit(16)?;
|
||||||
|
|
||||||
// left shift high nibble by 4 bits
|
// left shift high nibble by 4 bits
|
||||||
Some(d_high << 4 | d_low)
|
Some((d_high as u8) << 4 | (d_low as u8))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets bit in given bit-map to 1=true.
|
#[derive(Debug, Default, Clone)]
|
||||||
///
|
struct AsciiBitmap {
|
||||||
/// # Panics
|
array: [u8; 16],
|
||||||
/// Panics if `ch` index is out of bounds.
|
|
||||||
fn set_bit(array: &mut [u8], ch: u8) {
|
|
||||||
array[(ch >> 3) as usize] |= 0b1 << (ch & 0b111)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if bit to true in given bit-map.
|
impl AsciiBitmap {
|
||||||
///
|
/// Sets bit in given bit-map to 1=true.
|
||||||
/// # Panics
|
///
|
||||||
/// Panics if `ch` index is out of bounds.
|
/// # Panics
|
||||||
fn bit_at(array: &[u8], ch: u8) -> bool {
|
/// Panics if `ch` index is out of bounds.
|
||||||
array[(ch >> 3) as usize] & (0b1 << (ch & 0b111)) != 0
|
fn set_bit(&mut self, ch: u8) {
|
||||||
|
self.array[(ch >> 3) as usize] |= 0b1 << (ch & 0b111)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if bit to true in given bit-map.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if `ch` index is out of bounds.
|
||||||
|
fn bit_at(&self, ch: u8) -> bool {
|
||||||
|
self.array[(ch >> 3) as usize] & (0b1 << (ch & 0b111)) != 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hex_encoding() {
|
|
||||||
let hex = b"0123456789abcdefABCDEF";
|
|
||||||
|
|
||||||
for i in 0..256 {
|
|
||||||
let c = i as u8;
|
|
||||||
if hex.contains(&c) {
|
|
||||||
assert!(from_ascii_hex(c).is_some())
|
|
||||||
} else {
|
|
||||||
assert!(from_ascii_hex(c).is_none())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let expected = [
|
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15,
|
|
||||||
];
|
|
||||||
for i in 0..hex.len() {
|
|
||||||
assert_eq!(from_ascii_hex(hex[i]).unwrap(), expected[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn custom_quoter() {
|
fn custom_quoter() {
|
||||||
let q = Quoter::new(b"", b"+");
|
let q = Quoter::new(b"", b"+");
|
||||||
assert_eq!(q.requote(b"/a%25c").unwrap(), b"/a%c");
|
assert_eq!(q.requote(b"/a%25c").unwrap(), b"/a%c");
|
||||||
assert_eq!(q.requote(b"/a%2Bc").unwrap(), b"/a%2Bc");
|
assert_eq!(q.requote(b"/a%2Bc"), None);
|
||||||
|
|
||||||
let q = Quoter::new(b"%+", b"/");
|
let q = Quoter::new(b"%+", b"/");
|
||||||
assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), b"/a%b+c");
|
assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), b"/a%b+c");
|
||||||
assert_eq!(q.requote(b"/a%2fb").unwrap(), b"/a%2fb");
|
assert_eq!(q.requote(b"/a%2fb"), None);
|
||||||
assert_eq!(q.requote(b"/a%2Fb").unwrap(), b"/a%2Fb");
|
assert_eq!(q.requote(b"/a%2Fb"), None);
|
||||||
assert_eq!(q.requote(b"/a%0Ab").unwrap(), b"/a\nb");
|
assert_eq!(q.requote(b"/a%0Ab").unwrap(), b"/a\nb");
|
||||||
assert_eq!(q.requote(b"/a%FE\xffb").unwrap(), b"/a\xfe\xffb");
|
assert_eq!(q.requote(b"/a%FE\xffb").unwrap(), b"/a\xfe\xffb");
|
||||||
assert_eq!(q.requote(b"/a\xfe\xffb"), None);
|
assert_eq!(q.requote(b"/a\xfe\xffb"), None);
|
||||||
@ -233,7 +160,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn invalid_sequences() {
|
fn invalid_sequences() {
|
||||||
let q = Quoter::new(b"%+", b"/");
|
let q = Quoter::new(b"%+", b"/");
|
||||||
assert_eq!(q.requote(b"/a%2x%2X%%").unwrap(), b"/a%2x%2X");
|
assert_eq!(q.requote(b"/a%2x%2X%%"), None);
|
||||||
|
assert_eq!(q.requote(b"/a%20%2X%%").unwrap(), b"/a %2X%%");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -5,8 +5,8 @@ use std::{
|
|||||||
mem,
|
mem,
|
||||||
};
|
};
|
||||||
|
|
||||||
use firestorm::{profile_fn, profile_method, profile_section};
|
|
||||||
use regex::{escape, Regex, RegexSet};
|
use regex::{escape, Regex, RegexSet};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{path::PathItem, IntoPatterns, Patterns, Resource, ResourcePath};
|
use crate::{path::PathItem, IntoPatterns, Patterns, Resource, ResourcePath};
|
||||||
|
|
||||||
@ -271,7 +271,6 @@ impl ResourceDef {
|
|||||||
/// assert!(!resource.is_match("/foo"));
|
/// assert!(!resource.is_match("/foo"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new<T: IntoPatterns>(paths: T) -> Self {
|
pub fn new<T: IntoPatterns>(paths: T) -> Self {
|
||||||
profile_method!(new);
|
|
||||||
Self::construct(paths, false)
|
Self::construct(paths, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,7 +298,6 @@ impl ResourceDef {
|
|||||||
/// assert!(!resource.is_match("/foo"));
|
/// assert!(!resource.is_match("/foo"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn prefix<T: IntoPatterns>(paths: T) -> Self {
|
pub fn prefix<T: IntoPatterns>(paths: T) -> Self {
|
||||||
profile_method!(prefix);
|
|
||||||
ResourceDef::construct(paths, true)
|
ResourceDef::construct(paths, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +322,6 @@ impl ResourceDef {
|
|||||||
/// assert!(!resource.is_match("user/123"));
|
/// assert!(!resource.is_match("user/123"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn root_prefix(path: &str) -> Self {
|
pub fn root_prefix(path: &str) -> Self {
|
||||||
profile_method!(root_prefix);
|
|
||||||
ResourceDef::prefix(insert_slash(path).into_owned())
|
ResourceDef::prefix(insert_slash(path).into_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,8 +545,6 @@ impl ResourceDef {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_match(&self, path: &str) -> bool {
|
pub fn is_match(&self, path: &str) -> bool {
|
||||||
profile_method!(is_match);
|
|
||||||
|
|
||||||
// this function could be expressed as:
|
// this function could be expressed as:
|
||||||
// `self.find_match(path).is_some()`
|
// `self.find_match(path).is_some()`
|
||||||
// but this skips some checks and uses potentially faster regex methods
|
// but this skips some checks and uses potentially faster regex methods
|
||||||
@ -597,8 +592,6 @@ impl ResourceDef {
|
|||||||
/// assert_eq!(resource.find_match("/profile/1234"), Some(13));
|
/// assert_eq!(resource.find_match("/profile/1234"), Some(13));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn find_match(&self, path: &str) -> Option<usize> {
|
pub fn find_match(&self, path: &str) -> Option<usize> {
|
||||||
profile_method!(find_match);
|
|
||||||
|
|
||||||
match &self.pat_type {
|
match &self.pat_type {
|
||||||
PatternType::Static(pattern) => self.static_match(pattern, path),
|
PatternType::Static(pattern) => self.static_match(pattern, path),
|
||||||
|
|
||||||
@ -633,7 +626,6 @@ impl ResourceDef {
|
|||||||
/// assert_eq!(path.unprocessed(), "");
|
/// assert_eq!(path.unprocessed(), "");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn capture_match_info<R: Resource>(&self, resource: &mut R) -> bool {
|
pub fn capture_match_info<R: Resource>(&self, resource: &mut R) -> bool {
|
||||||
profile_method!(capture_match_info);
|
|
||||||
self.capture_match_info_fn(resource, |_| true)
|
self.capture_match_info_fn(resource, |_| true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -679,56 +671,35 @@ impl ResourceDef {
|
|||||||
R: Resource,
|
R: Resource,
|
||||||
F: FnOnce(&R) -> bool,
|
F: FnOnce(&R) -> bool,
|
||||||
{
|
{
|
||||||
profile_method!(capture_match_info_fn);
|
|
||||||
|
|
||||||
let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default();
|
let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default();
|
||||||
let path = resource.resource_path();
|
let path = resource.resource_path();
|
||||||
let path_str = path.unprocessed();
|
let path_str = path.unprocessed();
|
||||||
|
|
||||||
let (matched_len, matched_vars) = match &self.pat_type {
|
let (matched_len, matched_vars) = match &self.pat_type {
|
||||||
PatternType::Static(pattern) => {
|
PatternType::Static(pattern) => match self.static_match(pattern, path_str) {
|
||||||
profile_section!(pattern_static_or_prefix);
|
|
||||||
|
|
||||||
match self.static_match(pattern, path_str) {
|
|
||||||
Some(len) => (len, None),
|
Some(len) => (len, None),
|
||||||
None => return false,
|
None => return false,
|
||||||
}
|
},
|
||||||
}
|
|
||||||
|
|
||||||
PatternType::Dynamic(re, names) => {
|
PatternType::Dynamic(re, names) => {
|
||||||
profile_section!(pattern_dynamic);
|
let captures = match re.captures(path.unprocessed()) {
|
||||||
|
|
||||||
let captures = {
|
|
||||||
profile_section!(pattern_dynamic_regex_exec);
|
|
||||||
|
|
||||||
match re.captures(path.unprocessed()) {
|
|
||||||
Some(captures) => captures,
|
Some(captures) => captures,
|
||||||
_ => return false,
|
_ => return false,
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
|
||||||
profile_section!(pattern_dynamic_extract_captures);
|
|
||||||
|
|
||||||
for (no, name) in names.iter().enumerate() {
|
for (no, name) in names.iter().enumerate() {
|
||||||
if let Some(m) = captures.name(name) {
|
if let Some(m) = captures.name(name) {
|
||||||
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
|
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
|
||||||
} else {
|
} else {
|
||||||
log::error!(
|
error!("Dynamic path match but not all segments found: {}", name);
|
||||||
"Dynamic path match but not all segments found: {}",
|
|
||||||
name
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
(captures[1].len(), Some(names))
|
(captures[1].len(), Some(names))
|
||||||
}
|
}
|
||||||
|
|
||||||
PatternType::DynamicSet(re, params) => {
|
PatternType::DynamicSet(re, params) => {
|
||||||
profile_section!(pattern_dynamic_set);
|
|
||||||
|
|
||||||
let path = path.unprocessed();
|
let path = path.unprocessed();
|
||||||
let (pattern, names) = match re.matches(path).into_iter().next() {
|
let (pattern, names) = match re.matches(path).into_iter().next() {
|
||||||
Some(idx) => ¶ms[idx],
|
Some(idx) => ¶ms[idx],
|
||||||
@ -744,7 +715,7 @@ impl ResourceDef {
|
|||||||
if let Some(m) = captures.name(name) {
|
if let Some(m) = captures.name(name) {
|
||||||
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
|
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
|
||||||
} else {
|
} else {
|
||||||
log::error!("Dynamic path match but not all segments found: {}", name);
|
error!("Dynamic path match but not all segments found: {}", name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -811,7 +782,6 @@ impl ResourceDef {
|
|||||||
I: IntoIterator,
|
I: IntoIterator,
|
||||||
I::Item: AsRef<str>,
|
I::Item: AsRef<str>,
|
||||||
{
|
{
|
||||||
profile_method!(resource_path_from_iter);
|
|
||||||
let mut iter = values.into_iter();
|
let mut iter = values.into_iter();
|
||||||
self.build_resource_path(path, |_| iter.next())
|
self.build_resource_path(path, |_| iter.next())
|
||||||
}
|
}
|
||||||
@ -847,7 +817,6 @@ impl ResourceDef {
|
|||||||
V: AsRef<str>,
|
V: AsRef<str>,
|
||||||
S: BuildHasher,
|
S: BuildHasher,
|
||||||
{
|
{
|
||||||
profile_method!(resource_path_from_map);
|
|
||||||
self.build_resource_path(path, |name| values.get(name))
|
self.build_resource_path(path, |name| values.get(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -868,8 +837,6 @@ impl ResourceDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn construct<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
|
fn construct<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
|
||||||
profile_method!(construct);
|
|
||||||
|
|
||||||
let patterns = paths.patterns();
|
let patterns = paths.patterns();
|
||||||
let (pat_type, segments) = match &patterns {
|
let (pat_type, segments) = match &patterns {
|
||||||
Patterns::Single(pattern) => ResourceDef::parse(pattern, is_prefix, false),
|
Patterns::Single(pattern) => ResourceDef::parse(pattern, is_prefix, false),
|
||||||
@ -898,7 +865,7 @@ impl ResourceDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let pattern_re_set = RegexSet::new(re_set).unwrap();
|
let pattern_re_set = RegexSet::new(re_set).unwrap();
|
||||||
let segments = segments.unwrap_or_else(Vec::new);
|
let segments = segments.unwrap_or_default();
|
||||||
|
|
||||||
(
|
(
|
||||||
PatternType::DynamicSet(pattern_re_set, pattern_data),
|
PatternType::DynamicSet(pattern_re_set, pattern_data),
|
||||||
@ -928,8 +895,6 @@ impl ResourceDef {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
/// Panics if given patterns does not contain a dynamic segment.
|
/// Panics if given patterns does not contain a dynamic segment.
|
||||||
fn parse_param(pattern: &str) -> (PatternSegment, String, &str, bool) {
|
fn parse_param(pattern: &str) -> (PatternSegment, String, &str, bool) {
|
||||||
profile_method!(parse_param);
|
|
||||||
|
|
||||||
const DEFAULT_PATTERN: &str = "[^/]+";
|
const DEFAULT_PATTERN: &str = "[^/]+";
|
||||||
const DEFAULT_PATTERN_TAIL: &str = ".*";
|
const DEFAULT_PATTERN_TAIL: &str = ".*";
|
||||||
|
|
||||||
@ -999,8 +964,6 @@ impl ResourceDef {
|
|||||||
is_prefix: bool,
|
is_prefix: bool,
|
||||||
force_dynamic: bool,
|
force_dynamic: bool,
|
||||||
) -> (PatternType, Vec<PatternSegment>) {
|
) -> (PatternType, Vec<PatternSegment>) {
|
||||||
profile_method!(parse);
|
|
||||||
|
|
||||||
if !force_dynamic && pattern.find('{').is_none() && !pattern.ends_with('*') {
|
if !force_dynamic && pattern.find('{').is_none() && !pattern.ends_with('*') {
|
||||||
// pattern is static
|
// pattern is static
|
||||||
return (
|
return (
|
||||||
@ -1038,7 +1001,7 @@ impl ResourceDef {
|
|||||||
// tail segments in prefixes have no defined semantics
|
// tail segments in prefixes have no defined semantics
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
log::warn!(
|
tracing::warn!(
|
||||||
"Prefix resources should not have tail segments. \
|
"Prefix resources should not have tail segments. \
|
||||||
Use `ResourceDef::new` constructor. \
|
Use `ResourceDef::new` constructor. \
|
||||||
This may become a panic in the future."
|
This may become a panic in the future."
|
||||||
@ -1053,7 +1016,7 @@ impl ResourceDef {
|
|||||||
// unnamed tail segment
|
// unnamed tail segment
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
log::warn!(
|
tracing::warn!(
|
||||||
"Tail segments must have names. \
|
"Tail segments must have names. \
|
||||||
Consider `.../{{tail}}*`. \
|
Consider `.../{{tail}}*`. \
|
||||||
This may become a panic in the future."
|
This may become a panic in the future."
|
||||||
@ -1133,8 +1096,6 @@ impl From<String> for ResourceDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> {
|
pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> {
|
||||||
profile_fn!(insert_slash);
|
|
||||||
|
|
||||||
if !path.is_empty() && !path.starts_with('/') {
|
if !path.is_empty() && !path.starts_with('/') {
|
||||||
let mut new_path = String::with_capacity(path.len() + 1);
|
let mut new_path = String::with_capacity(path.len() + 1);
|
||||||
new_path.push('/');
|
new_path.push('/');
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use firestorm::profile_method;
|
|
||||||
|
|
||||||
use crate::{IntoPatterns, Resource, ResourceDef};
|
use crate::{IntoPatterns, Resource, ResourceDef};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
@ -30,7 +28,6 @@ impl<T, U> Router<T, U> {
|
|||||||
where
|
where
|
||||||
R: Resource,
|
R: Resource,
|
||||||
{
|
{
|
||||||
profile_method!(recognize);
|
|
||||||
self.recognize_fn(resource, |_, _| true)
|
self.recognize_fn(resource, |_, _| true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +36,6 @@ impl<T, U> Router<T, U> {
|
|||||||
where
|
where
|
||||||
R: Resource,
|
R: Resource,
|
||||||
{
|
{
|
||||||
profile_method!(recognize_mut);
|
|
||||||
self.recognize_mut_fn(resource, |_, _| true)
|
self.recognize_mut_fn(resource, |_, _| true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,8 +51,6 @@ impl<T, U> Router<T, U> {
|
|||||||
R: Resource,
|
R: Resource,
|
||||||
F: FnMut(&R, &U) -> bool,
|
F: FnMut(&R, &U) -> bool,
|
||||||
{
|
{
|
||||||
profile_method!(recognize_checked);
|
|
||||||
|
|
||||||
for (rdef, val, ctx) in self.routes.iter() {
|
for (rdef, val, ctx) in self.routes.iter() {
|
||||||
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
|
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
|
||||||
return Some((val, ResourceId(rdef.id())));
|
return Some((val, ResourceId(rdef.id())));
|
||||||
@ -77,8 +71,6 @@ impl<T, U> Router<T, U> {
|
|||||||
R: Resource,
|
R: Resource,
|
||||||
F: FnMut(&R, &U) -> bool,
|
F: FnMut(&R, &U) -> bool,
|
||||||
{
|
{
|
||||||
profile_method!(recognize_mut_checked);
|
|
||||||
|
|
||||||
for (rdef, val, ctx) in self.routes.iter_mut() {
|
for (rdef, val, ctx) in self.routes.iter_mut() {
|
||||||
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
|
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
|
||||||
return Some((val, ResourceId(rdef.id())));
|
return Some((val, ResourceId(rdef.id())));
|
||||||
@ -104,7 +96,6 @@ impl<T, U> RouterBuilder<T, U> {
|
|||||||
val: T,
|
val: T,
|
||||||
ctx: U,
|
ctx: U,
|
||||||
) -> (&mut ResourceDef, &mut T, &mut U) {
|
) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||||
profile_method!(push);
|
|
||||||
self.routes.push((rdef, val, ctx));
|
self.routes.push((rdef, val, ctx));
|
||||||
self.routes
|
self.routes
|
||||||
.last_mut()
|
.last_mut()
|
||||||
@ -131,7 +122,6 @@ where
|
|||||||
path: impl IntoPatterns,
|
path: impl IntoPatterns,
|
||||||
val: T,
|
val: T,
|
||||||
) -> (&mut ResourceDef, &mut T, &mut U) {
|
) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||||
profile_method!(path);
|
|
||||||
self.push(ResourceDef::new(path), val, U::default())
|
self.push(ResourceDef::new(path), val, U::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,13 +131,11 @@ where
|
|||||||
prefix: impl IntoPatterns,
|
prefix: impl IntoPatterns,
|
||||||
val: T,
|
val: T,
|
||||||
) -> (&mut ResourceDef, &mut T, &mut U) {
|
) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||||
profile_method!(prefix);
|
|
||||||
self.push(ResourceDef::prefix(prefix), val, U::default())
|
self.push(ResourceDef::prefix(prefix), val, U::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers resource for [`ResourceDef`].
|
/// Registers resource for [`ResourceDef`].
|
||||||
pub fn rdef(&mut self, rdef: ResourceDef, val: T) -> (&mut ResourceDef, &mut T, &mut U) {
|
pub fn rdef(&mut self, rdef: ResourceDef, val: T) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||||
profile_method!(rdef);
|
|
||||||
self.push(rdef, val, U::default())
|
self.push(rdef, val, U::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use crate::ResourcePath;
|
|||||||
use crate::Quoter;
|
use crate::Quoter;
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+");
|
static DEFAULT_QUOTER: Quoter = Quoter::new(b"", b"%/+");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.1.0-beta.13 - 2022-02-16
|
||||||
|
- No significant changes since `0.1.0-beta.12`.
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0-beta.12 - 2022-01-31
|
## 0.1.0-beta.12 - 2022-01-31
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-test"
|
name = "actix-test"
|
||||||
version = "0.1.0-beta.12"
|
version = "0.1.0-beta.13"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
@ -28,14 +28,14 @@ rustls = ["tls-rustls", "actix-http/rustls", "awc/rustls"]
|
|||||||
openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
|
openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.4.1"
|
actix-codec = "0.5"
|
||||||
actix-http = "3.0.0-rc.1"
|
actix-http = "3"
|
||||||
actix-http-test = "3.0.0-beta.12"
|
actix-http-test = "3.0.0-beta.13"
|
||||||
actix-rt = "2.1"
|
actix-rt = "2.1"
|
||||||
actix-service = "2.0.0"
|
actix-service = "2"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3"
|
||||||
actix-web = { version = "4.0.0-rc.1", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4", default-features = false, features = ["cookies"] }
|
||||||
awc = { version = "3.0.0-beta.20", default-features = false, features = ["cookies"] }
|
awc = { version = "3", default-features = false, features = ["cookies"] }
|
||||||
|
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = [] }
|
futures-util = { version = "0.3.7", default-features = false, features = [] }
|
||||||
|
@ -43,7 +43,7 @@ pub use actix_http_test::unused_addr;
|
|||||||
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
|
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
|
||||||
pub use actix_web::test::{
|
pub use actix_web::test::{
|
||||||
call_and_read_body, call_and_read_body_json, call_service, init_service, ok_service,
|
call_and_read_body, call_and_read_body_json, call_service, init_service, ok_service,
|
||||||
read_body, read_body_json, simple_service, TestRequest,
|
read_body, read_body_json, status_service, TestRequest,
|
||||||
};
|
};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
body::MessageBody,
|
body::MessageBody,
|
||||||
|
@ -1,6 +1,21 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency.
|
||||||
|
|
||||||
|
|
||||||
|
## 4.1.0 - 2022-03-02
|
||||||
|
- Add support for `actix` version `0.13`. [#2675]
|
||||||
|
|
||||||
|
[#2675]: https://github.com/actix/actix-web/pull/2675
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0 - 2022-02-25
|
||||||
|
- No significant changes since `4.0.0-beta.12`.
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0-beta.12 - 2022-02-16
|
||||||
|
- No significant changes since `4.0.0-beta.11`.
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.11 - 2022-01-31
|
## 4.0.0-beta.11 - 2022-01-31
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web-actors"
|
name = "actix-web-actors"
|
||||||
version = "4.0.0-beta.11"
|
version = "4.1.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix actors support for Actix Web"
|
description = "Actix actors support for Actix Web"
|
||||||
keywords = ["actix", "http", "web", "framework", "async"]
|
keywords = ["actix", "http", "web", "framework", "async"]
|
||||||
@ -14,21 +14,21 @@ name = "actix_web_actors"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = { version = "0.12.0", default-features = false }
|
actix = { version = ">=0.12, <0.14", default-features = false }
|
||||||
actix-codec = "0.4.1"
|
actix-codec = "0.5"
|
||||||
actix-http = "3.0.0-rc.1"
|
actix-http = "3"
|
||||||
actix-web = { version = "4.0.0-rc.1", default-features = false }
|
actix-web = { version = "4", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
pin-project-lite = "0.2"
|
pin-project-lite = "0.2"
|
||||||
tokio = { version = "1.8.4", features = ["sync"] }
|
tokio = { version = "1.13.1", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.12"
|
actix-test = "0.1.0-beta.13"
|
||||||
awc = { version = "3.0.0-beta.20", default-features = false }
|
awc = { version = "3", default-features = false }
|
||||||
|
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
> Actix actors support for Actix Web.
|
> Actix actors support for Actix Web.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web-actors)
|
[](https://crates.io/crates/actix-web-actors)
|
||||||
[](https://docs.rs/actix-web-actors/4.0.0-beta.11)
|
[](https://docs.rs/actix-web-actors/4.1.0)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-web-actors/4.0.0-beta.11)
|
[](https://deps.rs/crate/actix-web-actors/4.1.0)
|
||||||
[](https://crates.io/crates/actix-web-actors)
|
[](https://crates.io/crates/actix-web-actors)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -3,6 +3,26 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.1 - 2022-06-11
|
||||||
|
- Fix support for guard paths in route handler macros. [#2771]
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency.
|
||||||
|
|
||||||
|
[#2771]: https://github.com/actix/actix-web/pull/2771
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0 - 2022-02-24
|
||||||
|
- Version aligned with `actix-web` and will remain in sync going forward.
|
||||||
|
- No significant changes since `0.5.0`.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0 - 2022-02-24
|
||||||
|
- No significant changes since `0.5.0-rc.2`.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0-rc.2 - 2022-02-01
|
||||||
|
- No significant changes since `0.5.0-rc.1`.
|
||||||
|
|
||||||
|
|
||||||
## 0.5.0-rc.1 - 2022-01-04
|
## 0.5.0-rc.1 - 2022-01-04
|
||||||
- Minimum supported Rust version (MSRV) is now 1.54.
|
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web-codegen"
|
name = "actix-web-codegen"
|
||||||
version = "0.5.0-rc.1"
|
version = "4.0.1"
|
||||||
description = "Routing and runtime macros for Actix Web"
|
description = "Routing and runtime macros for Actix Web"
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
repository = "https://github.com/actix/actix-web.git"
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
@ -15,7 +15,7 @@ edition = "2018"
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-router = "0.5.0-rc.3"
|
actix-router = "0.5.0"
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
syn = { version = "1", features = ["full", "parsing"] }
|
syn = { version = "1", features = ["full", "parsing"] }
|
||||||
@ -23,9 +23,9 @@ syn = { version = "1", features = ["full", "parsing"] }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-macros = "0.2.3"
|
actix-macros = "0.2.3"
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.12"
|
actix-test = "0.1.0-beta.13"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = "4.0.0-rc.1"
|
actix-web = "4.0.0"
|
||||||
|
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
trybuild = "1"
|
trybuild = "1"
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
> Routing and runtime macros for Actix Web.
|
> Routing and runtime macros for Actix Web.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web-codegen)
|
[](https://crates.io/crates/actix-web-codegen)
|
||||||
[](https://docs.rs/actix-web-codegen/0.5.0-rc.1)
|
[](https://docs.rs/actix-web-codegen/4.0.1)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-web-codegen/0.5.0-rc.1)
|
[](https://deps.rs/crate/actix-web-codegen/4.0.1)
|
||||||
[](https://crates.io/crates/actix-web-codegen)
|
[](https://crates.io/crates/actix-web-codegen)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -152,6 +152,10 @@ method_macro!(Patch, patch);
|
|||||||
|
|
||||||
/// Marks async main function as the Actix Web system entry-point.
|
/// Marks async main function as the Actix Web system entry-point.
|
||||||
///
|
///
|
||||||
|
/// Note that Actix Web also works under `#[tokio::main]` since version 4.0. However, this macro is
|
||||||
|
/// still necessary for actor support (since actors use a `System`). Read more in the
|
||||||
|
/// [`actix_web::rt`](https://docs.rs/actix-web/4/actix_web/rt) module docs.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// #[actix_web::main]
|
/// #[actix_web::main]
|
||||||
|
@ -4,7 +4,7 @@ use actix_router::ResourceDef;
|
|||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
|
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
|
||||||
use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, NestedMeta};
|
use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, NestedMeta, Path};
|
||||||
|
|
||||||
enum ResourceType {
|
enum ResourceType {
|
||||||
Async,
|
Async,
|
||||||
@ -77,7 +77,7 @@ impl TryFrom<&syn::LitStr> for MethodType {
|
|||||||
struct Args {
|
struct Args {
|
||||||
path: syn::LitStr,
|
path: syn::LitStr,
|
||||||
resource_name: Option<syn::LitStr>,
|
resource_name: Option<syn::LitStr>,
|
||||||
guards: Vec<Ident>,
|
guards: Vec<Path>,
|
||||||
wrappers: Vec<syn::Type>,
|
wrappers: Vec<syn::Type>,
|
||||||
methods: HashSet<MethodType>,
|
methods: HashSet<MethodType>,
|
||||||
}
|
}
|
||||||
@ -121,7 +121,7 @@ impl Args {
|
|||||||
}
|
}
|
||||||
} else if nv.path.is_ident("guard") {
|
} else if nv.path.is_ident("guard") {
|
||||||
if let syn::Lit::Str(lit) = nv.lit {
|
if let syn::Lit::Str(lit) = nv.lit {
|
||||||
guards.push(Ident::new(&lit.value(), Span::call_site()));
|
guards.push(lit.parse::<Path>()?);
|
||||||
} else {
|
} else {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
nv.lit,
|
nv.lit,
|
||||||
|
@ -96,6 +96,21 @@ async fn custom_resource_name_test<'a>(req: actix_web::HttpRequest) -> impl Resp
|
|||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod guard_module {
|
||||||
|
use actix_web::{guard::GuardContext, http::header};
|
||||||
|
|
||||||
|
pub fn guard(ctx: &GuardContext) -> bool {
|
||||||
|
ctx.header::<header::Accept>()
|
||||||
|
.map(|h| h.preference() == "image/*")
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/test/guard", guard = "guard_module::guard")]
|
||||||
|
async fn guard_test() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ChangeStatusCode;
|
pub struct ChangeStatusCode;
|
||||||
|
|
||||||
impl<S, B> Transform<S, ServiceRequest> for ChangeStatusCode
|
impl<S, B> Transform<S, ServiceRequest> for ChangeStatusCode
|
||||||
@ -187,6 +202,7 @@ async fn test_body() {
|
|||||||
.service(test_handler)
|
.service(test_handler)
|
||||||
.service(route_test)
|
.service(route_test)
|
||||||
.service(custom_resource_name_test)
|
.service(custom_resource_name_test)
|
||||||
|
.service(guard_test)
|
||||||
});
|
});
|
||||||
let request = srv.request(http::Method::GET, srv.url("/test"));
|
let request = srv.request(http::Method::GET, srv.url("/test"));
|
||||||
let response = request.send().await.unwrap();
|
let response = request.send().await.unwrap();
|
||||||
@ -245,6 +261,12 @@ async fn test_body() {
|
|||||||
let request = srv.request(http::Method::GET, srv.url("/custom_resource_name"));
|
let request = srv.request(http::Method::GET, srv.url("/custom_resource_name"));
|
||||||
let response = request.send().await.unwrap();
|
let response = request.send().await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv
|
||||||
|
.request(http::Method::GET, srv.url("/test/guard"))
|
||||||
|
.insert_header(("Accept", "image/*"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#[rustversion::stable(1.54)] // MSRV
|
#[rustversion::stable(1.56)] // MSRV
|
||||||
#[test]
|
#[test]
|
||||||
fn compile_macros() {
|
fn compile_macros() {
|
||||||
let t = trybuild::TestCases::new();
|
let t = trybuild::TestCases::new();
|
||||||
|
1352
actix-web/CHANGES.md
Normal file
1352
actix-web/CHANGES.md
Normal file
File diff suppressed because it is too large
Load Diff
153
actix-web/Cargo.toml
Normal file
153
actix-web/Cargo.toml
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
[package]
|
||||||
|
name = "actix-web"
|
||||||
|
version = "4.1.0"
|
||||||
|
authors = [
|
||||||
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
|
]
|
||||||
|
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
||||||
|
keywords = ["actix", "http", "web", "framework", "async"]
|
||||||
|
categories = [
|
||||||
|
"network-programming",
|
||||||
|
"asynchronous",
|
||||||
|
"web-programming::http-server",
|
||||||
|
"web-programming::websocket"
|
||||||
|
]
|
||||||
|
homepage = "https://actix.rs"
|
||||||
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
# features that docs.rs will build with
|
||||||
|
features = ["macros", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"]
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "actix_web"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["macros", "compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
|
||||||
|
|
||||||
|
# Brotli algorithm content-encoding support
|
||||||
|
compress-brotli = ["actix-http/compress-brotli", "__compress"]
|
||||||
|
# Gzip and deflate algorithms content-encoding support
|
||||||
|
compress-gzip = ["actix-http/compress-gzip", "__compress"]
|
||||||
|
# Zstd algorithm content-encoding support
|
||||||
|
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
||||||
|
|
||||||
|
# Routing and runtime proc macros
|
||||||
|
macros = [
|
||||||
|
"actix-macros",
|
||||||
|
"actix-web-codegen",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Cookies support
|
||||||
|
cookies = ["cookie"]
|
||||||
|
|
||||||
|
# Secure & signed cookies
|
||||||
|
secure-cookies = ["cookies", "cookie/secure"]
|
||||||
|
|
||||||
|
# TLS via OpenSSL
|
||||||
|
openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"]
|
||||||
|
|
||||||
|
# TLS via Rustls
|
||||||
|
rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"]
|
||||||
|
|
||||||
|
# Internal (PRIVATE!) features used to aid testing and checking feature status.
|
||||||
|
# Don't rely on these whatsoever. They may disappear at anytime.
|
||||||
|
__compress = []
|
||||||
|
|
||||||
|
# io-uring feature only available for Linux OSes.
|
||||||
|
experimental-io-uring = ["actix-server/io-uring"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-codec = "0.5"
|
||||||
|
actix-macros = { version = "0.2.3", optional = true }
|
||||||
|
actix-rt = { version = "2.6", default-features = false }
|
||||||
|
actix-server = "2"
|
||||||
|
actix-service = "2"
|
||||||
|
actix-utils = "3"
|
||||||
|
actix-tls = { version = "3", default-features = false, optional = true }
|
||||||
|
|
||||||
|
actix-http = { version = "3", features = ["http2", "ws"] }
|
||||||
|
actix-router = "0.5"
|
||||||
|
actix-web-codegen = { version = "4", optional = true }
|
||||||
|
|
||||||
|
ahash = "0.7"
|
||||||
|
bytes = "1"
|
||||||
|
bytestring = "1"
|
||||||
|
cfg-if = "1"
|
||||||
|
cookie = { version = "0.16", features = ["percent-encode"], optional = true }
|
||||||
|
derive_more = "0.99.5"
|
||||||
|
encoding_rs = "0.8"
|
||||||
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
|
itoa = "1"
|
||||||
|
language-tags = "0.3"
|
||||||
|
once_cell = "1.5"
|
||||||
|
log = "0.4"
|
||||||
|
mime = "0.3"
|
||||||
|
pin-project-lite = "0.2.7"
|
||||||
|
regex = "1.5.5"
|
||||||
|
serde = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde_urlencoded = "0.7"
|
||||||
|
smallvec = "1.6.1"
|
||||||
|
socket2 = "0.4.0"
|
||||||
|
time = { version = "0.3", default-features = false, features = ["formatting"] }
|
||||||
|
url = "2.1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
actix-files = "0.6"
|
||||||
|
actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] }
|
||||||
|
awc = { version = "3", features = ["openssl"] }
|
||||||
|
|
||||||
|
brotli = "3.3.3"
|
||||||
|
const-str = "0.3"
|
||||||
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
|
env_logger = "0.9"
|
||||||
|
flate2 = "1.0.13"
|
||||||
|
futures-util = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||||
|
rand = "0.8"
|
||||||
|
rcgen = "0.8"
|
||||||
|
rustls-pemfile = "0.2"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
static_assertions = "1"
|
||||||
|
tls-openssl = { package = "openssl", version = "0.10.9" }
|
||||||
|
tls-rustls = { package = "rustls", version = "0.20.0" }
|
||||||
|
tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] }
|
||||||
|
zstd = "0.11"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "test_server"
|
||||||
|
required-features = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "compression"
|
||||||
|
required-features = ["compress-brotli", "compress-gzip", "compress-zstd"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "basic"
|
||||||
|
required-features = ["compress-gzip"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "uds"
|
||||||
|
required-features = ["compress-gzip"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "on-connect"
|
||||||
|
required-features = []
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "server"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "service"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "responder"
|
||||||
|
harness = false
|
1
actix-web/LICENSE-APACHE
Symbolic link
1
actix-web/LICENSE-APACHE
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../LICENSE-APACHE
|
1
actix-web/LICENSE-MIT
Symbolic link
1
actix-web/LICENSE-MIT
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../LICENSE-MIT
|
198
actix-web/MIGRATION-0.x.md
Normal file
198
actix-web/MIGRATION-0.x.md
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
# 0.7.15
|
||||||
|
|
||||||
|
- The `' '` character is not percent decoded anymore before matching routes. If you need to use it in
|
||||||
|
your routes, you should use `%20`.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource("/my index", |r| {
|
||||||
|
r.method(http::Method::GET)
|
||||||
|
.with(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
use
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource("/my%20index", |r| {
|
||||||
|
r.method(http::Method::GET)
|
||||||
|
.with(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- If you used `AsyncResult::async` you need to replace it with `AsyncResult::future`
|
||||||
|
|
||||||
|
# 0.7.4
|
||||||
|
|
||||||
|
- `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
|
||||||
|
even for handler with one parameter.
|
||||||
|
|
||||||
|
# 0.7
|
||||||
|
|
||||||
|
- `HttpRequest` does not implement `Stream` anymore. If you need to read request payload
|
||||||
|
use `HttpMessage::payload()` method.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(req: HttpRequest) -> impl Responder {
|
||||||
|
req
|
||||||
|
.from_err()
|
||||||
|
.fold(...)
|
||||||
|
....
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
use `.payload()`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(req: HttpRequest) -> impl Responder {
|
||||||
|
req
|
||||||
|
.payload() // <- get request payload stream
|
||||||
|
.from_err()
|
||||||
|
.fold(...)
|
||||||
|
....
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html)
|
||||||
|
trait uses `&HttpRequest` instead of `&mut HttpRequest`.
|
||||||
|
|
||||||
|
- Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(query: Query<..>, info: Json<MyStruct) -> impl Responder {}
|
||||||
|
```
|
||||||
|
|
||||||
|
use tuple of extractors and use `.with()` for registration:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index((query, json): (Query<..>, Json<MyStruct)) -> impl Responder {}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `Handler::handle()` uses `&self` instead of `&mut self`
|
||||||
|
|
||||||
|
- `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value
|
||||||
|
|
||||||
|
- Removed deprecated `HttpServer::threads()`, use
|
||||||
|
[HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead.
|
||||||
|
|
||||||
|
- Renamed `client::ClientConnectorError::Connector` to
|
||||||
|
`client::ClientConnectorError::Resolver`
|
||||||
|
|
||||||
|
- `Route::with()` does not return `ExtractorConfig`, to configure
|
||||||
|
extractor use `Route::with_config()`
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource("/index.html", |r| {
|
||||||
|
r.method(http::Method::GET)
|
||||||
|
.with(index)
|
||||||
|
.limit(4096); // <- limit size of the payload
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
use
|
||||||
|
|
||||||
|
```rust
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource("/index.html", |r| {
|
||||||
|
r.method(http::Method::GET)
|
||||||
|
.with_config(index, |cfg| { // <- register handler
|
||||||
|
cfg.limit(4096); // <- limit size of the payload
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `Route::with_async()` does not return `ExtractorConfig`, to configure
|
||||||
|
extractor use `Route::with_async_config()`
|
||||||
|
|
||||||
|
# 0.6
|
||||||
|
|
||||||
|
- `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
|
||||||
|
|
||||||
|
- `ws::Message::Close` now includes optional close reason.
|
||||||
|
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
|
||||||
|
|
||||||
|
- `HttpServer::threads()` renamed to `HttpServer::workers()`.
|
||||||
|
|
||||||
|
- `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated.
|
||||||
|
Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead.
|
||||||
|
|
||||||
|
- `HttpRequest::extensions()` returns read only reference to the request's Extension
|
||||||
|
`HttpRequest::extensions_mut()` returns mutable reference.
|
||||||
|
|
||||||
|
- Instead of
|
||||||
|
|
||||||
|
`use actix_web::middleware::{ CookieSessionBackend, CookieSessionError, RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
|
||||||
|
|
||||||
|
use `actix_web::middleware::session`
|
||||||
|
|
||||||
|
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError, RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
|
||||||
|
|
||||||
|
- `FromRequest::from_request()` accepts mutable reference to a request
|
||||||
|
|
||||||
|
- `FromRequest::Result` has to implement `Into<Reply<Self>>`
|
||||||
|
|
||||||
|
- [`Responder::respond_to()`](https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
|
||||||
|
is generic over `S`
|
||||||
|
|
||||||
|
- Use `Query` extractor instead of HttpRequest::query()`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let q = Query::<HashMap<String, String>>::extract(req);
|
||||||
|
```
|
||||||
|
|
||||||
|
- Websocket operations are implemented as `WsWriter` trait.
|
||||||
|
you need to use `use actix_web::ws::WsWriter`
|
||||||
|
|
||||||
|
# 0.5
|
||||||
|
|
||||||
|
- `HttpResponseBuilder::body()`, `.finish()`, `.json()`
|
||||||
|
methods return `HttpResponse` instead of `Result<HttpResponse>`
|
||||||
|
|
||||||
|
- `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version`
|
||||||
|
moved to `actix_web::http` module
|
||||||
|
|
||||||
|
- `actix_web::header` moved to `actix_web::http::header`
|
||||||
|
|
||||||
|
- `NormalizePath` moved to `actix_web::http` module
|
||||||
|
|
||||||
|
- `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function,
|
||||||
|
shortcut for `actix_web::server::HttpServer::new()`
|
||||||
|
|
||||||
|
- `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself
|
||||||
|
|
||||||
|
- `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead.
|
||||||
|
|
||||||
|
- `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type
|
||||||
|
|
||||||
|
- `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()`
|
||||||
|
functions should be used instead
|
||||||
|
|
||||||
|
- `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>`
|
||||||
|
instead of `Result<_, http::Error>`
|
||||||
|
|
||||||
|
- `Application` renamed to a `App`
|
||||||
|
|
||||||
|
- `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev`
|
337
actix-web/MIGRATION-1.0.md
Normal file
337
actix-web/MIGRATION-1.0.md
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
- Cors middleware has been moved to `actix-cors` crate
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::middleware::cors::Cors;
|
||||||
|
```
|
||||||
|
|
||||||
|
use
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_cors::Cors;
|
||||||
|
```
|
||||||
|
|
||||||
|
- Identity middleware has been moved to `actix-identity` crate
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::middleware::identity::{Identity, CookieIdentityPolicy, IdentityService};
|
||||||
|
```
|
||||||
|
|
||||||
|
use
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_identity::{Identity, CookieIdentityPolicy, IdentityService};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ExtractorConfig {
|
||||||
|
config: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromRequest for YourExtractor {
|
||||||
|
type Config = ExtractorConfig;
|
||||||
|
type Result = Result<YourExtractor, Error>;
|
||||||
|
|
||||||
|
fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result {
|
||||||
|
println!("use the config: {:?}", cfg.config);
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App::new().resource("/route_with_config", |r| {
|
||||||
|
r.post().with_config(handler_fn, |cfg| {
|
||||||
|
cfg.0.config = "test".to_string();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
use the HttpRequest to get the configuration like any other `Data` with `req.app_data::<C>()` and set it with the `data()` method on the `resource`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ExtractorConfig {
|
||||||
|
config: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromRequest for YourExtractor {
|
||||||
|
type Error = Error;
|
||||||
|
type Future = Result<Self, Self::Error>;
|
||||||
|
type Config = ExtractorConfig;
|
||||||
|
|
||||||
|
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||||
|
let cfg = req.app_data::<ExtractorConfig>();
|
||||||
|
println!("config data?: {:?}", cfg.unwrap().role);
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App::new().service(
|
||||||
|
resource("/route_with_config")
|
||||||
|
.data(ExtractorConfig {
|
||||||
|
config: "test".to_string(),
|
||||||
|
})
|
||||||
|
.route(post().to(handler_fn)),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Resource registration. 1.0 version uses generalized resource
|
||||||
|
registration via `.service()` method.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
App.new().resource("/welcome", |r| r.f(welcome))
|
||||||
|
```
|
||||||
|
|
||||||
|
use App's or Scope's `.service()` method. `.service()` method accepts
|
||||||
|
object that implements `HttpServiceFactory` trait. By default
|
||||||
|
actix-web provides `Resource` and `Scope` services.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
App.new().service(
|
||||||
|
web::resource("/welcome")
|
||||||
|
.route(web::get().to(welcome))
|
||||||
|
.route(web::post().to(post_handler))
|
||||||
|
```
|
||||||
|
|
||||||
|
- Scope registration.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let app = App::new().scope("/{project_id}", |scope| {
|
||||||
|
scope
|
||||||
|
.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
.resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
.resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
use `.service()` for registration and `web::scope()` as scope object factory.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let app = App::new().service(
|
||||||
|
web::scope("/{project_id}")
|
||||||
|
.service(web::resource("/path1").to(|| HttpResponse::Ok()))
|
||||||
|
.service(web::resource("/path2").to(|| HttpResponse::Ok()))
|
||||||
|
.service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed()))
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
- `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
App.new().resource("/welcome", |r| r.with(welcome))
|
||||||
|
```
|
||||||
|
|
||||||
|
use `.to()` or `.to_async()` methods
|
||||||
|
|
||||||
|
```rust
|
||||||
|
App.new().service(web::resource("/welcome").to(welcome))
|
||||||
|
```
|
||||||
|
|
||||||
|
- Passing arguments to handler with extractors, multiple arguments are allowed
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn welcome((body, req): (Bytes, HttpRequest)) -> ... {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
use multiple arguments
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn welcome(body: Bytes, req: HttpRequest) -> ... {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `.f()`, `.a()` and `.h()` handler registration methods have been removed.
|
||||||
|
Use `.to()` for handlers and `.to_async()` for async handlers. Handler function
|
||||||
|
must use extractors.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
App.new().resource("/welcome", |r| r.f(welcome))
|
||||||
|
```
|
||||||
|
|
||||||
|
use App's `to()` or `to_async()` methods
|
||||||
|
|
||||||
|
```rust
|
||||||
|
App.new().service(web::resource("/welcome").to(welcome))
|
||||||
|
```
|
||||||
|
|
||||||
|
- `HttpRequest` does not provide access to request's payload stream.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(req: &HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
req
|
||||||
|
.payload()
|
||||||
|
.from_err()
|
||||||
|
.fold((), |_, chunk| {
|
||||||
|
...
|
||||||
|
})
|
||||||
|
.map(|_| HttpResponse::Ok().finish())
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
use `Payload` extractor
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(stream: web::Payload) -> impl Future<Item=HttpResponse, Error=Error> {
|
||||||
|
stream
|
||||||
|
.from_err()
|
||||||
|
.fold((), |_, chunk| {
|
||||||
|
...
|
||||||
|
})
|
||||||
|
.map(|_| HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `State` is now `Data`. You register Data during the App initialization process
|
||||||
|
and then access it from handlers either using a Data extractor or using
|
||||||
|
HttpRequest's api.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
App.with_state(T)
|
||||||
|
```
|
||||||
|
|
||||||
|
use App's `data` method
|
||||||
|
|
||||||
|
```rust
|
||||||
|
App.new()
|
||||||
|
.data(T)
|
||||||
|
```
|
||||||
|
|
||||||
|
and either use the Data extractor within your handler
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::web::Data;
|
||||||
|
|
||||||
|
fn endpoint_handler(Data<T>)){
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
.. or access your Data element from the HttpRequest
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn endpoint_handler(req: HttpRequest) {
|
||||||
|
let data: Option<Data<T>> = req.app_data::<T>();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use actix_web::AsyncResponder;
|
||||||
|
|
||||||
|
fn endpoint_handler(...) -> impl Future<Item=HttpResponse, Error=Error>{
|
||||||
|
...
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
.. simply omit AsyncResponder and the corresponding responder() finish method
|
||||||
|
|
||||||
|
- Middleware
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let app = App::new()
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
```
|
||||||
|
|
||||||
|
use `.wrap()` method
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let app = App::new()
|
||||||
|
.wrap(middleware::Logger::default())
|
||||||
|
.route("/index.html", web::get().to(index));
|
||||||
|
```
|
||||||
|
|
||||||
|
- `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()`
|
||||||
|
method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(req: &HttpRequest) -> Responder {
|
||||||
|
req.body()
|
||||||
|
.and_then(|body| {
|
||||||
|
...
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
use
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(body: Bytes) -> Responder {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type
|
||||||
|
|
||||||
|
- StaticFiles and NamedFile have been moved to a separate crate.
|
||||||
|
|
||||||
|
instead of `use actix_web::fs::StaticFile`
|
||||||
|
|
||||||
|
use `use actix_files::Files`
|
||||||
|
|
||||||
|
instead of `use actix_web::fs::Namedfile`
|
||||||
|
|
||||||
|
use `use actix_files::NamedFile`
|
||||||
|
|
||||||
|
- Multipart has been moved to a separate crate.
|
||||||
|
|
||||||
|
instead of `use actix_web::multipart::Multipart`
|
||||||
|
|
||||||
|
use `use actix_multipart::Multipart`
|
||||||
|
|
||||||
|
- Response compression is not enabled by default.
|
||||||
|
To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`.
|
||||||
|
|
||||||
|
- Session middleware moved to actix-session crate
|
||||||
|
|
||||||
|
- Actors support have been moved to `actix-web-actors` crate
|
||||||
|
|
||||||
|
- Custom Error
|
||||||
|
|
||||||
|
Instead of error_response method alone, ResponseError now provides two methods: error_response and render_response respectively. Where, error_response creates the error response and render_response returns the error response to the caller.
|
||||||
|
|
||||||
|
Simplest migration from 0.7 to 1.0 shall include below method to the custom implementation of ResponseError:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn render_response(&self) -> HttpResponse {
|
||||||
|
self.error_response()
|
||||||
|
}
|
||||||
|
```
|
48
actix-web/MIGRATION-2.0.md
Normal file
48
actix-web/MIGRATION-2.0.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Migrating to 2.0.0
|
||||||
|
|
||||||
|
- `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to
|
||||||
|
`.await` on `run` method result, in that case it awaits server exit.
|
||||||
|
|
||||||
|
- `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`.
|
||||||
|
Stored data is available via `HttpRequest::app_data()` method at runtime.
|
||||||
|
|
||||||
|
- Extractor configuration must be registered with `App::app_data()` instead of `App::data()`
|
||||||
|
|
||||||
|
- Sync handlers has been removed. `.to_async()` method has been renamed to `.to()`
|
||||||
|
replace `fn` with `async fn` to convert sync handler to async
|
||||||
|
|
||||||
|
- `actix_http_test::TestServer` moved to `actix_web::test` module. To start
|
||||||
|
test server use `test::start()` or `test_start_with_config()` methods
|
||||||
|
|
||||||
|
- `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders
|
||||||
|
http response.
|
||||||
|
|
||||||
|
- Feature `rust-tls` renamed to `rustls`
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
actix-web = { version = "2.0.0", features = ["rust-tls"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
use
|
||||||
|
|
||||||
|
```rust
|
||||||
|
actix-web = { version = "2.0.0", features = ["rustls"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
- Feature `ssl` renamed to `openssl`
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
actix-web = { version = "2.0.0", features = ["ssl"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
use
|
||||||
|
|
||||||
|
```rust
|
||||||
|
actix-web = { version = "2.0.0", features = ["openssl"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
- `Cors` builder now requires that you call `.finish()` to construct the middleware
|
53
actix-web/MIGRATION-3.0.md
Normal file
53
actix-web/MIGRATION-3.0.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Migrating to 3.0.0
|
||||||
|
|
||||||
|
- The return type for `ServiceRequest::app_data::<T>()` was changed from returning a `Data<T>` to
|
||||||
|
simply a `T`. To access a `Data<T>` use `ServiceRequest::app_data::<Data<T>>()`.
|
||||||
|
|
||||||
|
- Cookie handling has been offloaded to the `cookie` crate:
|
||||||
|
|
||||||
|
- `USERINFO_ENCODE_SET` is no longer exposed. Percent-encoding is still supported; check docs.
|
||||||
|
- Some types now require lifetime parameters.
|
||||||
|
|
||||||
|
- The time crate was updated to `v0.2`, a major breaking change to the time crate, which affects
|
||||||
|
any `actix-web` method previously expecting a time v0.1 input.
|
||||||
|
|
||||||
|
- Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now
|
||||||
|
result in `SameSite=None` being sent with the response Set-Cookie header.
|
||||||
|
To create a cookie without a SameSite attribute, remove any calls setting same_site.
|
||||||
|
|
||||||
|
- actix-http support for Actors messages was moved to actix-http crate and is enabled
|
||||||
|
with feature `actors`
|
||||||
|
|
||||||
|
- content_length function is removed from actix-http.
|
||||||
|
You can set Content-Length by normally setting the response body or calling no_chunking function.
|
||||||
|
|
||||||
|
- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
|
||||||
|
`u64` instead of a `usize`.
|
||||||
|
|
||||||
|
- Code that was using `path.<index>` to access a `web::Path<(A, B, C)>`s elements now needs to use
|
||||||
|
destructuring or `.into_inner()`. For example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Previously:
|
||||||
|
async fn some_route(path: web::Path<(String, String)>) -> String {
|
||||||
|
format!("Hello, {} {}", path.0, path.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now (this also worked before):
|
||||||
|
async fn some_route(path: web::Path<(String, String)>) -> String {
|
||||||
|
let (first_name, last_name) = path.into_inner();
|
||||||
|
format!("Hello, {} {}", first_name, last_name)
|
||||||
|
}
|
||||||
|
// Or (this wasn't previously supported):
|
||||||
|
async fn some_route(web::Path((first_name, last_name)): web::Path<(String, String)>) -> String {
|
||||||
|
format!("Hello, {} {}", first_name, last_name)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one.
|
||||||
|
It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`,
|
||||||
|
or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly))`.
|
||||||
|
|
||||||
|
- `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`.
|
||||||
|
|
||||||
|
- `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`.
|
507
actix-web/MIGRATION-4.0.md
Normal file
507
actix-web/MIGRATION-4.0.md
Normal file
@ -0,0 +1,507 @@
|
|||||||
|
# Migrating to 4.0.0
|
||||||
|
|
||||||
|
This guide walks you through the process of migrating from v3.x.y to v4.x.y.
|
||||||
|
If you are migrating to v4.x.y from an older version of Actix Web (v2.x.y or earlier), check out the other historical migration notes in this folder.
|
||||||
|
|
||||||
|
This document is not designed to be exhaustive—it focuses on the most significant changes in v4. You can find an exhaustive changelog in the changelogs for [`actix-web`](./CHANGES.md#400---2022-02-25) and [`actix-http`](../actix-http/CHANGES.md#300---2022-02-25), complete with PR links. If you think there are any changes that deserve to be called out in this document, please open an issue or pull request.
|
||||||
|
|
||||||
|
Headings marked with :warning: are **breaking behavioral changes**. They will probably not surface as compile-time errors though automated tests _might_ detect their effects on your app.
|
||||||
|
|
||||||
|
## Table of Contents:
|
||||||
|
|
||||||
|
- [MSRV](#msrv)
|
||||||
|
- [Tokio v1 Ecosystem](#tokio-v1-ecosystem)
|
||||||
|
- [Module Structure](#module-structure)
|
||||||
|
- [`NormalizePath` Middleware :warning:](#normalizepath-middleware-warning)
|
||||||
|
- [Server Settings :warning:](#server-settings-warning)
|
||||||
|
- [`FromRequest` Trait](#fromrequest-trait)
|
||||||
|
- [Compression Feature Flags](#compression-feature-flags)
|
||||||
|
- [`web::Path`](#webpath)
|
||||||
|
- [Rustls Crate Upgrade](#rustls-crate-upgrade)
|
||||||
|
- [Removed `awc` Client Re-export](#removed-awc-client-re-export)
|
||||||
|
- [Integration Testing Utils Moved To `actix-test`](#integration-testing-utils-moved-to-actix-test)
|
||||||
|
- [Header APIs](#header-apis)
|
||||||
|
- [Response Body Types](#response-body-types)
|
||||||
|
- [Middleware Trait APIs](#middleware-trait-apis)
|
||||||
|
- [`Responder` Trait](#responder-trait)
|
||||||
|
- [`App::data` Deprecation :warning:](#appdata-deprecation-warning)
|
||||||
|
- [Direct Dependency On `actix-rt` And `actix-service`](#direct-dependency-on-actix-rt-and-actix-service)
|
||||||
|
- [Server Must Be Polled :warning:](#server-must-be-polled-warning)
|
||||||
|
- [Guards API](#guards-api)
|
||||||
|
- [Returning `HttpResponse` synchronously](#returning-httpresponse-synchronously)
|
||||||
|
- [`#[actix_web::main]` and `#[tokio::main]`](#actix_webmain-and-tokiomain)
|
||||||
|
- [`web::block`](#webblock)
|
||||||
|
-
|
||||||
|
|
||||||
|
## MSRV
|
||||||
|
|
||||||
|
The MSRV of Actix Web has been raised from 1.42 to 1.54.
|
||||||
|
|
||||||
|
## Tokio v1 Ecosystem
|
||||||
|
|
||||||
|
Actix Web v4 is now underpinned by `tokio`'s v1 ecosystem.
|
||||||
|
|
||||||
|
`cargo` supports having multiple versions of the same crate within the same dependency tree, but `tokio` v1 does not interoperate transparently with its previous versions (v0.2, v0.1). Some of your dependencies might rely on `tokio`, either directly or indirectly—if they are using an older version of `tokio`, check if an update is available.
|
||||||
|
The following command can help you to identify these dependencies:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Find all crates in your dependency tree that depend on `tokio`
|
||||||
|
# It also reports the different versions of `tokio` in your dependency tree.
|
||||||
|
cargo tree -i tokio
|
||||||
|
|
||||||
|
# if you depend on multiple versions of tokio, use this command to
|
||||||
|
# list the dependencies relying on a specific version of tokio:
|
||||||
|
cargo tree -i tokio:0.2.25
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module Structure
|
||||||
|
|
||||||
|
Lots of modules have been re-organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", check the [documentation on docs.rs](https://docs.rs/actix-web) to search for items' new locations.
|
||||||
|
|
||||||
|
## `NormalizePath` Middleware :warning:
|
||||||
|
|
||||||
|
The default `NormalizePath` behavior now strips trailing slashes by default. This was the _documented_ behaviour in Actix Web v3, but the _actual_ behaviour differed. The discrepancy has now been resolved.
|
||||||
|
|
||||||
|
As a consequence of this change, routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. Calling `NormalizePath::default()` will log a warning. We suggest to use `new` or `trim`.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- #[get("/test/")]
|
||||||
|
+ #[get("/test")]
|
||||||
|
async fn handler() {
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
- .wrap(NormalizePath::default())
|
||||||
|
+ .wrap(NormalizePath::trim())
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
|
||||||
|
|
||||||
|
## Server Settings :warning:
|
||||||
|
|
||||||
|
Until Actix Web v4, the underlying `actix-server` crate used the number of available **logical** cores as the default number of worker threads. The new default is the number of [physical CPU cores available](https://github.com/actix/actix-net/commit/3a3d654c). For more information about this change, refer to [this analysis](https://github.com/actix/actix-web/issues/957).
|
||||||
|
|
||||||
|
If you notice performance regressions, please open a new issue detailing your observations.
|
||||||
|
|
||||||
|
## `FromRequest` Trait
|
||||||
|
|
||||||
|
The associated type `Config` of `FromRequest` was removed. If you have custom extractors, you can just remove this implementation and refer to config types directly, if required.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
impl FromRequest for MyExtractor {
|
||||||
|
- type Config = ();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Consequently, the `FromRequest::configure` method was also removed. Config for extractors is still provided using `App::app_data` but should now be constructed in a standalone way.
|
||||||
|
|
||||||
|
## Compression Feature Flags
|
||||||
|
|
||||||
|
The `compress` feature flag has been split into more granular feature flags, one for each supported algorithm (brotli, gzip, zstd). By default, all compression algorithms are enabled. If you want to select specific compression codecs, the new flags are:
|
||||||
|
|
||||||
|
- `compress-brotli`
|
||||||
|
- `compress-gzip`
|
||||||
|
- `compress-zstd`
|
||||||
|
|
||||||
|
## `web::Path`
|
||||||
|
|
||||||
|
The inner field for `web::Path` is now private. It was causing ambiguity when trying to use tuple indexing due to its `Deref` implementation.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- async fn handler(web::Path((foo, bar)): web::Path<(String, String)>) {
|
||||||
|
+ async fn handler(params: web::Path<(String, String)>) {
|
||||||
|
+ let (foo, bar) = params.into_inner();
|
||||||
|
```
|
||||||
|
|
||||||
|
An alternative [path param type with public field but no `Deref` impl is available in `actix-web-lab`](https://docs.rs/actix-web-lab/0.12.0/actix_web_lab/extract/struct.Path.html).
|
||||||
|
|
||||||
|
## Rustls Crate Upgrade
|
||||||
|
|
||||||
|
Actix Web now depends on version 0.20 of `rustls`. As a result, the server config builder has changed. [See the updated example project.](https://github.com/actix/examples/tree/master/https-tls/rustls/)
|
||||||
|
|
||||||
|
## Removed `awc` Client Re-export
|
||||||
|
|
||||||
|
Actix Web's sister crate `awc` is no longer re-exported through the `client` module. This allows `awc` to have its own release cadence—its breaking changes are no longer blocked by Actix Web's (more conservative) release schedule.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- use actix_web::client::Client;
|
||||||
|
+ use awc::Client;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Testing Utils Moved To `actix-test`
|
||||||
|
|
||||||
|
`TestServer` has been moved to its own crate, [`actix-test`](https://docs.rs/actix-test).
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- use use actix_web::test::start;
|
||||||
|
+ use use actix_test::start;
|
||||||
|
```
|
||||||
|
|
||||||
|
`TestServer` previously lived in `actix_web::test`, but it depends on `awc` which is no longer part of Actix Web's public API (see above).
|
||||||
|
|
||||||
|
## Header APIs
|
||||||
|
|
||||||
|
Header related APIs have been standardized across all `actix-*` crates. The terminology now better matches the underlying `HeaderMap` naming conventions.
|
||||||
|
|
||||||
|
In short, "insert" always indicates that any existing headers with the same name are overridden, while "append" is used for adding with no removal (e.g. multi-valued headers).
|
||||||
|
|
||||||
|
For request and response builder APIs, the new methods provide a unified interface for adding key-value pairs _and_ typed headers, which can often be more expressive.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- .set_header("Api-Key", "1234")
|
||||||
|
+ .insert_header(("Api-Key", "1234"))
|
||||||
|
|
||||||
|
- .header("Api-Key", "1234")
|
||||||
|
+ .append_header(("Api-Key", "1234"))
|
||||||
|
|
||||||
|
- .set(ContentType::json())
|
||||||
|
+ .insert_header(ContentType::json())
|
||||||
|
```
|
||||||
|
|
||||||
|
We chose to deprecate most of the old methods instead of removing them immediately—the warning notes will guide you on how to update.
|
||||||
|
|
||||||
|
## Response Body Types
|
||||||
|
|
||||||
|
There have been a lot of changes to response body types. They are now more expressive and their purpose should be more intuitive.
|
||||||
|
|
||||||
|
We have boosted the quality and completeness of the documentation for all items in the [`body` module](https://docs.rs/actix-web/4/actix_web/body).
|
||||||
|
|
||||||
|
### `ResponseBody`
|
||||||
|
|
||||||
|
`ResponseBody` is gone. Its purpose was confusing and has been replaced by better components.
|
||||||
|
|
||||||
|
### `Body`
|
||||||
|
|
||||||
|
`Body` is also gone. In combination with `ResponseBody`, the API it provided was sub-optimal and did not encourage expressive types. Here are the equivalents in the new system (check docs):
|
||||||
|
|
||||||
|
- `Body::None` => `body::None::new()`
|
||||||
|
- `Body::Empty` => `()` / `web::Bytes::new()`
|
||||||
|
- `Body::Bytes` => `web::Bytes::from(...)`
|
||||||
|
- `Body::Message` => `.boxed()` / `BoxBody`
|
||||||
|
|
||||||
|
### `BoxBody`
|
||||||
|
|
||||||
|
`BoxBody` is a new type-erased body type.
|
||||||
|
|
||||||
|
It can be useful when writing handlers, responders, and middleware when you want to trade a (very) small amount of performance for a simpler type.
|
||||||
|
|
||||||
|
Creating a boxed body is done most efficiently by calling [`.boxed()`](https://docs.rs/actix-web/4/actix_web/body/trait.MessageBody.html#method.boxed) on a `MessageBody` type.
|
||||||
|
|
||||||
|
### `EitherBody`
|
||||||
|
|
||||||
|
`EitherBody` is a new "either" type that implements `MessageBody`
|
||||||
|
|
||||||
|
It is particularly useful in middleware that can bail early, returning their own response plus body type. By default the "right" variant is `BoxBody` (i.e., `EitherBody<B>` === `EitherBody<B, BoxBody>`) but it can be anything that implements `MessageBody`.
|
||||||
|
|
||||||
|
For example, it will be common among middleware which value performance of the hot path to use:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
type Response = Result<ServiceResponse<EitherBody<B>>, Error>
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be read (ignoring the `Result`) as "resolves with a `ServiceResponse` that is either the inner service's `B` body type or a boxed body type from elsewhere, likely constructed within the middleware itself". Of course, if your middleware contains only simple string other/error responses, it's possible to use them without boxes at the cost of a less simple implementation:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
type Response = Result<ServiceResponse<EitherBody<B, String>>, Error>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handlers
|
||||||
|
|
||||||
|
`ErrorHandlers` is a commonly used middleware that has changed in design slightly due to the other body type changes.
|
||||||
|
|
||||||
|
In particular, an implicit `EitherBody` is used in the `ErrorHandlerResponse<B>` type. An `ErrorHandlerResponse<B>` now expects a `ServiceResponse<EitherBody<B>>` to be returned within response variants. The following is a migration for an error handler that **only modifies** the response argument (left body).
|
||||||
|
|
||||||
|
```diff
|
||||||
|
fn add_error_header<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>, Error> {
|
||||||
|
res.response_mut().headers_mut().insert(
|
||||||
|
header::CONTENT_TYPE,
|
||||||
|
header::HeaderValue::from_static("Error"),
|
||||||
|
);
|
||||||
|
- Ok(ErrorHandlerResponse::Response(res))
|
||||||
|
+ Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The following is a migration for an error handler that creates a new response instead (right body).
|
||||||
|
|
||||||
|
```diff
|
||||||
|
fn error_handler<B>(res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>, Error> {
|
||||||
|
- let req = res.request().clone();
|
||||||
|
+ let (req, _res) = res.into_parts();
|
||||||
|
|
||||||
|
let res = actix_files::NamedFile::open("./templates/404.html")?
|
||||||
|
.set_status_code(StatusCode::NOT_FOUND)
|
||||||
|
- .into_response(&req)?
|
||||||
|
- .into_body();
|
||||||
|
+ .into_response(&req);
|
||||||
|
|
||||||
|
- let res = ServiceResponse::new(req, res);
|
||||||
|
+ let res = ServiceResponse::new(req, res).map_into_right_body();
|
||||||
|
Ok(ErrorHandlerResponse::Response(res))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Middleware Trait APIs
|
||||||
|
|
||||||
|
The underlying traits that are used for creating middleware, `Service`, `ServiceFactory`, and `Transform`, have changed in design.
|
||||||
|
|
||||||
|
- The associated `Request` type has moved to the type parameter position in order to allow multiple request implementations in other areas of the service stack.
|
||||||
|
- The `self` arguments in `Service` have changed from exclusive (mutable) borrows to shared (immutable) borrows. Since most service layers, such as middleware, do not host mutable state, it reduces the runtime overhead in places where a `RefCell` used to be required for wrapping an inner service.
|
||||||
|
- We've also introduced some macros that reduce boilerplate when implementing `poll_ready`.
|
||||||
|
- Further to the guidance on [response body types](#response-body-types), any use of the old methods on `ServiceResponse` designed to match up body types (e.g., the old `into_body` method), should be replaced with an explicit response body type utilizing `EitherBody<B>`.
|
||||||
|
|
||||||
|
A typical migration would look like this:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
use std::{
|
||||||
|
- cell::RefCell,
|
||||||
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
rc::Rc,
|
||||||
|
- task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use actix_web::{
|
||||||
|
dev::{Service, ServiceRequest, ServiceResponse, Transform},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
use futures_util::future::{ok, LocalBoxFuture, Ready};
|
||||||
|
|
||||||
|
pub struct SayHi;
|
||||||
|
|
||||||
|
- impl<S, B> Transform<S> for SayHi
|
||||||
|
+ impl<S, B> Transform<S, ServiceRequest> for SayHi
|
||||||
|
where
|
||||||
|
- S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
+ S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
- type Request = ServiceRequest;
|
||||||
|
type Response = ServiceResponse<B>;
|
||||||
|
type Error = Error;
|
||||||
|
type InitError = ();
|
||||||
|
type Transform = SayHiMiddleware<S>;
|
||||||
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||||
|
|
||||||
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
|
ok(SayHiMiddleware {
|
||||||
|
- service: Rc::new(RefCell::new(service)),
|
||||||
|
+ service: Rc::new(service),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SayHiMiddleware<S> {
|
||||||
|
- service: Rc<RefCell<S>>,
|
||||||
|
+ service: Rc<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
- impl<S, B> Service for SayHiMiddleware<S>
|
||||||
|
+ impl<S, B> Service<ServiceRequest> for SayHiMiddleware<S>
|
||||||
|
where
|
||||||
|
- S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
+ S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
- type Request = ServiceRequest;
|
||||||
|
type Response = ServiceResponse<B>;
|
||||||
|
type Error = Error;
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
- fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
- self.service.poll_ready(cx)
|
||||||
|
- }
|
||||||
|
+ actix_web::dev::forward_ready!(service);
|
||||||
|
|
||||||
|
- fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||||
|
+ fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
|
println!("Hi from start. You requested: {}", req.path());
|
||||||
|
|
||||||
|
let fut = self.service.call(req);
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
let res = fut.await?;
|
||||||
|
|
||||||
|
println!("Hi from response");
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This new design is forward-looking and should ease transition to traits that support the upcoming Generic Associated Type (GAT) feature in Rust while also trimming down the boilerplate required to implement middleware.
|
||||||
|
|
||||||
|
We understand that creating middleware is still a pain point for Actix Web and we hope to provide [an even more ergonomic solution](https://docs.rs/actix-web-lab/0.11.0/actix_web_lab/middleware/fn.from_fn.html) in a v4.x release.
|
||||||
|
|
||||||
|
## `Responder` Trait
|
||||||
|
|
||||||
|
The `Responder` trait's interface has changed. Errors should be handled and converted to responses within the `respond_to` method. It's also no longer async so the associated `type Future` has been removed; there was no compelling use case found for it. These changes simplify the interface and implementation a lot.
|
||||||
|
|
||||||
|
Now that more emphasis is placed on expressive body types, as explained in the [body types migration section](#response-body-types), this trait has introduced an associated `type Body`. The simplest migration will be to use `BoxBody` + `.map_into_boxed_body()` but if there is a more expressive type for your responder then try to use that instead.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
impl Responder for &'static str {
|
||||||
|
- type Error = Error;
|
||||||
|
- type Future = Ready<Result<HttpResponse, Error>>;
|
||||||
|
+ type Body = &'static str;
|
||||||
|
|
||||||
|
- fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
||||||
|
+ fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
|
let res = HttpResponse::build(StatusCode::OK)
|
||||||
|
.content_type("text/plain; charset=utf-8")
|
||||||
|
.body(self);
|
||||||
|
|
||||||
|
- ok(res)
|
||||||
|
+ res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `App::data` Deprecation :warning:
|
||||||
|
|
||||||
|
The `App::data` method is deprecated. Replace instances of this with `App::app_data`. Exposing both methods led to lots of confusion when trying to extract the data in handlers. Now, when using the `Data` wrapper, the type you put in to `app_data` is the same type you extract in handler arguments.
|
||||||
|
|
||||||
|
You may need to review the [guidance on shared mutable state](https://docs.rs/actix-web/4/actix_web/struct.App.html#shared-mutable-state) in order to migrate this correctly.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
use actix_web::web::Data;
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
async fn handler(my_state: Data<MyState>) -> { todo!() }
|
||||||
|
|
||||||
|
HttpServer::new(|| {
|
||||||
|
- App::new()
|
||||||
|
- .data(MyState::default())
|
||||||
|
- .service(hander)
|
||||||
|
|
||||||
|
+ let my_state: Data<MyState> = Data::new(MyState::default());
|
||||||
|
+
|
||||||
|
+ App::new()
|
||||||
|
+ .app_data(my_state)
|
||||||
|
+ .service(hander)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Direct Dependency On `actix-rt` And `actix-service`
|
||||||
|
|
||||||
|
Improvements to module management and re-exports have resulted in not needing direct dependencies on these underlying crates for the vast majority of cases. In particular:
|
||||||
|
|
||||||
|
- all traits necessary for creating middlewares are now re-exported through the `dev` modules;
|
||||||
|
- `#[actix_web::test]` now exists for async test definitions.
|
||||||
|
|
||||||
|
Relying on these re-exports will ease the transition to future versions of Actix Web.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- use actix_service::{Service, Transform};
|
||||||
|
+ use actix_web::dev::{Service, Transform};
|
||||||
|
```
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- #[actix_rt::test]
|
||||||
|
+ #[actix_web::test]
|
||||||
|
async fn test_thing() {
|
||||||
|
```
|
||||||
|
|
||||||
|
## Server Must Be Polled :warning:
|
||||||
|
|
||||||
|
In order to _start_ serving requests, the `Server` object returned from `run` **must** be `poll`ed, `await`ed, or `spawn`ed. This was done to prevent unexpected behavior and ensure that things like signal handlers are able to function correctly when enabled.
|
||||||
|
|
||||||
|
For example, in this contrived example where the server is started and then the main thread is sent to sleep, the server will no longer be able to serve requests with v4.0:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() {
|
||||||
|
HttpServer::new(|| App::new().default_service(web::to(HttpResponse::Conflict)))
|
||||||
|
.bind(("127.0.0.1", 8080))
|
||||||
|
.unwrap()
|
||||||
|
.run();
|
||||||
|
|
||||||
|
thread::sleep(Duration::from_secs(1000));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Guards API
|
||||||
|
|
||||||
|
Implementors of routing guards will need to use the modified interface of the `Guard` trait. The API is more flexible than before. See [guard module docs](https://docs.rs/actix-web/4/actix_web/guard/struct.GuardContext.html) for more details.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
struct MethodGuard(HttpMethod);
|
||||||
|
|
||||||
|
impl Guard for MethodGuard {
|
||||||
|
- fn check(&self, request: &RequestHead) -> bool {
|
||||||
|
+ fn check(&self, ctx: &GuardContext<'_>) -> bool {
|
||||||
|
- request.method == self.0
|
||||||
|
+ ctx.head().method == self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Returning `HttpResponse` synchronously
|
||||||
|
|
||||||
|
The implementation of `Future` for `HttpResponse` was removed because it was largely useless for all but the simplest handlers like `web::to(|| HttpResponse::Ok().finish())`. It also caused false positives on the `async_yields_async` clippy lint in reasonable scenarios. The compiler errors will looks something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
web::to(|| HttpResponse::Ok().finish())
|
||||||
|
^^^^^^^ the trait `Handler<_>` is not implemented for `[closure@...]`
|
||||||
|
```
|
||||||
|
|
||||||
|
This form should be replaced with explicit async functions and closures:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- fn handler() -> HttpResponse {
|
||||||
|
+ async fn handler() -> HttpResponse {
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- web::to(|| HttpResponse::Ok().finish())
|
||||||
|
+ web::to(|| async { HttpResponse::Ok().finish() })
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, for these extremely simple cases, utilise an `HttpResponseBuilder`:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- web::to(|| HttpResponse::Ok().finish())
|
||||||
|
+ web::to(HttpResponse::Ok)
|
||||||
|
```
|
||||||
|
|
||||||
|
## `#[actix_web::main]` and `#[tokio::main]`
|
||||||
|
|
||||||
|
Actix Web now works seamlessly with the primary way of starting a multi-threaded Tokio runtime, `#[tokio::main]`. Therefore, it is no longer necessary to spawn a thread when you need to run something alongside Actix Web that uses Tokio's multi-threaded mode; you can simply await the server within this context or, if preferred, use `tokio::spawn` just like any other async task.
|
||||||
|
|
||||||
|
For now, `actix` actor support (and therefore WebSocket support via `actix-web-actors`) still requires `#[actix_web::main]` so that a `System` context is created. Designs are being created for an alternative WebSocket interface that does not require actors that should land sometime in the v4.x cycle.
|
||||||
|
|
||||||
|
## `web::block`
|
||||||
|
|
||||||
|
The `web::block` helper has changed return type from roughly `async fn(fn() -> Result<T, E>) Result<T, BlockingError<E>>` to `async fn(fn() -> T) Result<T, BlockingError>`. That's to say that the blocking function can now return things that are not `Result`s and it does not wrap error types anymore. If you still need to return `Result`s then you'll likely want to use double `?` after the `.await`.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- let n: u32 = web::block(|| Ok(123)).await?;
|
||||||
|
+ let n: u32 = web::block(|| 123).await?;
|
||||||
|
|
||||||
|
- let n: u32 = web::block(|| Ok(123)).await?;
|
||||||
|
+ let n: u32 = web::block(|| Ok(123)).await??;
|
||||||
|
```
|
||||||
|
|
||||||
|
## `HttpResponse` as a `ResponseError`
|
||||||
|
|
||||||
|
The implementation of `ResponseError` for `HttpResponse` has been removed.
|
||||||
|
|
||||||
|
It was common in v3 to use `HttpResponse` as an error type in fallible handlers. The problem is that `HttpResponse` contains no knowledge or reference to the source error. Being able to guarantee that an "error" response actually contains an error reference makes middleware and other parts of Actix Web more effective.
|
||||||
|
|
||||||
|
The error response builders in the `error` module were available in v3 but are now the best method for simple error responses without requiring you to implement the trait on your own custom error types. These builders can receive simple strings and third party errors that can not implement the `ResponseError` trait.
|
||||||
|
|
||||||
|
A few common patterns are affected by this change:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- Err(HttpResponse::InternalServerError().finish())
|
||||||
|
+ Err(error::ErrorInternalServerError("reason"))
|
||||||
|
|
||||||
|
- Err(HttpResponse::InternalServerError().body(third_party_error.to_string()))
|
||||||
|
+ Err(error::ErrorInternalServerError(err))
|
||||||
|
|
||||||
|
- .map_err(|err| HttpResponse::InternalServerError().finish())?
|
||||||
|
+ .map_err(error::ErrorInternalServerError)?
|
||||||
|
```
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user