mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-04 18:06:23 +02:00
Compare commits
98 Commits
files-v0.6
...
test-v0.1.
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
a66cd38ec5 | |||
20609e93fd | |||
bf282472ab | |||
7f4b44c258 | |||
66243717b3 | |||
102720d398 | |||
c3c7eb8df9 | |||
21f57caf4a | |||
47f5faf26e | |||
9777653dc0 | |||
9fde5b30db | |||
fd412a8223 | |||
cd511affd5 | |||
3200de3f34 | |||
b3e84b5c4b | |||
a3416112a5 | |||
21a08ca796 | |||
a9f497d05f | |||
cc9ba162f7 | |||
37799df978 | |||
0d93a8c273 | |||
3ae4f0a629 | |||
14a4f325d3 | |||
1bd2076b35 | |||
5454699bab | |||
d7c5c966d2 | |||
50894e392e | |||
008753f07a | |||
c92aa31f91 | |||
c25dd23820 | |||
acacb90b2e | |||
8459f566a8 | |||
232a14dc8b | |||
6e9f5fba24 | |||
c5d6df0078 | |||
8865540f3b | |||
141790b200 | |||
9668a2396f | |||
cb7347216c | |||
ae7f71e317 | |||
bc89f0bfc2 | |||
c959916346 | |||
f227e880d7 | |||
68ad81f989 | |||
f2e736719a | |||
81ef12a0fd | |||
1bc1538118 | |||
1cc3e7b24c | |||
3dd98c308c | |||
cb5d9a7e64 | |||
5ee555462f | |||
ad159f5219 | |||
2ffc21dd4f | |||
7b8a392ef5 | |||
3c7ccf5521 | |||
e7cae5a95b |
@ -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:
|
||||||
@ -125,16 +125,44 @@ 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:
|
# job currently (1st Feb 2022) segfaults
|
||||||
name: coverage
|
# 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 }
|
||||||
|
|
||||||
|
nextest:
|
||||||
|
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 +170,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
|
18
.github/workflows/clippy-fmt.yml
vendored
18
.github/workflows/clippy-fmt.yml
vendored
@ -46,3 +46,21 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
args: --workspace --tests --examples --all-features
|
args: --workspace --tests --examples --all-features
|
||||||
|
|
||||||
|
lint-docs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
profile: minimal
|
||||||
|
components: rust-docs
|
||||||
|
- name: Check for broken intra-doc links
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
env:
|
||||||
|
RUSTDOCFLAGS: "-D warnings"
|
||||||
|
with:
|
||||||
|
command: doc
|
||||||
|
args: --no-deps --all-features --workspace
|
||||||
|
3
.prettierrc.json
Normal file
3
.prettierrc.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"proseWrap": "never"
|
||||||
|
}
|
1004
CHANGES.md
1004
CHANGES.md
File diff suppressed because it is too large
Load Diff
142
Cargo.toml
142
Cargo.toml
@ -1,33 +1,6 @@
|
|||||||
[package]
|
|
||||||
name = "actix-web"
|
|
||||||
version = "4.0.0-beta.20"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.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",
|
||||||
@ -36,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.0.0-rc.4"
|
|
||||||
actix-service = "2.0.0"
|
|
||||||
actix-utils = "3.0.0"
|
|
||||||
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
|
||||||
|
|
||||||
actix-http = "3.0.0-beta.18"
|
|
||||||
actix-router = "0.5.0-rc.1"
|
|
||||||
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.14"
|
|
||||||
actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] }
|
|
||||||
awc = { version = "3.0.0-beta.18", features = ["openssl"] }
|
|
||||||
|
|
||||||
brotli2 = "0.3.2"
|
|
||||||
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
|
||||||
@ -139,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" }
|
||||||
@ -152,31 +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"]
|
|
||||||
|
|
||||||
[[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`
|
|
109
README.md
109
README.md
@ -1,109 +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-beta.20)
|
|
||||||

|
|
||||||

|
|
||||||
[](https://deps.rs/crate/actix-web/4.0.0-beta.20)
|
|
||||||
<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
|
|
||||||
- Keep-alive and slow requests handling
|
|
||||||
- Client/server [WebSockets](https://actix.rs/docs/websockets/) support
|
|
||||||
- Transparent content compression/decompression (br, gzip, deflate, zstd)
|
|
||||||
- Powerful [request routing](https://actix.rs/docs/url-dispatch/)
|
|
||||||
- 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 = "3"
|
|
||||||
```
|
|
||||||
|
|
||||||
Code:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use actix_web::{get, web, App, HttpServer, Responder};
|
|
||||||
|
|
||||||
#[get("/{id}/{name}/index.html")]
|
|
||||||
async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
|
|
||||||
format!("Hello {}! id:{}", name, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::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
|
|
||||||
|
|
||||||
- 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])
|
|
||||||
|
|
||||||
at your option.
|
|
||||||
|
|
||||||
## 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,14 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0-beta.16 - 2022-01-31
|
||||||
|
- No significant changes since `0.6.0-beta.15`.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0-beta.15 - 2022-01-21
|
||||||
|
- No significant changes since `0.6.0-beta.14`.
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.14 - 2022-01-14
|
## 0.6.0-beta.14 - 2022-01-14
|
||||||
- The `prefer_utf8` option introduced in `0.4.0` is now true by default. [#2583]
|
- The `prefer_utf8` option introduced in `0.4.0` is now true by default. [#2583]
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.6.0-beta.14"
|
version = "0.6.0-beta.16"
|
||||||
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-beta.18"
|
actix-http = "3.0.0-rc.3"
|
||||||
actix-service = "2"
|
actix-service = "2"
|
||||||
actix-utils = "3"
|
actix-utils = "3"
|
||||||
actix-web = { version = "4.0.0-beta.20", default-features = false }
|
actix-web = { version = "4.0.0-rc.3", default-features = false }
|
||||||
|
|
||||||
askama_escape = "0.10"
|
askama_escape = "0.10"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
@ -43,6 +43,6 @@ tokio-uring = { version = "0.2", optional = true, features = ["bytes"] }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.11"
|
actix-test = "0.1.0-beta.13"
|
||||||
actix-web = "4.0.0-beta.20"
|
actix-web = "4.0.0-rc.3"
|
||||||
tempfile = "3.2"
|
tempfile = "3.2"
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
> 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.14)
|
[](https://docs.rs/actix-files/0.6.0-beta.16)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
[](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.14)
|
[](https://deps.rs/crate/actix-files/0.6.0-beta.16)
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
@ -37,7 +37,7 @@ use crate::{
|
|||||||
/// .service(Files::new("/static", "."));
|
/// .service(Files::new("/static", "."));
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Files {
|
pub struct Files {
|
||||||
path: String,
|
mount_path: String,
|
||||||
directory: PathBuf,
|
directory: PathBuf,
|
||||||
index: Option<String>,
|
index: Option<String>,
|
||||||
show_index: bool,
|
show_index: bool,
|
||||||
@ -68,7 +68,7 @@ impl Clone for Files {
|
|||||||
default: self.default.clone(),
|
default: self.default.clone(),
|
||||||
renderer: self.renderer.clone(),
|
renderer: self.renderer.clone(),
|
||||||
file_flags: self.file_flags,
|
file_flags: self.file_flags,
|
||||||
path: self.path.clone(),
|
mount_path: self.mount_path.clone(),
|
||||||
mime_override: self.mime_override.clone(),
|
mime_override: self.mime_override.clone(),
|
||||||
path_filter: self.path_filter.clone(),
|
path_filter: self.path_filter.clone(),
|
||||||
use_guards: self.use_guards.clone(),
|
use_guards: self.use_guards.clone(),
|
||||||
@ -107,7 +107,7 @@ impl Files {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Files {
|
Files {
|
||||||
path: mount_path.trim_end_matches('/').to_owned(),
|
mount_path: mount_path.trim_end_matches('/').to_owned(),
|
||||||
directory: dir,
|
directory: dir,
|
||||||
index: None,
|
index: None,
|
||||||
show_index: false,
|
show_index: false,
|
||||||
@ -342,9 +342,9 @@ impl HttpServiceFactory for Files {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let rdef = if config.is_root() {
|
let rdef = if config.is_root() {
|
||||||
ResourceDef::root_prefix(&self.path)
|
ResourceDef::root_prefix(&self.mount_path)
|
||||||
} else {
|
} else {
|
||||||
ResourceDef::prefix(&self.path)
|
ResourceDef::prefix(&self.mount_path)
|
||||||
};
|
};
|
||||||
|
|
||||||
config.register_service(rdef, guards, self, None)
|
config.register_service(rdef, guards, self, None)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! Provides a non-blocking service for serving static files from disk.
|
//! Provides a non-blocking service for serving static files from disk.
|
||||||
//!
|
//!
|
||||||
//! # Example
|
//! # Examples
|
||||||
//! ```
|
//! ```
|
||||||
//! use actix_web::App;
|
//! use actix_web::App;
|
||||||
//! use actix_files::Files;
|
//! use actix_files::Files;
|
||||||
@ -106,7 +106,7 @@ mod tests {
|
|||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.insert_header((header::IF_MODIFIED_SINCE, since))
|
.insert_header((header::IF_MODIFIED_SINCE, since))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ mod tests {
|
|||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.insert_header((header::IF_MODIFIED_SINCE, since))
|
.insert_header((header::IF_MODIFIED_SINCE, since))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ mod tests {
|
|||||||
.insert_header((header::IF_NONE_MATCH, "miss_etag"))
|
.insert_header((header::IF_NONE_MATCH, "miss_etag"))
|
||||||
.insert_header((header::IF_MODIFIED_SINCE, since))
|
.insert_header((header::IF_MODIFIED_SINCE, since))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
|
assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ mod tests {
|
|||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.insert_header((header::IF_UNMODIFIED_SINCE, since))
|
.insert_header((header::IF_UNMODIFIED_SINCE, since))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +155,7 @@ mod tests {
|
|||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.insert_header((header::IF_UNMODIFIED_SINCE, since))
|
.insert_header((header::IF_UNMODIFIED_SINCE, since))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED);
|
assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,7 +172,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
"text/x-toml"
|
"text/x-toml"
|
||||||
@ -196,7 +196,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
||||||
"inline; filename=\"Cargo.toml\""
|
"inline; filename=\"Cargo.toml\""
|
||||||
@ -207,7 +207,7 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.disable_content_disposition();
|
.disable_content_disposition();
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none());
|
assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,7 +235,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
"text/x-toml"
|
"text/x-toml"
|
||||||
@ -261,7 +261,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
"text/xml"
|
"text/xml"
|
||||||
@ -284,7 +284,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
"image/png"
|
"image/png"
|
||||||
@ -300,7 +300,7 @@ mod tests {
|
|||||||
let file = NamedFile::open_async("tests/test.js").await.unwrap();
|
let file = NamedFile::open_async("tests/test.js").await.unwrap();
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
"application/javascript; charset=utf-8"
|
"application/javascript; charset=utf-8"
|
||||||
@ -330,7 +330,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
"image/png"
|
"image/png"
|
||||||
@ -353,7 +353,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
"application/octet-stream"
|
"application/octet-stream"
|
||||||
@ -379,7 +379,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
"text/x-toml"
|
"text/x-toml"
|
||||||
@ -633,7 +633,7 @@ mod tests {
|
|||||||
async fn test_named_file_allowed_method() {
|
async fn test_named_file_allowed_method() {
|
||||||
let req = TestRequest::default().method(Method::GET).to_http_request();
|
let req = TestRequest::default().method(Method::GET).to_http_request();
|
||||||
let file = NamedFile::open_async("Cargo.toml").await.unwrap();
|
let file = NamedFile::open_async("Cargo.toml").await.unwrap();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,6 +209,7 @@ 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.
|
||||||
@ -298,9 +299,11 @@ impl NamedFile {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set content encoding for serving this file
|
/// Sets content encoding for this file.
|
||||||
///
|
///
|
||||||
/// Must be used with [`actix_web::middleware::Compress`] to take effect.
|
/// This prevents the `Compress` middleware from modifying the file contents and signals to
|
||||||
|
/// browsers/clients how to decode it. For example, if serving a compressed HTML file (e.g.,
|
||||||
|
/// `index.html.gz`) then use `.set_content_encoding(ContentEncoding::Gzip)`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self {
|
pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self {
|
||||||
self.encoding = Some(enc);
|
self.encoding = Some(enc);
|
||||||
|
@ -59,6 +59,8 @@ impl PathBufWrap {
|
|||||||
continue;
|
continue;
|
||||||
} else if cfg!(windows) && segment.contains('\\') {
|
} else if cfg!(windows) && segment.contains('\\') {
|
||||||
return Err(UriSegmentError::BadChar('\\'));
|
return Err(UriSegmentError::BadChar('\\'));
|
||||||
|
} else if cfg!(windows) && segment.contains(':') {
|
||||||
|
return Err(UriSegmentError::BadChar(':'));
|
||||||
} else {
|
} else {
|
||||||
buf.push(segment)
|
buf.push(segment)
|
||||||
}
|
}
|
||||||
@ -66,7 +68,11 @@ impl PathBufWrap {
|
|||||||
|
|
||||||
// make sure we agree with stdlib parser
|
// make sure we agree with stdlib parser
|
||||||
for (i, component) in buf.components().enumerate() {
|
for (i, component) in buf.components().enumerate() {
|
||||||
assert!(matches!(component, Component::Normal(_)));
|
assert!(
|
||||||
|
matches!(component, Component::Normal(_)),
|
||||||
|
"component `{:?}` is not normal",
|
||||||
|
component
|
||||||
|
);
|
||||||
assert!(i < segment_count);
|
assert!(i < segment_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +91,7 @@ impl FromRequest for PathBufWrap {
|
|||||||
type Future = Ready<Result<Self, Self::Error>>;
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
ready(req.match_info().path().parse())
|
ready(req.match_info().unprocessed().parse())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,4 +165,26 @@ mod tests {
|
|||||||
PathBuf::from_iter(vec!["etc/passwd"])
|
PathBuf::from_iter(vec!["etc/passwd"])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg_attr(windows, should_panic)]
|
||||||
|
fn windows_drive_traversal() {
|
||||||
|
// detect issues in windows that could lead to path traversal
|
||||||
|
// see <https://github.com/SergioBenitez/Rocket/issues/1949
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
PathBufWrap::parse_path("C:test.txt", false).unwrap().0,
|
||||||
|
PathBuf::from_iter(vec!["C:test.txt"])
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
PathBufWrap::parse_path("C:../whatever", false).unwrap().0,
|
||||||
|
PathBuf::from_iter(vec!["C:../whatever"])
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
PathBufWrap::parse_path(":test.txt", false).unwrap().0,
|
||||||
|
PathBuf::from_iter(vec![":test.txt"])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,14 +120,16 @@ impl Service<ServiceRequest> for FilesService {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let real_path =
|
let path_on_disk = match PathBufWrap::parse_path(
|
||||||
match PathBufWrap::parse_path(req.match_info().path(), this.hidden_files) {
|
req.match_info().unprocessed(),
|
||||||
Ok(item) => item,
|
this.hidden_files,
|
||||||
Err(err) => return Ok(req.error_response(err)),
|
) {
|
||||||
};
|
Ok(item) => item,
|
||||||
|
Err(err) => return Ok(req.error_response(err)),
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(filter) = &this.path_filter {
|
if let Some(filter) = &this.path_filter {
|
||||||
if !filter(real_path.as_ref(), req.head()) {
|
if !filter(path_on_disk.as_ref(), req.head()) {
|
||||||
if let Some(ref default) = this.default {
|
if let Some(ref default) = this.default {
|
||||||
return default.call(req).await;
|
return default.call(req).await;
|
||||||
} else {
|
} else {
|
||||||
@ -137,7 +139,7 @@ impl Service<ServiceRequest> for FilesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// full file path
|
// full file path
|
||||||
let path = this.directory.join(&real_path);
|
let path = this.directory.join(&path_on_disk);
|
||||||
if let Err(err) = path.canonicalize() {
|
if let Err(err) = path.canonicalize() {
|
||||||
return this.handle_err(err, req).await;
|
return this.handle_err(err, req).await;
|
||||||
}
|
}
|
||||||
@ -166,7 +168,7 @@ impl Service<ServiceRequest> for FilesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None if this.show_index => Ok(this.show_index(req, path)),
|
None if this.show_index => Ok(this.show_index(req, path)),
|
||||||
_ => Ok(ServiceResponse::from_err(
|
None => Ok(ServiceResponse::from_err(
|
||||||
FilesError::IsDirectory,
|
FilesError::IsDirectory,
|
||||||
req.into_parts().0,
|
req.into_parts().0,
|
||||||
)),
|
)),
|
||||||
|
@ -3,6 +3,14 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- No significant changes since `3.0.0-beta.11`.
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.11 - 2022-01-04
|
## 3.0.0-beta.11 - 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-http-test"
|
name = "actix-http-test"
|
||||||
version = "3.0.0-beta.11"
|
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"]
|
||||||
@ -30,12 +30,12 @@ openssl = ["tls-openssl", "awc/openssl"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "2.0.0"
|
actix-service = "2.0.0"
|
||||||
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.0.0"
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-server = "2.0.0-rc.2"
|
actix-server = "2"
|
||||||
awc = { version = "3.0.0-beta.18", default-features = false }
|
awc = { version = "3.0.0-beta.21", 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-beta.20", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] }
|
||||||
actix-http = "3.0.0-beta.18"
|
actix-http = "3.0.0-rc.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.11)
|
[](https://docs.rs/actix-http-test/3.0.0-beta.13)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
[](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.11)
|
[](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,73 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 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
|
||||||
|
### Added
|
||||||
|
- Implement `Default` for `KeepAlive`. [#2611]
|
||||||
|
- Implement `From<Duration>` for `KeepAlive`. [#2611]
|
||||||
|
- Implement `From<Option<Duration>>` for `KeepAlive`. [#2611]
|
||||||
|
- Implement `Default` for `HttpServiceBuilder`. [#2611]
|
||||||
|
- Crate `ws` feature flag, disabled by default. [#2618]
|
||||||
|
- Crate `http2` feature flag, disabled by default. [#2618]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Rename `ServiceConfig::{client_timer_expire => client_request_deadline}`. [#2611]
|
||||||
|
- Rename `ServiceConfig::{client_disconnect_timer => client_disconnect_deadline}`. [#2611]
|
||||||
|
- Deadline methods in `ServiceConfig` now return `std::time::Instant`s instead of Tokio's wrapper type. [#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]
|
||||||
|
- `ServiceConfig::keep_alive` now returns a `KeepAlive`. [#2611]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- HTTP/1.1 dispatcher correctly uses client request timeout. [#2611]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `ServiceConfig::{client_timer, keep_alive_timer}`. [#2611]
|
||||||
|
- `impl From<usize> for KeepAlive`; use `Duration`s instead. [#2611]
|
||||||
|
- `impl From<Option<usize>> for KeepAlive`; use `Duration`s instead. [#2611]
|
||||||
|
- `HttpServiceBuilder::new`; use `default` instead. [#2611]
|
||||||
|
|
||||||
|
[#2611]: https://github.com/actix/actix-web/pull/2611
|
||||||
|
[#2618]: https://github.com/actix/actix-web/pull/2618
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.19 - 2022-01-21
|
||||||
|
### Added
|
||||||
|
- Response headers can be sent as camel case using `res.head_mut().set_camel_case_headers(true)`. [#2587]
|
||||||
|
- `ResponseHead` now implements `Clone`. [#2585]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Brotli (de)compression support is now provided by the `brotli` crate. [#2538]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `ResponseHead::extensions[_mut]()`. [#2585]
|
||||||
|
- `ResponseBuilder::extensions[_mut]()`. [#2585]
|
||||||
|
|
||||||
|
[#2538]: https://github.com/actix/actix-web/pull/2538
|
||||||
|
[#2585]: https://github.com/actix/actix-web/pull/2585
|
||||||
|
[#2587]: https://github.com/actix/actix-web/pull/2587
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.18 - 2022-01-04
|
## 3.0.0-beta.18 - 2022-01-04
|
||||||
### Added
|
### Added
|
||||||
- `impl Eq` for `header::ContentEncoding`. [#2501]
|
- `impl Eq` for `header::ContentEncoding`. [#2501]
|
||||||
@ -15,8 +82,8 @@
|
|||||||
- `Quality::MIN` is now the smallest non-zero value. [#2501]
|
- `Quality::MIN` is now the smallest non-zero value. [#2501]
|
||||||
- `QualityItem::min` semantics changed with `QualityItem::MIN`. [#2501]
|
- `QualityItem::min` semantics changed with `QualityItem::MIN`. [#2501]
|
||||||
- Rename `ContentEncoding::{Br => Brotli}`. [#2501]
|
- Rename `ContentEncoding::{Br => Brotli}`. [#2501]
|
||||||
- Minimum supported Rust version (MSRV) is now 1.54.
|
|
||||||
- Rename `header::EntityTag::{weak => new_weak, strong => new_strong}`. [#2565]
|
- Rename `header::EntityTag::{weak => new_weak, strong => new_strong}`. [#2565]
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- `ContentEncoding::Identity` can now be parsed from a string. [#2501]
|
- `ContentEncoding::Identity` can now be parsed from a string. [#2501]
|
||||||
@ -399,6 +466,13 @@
|
|||||||
[#1878]: https://github.com/actix/actix-web/pull/1878
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
|
||||||
|
|
||||||
|
## 2.2.2 - 2022-01-21
|
||||||
|
### Changed
|
||||||
|
- Migrate to `brotli` crate. [ad7e3c06]
|
||||||
|
|
||||||
|
[ad7e3c06]: https://github.com/actix/actix-web/commit/ad7e3c06
|
||||||
|
|
||||||
|
|
||||||
## 2.2.1 - 2021-08-09
|
## 2.2.1 - 2021-08-09
|
||||||
### Fixed
|
### Fixed
|
||||||
- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
|
- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "3.0.0-beta.18"
|
version = "3.0.0-rc.3"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = [
|
||||||
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
|
]
|
||||||
description = "HTTP primitives for the Actix ecosystem"
|
description = "HTTP primitives for the Actix ecosystem"
|
||||||
keywords = ["actix", "http", "framework", "async", "futures"]
|
keywords = ["actix", "http", "framework", "async", "futures"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
@ -17,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", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "actix_http"
|
name = "actix_http"
|
||||||
@ -26,68 +29,85 @@ path = "src/lib.rs"
|
|||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
# openssl
|
# HTTP/2 protocol support
|
||||||
|
http2 = ["h2"]
|
||||||
|
|
||||||
|
# WebSocket protocol implementation
|
||||||
|
ws = [
|
||||||
|
"local-channel",
|
||||||
|
"base64",
|
||||||
|
"rand",
|
||||||
|
"sha-1",
|
||||||
|
]
|
||||||
|
|
||||||
|
# TLS via OpenSSL
|
||||||
openssl = ["actix-tls/accept", "actix-tls/openssl"]
|
openssl = ["actix-tls/accept", "actix-tls/openssl"]
|
||||||
|
|
||||||
# rustls support
|
# TLS via Rustls
|
||||||
rustls = ["actix-tls/accept", "actix-tls/rustls"]
|
rustls = ["actix-tls/accept", "actix-tls/rustls"]
|
||||||
|
|
||||||
# enable compression support
|
# Compression codecs
|
||||||
compress-brotli = ["brotli2", "__compress"]
|
compress-brotli = ["__compress", "brotli"]
|
||||||
compress-gzip = ["flate2", "__compress"]
|
compress-gzip = ["__compress", "flate2"]
|
||||||
compress-zstd = ["zstd", "__compress"]
|
compress-zstd = ["__compress", "zstd"]
|
||||||
|
|
||||||
# Internal (PRIVATE!) features used to aid testing and cheking feature status.
|
# Internal (PRIVATE!) features used to aid testing and cheking feature status.
|
||||||
# Don't rely on these whatsoever. They may disappear at anytime.
|
# Don't rely on these whatsoever. They are semver-exempt and may disappear at anytime.
|
||||||
__compress = []
|
__compress = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "2.0.0"
|
actix-service = "2"
|
||||||
actix-codec = "0.4.1"
|
actix-codec = "0.5"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3"
|
||||||
actix-rt = { version = "2.2", default-features = false }
|
actix-rt = { version = "2.2", default-features = false }
|
||||||
|
|
||||||
ahash = "0.7"
|
ahash = "0.7"
|
||||||
base64 = "0.13"
|
|
||||||
bitflags = "1.2"
|
bitflags = "1.2"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
h2 = "0.3.9"
|
|
||||||
http = "0.2.5"
|
http = "0.2.5"
|
||||||
httparse = "1.5.1"
|
httparse = "1.5.1"
|
||||||
httpdate = "1.0.1"
|
httpdate = "1.0.1"
|
||||||
itoa = "1"
|
itoa = "1"
|
||||||
language-tags = "0.3"
|
language-tags = "0.3"
|
||||||
local-channel = "0.1"
|
|
||||||
log = "0.4"
|
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"
|
||||||
rand = "0.8"
|
|
||||||
sha-1 = "0.10"
|
|
||||||
smallvec = "1.6.1"
|
smallvec = "1.6.1"
|
||||||
|
|
||||||
# tls
|
# http2
|
||||||
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
h2 = { version = "0.3.9", optional = true }
|
||||||
|
|
||||||
# compression
|
# websockets
|
||||||
brotli2 = { version="0.3.2", optional = true }
|
local-channel = { version = "0.1", optional = true }
|
||||||
|
base64 = { version = "0.13", optional = true }
|
||||||
|
rand = { version = "0.8", optional = true }
|
||||||
|
sha-1 = { version = "0.10", optional = true }
|
||||||
|
|
||||||
|
# openssl/rustls
|
||||||
|
actix-tls = { version = "3", default-features = false, optional = true }
|
||||||
|
|
||||||
|
# compress-*
|
||||||
|
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.10", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] }
|
||||||
actix-server = "2.0.0-rc.2"
|
actix-server = "2"
|
||||||
actix-tls = { version = "3.0.0", features = ["openssl"] }
|
actix-tls = { version = "3", features = ["openssl"] }
|
||||||
actix-web = "4.0.0-beta.20"
|
actix-web = "4.0.0-rc.3"
|
||||||
|
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
|
memchr = "2.4"
|
||||||
|
once_cell = "1.9"
|
||||||
rcgen = "0.8"
|
rcgen = "0.8"
|
||||||
regex = "1.3"
|
regex = "1.3"
|
||||||
rustls-pemfile = "0.2"
|
rustls-pemfile = "0.2"
|
||||||
|
@ -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-beta.18)
|
[](https://docs.rs/actix-http/3.0.0-rc.3)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-http/3.0.0-beta.18)
|
[](https://deps.rs/crate/actix-http/3.0.0-rc.3)
|
||||||
[](https://crates.io/crates/actix-http)
|
[](https://crates.io/crates/actix-http)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
27
actix-http/examples/bench.rs
Normal file
27
actix-http/examples/bench.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use std::{convert::Infallible, io, time::Duration};
|
||||||
|
|
||||||
|
use actix_http::{HttpService, Request, Response, StatusCode};
|
||||||
|
use actix_server::Server;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
static STR: Lazy<String> = Lazy::new(|| "HELLO WORLD ".repeat(20));
|
||||||
|
|
||||||
|
#[actix_rt::main]
|
||||||
|
async fn main() -> io::Result<()> {
|
||||||
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
|
|
||||||
|
Server::build()
|
||||||
|
.bind("dispatcher-benchmark", ("127.0.0.1", 8080), || {
|
||||||
|
HttpService::build()
|
||||||
|
.client_request_timeout(Duration::from_secs(1))
|
||||||
|
.finish(|_: Request| async move {
|
||||||
|
let mut res = Response::build(StatusCode::OK);
|
||||||
|
Ok::<_, Infallible>(res.body(&**STR))
|
||||||
|
})
|
||||||
|
.tcp()
|
||||||
|
})?
|
||||||
|
// limiting number of workers so that bench client is not sharing as many resources
|
||||||
|
.workers(4)
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
use std::io;
|
use std::{io, time::Duration};
|
||||||
|
|
||||||
use actix_http::{Error, HttpService, Request, Response, StatusCode};
|
use actix_http::{Error, HttpService, Request, Response, StatusCode};
|
||||||
use actix_server::Server;
|
use actix_server::Server;
|
||||||
@ -13,8 +13,9 @@ async fn main() -> io::Result<()> {
|
|||||||
Server::build()
|
Server::build()
|
||||||
.bind("echo", ("127.0.0.1", 8080), || {
|
.bind("echo", ("127.0.0.1", 8080), || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(1000)
|
.client_request_timeout(Duration::from_secs(1))
|
||||||
.client_disconnect(1000)
|
.client_disconnect_timeout(Duration::from_secs(1))
|
||||||
|
// handles HTTP/1.1 and HTTP/2
|
||||||
.finish(|mut req: Request| async move {
|
.finish(|mut req: Request| async move {
|
||||||
let mut body = BytesMut::new();
|
let mut body = BytesMut::new();
|
||||||
while let Some(item) = req.payload().next().await {
|
while let Some(item) = req.payload().next().await {
|
||||||
@ -23,12 +24,13 @@ async fn main() -> io::Result<()> {
|
|||||||
|
|
||||||
log::info!("request body: {:?}", body);
|
log::info!("request body: {:?}", body);
|
||||||
|
|
||||||
Ok::<_, Error>(
|
let res = Response::build(StatusCode::OK)
|
||||||
Response::build(StatusCode::OK)
|
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
|
||||||
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
|
.body(body);
|
||||||
.body(body),
|
|
||||||
)
|
Ok::<_, Error>(res)
|
||||||
})
|
})
|
||||||
|
// No TLS
|
||||||
.tcp()
|
.tcp()
|
||||||
})?
|
})?
|
||||||
.run()
|
.run()
|
||||||
|
@ -1,32 +1,34 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response, StatusCode,
|
body::{BodyStream, MessageBody},
|
||||||
|
header, Error, HttpMessage, HttpService, Request, Response, StatusCode,
|
||||||
};
|
};
|
||||||
use actix_server::Server;
|
|
||||||
use bytes::BytesMut;
|
|
||||||
use futures_util::StreamExt as _;
|
|
||||||
|
|
||||||
async fn handle_request(mut req: Request) -> Result<Response<impl MessageBody>, Error> {
|
async fn handle_request(mut req: Request) -> Result<Response<impl MessageBody>, Error> {
|
||||||
let mut body = BytesMut::new();
|
let mut res = Response::build(StatusCode::OK);
|
||||||
while let Some(item) = req.payload().next().await {
|
|
||||||
body.extend_from_slice(&item?)
|
if let Some(ct) = req.headers().get(header::CONTENT_TYPE) {
|
||||||
|
res.insert_header((header::CONTENT_TYPE, ct));
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("request body: {:?}", body);
|
// echo request payload stream as (chunked) response body
|
||||||
|
let res = res.message_body(BodyStream::new(req.payload().take()))?;
|
||||||
|
|
||||||
Ok(Response::build(StatusCode::OK)
|
Ok(res)
|
||||||
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
|
|
||||||
.body(body))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
|
|
||||||
Server::build()
|
actix_server::Server::build()
|
||||||
.bind("echo", ("127.0.0.1", 8080), || {
|
.bind("echo", ("127.0.0.1", 8080), || {
|
||||||
HttpService::build().finish(handle_request).tcp()
|
HttpService::build()
|
||||||
|
// handles HTTP/1.1 only
|
||||||
|
.h1(handle_request)
|
||||||
|
// No TLS
|
||||||
|
.tcp()
|
||||||
})?
|
})?
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
|
25
actix-http/examples/h2spec.rs
Normal file
25
actix-http/examples/h2spec.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use std::{convert::Infallible, io};
|
||||||
|
|
||||||
|
use actix_http::{HttpService, Request, Response, StatusCode};
|
||||||
|
use actix_server::Server;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
static STR: Lazy<String> = Lazy::new(|| "HELLO WORLD ".repeat(100));
|
||||||
|
|
||||||
|
#[actix_rt::main]
|
||||||
|
async fn main() -> io::Result<()> {
|
||||||
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
|
|
||||||
|
Server::build()
|
||||||
|
.bind("h2spec", ("127.0.0.1", 8080), || {
|
||||||
|
HttpService::build()
|
||||||
|
.h2(|_: Request| async move {
|
||||||
|
let mut res = Response::build(StatusCode::OK);
|
||||||
|
Ok::<_, Infallible>(res.body(&**STR))
|
||||||
|
})
|
||||||
|
.tcp()
|
||||||
|
})?
|
||||||
|
.workers(4)
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
use std::{convert::Infallible, io};
|
use std::{convert::Infallible, io, time::Duration};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
header::HeaderValue, HttpMessage, HttpService, Request, Response, StatusCode,
|
header::HeaderValue, HttpMessage, HttpService, Request, Response, StatusCode,
|
||||||
@ -12,8 +12,8 @@ async fn main() -> io::Result<()> {
|
|||||||
Server::build()
|
Server::build()
|
||||||
.bind("hello-world", ("127.0.0.1", 8080), || {
|
.bind("hello-world", ("127.0.0.1", 8080), || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(1000)
|
.client_request_timeout(Duration::from_secs(1))
|
||||||
.client_disconnect(1000)
|
.client_disconnect_timeout(Duration::from_secs(1))
|
||||||
.on_connect_ext(|_, ext| {
|
.on_connect_ext(|_, ext| {
|
||||||
ext.insert(42u32);
|
ext.insert(42u32);
|
||||||
})
|
})
|
||||||
|
@ -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() {
|
||||||
|
@ -31,7 +31,7 @@ impl fmt::Debug for BoxBodyInner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BoxBody {
|
impl BoxBody {
|
||||||
/// Same as `MessageBody::boxed`.
|
/// Boxes body type, erasing type information.
|
||||||
///
|
///
|
||||||
/// If the body type to wrap is unknown or generic it is better to use [`MessageBody::boxed`] to
|
/// If the body type to wrap is unknown or generic it is better to use [`MessageBody::boxed`] to
|
||||||
/// avoid double boxing.
|
/// avoid double boxing.
|
||||||
@ -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 }
|
||||||
|
@ -14,8 +14,44 @@ use pin_project_lite::pin_project;
|
|||||||
|
|
||||||
use super::{BodySize, BoxBody};
|
use super::{BodySize, BoxBody};
|
||||||
|
|
||||||
/// An interface types that can converted to bytes and used as response bodies.
|
/// An interface for types that can be used as a response body.
|
||||||
// TODO: examples
|
///
|
||||||
|
/// 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:
|
||||||
|
/// - Empty body: `()`
|
||||||
|
/// - Text-based: `String`, `&'static str`, [`ByteString`](https://docs.rs/bytestring/1).
|
||||||
|
/// - Byte-based: `Bytes`, `BytesMut`, `Vec<u8>`, `&'static [u8]`;
|
||||||
|
/// - Streams: [`BodyStream`](super::BodyStream), [`SizedStream`](super::SizedStream)
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use std::convert::Infallible;
|
||||||
|
/// # use std::task::{Poll, Context};
|
||||||
|
/// # use std::pin::Pin;
|
||||||
|
/// # use bytes::Bytes;
|
||||||
|
/// # use actix_http::body::{BodySize, MessageBody};
|
||||||
|
/// struct Repeat {
|
||||||
|
/// chunk: String,
|
||||||
|
/// n_times: usize,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl MessageBody for Repeat {
|
||||||
|
/// type Error = Infallible;
|
||||||
|
///
|
||||||
|
/// fn size(&self) -> BodySize {
|
||||||
|
/// BodySize::Sized((self.chunk.len() * self.n_times) as u64)
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn poll_next(
|
||||||
|
/// self: Pin<&mut Self>,
|
||||||
|
/// _cx: &mut Context<'_>,
|
||||||
|
/// ) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
/// let payload_string = self.chunk.repeat(self.n_times);
|
||||||
|
/// let payload_bytes = Bytes::from(payload_string);
|
||||||
|
/// Poll::Ready(Some(Ok(payload_bytes)))
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub trait MessageBody {
|
pub trait MessageBody {
|
||||||
/// The type of error that will be returned if streaming body fails.
|
/// The type of error that will be returned if streaming body fails.
|
||||||
///
|
///
|
||||||
@ -29,7 +65,22 @@ pub trait MessageBody {
|
|||||||
fn size(&self) -> BodySize;
|
fn size(&self) -> BodySize;
|
||||||
|
|
||||||
/// Attempt to pull out the next chunk of body bytes.
|
/// Attempt to pull out the next chunk of body bytes.
|
||||||
// TODO: expand documentation
|
///
|
||||||
|
/// # Return Value
|
||||||
|
/// Similar to the `Stream` interface, there are several possible return values, each indicating
|
||||||
|
/// a distinct state:
|
||||||
|
/// - `Poll::Pending` means that this body's next chunk is not ready yet. Implementations must
|
||||||
|
/// ensure that the current task will be notified when the next chunk may be ready.
|
||||||
|
/// - `Poll::Ready(Some(val))` means that the body has successfully produced a chunk, `val`,
|
||||||
|
/// and may produce further values on subsequent `poll_next` calls.
|
||||||
|
/// - `Poll::Ready(None)` means that the body is complete, and `poll_next` should not be
|
||||||
|
/// invoked again.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Once a body is complete (i.e., `poll_next` returned `Ready(None)`), calling its `poll_next`
|
||||||
|
/// method again may panic, block forever, or cause other kinds of problems; this trait places
|
||||||
|
/// no requirements on the effects of such a call. However, as the `poll_next` method is not
|
||||||
|
/// marked unsafe, Rust’s usual rules apply: calls must never cause UB, regardless of its state.
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
@ -37,7 +88,7 @@ pub trait MessageBody {
|
|||||||
|
|
||||||
/// Try to convert into the complete chunk of body bytes.
|
/// Try to convert into the complete chunk of body bytes.
|
||||||
///
|
///
|
||||||
/// Implement this method if the entire body can be trivially extracted. This is useful for
|
/// Override this method if the complete body can be trivially extracted. This is useful for
|
||||||
/// optimizations where `poll_next` calls can be avoided.
|
/// optimizations where `poll_next` calls can be avoided.
|
||||||
///
|
///
|
||||||
/// Body types with [`BodySize::None`] are allowed to return empty `Bytes`. Although, if calling
|
/// Body types with [`BodySize::None`] are allowed to return empty `Bytes`. Although, if calling
|
||||||
@ -54,7 +105,11 @@ pub trait MessageBody {
|
|||||||
Err(self)
|
Err(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts this body into `BoxBody`.
|
/// Wraps this body into a `BoxBody`.
|
||||||
|
///
|
||||||
|
/// No-op when called on a `BoxBody`, meaning there is no risk of double boxing when calling
|
||||||
|
/// this on a generic `MessageBody`. Prefer this over [`BoxBody::new`] when a boxed body
|
||||||
|
/// is required.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn boxed(self) -> BoxBody
|
fn boxed(self) -> BoxBody
|
||||||
where
|
where
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
//! Traits and structures to aid consuming and writing HTTP payloads.
|
//! Traits and structures to aid consuming and writing HTTP payloads.
|
||||||
|
//!
|
||||||
|
//! "Body" and "payload" are used somewhat interchangeably in this documentation.
|
||||||
|
|
||||||
|
// Though the spec kinda reads like "payload" is the possibly-transfer-encoded part of the message
|
||||||
|
// and the "body" is the intended possibly-decoded version of that.
|
||||||
|
|
||||||
mod body_stream;
|
mod body_stream;
|
||||||
mod boxed;
|
mod boxed;
|
||||||
|
@ -10,9 +10,12 @@ use super::{BodySize, MessageBody};
|
|||||||
|
|
||||||
/// Body type for responses that forbid payloads.
|
/// Body type for responses that forbid payloads.
|
||||||
///
|
///
|
||||||
/// Distinct from an empty response which would contain a Content-Length header.
|
/// This is distinct from an "empty" response which _would_ contain a `Content-Length` header.
|
||||||
///
|
|
||||||
/// For an "empty" body, use `()` or `Bytes::new()`.
|
/// For an "empty" body, use `()` or `Bytes::new()`.
|
||||||
|
///
|
||||||
|
/// For example, the HTTP spec forbids a payload to be sent with a `204 No Content` response.
|
||||||
|
/// In this case, the payload (or lack thereof) is implicit from the status code, so a
|
||||||
|
/// `Content-Length` header is not required.
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct None;
|
pub struct None;
|
||||||
|
@ -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() {
|
||||||
|
@ -1,25 +1,22 @@
|
|||||||
use std::{fmt, marker::PhantomData, net, rc::Rc};
|
use std::{fmt, marker::PhantomData, net, rc::Rc, time::Duration};
|
||||||
|
|
||||||
use actix_codec::Framed;
|
use actix_codec::Framed;
|
||||||
use actix_service::{IntoServiceFactory, Service, ServiceFactory};
|
use actix_service::{IntoServiceFactory, Service, ServiceFactory};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BoxBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
config::{KeepAlive, ServiceConfig},
|
|
||||||
h1::{self, ExpectHandler, H1Service, UpgradeHandler},
|
h1::{self, ExpectHandler, H1Service, UpgradeHandler},
|
||||||
h2::H2Service,
|
|
||||||
service::HttpService,
|
service::HttpService,
|
||||||
ConnectCallback, Extensions, Request, Response,
|
ConnectCallback, Extensions, KeepAlive, Request, Response, ServiceConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A HTTP service builder
|
/// An HTTP service builder.
|
||||||
///
|
///
|
||||||
/// This type can be used to construct an instance of [`HttpService`] through a
|
/// This type can construct an instance of [`HttpService`] through a builder-like pattern.
|
||||||
/// builder-like pattern.
|
|
||||||
pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
|
pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
|
||||||
keep_alive: KeepAlive,
|
keep_alive: KeepAlive,
|
||||||
client_timeout: u64,
|
client_request_timeout: Duration,
|
||||||
client_disconnect: u64,
|
client_disconnect_timeout: Duration,
|
||||||
secure: bool,
|
secure: bool,
|
||||||
local_addr: Option<net::SocketAddr>,
|
local_addr: Option<net::SocketAddr>,
|
||||||
expect: X,
|
expect: X,
|
||||||
@ -28,22 +25,23 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
|
|||||||
_phantom: PhantomData<S>,
|
_phantom: PhantomData<S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
|
impl<T, S> Default for HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Error: Into<Response<BoxBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
{
|
{
|
||||||
/// Create instance of `ServiceConfigBuilder`
|
fn default() -> Self {
|
||||||
#[allow(clippy::new_without_default)]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
HttpServiceBuilder {
|
HttpServiceBuilder {
|
||||||
keep_alive: KeepAlive::Timeout(5),
|
// ServiceConfig parts (make sure defaults match)
|
||||||
client_timeout: 5000,
|
keep_alive: KeepAlive::default(),
|
||||||
client_disconnect: 0,
|
client_request_timeout: Duration::from_secs(5),
|
||||||
|
client_disconnect_timeout: Duration::ZERO,
|
||||||
secure: false,
|
secure: false,
|
||||||
local_addr: None,
|
local_addr: None,
|
||||||
|
|
||||||
|
// dispatcher parts
|
||||||
expect: ExpectHandler,
|
expect: ExpectHandler,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
on_connect_ext: None,
|
on_connect_ext: None,
|
||||||
@ -65,9 +63,11 @@ where
|
|||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
/// Set server keep-alive setting.
|
/// Set connection keep-alive setting.
|
||||||
///
|
///
|
||||||
/// By default keep alive is set to a 5 seconds.
|
/// Applies to HTTP/1.1 keep-alive and HTTP/2 ping-pong.
|
||||||
|
///
|
||||||
|
/// By default keep-alive is 5 seconds.
|
||||||
pub fn keep_alive<W: Into<KeepAlive>>(mut self, val: W) -> Self {
|
pub fn keep_alive<W: Into<KeepAlive>>(mut self, val: W) -> Self {
|
||||||
self.keep_alive = val.into();
|
self.keep_alive = val.into();
|
||||||
self
|
self
|
||||||
@ -85,33 +85,45 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set server client timeout in milliseconds for first request.
|
/// Set client request timeout (for first request).
|
||||||
///
|
///
|
||||||
/// Defines a timeout for reading client request header. If a client does not transmit
|
/// Defines a timeout for reading client request header. If the client does not transmit the
|
||||||
/// the entire set headers within this time, the request is terminated with
|
/// request head within this duration, the connection is terminated with a `408 Request Timeout`
|
||||||
/// the 408 (Request Time-out) error.
|
/// response error.
|
||||||
///
|
///
|
||||||
/// To disable timeout set value to 0.
|
/// A duration of zero disables the timeout.
|
||||||
///
|
///
|
||||||
/// By default client timeout is set to 5000 milliseconds.
|
/// By default, the client timeout is 5 seconds.
|
||||||
pub fn client_timeout(mut self, val: u64) -> Self {
|
pub fn client_request_timeout(mut self, dur: Duration) -> Self {
|
||||||
self.client_timeout = val;
|
self.client_request_timeout = dur;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set server connection disconnect timeout in milliseconds.
|
#[doc(hidden)]
|
||||||
|
#[deprecated(since = "3.0.0", note = "Renamed to `client_request_timeout`.")]
|
||||||
|
pub fn client_timeout(self, dur: Duration) -> Self {
|
||||||
|
self.client_request_timeout(dur)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set client connection disconnect timeout.
|
||||||
///
|
///
|
||||||
/// Defines a timeout for disconnect connection. If a disconnect procedure does not complete
|
/// Defines a timeout for disconnect connection. If a disconnect procedure does not complete
|
||||||
/// within this time, the request get dropped. This timeout affects secure connections.
|
/// within this time, the request get dropped. This timeout affects secure connections.
|
||||||
///
|
///
|
||||||
/// To disable timeout set value to 0.
|
/// A duration of zero disables the timeout.
|
||||||
///
|
///
|
||||||
/// By default disconnect timeout is set to 0.
|
/// By default, the disconnect timeout is disabled.
|
||||||
pub fn client_disconnect(mut self, val: u64) -> Self {
|
pub fn client_disconnect_timeout(mut self, dur: Duration) -> Self {
|
||||||
self.client_disconnect = val;
|
self.client_disconnect_timeout = dur;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[deprecated(since = "3.0.0", note = "Renamed to `client_disconnect_timeout`.")]
|
||||||
|
pub fn client_disconnect(self, dur: Duration) -> Self {
|
||||||
|
self.client_disconnect_timeout(dur)
|
||||||
|
}
|
||||||
|
|
||||||
/// Provide service for `EXPECT: 100-Continue` support.
|
/// Provide service for `EXPECT: 100-Continue` support.
|
||||||
///
|
///
|
||||||
/// Service get called with request that contains `EXPECT` header.
|
/// Service get called with request that contains `EXPECT` header.
|
||||||
@ -126,8 +138,8 @@ where
|
|||||||
{
|
{
|
||||||
HttpServiceBuilder {
|
HttpServiceBuilder {
|
||||||
keep_alive: self.keep_alive,
|
keep_alive: self.keep_alive,
|
||||||
client_timeout: self.client_timeout,
|
client_request_timeout: self.client_request_timeout,
|
||||||
client_disconnect: self.client_disconnect,
|
client_disconnect_timeout: self.client_disconnect_timeout,
|
||||||
secure: self.secure,
|
secure: self.secure,
|
||||||
local_addr: self.local_addr,
|
local_addr: self.local_addr,
|
||||||
expect: expect.into_factory(),
|
expect: expect.into_factory(),
|
||||||
@ -150,8 +162,8 @@ where
|
|||||||
{
|
{
|
||||||
HttpServiceBuilder {
|
HttpServiceBuilder {
|
||||||
keep_alive: self.keep_alive,
|
keep_alive: self.keep_alive,
|
||||||
client_timeout: self.client_timeout,
|
client_request_timeout: self.client_request_timeout,
|
||||||
client_disconnect: self.client_disconnect,
|
client_disconnect_timeout: self.client_disconnect_timeout,
|
||||||
secure: self.secure,
|
secure: self.secure,
|
||||||
local_addr: self.local_addr,
|
local_addr: self.local_addr,
|
||||||
expect: self.expect,
|
expect: self.expect,
|
||||||
@ -185,8 +197,8 @@ where
|
|||||||
{
|
{
|
||||||
let cfg = ServiceConfig::new(
|
let cfg = ServiceConfig::new(
|
||||||
self.keep_alive,
|
self.keep_alive,
|
||||||
self.client_timeout,
|
self.client_request_timeout,
|
||||||
self.client_disconnect,
|
self.client_disconnect_timeout,
|
||||||
self.secure,
|
self.secure,
|
||||||
self.local_addr,
|
self.local_addr,
|
||||||
);
|
);
|
||||||
@ -198,7 +210,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Finish service configuration and create a HTTP service for HTTP/2 protocol.
|
/// Finish service configuration and create a HTTP service for HTTP/2 protocol.
|
||||||
pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
|
#[cfg(feature = "http2")]
|
||||||
|
pub fn h2<F, B>(self, service: F) -> crate::h2::H2Service<T, S, B>
|
||||||
where
|
where
|
||||||
F: IntoServiceFactory<S, Request>,
|
F: IntoServiceFactory<S, Request>,
|
||||||
S::Error: Into<Response<BoxBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
@ -209,13 +222,14 @@ where
|
|||||||
{
|
{
|
||||||
let cfg = ServiceConfig::new(
|
let cfg = ServiceConfig::new(
|
||||||
self.keep_alive,
|
self.keep_alive,
|
||||||
self.client_timeout,
|
self.client_request_timeout,
|
||||||
self.client_disconnect,
|
self.client_disconnect_timeout,
|
||||||
self.secure,
|
self.secure,
|
||||||
self.local_addr,
|
self.local_addr,
|
||||||
);
|
);
|
||||||
|
|
||||||
H2Service::with_config(cfg, service.into_factory()).on_connect_ext(self.on_connect_ext)
|
crate::h2::H2Service::with_config(cfg, service.into_factory())
|
||||||
|
.on_connect_ext(self.on_connect_ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish service configuration and create `HttpService` instance.
|
/// Finish service configuration and create `HttpService` instance.
|
||||||
@ -230,8 +244,8 @@ where
|
|||||||
{
|
{
|
||||||
let cfg = ServiceConfig::new(
|
let cfg = ServiceConfig::new(
|
||||||
self.keep_alive,
|
self.keep_alive,
|
||||||
self.client_timeout,
|
self.client_request_timeout,
|
||||||
self.client_disconnect,
|
self.client_disconnect_timeout,
|
||||||
self.secure,
|
self.secure,
|
||||||
self.local_addr,
|
self.local_addr,
|
||||||
);
|
);
|
||||||
|
@ -1,71 +1,36 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cell::Cell,
|
|
||||||
fmt::{self, Write},
|
|
||||||
net,
|
net,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
time::{Duration, SystemTime},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_rt::{
|
|
||||||
task::JoinHandle,
|
|
||||||
time::{interval, sleep_until, Instant, Sleep},
|
|
||||||
};
|
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
|
|
||||||
/// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
use crate::{date::DateService, KeepAlive};
|
||||||
pub(crate) const DATE_VALUE_LENGTH: usize = 29;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
/// HTTP service configuration.
|
||||||
/// Server keep-alive setting
|
#[derive(Debug, Clone)]
|
||||||
pub enum KeepAlive {
|
|
||||||
/// Keep alive in seconds
|
|
||||||
Timeout(usize),
|
|
||||||
|
|
||||||
/// Rely on OS to shutdown tcp connection
|
|
||||||
Os,
|
|
||||||
|
|
||||||
/// Disabled
|
|
||||||
Disabled,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<usize> for KeepAlive {
|
|
||||||
fn from(keepalive: usize) -> Self {
|
|
||||||
KeepAlive::Timeout(keepalive)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Option<usize>> for KeepAlive {
|
|
||||||
fn from(keepalive: Option<usize>) -> Self {
|
|
||||||
if let Some(keepalive) = keepalive {
|
|
||||||
KeepAlive::Timeout(keepalive)
|
|
||||||
} else {
|
|
||||||
KeepAlive::Disabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Http service configuration
|
|
||||||
pub struct ServiceConfig(Rc<Inner>);
|
pub struct ServiceConfig(Rc<Inner>);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct Inner {
|
struct Inner {
|
||||||
keep_alive: Option<Duration>,
|
keep_alive: KeepAlive,
|
||||||
client_timeout: u64,
|
client_request_timeout: Duration,
|
||||||
client_disconnect: u64,
|
client_disconnect_timeout: Duration,
|
||||||
ka_enabled: bool,
|
|
||||||
secure: bool,
|
secure: bool,
|
||||||
local_addr: Option<std::net::SocketAddr>,
|
local_addr: Option<std::net::SocketAddr>,
|
||||||
date_service: DateService,
|
date_service: DateService,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for ServiceConfig {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
ServiceConfig(self.0.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ServiceConfig {
|
impl Default for ServiceConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(KeepAlive::Timeout(5), 0, 0, false, None)
|
Self::new(
|
||||||
|
KeepAlive::default(),
|
||||||
|
Duration::from_secs(5),
|
||||||
|
Duration::ZERO,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,34 +38,22 @@ impl ServiceConfig {
|
|||||||
/// Create instance of `ServiceConfig`
|
/// Create instance of `ServiceConfig`
|
||||||
pub fn new(
|
pub fn new(
|
||||||
keep_alive: KeepAlive,
|
keep_alive: KeepAlive,
|
||||||
client_timeout: u64,
|
client_request_timeout: Duration,
|
||||||
client_disconnect: u64,
|
client_disconnect_timeout: Duration,
|
||||||
secure: bool,
|
secure: bool,
|
||||||
local_addr: Option<net::SocketAddr>,
|
local_addr: Option<net::SocketAddr>,
|
||||||
) -> ServiceConfig {
|
) -> ServiceConfig {
|
||||||
let (keep_alive, ka_enabled) = match keep_alive {
|
|
||||||
KeepAlive::Timeout(val) => (val as u64, true),
|
|
||||||
KeepAlive::Os => (0, true),
|
|
||||||
KeepAlive::Disabled => (0, false),
|
|
||||||
};
|
|
||||||
let keep_alive = if ka_enabled && keep_alive > 0 {
|
|
||||||
Some(Duration::from_secs(keep_alive))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
ServiceConfig(Rc::new(Inner {
|
ServiceConfig(Rc::new(Inner {
|
||||||
keep_alive,
|
keep_alive: keep_alive.normalize(),
|
||||||
ka_enabled,
|
client_request_timeout,
|
||||||
client_timeout,
|
client_disconnect_timeout,
|
||||||
client_disconnect,
|
|
||||||
secure,
|
secure,
|
||||||
local_addr,
|
local_addr,
|
||||||
date_service: DateService::new(),
|
date_service: DateService::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if connection is secure (HTTPS)
|
/// Returns `true` if connection is secure (i.e., using TLS / HTTPS).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn secure(&self) -> bool {
|
pub fn secure(&self) -> bool {
|
||||||
self.0.secure
|
self.0.secure
|
||||||
@ -114,235 +67,97 @@ impl ServiceConfig {
|
|||||||
self.0.local_addr
|
self.0.local_addr
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keep alive duration if configured.
|
/// Connection keep-alive setting.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn keep_alive(&self) -> Option<Duration> {
|
pub fn keep_alive(&self) -> KeepAlive {
|
||||||
self.0.keep_alive
|
self.0.keep_alive
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return state of connection keep-alive functionality
|
/// Creates a time object representing the deadline for this connection's keep-alive period, if
|
||||||
#[inline]
|
/// enabled.
|
||||||
pub fn keep_alive_enabled(&self) -> bool {
|
///
|
||||||
self.0.ka_enabled
|
/// When [`KeepAlive::Os`] or [`KeepAlive::Disabled`] is set, this will return `None`.
|
||||||
}
|
pub fn keep_alive_deadline(&self) -> Option<Instant> {
|
||||||
|
match self.keep_alive() {
|
||||||
/// Client timeout for first request.
|
KeepAlive::Timeout(dur) => Some(self.now() + dur),
|
||||||
#[inline]
|
KeepAlive::Os => None,
|
||||||
pub fn client_timer(&self) -> Option<Sleep> {
|
KeepAlive::Disabled => None,
|
||||||
let delay_time = self.0.client_timeout;
|
|
||||||
if delay_time != 0 {
|
|
||||||
Some(sleep_until(self.now() + Duration::from_millis(delay_time)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Client timeout for first request.
|
/// Creates a time object representing the deadline for the client to finish sending the head of
|
||||||
pub fn client_timer_expire(&self) -> Option<Instant> {
|
/// its first request.
|
||||||
let delay = self.0.client_timeout;
|
///
|
||||||
if delay != 0 {
|
/// Returns `None` if this `ServiceConfig was` constructed with `client_request_timeout: 0`.
|
||||||
Some(self.now() + Duration::from_millis(delay))
|
pub fn client_request_deadline(&self) -> Option<Instant> {
|
||||||
} else {
|
let timeout = self.0.client_request_timeout;
|
||||||
None
|
(timeout != Duration::ZERO).then(|| self.now() + timeout)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Client disconnect timer
|
/// Creates a time object representing the deadline for the client to disconnect.
|
||||||
pub fn client_disconnect_timer(&self) -> Option<Instant> {
|
pub fn client_disconnect_deadline(&self) -> Option<Instant> {
|
||||||
let delay = self.0.client_disconnect;
|
let timeout = self.0.client_disconnect_timeout;
|
||||||
if delay != 0 {
|
(timeout != Duration::ZERO).then(|| self.now() + timeout)
|
||||||
Some(self.now() + Duration::from_millis(delay))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return keep-alive timer delay is configured.
|
|
||||||
#[inline]
|
|
||||||
pub fn keep_alive_timer(&self) -> Option<Sleep> {
|
|
||||||
self.keep_alive().map(|ka| sleep_until(self.now() + ka))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Keep-alive expire time
|
|
||||||
pub fn keep_alive_expire(&self) -> Option<Instant> {
|
|
||||||
self.keep_alive().map(|ka| self.now() + ka)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn now(&self) -> Instant {
|
pub(crate) fn now(&self) -> Instant {
|
||||||
self.0.date_service.now()
|
self.0.date_service.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes date header to `dst` buffer.
|
||||||
|
///
|
||||||
|
/// 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)]
|
#[doc(hidden)]
|
||||||
pub fn set_date(&self, dst: &mut BytesMut) {
|
pub fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) {
|
||||||
let mut buf: [u8; 39] = [0; 39];
|
let mut buf: [u8; 37] = [0; 37];
|
||||||
buf[..6].copy_from_slice(b"date: ");
|
|
||||||
|
buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " });
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
.date_service
|
.date_service
|
||||||
.set_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);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_date_header(&self, dst: &mut BytesMut) {
|
#[allow(unused)] // used with `http2` feature flag
|
||||||
|
pub(crate) fn write_date_header_value(&self, dst: &mut BytesMut) {
|
||||||
self.0
|
self.0
|
||||||
.date_service
|
.date_service
|
||||||
.set_date(|date| dst.extend_from_slice(&date.bytes));
|
.with_date(|date| dst.extend_from_slice(&date.bytes));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct Date {
|
|
||||||
bytes: [u8; DATE_VALUE_LENGTH],
|
|
||||||
pos: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Date {
|
|
||||||
fn new() -> Date {
|
|
||||||
let mut date = Date {
|
|
||||||
bytes: [0; DATE_VALUE_LENGTH],
|
|
||||||
pos: 0,
|
|
||||||
};
|
|
||||||
date.update();
|
|
||||||
date
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self) {
|
|
||||||
self.pos = 0;
|
|
||||||
write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Write for Date {
|
|
||||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
|
||||||
let len = s.len();
|
|
||||||
self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes());
|
|
||||||
self.pos += len;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Service for update Date and Instant periodically at 500 millis interval.
|
|
||||||
struct DateService {
|
|
||||||
current: Rc<Cell<(Date, Instant)>>,
|
|
||||||
handle: JoinHandle<()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for DateService {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// stop the timer update async task on drop.
|
|
||||||
self.handle.abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DateService {
|
|
||||||
fn new() -> Self {
|
|
||||||
// shared date and timer for DateService and update async task.
|
|
||||||
let current = Rc::new(Cell::new((Date::new(), Instant::now())));
|
|
||||||
let current_clone = Rc::clone(¤t);
|
|
||||||
// spawn an async task sleep for 500 milli and update current date/timer in a loop.
|
|
||||||
// handle is used to stop the task on DateService drop.
|
|
||||||
let handle = actix_rt::spawn(async move {
|
|
||||||
#[cfg(test)]
|
|
||||||
let _notify = notify_on_drop::NotifyOnDrop::new();
|
|
||||||
|
|
||||||
let mut interval = interval(Duration::from_millis(500));
|
|
||||||
loop {
|
|
||||||
let now = interval.tick().await;
|
|
||||||
let date = Date::new();
|
|
||||||
current_clone.set((date, now));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
DateService { current, handle }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn now(&self) -> Instant {
|
|
||||||
self.current.get().1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_date<F: FnMut(&Date)>(&self, mut f: F) {
|
|
||||||
f(&self.current.get().0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: move to a util module for testing all spawn handle drop style tasks.
|
|
||||||
/// Test Module for checking the drop state of certain async tasks that are spawned
|
|
||||||
/// with `actix_rt::spawn`
|
|
||||||
///
|
|
||||||
/// The target task must explicitly generate `NotifyOnDrop` when spawn the task
|
|
||||||
#[cfg(test)]
|
|
||||||
mod notify_on_drop {
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
static NOTIFY_DROPPED: RefCell<Option<bool>> = RefCell::new(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the spawned task is dropped.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// Panics when there was no `NotifyOnDrop` instance on current thread.
|
|
||||||
pub(crate) fn is_dropped() -> bool {
|
|
||||||
NOTIFY_DROPPED.with(|bool| {
|
|
||||||
bool.borrow()
|
|
||||||
.expect("No NotifyOnDrop existed on current thread")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct NotifyOnDrop;
|
|
||||||
|
|
||||||
impl NotifyOnDrop {
|
|
||||||
/// # Panic:
|
|
||||||
///
|
|
||||||
/// When construct multiple instances on any given thread.
|
|
||||||
pub(crate) fn new() -> Self {
|
|
||||||
NOTIFY_DROPPED.with(|bool| {
|
|
||||||
let mut bool = bool.borrow_mut();
|
|
||||||
if bool.is_some() {
|
|
||||||
panic!("NotifyOnDrop existed on current thread");
|
|
||||||
} else {
|
|
||||||
*bool = Some(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
NotifyOnDrop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for NotifyOnDrop {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
NOTIFY_DROPPED.with(|bool| {
|
|
||||||
if let Some(b) = bool.borrow_mut().as_mut() {
|
|
||||||
*b = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::{date::DATE_VALUE_LENGTH, notify_on_drop};
|
||||||
|
|
||||||
use actix_rt::{task::yield_now, time::sleep};
|
use actix_rt::{
|
||||||
|
task::yield_now,
|
||||||
|
time::{sleep, sleep_until},
|
||||||
|
};
|
||||||
|
use memchr::memmem;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_date_service_update() {
|
async fn test_date_service_update() {
|
||||||
let settings = ServiceConfig::new(KeepAlive::Os, 0, 0, false, None);
|
let settings =
|
||||||
|
ServiceConfig::new(KeepAlive::Os, Duration::ZERO, Duration::ZERO, false, None);
|
||||||
|
|
||||||
yield_now().await;
|
yield_now().await;
|
||||||
|
|
||||||
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||||
settings.set_date(&mut buf1);
|
settings.write_date_header(&mut buf1, false);
|
||||||
let now1 = settings.now();
|
let now1 = settings.now();
|
||||||
|
|
||||||
sleep_until(Instant::now() + Duration::from_secs(2)).await;
|
sleep_until((Instant::now() + Duration::from_secs(2)).into()).await;
|
||||||
yield_now().await;
|
yield_now().await;
|
||||||
|
|
||||||
let now2 = settings.now();
|
let now2 = settings.now();
|
||||||
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||||
settings.set_date(&mut buf2);
|
settings.write_date_header(&mut buf2, false);
|
||||||
|
|
||||||
assert_ne!(now1, now2);
|
assert_ne!(now1, now2);
|
||||||
|
|
||||||
@ -395,11 +210,27 @@ mod tests {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_date() {
|
async fn test_date() {
|
||||||
let settings = ServiceConfig::new(KeepAlive::Os, 0, 0, false, None);
|
let settings = ServiceConfig::default();
|
||||||
|
|
||||||
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||||
settings.set_date(&mut buf1);
|
settings.write_date_header(&mut buf1, false);
|
||||||
|
|
||||||
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||||
settings.set_date(&mut buf2);
|
settings.write_date_header(&mut buf2, false);
|
||||||
|
|
||||||
assert_eq!(buf1, buf2);
|
assert_eq!(buf1, buf2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_date_camel_case() {
|
||||||
|
let settings = ServiceConfig::default();
|
||||||
|
|
||||||
|
let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||||
|
settings.write_date_header(&mut buf, false);
|
||||||
|
assert!(memmem::find(&buf, b"date:").is_some());
|
||||||
|
|
||||||
|
let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||||
|
settings.write_date_header(&mut buf, true);
|
||||||
|
assert!(memmem::find(&buf, b"Date:").is_some());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
92
actix-http/src/date.rs
Normal file
92
actix-http/src/date.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
use std::{
|
||||||
|
cell::Cell,
|
||||||
|
fmt::{self, Write},
|
||||||
|
rc::Rc,
|
||||||
|
time::{Duration, Instant, SystemTime},
|
||||||
|
};
|
||||||
|
|
||||||
|
use actix_rt::{task::JoinHandle, time::interval};
|
||||||
|
|
||||||
|
/// "Thu, 01 Jan 1970 00:00:00 GMT".len()
|
||||||
|
pub(crate) const DATE_VALUE_LENGTH: usize = 29;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(crate) struct Date {
|
||||||
|
pub(crate) bytes: [u8; DATE_VALUE_LENGTH],
|
||||||
|
pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Date {
|
||||||
|
fn new() -> Date {
|
||||||
|
let mut date = Date {
|
||||||
|
bytes: [0; DATE_VALUE_LENGTH],
|
||||||
|
pos: 0,
|
||||||
|
};
|
||||||
|
date.update();
|
||||||
|
date
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self) {
|
||||||
|
self.pos = 0;
|
||||||
|
write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Write for Date {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
let len = s.len();
|
||||||
|
self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes());
|
||||||
|
self.pos += len;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Service for update Date and Instant periodically at 500 millis interval.
|
||||||
|
pub(crate) struct DateService {
|
||||||
|
current: Rc<Cell<(Date, Instant)>>,
|
||||||
|
handle: JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DateService {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
// shared date and timer for DateService and update async task.
|
||||||
|
let current = Rc::new(Cell::new((Date::new(), Instant::now())));
|
||||||
|
let current_clone = Rc::clone(¤t);
|
||||||
|
// spawn an async task sleep for 500 millis and update current date/timer in a loop.
|
||||||
|
// handle is used to stop the task on DateService drop.
|
||||||
|
let handle = actix_rt::spawn(async move {
|
||||||
|
#[cfg(test)]
|
||||||
|
let _notify = crate::notify_on_drop::NotifyOnDrop::new();
|
||||||
|
|
||||||
|
let mut interval = interval(Duration::from_millis(500));
|
||||||
|
loop {
|
||||||
|
let now = interval.tick().await;
|
||||||
|
let date = Date::new();
|
||||||
|
current_clone.set((date, now.into_std()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
DateService { current, handle }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn now(&self) -> Instant {
|
||||||
|
self.current.get().1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn with_date<F: FnMut(&Date)>(&self, mut f: F) {
|
||||||
|
f(&self.current.get().0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for DateService {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("DateService").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DateService {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// stop the timer update async task on drop.
|
||||||
|
self.handle.abort();
|
||||||
|
}
|
||||||
|
}
|
@ -11,9 +11,6 @@ use actix_rt::task::{spawn_blocking, JoinHandle};
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
|
|
||||||
#[cfg(feature = "compress-brotli")]
|
|
||||||
use brotli2::write::BrotliDecoder;
|
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
use flate2::write::{GzDecoder, ZlibDecoder};
|
use flate2::write::{GzDecoder, ZlibDecoder};
|
||||||
|
|
||||||
@ -48,7 +45,7 @@ where
|
|||||||
let decoder = match encoding {
|
let decoder = match encoding {
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
ContentEncoding::Brotli => Some(ContentDecoder::Brotli(Box::new(
|
ContentEncoding::Brotli => Some(ContentDecoder::Brotli(Box::new(
|
||||||
BrotliDecoder::new(Writer::new()),
|
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(
|
||||||
@ -165,7 +162,7 @@ enum ContentDecoder {
|
|||||||
#[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<BrotliDecoder<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")]
|
||||||
|
@ -14,9 +14,6 @@ use derive_more::Display;
|
|||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
#[cfg(feature = "compress-brotli")]
|
|
||||||
use brotli2::write::BrotliEncoder;
|
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||||
|
|
||||||
@ -268,7 +265,7 @@ enum ContentEncoder {
|
|||||||
Gzip(GzEncoder<Writer>),
|
Gzip(GzEncoder<Writer>),
|
||||||
|
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
Brotli(BrotliEncoder<Writer>),
|
Brotli(Box<brotli::CompressorWriter<Writer>>),
|
||||||
|
|
||||||
// Wwe need explicit 'static lifetime here because ZstdEncoder needs a lifetime argument and we
|
// Wwe need explicit 'static lifetime here because ZstdEncoder needs a lifetime argument and we
|
||||||
// use `spawn_blocking` in `Encoder::poll_next` that requires `FnOnce() -> R + Send + 'static`.
|
// use `spawn_blocking` in `Encoder::poll_next` that requires `FnOnce() -> R + Send + 'static`.
|
||||||
@ -292,9 +289,7 @@ impl ContentEncoder {
|
|||||||
))),
|
))),
|
||||||
|
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
ContentEncoding::Brotli => {
|
ContentEncoding::Brotli => Some(ContentEncoder::Brotli(new_brotli_compressor())),
|
||||||
Some(ContentEncoder::Brotli(BrotliEncoder::new(Writer::new(), 3)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
ContentEncoding::Zstd => {
|
ContentEncoding::Zstd => {
|
||||||
@ -326,8 +321,8 @@ impl ContentEncoder {
|
|||||||
fn finish(self) -> Result<Bytes, io::Error> {
|
fn finish(self) -> Result<Bytes, io::Error> {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
ContentEncoder::Brotli(encoder) => match encoder.finish() {
|
ContentEncoder::Brotli(mut encoder) => match encoder.flush() {
|
||||||
Ok(writer) => Ok(writer.buf.freeze()),
|
Ok(()) => Ok(encoder.into_inner().buf.freeze()),
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -357,7 +352,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) => {
|
||||||
trace!("Error decoding br encoding: {}", err);
|
log::trace!("Error decoding br encoding: {}", err);
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -366,7 +361,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) => {
|
||||||
trace!("Error decoding gzip encoding: {}", err);
|
log::trace!("Error decoding gzip encoding: {}", err);
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -375,7 +370,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) => {
|
||||||
trace!("Error decoding deflate encoding: {}", err);
|
log::trace!("Error decoding deflate encoding: {}", err);
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -384,7 +379,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) => {
|
||||||
trace!("Error decoding ztsd encoding: {}", err);
|
log::trace!("Error decoding ztsd encoding: {}", err);
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -392,6 +387,16 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "compress-brotli")]
|
||||||
|
fn new_brotli_compressor() -> Box<brotli::CompressorWriter<Writer>> {
|
||||||
|
Box::new(brotli::CompressorWriter::new(
|
||||||
|
Writer::new(),
|
||||||
|
32 * 1024, // 32 KiB buffer
|
||||||
|
3, // BROTLI_PARAM_QUALITY
|
||||||
|
22, // BROTLI_PARAM_LGWIN
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Display)]
|
#[derive(Debug, Display)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum EncoderError {
|
pub enum EncoderError {
|
||||||
|
@ -5,7 +5,7 @@ use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Err
|
|||||||
use derive_more::{Display, Error, From};
|
use derive_more::{Display, Error, From};
|
||||||
use http::{uri::InvalidUri, StatusCode};
|
use http::{uri::InvalidUri, StatusCode};
|
||||||
|
|
||||||
use crate::{body::BoxBody, ws, Response};
|
use crate::{body::BoxBody, Response};
|
||||||
|
|
||||||
pub use http::Error as HttpError;
|
pub use http::Error as HttpError;
|
||||||
|
|
||||||
@ -61,6 +61,7 @@ impl Error {
|
|||||||
Self::new(Kind::Encoder)
|
Self::new(Kind::Encoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)] // used with `ws` feature flag
|
||||||
pub(crate) fn new_ws() -> Self {
|
pub(crate) fn new_ws() -> Self {
|
||||||
Self::new(Kind::Ws)
|
Self::new(Kind::Ws)
|
||||||
}
|
}
|
||||||
@ -139,14 +140,16 @@ impl From<HttpError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ws::HandshakeError> for Error {
|
#[cfg(feature = "ws")]
|
||||||
fn from(err: ws::HandshakeError) -> Self {
|
impl From<crate::ws::HandshakeError> for Error {
|
||||||
|
fn from(err: crate::ws::HandshakeError) -> Self {
|
||||||
Self::new_ws().with_cause(err)
|
Self::new_ws().with_cause(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ws::ProtocolError> for Error {
|
#[cfg(feature = "ws")]
|
||||||
fn from(err: ws::ProtocolError) -> Self {
|
impl From<crate::ws::ProtocolError> for Error {
|
||||||
|
fn from(err: crate::ws::ProtocolError) -> Self {
|
||||||
Self::new_ws().with_cause(err)
|
Self::new_ws().with_cause(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,8 +280,9 @@ pub enum PayloadError {
|
|||||||
UnknownLength,
|
UnknownLength,
|
||||||
|
|
||||||
/// HTTP/2 payload error.
|
/// HTTP/2 payload error.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
Http2Payload(h2::Error),
|
Http2Payload(::h2::Error),
|
||||||
|
|
||||||
/// Generic I/O error.
|
/// Generic I/O error.
|
||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
@ -293,14 +297,16 @@ impl std::error::Error for PayloadError {
|
|||||||
PayloadError::EncodingCorrupted => None,
|
PayloadError::EncodingCorrupted => None,
|
||||||
PayloadError::Overflow => None,
|
PayloadError::Overflow => None,
|
||||||
PayloadError::UnknownLength => None,
|
PayloadError::UnknownLength => None,
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error),
|
PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error),
|
||||||
PayloadError::Io(err) => Some(err as &dyn std::error::Error),
|
PayloadError::Io(err) => Some(err as &dyn std::error::Error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<h2::Error> for PayloadError {
|
#[cfg(feature = "http2")]
|
||||||
fn from(err: h2::Error) -> Self {
|
impl From<::h2::Error> for PayloadError {
|
||||||
|
fn from(err: ::h2::Error) -> Self {
|
||||||
PayloadError::Http2Payload(err)
|
PayloadError::Http2Payload(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -334,6 +340,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")]
|
||||||
@ -356,6 +363,7 @@ pub enum DispatchError {
|
|||||||
|
|
||||||
/// HTTP/2 error.
|
/// HTTP/2 error.
|
||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
H2(h2::Error),
|
H2(h2::Error),
|
||||||
|
|
||||||
/// The first request did not complete within the specified timeout.
|
/// The first request did not complete within the specified timeout.
|
||||||
@ -366,6 +374,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,
|
||||||
@ -379,7 +391,10 @@ impl StdError for DispatchError {
|
|||||||
DispatchError::Body(err) => Some(&**err),
|
DispatchError::Body(err) => Some(&**err),
|
||||||
DispatchError::Io(err) => Some(err),
|
DispatchError::Io(err) => Some(err),
|
||||||
DispatchError::Parse(err) => Some(err),
|
DispatchError::Parse(err) => Some(err),
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
DispatchError::H2(err) => Some(err),
|
DispatchError::H2(err) => Some(err),
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -387,6 +402,7 @@ impl StdError for DispatchError {
|
|||||||
|
|
||||||
/// A set of error that can occur during parsing content type.
|
/// A set of error that can occur during parsing content type.
|
||||||
#[derive(Debug, Display, Error)]
|
#[derive(Debug, Display, Error)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum ContentTypeError {
|
pub enum ContentTypeError {
|
||||||
/// Can not parse content type
|
/// Can not parse content type
|
||||||
@ -398,28 +414,14 @@ pub enum ContentTypeError {
|
|||||||
UnknownEncoding,
|
UnknownEncoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod content_type_test_impls {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
impl std::cmp::PartialEq for ContentTypeError {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::ParseError => matches!(other, ContentTypeError::ParseError),
|
|
||||||
Self::UnknownEncoding => {
|
|
||||||
matches!(other, ContentTypeError::UnknownEncoding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use http::{Error as HttpError, StatusCode};
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
use http::{Error as HttpError, StatusCode};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_into_response() {
|
fn test_into_response() {
|
||||||
let resp: Response<BoxBody> = ParseError::Incomplete.into();
|
let resp: Response<BoxBody> = ParseError::Incomplete.into();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::io;
|
use std::{fmt, io};
|
||||||
|
|
||||||
use actix_codec::{Decoder, Encoder};
|
use actix_codec::{Decoder, Encoder};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
@ -17,9 +17,9 @@ use crate::{
|
|||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
struct Flags: u8 {
|
struct Flags: u8 {
|
||||||
const HEAD = 0b0000_0001;
|
const HEAD = 0b0000_0001;
|
||||||
const KEEPALIVE_ENABLED = 0b0000_1000;
|
const KEEP_ALIVE_ENABLED = 0b0000_1000;
|
||||||
const STREAM = 0b0001_0000;
|
const STREAM = 0b0001_0000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ struct ClientCodecInner {
|
|||||||
decoder: decoder::MessageDecoder<ResponseHead>,
|
decoder: decoder::MessageDecoder<ResponseHead>,
|
||||||
payload: Option<PayloadDecoder>,
|
payload: Option<PayloadDecoder>,
|
||||||
version: Version,
|
version: Version,
|
||||||
ctype: ConnectionType,
|
conn_type: ConnectionType,
|
||||||
|
|
||||||
// encoder part
|
// encoder part
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
@ -51,23 +51,32 @@ impl Default for ClientCodec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ClientCodec {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("h1::ClientCodec")
|
||||||
|
.field("flags", &self.inner.flags)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ClientCodec {
|
impl ClientCodec {
|
||||||
/// Create HTTP/1 codec.
|
/// Create HTTP/1 codec.
|
||||||
///
|
///
|
||||||
/// `keepalive_enabled` how response `connection` header get generated.
|
/// `keepalive_enabled` how response `connection` header get generated.
|
||||||
pub fn new(config: ServiceConfig) -> Self {
|
pub fn new(config: ServiceConfig) -> Self {
|
||||||
let flags = if config.keep_alive_enabled() {
|
let flags = if config.keep_alive().enabled() {
|
||||||
Flags::KEEPALIVE_ENABLED
|
Flags::KEEP_ALIVE_ENABLED
|
||||||
} else {
|
} else {
|
||||||
Flags::empty()
|
Flags::empty()
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientCodec {
|
ClientCodec {
|
||||||
inner: ClientCodecInner {
|
inner: ClientCodecInner {
|
||||||
config,
|
config,
|
||||||
decoder: decoder::MessageDecoder::default(),
|
decoder: decoder::MessageDecoder::default(),
|
||||||
payload: None,
|
payload: None,
|
||||||
version: Version::HTTP_11,
|
version: Version::HTTP_11,
|
||||||
ctype: ConnectionType::Close,
|
conn_type: ConnectionType::Close,
|
||||||
|
|
||||||
flags,
|
flags,
|
||||||
encoder: encoder::MessageEncoder::default(),
|
encoder: encoder::MessageEncoder::default(),
|
||||||
@ -77,12 +86,12 @@ impl ClientCodec {
|
|||||||
|
|
||||||
/// Check if request is upgrade
|
/// Check if request is upgrade
|
||||||
pub fn upgrade(&self) -> bool {
|
pub fn upgrade(&self) -> bool {
|
||||||
self.inner.ctype == ConnectionType::Upgrade
|
self.inner.conn_type == ConnectionType::Upgrade
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if last response is keep-alive
|
/// Check if last response is keep-alive
|
||||||
pub fn keepalive(&self) -> bool {
|
pub fn keep_alive(&self) -> bool {
|
||||||
self.inner.ctype == ConnectionType::KeepAlive
|
self.inner.conn_type == ConnectionType::KeepAlive
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check last request's message type
|
/// Check last request's message type
|
||||||
@ -104,8 +113,8 @@ impl ClientCodec {
|
|||||||
|
|
||||||
impl ClientPayloadCodec {
|
impl ClientPayloadCodec {
|
||||||
/// Check if last response is keep-alive
|
/// Check if last response is keep-alive
|
||||||
pub fn keepalive(&self) -> bool {
|
pub fn keep_alive(&self) -> bool {
|
||||||
self.inner.ctype == ConnectionType::KeepAlive
|
self.inner.conn_type == ConnectionType::KeepAlive
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transform payload codec to a message codec
|
/// Transform payload codec to a message codec
|
||||||
@ -119,15 +128,18 @@ 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(ctype) = req.conn_type() {
|
if let Some(conn_type) = req.conn_type() {
|
||||||
// do not use peer's keep-alive
|
// do not use peer's keep-alive
|
||||||
self.inner.ctype = if ctype == ConnectionType::KeepAlive {
|
self.inner.conn_type = if conn_type == ConnectionType::KeepAlive {
|
||||||
self.inner.ctype
|
self.inner.conn_type
|
||||||
} else {
|
} else {
|
||||||
ctype
|
conn_type
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,9 +204,9 @@ impl Encoder<Message<(RequestHeadType, BodySize)>> for ClientCodec {
|
|||||||
.set(Flags::HEAD, head.as_ref().method == Method::HEAD);
|
.set(Flags::HEAD, head.as_ref().method == Method::HEAD);
|
||||||
|
|
||||||
// connection status
|
// connection status
|
||||||
inner.ctype = match head.as_ref().connection_type() {
|
inner.conn_type = match head.as_ref().connection_type() {
|
||||||
ConnectionType::KeepAlive => {
|
ConnectionType::KeepAlive => {
|
||||||
if inner.flags.contains(Flags::KEEPALIVE_ENABLED) {
|
if inner.flags.contains(Flags::KEEP_ALIVE_ENABLED) {
|
||||||
ConnectionType::KeepAlive
|
ConnectionType::KeepAlive
|
||||||
} else {
|
} else {
|
||||||
ConnectionType::Close
|
ConnectionType::Close
|
||||||
@ -211,7 +223,7 @@ impl Encoder<Message<(RequestHeadType, BodySize)>> for ClientCodec {
|
|||||||
false,
|
false,
|
||||||
inner.version,
|
inner.version,
|
||||||
length,
|
length,
|
||||||
inner.ctype,
|
inner.conn_type,
|
||||||
&inner.config,
|
&inner.config,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@ use crate::{
|
|||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
struct Flags: u8 {
|
struct Flags: u8 {
|
||||||
const HEAD = 0b0000_0001;
|
const HEAD = 0b0000_0001;
|
||||||
const KEEPALIVE_ENABLED = 0b0000_0010;
|
const KEEP_ALIVE_ENABLED = 0b0000_0010;
|
||||||
const STREAM = 0b0000_0100;
|
const STREAM = 0b0000_0100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +42,9 @@ impl Default for Codec {
|
|||||||
|
|
||||||
impl fmt::Debug for Codec {
|
impl fmt::Debug for Codec {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "h1::Codec({:?})", self.flags)
|
f.debug_struct("h1::Codec")
|
||||||
|
.field("flags", &self.flags)
|
||||||
|
.finish_non_exhaustive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,8 +53,8 @@ impl Codec {
|
|||||||
///
|
///
|
||||||
/// `keepalive_enabled` how response `connection` header get generated.
|
/// `keepalive_enabled` how response `connection` header get generated.
|
||||||
pub fn new(config: ServiceConfig) -> Self {
|
pub fn new(config: ServiceConfig) -> Self {
|
||||||
let flags = if config.keep_alive_enabled() {
|
let flags = if config.keep_alive().enabled() {
|
||||||
Flags::KEEPALIVE_ENABLED
|
Flags::KEEP_ALIVE_ENABLED
|
||||||
} else {
|
} else {
|
||||||
Flags::empty()
|
Flags::empty()
|
||||||
};
|
};
|
||||||
@ -76,14 +78,14 @@ impl Codec {
|
|||||||
|
|
||||||
/// Check if last response is keep-alive.
|
/// Check if last response is keep-alive.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn keepalive(&self) -> bool {
|
pub fn keep_alive(&self) -> bool {
|
||||||
self.conn_type == ConnectionType::KeepAlive
|
self.conn_type == ConnectionType::KeepAlive
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if keep-alive enabled on server level.
|
/// Check if keep-alive enabled on server level.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn keepalive_enabled(&self) -> bool {
|
pub fn keep_alive_enabled(&self) -> bool {
|
||||||
self.flags.contains(Flags::KEEPALIVE_ENABLED)
|
self.flags.contains(Flags::KEEP_ALIVE_ENABLED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check last request's message type.
|
/// Check last request's message type.
|
||||||
@ -123,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::KEEPALIVE_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),
|
||||||
@ -179,9 +183,11 @@ impl Encoder<Message<(Response<()>, BodySize)>> for Codec {
|
|||||||
&self.config,
|
&self.config,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::Chunk(Some(bytes)) => {
|
Message::Chunk(Some(bytes)) => {
|
||||||
self.encoder.encode_chunk(bytes.as_ref(), dst)?;
|
self.encoder.encode_chunk(bytes.as_ref(), dst)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::Chunk(None) => {
|
Message::Chunk(None) => {
|
||||||
self.encoder.encode_eof(dst)?;
|
self.encoder.encode_eof(dst)?;
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
@ -380,34 +382,36 @@ impl HeaderIndex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
/// Http payload item
|
/// Chunk type yielded while decoding a payload.
|
||||||
pub enum PayloadItem {
|
pub enum PayloadItem {
|
||||||
Chunk(Bytes),
|
Chunk(Bytes),
|
||||||
Eof,
|
Eof,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decoders to handle different Transfer-Encodings.
|
/// Decoder that can handle different payload types.
|
||||||
///
|
///
|
||||||
/// If a message body does not include a Transfer-Encoding, it *should*
|
/// If a message body does not use `Transfer-Encoding`, it should include a `Content-Length`.
|
||||||
/// include a Content-Length header.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct PayloadDecoder {
|
pub struct PayloadDecoder {
|
||||||
kind: Kind,
|
kind: Kind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PayloadDecoder {
|
impl PayloadDecoder {
|
||||||
|
/// Constructs a fixed-length payload decoder.
|
||||||
pub fn length(x: u64) -> PayloadDecoder {
|
pub fn length(x: u64) -> PayloadDecoder {
|
||||||
PayloadDecoder {
|
PayloadDecoder {
|
||||||
kind: Kind::Length(x),
|
kind: Kind::Length(x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs a chunked encoding decoder.
|
||||||
pub fn chunked() -> PayloadDecoder {
|
pub fn chunked() -> PayloadDecoder {
|
||||||
PayloadDecoder {
|
PayloadDecoder {
|
||||||
kind: Kind::Chunked(ChunkedState::Size, 0),
|
kind: Kind::Chunked(ChunkedState::Size, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an decoder that yields chunks until the stream returns EOF.
|
||||||
pub fn eof() -> PayloadDecoder {
|
pub fn eof() -> PayloadDecoder {
|
||||||
PayloadDecoder { kind: Kind::Eof }
|
PayloadDecoder { kind: Kind::Eof }
|
||||||
}
|
}
|
||||||
@ -415,25 +419,26 @@ impl PayloadDecoder {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
enum Kind {
|
enum Kind {
|
||||||
/// A Reader used when a Content-Length header is passed with a positive
|
/// A reader used when a `Content-Length` header is passed with a positive integer.
|
||||||
/// integer.
|
|
||||||
Length(u64),
|
Length(u64),
|
||||||
/// A Reader used when Transfer-Encoding is `chunked`.
|
|
||||||
|
/// A reader used when `Transfer-Encoding` is `chunked`.
|
||||||
Chunked(ChunkedState, u64),
|
Chunked(ChunkedState, u64),
|
||||||
/// A Reader used for responses that don't indicate a length or chunked.
|
|
||||||
|
/// A reader used for responses that don't indicate a length or chunked.
|
||||||
///
|
///
|
||||||
/// Note: This should only used for `Response`s. It is illegal for a
|
/// Note: This should only used for `Response`s. It is illegal for a `Request` to be made
|
||||||
/// `Request` to be made with both `Content-Length` and
|
/// without either of `Content-Length` and `Transfer-Encoding: chunked` missing, as explained
|
||||||
/// `Transfer-Encoding: chunked` missing, as explained from the spec:
|
/// in [RFC 7230 §3.3.3]:
|
||||||
///
|
///
|
||||||
/// > If a Transfer-Encoding header field is present in a response and
|
/// > If a Transfer-Encoding header field is present in a response and the chunked transfer
|
||||||
/// > the chunked transfer coding is not the final encoding, the
|
/// > coding is not the final encoding, the message body length is determined by reading the
|
||||||
/// > message body length is determined by reading the connection until
|
/// > connection until it is closed by the server. If a Transfer-Encoding header field is
|
||||||
/// > it is closed by the server. If a Transfer-Encoding header field
|
/// > present in a request and the chunked transfer coding is not the final encoding, the
|
||||||
/// > is present in a request and the chunked transfer coding is not
|
/// > message body length cannot be determined reliably; the server MUST respond with the 400
|
||||||
/// > the final encoding, the message body length cannot be determined
|
/// > (Bad Request) status code and then close the connection.
|
||||||
/// > reliably; the server MUST respond with the 400 (Bad Request)
|
///
|
||||||
/// > status code and then close the connection.
|
/// [RFC 7230 §3.3.3]: https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3
|
||||||
Eof,
|
Eof,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,6 +468,7 @@ impl Decoder for PayloadDecoder {
|
|||||||
Ok(Some(PayloadItem::Chunk(buf)))
|
Ok(Some(PayloadItem::Chunk(buf)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Kind::Chunked(ref mut state, ref mut size) => {
|
Kind::Chunked(ref mut state, ref mut size) => {
|
||||||
loop {
|
loop {
|
||||||
let mut buf = None;
|
let mut buf = None;
|
||||||
@ -488,6 +494,7 @@ impl Decoder for PayloadDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Kind::Eof => {
|
Kind::Eof => {
|
||||||
if src.is_empty() {
|
if src.is_empty() {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
File diff suppressed because it is too large
Load Diff
973
actix-http/src/h1/dispatcher_tests.rs
Normal file
973
actix-http/src/h1/dispatcher_tests.rs
Normal file
@ -0,0 +1,973 @@
|
|||||||
|
use std::{future::Future, str, task::Poll, time::Duration};
|
||||||
|
|
||||||
|
use actix_rt::{pin, time::sleep};
|
||||||
|
use actix_service::fn_service;
|
||||||
|
use actix_utils::future::{ready, Ready};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures_util::future::lazy;
|
||||||
|
|
||||||
|
use actix_codec::Framed;
|
||||||
|
use actix_service::Service;
|
||||||
|
use bytes::{Buf, BytesMut};
|
||||||
|
|
||||||
|
use super::dispatcher::{Dispatcher, DispatcherState, DispatcherStateProj, Flags};
|
||||||
|
use crate::{
|
||||||
|
body::MessageBody,
|
||||||
|
config::ServiceConfig,
|
||||||
|
h1::{Codec, ExpectHandler, UpgradeHandler},
|
||||||
|
service::HttpFlow,
|
||||||
|
test::{TestBuffer, TestSeqBuffer},
|
||||||
|
Error, HttpMessage, KeepAlive, Method, OnConnectData, Request, Response, StatusCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option<usize> {
|
||||||
|
memchr::memmem::find(&haystack[from..], needle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stabilize_date_header(payload: &mut [u8]) {
|
||||||
|
let mut from = 0;
|
||||||
|
while let Some(pos) = find_slice(payload, b"date", from) {
|
||||||
|
payload[(from + pos)..(from + pos + 35)]
|
||||||
|
.copy_from_slice(b"date: Thu, 01 Jan 1970 12:34:56 UTC");
|
||||||
|
from += 35;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ok_service() -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
|
||||||
|
status_service(StatusCode::OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status_service(
|
||||||
|
status: StatusCode,
|
||||||
|
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
|
||||||
|
fn_service(move |_req: Request| ready(Ok::<_, Error>(Response::new(status))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn echo_path_service(
|
||||||
|
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
|
||||||
|
fn_service(|req: Request| {
|
||||||
|
let path = req.path().as_bytes();
|
||||||
|
ready(Ok::<_, Error>(
|
||||||
|
Response::ok().set_body(Bytes::copy_from_slice(path)),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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_service(|mut req: Request| {
|
||||||
|
Box::pin(async move {
|
||||||
|
use futures_util::stream::StreamExt as _;
|
||||||
|
|
||||||
|
let mut pl = req.take_payload();
|
||||||
|
let mut body = BytesMut::new();
|
||||||
|
while let Some(chunk) = pl.next().await {
|
||||||
|
body.extend_from_slice(chunk.unwrap().chunk())
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<_, Error>(Response::ok().set_body(body.freeze()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn late_request() {
|
||||||
|
let mut buf = TestBuffer::empty();
|
||||||
|
|
||||||
|
let cfg = ServiceConfig::new(
|
||||||
|
KeepAlive::Disabled,
|
||||||
|
Duration::from_millis(100),
|
||||||
|
Duration::ZERO,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let services = HttpFlow::new(ok_service(), ExpectHandler, None);
|
||||||
|
|
||||||
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
|
buf.clone(),
|
||||||
|
services,
|
||||||
|
cfg,
|
||||||
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
|
);
|
||||||
|
pin!(h1);
|
||||||
|
|
||||||
|
lazy(|cx| {
|
||||||
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
|
match h1.as_mut().poll(cx) {
|
||||||
|
Poll::Ready(_) => panic!("first poll should not be ready"),
|
||||||
|
Poll::Pending => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// polls: initial
|
||||||
|
assert_eq!(h1.poll_count, 1);
|
||||||
|
|
||||||
|
buf.extend_read_buf("GET /abcd HTTP/1.1\r\nConnection: close\r\n\r\n");
|
||||||
|
|
||||||
|
match h1.as_mut().poll(cx) {
|
||||||
|
Poll::Pending => panic!("second poll should not be pending"),
|
||||||
|
Poll::Ready(res) => assert!(res.is_ok()),
|
||||||
|
}
|
||||||
|
|
||||||
|
// polls: initial pending => handle req => shutdown
|
||||||
|
assert_eq!(h1.poll_count, 3);
|
||||||
|
|
||||||
|
let mut res = buf.take_write_buf().to_vec();
|
||||||
|
stabilize_date_header(&mut res);
|
||||||
|
let res = &res[..];
|
||||||
|
|
||||||
|
let exp = b"\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
content-length: 0\r\n\
|
||||||
|
connection: close\r\n\
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
|
||||||
|
";
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn oneshot_connection() {
|
||||||
|
let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n");
|
||||||
|
|
||||||
|
let cfg = ServiceConfig::new(
|
||||||
|
KeepAlive::Disabled,
|
||||||
|
Duration::from_millis(100),
|
||||||
|
Duration::ZERO,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
|
||||||
|
|
||||||
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
|
buf.clone(),
|
||||||
|
services,
|
||||||
|
cfg,
|
||||||
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
|
);
|
||||||
|
pin!(h1);
|
||||||
|
|
||||||
|
lazy(|cx| {
|
||||||
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
|
match h1.as_mut().poll(cx) {
|
||||||
|
Poll::Pending => panic!("first poll should not be pending"),
|
||||||
|
Poll::Ready(res) => assert!(res.is_ok()),
|
||||||
|
}
|
||||||
|
|
||||||
|
// polls: initial => shutdown
|
||||||
|
assert_eq!(h1.poll_count, 2);
|
||||||
|
|
||||||
|
let mut res = buf.take_write_buf().to_vec();
|
||||||
|
stabilize_date_header(&mut res);
|
||||||
|
let res = &res[..];
|
||||||
|
|
||||||
|
let exp = http_msg(
|
||||||
|
r"
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
content-length: 5
|
||||||
|
connection: close
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC
|
||||||
|
|
||||||
|
/abcd
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn keep_alive_timeout() {
|
||||||
|
let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n");
|
||||||
|
|
||||||
|
let cfg = ServiceConfig::new(
|
||||||
|
KeepAlive::Timeout(Duration::from_millis(200)),
|
||||||
|
Duration::from_millis(100),
|
||||||
|
Duration::ZERO,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
|
||||||
|
|
||||||
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
|
buf.clone(),
|
||||||
|
services,
|
||||||
|
cfg,
|
||||||
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
|
);
|
||||||
|
pin!(h1);
|
||||||
|
|
||||||
|
lazy(|cx| {
|
||||||
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
h1.as_mut().poll(cx).is_pending(),
|
||||||
|
"keep-alive should prevent poll from resolving"
|
||||||
|
);
|
||||||
|
|
||||||
|
// polls: initial
|
||||||
|
assert_eq!(h1.poll_count, 1);
|
||||||
|
|
||||||
|
let mut res = buf.take_write_buf().to_vec();
|
||||||
|
stabilize_date_header(&mut res);
|
||||||
|
let res = &res[..];
|
||||||
|
|
||||||
|
let exp = b"\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
content-length: 5\r\n\
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
|
||||||
|
/abcd\
|
||||||
|
";
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// sleep slightly longer than keep-alive timeout
|
||||||
|
sleep(Duration::from_millis(250)).await;
|
||||||
|
|
||||||
|
lazy(|cx| {
|
||||||
|
assert!(
|
||||||
|
h1.as_mut().poll(cx).is_ready(),
|
||||||
|
"keep-alive should have resolved",
|
||||||
|
);
|
||||||
|
|
||||||
|
// polls: initial => keep-alive wake-up shutdown
|
||||||
|
assert_eq!(h1.poll_count, 2);
|
||||||
|
|
||||||
|
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() {
|
||||||
|
// connection closed
|
||||||
|
assert!(inner.flags.contains(Flags::SHUTDOWN));
|
||||||
|
assert!(inner.flags.contains(Flags::WRITE_DISCONNECT));
|
||||||
|
// and nothing added to write buffer
|
||||||
|
assert!(buf.write_buf_slice().is_empty());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn keep_alive_follow_up_req() {
|
||||||
|
let mut buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n");
|
||||||
|
|
||||||
|
let cfg = ServiceConfig::new(
|
||||||
|
KeepAlive::Timeout(Duration::from_millis(500)),
|
||||||
|
Duration::from_millis(100),
|
||||||
|
Duration::ZERO,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
|
||||||
|
|
||||||
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
|
buf.clone(),
|
||||||
|
services,
|
||||||
|
cfg,
|
||||||
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
|
);
|
||||||
|
pin!(h1);
|
||||||
|
|
||||||
|
lazy(|cx| {
|
||||||
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
h1.as_mut().poll(cx).is_pending(),
|
||||||
|
"keep-alive should prevent poll from resolving"
|
||||||
|
);
|
||||||
|
|
||||||
|
// polls: initial
|
||||||
|
assert_eq!(h1.poll_count, 1);
|
||||||
|
|
||||||
|
let mut res = buf.take_write_buf().to_vec();
|
||||||
|
stabilize_date_header(&mut res);
|
||||||
|
let res = &res[..];
|
||||||
|
|
||||||
|
let exp = b"\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
content-length: 5\r\n\
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
|
||||||
|
/abcd\
|
||||||
|
";
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// sleep for less than KA timeout
|
||||||
|
sleep(Duration::from_millis(100)).await;
|
||||||
|
|
||||||
|
lazy(|cx| {
|
||||||
|
assert!(
|
||||||
|
h1.as_mut().poll(cx).is_pending(),
|
||||||
|
"keep-alive should not have resolved dispatcher yet",
|
||||||
|
);
|
||||||
|
|
||||||
|
// polls: initial => manual
|
||||||
|
assert_eq!(h1.poll_count, 2);
|
||||||
|
|
||||||
|
if let DispatcherStateProj::Normal { inner } = h1.as_mut().project().inner.project() {
|
||||||
|
// connection not closed
|
||||||
|
assert!(!inner.flags.contains(Flags::SHUTDOWN));
|
||||||
|
assert!(!inner.flags.contains(Flags::WRITE_DISCONNECT));
|
||||||
|
// and nothing added to write buffer
|
||||||
|
assert!(buf.write_buf_slice().is_empty());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
lazy(|cx| {
|
||||||
|
buf.extend_read_buf(
|
||||||
|
"\
|
||||||
|
GET /efg HTTP/1.1\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\r\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
h1.as_mut().poll(cx).is_ready(),
|
||||||
|
"connection close header should override keep-alive setting",
|
||||||
|
);
|
||||||
|
|
||||||
|
// polls: initial => manual => follow-up req => shutdown
|
||||||
|
assert_eq!(h1.poll_count, 4);
|
||||||
|
|
||||||
|
if let DispatcherStateProj::Normal { inner } = h1.as_mut().project().inner.project() {
|
||||||
|
// connection closed
|
||||||
|
assert!(inner.flags.contains(Flags::SHUTDOWN));
|
||||||
|
assert!(!inner.flags.contains(Flags::WRITE_DISCONNECT));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut res = buf.take_write_buf().to_vec();
|
||||||
|
stabilize_date_header(&mut res);
|
||||||
|
let res = &res[..];
|
||||||
|
|
||||||
|
let exp = b"\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
content-length: 4\r\n\
|
||||||
|
connection: close\r\n\
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
|
||||||
|
/efg\
|
||||||
|
";
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn req_parse_err() {
|
||||||
|
lazy(|cx| {
|
||||||
|
let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n");
|
||||||
|
|
||||||
|
let services = HttpFlow::new(ok_service(), ExpectHandler, None);
|
||||||
|
|
||||||
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
|
buf.clone(),
|
||||||
|
services,
|
||||||
|
ServiceConfig::default(),
|
||||||
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
pin!(h1);
|
||||||
|
|
||||||
|
match h1.as_mut().poll(cx) {
|
||||||
|
Poll::Pending => panic!(),
|
||||||
|
Poll::Ready(res) => assert!(res.is_err()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() {
|
||||||
|
assert!(inner.flags.contains(Flags::READ_DISCONNECT));
|
||||||
|
assert_eq!(
|
||||||
|
&buf.write_buf_slice()[..26],
|
||||||
|
b"HTTP/1.1 400 Bad Request\r\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn pipelining_ok_then_ok() {
|
||||||
|
lazy(|cx| {
|
||||||
|
let buf = TestBuffer::new(
|
||||||
|
"\
|
||||||
|
GET /abcd HTTP/1.1\r\n\r\n\
|
||||||
|
GET /def HTTP/1.1\r\n\r\n\
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
let cfg = ServiceConfig::new(
|
||||||
|
KeepAlive::Disabled,
|
||||||
|
Duration::from_millis(1),
|
||||||
|
Duration::from_millis(1),
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
|
||||||
|
|
||||||
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
|
buf.clone(),
|
||||||
|
services,
|
||||||
|
cfg,
|
||||||
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
pin!(h1);
|
||||||
|
|
||||||
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
|
match h1.as_mut().poll(cx) {
|
||||||
|
Poll::Pending => panic!("first poll should not be pending"),
|
||||||
|
Poll::Ready(res) => assert!(res.is_ok()),
|
||||||
|
}
|
||||||
|
|
||||||
|
// polls: initial => shutdown
|
||||||
|
assert_eq!(h1.poll_count, 2);
|
||||||
|
|
||||||
|
let mut res = buf.write_buf_slice_mut();
|
||||||
|
stabilize_date_header(&mut res);
|
||||||
|
let res = &res[..];
|
||||||
|
|
||||||
|
let exp = b"\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
content-length: 5\r\n\
|
||||||
|
connection: close\r\n\
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
|
||||||
|
/abcd\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
content-length: 4\r\n\
|
||||||
|
connection: close\r\n\
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
|
||||||
|
/def\
|
||||||
|
";
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn pipelining_ok_then_bad() {
|
||||||
|
lazy(|cx| {
|
||||||
|
let buf = TestBuffer::new(
|
||||||
|
"\
|
||||||
|
GET /abcd HTTP/1.1\r\n\r\n\
|
||||||
|
GET /def HTTP/1\r\n\r\n\
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
let cfg = ServiceConfig::new(
|
||||||
|
KeepAlive::Disabled,
|
||||||
|
Duration::from_millis(1),
|
||||||
|
Duration::from_millis(1),
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
|
||||||
|
|
||||||
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
|
buf.clone(),
|
||||||
|
services,
|
||||||
|
cfg,
|
||||||
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
pin!(h1);
|
||||||
|
|
||||||
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
|
match h1.as_mut().poll(cx) {
|
||||||
|
Poll::Pending => panic!("first poll should not be pending"),
|
||||||
|
Poll::Ready(res) => assert!(res.is_err()),
|
||||||
|
}
|
||||||
|
|
||||||
|
// polls: initial => shutdown
|
||||||
|
assert_eq!(h1.poll_count, 1);
|
||||||
|
|
||||||
|
let mut res = buf.write_buf_slice_mut();
|
||||||
|
stabilize_date_header(&mut res);
|
||||||
|
let res = &res[..];
|
||||||
|
|
||||||
|
let exp = b"\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
content-length: 5\r\n\
|
||||||
|
connection: close\r\n\
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
|
||||||
|
/abcd\
|
||||||
|
HTTP/1.1 400 Bad Request\r\n\
|
||||||
|
content-length: 0\r\n\
|
||||||
|
connection: close\r\n\
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
|
||||||
|
";
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn expect_handling() {
|
||||||
|
lazy(|cx| {
|
||||||
|
let mut buf = TestSeqBuffer::empty();
|
||||||
|
let cfg = ServiceConfig::new(
|
||||||
|
KeepAlive::Disabled,
|
||||||
|
Duration::ZERO,
|
||||||
|
Duration::ZERO,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let services = HttpFlow::new(echo_payload_service(), ExpectHandler, None);
|
||||||
|
|
||||||
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
|
buf.clone(),
|
||||||
|
services,
|
||||||
|
cfg,
|
||||||
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
buf.extend_read_buf(
|
||||||
|
"\
|
||||||
|
POST /upload HTTP/1.1\r\n\
|
||||||
|
Content-Length: 5\r\n\
|
||||||
|
Expect: 100-continue\r\n\
|
||||||
|
\r\n\
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
pin!(h1);
|
||||||
|
|
||||||
|
assert!(h1.as_mut().poll(cx).is_pending());
|
||||||
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
|
// polls: manual
|
||||||
|
assert_eq!(h1.poll_count, 1);
|
||||||
|
|
||||||
|
if let DispatcherState::Normal { ref inner } = h1.inner {
|
||||||
|
let io = inner.io.as_ref().unwrap();
|
||||||
|
let res = &io.write_buf()[..];
|
||||||
|
assert_eq!(
|
||||||
|
str::from_utf8(res).unwrap(),
|
||||||
|
"HTTP/1.1 100 Continue\r\n\r\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.extend_read_buf("12345");
|
||||||
|
assert!(h1.as_mut().poll(cx).is_ready());
|
||||||
|
|
||||||
|
// polls: manual manual shutdown
|
||||||
|
assert_eq!(h1.poll_count, 3);
|
||||||
|
|
||||||
|
if let DispatcherState::Normal { ref inner } = h1.inner {
|
||||||
|
let io = inner.io.as_ref().unwrap();
|
||||||
|
let mut res = (&io.write_buf()[..]).to_owned();
|
||||||
|
stabilize_date_header(&mut res);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
str::from_utf8(&res).unwrap(),
|
||||||
|
"\
|
||||||
|
HTTP/1.1 100 Continue\r\n\
|
||||||
|
\r\n\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
content-length: 5\r\n\
|
||||||
|
connection: close\r\n\
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\
|
||||||
|
\r\n\
|
||||||
|
12345\
|
||||||
|
"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn expect_eager() {
|
||||||
|
lazy(|cx| {
|
||||||
|
let mut buf = TestSeqBuffer::empty();
|
||||||
|
let cfg = ServiceConfig::new(
|
||||||
|
KeepAlive::Disabled,
|
||||||
|
Duration::ZERO,
|
||||||
|
Duration::ZERO,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
|
||||||
|
|
||||||
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
|
buf.clone(),
|
||||||
|
services,
|
||||||
|
cfg,
|
||||||
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
buf.extend_read_buf(
|
||||||
|
"\
|
||||||
|
POST /upload HTTP/1.1\r\n\
|
||||||
|
Content-Length: 5\r\n\
|
||||||
|
Expect: 100-continue\r\n\
|
||||||
|
\r\n\
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
pin!(h1);
|
||||||
|
|
||||||
|
assert!(h1.as_mut().poll(cx).is_ready());
|
||||||
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
|
// polls: manual shutdown
|
||||||
|
assert_eq!(h1.poll_count, 2);
|
||||||
|
|
||||||
|
if let DispatcherState::Normal { ref inner } = h1.inner {
|
||||||
|
let io = inner.io.as_ref().unwrap();
|
||||||
|
let mut res = (&io.write_buf()[..]).to_owned();
|
||||||
|
stabilize_date_header(&mut res);
|
||||||
|
|
||||||
|
// Despite the content-length header and even though the request payload has not
|
||||||
|
// been sent, this test expects a complete service response since the payload
|
||||||
|
// is not used at all. The service passed to dispatcher is path echo and doesn't
|
||||||
|
// consume payload bytes.
|
||||||
|
assert_eq!(
|
||||||
|
str::from_utf8(&res).unwrap(),
|
||||||
|
"\
|
||||||
|
HTTP/1.1 100 Continue\r\n\
|
||||||
|
\r\n\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
content-length: 7\r\n\
|
||||||
|
connection: close\r\n\
|
||||||
|
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\
|
||||||
|
\r\n\
|
||||||
|
/upload\
|
||||||
|
"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn upgrade_handling() {
|
||||||
|
struct TestUpgrade;
|
||||||
|
|
||||||
|
impl<T> Service<(Request, Framed<T, Codec>)> for TestUpgrade {
|
||||||
|
type Response = ();
|
||||||
|
type Error = Error;
|
||||||
|
type Future = Ready<Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
actix_service::always_ready!();
|
||||||
|
|
||||||
|
fn call(&self, (req, _framed): (Request, Framed<T, Codec>)) -> Self::Future {
|
||||||
|
assert_eq!(req.method(), Method::GET);
|
||||||
|
assert!(req.upgrade());
|
||||||
|
assert_eq!(req.headers().get("upgrade").unwrap(), "websocket");
|
||||||
|
ready(Ok(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy(|cx| {
|
||||||
|
let mut buf = TestSeqBuffer::empty();
|
||||||
|
let cfg = ServiceConfig::new(
|
||||||
|
KeepAlive::Disabled,
|
||||||
|
Duration::ZERO,
|
||||||
|
Duration::ZERO,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let services = HttpFlow::new(ok_service(), ExpectHandler, Some(TestUpgrade));
|
||||||
|
|
||||||
|
let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new(
|
||||||
|
buf.clone(),
|
||||||
|
services,
|
||||||
|
cfg,
|
||||||
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
buf.extend_read_buf(
|
||||||
|
"\
|
||||||
|
GET /ws HTTP/1.1\r\n\
|
||||||
|
Connection: Upgrade\r\n\
|
||||||
|
Upgrade: websocket\r\n\
|
||||||
|
\r\n\
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
pin!(h1);
|
||||||
|
|
||||||
|
assert!(h1.as_mut().poll(cx).is_ready());
|
||||||
|
assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. }));
|
||||||
|
|
||||||
|
// polls: manual shutdown
|
||||||
|
assert_eq!(h1.poll_count, 2);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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"
|
||||||
|
);
|
||||||
|
}
|
@ -105,7 +105,7 @@ pub(crate) trait MessageType: Sized {
|
|||||||
}
|
}
|
||||||
BodySize::Sized(0) if camel_case => dst.put_slice(b"\r\nContent-Length: 0\r\n"),
|
BodySize::Sized(0) if camel_case => dst.put_slice(b"\r\nContent-Length: 0\r\n"),
|
||||||
BodySize::Sized(0) => dst.put_slice(b"\r\ncontent-length: 0\r\n"),
|
BodySize::Sized(0) => dst.put_slice(b"\r\ncontent-length: 0\r\n"),
|
||||||
BodySize::Sized(len) => helpers::write_content_length(len, dst),
|
BodySize::Sized(len) => helpers::write_content_length(len, dst, camel_case),
|
||||||
BodySize::None => dst.put_slice(b"\r\n"),
|
BodySize::None => dst.put_slice(b"\r\n"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +152,6 @@ pub(crate) trait MessageType: Sized {
|
|||||||
let k = key.as_str().as_bytes();
|
let k = key.as_str().as_bytes();
|
||||||
let k_len = k.len();
|
let k_len = k.len();
|
||||||
|
|
||||||
// TODO: drain?
|
|
||||||
for val in value.iter() {
|
for val in value.iter() {
|
||||||
let v = val.as_ref();
|
let v = val.as_ref();
|
||||||
let v_len = v.len();
|
let v_len = v.len();
|
||||||
@ -211,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 {
|
||||||
config.set_date(dst);
|
// optimized date header, write_date_header writes its own \r\n
|
||||||
} else {
|
config.write_date_header(dst, camel_case);
|
||||||
// msg eof
|
|
||||||
dst.extend_from_slice(b"\r\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// end-of-headers marker
|
||||||
|
dst.extend_from_slice(b"\r\n");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,6 +257,12 @@ impl MessageType for Response<()> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn camel_case(&self) -> bool {
|
||||||
|
self.head()
|
||||||
|
.flags
|
||||||
|
.contains(crate::message::Flags::CAMEL_CASE)
|
||||||
|
}
|
||||||
|
|
||||||
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
|
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
|
||||||
let head = self.head();
|
let head = self.head();
|
||||||
let reason = head.reason().as_bytes();
|
let reason = head.reason().as_bytes();
|
||||||
@ -313,16 +318,17 @@ impl MessageType for RequestHeadType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: MessageType> MessageEncoder<T> {
|
impl<T: MessageType> MessageEncoder<T> {
|
||||||
/// Encode message
|
/// Encode chunk.
|
||||||
pub fn encode_chunk(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result<bool> {
|
pub fn encode_chunk(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result<bool> {
|
||||||
self.te.encode(msg, buf)
|
self.te.encode(msg, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encode eof
|
/// Encode EOF.
|
||||||
pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> {
|
pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> {
|
||||||
self.te.encode_eof(buf)
|
self.te.encode_eof(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Encode message.
|
||||||
pub fn encode(
|
pub fn encode(
|
||||||
&mut self,
|
&mut self,
|
||||||
dst: &mut BytesMut,
|
dst: &mut BytesMut,
|
||||||
|
@ -7,10 +7,13 @@ mod client;
|
|||||||
mod codec;
|
mod codec;
|
||||||
mod decoder;
|
mod decoder;
|
||||||
mod dispatcher;
|
mod dispatcher;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod dispatcher_tests;
|
||||||
mod encoder;
|
mod encoder;
|
||||||
mod expect;
|
mod expect;
|
||||||
mod payload;
|
mod payload;
|
||||||
mod service;
|
mod service;
|
||||||
|
mod timer;
|
||||||
mod upgrade;
|
mod upgrade;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
@ -26,9 +29,10 @@ pub use self::utils::SendResponse;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
/// Codec message
|
/// Codec message
|
||||||
pub enum Message<T> {
|
pub enum Message<T> {
|
||||||
/// Http message
|
/// HTTP message.
|
||||||
Item(T),
|
Item(T),
|
||||||
/// Payload chunk
|
|
||||||
|
/// Payload chunk.
|
||||||
Chunk(Option<Bytes>),
|
Chunk(Option<Bytes>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
80
actix-http/src/h1/timer.rs
Normal file
80
actix-http/src/h1/timer.rs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
use std::{fmt, future::Future, pin::Pin, task::Context};
|
||||||
|
|
||||||
|
use actix_rt::time::{Instant, Sleep};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) enum TimerState {
|
||||||
|
Disabled,
|
||||||
|
Inactive,
|
||||||
|
Active { timer: Pin<Box<Sleep>> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimerState {
|
||||||
|
pub(super) fn new(enabled: bool) -> Self {
|
||||||
|
if enabled {
|
||||||
|
Self::Inactive
|
||||||
|
} else {
|
||||||
|
Self::Disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn is_enabled(&self) -> bool {
|
||||||
|
matches!(self, Self::Active { .. } | Self::Inactive)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn set(&mut self, timer: Sleep, line: u32) {
|
||||||
|
if matches!(self, Self::Disabled) {
|
||||||
|
log::trace!("setting disabled timer from line {}", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
*self = Self::Active {
|
||||||
|
timer: Box::pin(timer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn set_and_init(&mut self, cx: &mut Context<'_>, timer: Sleep, line: u32) {
|
||||||
|
self.set(timer, line);
|
||||||
|
self.init(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn clear(&mut self, line: u32) {
|
||||||
|
if matches!(self, Self::Disabled) {
|
||||||
|
log::trace!("trying to clear a disabled timer from line {}", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(self, Self::Inactive) {
|
||||||
|
log::trace!("trying to clear an inactive timer from line {}", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
*self = Self::Inactive;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn init(&mut self, cx: &mut Context<'_>) {
|
||||||
|
if let TimerState::Active { timer } = self {
|
||||||
|
let _ = timer.as_mut().poll(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TimerState {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
TimerState::Disabled => f.write_str("timer is disabled"),
|
||||||
|
TimerState::Inactive => f.write_str("timer is inactive"),
|
||||||
|
TimerState::Active { timer } => {
|
||||||
|
let deadline = timer.deadline();
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
if deadline < now {
|
||||||
|
f.write_str("timer is active and has reached deadline")
|
||||||
|
} else {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"timer is active and due to expire in {} milliseconds",
|
||||||
|
((deadline - now).as_secs_f32() * 1000.0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -57,11 +57,11 @@ where
|
|||||||
conn_data: OnConnectData,
|
conn_data: OnConnectData,
|
||||||
timer: Option<Pin<Box<Sleep>>>,
|
timer: Option<Pin<Box<Sleep>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ping_pong = config.keep_alive().map(|dur| H2PingPong {
|
let ping_pong = config.keep_alive().duration().map(|dur| H2PingPong {
|
||||||
timer: timer
|
timer: timer
|
||||||
.map(|mut timer| {
|
.map(|mut timer| {
|
||||||
// reset timer if it's received from new function.
|
// reuse timer slot if it was initialized for handshake
|
||||||
timer.as_mut().reset(config.now() + dur);
|
timer.as_mut().reset((config.now() + dur).into());
|
||||||
timer
|
timer
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| Box::pin(sleep(dur))),
|
.unwrap_or_else(|| Box::pin(sleep(dur))),
|
||||||
@ -141,7 +141,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) => warn!("{:?}", err),
|
DispatchError::SendData(err) => log::warn!("{:?}", err),
|
||||||
DispatchError::ResponseBody(err) => {
|
DispatchError::ResponseBody(err) => {
|
||||||
error!("Response payload stream error: {:?}", err)
|
error!("Response payload stream error: {:?}", err)
|
||||||
}
|
}
|
||||||
@ -160,8 +160,8 @@ where
|
|||||||
Poll::Ready(_) => {
|
Poll::Ready(_) => {
|
||||||
ping_pong.on_flight = false;
|
ping_pong.on_flight = false;
|
||||||
|
|
||||||
let dead_line = this.config.keep_alive_expire().unwrap();
|
let dead_line = this.config.keep_alive_deadline().unwrap();
|
||||||
ping_pong.timer.as_mut().reset(dead_line);
|
ping_pong.timer.as_mut().reset(dead_line.into());
|
||||||
}
|
}
|
||||||
Poll::Pending => {
|
Poll::Pending => {
|
||||||
return ping_pong.timer.as_mut().poll(cx).map(|_| Ok(()))
|
return ping_pong.timer.as_mut().poll(cx).map(|_| Ok(()))
|
||||||
@ -174,8 +174,8 @@ where
|
|||||||
|
|
||||||
ping_pong.ping_pong.send_ping(Ping::opaque())?;
|
ping_pong.ping_pong.send_ping(Ping::opaque())?;
|
||||||
|
|
||||||
let dead_line = this.config.keep_alive_expire().unwrap();
|
let dead_line = this.config.keep_alive_deadline().unwrap();
|
||||||
ping_pong.timer.as_mut().reset(dead_line);
|
ping_pong.timer.as_mut().reset(dead_line.into());
|
||||||
|
|
||||||
ping_pong.on_flight = true;
|
ping_pong.on_flight = true;
|
||||||
}
|
}
|
||||||
@ -322,7 +322,7 @@ fn prepare_response(
|
|||||||
// set date header
|
// set date header
|
||||||
if !has_date {
|
if !has_date {
|
||||||
let mut bytes = BytesMut::with_capacity(29);
|
let mut bytes = BytesMut::with_capacity(29);
|
||||||
config.set_date_header(&mut bytes);
|
config.write_date_header_value(&mut bytes);
|
||||||
res.headers_mut().insert(
|
res.headers_mut().insert(
|
||||||
DATE,
|
DATE,
|
||||||
// SAFETY: serialized date-times are known ASCII strings
|
// SAFETY: serialized date-times are known ASCII strings
|
||||||
|
@ -7,7 +7,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite};
|
use actix_codec::{AsyncRead, AsyncWrite};
|
||||||
use actix_rt::time::Sleep;
|
use actix_rt::time::{sleep_until, Sleep};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
use h2::{
|
use h2::{
|
||||||
@ -15,17 +15,17 @@ use h2::{
|
|||||||
RecvStream,
|
RecvStream,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::ServiceConfig,
|
||||||
|
error::{DispatchError, PayloadError},
|
||||||
|
};
|
||||||
|
|
||||||
mod dispatcher;
|
mod dispatcher;
|
||||||
mod service;
|
mod service;
|
||||||
|
|
||||||
pub use self::dispatcher::Dispatcher;
|
pub use self::dispatcher::Dispatcher;
|
||||||
pub use self::service::H2Service;
|
pub use self::service::H2Service;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
config::ServiceConfig,
|
|
||||||
error::{DispatchError, PayloadError},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// HTTP/2 peer stream.
|
/// HTTP/2 peer stream.
|
||||||
pub struct Payload {
|
pub struct Payload {
|
||||||
stream: RecvStream,
|
stream: RecvStream,
|
||||||
@ -67,7 +67,9 @@ where
|
|||||||
{
|
{
|
||||||
HandshakeWithTimeout {
|
HandshakeWithTimeout {
|
||||||
handshake: handshake(io),
|
handshake: handshake(io),
|
||||||
timer: config.client_timer().map(Box::pin),
|
timer: config
|
||||||
|
.client_request_deadline()
|
||||||
|
.map(|deadline| Box::pin(sleep_until(deadline.into()))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +88,7 @@ where
|
|||||||
let this = self.get_mut();
|
let this = self.get_mut();
|
||||||
|
|
||||||
match Pin::new(&mut this.handshake).poll(cx)? {
|
match Pin::new(&mut this.handshake).poll(cx)? {
|
||||||
// return the timer on success handshake. It can be re-used for h2 ping-pong.
|
// return the timer on success handshake; its slot can be re-used for h2 ping-pong
|
||||||
Poll::Ready(conn) => Poll::Ready(Ok((conn, this.timer.take()))),
|
Poll::Ready(conn) => Poll::Ready(Ok((conn, this.timer.take()))),
|
||||||
Poll::Pending => match this.timer.as_mut() {
|
Poll::Pending => match this.timer.as_mut() {
|
||||||
Some(timer) => {
|
Some(timer) => {
|
||||||
|
@ -355,7 +355,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
trace!("H2 handshake error: {}", err);
|
log::trace!("H2 handshake error: {}", err);
|
||||||
Poll::Ready(Err(err))
|
Poll::Ready(Err(err))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -630,7 +630,7 @@ impl Removed {
|
|||||||
/// Returns true if iterator contains no elements, without consuming it.
|
/// Returns true if iterator contains no elements, without consuming it.
|
||||||
///
|
///
|
||||||
/// If called immediately after [`HeaderMap::insert`] or [`HeaderMap::remove`], it will indicate
|
/// If called immediately after [`HeaderMap::insert`] or [`HeaderMap::remove`], it will indicate
|
||||||
/// wether any items were actually replaced or removed, respectively.
|
/// whether any items were actually replaced or removed, respectively.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
match self.inner {
|
match self.inner {
|
||||||
// size hint lower bound of smallvec is the correct length
|
// size hint lower bound of smallvec is the correct length
|
||||||
|
@ -4,8 +4,7 @@ use bytes::BytesMut;
|
|||||||
use http::header::{HeaderValue, InvalidHeaderValue};
|
use http::header::{HeaderValue, InvalidHeaderValue};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue,
|
date::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue, helpers::MutWriter,
|
||||||
helpers::MutWriter,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A timestamp with HTTP-style formatting and parsing.
|
/// A timestamp with HTTP-style formatting and parsing.
|
||||||
|
@ -30,15 +30,25 @@ pub(crate) fn write_status_line<B: BufMut>(version: Version, n: u16, buf: &mut B
|
|||||||
/// Write out content length header.
|
/// Write out content length header.
|
||||||
///
|
///
|
||||||
/// Buffer must to contain enough space or be implicitly extendable.
|
/// Buffer must to contain enough space or be implicitly extendable.
|
||||||
pub fn write_content_length<B: BufMut>(n: u64, buf: &mut B) {
|
pub fn write_content_length<B: BufMut>(n: u64, buf: &mut B, camel_case: bool) {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
buf.put_slice(b"\r\ncontent-length: 0\r\n");
|
if camel_case {
|
||||||
|
buf.put_slice(b"\r\nContent-Length: 0\r\n");
|
||||||
|
} else {
|
||||||
|
buf.put_slice(b"\r\ncontent-length: 0\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut buffer = itoa::Buffer::new();
|
let mut buffer = itoa::Buffer::new();
|
||||||
|
|
||||||
buf.put_slice(b"\r\ncontent-length: ");
|
if camel_case {
|
||||||
|
buf.put_slice(b"\r\nContent-Length: ");
|
||||||
|
} else {
|
||||||
|
buf.put_slice(b"\r\ncontent-length: ");
|
||||||
|
}
|
||||||
|
|
||||||
buf.put_slice(buffer.format(n).as_bytes());
|
buf.put_slice(buffer.format(n).as_bytes());
|
||||||
buf.put_slice(b"\r\n");
|
buf.put_slice(b"\r\n");
|
||||||
}
|
}
|
||||||
@ -95,77 +105,88 @@ mod tests {
|
|||||||
fn test_write_content_length() {
|
fn test_write_content_length() {
|
||||||
let mut bytes = BytesMut::new();
|
let mut bytes = BytesMut::new();
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(0, &mut bytes);
|
write_content_length(0, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 0\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 0\r\n"[..]);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(9, &mut bytes);
|
write_content_length(9, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 9\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 9\r\n"[..]);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(10, &mut bytes);
|
write_content_length(10, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 10\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 10\r\n"[..]);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(99, &mut bytes);
|
write_content_length(99, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 99\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 99\r\n"[..]);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(100, &mut bytes);
|
write_content_length(100, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 100\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 100\r\n"[..]);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(101, &mut bytes);
|
write_content_length(101, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 101\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 101\r\n"[..]);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(998, &mut bytes);
|
write_content_length(998, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 998\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 998\r\n"[..]);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(1000, &mut bytes);
|
write_content_length(1000, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 1000\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 1000\r\n"[..]);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(1001, &mut bytes);
|
write_content_length(1001, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 1001\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 1001\r\n"[..]);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(5909, &mut bytes);
|
write_content_length(5909, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 5909\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 5909\r\n"[..]);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(9999, &mut bytes);
|
write_content_length(9999, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 9999\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 9999\r\n"[..]);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(10001, &mut bytes);
|
write_content_length(10001, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 10001\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 10001\r\n"[..]);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(59094, &mut bytes);
|
write_content_length(59094, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 59094\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 59094\r\n"[..]);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(99999, &mut bytes);
|
write_content_length(99999, &mut bytes, false);
|
||||||
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 99999\r\n"[..]);
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 99999\r\n"[..]);
|
||||||
|
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(590947, &mut bytes);
|
write_content_length(590947, &mut bytes, false);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bytes.split().freeze(),
|
bytes.split().freeze(),
|
||||||
b"\r\ncontent-length: 590947\r\n"[..]
|
b"\r\ncontent-length: 590947\r\n"[..]
|
||||||
);
|
);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(999999, &mut bytes);
|
write_content_length(999999, &mut bytes, false);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bytes.split().freeze(),
|
bytes.split().freeze(),
|
||||||
b"\r\ncontent-length: 999999\r\n"[..]
|
b"\r\ncontent-length: 999999\r\n"[..]
|
||||||
);
|
);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(5909471, &mut bytes);
|
write_content_length(5909471, &mut bytes, false);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bytes.split().freeze(),
|
bytes.split().freeze(),
|
||||||
b"\r\ncontent-length: 5909471\r\n"[..]
|
b"\r\ncontent-length: 5909471\r\n"[..]
|
||||||
);
|
);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(59094718, &mut bytes);
|
write_content_length(59094718, &mut bytes, false);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bytes.split().freeze(),
|
bytes.split().freeze(),
|
||||||
b"\r\ncontent-length: 59094718\r\n"[..]
|
b"\r\ncontent-length: 59094718\r\n"[..]
|
||||||
);
|
);
|
||||||
bytes.reserve(50);
|
bytes.reserve(50);
|
||||||
write_content_length(4294973728, &mut bytes);
|
write_content_length(4294973728, &mut bytes, false);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bytes.split().freeze(),
|
bytes.split().freeze(),
|
||||||
b"\r\ncontent-length: 4294973728\r\n"[..]
|
b"\r\ncontent-length: 4294973728\r\n"[..]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_content_length_camel_case() {
|
||||||
|
let mut bytes = BytesMut::new();
|
||||||
|
write_content_length(0, &mut bytes, false);
|
||||||
|
assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 0\r\n"[..]);
|
||||||
|
|
||||||
|
let mut bytes = BytesMut::new();
|
||||||
|
write_content_length(0, &mut bytes, true);
|
||||||
|
assert_eq!(bytes.split().freeze(), b"\r\nContent-Length: 0\r\n"[..]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,10 @@ pub trait HttpMessage: Sized {
|
|||||||
/// Message payload stream
|
/// Message payload stream
|
||||||
fn take_payload(&mut self) -> Payload<Self::Stream>;
|
fn take_payload(&mut self) -> Payload<Self::Stream>;
|
||||||
|
|
||||||
/// Request's extensions container
|
/// Returns a reference to the request-local data/extensions container.
|
||||||
fn extensions(&self) -> Ref<'_, Extensions>;
|
fn extensions(&self) -> Ref<'_, Extensions>;
|
||||||
|
|
||||||
/// Mutable reference to a the request's extensions container
|
/// Returns a mutable reference to the request-local data/extensions container.
|
||||||
fn extensions_mut(&self) -> RefMut<'_, Extensions>;
|
fn extensions_mut(&self) -> RefMut<'_, Extensions>;
|
||||||
|
|
||||||
/// Get a header.
|
/// Get a header.
|
||||||
@ -55,7 +55,7 @@ pub trait HttpMessage: Sized {
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get content type encoding
|
/// Get content type encoding.
|
||||||
///
|
///
|
||||||
/// UTF-8 is used by default, If request charset is not set.
|
/// UTF-8 is used by default, If request charset is not set.
|
||||||
fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> {
|
fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> {
|
||||||
|
84
actix-http/src/keep_alive.rs
Normal file
84
actix-http/src/keep_alive.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// Connection keep-alive config.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum KeepAlive {
|
||||||
|
/// Keep-alive duration.
|
||||||
|
///
|
||||||
|
/// `KeepAlive::Timeout(Duration::ZERO)` is mapped to `KeepAlive::Disabled`.
|
||||||
|
Timeout(Duration),
|
||||||
|
|
||||||
|
/// Rely on OS to shutdown TCP connection.
|
||||||
|
///
|
||||||
|
/// Some defaults can be very long, check your OS documentation.
|
||||||
|
Os,
|
||||||
|
|
||||||
|
/// Keep-alive is disabled.
|
||||||
|
///
|
||||||
|
/// Connections will be closed immediately.
|
||||||
|
Disabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeepAlive {
|
||||||
|
pub(crate) fn enabled(&self) -> bool {
|
||||||
|
!matches!(self, Self::Disabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)] // used with `http2` feature flag
|
||||||
|
pub(crate) fn duration(&self) -> Option<Duration> {
|
||||||
|
match self {
|
||||||
|
KeepAlive::Timeout(dur) => Some(*dur),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map zero duration to disabled.
|
||||||
|
pub(crate) fn normalize(self) -> KeepAlive {
|
||||||
|
match self {
|
||||||
|
KeepAlive::Timeout(Duration::ZERO) => KeepAlive::Disabled,
|
||||||
|
ka => ka,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for KeepAlive {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Timeout(Duration::from_secs(5))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Duration> for KeepAlive {
|
||||||
|
fn from(dur: Duration) -> Self {
|
||||||
|
KeepAlive::Timeout(dur).normalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Option<Duration>> for KeepAlive {
|
||||||
|
fn from(ka_dur: Option<Duration>) -> Self {
|
||||||
|
match ka_dur {
|
||||||
|
Some(dur) => KeepAlive::from(dur),
|
||||||
|
None => KeepAlive::Disabled,
|
||||||
|
}
|
||||||
|
.normalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_impls() {
|
||||||
|
let test: KeepAlive = Duration::from_secs(1).into();
|
||||||
|
assert_eq!(test, KeepAlive::Timeout(Duration::from_secs(1)));
|
||||||
|
|
||||||
|
let test: KeepAlive = Duration::from_secs(0).into();
|
||||||
|
assert_eq!(test, KeepAlive::Disabled);
|
||||||
|
|
||||||
|
let test: KeepAlive = Some(Duration::from_secs(0)).into();
|
||||||
|
assert_eq!(test, KeepAlive::Disabled);
|
||||||
|
|
||||||
|
let test: KeepAlive = None.into();
|
||||||
|
assert_eq!(test, KeepAlive::Disabled);
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
@ -24,38 +26,42 @@
|
|||||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
pub use ::http::{uri, uri::Uri};
|
pub use ::http::{uri, uri::Uri};
|
||||||
pub use ::http::{Method, StatusCode, Version};
|
pub use ::http::{Method, StatusCode, Version};
|
||||||
|
|
||||||
pub mod body;
|
pub mod body;
|
||||||
mod builder;
|
mod builder;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod date;
|
||||||
#[cfg(feature = "__compress")]
|
#[cfg(feature = "__compress")]
|
||||||
pub mod encoding;
|
pub mod encoding;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod extensions;
|
mod extensions;
|
||||||
pub mod h1;
|
pub mod h1;
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
pub mod h2;
|
pub mod h2;
|
||||||
pub mod header;
|
pub mod header;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod http_message;
|
mod http_message;
|
||||||
|
mod keep_alive;
|
||||||
mod message;
|
mod message;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod notify_on_drop;
|
||||||
mod payload;
|
mod payload;
|
||||||
mod requests;
|
mod requests;
|
||||||
mod responses;
|
mod responses;
|
||||||
mod service;
|
mod service;
|
||||||
pub mod test;
|
pub mod test;
|
||||||
|
#[cfg(feature = "ws")]
|
||||||
pub mod ws;
|
pub mod ws;
|
||||||
|
|
||||||
pub use self::builder::HttpServiceBuilder;
|
pub use self::builder::HttpServiceBuilder;
|
||||||
pub use self::config::{KeepAlive, ServiceConfig};
|
pub use self::config::ServiceConfig;
|
||||||
pub use self::error::Error;
|
pub use self::error::Error;
|
||||||
pub use self::extensions::Extensions;
|
pub use self::extensions::Extensions;
|
||||||
pub use self::header::ContentEncoding;
|
pub use self::header::ContentEncoding;
|
||||||
pub use self::http_message::HttpMessage;
|
pub use self::http_message::HttpMessage;
|
||||||
|
pub use self::keep_alive::KeepAlive;
|
||||||
pub use self::message::ConnectionType;
|
pub use self::message::ConnectionType;
|
||||||
pub use self::message::Message;
|
pub use self::message::Message;
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
|
@ -5,13 +5,13 @@ use bitflags::bitflags;
|
|||||||
/// Represents various types of connection
|
/// Represents various types of connection
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
pub enum ConnectionType {
|
pub enum ConnectionType {
|
||||||
/// Close connection after response
|
/// Close connection after response.
|
||||||
Close,
|
Close,
|
||||||
|
|
||||||
/// Keep connection alive after response
|
/// Keep connection alive after response.
|
||||||
KeepAlive,
|
KeepAlive,
|
||||||
|
|
||||||
/// Connection is upgraded to different type
|
/// Connection is upgraded to different type.
|
||||||
Upgrade,
|
Upgrade,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,8 +69,8 @@ impl<T: Head> Drop for Message<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generic `Head` object pool.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Request's objects pool
|
|
||||||
pub struct MessagePool<T: Head>(RefCell<Vec<Rc<T>>>);
|
pub struct MessagePool<T: Head>(RefCell<Vec<Rc<T>>>);
|
||||||
|
|
||||||
impl<T: Head> MessagePool<T> {
|
impl<T: Head> MessagePool<T> {
|
||||||
|
49
actix-http/src/notify_on_drop.rs
Normal file
49
actix-http/src/notify_on_drop.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/// Test Module for checking the drop state of certain async tasks that are spawned
|
||||||
|
/// with `actix_rt::spawn`
|
||||||
|
///
|
||||||
|
/// The target task must explicitly generate `NotifyOnDrop` when spawn the task
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static NOTIFY_DROPPED: RefCell<Option<bool>> = RefCell::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the spawned task is dropped.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics when there was no `NotifyOnDrop` instance on current thread.
|
||||||
|
pub(crate) fn is_dropped() -> bool {
|
||||||
|
NOTIFY_DROPPED.with(|bool| {
|
||||||
|
bool.borrow()
|
||||||
|
.expect("No NotifyOnDrop existed on current thread")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct NotifyOnDrop;
|
||||||
|
|
||||||
|
impl NotifyOnDrop {
|
||||||
|
/// # Panics
|
||||||
|
/// Panics hen construct multiple instances on any given thread.
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
NOTIFY_DROPPED.with(|bool| {
|
||||||
|
let mut bool = bool.borrow_mut();
|
||||||
|
if bool.is_some() {
|
||||||
|
panic!("NotifyOnDrop existed on current thread");
|
||||||
|
} else {
|
||||||
|
*bool = Some(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
NotifyOnDrop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for NotifyOnDrop {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
NOTIFY_DROPPED.with(|bool| {
|
||||||
|
if let Some(b) = bool.borrow_mut().as_mut() {
|
||||||
|
*b = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ use std::{
|
|||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use crate::error::PayloadError;
|
use crate::error::PayloadError;
|
||||||
|
|
||||||
@ -15,7 +16,19 @@ pub type BoxedPayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadErr
|
|||||||
#[deprecated(since = "4.0.0", note = "Renamed to `BoxedPayloadStream`.")]
|
#[deprecated(since = "4.0.0", note = "Renamed to `BoxedPayloadStream`.")]
|
||||||
pub type PayloadStream = BoxedPayloadStream;
|
pub type PayloadStream = BoxedPayloadStream;
|
||||||
|
|
||||||
pin_project_lite::pin_project! {
|
#[cfg(not(feature = "http2"))]
|
||||||
|
pin_project! {
|
||||||
|
/// A streaming payload.
|
||||||
|
#[project = PayloadProj]
|
||||||
|
pub enum Payload<S = BoxedPayloadStream> {
|
||||||
|
None,
|
||||||
|
H1 { payload: crate::h1::Payload },
|
||||||
|
Stream { #[pin] payload: S },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pin_project! {
|
||||||
/// A streaming payload.
|
/// A streaming payload.
|
||||||
#[project = PayloadProj]
|
#[project = PayloadProj]
|
||||||
pub enum Payload<S = BoxedPayloadStream> {
|
pub enum Payload<S = BoxedPayloadStream> {
|
||||||
@ -32,14 +45,16 @@ impl<S> From<crate::h1::Payload> for Payload<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
impl<S> From<crate::h2::Payload> for Payload<S> {
|
impl<S> From<crate::h2::Payload> for Payload<S> {
|
||||||
fn from(payload: crate::h2::Payload) -> Self {
|
fn from(payload: crate::h2::Payload) -> Self {
|
||||||
Payload::H2 { payload }
|
Payload::H2 { payload }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> From<h2::RecvStream> for Payload<S> {
|
#[cfg(feature = "http2")]
|
||||||
fn from(stream: h2::RecvStream) -> Self {
|
impl<S> From<::h2::RecvStream> for Payload<S> {
|
||||||
|
fn from(stream: ::h2::RecvStream) -> Self {
|
||||||
Payload::H2 {
|
Payload::H2 {
|
||||||
payload: crate::h2::Payload::new(stream),
|
payload: crate::h2::Payload::new(stream),
|
||||||
}
|
}
|
||||||
@ -70,7 +85,10 @@ where
|
|||||||
match self.project() {
|
match self.project() {
|
||||||
PayloadProj::None => Poll::Ready(None),
|
PayloadProj::None => Poll::Ready(None),
|
||||||
PayloadProj::H1 { payload } => Pin::new(payload).poll_next(cx),
|
PayloadProj::H1 { payload } => Pin::new(payload).poll_next(cx),
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
PayloadProj::H2 { payload } => Pin::new(payload).poll_next(cx),
|
PayloadProj::H2 { payload } => Pin::new(payload).poll_next(cx),
|
||||||
|
|
||||||
PayloadProj::Stream { payload } => payload.poll_next(cx),
|
PayloadProj::Stream { payload } => payload.poll_next(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,8 +130,8 @@ impl RequestHead {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request contains `EXPECT` header.
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Request contains `EXPECT` header
|
|
||||||
pub fn expect(&self) -> bool {
|
pub fn expect(&self) -> bool {
|
||||||
self.flags.contains(Flags::EXPECT)
|
self.flags.contains(Flags::EXPECT)
|
||||||
}
|
}
|
||||||
@ -142,8 +142,8 @@ impl RequestHead {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum RequestHeadType {
|
pub enum RequestHeadType {
|
||||||
Owned(RequestHead),
|
Owned(RequestHead),
|
||||||
Rc(Rc<RequestHead>, Option<HeaderMap>),
|
Rc(Rc<RequestHead>, Option<HeaderMap>),
|
||||||
|
@ -19,7 +19,7 @@ pub struct Request<P = BoxedPayloadStream> {
|
|||||||
pub(crate) payload: Payload<P>,
|
pub(crate) payload: Payload<P>,
|
||||||
pub(crate) head: Message<RequestHead>,
|
pub(crate) head: Message<RequestHead>,
|
||||||
pub(crate) conn_data: Option<Rc<Extensions>>,
|
pub(crate) conn_data: Option<Rc<Extensions>>,
|
||||||
pub(crate) req_data: RefCell<Extensions>,
|
pub(crate) extensions: RefCell<Extensions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> HttpMessage for Request<P> {
|
impl<P> HttpMessage for Request<P> {
|
||||||
@ -34,16 +34,14 @@ impl<P> HttpMessage for Request<P> {
|
|||||||
mem::replace(&mut self.payload, Payload::None)
|
mem::replace(&mut self.payload, Payload::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request extensions
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extensions(&self) -> Ref<'_, Extensions> {
|
fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
self.req_data.borrow()
|
self.extensions.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutable reference to a the request's extensions
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
||||||
self.req_data.borrow_mut()
|
self.extensions.borrow_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +50,7 @@ impl From<Message<RequestHead>> for Request<BoxedPayloadStream> {
|
|||||||
Request {
|
Request {
|
||||||
head,
|
head,
|
||||||
payload: Payload::None,
|
payload: Payload::None,
|
||||||
req_data: RefCell::new(Extensions::default()),
|
extensions: RefCell::new(Extensions::default()),
|
||||||
conn_data: None,
|
conn_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,7 +63,7 @@ impl Request<BoxedPayloadStream> {
|
|||||||
Request {
|
Request {
|
||||||
head: Message::new(),
|
head: Message::new(),
|
||||||
payload: Payload::None,
|
payload: Payload::None,
|
||||||
req_data: RefCell::new(Extensions::default()),
|
extensions: RefCell::new(Extensions::default()),
|
||||||
conn_data: None,
|
conn_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,7 +75,7 @@ impl<P> Request<P> {
|
|||||||
Request {
|
Request {
|
||||||
payload,
|
payload,
|
||||||
head: Message::new(),
|
head: Message::new(),
|
||||||
req_data: RefCell::new(Extensions::default()),
|
extensions: RefCell::new(Extensions::default()),
|
||||||
conn_data: None,
|
conn_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +88,7 @@ impl<P> Request<P> {
|
|||||||
Request {
|
Request {
|
||||||
payload,
|
payload,
|
||||||
head: self.head,
|
head: self.head,
|
||||||
req_data: self.req_data,
|
extensions: self.extensions,
|
||||||
conn_data: self.conn_data,
|
conn_data: self.conn_data,
|
||||||
},
|
},
|
||||||
pl,
|
pl,
|
||||||
@ -195,16 +193,17 @@ impl<P> Request<P> {
|
|||||||
.and_then(|container| container.get::<T>())
|
.and_then(|container| container.get::<T>())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the connection data container if an [on-connect] callback was registered.
|
/// Returns the connection-level data/extensions container if an [on-connect] callback was
|
||||||
|
/// registered, leaving an empty one in its place.
|
||||||
///
|
///
|
||||||
/// [on-connect]: crate::HttpServiceBuilder::on_connect_ext
|
/// [on-connect]: crate::HttpServiceBuilder::on_connect_ext
|
||||||
pub fn take_conn_data(&mut self) -> Option<Rc<Extensions>> {
|
pub fn take_conn_data(&mut self) -> Option<Rc<Extensions>> {
|
||||||
self.conn_data.take()
|
self.conn_data.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the request data container, leaving an empty one in it's place.
|
/// Returns the request-local data/extensions container, leaving an empty one in its place.
|
||||||
pub fn take_req_data(&mut self) -> Extensions {
|
pub fn take_req_data(&mut self) -> Extensions {
|
||||||
mem::take(self.req_data.get_mut())
|
mem::take(self.extensions.get_mut())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
//! HTTP response builder.
|
//! HTTP response builder.
|
||||||
|
|
||||||
use std::{
|
use std::{cell::RefCell, fmt, str};
|
||||||
cell::{Ref, RefMut},
|
|
||||||
fmt, str,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{EitherBody, MessageBody},
|
body::{EitherBody, MessageBody},
|
||||||
@ -202,20 +199,6 @@ impl ResponseBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Responses extensions
|
|
||||||
#[inline]
|
|
||||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
|
||||||
let head = self.head.as_ref().expect("cannot reuse response builder");
|
|
||||||
head.extensions.borrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mutable reference to a the response's extensions
|
|
||||||
#[inline]
|
|
||||||
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
|
||||||
let head = self.head.as_ref().expect("cannot reuse response builder");
|
|
||||||
head.extensions.borrow_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate response with a wrapped body.
|
/// Generate response with a wrapped body.
|
||||||
///
|
///
|
||||||
/// This `ResponseBuilder` will be left in a useless state.
|
/// This `ResponseBuilder` will be left in a useless state.
|
||||||
@ -238,7 +221,12 @@ impl ResponseBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let head = self.head.take().expect("cannot reuse response builder");
|
let head = self.head.take().expect("cannot reuse response builder");
|
||||||
Ok(Response { head, body })
|
|
||||||
|
Ok(Response {
|
||||||
|
head,
|
||||||
|
body,
|
||||||
|
extensions: RefCell::new(Extensions::new()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate response with an empty body.
|
/// Generate response with an empty body.
|
||||||
|
@ -1,26 +1,20 @@
|
|||||||
//! Response head type and caching pool.
|
//! Response head type and caching pool.
|
||||||
|
|
||||||
use std::{
|
use std::{cell::RefCell, ops};
|
||||||
cell::{Ref, RefCell, RefMut},
|
|
||||||
ops,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{header::HeaderMap, message::Flags, ConnectionType, StatusCode, Version};
|
||||||
header::HeaderMap, message::Flags, ConnectionType, Extensions, StatusCode, Version,
|
|
||||||
};
|
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create();
|
static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ResponseHead {
|
pub struct ResponseHead {
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
pub status: StatusCode,
|
pub status: StatusCode,
|
||||||
pub headers: HeaderMap,
|
pub headers: HeaderMap,
|
||||||
pub reason: Option<&'static str>,
|
pub reason: Option<&'static str>,
|
||||||
pub(crate) extensions: RefCell<Extensions>,
|
pub(crate) flags: Flags,
|
||||||
flags: Flags,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseHead {
|
impl ResponseHead {
|
||||||
@ -33,36 +27,35 @@ impl ResponseHead {
|
|||||||
headers: HeaderMap::with_capacity(12),
|
headers: HeaderMap::with_capacity(12),
|
||||||
reason: None,
|
reason: None,
|
||||||
flags: Flags::empty(),
|
flags: Flags::empty(),
|
||||||
extensions: RefCell::new(Extensions::new()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Read the message headers.
|
/// Read the message headers.
|
||||||
|
#[inline]
|
||||||
pub fn headers(&self) -> &HeaderMap {
|
pub fn headers(&self) -> &HeaderMap {
|
||||||
&self.headers
|
&self.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Mutable reference to the message headers.
|
/// Mutable reference to the message headers.
|
||||||
|
#[inline]
|
||||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||||
&mut self.headers
|
&mut self.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Message extensions
|
/// Sets the flag that controls whether to send headers formatted as Camel-Case.
|
||||||
|
///
|
||||||
|
/// Only applicable to HTTP/1.x responses; HTTP/2 header names are always lowercase.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
pub fn set_camel_case_headers(&mut self, camel_case: bool) {
|
||||||
self.extensions.borrow()
|
if camel_case {
|
||||||
|
self.flags.insert(Flags::CAMEL_CASE);
|
||||||
|
} else {
|
||||||
|
self.flags.remove(Flags::CAMEL_CASE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutable reference to a the message's extensions
|
|
||||||
#[inline]
|
|
||||||
pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
|
||||||
self.extensions.borrow_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Set connection type of the message
|
/// Set connection type of the message
|
||||||
|
#[inline]
|
||||||
pub fn set_connection_type(&mut self, ctype: ConnectionType) {
|
pub fn set_connection_type(&mut self, ctype: ConnectionType) {
|
||||||
match ctype {
|
match ctype {
|
||||||
ConnectionType::Close => self.flags.insert(Flags::CLOSE),
|
ConnectionType::Close => self.flags.insert(Flags::CLOSE),
|
||||||
@ -121,14 +114,14 @@ impl ResponseHead {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Get response body chunking state
|
/// Get response body chunking state
|
||||||
|
#[inline]
|
||||||
pub fn chunked(&self) -> bool {
|
pub fn chunked(&self) -> bool {
|
||||||
!self.flags.contains(Flags::NO_CHUNKING)
|
!self.flags.contains(Flags::NO_CHUNKING)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Set no chunking for payload
|
/// Set no chunking for payload
|
||||||
|
#[inline]
|
||||||
pub fn no_chunking(&mut self, val: bool) {
|
pub fn no_chunking(&mut self, val: bool) {
|
||||||
if val {
|
if val {
|
||||||
self.flags.insert(Flags::NO_CHUNKING);
|
self.flags.insert(Flags::NO_CHUNKING);
|
||||||
@ -171,7 +164,7 @@ impl Drop for BoxedResponseHead {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request's objects pool
|
/// Response head object pool.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct BoxedResponsePool(#[allow(clippy::vec_box)] RefCell<Vec<Box<ResponseHead>>>);
|
pub struct BoxedResponsePool(#[allow(clippy::vec_box)] RefCell<Vec<Box<ResponseHead>>>);
|
||||||
|
|
||||||
@ -180,7 +173,7 @@ impl BoxedResponsePool {
|
|||||||
BoxedResponsePool(RefCell::new(Vec::with_capacity(128)))
|
BoxedResponsePool(RefCell::new(Vec::with_capacity(128)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get message from the pool
|
/// Get message from the pool.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_message(&self, status: StatusCode) -> BoxedResponseHead {
|
fn get_message(&self, status: StatusCode) -> BoxedResponseHead {
|
||||||
if let Some(mut head) = self.0.borrow_mut().pop() {
|
if let Some(mut head) = self.0.borrow_mut().pop() {
|
||||||
@ -196,13 +189,81 @@ impl BoxedResponsePool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Release request instance
|
/// Release request instance.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn release(&self, mut msg: Box<ResponseHead>) {
|
fn release(&self, msg: Box<ResponseHead>) {
|
||||||
let pool = &mut self.0.borrow_mut();
|
let pool = &mut self.0.borrow_mut();
|
||||||
|
|
||||||
if pool.len() < 128 {
|
if pool.len() < 128 {
|
||||||
msg.extensions.get_mut().clear();
|
|
||||||
pool.push(msg);
|
pool.push(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{
|
||||||
|
io::{Read as _, Write as _},
|
||||||
|
net,
|
||||||
|
};
|
||||||
|
|
||||||
|
use memchr::memmem;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
h1::H1Service,
|
||||||
|
header::{HeaderName, HeaderValue},
|
||||||
|
Error, Request, Response, ServiceConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn camel_case_headers() {
|
||||||
|
let mut srv = actix_http_test::test_server(|| {
|
||||||
|
H1Service::with_config(ServiceConfig::default(), |req: Request| async move {
|
||||||
|
let mut res = Response::ok();
|
||||||
|
|
||||||
|
if req.path().contains("camel") {
|
||||||
|
res.head_mut().set_camel_case_headers(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.headers_mut().insert(
|
||||||
|
HeaderName::from_static("foo-bar"),
|
||||||
|
HeaderValue::from_static("baz"),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok::<_, Error>(res)
|
||||||
|
})
|
||||||
|
.tcp()
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||||
|
let _ = stream
|
||||||
|
.write_all(b"GET /camel HTTP/1.1\r\nConnection: Close\r\n\r\n")
|
||||||
|
.unwrap();
|
||||||
|
let mut data = vec![];
|
||||||
|
let _ = stream.read_to_end(&mut data).unwrap();
|
||||||
|
assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
|
||||||
|
assert!(memmem::find(&data, b"Foo-Bar").is_some());
|
||||||
|
assert!(memmem::find(&data, b"foo-bar").is_none());
|
||||||
|
assert!(memmem::find(&data, b"Date").is_some());
|
||||||
|
assert!(memmem::find(&data, b"date").is_none());
|
||||||
|
assert!(memmem::find(&data, b"Content-Length").is_some());
|
||||||
|
assert!(memmem::find(&data, b"content-length").is_none());
|
||||||
|
|
||||||
|
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||||
|
let _ = stream
|
||||||
|
.write_all(b"GET /lower HTTP/1.1\r\nConnection: Close\r\n\r\n")
|
||||||
|
.unwrap();
|
||||||
|
let mut data = vec![];
|
||||||
|
let _ = stream.read_to_end(&mut data).unwrap();
|
||||||
|
assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
|
||||||
|
assert!(memmem::find(&data, b"Foo-Bar").is_none());
|
||||||
|
assert!(memmem::find(&data, b"foo-bar").is_some());
|
||||||
|
assert!(memmem::find(&data, b"Date").is_none());
|
||||||
|
assert!(memmem::find(&data, b"date").is_some());
|
||||||
|
assert!(memmem::find(&data, b"Content-Length").is_none());
|
||||||
|
assert!(memmem::find(&data, b"content-length").is_some());
|
||||||
|
|
||||||
|
srv.stop().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! HTTP response.
|
//! HTTP response.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefMut},
|
cell::{Ref, RefCell, RefMut},
|
||||||
fmt, str,
|
fmt, str,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ use bytes::{Bytes, BytesMut};
|
|||||||
use bytestring::ByteString;
|
use bytestring::ByteString;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BoxBody, MessageBody},
|
body::{BoxBody, EitherBody, MessageBody},
|
||||||
header::{self, HeaderMap, TryIntoHeaderValue},
|
header::{self, HeaderMap, TryIntoHeaderValue},
|
||||||
responses::BoxedResponseHead,
|
responses::BoxedResponseHead,
|
||||||
Error, Extensions, ResponseBuilder, ResponseHead, StatusCode,
|
Error, Extensions, ResponseBuilder, ResponseHead, StatusCode,
|
||||||
@ -19,6 +19,7 @@ use crate::{
|
|||||||
pub struct Response<B> {
|
pub struct Response<B> {
|
||||||
pub(crate) head: BoxedResponseHead,
|
pub(crate) head: BoxedResponseHead,
|
||||||
pub(crate) body: B,
|
pub(crate) body: B,
|
||||||
|
pub(crate) extensions: RefCell<Extensions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response<BoxBody> {
|
impl Response<BoxBody> {
|
||||||
@ -28,6 +29,7 @@ impl Response<BoxBody> {
|
|||||||
Response {
|
Response {
|
||||||
head: BoxedResponseHead::new(status),
|
head: BoxedResponseHead::new(status),
|
||||||
body: BoxBody::new(()),
|
body: BoxBody::new(()),
|
||||||
|
extensions: RefCell::new(Extensions::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +76,7 @@ impl<B> Response<B> {
|
|||||||
Response {
|
Response {
|
||||||
head: BoxedResponseHead::new(status),
|
head: BoxedResponseHead::new(status),
|
||||||
body,
|
body,
|
||||||
|
extensions: RefCell::new(Extensions::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,20 +123,21 @@ impl<B> Response<B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if keep-alive is enabled.
|
/// Returns true if keep-alive is enabled.
|
||||||
|
#[inline]
|
||||||
pub fn keep_alive(&self) -> bool {
|
pub fn keep_alive(&self) -> bool {
|
||||||
self.head.keep_alive()
|
self.head.keep_alive()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the extensions of this response.
|
/// Returns a reference to the request-local data/extensions container.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
self.head.extensions.borrow()
|
self.extensions.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to the extensions of this response.
|
/// Returns a mutable reference to the request-local data/extensions container.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
||||||
self.head.extensions.borrow_mut()
|
self.extensions.borrow_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the body of this response.
|
/// Returns a reference to the body of this response.
|
||||||
@ -143,24 +147,29 @@ impl<B> Response<B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets new body.
|
/// Sets new body.
|
||||||
|
#[inline]
|
||||||
pub fn set_body<B2>(self, body: B2) -> Response<B2> {
|
pub fn set_body<B2>(self, body: B2) -> Response<B2> {
|
||||||
Response {
|
Response {
|
||||||
head: self.head,
|
head: self.head,
|
||||||
body,
|
body,
|
||||||
|
extensions: self.extensions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drops body and returns new response.
|
/// Drops body and returns new response.
|
||||||
|
#[inline]
|
||||||
pub fn drop_body(self) -> Response<()> {
|
pub fn drop_body(self) -> Response<()> {
|
||||||
self.set_body(())
|
self.set_body(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets new body, returning new response and previous body value.
|
/// Sets new body, returning new response and previous body value.
|
||||||
|
#[inline]
|
||||||
pub(crate) fn replace_body<B2>(self, body: B2) -> (Response<B2>, B) {
|
pub(crate) fn replace_body<B2>(self, body: B2) -> (Response<B2>, B) {
|
||||||
(
|
(
|
||||||
Response {
|
Response {
|
||||||
head: self.head,
|
head: self.head,
|
||||||
body,
|
body,
|
||||||
|
extensions: self.extensions,
|
||||||
},
|
},
|
||||||
self.body,
|
self.body,
|
||||||
)
|
)
|
||||||
@ -171,11 +180,15 @@ impl<B> Response<B> {
|
|||||||
/// # Implementation Notes
|
/// # Implementation Notes
|
||||||
/// Due to internal performance optimizations, the first element of the returned tuple is a
|
/// Due to internal performance optimizations, the first element of the returned tuple is a
|
||||||
/// `Response` as well but only contains the head of the response this was called on.
|
/// `Response` as well but only contains the head of the response this was called on.
|
||||||
|
#[inline]
|
||||||
pub fn into_parts(self) -> (Response<()>, B) {
|
pub fn into_parts(self) -> (Response<()>, B) {
|
||||||
self.replace_body(())
|
self.replace_body(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns new response with mapped body.
|
/// Map the current body type to another using a closure, returning a new response.
|
||||||
|
///
|
||||||
|
/// Closure receives the response head and the current body type.
|
||||||
|
#[inline]
|
||||||
pub fn map_body<F, B2>(mut self, f: F) -> Response<B2>
|
pub fn map_body<F, B2>(mut self, f: F) -> Response<B2>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut ResponseHead, B) -> B2,
|
F: FnOnce(&mut ResponseHead, B) -> B2,
|
||||||
@ -185,9 +198,11 @@ impl<B> Response<B> {
|
|||||||
Response {
|
Response {
|
||||||
head: self.head,
|
head: self.head,
|
||||||
body,
|
body,
|
||||||
|
extensions: self.extensions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Map the current body to a type-erased `BoxBody`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn map_into_boxed_body(self) -> Response<BoxBody>
|
pub fn map_into_boxed_body(self) -> Response<BoxBody>
|
||||||
where
|
where
|
||||||
@ -196,7 +211,8 @@ impl<B> Response<B> {
|
|||||||
self.map_body(|_, body| body.boxed())
|
self.map_body(|_, body| body.boxed())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns body, consuming this response.
|
/// Returns the response body, dropping all other parts.
|
||||||
|
#[inline]
|
||||||
pub fn into_body(self) -> B {
|
pub fn into_body(self) -> B {
|
||||||
self.body
|
self.body
|
||||||
}
|
}
|
||||||
@ -239,9 +255,9 @@ impl<I: Into<Response<BoxBody>>, E: Into<Error>> From<Result<I, E>> for Response
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ResponseBuilder> for Response<BoxBody> {
|
impl From<ResponseBuilder> for Response<EitherBody<()>> {
|
||||||
fn from(mut builder: ResponseBuilder) -> Self {
|
fn from(mut builder: ResponseBuilder) -> Self {
|
||||||
builder.finish().map_into_boxed_body()
|
builder.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,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);
|
||||||
|
@ -19,9 +19,8 @@ use pin_project_lite::pin_project;
|
|||||||
use crate::{
|
use crate::{
|
||||||
body::{BoxBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
builder::HttpServiceBuilder,
|
builder::HttpServiceBuilder,
|
||||||
config::{KeepAlive, ServiceConfig},
|
|
||||||
error::DispatchError,
|
error::DispatchError,
|
||||||
h1, h2, ConnectCallback, OnConnectData, Protocol, Request, Response,
|
h1, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol.
|
/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol.
|
||||||
@ -43,9 +42,9 @@ where
|
|||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create builder for `HttpService` instance.
|
/// Constructs builder for `HttpService` instance.
|
||||||
pub fn build() -> HttpServiceBuilder<T, S> {
|
pub fn build() -> HttpServiceBuilder<T, S> {
|
||||||
HttpServiceBuilder::new()
|
HttpServiceBuilder::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,12 +57,10 @@ where
|
|||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create new `HttpService` instance.
|
/// Constructs new `HttpService` instance from service with default config.
|
||||||
pub fn new<F: IntoServiceFactory<S, Request>>(service: F) -> Self {
|
pub fn new<F: IntoServiceFactory<S, Request>>(service: F) -> Self {
|
||||||
let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0, false, None);
|
|
||||||
|
|
||||||
HttpService {
|
HttpService {
|
||||||
cfg,
|
cfg: ServiceConfig::default(),
|
||||||
srv: service.into_factory(),
|
srv: service.into_factory(),
|
||||||
expect: h1::ExpectHandler,
|
expect: h1::ExpectHandler,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
@ -72,7 +69,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create new `HttpService` instance with config.
|
/// Constructs new `HttpService` instance from config and service.
|
||||||
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
|
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
service: F,
|
service: F,
|
||||||
@ -97,11 +94,10 @@ where
|
|||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
/// Provide service for `EXPECT: 100-Continue` support.
|
/// Sets service for `Expect: 100-Continue` handling.
|
||||||
///
|
///
|
||||||
/// Service get called with request that contains `EXPECT` header.
|
/// An expect service is called with requests that contain an `Expect` header. A successful
|
||||||
/// Service must return request in case of success, in that case
|
/// response type is also a request which will be forwarded to the main service.
|
||||||
/// request will be forwarded to main service.
|
|
||||||
pub fn expect<X1>(self, expect: X1) -> HttpService<T, S, B, X1, U>
|
pub fn expect<X1>(self, expect: X1) -> HttpService<T, S, B, X1, U>
|
||||||
where
|
where
|
||||||
X1: ServiceFactory<Request, Config = (), Response = Request>,
|
X1: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
@ -118,10 +114,10 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provide service for custom `Connection: UPGRADE` support.
|
/// Sets service for custom `Connection: Upgrade` handling.
|
||||||
///
|
///
|
||||||
/// If service is provided then normal requests handling get halted
|
/// If service is provided then normal requests handling get halted and this service get called
|
||||||
/// and this service get called with original request and framed object.
|
/// with original request and framed object.
|
||||||
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> HttpService<T, S, B, X, U1>
|
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> HttpService<T, S, B, X, U1>
|
||||||
where
|
where
|
||||||
U1: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
|
U1: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
|
||||||
@ -506,10 +502,11 @@ where
|
|||||||
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||||
|
|
||||||
match proto {
|
match proto {
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
Protocol::Http2 => HttpServiceHandlerResponse {
|
Protocol::Http2 => HttpServiceHandlerResponse {
|
||||||
state: State::H2Handshake {
|
state: State::H2Handshake {
|
||||||
handshake: Some((
|
handshake: Some((
|
||||||
h2::handshake_with_timeout(io, &self.cfg),
|
crate::h2::handshake_with_timeout(io, &self.cfg),
|
||||||
self.cfg.clone(),
|
self.cfg.clone(),
|
||||||
self.flow.clone(),
|
self.flow.clone(),
|
||||||
conn_data,
|
conn_data,
|
||||||
@ -518,6 +515,11 @@ where
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[cfg(not(feature = "http2"))]
|
||||||
|
Protocol::Http2 => {
|
||||||
|
panic!("HTTP/2 support is disabled (enable with the `http2` feature flag)")
|
||||||
|
}
|
||||||
|
|
||||||
Protocol::Http1 => HttpServiceHandlerResponse {
|
Protocol::Http1 => HttpServiceHandlerResponse {
|
||||||
state: State::H1 {
|
state: State::H1 {
|
||||||
dispatcher: h1::Dispatcher::new(
|
dispatcher: h1::Dispatcher::new(
|
||||||
@ -535,6 +537,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "http2"))]
|
||||||
pin_project! {
|
pin_project! {
|
||||||
#[project = StateProj]
|
#[project = StateProj]
|
||||||
enum State<T, S, B, X, U>
|
enum State<T, S, B, X, U>
|
||||||
@ -556,10 +559,37 @@ pin_project! {
|
|||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
H1 { #[pin] dispatcher: h1::Dispatcher<T, S, B, X, U> },
|
H1 { #[pin] dispatcher: h1::Dispatcher<T, S, B, X, U> },
|
||||||
H2 { #[pin] dispatcher: h2::Dispatcher<T, S, B, X, U> },
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pin_project! {
|
||||||
|
#[project = StateProj]
|
||||||
|
enum State<T, S, B, X, U>
|
||||||
|
where
|
||||||
|
T: AsyncRead,
|
||||||
|
T: AsyncWrite,
|
||||||
|
T: Unpin,
|
||||||
|
|
||||||
|
S: Service<Request>,
|
||||||
|
S::Future: 'static,
|
||||||
|
S::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
|
B: MessageBody,
|
||||||
|
|
||||||
|
X: Service<Request, Response = Request>,
|
||||||
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
|
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
|
U::Error: fmt::Display,
|
||||||
|
{
|
||||||
|
H1 { #[pin] dispatcher: h1::Dispatcher<T, S, B, X, U> },
|
||||||
|
|
||||||
|
H2 { #[pin] dispatcher: crate::h2::Dispatcher<T, S, B, X, U> },
|
||||||
|
|
||||||
H2Handshake {
|
H2Handshake {
|
||||||
handshake: Option<(
|
handshake: Option<(
|
||||||
h2::HandshakeWithTimeout<T>,
|
crate::h2::HandshakeWithTimeout<T>,
|
||||||
ServiceConfig,
|
ServiceConfig,
|
||||||
Rc<HttpFlow<S, X, U>>,
|
Rc<HttpFlow<S, X, U>>,
|
||||||
OnConnectData,
|
OnConnectData,
|
||||||
@ -618,21 +648,25 @@ where
|
|||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
match self.as_mut().project().state.project() {
|
match self.as_mut().project().state.project() {
|
||||||
StateProj::H1 { dispatcher } => dispatcher.poll(cx),
|
StateProj::H1 { dispatcher } => dispatcher.poll(cx),
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
StateProj::H2 { dispatcher } => dispatcher.poll(cx),
|
StateProj::H2 { dispatcher } => dispatcher.poll(cx),
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
StateProj::H2Handshake { handshake: data } => {
|
StateProj::H2Handshake { handshake: data } => {
|
||||||
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
|
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
|
||||||
Ok((conn, timer)) => {
|
Ok((conn, timer)) => {
|
||||||
let (_, config, flow, conn_data, peer_addr) = data.take().unwrap();
|
let (_, config, flow, conn_data, peer_addr) = data.take().unwrap();
|
||||||
|
|
||||||
self.as_mut().project().state.set(State::H2 {
|
self.as_mut().project().state.set(State::H2 {
|
||||||
dispatcher: h2::Dispatcher::new(
|
dispatcher: crate::h2::Dispatcher::new(
|
||||||
conn, flow, config, peer_addr, conn_data, timer,
|
conn, flow, config, peer_addr, conn_data, timer,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
trace!("H2 handshake error: {}", err);
|
log::trace!("H2 handshake error: {}", err);
|
||||||
Poll::Ready(Err(err))
|
Poll::Ready(Err(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Various testing helpers for use in internal and app tests.
|
//! Various testing helpers for use in internal and app tests.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefCell},
|
cell::{Ref, RefCell, RefMut},
|
||||||
io::{self, Read, Write},
|
io::{self, Read, Write},
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
@ -157,10 +157,11 @@ fn parts(parts: &mut Option<Inner>) -> &mut Inner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Async I/O test buffer.
|
/// Async I/O test buffer.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct TestBuffer {
|
pub struct TestBuffer {
|
||||||
pub read_buf: BytesMut,
|
pub read_buf: Rc<RefCell<BytesMut>>,
|
||||||
pub write_buf: BytesMut,
|
pub write_buf: Rc<RefCell<BytesMut>>,
|
||||||
pub err: Option<io::Error>,
|
pub err: Option<Rc<io::Error>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestBuffer {
|
impl TestBuffer {
|
||||||
@ -170,34 +171,69 @@ impl TestBuffer {
|
|||||||
T: Into<BytesMut>,
|
T: Into<BytesMut>,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
read_buf: data.into(),
|
read_buf: Rc::new(RefCell::new(data.into())),
|
||||||
write_buf: BytesMut::new(),
|
write_buf: Rc::new(RefCell::new(BytesMut::new())),
|
||||||
err: None,
|
err: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// intentionally not using Clone trait
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
read_buf: self.read_buf.clone(),
|
||||||
|
write_buf: self.write_buf.clone(),
|
||||||
|
err: self.err.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create new empty `TestBuffer` instance.
|
/// Create new empty `TestBuffer` instance.
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self::new("")
|
Self::new("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn read_buf_slice(&self) -> Ref<'_, [u8]> {
|
||||||
|
Ref::map(self.read_buf.borrow(), |b| b.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn read_buf_slice_mut(&self) -> RefMut<'_, [u8]> {
|
||||||
|
RefMut::map(self.read_buf.borrow_mut(), |b| b.as_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn write_buf_slice(&self) -> Ref<'_, [u8]> {
|
||||||
|
Ref::map(self.write_buf.borrow(), |b| b.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn write_buf_slice_mut(&self) -> RefMut<'_, [u8]> {
|
||||||
|
RefMut::map(self.write_buf.borrow_mut(), |b| b.as_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn take_write_buf(&self) -> Bytes {
|
||||||
|
self.write_buf.borrow_mut().split().freeze()
|
||||||
|
}
|
||||||
|
|
||||||
/// Add data to read buffer.
|
/// Add data to read buffer.
|
||||||
pub fn extend_read_buf<T: AsRef<[u8]>>(&mut self, data: T) {
|
pub fn extend_read_buf<T: AsRef<[u8]>>(&mut self, data: T) {
|
||||||
self.read_buf.extend_from_slice(data.as_ref())
|
self.read_buf.borrow_mut().extend_from_slice(data.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl io::Read for TestBuffer {
|
impl io::Read for TestBuffer {
|
||||||
fn read(&mut self, dst: &mut [u8]) -> Result<usize, io::Error> {
|
fn read(&mut self, dst: &mut [u8]) -> Result<usize, io::Error> {
|
||||||
if self.read_buf.is_empty() {
|
if self.read_buf.borrow().is_empty() {
|
||||||
if self.err.is_some() {
|
if self.err.is_some() {
|
||||||
Err(self.err.take().unwrap())
|
Err(Rc::try_unwrap(self.err.take().unwrap()).unwrap())
|
||||||
} else {
|
} else {
|
||||||
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
|
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let size = std::cmp::min(self.read_buf.len(), dst.len());
|
let size = std::cmp::min(self.read_buf.borrow().len(), dst.len());
|
||||||
let b = self.read_buf.split_to(size);
|
let b = self.read_buf.borrow_mut().split_to(size);
|
||||||
dst[..size].copy_from_slice(&b);
|
dst[..size].copy_from_slice(&b);
|
||||||
Ok(size)
|
Ok(size)
|
||||||
}
|
}
|
||||||
@ -206,7 +242,7 @@ impl io::Read for TestBuffer {
|
|||||||
|
|
||||||
impl io::Write for TestBuffer {
|
impl io::Write for TestBuffer {
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
self.write_buf.extend(buf);
|
self.write_buf.borrow_mut().extend(buf);
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,9 +3,11 @@ use bitflags::bitflags;
|
|||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use bytestring::ByteString;
|
use bytestring::ByteString;
|
||||||
|
|
||||||
use super::frame::Parser;
|
use super::{
|
||||||
use super::proto::{CloseReason, OpCode};
|
frame::Parser,
|
||||||
use super::ProtocolError;
|
proto::{CloseReason, OpCode},
|
||||||
|
ProtocolError,
|
||||||
|
};
|
||||||
|
|
||||||
/// A WebSocket message.
|
/// A WebSocket message.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -251,7 +253,7 @@ impl Decoder for Codec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("Unfinished fragment {:?}", opcode);
|
log::error!("Unfinished fragment {:?}", opcode);
|
||||||
Err(ProtocolError::ContinuationFragment(opcode))
|
Err(ProtocolError::ContinuationFragment(opcode))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use std::future::Future;
|
use std::{
|
||||||
use std::pin::Pin;
|
future::Future,
|
||||||
use std::task::{Context, Poll};
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
use actix_service::{IntoService, Service};
|
use actix_service::{IntoService, Service};
|
||||||
|
@ -3,9 +3,11 @@ use std::convert::TryFrom;
|
|||||||
use bytes::{Buf, BufMut, BytesMut};
|
use bytes::{Buf, BufMut, BytesMut};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
use crate::ws::mask::apply_mask;
|
use super::{
|
||||||
use crate::ws::proto::{CloseCode, CloseReason, OpCode};
|
mask::apply_mask,
|
||||||
use crate::ws::ProtocolError;
|
proto::{CloseCode, CloseReason, OpCode},
|
||||||
|
ProtocolError,
|
||||||
|
};
|
||||||
|
|
||||||
/// A struct representing a WebSocket frame.
|
/// A struct representing a WebSocket frame.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -31,7 +31,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_h1_v2() {
|
async fn h1_v2() {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.finish(|_| future::ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.finish(|_| future::ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -59,7 +59,7 @@ async fn test_h1_v2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_connection_close() {
|
async fn connection_close() {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.finish(|_| future::ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.finish(|_| future::ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -73,7 +73,7 @@ async fn test_connection_close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_with_query_parameter() {
|
async fn with_query_parameter() {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.finish(|req: Request| async move {
|
.finish(|req: Request| async move {
|
||||||
@ -104,7 +104,7 @@ impl From<ExpectFailed> for Response<BoxBody> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_expect() {
|
async fn h1_expect() {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.expect(|req: Request| async {
|
.expect(|req: Request| async {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::io;
|
use std::{io, time::Duration};
|
||||||
|
|
||||||
use actix_http::{error::Error, HttpService, Response};
|
use actix_http::{error::Error, HttpService, Response};
|
||||||
use actix_server::Server;
|
use actix_server::Server;
|
||||||
@ -19,7 +19,7 @@ async fn h2_ping_pong() -> io::Result<()> {
|
|||||||
.workers(1)
|
.workers(1)
|
||||||
.listen("h2_ping_pong", lst, || {
|
.listen("h2_ping_pong", lst, || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.keep_alive(3)
|
.keep_alive(Duration::from_secs(3))
|
||||||
.h2(|_| async { Ok::<_, Error>(Response::ok()) })
|
.h2(|_| async { Ok::<_, Error>(Response::ok()) })
|
||||||
.tcp()
|
.tcp()
|
||||||
})?
|
})?
|
||||||
@ -92,10 +92,10 @@ async fn h2_handshake_timeout() -> io::Result<()> {
|
|||||||
.workers(1)
|
.workers(1)
|
||||||
.listen("h2_ping_pong", lst, || {
|
.listen("h2_ping_pong", lst, || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.keep_alive(30)
|
.keep_alive(Duration::from_secs(30))
|
||||||
// set first request timeout to 5 seconds.
|
// set first request timeout to 5 seconds.
|
||||||
// this is the timeout used for http2 handshake.
|
// this is the timeout used for http2 handshake.
|
||||||
.client_timeout(5000)
|
.client_request_timeout(Duration::from_secs(5))
|
||||||
.h2(|_| async { Ok::<_, Error>(Response::ok()) })
|
.h2(|_| async { Ok::<_, Error>(Response::ok()) })
|
||||||
.tcp()
|
.tcp()
|
||||||
})?
|
})?
|
||||||
|
@ -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| {
|
||||||
@ -245,7 +245,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 +309,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 +326,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 +352,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 +377,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 +398,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 +420,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 +447,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 +486,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 +503,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 +524,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 +546,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 +572,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());
|
||||||
|
@ -2,7 +2,7 @@ use std::{
|
|||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
net, thread,
|
net, thread,
|
||||||
time::Duration,
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
@ -22,12 +22,12 @@ use futures_util::{
|
|||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1() {
|
async fn h1_basic() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.keep_alive(KeepAlive::Disabled)
|
.keep_alive(KeepAlive::Disabled)
|
||||||
.client_timeout(1000)
|
.client_request_timeout(Duration::from_secs(1))
|
||||||
.client_disconnect(1000)
|
.client_disconnect_timeout(Duration::from_secs(1))
|
||||||
.h1(|req: Request| {
|
.h1(|req: Request| {
|
||||||
assert!(req.peer_addr().is_some());
|
assert!(req.peer_addr().is_some());
|
||||||
ok::<_, Infallible>(Response::ok())
|
ok::<_, Infallible>(Response::ok())
|
||||||
@ -43,12 +43,12 @@ async fn test_h1() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_2() {
|
async fn h1_2() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.keep_alive(KeepAlive::Disabled)
|
.keep_alive(KeepAlive::Disabled)
|
||||||
.client_timeout(1000)
|
.client_request_timeout(Duration::from_secs(1))
|
||||||
.client_disconnect(1000)
|
.client_disconnect_timeout(Duration::from_secs(1))
|
||||||
.finish(|req: Request| {
|
.finish(|req: Request| {
|
||||||
assert!(req.peer_addr().is_some());
|
assert!(req.peer_addr().is_some());
|
||||||
assert_eq!(req.version(), http::Version::HTTP_11);
|
assert_eq!(req.version(), http::Version::HTTP_11);
|
||||||
@ -75,7 +75,7 @@ impl From<ExpectFailed> for Response<BoxBody> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_expect_continue() {
|
async fn expect_continue() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.expect(fn_service(|req: Request| {
|
.expect(fn_service(|req: Request| {
|
||||||
@ -106,7 +106,7 @@ async fn test_expect_continue() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_expect_continue_h1() {
|
async fn expect_continue_h1() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.expect(fn_service(|req: Request| {
|
.expect(fn_service(|req: Request| {
|
||||||
@ -139,7 +139,7 @@ async fn test_expect_continue_h1() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_chunked_payload() {
|
async fn chunked_payload() {
|
||||||
let chunk_sizes = vec![32768, 32, 32768];
|
let chunk_sizes = vec![32768, 32, 32768];
|
||||||
let total_size: usize = chunk_sizes.iter().sum();
|
let total_size: usize = chunk_sizes.iter().sum();
|
||||||
|
|
||||||
@ -197,26 +197,43 @@ async fn test_chunked_payload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_slow_request() {
|
async fn slow_request_408() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(100)
|
.client_request_timeout(Duration::from_millis(200))
|
||||||
|
.keep_alive(Duration::from_secs(2))
|
||||||
.finish(|_| ok::<_, Infallible>(Response::ok()))
|
.finish(|_| ok::<_, Infallible>(Response::ok()))
|
||||||
.tcp()
|
.tcp()
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||||
let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n");
|
let _ = stream.write_all(b"GET /test HTTP/1.1\r\n");
|
||||||
let mut data = String::new();
|
let mut data = String::new();
|
||||||
let _ = stream.read_to_string(&mut data);
|
let _ = stream.read_to_string(&mut data);
|
||||||
assert!(data.starts_with("HTTP/1.1 408 Request Timeout"));
|
assert!(
|
||||||
|
data.starts_with("HTTP/1.1 408 Request Timeout"),
|
||||||
|
"response was not 408: {}",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
|
let diff = start.elapsed();
|
||||||
|
|
||||||
|
if diff < Duration::from_secs(1) {
|
||||||
|
// test success
|
||||||
|
} else if diff < Duration::from_secs(3) {
|
||||||
|
panic!("request seems to have wrongly timed-out according to keep-alive");
|
||||||
|
} else {
|
||||||
|
panic!("request took way too long to time out");
|
||||||
|
}
|
||||||
|
|
||||||
srv.stop().await;
|
srv.stop().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_http1_malformed_request() {
|
async fn http1_malformed_request() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
||||||
@ -234,7 +251,7 @@ async fn test_http1_malformed_request() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_http1_keepalive() {
|
async fn http1_keepalive() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
||||||
@ -257,23 +274,25 @@ async fn test_http1_keepalive() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_http1_keepalive_timeout() {
|
async fn http1_keepalive_timeout() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.keep_alive(1)
|
.keep_alive(Duration::from_secs(1))
|
||||||
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
||||||
.tcp()
|
.tcp()
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||||
let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n");
|
|
||||||
let mut data = vec![0; 1024];
|
let _ = stream.write_all(b"GET /test HTTP/1.1\r\n\r\n");
|
||||||
|
let mut data = vec![0; 256];
|
||||||
let _ = stream.read(&mut data);
|
let _ = stream.read(&mut data);
|
||||||
assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
|
assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
|
||||||
|
|
||||||
thread::sleep(Duration::from_millis(1100));
|
thread::sleep(Duration::from_millis(1100));
|
||||||
|
|
||||||
let mut data = vec![0; 1024];
|
let mut data = vec![0; 256];
|
||||||
let res = stream.read(&mut data).unwrap();
|
let res = stream.read(&mut data).unwrap();
|
||||||
assert_eq!(res, 0);
|
assert_eq!(res, 0);
|
||||||
|
|
||||||
@ -281,7 +300,7 @@ async fn test_http1_keepalive_timeout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_http1_keepalive_close() {
|
async fn http1_keepalive_close() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
||||||
@ -303,7 +322,7 @@ async fn test_http1_keepalive_close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_http10_keepalive_default_close() {
|
async fn http10_keepalive_default_close() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
||||||
@ -325,7 +344,7 @@ async fn test_http10_keepalive_default_close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_http10_keepalive() {
|
async fn http10_keepalive() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
.h1(|_| ok::<_, Infallible>(Response::ok()))
|
||||||
@ -354,7 +373,7 @@ async fn test_http10_keepalive() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_http1_keepalive_disabled() {
|
async fn http1_keepalive_disabled() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.keep_alive(KeepAlive::Disabled)
|
.keep_alive(KeepAlive::Disabled)
|
||||||
@ -377,7 +396,7 @@ async fn test_http1_keepalive_disabled() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_content_length() {
|
async fn content_length() {
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
header::{HeaderName, HeaderValue},
|
header::{HeaderName, HeaderValue},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
@ -426,7 +445,7 @@ async fn test_content_length() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_headers() {
|
async fn h1_headers() {
|
||||||
let data = STR.repeat(10);
|
let data = STR.repeat(10);
|
||||||
let data2 = data.clone();
|
let data2 = data.clone();
|
||||||
|
|
||||||
@ -492,7 +511,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_h1_body() {
|
async fn h1_body() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -511,7 +530,7 @@ async fn test_h1_body() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_head_empty() {
|
async fn h1_head_empty() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -538,7 +557,7 @@ async fn test_h1_head_empty() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_head_binary() {
|
async fn h1_head_binary() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -565,7 +584,7 @@ async fn test_h1_head_binary() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_head_binary2() {
|
async fn h1_head_binary2() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
.h1(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
|
||||||
@ -588,7 +607,7 @@ async fn test_h1_head_binary2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_body_length() {
|
async fn h1_body_length() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| {
|
.h1(|_| {
|
||||||
@ -612,7 +631,7 @@ async fn test_h1_body_length() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_body_chunked_explicit() {
|
async fn h1_body_chunked_explicit() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| {
|
.h1(|_| {
|
||||||
@ -649,7 +668,7 @@ async fn test_h1_body_chunked_explicit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_body_chunked_implicit() {
|
async fn h1_body_chunked_implicit() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| {
|
.h1(|_| {
|
||||||
@ -680,7 +699,7 @@ async fn test_h1_body_chunked_implicit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_response_http_error_handling() {
|
async fn h1_response_http_error_handling() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(fn_service(|_| {
|
.h1(fn_service(|_| {
|
||||||
@ -719,7 +738,7 @@ impl From<BadRequest> for Response<BoxBody> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_service_error() {
|
async fn h1_service_error() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| err::<Response<()>, _>(BadRequest))
|
.h1(|_| err::<Response<()>, _>(BadRequest))
|
||||||
@ -738,7 +757,7 @@ async fn test_h1_service_error() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h1_on_connect() {
|
async fn h1_on_connect() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.on_connect_ext(|_, data| {
|
.on_connect_ext(|_, data| {
|
||||||
@ -761,7 +780,7 @@ async fn test_h1_on_connect() {
|
|||||||
/// Tests compliance with 304 Not Modified spec in RFC 7232 §4.1.
|
/// Tests compliance with 304 Not Modified spec in RFC 7232 §4.1.
|
||||||
/// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1
|
/// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_not_modified_spec_h1() {
|
async fn not_modified_spec_h1() {
|
||||||
// TODO: this test needing a few seconds to complete reveals some weirdness with either the
|
// TODO: this test needing a few seconds to complete reveals some weirdness with either the
|
||||||
// dispatcher or the client, though similar hangs occur on other tests in this file, only
|
// dispatcher or the client, though similar hangs occur on other tests in this file, only
|
||||||
// succeeding, it seems, because of the keepalive timer
|
// succeeding, it seems, because of the keepalive timer
|
||||||
|
@ -109,7 +109,7 @@ async fn service(msg: Frame) -> Result<Message, Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_simple() {
|
async fn simple() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.upgrade(fn_factory(|| async {
|
.upgrade(fn_factory(|| async {
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.0-beta.13 - 2022-01-31
|
||||||
|
- No significant changes since `0.4.0-beta.12`.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.12 - 2022-01-04
|
## 0.4.0-beta.12 - 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-multipart"
|
name = "actix-multipart"
|
||||||
version = "0.4.0-beta.12"
|
version = "0.4.0-beta.13"
|
||||||
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"]
|
||||||
@ -15,7 +15,7 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = { version = "4.0.0-beta.20", default-features = false }
|
actix-web = { version = "4.0.0-rc.3", 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-beta.18"
|
actix-http = "3.0.0-rc.3"
|
||||||
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.12)
|
[](https://docs.rs/actix-multipart/0.4.0-beta.13)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
[](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.12)
|
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.13)
|
||||||
[](https://crates.io/crates/actix-multipart)
|
[](https://crates.io/crates/actix-multipart)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -3,6 +3,25 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0-rc.3 - 2022-01-31
|
||||||
|
- Remove unused `ResourceInfo`. [#2612]
|
||||||
|
- Add `RouterBuilder::push`. [#2612]
|
||||||
|
- Change signature of `ResourceDef::capture_match_info_fn` to remove `user_data` parameter. [#2612]
|
||||||
|
- Replace `Option<U>` with `U` in `Router` API. [#2612]
|
||||||
|
- Relax bounds on `Router::recognize*` and `ResourceDef::capture_match_info`. [#2612]
|
||||||
|
- `Quoter::requote` now returns `Option<Vec<u8>>`. [#2613]
|
||||||
|
|
||||||
|
[#2612]: https://github.com/actix/actix-web/pull/2612
|
||||||
|
[#2613]: https://github.com/actix/actix-web/pull/2613
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0-rc.2 - 2022-01-21
|
||||||
|
- Add `Path::as_str`. [#2590]
|
||||||
|
- Deprecate `Path::path`. [#2590]
|
||||||
|
|
||||||
|
[#2590]: https://github.com/actix/actix-web/pull/2590
|
||||||
|
|
||||||
|
|
||||||
## 0.5.0-rc.1 - 2022-01-14
|
## 0.5.0-rc.1 - 2022-01-14
|
||||||
- `Resource` trait now have an associated type, `Path`, instead of the generic parameter. [#2568]
|
- `Resource` trait now have an associated type, `Path`, instead of the generic parameter. [#2568]
|
||||||
- `Resource` is now implemented for `&mut Path<_>` and `RefMut<Path<_>>`. [#2568]
|
- `Resource` is now implemented for `&mut Path<_>` and `RefMut<Path<_>>`. [#2568]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.5.0-rc.1"
|
version = "0.5.0-rc.3"
|
||||||
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>",
|
||||||
|
@ -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) {
|
||||||
|
@ -52,7 +52,7 @@ macro_rules! parse_value {
|
|||||||
V: Visitor<'de>,
|
V: Visitor<'de>,
|
||||||
{
|
{
|
||||||
let decoded = FULL_QUOTER
|
let decoded = FULL_QUOTER
|
||||||
.with(|q| q.requote(self.value.as_bytes()))
|
.with(|q| q.requote_str_lossy(self.value))
|
||||||
.map(Cow::Owned)
|
.map(Cow::Owned)
|
||||||
.unwrap_or(Cow::Borrowed(self.value));
|
.unwrap_or(Cow::Borrowed(self.value));
|
||||||
|
|
||||||
@ -332,7 +332,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
|||||||
where
|
where
|
||||||
V: Visitor<'de>,
|
V: Visitor<'de>,
|
||||||
{
|
{
|
||||||
match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) {
|
match FULL_QUOTER.with(|q| q.requote_str_lossy(self.value)) {
|
||||||
Some(s) => visitor.visit_string(s),
|
Some(s) => visitor.visit_string(s),
|
||||||
None => visitor.visit_borrowed_str(self.value),
|
None => visitor.visit_borrowed_str(self.value),
|
||||||
}
|
}
|
||||||
@ -342,7 +342,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
|||||||
where
|
where
|
||||||
V: Visitor<'de>,
|
V: Visitor<'de>,
|
||||||
{
|
{
|
||||||
match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) {
|
match FULL_QUOTER.with(|q| q.requote_str_lossy(self.value)) {
|
||||||
Some(s) => visitor.visit_byte_buf(s.into()),
|
Some(s) => visitor.visit_byte_buf(s.into()),
|
||||||
None => visitor.visit_borrowed_bytes(self.value.as_bytes()),
|
None => visitor.visit_borrowed_bytes(self.value.as_bytes()),
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ pub use self::pattern::{IntoPatterns, Patterns};
|
|||||||
pub use self::quoter::Quoter;
|
pub use self::quoter::Quoter;
|
||||||
pub use self::resource::ResourceDef;
|
pub use self::resource::ResourceDef;
|
||||||
pub use self::resource_path::{Resource, ResourcePath};
|
pub use self::resource_path::{Resource, ResourcePath};
|
||||||
pub use self::router::{ResourceInfo, Router, RouterBuilder};
|
pub use self::router::{ResourceId, Router, RouterBuilder};
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
pub use self::url::Url;
|
pub use self::url::Url;
|
||||||
|
@ -37,19 +37,39 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get reference to inner path instance.
|
/// Returns reference to inner path instance.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_ref(&self) -> &T {
|
pub fn get_ref(&self) -> &T {
|
||||||
&self.path
|
&self.path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get mutable reference to inner path instance.
|
/// Returns mutable reference to inner path instance.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_mut(&mut self) -> &mut T {
|
pub fn get_mut(&mut self) -> &mut T {
|
||||||
&mut self.path
|
&mut self.path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Path.
|
/// Returns full path as a string.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
profile_method!(as_str);
|
||||||
|
self.path.path()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns unprocessed part of the path.
|
||||||
|
///
|
||||||
|
/// Returns empty string if no more is to be processed.
|
||||||
|
#[inline]
|
||||||
|
pub fn unprocessed(&self) -> &str {
|
||||||
|
profile_method!(unprocessed);
|
||||||
|
// clamp skip to path length
|
||||||
|
let skip = (self.skip as usize).min(self.as_str().len());
|
||||||
|
&self.path.path()[skip..]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns unprocessed part of the path.
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[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);
|
profile_method!(path);
|
||||||
@ -66,6 +86,8 @@ 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();
|
||||||
@ -74,6 +96,8 @@ 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();
|
||||||
}
|
}
|
||||||
@ -81,6 +105,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +127,8 @@ 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())));
|
||||||
}
|
}
|
||||||
@ -136,11 +163,6 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get unprocessed part of the path
|
|
||||||
pub fn unprocessed(&self) -> &str {
|
|
||||||
&self.path.path()[(self.skip as usize)..]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get matched parameter by name.
|
/// Get matched parameter by name.
|
||||||
///
|
///
|
||||||
/// 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.
|
||||||
|
@ -64,10 +64,15 @@ impl Quoter {
|
|||||||
quoter
|
quoter
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Re-quotes... ?
|
/// Decodes safe percent-encoded sequences from `val`.
|
||||||
///
|
///
|
||||||
/// Returns `None` when no modification to the original string was required.
|
/// Returns `None` when no modification to the original byte string was required.
|
||||||
pub fn requote(&self, val: &[u8]) -> Option<String> {
|
///
|
||||||
|
/// Non-ASCII bytes are accepted as valid input.
|
||||||
|
///
|
||||||
|
/// Behavior for invalid/incomplete percent-encoding sequences is unspecified and may include
|
||||||
|
/// removing the invalid sequence from the output or passing it as-is.
|
||||||
|
pub fn requote(&self, val: &[u8]) -> Option<Vec<u8>> {
|
||||||
let mut has_pct = 0;
|
let mut has_pct = 0;
|
||||||
let mut pct = [b'%', 0, 0];
|
let mut pct = [b'%', 0, 0];
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
@ -121,7 +126,12 @@ impl Quoter {
|
|||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
cloned.map(|data| String::from_utf8_lossy(&data).into_owned())
|
cloned
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn requote_str_lossy(&self, val: &str) -> Option<String> {
|
||||||
|
self.requote(val.as_bytes())
|
||||||
|
.map(|data| String::from_utf8_lossy(&data).into_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,14 +211,29 @@ mod tests {
|
|||||||
#[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(), "/a%c");
|
assert_eq!(q.requote(b"/a%25c").unwrap(), b"/a%c");
|
||||||
assert_eq!(q.requote(b"/a%2Bc").unwrap(), "/a%2Bc");
|
assert_eq!(q.requote(b"/a%2Bc").unwrap(), b"/a%2Bc");
|
||||||
|
|
||||||
let q = Quoter::new(b"%+", b"/");
|
let q = Quoter::new(b"%+", b"/");
|
||||||
assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), "/a%b+c");
|
assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), b"/a%b+c");
|
||||||
assert_eq!(q.requote(b"/a%2fb").unwrap(), "/a%2fb");
|
assert_eq!(q.requote(b"/a%2fb").unwrap(), b"/a%2fb");
|
||||||
assert_eq!(q.requote(b"/a%2Fb").unwrap(), "/a%2Fb");
|
assert_eq!(q.requote(b"/a%2Fb").unwrap(), b"/a%2Fb");
|
||||||
assert_eq!(q.requote(b"/a%0Ab").unwrap(), "/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\xfe\xffb"), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_ascii() {
|
||||||
|
let q = Quoter::new(b"%+", b"/");
|
||||||
|
assert_eq!(q.requote(b"/a%FE\xffb").unwrap(), b"/a\xfe\xffb");
|
||||||
|
assert_eq!(q.requote(b"/a\xfe\xffb"), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_sequences() {
|
||||||
|
let q = Quoter::new(b"%+", b"/");
|
||||||
|
assert_eq!(q.requote(b"/a%2x%2X%%").unwrap(), b"/a%2x%2X");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -8,10 +8,7 @@ use std::{
|
|||||||
use firestorm::{profile_fn, profile_method, profile_section};
|
use firestorm::{profile_fn, profile_method, profile_section};
|
||||||
use regex::{escape, Regex, RegexSet};
|
use regex::{escape, Regex, RegexSet};
|
||||||
|
|
||||||
use crate::{
|
use crate::{path::PathItem, IntoPatterns, Patterns, Resource, ResourcePath};
|
||||||
path::{Path, PathItem},
|
|
||||||
IntoPatterns, Patterns, Resource, ResourcePath,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MAX_DYNAMIC_SEGMENTS: usize = 16;
|
const MAX_DYNAMIC_SEGMENTS: usize = 16;
|
||||||
|
|
||||||
@ -615,7 +612,7 @@ impl ResourceDef {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collects dynamic segment values into `path`.
|
/// Collects dynamic segment values into `resource`.
|
||||||
///
|
///
|
||||||
/// Returns `true` if `path` matches this resource.
|
/// Returns `true` if `path` matches this resource.
|
||||||
///
|
///
|
||||||
@ -635,9 +632,9 @@ impl ResourceDef {
|
|||||||
/// assert_eq!(path.get("path").unwrap(), "HEAD/Cargo.toml");
|
/// assert_eq!(path.get("path").unwrap(), "HEAD/Cargo.toml");
|
||||||
/// assert_eq!(path.unprocessed(), "");
|
/// assert_eq!(path.unprocessed(), "");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn capture_match_info<T: ResourcePath>(&self, path: &mut Path<T>) -> bool {
|
pub fn capture_match_info<R: Resource>(&self, resource: &mut R) -> bool {
|
||||||
profile_method!(capture_match_info);
|
profile_method!(capture_match_info);
|
||||||
self.capture_match_info_fn(path, |_, _| true, ())
|
self.capture_match_info_fn(resource, |_| true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collects dynamic segment values into `resource` after matching paths and executing
|
/// Collects dynamic segment values into `resource` after matching paths and executing
|
||||||
@ -655,13 +652,12 @@ impl ResourceDef {
|
|||||||
/// use actix_router::{Path, ResourceDef};
|
/// use actix_router::{Path, ResourceDef};
|
||||||
///
|
///
|
||||||
/// fn try_match(resource: &ResourceDef, path: &mut Path<&str>) -> bool {
|
/// fn try_match(resource: &ResourceDef, path: &mut Path<&str>) -> bool {
|
||||||
/// let admin_allowed = std::env::var("ADMIN_ALLOWED").ok();
|
/// let admin_allowed = std::env::var("ADMIN_ALLOWED").is_ok();
|
||||||
///
|
///
|
||||||
/// resource.capture_match_info_fn(
|
/// resource.capture_match_info_fn(
|
||||||
/// path,
|
/// path,
|
||||||
/// // when env var is not set, reject when path contains "admin"
|
/// // when env var is not set, reject when path contains "admin"
|
||||||
/// |res, admin_allowed| !res.path().contains("admin"),
|
/// |res| !(!admin_allowed && res.path().contains("admin")),
|
||||||
/// &admin_allowed
|
|
||||||
/// )
|
/// )
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@ -678,21 +674,16 @@ impl ResourceDef {
|
|||||||
/// assert!(!try_match(&resource, &mut path));
|
/// assert!(!try_match(&resource, &mut path));
|
||||||
/// assert_eq!(path.unprocessed(), "/user/admin/stars");
|
/// assert_eq!(path.unprocessed(), "/user/admin/stars");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn capture_match_info_fn<R, F, U>(
|
pub fn capture_match_info_fn<R, F>(&self, resource: &mut R, check_fn: F) -> bool
|
||||||
&self,
|
|
||||||
resource: &mut R,
|
|
||||||
check_fn: F,
|
|
||||||
user_data: U,
|
|
||||||
) -> bool
|
|
||||||
where
|
where
|
||||||
R: Resource,
|
R: Resource,
|
||||||
F: FnOnce(&R, U) -> bool,
|
F: FnOnce(&R) -> bool,
|
||||||
{
|
{
|
||||||
profile_method!(capture_match_info_fn);
|
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.path();
|
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) => {
|
||||||
@ -710,7 +701,7 @@ impl ResourceDef {
|
|||||||
let captures = {
|
let captures = {
|
||||||
profile_section!(pattern_dynamic_regex_exec);
|
profile_section!(pattern_dynamic_regex_exec);
|
||||||
|
|
||||||
match re.captures(path.path()) {
|
match re.captures(path.unprocessed()) {
|
||||||
Some(captures) => captures,
|
Some(captures) => captures,
|
||||||
_ => return false,
|
_ => return false,
|
||||||
}
|
}
|
||||||
@ -738,7 +729,7 @@ impl ResourceDef {
|
|||||||
PatternType::DynamicSet(re, params) => {
|
PatternType::DynamicSet(re, params) => {
|
||||||
profile_section!(pattern_dynamic_set);
|
profile_section!(pattern_dynamic_set);
|
||||||
|
|
||||||
let path = path.path();
|
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],
|
||||||
_ => return false,
|
_ => return false,
|
||||||
@ -762,7 +753,7 @@ impl ResourceDef {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !check_fn(resource, user_data) {
|
if !check_fn(resource) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -857,7 +848,7 @@ impl ResourceDef {
|
|||||||
S: BuildHasher,
|
S: BuildHasher,
|
||||||
{
|
{
|
||||||
profile_method!(resource_path_from_map);
|
profile_method!(resource_path_from_map);
|
||||||
self.build_resource_path(path, |name| values.get(name).map(AsRef::<str>::as_ref))
|
self.build_resource_path(path, |name| values.get(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if `prefix` acts as a proper prefix (i.e., separated by a slash) in `path`.
|
/// Returns true if `prefix` acts as a proper prefix (i.e., separated by a slash) in `path`.
|
||||||
@ -907,7 +898,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),
|
||||||
@ -1157,6 +1148,7 @@ pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::Path;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn equivalence() {
|
fn equivalence() {
|
||||||
|
@ -5,87 +5,83 @@ use crate::{IntoPatterns, Resource, ResourceDef};
|
|||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct ResourceId(pub u16);
|
pub struct ResourceId(pub u16);
|
||||||
|
|
||||||
/// Information about current resource
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ResourceInfo {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
resource: ResourceId,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resource router.
|
/// Resource router.
|
||||||
// T is the resource itself
|
///
|
||||||
// U is any other data needed for routing like method guards
|
/// It matches a [routing resource](Resource) to an ordered list of _routes_. Each is defined by a
|
||||||
|
/// single [`ResourceDef`] and contains two types of custom data:
|
||||||
|
/// 1. The route _value_, of the generic type `T`.
|
||||||
|
/// 1. Some _context_ data, of the generic type `U`, which is only provided to the check function in
|
||||||
|
/// [`recognize_fn`](Self::recognize_fn). This parameter defaults to `()` and can be omitted if
|
||||||
|
/// not required.
|
||||||
pub struct Router<T, U = ()> {
|
pub struct Router<T, U = ()> {
|
||||||
routes: Vec<(ResourceDef, T, Option<U>)>,
|
routes: Vec<(ResourceDef, T, U)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> Router<T, U> {
|
impl<T, U> Router<T, U> {
|
||||||
|
/// Constructs new `RouterBuilder` with empty route list.
|
||||||
pub fn build() -> RouterBuilder<T, U> {
|
pub fn build() -> RouterBuilder<T, U> {
|
||||||
RouterBuilder {
|
RouterBuilder { routes: Vec::new() }
|
||||||
resources: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finds the value in the router that matches a given [routing resource](Resource).
|
||||||
|
///
|
||||||
|
/// The match result, including the captured dynamic segments, in the `resource`.
|
||||||
pub fn recognize<R>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
|
pub fn recognize<R>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
|
||||||
where
|
where
|
||||||
R: Resource,
|
R: Resource,
|
||||||
{
|
{
|
||||||
profile_method!(recognize);
|
profile_method!(recognize);
|
||||||
|
self.recognize_fn(resource, |_, _| true)
|
||||||
for item in self.routes.iter() {
|
|
||||||
if item.0.capture_match_info(resource.resource_path()) {
|
|
||||||
return Some((&item.1, ResourceId(item.0.id())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Same as [`recognize`](Self::recognize) but returns a mutable reference to the matched value.
|
||||||
pub fn recognize_mut<R>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)>
|
pub fn recognize_mut<R>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)>
|
||||||
where
|
where
|
||||||
R: Resource,
|
R: Resource,
|
||||||
{
|
{
|
||||||
profile_method!(recognize_mut);
|
profile_method!(recognize_mut);
|
||||||
|
self.recognize_mut_fn(resource, |_, _| true)
|
||||||
for item in self.routes.iter_mut() {
|
|
||||||
if item.0.capture_match_info(resource.resource_path()) {
|
|
||||||
return Some((&mut item.1, ResourceId(item.0.id())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recognize_fn<R, F>(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)>
|
/// Finds the value in the router that matches a given [routing resource](Resource) and passes
|
||||||
|
/// an additional predicate check using context data.
|
||||||
|
///
|
||||||
|
/// Similar to [`recognize`](Self::recognize). However, before accepting the route as matched,
|
||||||
|
/// the `check` closure is executed, passing the resource and each route's context data. If the
|
||||||
|
/// closure returns true then the match result is stored into `resource` and a reference to
|
||||||
|
/// the matched _value_ is returned.
|
||||||
|
pub fn recognize_fn<R, F>(&self, resource: &mut R, mut check: F) -> Option<(&T, ResourceId)>
|
||||||
where
|
where
|
||||||
F: Fn(&R, &Option<U>) -> bool,
|
|
||||||
R: Resource,
|
R: Resource,
|
||||||
|
F: FnMut(&R, &U) -> bool,
|
||||||
{
|
{
|
||||||
profile_method!(recognize_checked);
|
profile_method!(recognize_checked);
|
||||||
|
|
||||||
for item in self.routes.iter() {
|
for (rdef, val, ctx) in self.routes.iter() {
|
||||||
if item.0.capture_match_info_fn(resource, &check, &item.2) {
|
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
|
||||||
return Some((&item.1, ResourceId(item.0.id())));
|
return Some((val, ResourceId(rdef.id())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Same as [`recognize_fn`](Self::recognize_fn) but returns a mutable reference to the matched
|
||||||
|
/// value.
|
||||||
pub fn recognize_mut_fn<R, F>(
|
pub fn recognize_mut_fn<R, F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
resource: &mut R,
|
resource: &mut R,
|
||||||
check: F,
|
mut check: F,
|
||||||
) -> Option<(&mut T, ResourceId)>
|
) -> Option<(&mut T, ResourceId)>
|
||||||
where
|
where
|
||||||
F: Fn(&R, &Option<U>) -> bool,
|
|
||||||
R: Resource,
|
R: Resource,
|
||||||
|
F: FnMut(&R, &U) -> bool,
|
||||||
{
|
{
|
||||||
profile_method!(recognize_mut_checked);
|
profile_method!(recognize_mut_checked);
|
||||||
|
|
||||||
for item in self.routes.iter_mut() {
|
for (rdef, val, ctx) in self.routes.iter_mut() {
|
||||||
if item.0.capture_match_info_fn(resource, &check, &item.2) {
|
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
|
||||||
return Some((&mut item.1, ResourceId(item.0.id())));
|
return Some((val, ResourceId(rdef.id())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,49 +89,69 @@ impl<T, U> Router<T, U> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builder for an ordered [routing](Router) list.
|
||||||
pub struct RouterBuilder<T, U = ()> {
|
pub struct RouterBuilder<T, U = ()> {
|
||||||
resources: Vec<(ResourceDef, T, Option<U>)>,
|
routes: Vec<(ResourceDef, T, U)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> RouterBuilder<T, U> {
|
impl<T, U> RouterBuilder<T, U> {
|
||||||
/// Register resource for specified path.
|
/// Adds a new route to the end of the routing list.
|
||||||
pub fn path<P: IntoPatterns>(
|
///
|
||||||
|
/// Returns mutable references to elements of the new route.
|
||||||
|
pub fn push(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: P,
|
rdef: ResourceDef,
|
||||||
resource: T,
|
val: T,
|
||||||
) -> &mut (ResourceDef, T, Option<U>) {
|
ctx: U,
|
||||||
profile_method!(path);
|
) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||||
|
profile_method!(push);
|
||||||
self.resources
|
self.routes.push((rdef, val, ctx));
|
||||||
.push((ResourceDef::new(path), resource, None));
|
self.routes
|
||||||
self.resources.last_mut().unwrap()
|
.last_mut()
|
||||||
}
|
.map(|(rdef, val, ctx)| (rdef, val, ctx))
|
||||||
|
.unwrap()
|
||||||
/// Register resource for specified path prefix.
|
|
||||||
pub fn prefix(&mut self, prefix: &str, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
|
||||||
profile_method!(prefix);
|
|
||||||
|
|
||||||
self.resources
|
|
||||||
.push((ResourceDef::prefix(prefix), resource, None));
|
|
||||||
self.resources.last_mut().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register resource for ResourceDef
|
|
||||||
pub fn rdef(&mut self, rdef: ResourceDef, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
|
||||||
profile_method!(rdef);
|
|
||||||
|
|
||||||
self.resources.push((rdef, resource, None));
|
|
||||||
self.resources.last_mut().unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish configuration and create router instance.
|
/// Finish configuration and create router instance.
|
||||||
pub fn finish(self) -> Router<T, U> {
|
pub fn finish(self) -> Router<T, U> {
|
||||||
Router {
|
Router {
|
||||||
routes: self.resources,
|
routes: self.routes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience methods provided when context data impls [`Default`]
|
||||||
|
impl<T, U> RouterBuilder<T, U>
|
||||||
|
where
|
||||||
|
U: Default,
|
||||||
|
{
|
||||||
|
/// Registers resource for specified path.
|
||||||
|
pub fn path(
|
||||||
|
&mut self,
|
||||||
|
path: impl IntoPatterns,
|
||||||
|
val: T,
|
||||||
|
) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||||
|
profile_method!(path);
|
||||||
|
self.push(ResourceDef::new(path), val, U::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers resource for specified path prefix.
|
||||||
|
pub fn prefix(
|
||||||
|
&mut self,
|
||||||
|
prefix: impl IntoPatterns,
|
||||||
|
val: T,
|
||||||
|
) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||||
|
profile_method!(prefix);
|
||||||
|
self.push(ResourceDef::prefix(prefix), val, U::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers resource for [`ResourceDef`].
|
||||||
|
pub fn rdef(&mut self, rdef: ResourceDef, val: T) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||||
|
profile_method!(rdef);
|
||||||
|
self.push(rdef, val, U::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::path::Path;
|
use crate::path::Path;
|
||||||
@ -256,6 +272,7 @@ mod tests {
|
|||||||
router.path("/name/{val}", 11);
|
router.path("/name/{val}", 11);
|
||||||
let mut router = router.finish();
|
let mut router = router.finish();
|
||||||
|
|
||||||
|
// test skip beyond path length
|
||||||
let mut path = Path::new("/name");
|
let mut path = Path::new("/name");
|
||||||
path.skip(6);
|
path.skip(6);
|
||||||
assert!(router.recognize_mut(&mut path).is_none());
|
assert!(router.recognize_mut(&mut path).is_none());
|
||||||
|
@ -15,14 +15,14 @@ pub struct Url {
|
|||||||
impl Url {
|
impl Url {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(uri: http::Uri) -> Url {
|
pub fn new(uri: http::Uri) -> Url {
|
||||||
let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
|
let path = DEFAULT_QUOTER.with(|q| q.requote_str_lossy(uri.path()));
|
||||||
Url { uri, path }
|
Url { uri, path }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new_with_quoter(uri: http::Uri, quoter: &Quoter) -> Url {
|
pub fn new_with_quoter(uri: http::Uri, quoter: &Quoter) -> Url {
|
||||||
Url {
|
Url {
|
||||||
path: quoter.requote(uri.path().as_bytes()),
|
path: quoter.requote_str_lossy(uri.path()),
|
||||||
uri,
|
uri,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,13 +45,13 @@ impl Url {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn update(&mut self, uri: &http::Uri) {
|
pub fn update(&mut self, uri: &http::Uri) {
|
||||||
self.uri = uri.clone();
|
self.uri = uri.clone();
|
||||||
self.path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
|
self.path = DEFAULT_QUOTER.with(|q| q.requote_str_lossy(uri.path()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn update_with_quoter(&mut self, uri: &http::Uri, quoter: &Quoter) {
|
pub fn update_with_quoter(&mut self, uri: &http::Uri, quoter: &Quoter) {
|
||||||
self.uri = uri.clone();
|
self.uri = uri.clone();
|
||||||
self.path = quoter.requote(uri.path().as_bytes());
|
self.path = quoter.requote_str_lossy(uri.path());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn valid_utf8_multibyte() {
|
fn valid_utf8_multi_byte() {
|
||||||
let test = ('\u{FF00}'..='\u{FFFF}').collect::<String>();
|
let test = ('\u{FF00}'..='\u{FFFF}').collect::<String>();
|
||||||
let encoded = percent_encode(test.as_bytes());
|
let encoded = percent_encode(test.as_bytes());
|
||||||
let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded));
|
let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded));
|
||||||
@ -135,6 +135,6 @@ mod tests {
|
|||||||
let path = Path::new(Url::new(uri));
|
let path = Path::new(Url::new(uri));
|
||||||
|
|
||||||
// We should always get a valid utf8 string
|
// We should always get a valid utf8 string
|
||||||
assert!(String::from_utf8(path.path().as_bytes().to_owned()).is_ok());
|
assert!(String::from_utf8(path.as_str().as_bytes().to_owned()).is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,16 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- Rename `TestServerConfig::{client_timeout => client_request_timeout}`. [#2611]
|
||||||
|
|
||||||
|
[#2611]: https://github.com/actix/actix-web/pull/2611
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0-beta.11 - 2022-01-04
|
## 0.1.0-beta.11 - 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-test"
|
name = "actix-test"
|
||||||
version = "0.1.0-beta.11"
|
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-beta.18"
|
actix-http = "3.0.0-rc.3"
|
||||||
actix-http-test = "3.0.0-beta.11"
|
actix-http-test = "3.0.0-beta.13"
|
||||||
actix-rt = "2.1"
|
actix-rt = "2.1"
|
||||||
actix-service = "2.0.0"
|
actix-service = "2.0.0"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = { version = "4.0.0-beta.20", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] }
|
||||||
awc = { version = "3.0.0-beta.18", default-features = false, features = ["cookies"] }
|
awc = { version = "3.0.0-beta.21", 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 = [] }
|
||||||
|
@ -149,7 +149,7 @@ where
|
|||||||
let local_addr = tcp.local_addr().unwrap();
|
let local_addr = tcp.local_addr().unwrap();
|
||||||
let factory = factory.clone();
|
let factory = factory.clone();
|
||||||
let srv_cfg = cfg.clone();
|
let srv_cfg = cfg.clone();
|
||||||
let timeout = cfg.client_timeout;
|
let timeout = cfg.client_request_timeout;
|
||||||
|
|
||||||
let builder = Server::build().workers(1).disable_signals().system_exit();
|
let builder = Server::build().workers(1).disable_signals().system_exit();
|
||||||
|
|
||||||
@ -167,7 +167,7 @@ where
|
|||||||
.map_err(|err| err.into().error_response());
|
.map_err(|err| err.into().error_response());
|
||||||
|
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(timeout)
|
.client_request_timeout(timeout)
|
||||||
.h1(map_config(fac, move |_| app_cfg.clone()))
|
.h1(map_config(fac, move |_| app_cfg.clone()))
|
||||||
.tcp()
|
.tcp()
|
||||||
}),
|
}),
|
||||||
@ -183,7 +183,7 @@ where
|
|||||||
.map_err(|err| err.into().error_response());
|
.map_err(|err| err.into().error_response());
|
||||||
|
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(timeout)
|
.client_request_timeout(timeout)
|
||||||
.h2(map_config(fac, move |_| app_cfg.clone()))
|
.h2(map_config(fac, move |_| app_cfg.clone()))
|
||||||
.tcp()
|
.tcp()
|
||||||
}),
|
}),
|
||||||
@ -199,7 +199,7 @@ where
|
|||||||
.map_err(|err| err.into().error_response());
|
.map_err(|err| err.into().error_response());
|
||||||
|
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(timeout)
|
.client_request_timeout(timeout)
|
||||||
.finish(map_config(fac, move |_| app_cfg.clone()))
|
.finish(map_config(fac, move |_| app_cfg.clone()))
|
||||||
.tcp()
|
.tcp()
|
||||||
}),
|
}),
|
||||||
@ -218,7 +218,7 @@ where
|
|||||||
.map_err(|err| err.into().error_response());
|
.map_err(|err| err.into().error_response());
|
||||||
|
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(timeout)
|
.client_request_timeout(timeout)
|
||||||
.h1(map_config(fac, move |_| app_cfg.clone()))
|
.h1(map_config(fac, move |_| app_cfg.clone()))
|
||||||
.openssl(acceptor.clone())
|
.openssl(acceptor.clone())
|
||||||
}),
|
}),
|
||||||
@ -234,7 +234,7 @@ where
|
|||||||
.map_err(|err| err.into().error_response());
|
.map_err(|err| err.into().error_response());
|
||||||
|
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(timeout)
|
.client_request_timeout(timeout)
|
||||||
.h2(map_config(fac, move |_| app_cfg.clone()))
|
.h2(map_config(fac, move |_| app_cfg.clone()))
|
||||||
.openssl(acceptor.clone())
|
.openssl(acceptor.clone())
|
||||||
}),
|
}),
|
||||||
@ -250,7 +250,7 @@ where
|
|||||||
.map_err(|err| err.into().error_response());
|
.map_err(|err| err.into().error_response());
|
||||||
|
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(timeout)
|
.client_request_timeout(timeout)
|
||||||
.finish(map_config(fac, move |_| app_cfg.clone()))
|
.finish(map_config(fac, move |_| app_cfg.clone()))
|
||||||
.openssl(acceptor.clone())
|
.openssl(acceptor.clone())
|
||||||
}),
|
}),
|
||||||
@ -269,7 +269,7 @@ where
|
|||||||
.map_err(|err| err.into().error_response());
|
.map_err(|err| err.into().error_response());
|
||||||
|
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(timeout)
|
.client_request_timeout(timeout)
|
||||||
.h1(map_config(fac, move |_| app_cfg.clone()))
|
.h1(map_config(fac, move |_| app_cfg.clone()))
|
||||||
.rustls(config.clone())
|
.rustls(config.clone())
|
||||||
}),
|
}),
|
||||||
@ -285,7 +285,7 @@ where
|
|||||||
.map_err(|err| err.into().error_response());
|
.map_err(|err| err.into().error_response());
|
||||||
|
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(timeout)
|
.client_request_timeout(timeout)
|
||||||
.h2(map_config(fac, move |_| app_cfg.clone()))
|
.h2(map_config(fac, move |_| app_cfg.clone()))
|
||||||
.rustls(config.clone())
|
.rustls(config.clone())
|
||||||
}),
|
}),
|
||||||
@ -301,7 +301,7 @@ where
|
|||||||
.map_err(|err| err.into().error_response());
|
.map_err(|err| err.into().error_response());
|
||||||
|
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(timeout)
|
.client_request_timeout(timeout)
|
||||||
.finish(map_config(fac, move |_| app_cfg.clone()))
|
.finish(map_config(fac, move |_| app_cfg.clone()))
|
||||||
.rustls(config.clone())
|
.rustls(config.clone())
|
||||||
}),
|
}),
|
||||||
@ -388,7 +388,7 @@ pub fn config() -> TestServerConfig {
|
|||||||
pub struct TestServerConfig {
|
pub struct TestServerConfig {
|
||||||
tp: HttpVer,
|
tp: HttpVer,
|
||||||
stream: StreamType,
|
stream: StreamType,
|
||||||
client_timeout: u64,
|
client_request_timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TestServerConfig {
|
impl Default for TestServerConfig {
|
||||||
@ -403,7 +403,7 @@ impl TestServerConfig {
|
|||||||
TestServerConfig {
|
TestServerConfig {
|
||||||
tp: HttpVer::Both,
|
tp: HttpVer::Both,
|
||||||
stream: StreamType::Tcp,
|
stream: StreamType::Tcp,
|
||||||
client_timeout: 5000,
|
client_request_timeout: Duration::from_secs(5),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,9 +433,9 @@ impl TestServerConfig {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set client timeout in milliseconds for first request.
|
/// Set client timeout for first request.
|
||||||
pub fn client_timeout(mut self, val: u64) -> Self {
|
pub fn client_request_timeout(mut self, dur: Duration) -> Self {
|
||||||
self.client_timeout = val;
|
self.client_request_timeout = dur;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,14 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- No significant changes since `4.0.0-beta.10`.
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.10 - 2022-01-04
|
## 4.0.0-beta.10 - 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-actors"
|
name = "actix-web-actors"
|
||||||
version = "4.0.0-beta.10"
|
version = "4.0.0-beta.12"
|
||||||
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"]
|
||||||
@ -15,9 +15,9 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = { version = "0.12.0", default-features = false }
|
actix = { version = "0.12.0", default-features = false }
|
||||||
actix-codec = "0.4.1"
|
actix-codec = "0.5"
|
||||||
actix-http = "3.0.0-beta.18"
|
actix-http = "3.0.0-rc.3"
|
||||||
actix-web = { version = "4.0.0-beta.20", default-features = false }
|
actix-web = { version = "4.0.0-rc.3", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
@ -27,8 +27,8 @@ tokio = { version = "1.8.4", features = ["sync"] }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.11"
|
actix-test = "0.1.0-beta.13"
|
||||||
awc = { version = "3.0.0-beta.18", default-features = false }
|
awc = { version = "3.0.0-beta.21", 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.10)
|
[](https://docs.rs/actix-web-actors/4.0.0-beta.12)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
[](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.10)
|
[](https://deps.rs/crate/actix-web-actors/4.0.0-beta.12)
|
||||||
[](https://crates.io/crates/actix-web-actors)
|
[](https://crates.io/crates/actix-web-actors)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -228,11 +228,10 @@ mod tests {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_default_resource() {
|
async fn test_default_resource() {
|
||||||
let srv =
|
let srv = init_service(App::new().service(web::resource("/test").to(|| async {
|
||||||
init_service(App::new().service(web::resource("/test").to(|| {
|
HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 }))
|
||||||
HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 }))
|
})))
|
||||||
})))
|
.await;
|
||||||
.await;
|
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/test").to_request();
|
let req = TestRequest::with_uri("/test").to_request();
|
||||||
let resp = call_service(&srv, req).await;
|
let resp = call_service(&srv, req).await;
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 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 = "0.5.0-rc.2"
|
||||||
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-beta.4"
|
actix-router = "0.5.0-rc.3"
|
||||||
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.11"
|
actix-test = "0.1.0-beta.13"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = "4.0.0-beta.20"
|
actix-web = "4.0.0-rc.3"
|
||||||
|
|
||||||
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"
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user