mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into feat/awc_response_timeout
This commit is contained in:
commit
9684c9f4e7
|
@ -1,24 +1,27 @@
|
||||||
name: CI (Linux)
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
push:
|
push:
|
||||||
branches:
|
branches: [master]
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_test:
|
build_and_test:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
target:
|
||||||
|
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
|
||||||
|
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
|
||||||
|
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
|
||||||
version:
|
version:
|
||||||
- 1.46.0 # MSRV
|
- 1.46.0 # MSRV
|
||||||
- stable
|
- stable
|
||||||
- nightly
|
- nightly
|
||||||
|
|
||||||
name: ${{ matrix.version }} - x86_64-unknown-linux-gnu
|
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.target.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
@ -26,7 +29,7 @@ jobs:
|
||||||
- name: Install ${{ matrix.version }}
|
- name: Install ${{ matrix.version }}
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu
|
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
|
||||||
profile: minimal
|
profile: minimal
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
|
@ -37,18 +40,25 @@ jobs:
|
||||||
- name: Cache Dependencies
|
- name: Cache Dependencies
|
||||||
uses: Swatinem/rust-cache@v1.0.1
|
uses: Swatinem/rust-cache@v1.0.1
|
||||||
|
|
||||||
- name: check build
|
- name: check minimal
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: check
|
command: check
|
||||||
args: --all --bins --examples --tests
|
args: --workspace --no-default-features --tests
|
||||||
|
|
||||||
|
- name: check full
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: check
|
||||||
|
args: --workspace --bins --examples --tests
|
||||||
|
|
||||||
- name: tests
|
- name: tests
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
timeout-minutes: 40
|
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --all --all-features --no-fail-fast -- --nocapture
|
args: -v --workspace --all-features --no-fail-fast -- --nocapture
|
||||||
|
--skip=test_h2_content_length
|
||||||
|
--skip=test_reading_deflate_encoding_large_random_rustls
|
||||||
|
|
||||||
- name: tests (actix-http)
|
- name: tests (actix-http)
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
|
@ -65,12 +75,18 @@ jobs:
|
||||||
args: --package=awc --no-default-features --features=rustls -- --nocapture
|
args: --package=awc --no-default-features --features=rustls -- --nocapture
|
||||||
|
|
||||||
- name: Generate coverage file
|
- name: Generate coverage file
|
||||||
if: matrix.version == 'stable' && github.ref == 'refs/heads/master'
|
if: >
|
||||||
|
matrix.target.os == 'ubuntu-latest'
|
||||||
|
&& matrix.version == 'stable'
|
||||||
|
&& github.ref == 'refs/heads/master'
|
||||||
run: |
|
run: |
|
||||||
cargo install cargo-tarpaulin --vers "^0.13"
|
cargo install cargo-tarpaulin --vers "^0.13"
|
||||||
cargo tarpaulin --out Xml
|
cargo tarpaulin --out Xml
|
||||||
- name: Upload to Codecov
|
- name: Upload to Codecov
|
||||||
if: matrix.version == 'stable' && github.ref == 'refs/heads/master'
|
if: >
|
||||||
|
matrix.target.os == 'ubuntu-latest'
|
||||||
|
&& matrix.version == 'stable'
|
||||||
|
&& github.ref == 'refs/heads/master'
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v1
|
||||||
with:
|
with:
|
||||||
file: cobertura.xml
|
file: cobertura.xml
|
|
@ -1,56 +0,0 @@
|
||||||
name: CI (macOS)
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened]
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_and_test:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
version:
|
|
||||||
- stable
|
|
||||||
- nightly
|
|
||||||
|
|
||||||
name: ${{ matrix.version }} - x86_64-apple-darwin
|
|
||||||
runs-on: macOS-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Install ${{ matrix.version }}
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.version }}-x86_64-apple-darwin
|
|
||||||
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.0.1
|
|
||||||
|
|
||||||
- name: check build
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: check
|
|
||||||
args: --all --bins --examples --tests
|
|
||||||
|
|
||||||
- name: tests
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
args: --all --all-features --no-fail-fast -- --nocapture
|
|
||||||
--skip=test_h2_content_length
|
|
||||||
--skip=test_reading_deflate_encoding_large_random_rustls
|
|
||||||
|
|
||||||
- name: Clear the cargo caches
|
|
||||||
run: |
|
|
||||||
cargo install cargo-cache --no-default-features --features ci-autoclean
|
|
||||||
cargo-cache
|
|
|
@ -1,76 +0,0 @@
|
||||||
name: CI (Windows)
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened]
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
env:
|
|
||||||
VCPKGRS_DYNAMIC: 1
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_and_test:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
version:
|
|
||||||
- stable
|
|
||||||
- nightly
|
|
||||||
|
|
||||||
name: ${{ matrix.version }} - x86_64-pc-windows-msvc
|
|
||||||
runs-on: windows-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Install ${{ matrix.version }}
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.version }}-x86_64-pc-windows-msvc
|
|
||||||
profile: minimal
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Install OpenSSL
|
|
||||||
run: |
|
|
||||||
vcpkg integrate install
|
|
||||||
vcpkg install openssl:x64-windows
|
|
||||||
Copy-Item C:\vcpkg\installed\x64-windows\bin\libcrypto-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libcrypto.dll
|
|
||||||
Copy-Item C:\vcpkg\installed\x64-windows\bin\libssl-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libssl.dll
|
|
||||||
Get-ChildItem C:\vcpkg\installed\x64-windows\bin
|
|
||||||
Get-ChildItem C:\vcpkg\installed\x64-windows\lib
|
|
||||||
|
|
||||||
- name: Generate Cargo.lock
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: generate-lockfile
|
|
||||||
- name: Cache Dependencies
|
|
||||||
uses: Swatinem/rust-cache@v1.0.1
|
|
||||||
|
|
||||||
- name: check build
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: check
|
|
||||||
args: --all --bins --examples --tests
|
|
||||||
|
|
||||||
- name: tests
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
args: --all --all-features --no-fail-fast -- --nocapture
|
|
||||||
--skip=test_h2_content_length
|
|
||||||
--skip=test_reading_deflate_encoding_large_random_rustls
|
|
||||||
--skip=test_params
|
|
||||||
--skip=test_simple
|
|
||||||
--skip=test_expect_continue
|
|
||||||
--skip=test_http10_keepalive
|
|
||||||
--skip=test_slow_request
|
|
||||||
--skip=test_connection_force_close
|
|
||||||
--skip=test_connection_server_close
|
|
||||||
--skip=test_connection_wait_queue_force_close
|
|
||||||
|
|
||||||
- name: Clear the cargo caches
|
|
||||||
run: |
|
|
||||||
cargo install cargo-cache --no-default-features --features ci-autoclean
|
|
||||||
cargo-cache
|
|
27
CHANGES.md
27
CHANGES.md
|
@ -1,26 +1,33 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0-beta.3 - 2021-02-10
|
||||||
|
* Update `actix-web-codegen` to `0.5.0-beta.1`.
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0-beta.2 - 2021-02-10
|
||||||
### Added
|
### Added
|
||||||
* The method `Either<web::Json<T>, web::Form<T>>::into_inner()` which returns the inner type for
|
* The method `Either<web::Json<T>, web::Form<T>>::into_inner()` which returns the inner type for
|
||||||
whichever variant was created. Also works for `Either<web::Form<T>, web::Json<T>>`. [#1894]
|
whichever variant was created. Also works for `Either<web::Form<T>, web::Json<T>>`. [#1894]
|
||||||
* Add `services!` macro for helping register multiple services to `App`. [#1933]
|
* Add `services!` macro for helping register multiple services to `App`. [#1933]
|
||||||
* Enable registering vector of same type of `HttpServiceFactory` to `App` [#1933]
|
* Enable registering a vec of services of the same type to `App` [#1933]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly.
|
* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly.
|
||||||
Making it more simple and performant. [#1891]
|
Making it simpler and more performant. [#1891]
|
||||||
* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail.
|
* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893]
|
||||||
`ServiceRequest::from_request` would not fail and no payload would be generated [#1893]
|
* `ServiceRequest::from_request` can no longer fail. [#1893]
|
||||||
* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894]
|
* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894]
|
||||||
* `test::{call_service, read_response, read_response_json, send_request}` take `&Service`
|
* `test::{call_service, read_response, read_response_json, send_request}` take `&Service`
|
||||||
in argument [#1905]
|
in argument [#1905]
|
||||||
* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` would give `&Service` in closure
|
* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` provide `&Service` in closure
|
||||||
argument [#1905]
|
argument. [#1905]
|
||||||
* `web::block` accept any closure that has an output bound to `Send` and `'static`. [#1957]
|
* `web::block` no longer requires the output is a Result. [#1957]
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* Multiple calls `App::data` with the same type now keeps the latest call's data. [#1906]
|
* Multiple calls to `App::data` with the same type now keeps the latest call's data. [#1906]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* Public field of `web::Path` has been made private. [#1894]
|
* Public field of `web::Path` has been made private. [#1894]
|
||||||
|
|
26
Cargo.toml
26
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "4.0.0-beta.1"
|
version = "4.0.0-beta.3"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -47,10 +47,10 @@ compress = ["actix-http/compress", "awc/compress"]
|
||||||
secure-cookies = ["actix-http/secure-cookies"]
|
secure-cookies = ["actix-http/secure-cookies"]
|
||||||
|
|
||||||
# openssl
|
# openssl
|
||||||
openssl = ["tls_openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"]
|
openssl = ["tls-openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"]
|
||||||
|
|
||||||
# rustls
|
# rustls
|
||||||
rustls = ["tls_rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"]
|
rustls = ["tls-rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "basic"
|
name = "basic"
|
||||||
|
@ -82,9 +82,9 @@ actix-service = "2.0.0-beta.4"
|
||||||
actix-utils = "3.0.0-beta.2"
|
actix-utils = "3.0.0-beta.2"
|
||||||
actix-tls = { version = "3.0.0-beta.3", default-features = false, optional = true }
|
actix-tls = { version = "3.0.0-beta.3", default-features = false, optional = true }
|
||||||
|
|
||||||
actix-web-codegen = "0.4.0"
|
actix-web-codegen = "0.5.0-beta.1"
|
||||||
actix-http = "3.0.0-beta.1"
|
actix-http = "3.0.0-beta.3"
|
||||||
awc = { version = "3.0.0-beta.1", default-features = false }
|
awc = { version = "3.0.0-beta.2", default-features = false }
|
||||||
|
|
||||||
ahash = "0.7"
|
ahash = "0.7"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
@ -103,13 +103,18 @@ serde_json = "1.0"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
time = { version = "0.2.23", default-features = false, features = ["std"] }
|
time = { version = "0.2.23", default-features = false, features = ["std"] }
|
||||||
url = "2.1"
|
url = "2.1"
|
||||||
tls_openssl = { package = "openssl", version = "0.10.9", optional = true }
|
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
|
||||||
tls_rustls = { package = "rustls", version = "0.19.0", optional = true }
|
tls-rustls = { package = "rustls", version = "0.19.0", optional = true }
|
||||||
smallvec = "1.6"
|
smallvec = "1.6"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies.tls-openssl]
|
||||||
|
version = "0.10.9"
|
||||||
|
package = "openssl"
|
||||||
|
features = ["vendored"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix = { version = "0.11.0-beta.2", default-features = false }
|
actix = { version = "0.11.0-beta.2", default-features = false }
|
||||||
actix-http = { version = "3.0.0-beta.1", features = ["actors"] }
|
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
@ -117,6 +122,9 @@ brotli2 = "0.3.2"
|
||||||
flate2 = "1.0.13"
|
flate2 = "1.0.13"
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
debug = false
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h1>Actix web</h1>
|
<h1>Actix Web</h1>
|
||||||
<p>
|
<p>
|
||||||
<strong>Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust</strong>
|
<strong>Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust</strong>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web)
|
[](https://crates.io/crates/actix-web)
|
||||||
[](https://docs.rs/actix-web/3.3.2)
|
[](https://docs.rs/actix-web/4.0.0-beta.2)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-web/3.3.2)
|
[](https://deps.rs/crate/actix-web/4.0.0-beta.2)
|
||||||
<br />
|
<br />
|
||||||
[](https://github.com/actix/actix-web/actions)
|
[](https://github.com/actix/actix-web/actions)
|
||||||
[](https://codecov.io/gh/actix/actix-web)
|
[](https://codecov.io/gh/actix/actix-web)
|
||||||
|
@ -32,7 +32,6 @@
|
||||||
* SSL support using OpenSSL or Rustls
|
* SSL support using OpenSSL or Rustls
|
||||||
* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
|
* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
|
||||||
* Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
|
* Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
|
||||||
* Supports [Actix actor framework](https://github.com/actix/actix)
|
|
||||||
* Runs on stable Rust 1.46+
|
* Runs on stable Rust 1.46+
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0-beta.2 - 2021-02-10
|
||||||
* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
|
* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
|
||||||
* Replace `v_htmlescape` with `askama_escape`. [#1953]
|
* Replace `v_htmlescape` with `askama_escape`. [#1953]
|
||||||
|
|
||||||
[#1887]: https://github.com/actix/actix-web/pull/1887
|
[#1887]: https://github.com/actix/actix-web/pull/1887
|
||||||
[#1953]: https://github.com/actix/actix-web/pull/1953
|
[#1953]: https://github.com/actix/actix-web/pull/1953
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.1 - 2021-01-07
|
## 0.6.0-beta.1 - 2021-01-07
|
||||||
* `HttpRange::parse` now has its own error type.
|
* `HttpRange::parse` now has its own error type.
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.6.0-beta.1"
|
version = "0.6.0-beta.2"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Static file serving for Actix Web"
|
description = "Static file serving for Actix Web"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -17,7 +17,7 @@ name = "actix_files"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.1", default-features = false }
|
actix-web = { version = "4.0.0-beta.3", default-features = false }
|
||||||
actix-service = "2.0.0-beta.4"
|
actix-service = "2.0.0-beta.4"
|
||||||
|
|
||||||
askama_escape = "0.10"
|
askama_escape = "0.10"
|
||||||
|
@ -33,4 +33,4 @@ percent-encoding = "2.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2"
|
actix-rt = "2"
|
||||||
actix-web = "4.0.0-beta.1"
|
actix-web = "4.0.0-beta.3"
|
||||||
|
|
|
@ -49,10 +49,7 @@ impl fmt::Debug for ChunkedReadFile {
|
||||||
impl Stream for ChunkedReadFile {
|
impl Stream for ChunkedReadFile {
|
||||||
type Item = Result<Bytes, Error>;
|
type Item = Result<Bytes, Error>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Self::Item>> {
|
|
||||||
let this = self.as_mut().get_mut();
|
let this = self.as_mut().get_mut();
|
||||||
match this.state {
|
match this.state {
|
||||||
ChunkedReadFileState::File(ref mut file) => {
|
ChunkedReadFileState::File(ref mut file) => {
|
||||||
|
@ -68,16 +65,13 @@ impl Stream for ChunkedReadFile {
|
||||||
.expect("ChunkedReadFile polled after completion");
|
.expect("ChunkedReadFile polled after completion");
|
||||||
|
|
||||||
let fut = spawn_blocking(move || {
|
let fut = spawn_blocking(move || {
|
||||||
let max_bytes =
|
let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize;
|
||||||
cmp::min(size.saturating_sub(counter), 65_536) as usize;
|
|
||||||
|
|
||||||
let mut buf = Vec::with_capacity(max_bytes);
|
let mut buf = Vec::with_capacity(max_bytes);
|
||||||
file.seek(io::SeekFrom::Start(offset))?;
|
file.seek(io::SeekFrom::Start(offset))?;
|
||||||
|
|
||||||
let n_bytes = file
|
let n_bytes =
|
||||||
.by_ref()
|
file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?;
|
||||||
.take(max_bytes as u64)
|
|
||||||
.read_to_end(&mut buf)?;
|
|
||||||
|
|
||||||
if n_bytes == 0 {
|
if n_bytes == 0 {
|
||||||
return Err(io::ErrorKind::UnexpectedEof.into());
|
return Err(io::ErrorKind::UnexpectedEof.into());
|
||||||
|
|
|
@ -66,9 +66,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) => {
|
Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace("\\", "/"),
|
||||||
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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,9 +2,7 @@ use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc};
|
||||||
|
|
||||||
use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt};
|
use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{
|
dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse},
|
||||||
AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse,
|
|
||||||
},
|
|
||||||
error::Error,
|
error::Error,
|
||||||
guard::Guard,
|
guard::Guard,
|
||||||
http::header::DispositionType,
|
http::header::DispositionType,
|
||||||
|
@ -13,8 +11,8 @@ use actix_web::{
|
||||||
use futures_util::future::{ok, FutureExt, LocalBoxFuture};
|
use futures_util::future::{ok, FutureExt, LocalBoxFuture};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
directory_listing, named, Directory, DirectoryRenderer, FilesService,
|
directory_listing, named, Directory, DirectoryRenderer, FilesService, HttpNewService,
|
||||||
HttpNewService, MimeOverride,
|
MimeOverride,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Static files handling service.
|
/// Static files handling service.
|
||||||
|
@ -129,8 +127,8 @@ impl Files {
|
||||||
/// Set custom directory renderer
|
/// Set custom directory renderer
|
||||||
pub fn files_listing_renderer<F>(mut self, f: F) -> Self
|
pub fn files_listing_renderer<F>(mut self, f: F) -> Self
|
||||||
where
|
where
|
||||||
for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) -> Result<ServiceResponse, io::Error>
|
for<'r, 's> F:
|
||||||
+ 'static,
|
Fn(&'r Directory, &'s HttpRequest) -> Result<ServiceResponse, io::Error> + 'static,
|
||||||
{
|
{
|
||||||
self.renderer = Rc::new(f);
|
self.renderer = Rc::new(f);
|
||||||
self
|
self
|
||||||
|
|
|
@ -98,8 +98,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_if_modified_since_without_if_none_match() {
|
async fn test_if_modified_since_without_if_none_match() {
|
||||||
let file = NamedFile::open("Cargo.toml").unwrap();
|
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||||
let since =
|
let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
||||||
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.insert_header((header::IF_MODIFIED_SINCE, since))
|
.insert_header((header::IF_MODIFIED_SINCE, since))
|
||||||
|
@ -123,8 +122,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_if_modified_since_with_if_none_match() {
|
async fn test_if_modified_since_with_if_none_match() {
|
||||||
let file = NamedFile::open("Cargo.toml").unwrap();
|
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||||
let since =
|
let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
||||||
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.insert_header((header::IF_NONE_MATCH, "miss_etag"))
|
.insert_header((header::IF_NONE_MATCH, "miss_etag"))
|
||||||
|
@ -212,8 +210,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_named_file_non_ascii_file_name() {
|
async fn test_named_file_non_ascii_file_name() {
|
||||||
let mut file =
|
let mut file =
|
||||||
NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml")
|
NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml").unwrap();
|
||||||
.unwrap();
|
|
||||||
{
|
{
|
||||||
file.file();
|
file.file();
|
||||||
let _f: &File = &file;
|
let _f: &File = &file;
|
||||||
|
@ -605,10 +602,9 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_static_files() {
|
async fn test_static_files() {
|
||||||
let srv = test::init_service(
|
let srv =
|
||||||
App::new().service(Files::new("/", ".").show_files_listing()),
|
test::init_service(App::new().service(Files::new("/", ".").show_files_listing()))
|
||||||
)
|
.await;
|
||||||
.await;
|
|
||||||
let req = TestRequest::with_uri("/missing").to_request();
|
let req = TestRequest::with_uri("/missing").to_request();
|
||||||
|
|
||||||
let resp = test::call_service(&srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
|
@ -620,10 +616,9 @@ mod tests {
|
||||||
let resp = test::call_service(&srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
let srv = test::init_service(
|
let srv =
|
||||||
App::new().service(Files::new("/", ".").show_files_listing()),
|
test::init_service(App::new().service(Files::new("/", ".").show_files_listing()))
|
||||||
)
|
.await;
|
||||||
.await;
|
|
||||||
let req = TestRequest::with_uri("/tests").to_request();
|
let req = TestRequest::with_uri("/tests").to_request();
|
||||||
let resp = test::call_service(&srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -11,8 +11,7 @@ use actix_web::{
|
||||||
dev::{BodyEncoding, SizedStream},
|
dev::{BodyEncoding, SizedStream},
|
||||||
http::{
|
http::{
|
||||||
header::{
|
header::{
|
||||||
self, Charset, ContentDisposition, DispositionParam, DispositionType,
|
self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue,
|
||||||
ExtendedValue,
|
|
||||||
},
|
},
|
||||||
ContentEncoding, StatusCode,
|
ContentEncoding, StatusCode,
|
||||||
},
|
},
|
||||||
|
@ -395,18 +394,10 @@ impl NamedFile {
|
||||||
resp.encoding(ContentEncoding::Identity);
|
resp.encoding(ContentEncoding::Identity);
|
||||||
resp.insert_header((
|
resp.insert_header((
|
||||||
header::CONTENT_RANGE,
|
header::CONTENT_RANGE,
|
||||||
format!(
|
format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()),
|
||||||
"bytes {}-{}/{}",
|
|
||||||
offset,
|
|
||||||
offset + length - 1,
|
|
||||||
self.md.len()
|
|
||||||
),
|
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
resp.insert_header((
|
resp.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length)));
|
||||||
header::CONTENT_RANGE,
|
|
||||||
format!("bytes */{}", length),
|
|
||||||
));
|
|
||||||
return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
|
return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -46,8 +46,7 @@ impl HttpRange {
|
||||||
if start_str.is_empty() {
|
if start_str.is_empty() {
|
||||||
// If no start is specified, end specifies the
|
// If no start is specified, end specifies the
|
||||||
// range start relative to the end of the file.
|
// range start relative to the end of the file.
|
||||||
let mut length: i64 =
|
let mut length: i64 = end_str.parse().map_err(|_| ParseRangeErr(()))?;
|
||||||
end_str.parse().map_err(|_| ParseRangeErr(()))?;
|
|
||||||
|
|
||||||
if length > size_sig {
|
if length > size_sig {
|
||||||
length = size_sig;
|
length = size_sig;
|
||||||
|
@ -72,8 +71,7 @@ impl HttpRange {
|
||||||
// If no end is specified, range extends to end of the file.
|
// If no end is specified, range extends to end of the file.
|
||||||
size_sig - start
|
size_sig - start
|
||||||
} else {
|
} else {
|
||||||
let mut end: i64 =
|
let mut end: i64 = end_str.parse().map_err(|_| ParseRangeErr(()))?;
|
||||||
end_str.parse().map_err(|_| ParseRangeErr(()))?;
|
|
||||||
|
|
||||||
if start > end {
|
if start > end {
|
||||||
return Err(ParseRangeErr(()));
|
return Err(ParseRangeErr(()));
|
||||||
|
|
|
@ -11,8 +11,8 @@ use actix_web::{
|
||||||
use futures_util::future::{ok, Either, LocalBoxFuture, Ready};
|
use futures_util::future::{ok, Either, LocalBoxFuture, Ready};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride,
|
named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, NamedFile,
|
||||||
NamedFile, PathBufWrap,
|
PathBufWrap,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Assembled file serving service.
|
/// Assembled file serving service.
|
||||||
|
@ -138,8 +138,7 @@ impl Service<ServiceRequest> for FilesService {
|
||||||
match NamedFile::open(path) {
|
match NamedFile::open(path) {
|
||||||
Ok(mut named_file) => {
|
Ok(mut named_file) => {
|
||||||
if let Some(ref mime_override) = self.mime_override {
|
if let Some(ref mime_override) = self.mime_override {
|
||||||
let new_disposition =
|
let new_disposition = mime_override(&named_file.content_type.type_());
|
||||||
mime_override(&named_file.content_type.type_());
|
|
||||||
named_file.content_disposition.disposition = new_disposition;
|
named_file.content_disposition.disposition = new_disposition;
|
||||||
}
|
}
|
||||||
named_file.flags = self.file_flags;
|
named_file.flags = self.file_flags;
|
||||||
|
|
|
@ -23,10 +23,9 @@ async fn test_utf8_file_contents() {
|
||||||
);
|
);
|
||||||
|
|
||||||
// prefer UTF-8 encoding
|
// prefer UTF-8 encoding
|
||||||
let srv = test::init_service(
|
let srv =
|
||||||
App::new().service(Files::new("/", "./tests").prefer_utf8(true)),
|
test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(true)))
|
||||||
)
|
.await;
|
||||||
.await;
|
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/utf8.txt").to_request();
|
let req = TestRequest::with_uri("/utf8.txt").to_request();
|
||||||
let res = test::call_service(&srv, req).await;
|
let res = test::call_service(&srv, req).await;
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.2 - 2021-02-10
|
||||||
|
* No notable changes.
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.1 - 2021-01-07
|
## 3.0.0-beta.1 - 2021-01-07
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-http-test"
|
name = "actix-http-test"
|
||||||
version = "3.0.0-beta.1"
|
version = "3.0.0-beta.2"
|
||||||
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"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.3"
|
||||||
actix-utils = "3.0.0-beta.2"
|
actix-utils = "3.0.0-beta.2"
|
||||||
actix-rt = "2"
|
actix-rt = "2"
|
||||||
actix-server = "2.0.0-beta.3"
|
actix-server = "2.0.0-beta.3"
|
||||||
awc = "3.0.0-beta.1"
|
awc = "3.0.0-beta.2"
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
@ -50,6 +50,12 @@ serde_urlencoded = "0.7"
|
||||||
time = { version = "0.2.23", default-features = false, features = ["std"] }
|
time = { version = "0.2.23", default-features = false, features = ["std"] }
|
||||||
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies.tls-openssl]
|
||||||
|
version = "0.10.9"
|
||||||
|
package = "openssl"
|
||||||
|
features = ["vendored"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-web = "4.0.0-beta.1"
|
actix-web = "4.0.0-beta.3"
|
||||||
actix-http = "3.0.0-beta.1"
|
actix-http = "3.0.0-beta.3"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http-test)
|
[](https://crates.io/crates/actix-http-test)
|
||||||
[](https://docs.rs/actix-http-test/2.1.0)
|
[](https://docs.rs/actix-http-test/2.1.0)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-http-test/2.1.0)
|
[](https://deps.rs/crate/actix-http-test/2.1.0)
|
||||||
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
|
|
@ -120,8 +120,7 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
||||||
/// Get first available unused address
|
/// Get first available unused address
|
||||||
pub fn unused_addr() -> net::SocketAddr {
|
pub fn unused_addr() -> net::SocketAddr {
|
||||||
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||||
let socket =
|
let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
|
||||||
Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
|
|
||||||
socket.bind(&addr.into()).unwrap();
|
socket.bind(&addr.into()).unwrap();
|
||||||
socket.set_reuse_address(true).unwrap();
|
socket.set_reuse_address(true).unwrap();
|
||||||
let tcp = socket.into_tcp_listener();
|
let tcp = socket.into_tcp_listener();
|
||||||
|
@ -150,7 +149,7 @@ impl TestServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct test https server url
|
/// Construct test HTTPS server URL.
|
||||||
pub fn surl(&self, uri: &str) -> String {
|
pub fn surl(&self, uri: &str) -> String {
|
||||||
if uri.starts_with('/') {
|
if uri.starts_with('/') {
|
||||||
format!("https://localhost:{}{}", self.addr.port(), uri)
|
format!("https://localhost:{}{}", self.addr.port(), uri)
|
||||||
|
@ -164,7 +163,7 @@ impl TestServer {
|
||||||
self.client.get(self.url(path.as_ref()).as_str())
|
self.client.get(self.url(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create https `GET` request
|
/// Create HTTPS `GET` request
|
||||||
pub fn sget<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
pub fn sget<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||||
self.client.get(self.surl(path.as_ref()).as_str())
|
self.client.get(self.surl(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
@ -174,7 +173,7 @@ impl TestServer {
|
||||||
self.client.post(self.url(path.as_ref()).as_str())
|
self.client.post(self.url(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create https `POST` request
|
/// Create HTTPS `POST` request
|
||||||
pub fn spost<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
pub fn spost<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||||
self.client.post(self.surl(path.as_ref()).as_str())
|
self.client.post(self.surl(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
@ -184,7 +183,7 @@ impl TestServer {
|
||||||
self.client.head(self.url(path.as_ref()).as_str())
|
self.client.head(self.url(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create https `HEAD` request
|
/// Create HTTPS `HEAD` request
|
||||||
pub fn shead<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
pub fn shead<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||||
self.client.head(self.surl(path.as_ref()).as_str())
|
self.client.head(self.surl(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
@ -194,7 +193,7 @@ impl TestServer {
|
||||||
self.client.put(self.url(path.as_ref()).as_str())
|
self.client.put(self.url(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create https `PUT` request
|
/// Create HTTPS `PUT` request
|
||||||
pub fn sput<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
pub fn sput<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||||
self.client.put(self.surl(path.as_ref()).as_str())
|
self.client.put(self.surl(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
@ -204,7 +203,7 @@ impl TestServer {
|
||||||
self.client.patch(self.url(path.as_ref()).as_str())
|
self.client.patch(self.url(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create https `PATCH` request
|
/// Create HTTPS `PATCH` request
|
||||||
pub fn spatch<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
pub fn spatch<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||||
self.client.patch(self.surl(path.as_ref()).as_str())
|
self.client.patch(self.surl(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
@ -214,7 +213,7 @@ impl TestServer {
|
||||||
self.client.delete(self.url(path.as_ref()).as_str())
|
self.client.delete(self.url(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create https `DELETE` request
|
/// Create HTTPS `DELETE` request
|
||||||
pub fn sdelete<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
pub fn sdelete<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||||
self.client.delete(self.surl(path.as_ref()).as_str())
|
self.client.delete(self.surl(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
@ -224,12 +223,12 @@ impl TestServer {
|
||||||
self.client.options(self.url(path.as_ref()).as_str())
|
self.client.options(self.url(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create https `OPTIONS` request
|
/// Create HTTPS `OPTIONS` request
|
||||||
pub fn soptions<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
pub fn soptions<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||||
self.client.options(self.surl(path.as_ref()).as_str())
|
self.client.options(self.surl(path.as_ref()).as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Connect to test http server
|
/// Connect to test HTTP server
|
||||||
pub fn request<S: AsRef<str>>(&self, method: Method, path: S) -> ClientRequest {
|
pub fn request<S: AsRef<str>>(&self, method: Method, path: S) -> ClientRequest {
|
||||||
self.client.request(method, path.as_ref())
|
self.client.request(method, path.as_ref())
|
||||||
}
|
}
|
||||||
|
@ -244,26 +243,24 @@ impl TestServer {
|
||||||
response.body().limit(10_485_760).await
|
response.body().limit(10_485_760).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Connect to websocket server at a given path
|
/// Connect to WebSocket server at a given path.
|
||||||
pub async fn ws_at(
|
pub async fn ws_at(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError>
|
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError> {
|
||||||
{
|
|
||||||
let url = self.url(path);
|
let url = self.url(path);
|
||||||
let connect = self.client.ws(url).connect();
|
let connect = self.client.ws(url).connect();
|
||||||
connect.await.map(|(_, framed)| framed)
|
connect.await.map(|(_, framed)| framed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Connect to a websocket server
|
/// Connect to a WebSocket server.
|
||||||
pub async fn ws(
|
pub async fn ws(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError>
|
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError> {
|
||||||
{
|
|
||||||
self.ws_at("/").await
|
self.ws_at("/").await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop http server
|
/// Stop HTTP server
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self) {
|
||||||
self.system.stop();
|
self.system.stop();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.3 - 2021-02-10
|
||||||
|
* No notable changes.
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.2 - 2021-02-10
|
||||||
### Added
|
### Added
|
||||||
* `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
|
* `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
|
||||||
* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869]
|
* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869]
|
||||||
* `ResponseBuilder::append_header` method which allows using typed headers. [#1869]
|
* `ResponseBuilder::append_header` method which allows using typed headers. [#1869]
|
||||||
* `TestRequest::insert_header` method which allows using typed headers. [#1869]
|
* `TestRequest::insert_header` method which allows using typed headers. [#1869]
|
||||||
* `ContentEncoding` implements all necessary header traits. [#1912]
|
* `ContentEncoding` implements all necessary header traits. [#1912]
|
||||||
|
* `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964]
|
||||||
|
* `HeaderMap::drain` as an efficient draining iterator. [#1964]
|
||||||
|
* Implement `IntoIterator` for owned `HeaderMap`. [#1964]
|
||||||
|
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed
|
* `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed
|
||||||
|
@ -14,11 +25,15 @@
|
||||||
* Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
|
* Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
|
||||||
`TryInto` trait. [#1894]
|
`TryInto` trait. [#1894]
|
||||||
* `Extensions::insert` returns Option of replaced item. [#1904]
|
* `Extensions::insert` returns Option of replaced item. [#1904]
|
||||||
* Remove `HttpResponseBuilder::json2()` and make `HttpResponseBuilder::json()` take a value by
|
* Remove `HttpResponseBuilder::json2()`. [#1903]
|
||||||
reference. [#1903]
|
* Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903]
|
||||||
* `client::error::ConnectError` Resolver variant contains `Box<dyn std::error::Error>` type [#1905]
|
* `client::error::ConnectError` Resolver variant contains `Box<dyn std::error::Error>` type. [#1905]
|
||||||
* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905]
|
* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905]
|
||||||
* Simplify `BlockingError` type to a struct. It's only triggered with blocking thread pool is dead. [#1957]
|
* Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool
|
||||||
|
is dead. [#1957]
|
||||||
|
* `HeaderMap::len` now returns number of values instead of number of keys. [#1964]
|
||||||
|
* `HeaderMap::insert` now returns iterator of removed values. [#1964]
|
||||||
|
* `HeaderMap::remove` now returns iterator of removed values. [#1964]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869]
|
* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869]
|
||||||
|
@ -26,6 +41,11 @@
|
||||||
* `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869]
|
* `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869]
|
||||||
* `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869]
|
* `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869]
|
* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
|
* `actors` optional feature. [#1969]
|
||||||
|
* `ResponseError` impl for `actix::MailboxError`. [#1969]
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
* Vastly improve docs and add examples for `HeaderMap`. [#1964]
|
||||||
|
|
||||||
[#1869]: https://github.com/actix/actix-web/pull/1869
|
[#1869]: https://github.com/actix/actix-web/pull/1869
|
||||||
[#1894]: https://github.com/actix/actix-web/pull/1894
|
[#1894]: https://github.com/actix/actix-web/pull/1894
|
||||||
|
@ -34,6 +54,8 @@
|
||||||
[#1905]: https://github.com/actix/actix-web/pull/1905
|
[#1905]: https://github.com/actix/actix-web/pull/1905
|
||||||
[#1912]: https://github.com/actix/actix-web/pull/1912
|
[#1912]: https://github.com/actix/actix-web/pull/1912
|
||||||
[#1957]: https://github.com/actix/actix-web/pull/1957
|
[#1957]: https://github.com/actix/actix-web/pull/1957
|
||||||
|
[#1964]: https://github.com/actix/actix-web/pull/1964
|
||||||
|
[#1969]: https://github.com/actix/actix-web/pull/1969
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.1 - 2021-01-07
|
## 3.0.0-beta.1 - 2021-01-07
|
||||||
|
@ -109,15 +131,14 @@
|
||||||
* Update actix-connect and actix-tls dependencies.
|
* Update actix-connect and actix-tls dependencies.
|
||||||
|
|
||||||
|
|
||||||
## [2.0.0-beta.3] - 2020-08-14
|
## 2.0.0-beta.3 - 2020-08-14
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626]
|
* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626]
|
||||||
|
|
||||||
[#1626]: https://github.com/actix/actix-web/pull/1626
|
[#1626]: https://github.com/actix/actix-web/pull/1626
|
||||||
|
|
||||||
|
|
||||||
## [2.0.0-beta.2] - 2020-07-21
|
## 2.0.0-beta.2 - 2020-07-21
|
||||||
### Fixed
|
### Fixed
|
||||||
* Potential UB in h1 decoder using uninitialized memory. [#1614]
|
* Potential UB in h1 decoder using uninitialized memory. [#1614]
|
||||||
|
|
||||||
|
@ -128,10 +149,8 @@
|
||||||
[#1615]: https://github.com/actix/actix-web/pull/1615
|
[#1615]: https://github.com/actix/actix-web/pull/1615
|
||||||
|
|
||||||
|
|
||||||
## [2.0.0-beta.1] - 2020-07-11
|
## 2.0.0-beta.1 - 2020-07-11
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Migrate cookie handling to `cookie` crate. [#1558]
|
* Migrate cookie handling to `cookie` crate. [#1558]
|
||||||
* Update `sha-1` to 0.9. [#1586]
|
* Update `sha-1` to 0.9. [#1586]
|
||||||
* Fix leak in client pool. [#1580]
|
* Fix leak in client pool. [#1580]
|
||||||
|
@ -141,33 +160,30 @@
|
||||||
[#1586]: https://github.com/actix/actix-web/pull/1586
|
[#1586]: https://github.com/actix/actix-web/pull/1586
|
||||||
[#1580]: https://github.com/actix/actix-web/pull/1580
|
[#1580]: https://github.com/actix/actix-web/pull/1580
|
||||||
|
|
||||||
## [2.0.0-alpha.4] - 2020-05-21
|
|
||||||
|
|
||||||
|
## 2.0.0-alpha.4 - 2020-05-21
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Bump minimum supported Rust version to 1.40
|
* Bump minimum supported Rust version to 1.40
|
||||||
* content_length function is removed, and you can set Content-Length by calling no_chunking function [#1439]
|
* content_length function is removed, and you can set Content-Length by calling
|
||||||
|
no_chunking function [#1439]
|
||||||
* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
|
* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
|
||||||
`u64` instead of a `usize`.
|
`u64` instead of a `usize`.
|
||||||
* Update `base64` dependency to 0.12
|
* Update `base64` dependency to 0.12
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Support parsing of `SameSite=None` [#1503]
|
* Support parsing of `SameSite=None` [#1503]
|
||||||
|
|
||||||
[#1439]: https://github.com/actix/actix-web/pull/1439
|
[#1439]: https://github.com/actix/actix-web/pull/1439
|
||||||
[#1503]: https://github.com/actix/actix-web/pull/1503
|
[#1503]: https://github.com/actix/actix-web/pull/1503
|
||||||
|
|
||||||
## [2.0.0-alpha.3] - 2020-05-08
|
|
||||||
|
|
||||||
|
## 2.0.0-alpha.3 - 2020-05-08
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Correct spelling of ConnectError::Unresolved [#1487]
|
* Correct spelling of ConnectError::Unresolved [#1487]
|
||||||
* Fix a mistake in the encoding of websocket continuation messages wherein
|
* Fix a mistake in the encoding of websocket continuation messages wherein
|
||||||
Item::FirstText and Item::FirstBinary are each encoded as the other.
|
Item::FirstText and Item::FirstBinary are each encoded as the other.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Implement `std::error::Error` for our custom errors [#1422]
|
* Implement `std::error::Error` for our custom errors [#1422]
|
||||||
* Remove `failure` support for `ResponseError` since that crate
|
* Remove `failure` support for `ResponseError` since that crate
|
||||||
will be deprecated in the near future.
|
will be deprecated in the near future.
|
||||||
|
@ -175,338 +191,247 @@
|
||||||
[#1422]: https://github.com/actix/actix-web/pull/1422
|
[#1422]: https://github.com/actix/actix-web/pull/1422
|
||||||
[#1487]: https://github.com/actix/actix-web/pull/1487
|
[#1487]: https://github.com/actix/actix-web/pull/1487
|
||||||
|
|
||||||
## [2.0.0-alpha.2] - 2020-03-07
|
|
||||||
|
|
||||||
|
## 2.0.0-alpha.2 - 2020-03-07
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395]
|
* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395]
|
||||||
|
* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB
|
||||||
* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB respectively
|
respectively to improve download speed for awc when downloading large objects. [#1394]
|
||||||
to improve download speed for awc when downloading large objects. [#1394]
|
* client::Connector accepts initial_window_size and initial_connection_window_size
|
||||||
|
HTTP2 configuration. [#1394]
|
||||||
* client::Connector accepts initial_window_size and initial_connection_window_size HTTP2 configuration. [#1394]
|
|
||||||
|
|
||||||
* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394]
|
* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394]
|
||||||
|
|
||||||
[#1394]: https://github.com/actix/actix-web/pull/1394
|
[#1394]: https://github.com/actix/actix-web/pull/1394
|
||||||
[#1395]: https://github.com/actix/actix-web/pull/1395
|
[#1395]: https://github.com/actix/actix-web/pull/1395
|
||||||
|
|
||||||
## [2.0.0-alpha.1] - 2020-02-27
|
|
||||||
|
|
||||||
|
## 2.0.0-alpha.1 - 2020-02-27
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Update the `time` dependency to 0.2.7.
|
* Update the `time` dependency to 0.2.7.
|
||||||
* Moved actors messages support from actix crate, enabled with feature `actors`.
|
* Moved actors messages support from actix crate, enabled with feature `actors`.
|
||||||
* Breaking change: trait MessageBody requires Unpin and accepting Pin<&mut Self> instead of &mut self in the poll_next().
|
* Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of
|
||||||
|
`&mut self` in the poll_next().
|
||||||
* MessageBody is not implemented for &'static [u8] anymore.
|
* MessageBody is not implemented for &'static [u8] anymore.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Allow `SameSite=None` cookies to be sent in a response.
|
* Allow `SameSite=None` cookies to be sent in a response.
|
||||||
|
|
||||||
## [1.0.1] - 2019-12-20
|
|
||||||
|
|
||||||
|
## 1.0.1 - 2019-12-20
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Poll upgrade service's readiness from HTTP service handlers
|
* Poll upgrade service's readiness from HTTP service handlers
|
||||||
|
|
||||||
* Replace brotli with brotli2 #1224
|
* Replace brotli with brotli2 #1224
|
||||||
|
|
||||||
## [1.0.0] - 2019-12-13
|
|
||||||
|
|
||||||
|
## 1.0.0 - 2019-12-13
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Add websockets continuation frame support
|
* Add websockets continuation frame support
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Replace `flate2-xxx` features with `compress`
|
* Replace `flate2-xxx` features with `compress`
|
||||||
|
|
||||||
## [1.0.0-alpha.5] - 2019-12-09
|
|
||||||
|
|
||||||
|
## 1.0.0-alpha.5 - 2019-12-09
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Check `Upgrade` service readiness before calling it
|
* Check `Upgrade` service readiness before calling it
|
||||||
|
* Fix buffer remaining capacity calculation
|
||||||
* Fix buffer remaining capacity calcualtion
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Websockets: Ping and Pong should have binary data #1049
|
* Websockets: Ping and Pong should have binary data #1049
|
||||||
|
|
||||||
## [1.0.0-alpha.4] - 2019-12-08
|
|
||||||
|
|
||||||
|
## 1.0.0-alpha.4 - 2019-12-08
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Add impl ResponseBuilder for Error
|
* Add impl ResponseBuilder for Error
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Use rust based brotli compression library
|
* Use rust based brotli compression library
|
||||||
|
|
||||||
## [1.0.0-alpha.3] - 2019-12-07
|
## 1.0.0-alpha.3 - 2019-12-07
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Migrate to tokio 0.2
|
* Migrate to tokio 0.2
|
||||||
|
|
||||||
* Migrate to `std::future`
|
* Migrate to `std::future`
|
||||||
|
|
||||||
|
|
||||||
## [0.2.11] - 2019-11-06
|
## 0.2.11 - 2019-11-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()
|
* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()
|
||||||
|
* Add an additional `filename*` param in the `Content-Disposition` header of
|
||||||
* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151)
|
`actix_files::NamedFile` to be more compatible. (#1151)
|
||||||
|
|
||||||
* Allow to use `std::convert::Infallible` as `actix_http::error::Error`
|
* Allow to use `std::convert::Infallible` as `actix_http::error::Error`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain;
|
||||||
|
charset=utf-8` header [#1118]
|
||||||
|
|
||||||
* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header #1118
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
|
||||||
|
|
||||||
## [0.2.10] - 2019-09-11
|
## 0.2.10 - 2019-09-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
* Add support for sending HTTP requests with `Rc<RequestHead>` in addition to sending HTTP requests
|
||||||
* Add support for sending HTTP requests with `Rc<RequestHead>` in addition to sending HTTP requests with `RequestHead`
|
with `RequestHead`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* h2 will use error response #1080
|
* h2 will use error response #1080
|
||||||
|
|
||||||
* on_connect result isn't added to request extensions for http2 requests #1009
|
* on_connect result isn't added to request extensions for http2 requests #1009
|
||||||
|
|
||||||
|
|
||||||
## [0.2.9] - 2019-08-13
|
## 0.2.9 - 2019-08-13
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation
|
* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation
|
||||||
|
|
||||||
* Update percent-encoding to 2.1
|
* Update percent-encoding to 2.1
|
||||||
|
|
||||||
* Update serde_urlencoded to 0.6.1
|
* Update serde_urlencoded to 0.6.1
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031)
|
* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031)
|
||||||
|
|
||||||
|
|
||||||
## [0.2.8] - 2019-08-01
|
## 0.2.8 - 2019-08-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Add `rustls` support
|
* Add `rustls` support
|
||||||
|
|
||||||
* Add `Clone` impl for `HeaderMap`
|
* Add `Clone` impl for `HeaderMap`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* awc client panic #1016
|
* awc client panic #1016
|
||||||
|
* Invalid response with compression middleware enabled, but compression-related features
|
||||||
* Invalid response with compression middleware enabled, but compression-related features disabled #997
|
disabled #997
|
||||||
|
|
||||||
|
|
||||||
## [0.2.7] - 2019-07-18
|
## 0.2.7 - 2019-07-18
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Add support for downcasting response errors #986
|
* Add support for downcasting response errors #986
|
||||||
|
|
||||||
|
|
||||||
## [0.2.6] - 2019-07-17
|
## 0.2.6 - 2019-07-17
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Replace `ClonableService` with local copy
|
* Replace `ClonableService` with local copy
|
||||||
|
|
||||||
* Upgrade `rand` dependency version to 0.7
|
* Upgrade `rand` dependency version to 0.7
|
||||||
|
|
||||||
|
|
||||||
## [0.2.5] - 2019-06-28
|
## 0.2.5 - 2019-06-28
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946
|
* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Use `encoding_rs` crate instead of unmaintained `encoding` crate
|
* Use `encoding_rs` crate instead of unmaintained `encoding` crate
|
||||||
|
|
||||||
* Add `Copy` and `Clone` impls for `ws::Codec`
|
* Add `Copy` and `Clone` impls for `ws::Codec`
|
||||||
|
|
||||||
|
|
||||||
## [0.2.4] - 2019-06-16
|
## 0.2.4 - 2019-06-16
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Do not compress NoContent (204) responses #918
|
* Do not compress NoContent (204) responses #918
|
||||||
|
|
||||||
|
|
||||||
## [0.2.3] - 2019-06-02
|
## 0.2.3 - 2019-06-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Debug impl for ResponseBuilder
|
* Debug impl for ResponseBuilder
|
||||||
|
|
||||||
* From SizedStream and BodyStream for Body
|
* From SizedStream and BodyStream for Body
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* SizedStream uses u64
|
* SizedStream uses u64
|
||||||
|
|
||||||
|
|
||||||
## [0.2.2] - 2019-05-29
|
## 0.2.2 - 2019-05-29
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Parse incoming stream before closing stream on disconnect #868
|
* Parse incoming stream before closing stream on disconnect #868
|
||||||
|
|
||||||
|
|
||||||
## [0.2.1] - 2019-05-25
|
## 0.2.1 - 2019-05-25
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Handle socket read disconnect
|
* Handle socket read disconnect
|
||||||
|
|
||||||
|
|
||||||
## [0.2.0] - 2019-05-12
|
## 0.2.0 - 2019-05-12
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Update actix-service to 0.4
|
* Update actix-service to 0.4
|
||||||
|
|
||||||
* Expect and upgrade services accept `ServerConfig` config.
|
* Expect and upgrade services accept `ServerConfig` config.
|
||||||
|
|
||||||
### Deleted
|
### Deleted
|
||||||
|
|
||||||
* `OneRequest` service
|
* `OneRequest` service
|
||||||
|
|
||||||
|
|
||||||
## [0.1.5] - 2019-05-04
|
## 0.1.5 - 2019-05-04
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Clean up response extensions in response pool #817
|
* Clean up response extensions in response pool #817
|
||||||
|
|
||||||
|
|
||||||
## [0.1.4] - 2019-04-24
|
## 0.1.4 - 2019-04-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Allow to render h1 request headers in `Camel-Case`
|
* Allow to render h1 request headers in `Camel-Case`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Read until eof for http/1.0 responses #771
|
* Read until eof for http/1.0 responses #771
|
||||||
|
|
||||||
|
|
||||||
## [0.1.3] - 2019-04-23
|
## 0.1.3 - 2019-04-23
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Fix http client pool management
|
* Fix http client pool management
|
||||||
|
|
||||||
* Fix http client wait queue management #794
|
* Fix http client wait queue management #794
|
||||||
|
|
||||||
|
|
||||||
## [0.1.2] - 2019-04-23
|
## 0.1.2 - 2019-04-23
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Fix BorrowMutError panic in client connector #793
|
* Fix BorrowMutError panic in client connector #793
|
||||||
|
|
||||||
|
|
||||||
## [0.1.1] - 2019-04-19
|
## 0.1.1 - 2019-04-19
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Cookie::max_age() accepts value in seconds
|
* Cookie::max_age() accepts value in seconds
|
||||||
|
|
||||||
* Cookie::max_age_time() accepts value in time::Duration
|
* Cookie::max_age_time() accepts value in time::Duration
|
||||||
|
|
||||||
* Allow to specify server address for client connector
|
* Allow to specify server address for client connector
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0] - 2019-04-16
|
## 0.1.0 - 2019-04-16
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr`
|
* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr`
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* `actix_http::encoding` always available
|
* `actix_http::encoding` always available
|
||||||
|
|
||||||
* use trust-dns-resolver 0.11.0
|
* use trust-dns-resolver 0.11.0
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.5] - 2019-04-12
|
## 0.1.0-alpha.5 - 2019-04-12
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Allow to use custom service for upgrade requests
|
* Allow to use custom service for upgrade requests
|
||||||
|
|
||||||
* Added `h1::SendResponse` future.
|
* Added `h1::SendResponse` future.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* MessageBody::length() renamed to MessageBody::size() for consistency
|
* MessageBody::length() renamed to MessageBody::size() for consistency
|
||||||
|
|
||||||
* ws handshake verification functions take RequestHead instead of Request
|
* ws handshake verification functions take RequestHead instead of Request
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.4] - 2019-04-08
|
## 0.1.0-alpha.4 - 2019-04-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Allow to use custom `Expect` handler
|
* Allow to use custom `Expect` handler
|
||||||
|
|
||||||
* Add minimal `std::error::Error` impl for `Error`
|
* Add minimal `std::error::Error` impl for `Error`
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Export IntoHeaderValue
|
* Export IntoHeaderValue
|
||||||
|
|
||||||
* Render error and return as response body
|
* Render error and return as response body
|
||||||
|
* Use thread pool for response body compression
|
||||||
* Use thread pool for response body comression
|
|
||||||
|
|
||||||
### Deleted
|
### Deleted
|
||||||
|
|
||||||
* Removed PayloadBuffer
|
* Removed PayloadBuffer
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.3] - 2019-04-02
|
## 0.1.0-alpha.3 - 2019-04-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Warn when an unsealed private cookie isn't valid UTF-8
|
* Warn when an unsealed private cookie isn't valid UTF-8
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Rust 1.31.0 compatibility
|
* Rust 1.31.0 compatibility
|
||||||
|
|
||||||
* Preallocate read buffer for h1 codec
|
* Preallocate read buffer for h1 codec
|
||||||
|
|
||||||
* Detect socket disconnection during protocol selection
|
* Detect socket disconnection during protocol selection
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.2] - 2019-03-29
|
## 0.1.0-alpha.2 - 2019-03-29
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Added ws::Message::Nop, no-op websockets message
|
* Added ws::Message::Nop, no-op websockets message
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
* Do not use thread pool for decompression if chunk size is smaller than 2048.
|
||||||
* Do not use thread pool for decomression if chunk size is smaller than 2048.
|
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.1] - 2019-03-28
|
## 0.1.0-alpha.1 - 2019-03-28
|
||||||
|
|
||||||
* Initial impl
|
* Initial impl
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "3.0.0-beta.1"
|
version = "3.0.0-beta.3"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "HTTP primitives for the Actix ecosystem"
|
description = "HTTP primitives for the Actix ecosystem"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -15,7 +15,7 @@ license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["openssl", "rustls", "compress", "secure-cookies", "actors"]
|
features = ["openssl", "rustls", "compress", "secure-cookies"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "actix_http"
|
name = "actix_http"
|
||||||
|
@ -36,8 +36,8 @@ compress = ["flate2", "brotli2"]
|
||||||
# support for secure cookies
|
# support for secure cookies
|
||||||
secure-cookies = ["cookie/secure"]
|
secure-cookies = ["cookie/secure"]
|
||||||
|
|
||||||
# support for actix Actor messages
|
# trust-dns as client dns resolver
|
||||||
actors = ["actix"]
|
trust-dns = ["trust-dns-resolver"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "2.0.0-beta.4"
|
actix-service = "2.0.0-beta.4"
|
||||||
|
@ -45,7 +45,6 @@ actix-codec = "0.4.0-beta.1"
|
||||||
actix-utils = "3.0.0-beta.2"
|
actix-utils = "3.0.0-beta.2"
|
||||||
actix-rt = "2"
|
actix-rt = "2"
|
||||||
actix-tls = "3.0.0-beta.2"
|
actix-tls = "3.0.0-beta.2"
|
||||||
actix = { version = "0.11.0-beta.2", default-features = false, optional = true }
|
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bitflags = "1.2"
|
bitflags = "1.2"
|
||||||
|
@ -53,7 +52,6 @@ bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
cookie = { version = "0.14.1", features = ["percent-encode"] }
|
cookie = { version = "0.14.1", features = ["percent-encode"] }
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
either = "1.5.3"
|
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
|
@ -84,9 +82,11 @@ time = { version = "0.2.23", default-features = false, features = ["std"] }
|
||||||
brotli2 = { version="0.3.2", optional = true }
|
brotli2 = { version="0.3.2", optional = true }
|
||||||
flate2 = { version = "1.0.13", optional = true }
|
flate2 = { version = "1.0.13", optional = true }
|
||||||
|
|
||||||
|
trust-dns-resolver = { version = "0.20.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-server = "2.0.0-beta.3"
|
actix-server = "2.0.0-beta.3"
|
||||||
actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] }
|
||||||
actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] }
|
actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] }
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
|
@ -94,6 +94,11 @@ serde_derive = "1.0"
|
||||||
tls-openssl = { version = "0.10", package = "openssl" }
|
tls-openssl = { version = "0.10", package = "openssl" }
|
||||||
tls-rustls = { version = "0.19", package = "rustls" }
|
tls-rustls = { version = "0.19", package = "rustls" }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dev-dependencies.tls-openssl]
|
||||||
|
version = "0.10.9"
|
||||||
|
package = "openssl"
|
||||||
|
features = ["vendored"]
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "write-camel-case"
|
name = "write-camel-case"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
|
@ -3,10 +3,13 @@
|
||||||
> 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/2.2.0)
|
[](https://docs.rs/actix-http/3.0.0-beta.3)
|
||||||

|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||
[](https://deps.rs/crate/actix-http/2.2.0)
|

|
||||||
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
<br />
|
||||||
|
[](https://deps.rs/crate/actix-http/3.0.0-beta.3)
|
||||||
|
[](https://crates.io/crates/actix-http)
|
||||||
|
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
use std::pin::Pin;
|
//! Traits and structures to aid consuming and writing HTTP payloads.
|
||||||
use std::task::{Context, Poll};
|
|
||||||
use std::{fmt, mem};
|
use std::{
|
||||||
|
fmt, mem,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
|
@ -8,8 +12,8 @@ use pin_project::pin_project;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
|
/// Body size hint.
|
||||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||||
/// Body size hint
|
|
||||||
pub enum BodySize {
|
pub enum BodySize {
|
||||||
None,
|
None,
|
||||||
Empty,
|
Empty,
|
||||||
|
@ -23,7 +27,7 @@ impl BodySize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type that provides this trait can be streamed to a peer.
|
/// Type that implement this trait can be streamed to a peer.
|
||||||
pub trait MessageBody {
|
pub trait MessageBody {
|
||||||
fn size(&self) -> BodySize;
|
fn size(&self) -> BodySize;
|
||||||
|
|
||||||
|
@ -80,7 +84,7 @@ impl ResponseBody<Body> {
|
||||||
|
|
||||||
impl<B> ResponseBody<B> {
|
impl<B> ResponseBody<B> {
|
||||||
pub fn take_body(&mut self) -> ResponseBody<B> {
|
pub fn take_body(&mut self) -> ResponseBody<B> {
|
||||||
std::mem::replace(self, ResponseBody::Other(Body::None))
|
mem::replace(self, ResponseBody::Other(Body::None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +131,7 @@ impl<B: MessageBody> Stream for ResponseBody<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents various types of http message body.
|
/// Represents various types of HTTP message body.
|
||||||
pub enum Body {
|
pub enum Body {
|
||||||
/// Empty response. `Content-Length` header is not set.
|
/// Empty response. `Content-Length` header is not set.
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -6,7 +6,7 @@ use actix_codec::{AsyncRead, AsyncWrite};
|
||||||
use actix_rt::net::TcpStream;
|
use actix_rt::net::TcpStream;
|
||||||
use actix_service::{apply_fn, Service, ServiceExt};
|
use actix_service::{apply_fn, Service, ServiceExt};
|
||||||
use actix_tls::connect::{
|
use actix_tls::connect::{
|
||||||
default_connector, Connect as TcpConnect, Connection as TcpConnection,
|
new_connector, Connect as TcpConnect, Connection as TcpConnection, Resolver,
|
||||||
};
|
};
|
||||||
use actix_utils::timeout::{TimeoutError, TimeoutService};
|
use actix_utils::timeout::{TimeoutError, TimeoutService};
|
||||||
use http::Uri;
|
use http::Uri;
|
||||||
|
@ -19,7 +19,6 @@ use super::Connect;
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector;
|
use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector;
|
||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
use actix_tls::connect::ssl::rustls::ClientConfig;
|
use actix_tls::connect::ssl::rustls::ClientConfig;
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
|
@ -35,7 +34,8 @@ enum SslConnector {
|
||||||
#[cfg(not(any(feature = "openssl", feature = "rustls")))]
|
#[cfg(not(any(feature = "openssl", feature = "rustls")))]
|
||||||
type SslConnector = ();
|
type SslConnector = ();
|
||||||
|
|
||||||
/// Manages http client network connectivity
|
/// Manages HTTP client network connectivity.
|
||||||
|
///
|
||||||
/// The `Connector` type uses a builder-like combinator pattern for service
|
/// The `Connector` type uses a builder-like combinator pattern for service
|
||||||
/// construction that finishes by calling the `.finish()` method.
|
/// construction that finishes by calling the `.finish()` method.
|
||||||
///
|
///
|
||||||
|
@ -70,7 +70,7 @@ impl Connector<(), ()> {
|
||||||
> {
|
> {
|
||||||
Connector {
|
Connector {
|
||||||
ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
|
ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
|
||||||
connector: default_connector(),
|
connector: new_connector(resolver::resolver()),
|
||||||
config: ConnectorConfig::default(),
|
config: ConnectorConfig::default(),
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
|
@ -161,8 +161,9 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maximum supported http major version
|
/// Maximum supported HTTP major version.
|
||||||
/// Supported versions http/1.1, http/2
|
///
|
||||||
|
/// Supported versions are HTTP/1.1 and HTTP/2.
|
||||||
pub fn max_http_version(mut self, val: http::Version) -> Self {
|
pub fn max_http_version(mut self, val: http::Version) -> Self {
|
||||||
let versions = match val {
|
let versions = match val {
|
||||||
http::Version::HTTP_11 => vec![b"http/1.1".to_vec()],
|
http::Version::HTTP_11 => vec![b"http/1.1".to_vec()],
|
||||||
|
@ -532,3 +533,82 @@ mod connect_impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "trust-dns"))]
|
||||||
|
mod resolver {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) fn resolver() -> Resolver {
|
||||||
|
Resolver::Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "trust-dns")]
|
||||||
|
mod resolver {
|
||||||
|
use std::{cell::RefCell, net::SocketAddr};
|
||||||
|
|
||||||
|
use actix_tls::connect::Resolve;
|
||||||
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
use trust_dns_resolver::{
|
||||||
|
config::{ResolverConfig, ResolverOpts},
|
||||||
|
system_conf::read_system_conf,
|
||||||
|
TokioAsyncResolver,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) fn resolver() -> Resolver {
|
||||||
|
// new type for impl Resolve trait for TokioAsyncResolver.
|
||||||
|
struct TrustDnsResolver(TokioAsyncResolver);
|
||||||
|
|
||||||
|
impl Resolve for TrustDnsResolver {
|
||||||
|
fn lookup<'a>(
|
||||||
|
&'a self,
|
||||||
|
host: &'a str,
|
||||||
|
port: u16,
|
||||||
|
) -> LocalBoxFuture<'a, Result<Vec<SocketAddr>, Box<dyn std::error::Error>>>
|
||||||
|
{
|
||||||
|
Box::pin(async move {
|
||||||
|
let res = self
|
||||||
|
.0
|
||||||
|
.lookup_ip(host)
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|ip| SocketAddr::new(ip, port))
|
||||||
|
.collect();
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dns struct is cached in thread local.
|
||||||
|
// so new client constructor can reuse the existing dns resolver.
|
||||||
|
thread_local! {
|
||||||
|
static TRUST_DNS_RESOLVER: RefCell<Option<Resolver>> = RefCell::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get from thread local or construct a new trust-dns resolver.
|
||||||
|
TRUST_DNS_RESOLVER.with(|local| {
|
||||||
|
let resolver = local.borrow().as_ref().map(Clone::clone);
|
||||||
|
match resolver {
|
||||||
|
Some(resolver) => resolver,
|
||||||
|
None => {
|
||||||
|
let (cfg, opts) = match read_system_conf() {
|
||||||
|
Ok((cfg, opts)) => (cfg, opts),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("TRust-DNS can not load system config: {}", e);
|
||||||
|
(ResolverConfig::default(), ResolverOpts::default())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let resolver = TokioAsyncResolver::tokio(cfg, opts).unwrap();
|
||||||
|
|
||||||
|
// box trust dns resolver and put it in thread local.
|
||||||
|
let resolver = Resolver::new_custom(TrustDnsResolver(resolver));
|
||||||
|
*local.borrow_mut() = Some(resolver.clone());
|
||||||
|
resolver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -65,13 +65,16 @@ impl From<actix_tls::connect::ConnectError> for ConnectError {
|
||||||
|
|
||||||
#[derive(Debug, Display, From)]
|
#[derive(Debug, Display, From)]
|
||||||
pub enum InvalidUrl {
|
pub enum InvalidUrl {
|
||||||
#[display(fmt = "Missing url scheme")]
|
#[display(fmt = "Missing URL scheme")]
|
||||||
MissingScheme,
|
MissingScheme,
|
||||||
#[display(fmt = "Unknown url scheme")]
|
|
||||||
|
#[display(fmt = "Unknown URL scheme")]
|
||||||
UnknownScheme,
|
UnknownScheme,
|
||||||
|
|
||||||
#[display(fmt = "Missing host name")]
|
#[display(fmt = "Missing host name")]
|
||||||
MissingHost,
|
MissingHost,
|
||||||
#[display(fmt = "Url parse error: {}", _0)]
|
|
||||||
|
#[display(fmt = "URL parse error: {}", _0)]
|
||||||
HttpError(http::Error),
|
HttpError(http::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,25 +86,33 @@ pub enum SendRequestError {
|
||||||
/// Invalid URL
|
/// Invalid URL
|
||||||
#[display(fmt = "Invalid URL: {}", _0)]
|
#[display(fmt = "Invalid URL: {}", _0)]
|
||||||
Url(InvalidUrl),
|
Url(InvalidUrl),
|
||||||
|
|
||||||
/// Failed to connect to host
|
/// Failed to connect to host
|
||||||
#[display(fmt = "Failed to connect to host: {}", _0)]
|
#[display(fmt = "Failed to connect to host: {}", _0)]
|
||||||
Connect(ConnectError),
|
Connect(ConnectError),
|
||||||
|
|
||||||
/// Error sending request
|
/// Error sending request
|
||||||
Send(io::Error),
|
Send(io::Error),
|
||||||
|
|
||||||
/// Error parsing response
|
/// Error parsing response
|
||||||
Response(ParseError),
|
Response(ParseError),
|
||||||
|
|
||||||
/// Http error
|
/// Http error
|
||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
Http(HttpError),
|
Http(HttpError),
|
||||||
|
|
||||||
/// Http2 error
|
/// Http2 error
|
||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
H2(h2::Error),
|
H2(h2::Error),
|
||||||
|
|
||||||
/// Response took too long
|
/// Response took too long
|
||||||
#[display(fmt = "Timeout while waiting for response")]
|
#[display(fmt = "Timeout while waiting for response")]
|
||||||
Timeout,
|
Timeout,
|
||||||
/// Tunnels are not supported for http2 connection
|
|
||||||
|
/// Tunnels are not supported for HTTP/2 connection
|
||||||
#[display(fmt = "Tunnels are not supported for http2 connection")]
|
#[display(fmt = "Tunnels are not supported for http2 connection")]
|
||||||
TunnelNotSupported,
|
TunnelNotSupported,
|
||||||
|
|
||||||
/// Error sending request body
|
/// Error sending request body
|
||||||
Body(Error),
|
Body(Error),
|
||||||
}
|
}
|
||||||
|
@ -127,7 +138,8 @@ pub enum FreezeRequestError {
|
||||||
/// Invalid URL
|
/// Invalid URL
|
||||||
#[display(fmt = "Invalid URL: {}", _0)]
|
#[display(fmt = "Invalid URL: {}", _0)]
|
||||||
Url(InvalidUrl),
|
Url(InvalidUrl),
|
||||||
/// Http error
|
|
||||||
|
/// HTTP error
|
||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
Http(HttpError),
|
Http(HttpError),
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,11 +48,11 @@ where
|
||||||
match wrt.get_mut().split().freeze().try_into_value() {
|
match wrt.get_mut().split().freeze().try_into_value() {
|
||||||
Ok(value) => match head {
|
Ok(value) => match head {
|
||||||
RequestHeadType::Owned(ref mut head) => {
|
RequestHeadType::Owned(ref mut head) => {
|
||||||
head.headers.insert(HOST, value)
|
head.headers.insert(HOST, value);
|
||||||
}
|
}
|
||||||
RequestHeadType::Rc(_, ref mut extra_headers) => {
|
RequestHeadType::Rc(_, ref mut extra_headers) => {
|
||||||
let headers = extra_headers.get_or_insert(HeaderMap::new());
|
let headers = extra_headers.get_or_insert(HeaderMap::new());
|
||||||
headers.insert(HOST, value)
|
headers.insert(HOST, value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => log::error!("Can not set HOST header {}", e),
|
Err(e) => log::error!("Can not set HOST header {}", e),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//! Http client api
|
//! HTTP client.
|
||||||
|
|
||||||
use http::Uri;
|
use http::Uri;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
|
|
@ -4,9 +4,11 @@ use std::rc::Rc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{fmt, net};
|
use std::{fmt, net};
|
||||||
|
|
||||||
use actix_rt::time::{sleep, sleep_until, Instant, Sleep};
|
use actix_rt::{
|
||||||
|
task::JoinHandle,
|
||||||
|
time::{interval, sleep_until, Instant, Sleep},
|
||||||
|
};
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use futures_util::{future, FutureExt};
|
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
/// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
/// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
||||||
|
@ -49,7 +51,7 @@ struct Inner {
|
||||||
ka_enabled: bool,
|
ka_enabled: bool,
|
||||||
secure: bool,
|
secure: bool,
|
||||||
local_addr: Option<std::net::SocketAddr>,
|
local_addr: Option<std::net::SocketAddr>,
|
||||||
timer: DateService,
|
date_service: DateService,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for ServiceConfig {
|
impl Clone for ServiceConfig {
|
||||||
|
@ -91,41 +93,41 @@ impl ServiceConfig {
|
||||||
client_disconnect,
|
client_disconnect,
|
||||||
secure,
|
secure,
|
||||||
local_addr,
|
local_addr,
|
||||||
timer: DateService::new(),
|
date_service: DateService::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if connection is secure (HTTPS)
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Returns true if connection is secure(https)
|
|
||||||
pub fn secure(&self) -> bool {
|
pub fn secure(&self) -> bool {
|
||||||
self.0.secure
|
self.0.secure
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Returns the local address that this server is bound to.
|
/// Returns the local address that this server is bound to.
|
||||||
|
#[inline]
|
||||||
pub fn local_addr(&self) -> Option<net::SocketAddr> {
|
pub fn local_addr(&self) -> Option<net::SocketAddr> {
|
||||||
self.0.local_addr
|
self.0.local_addr
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Keep alive duration if configured.
|
/// Keep alive duration if configured.
|
||||||
|
#[inline]
|
||||||
pub fn keep_alive(&self) -> Option<Duration> {
|
pub fn keep_alive(&self) -> Option<Duration> {
|
||||||
self.0.keep_alive
|
self.0.keep_alive
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Return state of connection keep-alive functionality
|
/// Return state of connection keep-alive functionality
|
||||||
|
#[inline]
|
||||||
pub fn keep_alive_enabled(&self) -> bool {
|
pub fn keep_alive_enabled(&self) -> bool {
|
||||||
self.0.ka_enabled
|
self.0.ka_enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Client timeout for first request.
|
/// Client timeout for first request.
|
||||||
|
#[inline]
|
||||||
pub fn client_timer(&self) -> Option<Sleep> {
|
pub fn client_timer(&self) -> Option<Sleep> {
|
||||||
let delay_time = self.0.client_timeout;
|
let delay_time = self.0.client_timeout;
|
||||||
if delay_time != 0 {
|
if delay_time != 0 {
|
||||||
Some(sleep_until(
|
Some(sleep_until(
|
||||||
self.0.timer.now() + Duration::from_millis(delay_time),
|
self.0.date_service.now() + Duration::from_millis(delay_time),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -136,7 +138,7 @@ impl ServiceConfig {
|
||||||
pub fn client_timer_expire(&self) -> Option<Instant> {
|
pub fn client_timer_expire(&self) -> Option<Instant> {
|
||||||
let delay = self.0.client_timeout;
|
let delay = self.0.client_timeout;
|
||||||
if delay != 0 {
|
if delay != 0 {
|
||||||
Some(self.0.timer.now() + Duration::from_millis(delay))
|
Some(self.0.date_service.now() + Duration::from_millis(delay))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -146,7 +148,7 @@ impl ServiceConfig {
|
||||||
pub fn client_disconnect_timer(&self) -> Option<Instant> {
|
pub fn client_disconnect_timer(&self) -> Option<Instant> {
|
||||||
let delay = self.0.client_disconnect;
|
let delay = self.0.client_disconnect;
|
||||||
if delay != 0 {
|
if delay != 0 {
|
||||||
Some(self.0.timer.now() + Duration::from_millis(delay))
|
Some(self.0.date_service.now() + Duration::from_millis(delay))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -156,7 +158,7 @@ impl ServiceConfig {
|
||||||
/// Return keep-alive timer delay is configured.
|
/// Return keep-alive timer delay is configured.
|
||||||
pub fn keep_alive_timer(&self) -> Option<Sleep> {
|
pub fn keep_alive_timer(&self) -> Option<Sleep> {
|
||||||
if let Some(ka) = self.0.keep_alive {
|
if let Some(ka) = self.0.keep_alive {
|
||||||
Some(sleep_until(self.0.timer.now() + ka))
|
Some(sleep_until(self.0.date_service.now() + ka))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -165,7 +167,7 @@ impl ServiceConfig {
|
||||||
/// Keep-alive expire time
|
/// Keep-alive expire time
|
||||||
pub fn keep_alive_expire(&self) -> Option<Instant> {
|
pub fn keep_alive_expire(&self) -> Option<Instant> {
|
||||||
if let Some(ka) = self.0.keep_alive {
|
if let Some(ka) = self.0.keep_alive {
|
||||||
Some(self.0.timer.now() + ka)
|
Some(self.0.date_service.now() + ka)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -173,7 +175,7 @@ impl ServiceConfig {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn now(&self) -> Instant {
|
pub(crate) fn now(&self) -> Instant {
|
||||||
self.0.timer.now()
|
self.0.date_service.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -181,7 +183,7 @@ impl ServiceConfig {
|
||||||
let mut buf: [u8; 39] = [0; 39];
|
let mut buf: [u8; 39] = [0; 39];
|
||||||
buf[..6].copy_from_slice(b"date: ");
|
buf[..6].copy_from_slice(b"date: ");
|
||||||
self.0
|
self.0
|
||||||
.timer
|
.date_service
|
||||||
.set_date(|date| buf[6..35].copy_from_slice(&date.bytes));
|
.set_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\r\n");
|
||||||
dst.extend_from_slice(&buf);
|
dst.extend_from_slice(&buf);
|
||||||
|
@ -189,7 +191,7 @@ impl ServiceConfig {
|
||||||
|
|
||||||
pub(crate) fn set_date_header(&self, dst: &mut BytesMut) {
|
pub(crate) fn set_date_header(&self, dst: &mut BytesMut) {
|
||||||
self.0
|
self.0
|
||||||
.timer
|
.date_service
|
||||||
.set_date(|date| dst.extend_from_slice(&date.bytes));
|
.set_date(|date| dst.extend_from_slice(&date.bytes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,57 +232,103 @@ impl fmt::Write for Date {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
/// Service for update Date and Instant periodically at 500 millis interval.
|
||||||
struct DateService(Rc<DateServiceInner>);
|
struct DateService {
|
||||||
|
current: Rc<Cell<(Date, Instant)>>,
|
||||||
struct DateServiceInner {
|
handle: JoinHandle<()>,
|
||||||
current: Cell<Option<(Date, Instant)>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DateServiceInner {
|
impl Drop for DateService {
|
||||||
fn new() -> Self {
|
fn drop(&mut self) {
|
||||||
DateServiceInner {
|
// stop the timer update async task on drop.
|
||||||
current: Cell::new(None),
|
self.handle.abort();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset(&self) {
|
|
||||||
self.current.take();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&self) {
|
|
||||||
let now = Instant::now();
|
|
||||||
let date = Date::new();
|
|
||||||
self.current.set(Some((date, now)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DateService {
|
impl DateService {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
DateService(Rc::new(DateServiceInner::new()))
|
// 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();
|
||||||
|
|
||||||
fn check_date(&self) {
|
let mut interval = interval(Duration::from_millis(500));
|
||||||
if self.0.current.get().is_none() {
|
loop {
|
||||||
self.0.update();
|
let now = interval.tick().await;
|
||||||
|
let date = Date::new();
|
||||||
|
current_clone.set((date, now));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// periodic date update
|
DateService { current, handle }
|
||||||
let s = self.clone();
|
|
||||||
actix_rt::spawn(sleep(Duration::from_millis(500)).then(move |_| {
|
|
||||||
s.0.reset();
|
|
||||||
future::ready(())
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn now(&self) -> Instant {
|
fn now(&self) -> Instant {
|
||||||
self.check_date();
|
self.current.get().1
|
||||||
self.0.current.get().unwrap().1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_date<F: FnMut(&Date)>(&self, mut f: F) {
|
fn set_date<F: FnMut(&Date)>(&self, mut f: F) {
|
||||||
self.check_date();
|
f(&self.current.get().0);
|
||||||
f(&self.0.current.get().unwrap().0);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move to a util module for testing all spawn handle drop style tasks.
|
||||||
|
#[cfg(test)]
|
||||||
|
/// 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
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// # Panic:
|
||||||
|
///
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,14 +336,53 @@ impl DateService {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
// Test modifying the date from within the closure
|
use actix_rt::task::yield_now;
|
||||||
// passed to `set_date`
|
|
||||||
#[test]
|
#[actix_rt::test]
|
||||||
fn test_evil_date() {
|
async fn test_date_service_update() {
|
||||||
let service = DateService::new();
|
let settings = ServiceConfig::new(KeepAlive::Os, 0, 0, false, None);
|
||||||
// Make sure that `check_date` doesn't try to spawn a task
|
|
||||||
service.0.update();
|
yield_now().await;
|
||||||
service.set_date(|_| service.0.reset());
|
|
||||||
|
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||||
|
settings.set_date(&mut buf1);
|
||||||
|
let now1 = settings.now();
|
||||||
|
|
||||||
|
sleep_until(Instant::now() + Duration::from_secs(2)).await;
|
||||||
|
yield_now().await;
|
||||||
|
|
||||||
|
let now2 = settings.now();
|
||||||
|
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||||
|
settings.set_date(&mut buf2);
|
||||||
|
|
||||||
|
assert_ne!(now1, now2);
|
||||||
|
|
||||||
|
assert_ne!(buf1, buf2);
|
||||||
|
|
||||||
|
drop(settings);
|
||||||
|
assert!(notify_on_drop::is_dropped());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_date_service_drop() {
|
||||||
|
let service = Rc::new(DateService::new());
|
||||||
|
|
||||||
|
// yield so date service have a chance to register the spawned timer update task.
|
||||||
|
yield_now().await;
|
||||||
|
|
||||||
|
let clone1 = service.clone();
|
||||||
|
let clone2 = service.clone();
|
||||||
|
let clone3 = service.clone();
|
||||||
|
|
||||||
|
drop(clone1);
|
||||||
|
assert_eq!(false, notify_on_drop::is_dropped());
|
||||||
|
drop(clone2);
|
||||||
|
assert_eq!(false, notify_on_drop::is_dropped());
|
||||||
|
drop(clone3);
|
||||||
|
assert_eq!(false, notify_on_drop::is_dropped());
|
||||||
|
|
||||||
|
drop(service);
|
||||||
|
assert!(notify_on_drop::is_dropped());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
use std::future::Future;
|
//! Stream decoders.
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::pin::Pin;
|
use std::{
|
||||||
use std::task::{Context, Poll};
|
future::Future,
|
||||||
|
io::{self, Write as _},
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||||
use brotli2::write::BrotliDecoder;
|
use brotli2::write::BrotliDecoder;
|
||||||
|
@ -9,11 +13,13 @@ use bytes::Bytes;
|
||||||
use flate2::write::{GzDecoder, ZlibDecoder};
|
use flate2::write::{GzDecoder, ZlibDecoder};
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
|
|
||||||
use super::Writer;
|
use crate::{
|
||||||
use crate::error::{BlockingError, PayloadError};
|
encoding::Writer,
|
||||||
use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING};
|
error::{BlockingError, PayloadError},
|
||||||
|
http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING},
|
||||||
|
};
|
||||||
|
|
||||||
const INPLACE: usize = 2049;
|
const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049;
|
||||||
|
|
||||||
pub struct Decoder<S> {
|
pub struct Decoder<S> {
|
||||||
decoder: Option<ContentDecoder>,
|
decoder: Option<ContentDecoder>,
|
||||||
|
@ -41,6 +47,7 @@ where
|
||||||
))),
|
))),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Decoder {
|
Decoder {
|
||||||
decoder,
|
decoder,
|
||||||
stream,
|
stream,
|
||||||
|
@ -53,15 +60,11 @@ where
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder<S> {
|
pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder<S> {
|
||||||
// check content-encoding
|
// check content-encoding
|
||||||
let encoding = if let Some(enc) = headers.get(&CONTENT_ENCODING) {
|
let encoding = headers
|
||||||
if let Ok(enc) = enc.to_str() {
|
.get(&CONTENT_ENCODING)
|
||||||
ContentEncoding::from(enc)
|
.and_then(|val| val.to_str().ok())
|
||||||
} else {
|
.map(ContentEncoding::from)
|
||||||
ContentEncoding::Identity
|
.unwrap_or(ContentEncoding::Identity);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ContentEncoding::Identity
|
|
||||||
};
|
|
||||||
|
|
||||||
Self::new(stream, encoding)
|
Self::new(stream, encoding)
|
||||||
}
|
}
|
||||||
|
@ -81,8 +84,10 @@ where
|
||||||
if let Some(ref mut fut) = self.fut {
|
if let Some(ref mut fut) = self.fut {
|
||||||
let (chunk, decoder) =
|
let (chunk, decoder) =
|
||||||
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
||||||
|
|
||||||
self.decoder = Some(decoder);
|
self.decoder = Some(decoder);
|
||||||
self.fut.take();
|
self.fut.take();
|
||||||
|
|
||||||
if let Some(chunk) = chunk {
|
if let Some(chunk) = chunk {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
|
@ -92,13 +97,15 @@ where
|
||||||
return Poll::Ready(None);
|
return Poll::Ready(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
match Pin::new(&mut self.stream).poll_next(cx) {
|
match ready!(Pin::new(&mut self.stream).poll_next(cx)) {
|
||||||
Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))),
|
Some(Err(err)) => return Poll::Ready(Some(Err(err))),
|
||||||
Poll::Ready(Some(Ok(chunk))) => {
|
|
||||||
|
Some(Ok(chunk)) => {
|
||||||
if let Some(mut decoder) = self.decoder.take() {
|
if let Some(mut decoder) = self.decoder.take() {
|
||||||
if chunk.len() < INPLACE {
|
if chunk.len() < MAX_CHUNK_SIZE_DECODE_IN_PLACE {
|
||||||
let chunk = decoder.feed_data(chunk)?;
|
let chunk = decoder.feed_data(chunk)?;
|
||||||
self.decoder = Some(decoder);
|
self.decoder = Some(decoder);
|
||||||
|
|
||||||
if let Some(chunk) = chunk {
|
if let Some(chunk) = chunk {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
|
@ -108,13 +115,16 @@ where
|
||||||
Ok((chunk, decoder))
|
Ok((chunk, decoder))
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Ready(None) => {
|
|
||||||
|
None => {
|
||||||
self.eof = true;
|
self.eof = true;
|
||||||
|
|
||||||
return if let Some(mut decoder) = self.decoder.take() {
|
return if let Some(mut decoder) = self.decoder.take() {
|
||||||
match decoder.feed_eof() {
|
match decoder.feed_eof() {
|
||||||
Ok(Some(res)) => Poll::Ready(Some(Ok(res))),
|
Ok(Some(res)) => Poll::Ready(Some(Ok(res))),
|
||||||
|
@ -125,10 +135,8 @@ where
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Poll::Pending => break,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Pending
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +152,7 @@ impl ContentDecoder {
|
||||||
ContentDecoder::Br(ref mut decoder) => match decoder.flush() {
|
ContentDecoder::Br(ref mut decoder) => match decoder.flush() {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
let b = decoder.get_mut().take();
|
let b = decoder.get_mut().take();
|
||||||
|
|
||||||
if !b.is_empty() {
|
if !b.is_empty() {
|
||||||
Ok(Some(b))
|
Ok(Some(b))
|
||||||
} else {
|
} else {
|
||||||
|
@ -152,9 +161,11 @@ impl ContentDecoder {
|
||||||
}
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
},
|
},
|
||||||
|
|
||||||
ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() {
|
ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let b = decoder.get_mut().take();
|
let b = decoder.get_mut().take();
|
||||||
|
|
||||||
if !b.is_empty() {
|
if !b.is_empty() {
|
||||||
Ok(Some(b))
|
Ok(Some(b))
|
||||||
} else {
|
} else {
|
||||||
|
@ -163,6 +174,7 @@ impl ContentDecoder {
|
||||||
}
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
},
|
},
|
||||||
|
|
||||||
ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() {
|
ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let b = decoder.get_mut().take();
|
let b = decoder.get_mut().take();
|
||||||
|
@ -183,6 +195,7 @@ impl ContentDecoder {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
decoder.flush()?;
|
decoder.flush()?;
|
||||||
let b = decoder.get_mut().take();
|
let b = decoder.get_mut().take();
|
||||||
|
|
||||||
if !b.is_empty() {
|
if !b.is_empty() {
|
||||||
Ok(Some(b))
|
Ok(Some(b))
|
||||||
} else {
|
} else {
|
||||||
|
@ -191,10 +204,12 @@ impl ContentDecoder {
|
||||||
}
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
},
|
},
|
||||||
|
|
||||||
ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) {
|
ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
decoder.flush()?;
|
decoder.flush()?;
|
||||||
let b = decoder.get_mut().take();
|
let b = decoder.get_mut().take();
|
||||||
|
|
||||||
if !b.is_empty() {
|
if !b.is_empty() {
|
||||||
Ok(Some(b))
|
Ok(Some(b))
|
||||||
} else {
|
} else {
|
||||||
|
@ -203,9 +218,11 @@ impl ContentDecoder {
|
||||||
}
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
},
|
},
|
||||||
|
|
||||||
ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
|
ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
decoder.flush()?;
|
decoder.flush()?;
|
||||||
|
|
||||||
let b = decoder.get_mut().take();
|
let b = decoder.get_mut().take();
|
||||||
if !b.is_empty() {
|
if !b.is_empty() {
|
||||||
Ok(Some(b))
|
Ok(Some(b))
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
//! Stream encoder
|
//! Stream encoders.
|
||||||
use std::future::Future;
|
|
||||||
use std::io::{self, Write};
|
use std::{
|
||||||
use std::pin::Pin;
|
future::Future,
|
||||||
use std::task::{Context, Poll};
|
io::{self, Write as _},
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||||
use brotli2::write::BrotliEncoder;
|
use brotli2::write::BrotliEncoder;
|
||||||
|
@ -11,15 +14,19 @@ use flate2::write::{GzEncoder, ZlibEncoder};
|
||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
use crate::body::{Body, BodySize, MessageBody, ResponseBody};
|
use crate::{
|
||||||
use crate::http::header::{ContentEncoding, CONTENT_ENCODING};
|
body::{Body, BodySize, MessageBody, ResponseBody},
|
||||||
use crate::http::{HeaderValue, StatusCode};
|
http::{
|
||||||
use crate::{Error, ResponseHead};
|
header::{ContentEncoding, CONTENT_ENCODING},
|
||||||
|
HeaderValue, StatusCode,
|
||||||
|
},
|
||||||
|
Error, ResponseHead,
|
||||||
|
};
|
||||||
|
|
||||||
use super::Writer;
|
use super::Writer;
|
||||||
use crate::error::BlockingError;
|
use crate::error::BlockingError;
|
||||||
|
|
||||||
const INPLACE: usize = 1024;
|
const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024;
|
||||||
|
|
||||||
#[pin_project]
|
#[pin_project]
|
||||||
pub struct Encoder<B> {
|
pub struct Encoder<B> {
|
||||||
|
@ -138,23 +145,28 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
|
||||||
if let Some(ref mut fut) = this.fut {
|
if let Some(ref mut fut) = this.fut {
|
||||||
let mut encoder =
|
let mut encoder =
|
||||||
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
||||||
|
|
||||||
let chunk = encoder.take();
|
let chunk = encoder.take();
|
||||||
*this.encoder = Some(encoder);
|
*this.encoder = Some(encoder);
|
||||||
this.fut.take();
|
this.fut.take();
|
||||||
|
|
||||||
if !chunk.is_empty() {
|
if !chunk.is_empty() {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = this.body.as_mut().poll_next(cx);
|
let result = ready!(this.body.as_mut().poll_next(cx));
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Poll::Ready(Some(Ok(chunk))) => {
|
Some(Err(err)) => return Poll::Ready(Some(Err(err))),
|
||||||
|
|
||||||
|
Some(Ok(chunk)) => {
|
||||||
if let Some(mut encoder) = this.encoder.take() {
|
if let Some(mut encoder) = this.encoder.take() {
|
||||||
if chunk.len() < INPLACE {
|
if chunk.len() < MAX_CHUNK_SIZE_ENCODE_IN_PLACE {
|
||||||
encoder.write(&chunk)?;
|
encoder.write(&chunk)?;
|
||||||
let chunk = encoder.take();
|
let chunk = encoder.take();
|
||||||
*this.encoder = Some(encoder);
|
*this.encoder = Some(encoder);
|
||||||
|
|
||||||
if !chunk.is_empty() {
|
if !chunk.is_empty() {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
|
@ -168,7 +180,8 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Ready(None) => {
|
|
||||||
|
None => {
|
||||||
if let Some(encoder) = this.encoder.take() {
|
if let Some(encoder) = this.encoder.take() {
|
||||||
let chunk = encoder.finish()?;
|
let chunk = encoder.finish()?;
|
||||||
if chunk.is_empty() {
|
if chunk.is_empty() {
|
||||||
|
@ -181,7 +194,6 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
|
||||||
return Poll::Ready(None);
|
return Poll::Ready(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val => return val,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//! Content-Encoding support
|
//! Content-Encoding support.
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
|
@ -38,7 +38,7 @@ pub type Result<T, E = Error> = result::Result<T, E>;
|
||||||
/// converting errors with `into()`.
|
/// converting errors with `into()`.
|
||||||
///
|
///
|
||||||
/// Whenever it is created from an external object a response error is created
|
/// Whenever it is created from an external object a response error is created
|
||||||
/// for it that can be used to create an http response from it this means that
|
/// for it that can be used to create an HTTP response from it this means that
|
||||||
/// if you have access to an actix `Error` you can always get a
|
/// if you have access to an actix `Error` you can always get a
|
||||||
/// `ResponseError` reference from it.
|
/// `ResponseError` reference from it.
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
|
@ -404,7 +404,7 @@ impl ResponseError for crate::cookie::ParseError {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Display, From)]
|
#[derive(Debug, Display, From)]
|
||||||
/// A set of errors that can occur during dispatching http requests
|
/// A set of errors that can occur during dispatching HTTP requests
|
||||||
pub enum DispatchError {
|
pub enum DispatchError {
|
||||||
/// Service error
|
/// Service error
|
||||||
Service(Error),
|
Service(Error),
|
||||||
|
@ -968,12 +968,6 @@ where
|
||||||
InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into()
|
InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "actors")]
|
|
||||||
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix::MailboxError`].
|
|
||||||
///
|
|
||||||
/// This is only supported when the feature `actors` is enabled.
|
|
||||||
impl ResponseError for actix::MailboxError {}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -199,10 +199,10 @@ mod tests {
|
||||||
use http::Method;
|
use http::Method;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::httpmessage::HttpMessage;
|
use crate::HttpMessage;
|
||||||
|
|
||||||
#[test]
|
#[actix_rt::test]
|
||||||
fn test_http_request_chunked_payload_and_next_message() {
|
async fn test_http_request_chunked_payload_and_next_message() {
|
||||||
let mut codec = Codec::default();
|
let mut codec = Codec::default();
|
||||||
|
|
||||||
let mut buf = BytesMut::from(
|
let mut buf = BytesMut::from(
|
||||||
|
|
|
@ -224,7 +224,7 @@ impl MessageType for Request {
|
||||||
let decoder = match length {
|
let decoder = match length {
|
||||||
PayloadLength::Payload(pl) => pl,
|
PayloadLength::Payload(pl) => pl,
|
||||||
PayloadLength::UpgradeWebSocket => {
|
PayloadLength::UpgradeWebSocket => {
|
||||||
// upgrade(websocket)
|
// upgrade (WebSocket)
|
||||||
PayloadType::Stream(PayloadDecoder::eof())
|
PayloadType::Stream(PayloadDecoder::eof())
|
||||||
}
|
}
|
||||||
PayloadLength::None => {
|
PayloadLength::None => {
|
||||||
|
@ -652,7 +652,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use crate::http::header::{HeaderName, SET_COOKIE};
|
use crate::http::header::{HeaderName, SET_COOKIE};
|
||||||
use crate::httpmessage::HttpMessage;
|
use crate::HttpMessage;
|
||||||
|
|
||||||
impl PayloadType {
|
impl PayloadType {
|
||||||
fn unwrap(self) -> PayloadDecoder {
|
fn unwrap(self) -> PayloadDecoder {
|
||||||
|
@ -830,8 +830,8 @@ mod tests {
|
||||||
.get_all(SET_COOKIE)
|
.get_all(SET_COOKIE)
|
||||||
.map(|v| v.to_str().unwrap().to_owned())
|
.map(|v| v.to_str().unwrap().to_owned())
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(val[1], "c1=cookie1");
|
assert_eq!(val[0], "c1=cookie1");
|
||||||
assert_eq!(val[0], "c2=cookie2");
|
assert_eq!(val[1], "c2=cookie2");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -37,15 +37,14 @@ bitflags! {
|
||||||
pub struct Flags: u8 {
|
pub struct Flags: u8 {
|
||||||
const STARTED = 0b0000_0001;
|
const STARTED = 0b0000_0001;
|
||||||
const KEEPALIVE = 0b0000_0010;
|
const KEEPALIVE = 0b0000_0010;
|
||||||
const POLLED = 0b0000_0100;
|
const SHUTDOWN = 0b0000_0100;
|
||||||
const SHUTDOWN = 0b0000_1000;
|
const READ_DISCONNECT = 0b0000_1000;
|
||||||
const READ_DISCONNECT = 0b0001_0000;
|
const WRITE_DISCONNECT = 0b0001_0000;
|
||||||
const WRITE_DISCONNECT = 0b0010_0000;
|
const UPGRADE = 0b0010_0000;
|
||||||
const UPGRADE = 0b0100_0000;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project::pin_project]
|
#[pin_project]
|
||||||
/// Dispatcher for HTTP/1.1 protocol
|
/// Dispatcher for HTTP/1.1 protocol
|
||||||
pub struct Dispatcher<T, S, B, X, U>
|
pub struct Dispatcher<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
|
@ -139,27 +138,14 @@ where
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
matches!(self, State::None)
|
matches!(self, State::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_call(&self) -> bool {
|
|
||||||
matches!(self, State::ServiceCall(_))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PollResponse {
|
enum PollResponse {
|
||||||
Upgrade(Request),
|
Upgrade(Request),
|
||||||
DoNothing,
|
DoNothing,
|
||||||
DrainWriteBuf,
|
DrainWriteBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for PollResponse {
|
|
||||||
fn eq(&self, other: &PollResponse) -> bool {
|
|
||||||
match self {
|
|
||||||
PollResponse::DrainWriteBuf => matches!(other, PollResponse::DrainWriteBuf),
|
|
||||||
PollResponse::DoNothing => matches!(other, PollResponse::DoNothing),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
|
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
@ -174,62 +160,35 @@ where
|
||||||
{
|
{
|
||||||
/// Create HTTP/1 dispatcher.
|
/// Create HTTP/1 dispatcher.
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
stream: T,
|
|
||||||
config: ServiceConfig,
|
|
||||||
services: Rc<HttpFlow<S, X, U>>,
|
|
||||||
on_connect_data: OnConnectData,
|
|
||||||
peer_addr: Option<net::SocketAddr>,
|
|
||||||
) -> Self {
|
|
||||||
Dispatcher::with_timeout(
|
|
||||||
stream,
|
|
||||||
Codec::new(config.clone()),
|
|
||||||
config,
|
|
||||||
BytesMut::with_capacity(HW_BUFFER_SIZE),
|
|
||||||
None,
|
|
||||||
services,
|
|
||||||
on_connect_data,
|
|
||||||
peer_addr,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create http/1 dispatcher with slow request timeout.
|
|
||||||
pub(crate) fn with_timeout(
|
|
||||||
io: T,
|
io: T,
|
||||||
codec: Codec,
|
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
read_buf: BytesMut,
|
flow: Rc<HttpFlow<S, X, U>>,
|
||||||
timeout: Option<Sleep>,
|
|
||||||
services: Rc<HttpFlow<S, X, U>>,
|
|
||||||
on_connect_data: OnConnectData,
|
on_connect_data: OnConnectData,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let keepalive = config.keep_alive_enabled();
|
let flags = if config.keep_alive_enabled() {
|
||||||
let flags = if keepalive {
|
|
||||||
Flags::KEEPALIVE
|
Flags::KEEPALIVE
|
||||||
} else {
|
} else {
|
||||||
Flags::empty()
|
Flags::empty()
|
||||||
};
|
};
|
||||||
|
|
||||||
// keep-alive timer
|
// keep-alive timer
|
||||||
let (ka_expire, ka_timer) = if let Some(delay) = timeout {
|
let (ka_expire, ka_timer) = match config.keep_alive_timer() {
|
||||||
(delay.deadline(), Some(delay))
|
Some(delay) => (delay.deadline(), Some(delay)),
|
||||||
} else if let Some(delay) = config.keep_alive_timer() {
|
None => (config.now(), None),
|
||||||
(delay.deadline(), Some(delay))
|
|
||||||
} else {
|
|
||||||
(config.now(), None)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Dispatcher {
|
Dispatcher {
|
||||||
inner: DispatcherState::Normal(InnerDispatcher {
|
inner: DispatcherState::Normal(InnerDispatcher {
|
||||||
|
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||||
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||||
payload: None,
|
payload: None,
|
||||||
state: State::None,
|
state: State::None,
|
||||||
error: None,
|
error: None,
|
||||||
messages: VecDeque::new(),
|
messages: VecDeque::new(),
|
||||||
io: Some(io),
|
io: Some(io),
|
||||||
codec,
|
codec: Codec::new(config),
|
||||||
read_buf,
|
flow,
|
||||||
flow: services,
|
|
||||||
on_connect_data,
|
on_connect_data,
|
||||||
flags,
|
flags,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
|
@ -286,15 +245,12 @@ where
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Result<bool, DispatchError> {
|
) -> Result<bool, DispatchError> {
|
||||||
let len = self.write_buf.len();
|
|
||||||
if len == 0 {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let InnerDispatcherProj { io, write_buf, .. } = self.project();
|
let InnerDispatcherProj { io, write_buf, .. } = self.project();
|
||||||
let mut io = Pin::new(io.as_mut().unwrap());
|
let mut io = Pin::new(io.as_mut().unwrap());
|
||||||
|
|
||||||
|
let len = write_buf.len();
|
||||||
let mut written = 0;
|
let mut written = 0;
|
||||||
|
|
||||||
while written < len {
|
while written < len {
|
||||||
match io.as_mut().poll_write(cx, &write_buf[written..]) {
|
match io.as_mut().poll_write(cx, &write_buf[written..]) {
|
||||||
Poll::Ready(Ok(0)) => {
|
Poll::Ready(Ok(0)) => {
|
||||||
|
@ -312,11 +268,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: setting length to 0 is safe
|
// everything has written to io. clear buffer.
|
||||||
// skips one length check vs truncate
|
write_buf.clear();
|
||||||
unsafe { write_buf.set_len(0) }
|
|
||||||
|
|
||||||
Ok(false)
|
// flush the io and check if get blocked.
|
||||||
|
let blocked = io.poll_flush(cx)?.is_pending();
|
||||||
|
|
||||||
|
Ok(blocked)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_response(
|
fn send_response(
|
||||||
|
@ -324,9 +282,10 @@ where
|
||||||
message: Response<()>,
|
message: Response<()>,
|
||||||
body: ResponseBody<B>,
|
body: ResponseBody<B>,
|
||||||
) -> Result<(), DispatchError> {
|
) -> Result<(), DispatchError> {
|
||||||
|
let size = body.size();
|
||||||
let mut this = self.project();
|
let mut this = self.project();
|
||||||
this.codec
|
this.codec
|
||||||
.encode(Message::Item((message, body.size())), &mut this.write_buf)
|
.encode(Message::Item((message, size)), &mut this.write_buf)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
if let Some(mut payload) = this.payload.take() {
|
if let Some(mut payload) = this.payload.take() {
|
||||||
payload.set_error(PayloadError::Incomplete(None));
|
payload.set_error(PayloadError::Incomplete(None));
|
||||||
|
@ -335,7 +294,7 @@ where
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
this.flags.set(Flags::KEEPALIVE, this.codec.keepalive());
|
this.flags.set(Flags::KEEPALIVE, this.codec.keepalive());
|
||||||
match body.size() {
|
match size {
|
||||||
BodySize::None | BodySize::Empty => this.state.set(State::None),
|
BodySize::None | BodySize::Empty => this.state.set(State::None),
|
||||||
_ => this.state.set(State::SendPayload(body)),
|
_ => this.state.set(State::SendPayload(body)),
|
||||||
};
|
};
|
||||||
|
@ -352,109 +311,121 @@ where
|
||||||
mut self: Pin<&mut Self>,
|
mut self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Result<PollResponse, DispatchError> {
|
) -> Result<PollResponse, DispatchError> {
|
||||||
loop {
|
'res: loop {
|
||||||
let mut this = self.as_mut().project();
|
let mut this = self.as_mut().project();
|
||||||
// state is not changed on Poll::Pending.
|
match this.state.as_mut().project() {
|
||||||
// other variant and conditions always trigger a state change(or an error).
|
// no future is in InnerDispatcher state. pop next message.
|
||||||
let state_change = match this.state.project() {
|
|
||||||
StateProj::None => match this.messages.pop_front() {
|
StateProj::None => match this.messages.pop_front() {
|
||||||
|
// handle request message.
|
||||||
Some(DispatcherMessage::Item(req)) => {
|
Some(DispatcherMessage::Item(req)) => {
|
||||||
self.as_mut().handle_request(req, cx)?;
|
// Handle `EXPECT: 100-Continue` header
|
||||||
true
|
if req.head().expect() {
|
||||||
|
// set InnerDispatcher state and continue loop to poll it.
|
||||||
|
let task = this.flow.expect.call(req);
|
||||||
|
this.state.set(State::ExpectCall(task));
|
||||||
|
} else {
|
||||||
|
// the same as expect call.
|
||||||
|
let task = this.flow.service.call(req);
|
||||||
|
this.state.set(State::ServiceCall(task));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle error message.
|
||||||
Some(DispatcherMessage::Error(res)) => {
|
Some(DispatcherMessage::Error(res)) => {
|
||||||
|
// send_response would update InnerDispatcher state to SendPayload or
|
||||||
|
// None(If response body is empty).
|
||||||
|
// continue loop to poll it.
|
||||||
self.as_mut()
|
self.as_mut()
|
||||||
.send_response(res, ResponseBody::Other(Body::Empty))?;
|
.send_response(res, ResponseBody::Other(Body::Empty))?;
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return with upgrade request and poll it exclusively.
|
||||||
Some(DispatcherMessage::Upgrade(req)) => {
|
Some(DispatcherMessage::Upgrade(req)) => {
|
||||||
return Ok(PollResponse::Upgrade(req));
|
return Ok(PollResponse::Upgrade(req));
|
||||||
}
|
}
|
||||||
None => false,
|
|
||||||
},
|
// all messages are dealt with.
|
||||||
StateProj::ExpectCall(fut) => match fut.poll(cx) {
|
None => return Ok(PollResponse::DoNothing),
|
||||||
Poll::Ready(Ok(req)) => {
|
|
||||||
self.as_mut().send_continue();
|
|
||||||
this = self.as_mut().project();
|
|
||||||
let fut = this.flow.service.call(req);
|
|
||||||
this.state.set(State::ServiceCall(fut));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Poll::Ready(Err(e)) => {
|
|
||||||
let res: Response = e.into().into();
|
|
||||||
let (res, body) = res.replace_body(());
|
|
||||||
self.as_mut().send_response(res, body.into_body())?;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Poll::Pending => false,
|
|
||||||
},
|
},
|
||||||
StateProj::ServiceCall(fut) => match fut.poll(cx) {
|
StateProj::ServiceCall(fut) => match fut.poll(cx) {
|
||||||
|
// service call resolved. send response.
|
||||||
Poll::Ready(Ok(res)) => {
|
Poll::Ready(Ok(res)) => {
|
||||||
let (res, body) = res.into().replace_body(());
|
let (res, body) = res.into().replace_body(());
|
||||||
self.as_mut().send_response(res, body)?;
|
self.as_mut().send_response(res, body)?;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
Poll::Ready(Err(e)) => {
|
|
||||||
let res: Response = e.into().into();
|
// send service call error as response
|
||||||
|
Poll::Ready(Err(err)) => {
|
||||||
|
let res: Response = err.into().into();
|
||||||
let (res, body) = res.replace_body(());
|
let (res, body) = res.replace_body(());
|
||||||
self.as_mut().send_response(res, body.into_body())?;
|
self.as_mut().send_response(res, body.into_body())?;
|
||||||
true
|
|
||||||
}
|
}
|
||||||
Poll::Pending => false,
|
|
||||||
},
|
// service call pending and could be waiting for more chunk messages.
|
||||||
StateProj::SendPayload(mut stream) => {
|
// (pipeline message limit and/or payload can_read limit)
|
||||||
loop {
|
Poll::Pending => {
|
||||||
if this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
|
// no new message is decoded and no new payload is feed.
|
||||||
match stream.as_mut().poll_next(cx) {
|
// nothing to do except waiting for new incoming data from client.
|
||||||
Poll::Ready(Some(Ok(item))) => {
|
if !self.as_mut().poll_request(cx)? {
|
||||||
this.codec.encode(
|
return Ok(PollResponse::DoNothing);
|
||||||
Message::Chunk(Some(item)),
|
|
||||||
&mut this.write_buf,
|
|
||||||
)?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Poll::Ready(None) => {
|
|
||||||
this.codec.encode(
|
|
||||||
Message::Chunk(None),
|
|
||||||
&mut this.write_buf,
|
|
||||||
)?;
|
|
||||||
this = self.as_mut().project();
|
|
||||||
this.state.set(State::None);
|
|
||||||
}
|
|
||||||
Poll::Ready(Some(Err(_))) => {
|
|
||||||
return Err(DispatchError::Unknown)
|
|
||||||
}
|
|
||||||
Poll::Pending => return Ok(PollResponse::DoNothing),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Ok(PollResponse::DrainWriteBuf);
|
|
||||||
}
|
}
|
||||||
break;
|
// otherwise keep loop.
|
||||||
}
|
}
|
||||||
continue;
|
},
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// state is changed and continue when the state is not Empty
|
StateProj::SendPayload(mut stream) => {
|
||||||
if state_change {
|
// keep populate writer buffer until buffer size limit hit,
|
||||||
if !self.state.is_empty() {
|
// get blocked or finished.
|
||||||
continue;
|
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
|
||||||
}
|
match stream.as_mut().poll_next(cx) {
|
||||||
} else {
|
Poll::Ready(Some(Ok(item))) => {
|
||||||
// if read-backpressure is enabled and we consumed some data.
|
this.codec.encode(
|
||||||
// we may read more data and retry
|
Message::Chunk(Some(item)),
|
||||||
if self.state.is_call() {
|
&mut this.write_buf,
|
||||||
if self.as_mut().poll_request(cx)? {
|
)?;
|
||||||
continue;
|
}
|
||||||
|
|
||||||
|
Poll::Ready(None) => {
|
||||||
|
this.codec
|
||||||
|
.encode(Message::Chunk(None), &mut this.write_buf)?;
|
||||||
|
// payload stream finished.
|
||||||
|
// set state to None and handle next message
|
||||||
|
this.state.set(State::None);
|
||||||
|
continue 'res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Ready(Some(Err(err))) => {
|
||||||
|
return Err(DispatchError::Service(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Pending => return Ok(PollResponse::DoNothing),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if !self.messages.is_empty() {
|
// buffer is beyond max size.
|
||||||
continue;
|
// return and try to write the whole buffer to io stream.
|
||||||
|
return Ok(PollResponse::DrainWriteBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StateProj::ExpectCall(fut) => match fut.poll(cx) {
|
||||||
|
// expect resolved. write continue to buffer and set InnerDispatcher state
|
||||||
|
// to service call.
|
||||||
|
Poll::Ready(Ok(req)) => {
|
||||||
|
this.write_buf
|
||||||
|
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
|
||||||
|
let fut = this.flow.service.call(req);
|
||||||
|
this.state.set(State::ServiceCall(fut));
|
||||||
|
}
|
||||||
|
// send expect error as response
|
||||||
|
Poll::Ready(Err(err)) => {
|
||||||
|
let res: Response = err.into().into();
|
||||||
|
let (res, body) = res.replace_body(());
|
||||||
|
self.as_mut().send_response(res, body.into_body())?;
|
||||||
|
}
|
||||||
|
// expect must be solved before progress can be made.
|
||||||
|
Poll::Pending => return Ok(PollResponse::DoNothing),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PollResponse::DoNothing)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_request(
|
fn handle_request(
|
||||||
|
@ -494,9 +465,9 @@ where
|
||||||
// future is error. send response and return a result. On success
|
// future is error. send response and return a result. On success
|
||||||
// to notify the dispatcher a new state is set and the outer loop
|
// to notify the dispatcher a new state is set and the outer loop
|
||||||
// should be continue.
|
// should be continue.
|
||||||
Poll::Ready(Err(e)) => {
|
Poll::Ready(Err(err)) => {
|
||||||
let e = e.into();
|
let err = err.into();
|
||||||
let res: Response = e.into();
|
let res: Response = err.into();
|
||||||
let (res, body) = res.replace_body(());
|
let (res, body) = res.replace_body(());
|
||||||
return self.send_response(res, body.into_body());
|
return self.send_response(res, body.into_body());
|
||||||
}
|
}
|
||||||
|
@ -514,9 +485,9 @@ where
|
||||||
}
|
}
|
||||||
// see the comment on ExpectCall state branch's Pending.
|
// see the comment on ExpectCall state branch's Pending.
|
||||||
Poll::Pending => Ok(()),
|
Poll::Pending => Ok(()),
|
||||||
// see the comment on ExpectCall state branch's Ready(Err(e)).
|
// see the comment on ExpectCall state branch's Ready(Err(err)).
|
||||||
Poll::Ready(Err(e)) => {
|
Poll::Ready(Err(err)) => {
|
||||||
let res: Response = e.into().into();
|
let res: Response = err.into().into();
|
||||||
let (res, body) = res.replace_body(());
|
let (res, body) = res.replace_body(());
|
||||||
self.send_response(res, body.into_body())
|
self.send_response(res, body.into_body())
|
||||||
}
|
}
|
||||||
|
@ -608,25 +579,25 @@ where
|
||||||
// decode is partial and buffer is not full yet.
|
// decode is partial and buffer is not full yet.
|
||||||
// break and wait for more read.
|
// break and wait for more read.
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
Err(ParseError::Io(e)) => {
|
Err(ParseError::Io(err)) => {
|
||||||
self.as_mut().client_disconnected();
|
self.as_mut().client_disconnected();
|
||||||
this = self.as_mut().project();
|
this = self.as_mut().project();
|
||||||
*this.error = Some(DispatchError::Io(e));
|
*this.error = Some(DispatchError::Io(err));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(ParseError::TooLarge) => {
|
Err(ParseError::TooLarge) => {
|
||||||
if let Some(mut payload) = this.payload.take() {
|
if let Some(mut payload) = this.payload.take() {
|
||||||
payload.set_error(PayloadError::Overflow);
|
payload.set_error(PayloadError::Overflow);
|
||||||
}
|
}
|
||||||
// Requests overflow buffer size should be responded with 413
|
// Requests overflow buffer size should be responded with 431
|
||||||
this.messages.push_back(DispatcherMessage::Error(
|
this.messages.push_back(DispatcherMessage::Error(
|
||||||
Response::PayloadTooLarge().finish().drop_body(),
|
Response::RequestHeaderFieldsTooLarge().finish().drop_body(),
|
||||||
));
|
));
|
||||||
this.flags.insert(Flags::READ_DISCONNECT);
|
this.flags.insert(Flags::READ_DISCONNECT);
|
||||||
*this.error = Some(ParseError::TooLarge.into());
|
*this.error = Some(ParseError::TooLarge.into());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(err) => {
|
||||||
if let Some(mut payload) = this.payload.take() {
|
if let Some(mut payload) = this.payload.take() {
|
||||||
payload.set_error(PayloadError::EncodingCorrupted);
|
payload.set_error(PayloadError::EncodingCorrupted);
|
||||||
}
|
}
|
||||||
|
@ -636,7 +607,7 @@ where
|
||||||
Response::BadRequest().finish().drop_body(),
|
Response::BadRequest().finish().drop_body(),
|
||||||
));
|
));
|
||||||
this.flags.insert(Flags::READ_DISCONNECT);
|
this.flags.insert(Flags::READ_DISCONNECT);
|
||||||
*this.error = Some(e.into());
|
*this.error = Some(err.into());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -774,7 +745,12 @@ where
|
||||||
// at this point it's not known io is still scheduled to
|
// at this point it's not known io is still scheduled to
|
||||||
// be waked up. so force wake up dispatcher just in case.
|
// be waked up. so force wake up dispatcher just in case.
|
||||||
// TODO: figure out the overhead.
|
// TODO: figure out the overhead.
|
||||||
cx.waker().wake_by_ref();
|
if this.payload.is_none() {
|
||||||
|
// When dispatcher has a payload. The responsibility of
|
||||||
|
// wake up stream would be shift to PayloadSender.
|
||||||
|
// Therefore no self wake up is needed.
|
||||||
|
cx.waker().wake_by_ref();
|
||||||
|
}
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -839,9 +815,8 @@ where
|
||||||
if inner.flags.contains(Flags::WRITE_DISCONNECT) {
|
if inner.flags.contains(Flags::WRITE_DISCONNECT) {
|
||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
} else {
|
} else {
|
||||||
// flush buffer
|
// flush buffer and wait on block.
|
||||||
inner.as_mut().poll_flush(cx)?;
|
if inner.as_mut().poll_flush(cx)? {
|
||||||
if !inner.write_buf.is_empty() {
|
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
} else {
|
} else {
|
||||||
Pin::new(inner.project().io.as_mut().unwrap())
|
Pin::new(inner.project().io.as_mut().unwrap())
|
||||||
|
|
|
@ -144,104 +144,54 @@ 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();
|
||||||
|
|
||||||
match value {
|
// TODO: drain?
|
||||||
Value::One(ref val) => {
|
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();
|
||||||
|
|
||||||
// key length + value length + colon + space + \r\n
|
// key length + value length + colon + space + \r\n
|
||||||
let len = k_len + v_len + 4;
|
let len = k_len + v_len + 4;
|
||||||
|
|
||||||
if len > remaining {
|
if len > remaining {
|
||||||
// not enough room in buffer for this header; reserve more space
|
// SAFETY: all the bytes written up to position "pos" are initialized
|
||||||
|
// the written byte count and pointer advancement are kept in sync
|
||||||
// SAFETY: all the bytes written up to position "pos" are initialized
|
|
||||||
// the written byte count and pointer advancement are kept in sync
|
|
||||||
unsafe {
|
|
||||||
dst.advance_mut(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
pos = 0;
|
|
||||||
dst.reserve(len * 2);
|
|
||||||
remaining = dst.capacity() - dst.len();
|
|
||||||
|
|
||||||
// re-assign buf raw pointer since it's possible that the buffer was
|
|
||||||
// reallocated and/or resized
|
|
||||||
buf = dst.chunk_mut().as_mut_ptr();
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: on each write, it is enough to ensure that the advancement of the
|
|
||||||
// cursor matches the number of bytes written
|
|
||||||
unsafe {
|
unsafe {
|
||||||
// use upper Camel-Case
|
dst.advance_mut(pos);
|
||||||
if camel_case {
|
|
||||||
write_camel_case(k, from_raw_parts_mut(buf, k_len))
|
|
||||||
} else {
|
|
||||||
write_data(k, buf, k_len)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = buf.add(k_len);
|
|
||||||
|
|
||||||
write_data(b": ", buf, 2);
|
|
||||||
buf = buf.add(2);
|
|
||||||
|
|
||||||
write_data(v, buf, v_len);
|
|
||||||
buf = buf.add(v_len);
|
|
||||||
|
|
||||||
write_data(b"\r\n", buf, 2);
|
|
||||||
buf = buf.add(2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pos += len;
|
pos = 0;
|
||||||
remaining -= len;
|
dst.reserve(len * 2);
|
||||||
|
remaining = dst.capacity() - dst.len();
|
||||||
|
|
||||||
|
// re-assign buf raw pointer since it's possible that the buffer was
|
||||||
|
// reallocated and/or resized
|
||||||
|
buf = dst.chunk_mut().as_mut_ptr();
|
||||||
}
|
}
|
||||||
|
|
||||||
Value::Multi(ref vec) => {
|
// SAFETY: on each write, it is enough to ensure that the advancement of
|
||||||
for val in vec {
|
// the cursor matches the number of bytes written
|
||||||
let v = val.as_ref();
|
unsafe {
|
||||||
let v_len = v.len();
|
if camel_case {
|
||||||
let len = k_len + v_len + 4;
|
// use Camel-Case headers
|
||||||
|
write_camel_case(k, from_raw_parts_mut(buf, k_len));
|
||||||
if len > remaining {
|
} else {
|
||||||
// SAFETY: all the bytes written up to position "pos" are initialized
|
write_data(k, buf, k_len);
|
||||||
// the written byte count and pointer advancement are kept in sync
|
|
||||||
unsafe {
|
|
||||||
dst.advance_mut(pos);
|
|
||||||
}
|
|
||||||
pos = 0;
|
|
||||||
dst.reserve(len * 2);
|
|
||||||
remaining = dst.capacity() - dst.len();
|
|
||||||
|
|
||||||
// re-assign buf raw pointer since it's possible that the buffer was
|
|
||||||
// reallocated and/or resized
|
|
||||||
buf = dst.chunk_mut().as_mut_ptr();
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: on each write, it is enough to ensure that the advancement of
|
|
||||||
// the cursor matches the number of bytes written
|
|
||||||
unsafe {
|
|
||||||
if camel_case {
|
|
||||||
write_camel_case(k, from_raw_parts_mut(buf, k_len));
|
|
||||||
} else {
|
|
||||||
write_data(k, buf, k_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = buf.add(k_len);
|
|
||||||
|
|
||||||
write_data(b": ", buf, 2);
|
|
||||||
buf = buf.add(2);
|
|
||||||
|
|
||||||
write_data(v, buf, v_len);
|
|
||||||
buf = buf.add(v_len);
|
|
||||||
|
|
||||||
write_data(b"\r\n", buf, 2);
|
|
||||||
buf = buf.add(2);
|
|
||||||
};
|
|
||||||
|
|
||||||
pos += len;
|
|
||||||
remaining -= len;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
buf = buf.add(k_len);
|
||||||
|
|
||||||
|
write_data(b": ", buf, 2);
|
||||||
|
buf = buf.add(2);
|
||||||
|
|
||||||
|
write_data(v, buf, v_len);
|
||||||
|
buf = buf.add(v_len);
|
||||||
|
|
||||||
|
write_data(b"\r\n", buf, 2);
|
||||||
|
buf = buf.add(2);
|
||||||
|
};
|
||||||
|
|
||||||
|
pos += len;
|
||||||
|
remaining -= len;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -579,8 +529,8 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[actix_rt::test]
|
||||||
fn test_camel_case() {
|
async fn test_camel_case() {
|
||||||
let mut bytes = BytesMut::with_capacity(2048);
|
let mut bytes = BytesMut::with_capacity(2048);
|
||||||
let mut head = RequestHead::default();
|
let mut head = RequestHead::default();
|
||||||
head.set_camel_case_headers(true);
|
head.set_camel_case_headers(true);
|
||||||
|
@ -643,8 +593,8 @@ mod tests {
|
||||||
assert!(data.contains("date: date\r\n"));
|
assert!(data.contains("date: date\r\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[actix_rt::test]
|
||||||
fn test_extra_headers() {
|
async fn test_extra_headers() {
|
||||||
let mut bytes = BytesMut::with_capacity(2048);
|
let mut bytes = BytesMut::with_capacity(2048);
|
||||||
|
|
||||||
let mut head = RequestHead::default();
|
let mut head = RequestHead::default();
|
||||||
|
@ -677,8 +627,8 @@ mod tests {
|
||||||
assert!(data.contains("date: date\r\n"));
|
assert!(data.contains("date: date\r\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[actix_rt::test]
|
||||||
fn test_no_content_length() {
|
async fn test_no_content_length() {
|
||||||
let mut bytes = BytesMut::with_capacity(2048);
|
let mut bytes = BytesMut::with_capacity(2048);
|
||||||
|
|
||||||
let mut res: Response<()> =
|
let mut res: Response<()> =
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! HTTP/1 implementation
|
//! HTTP/1 protocol implementation.
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! HTTP/2 implementation.
|
//! HTTP/2 protocol.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
|
|
|
@ -243,7 +243,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Service` implementation for http/2 transport
|
/// `Service` implementation for HTTP/2 transport
|
||||||
pub struct H2ServiceHandler<T, S, B>
|
pub struct H2ServiceHandler<T, S, B>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
//! Helper trait for types that can be effectively borrowed as a [HeaderValue].
|
||||||
|
//!
|
||||||
|
//! [HeaderValue]: crate::http::HeaderValue
|
||||||
|
|
||||||
|
use std::{borrow::Cow, str::FromStr};
|
||||||
|
|
||||||
|
use http::header::{HeaderName, InvalidHeaderName};
|
||||||
|
|
||||||
|
pub trait AsHeaderName: Sealed {}
|
||||||
|
|
||||||
|
pub trait Sealed {
|
||||||
|
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sealed for HeaderName {
|
||||||
|
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
|
Ok(Cow::Borrowed(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsHeaderName for HeaderName {}
|
||||||
|
|
||||||
|
impl Sealed for &HeaderName {
|
||||||
|
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
|
Ok(Cow::Borrowed(*self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsHeaderName for &HeaderName {}
|
||||||
|
|
||||||
|
impl Sealed for &str {
|
||||||
|
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
|
HeaderName::from_str(self).map(Cow::Owned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsHeaderName for &str {}
|
||||||
|
|
||||||
|
impl Sealed for String {
|
||||||
|
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
|
HeaderName::from_str(self).map(Cow::Owned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsHeaderName for String {}
|
||||||
|
|
||||||
|
impl Sealed for &String {
|
||||||
|
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
|
HeaderName::from_str(self).map(Cow::Owned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsHeaderName for &String {}
|
|
@ -5,7 +5,7 @@ use crate::header::{
|
||||||
self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate,
|
self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate,
|
||||||
IntoHeaderValue, InvalidHeaderValue, Writer,
|
IntoHeaderValue, InvalidHeaderValue, Writer,
|
||||||
};
|
};
|
||||||
use crate::httpmessage::HttpMessage;
|
use crate::HttpMessage;
|
||||||
|
|
||||||
/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2)
|
/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2)
|
||||||
///
|
///
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
||||||
//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing/conversion and other
|
//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing and conversion, and other
|
||||||
//! header utility methods.
|
//! header utility methods.
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -9,8 +9,9 @@ use percent_encoding::{AsciiSet, CONTROLS};
|
||||||
pub use http::header::*;
|
pub use http::header::*;
|
||||||
|
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use crate::httpmessage::HttpMessage;
|
use crate::HttpMessage;
|
||||||
|
|
||||||
|
mod as_name;
|
||||||
mod into_pair;
|
mod into_pair;
|
||||||
mod into_value;
|
mod into_value;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
@ -23,6 +24,7 @@ pub use self::common::*;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use self::shared::*;
|
pub use self::shared::*;
|
||||||
|
|
||||||
|
pub use self::as_name::AsHeaderName;
|
||||||
pub use self::into_pair::IntoHeaderPair;
|
pub use self::into_pair::IntoHeaderPair;
|
||||||
pub use self::into_value::IntoHeaderValue;
|
pub use self::into_value::IntoHeaderValue;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -39,16 +41,14 @@ pub trait Header: IntoHeaderValue {
|
||||||
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
|
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Writer {
|
pub(crate) struct Writer {
|
||||||
buf: BytesMut,
|
buf: BytesMut,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Writer {
|
impl Writer {
|
||||||
fn new() -> Writer {
|
fn new() -> Writer {
|
||||||
Writer {
|
Writer::default()
|
||||||
buf: BytesMut::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take(&mut self) -> Bytes {
|
fn take(&mut self) -> Bytes {
|
||||||
|
@ -71,12 +71,8 @@ impl fmt::Write for Writer {
|
||||||
|
|
||||||
/// Convert `http::HeaderMap` to our `HeaderMap`.
|
/// Convert `http::HeaderMap` to our `HeaderMap`.
|
||||||
impl From<http::HeaderMap> for HeaderMap {
|
impl From<http::HeaderMap> for HeaderMap {
|
||||||
fn from(map: http::HeaderMap) -> HeaderMap {
|
fn from(mut map: http::HeaderMap) -> HeaderMap {
|
||||||
let mut new_map = HeaderMap::with_capacity(map.capacity());
|
HeaderMap::from_drain(map.drain())
|
||||||
for (h, v) in map.iter() {
|
|
||||||
new_map.append(h.clone(), v.clone());
|
|
||||||
}
|
|
||||||
new_map
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,14 @@ impl Response {
|
||||||
static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
|
static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
|
||||||
static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY);
|
static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY);
|
||||||
static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS);
|
static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS);
|
||||||
|
static_resp!(
|
||||||
|
RequestHeaderFieldsTooLarge,
|
||||||
|
StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE
|
||||||
|
);
|
||||||
|
static_resp!(
|
||||||
|
UnavailableForLegalReasons,
|
||||||
|
StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS
|
||||||
|
);
|
||||||
|
|
||||||
static_resp!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR);
|
static_resp!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
static_resp!(NotImplemented, StatusCode::NOT_IMPLEMENTED);
|
static_resp!(NotImplemented, StatusCode::NOT_IMPLEMENTED);
|
|
@ -1,6 +1,6 @@
|
||||||
//! HTTP primitives for the Actix ecosystem.
|
//! HTTP primitives for the Actix ecosystem.
|
||||||
|
|
||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
#![allow(
|
#![allow(
|
||||||
clippy::type_complexity,
|
clippy::type_complexity,
|
||||||
clippy::too_many_arguments,
|
clippy::too_many_arguments,
|
||||||
|
@ -25,8 +25,8 @@ pub mod encoding;
|
||||||
mod extensions;
|
mod extensions;
|
||||||
mod header;
|
mod header;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod httpcodes;
|
mod http_codes;
|
||||||
pub mod httpmessage;
|
mod http_message;
|
||||||
mod message;
|
mod message;
|
||||||
mod payload;
|
mod payload;
|
||||||
mod request;
|
mod request;
|
||||||
|
@ -45,7 +45,7 @@ pub use self::builder::HttpServiceBuilder;
|
||||||
pub use self::config::{KeepAlive, ServiceConfig};
|
pub use self::config::{KeepAlive, ServiceConfig};
|
||||||
pub use self::error::{Error, ResponseError, Result};
|
pub use self::error::{Error, ResponseError, Result};
|
||||||
pub use self::extensions::Extensions;
|
pub use self::extensions::Extensions;
|
||||||
pub use self::httpmessage::HttpMessage;
|
pub use self::http_message::HttpMessage;
|
||||||
pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead};
|
pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead};
|
||||||
pub use self::payload::{Payload, PayloadStream};
|
pub use self::payload::{Payload, PayloadStream};
|
||||||
pub use self::request::Request;
|
pub use self::request::Request;
|
||||||
|
|
|
@ -13,8 +13,10 @@ use crate::http::{header, Method, StatusCode, Uri, Version};
|
||||||
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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,9 @@ use http::{header, Method, Uri, Version};
|
||||||
|
|
||||||
use crate::extensions::Extensions;
|
use crate::extensions::Extensions;
|
||||||
use crate::header::HeaderMap;
|
use crate::header::HeaderMap;
|
||||||
use crate::httpmessage::HttpMessage;
|
|
||||||
use crate::message::{Message, RequestHead};
|
use crate::message::{Message, RequestHead};
|
||||||
use crate::payload::{Payload, PayloadStream};
|
use crate::payload::{Payload, PayloadStream};
|
||||||
|
use crate::HttpMessage;
|
||||||
|
|
||||||
/// Request
|
/// Request
|
||||||
pub struct Request<P = PayloadStream> {
|
pub struct Request<P = PayloadStream> {
|
||||||
|
@ -107,7 +107,7 @@ impl<P> Request<P> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Mutable reference to a http message part of the request
|
/// Mutable reference to a HTTP message part of the request
|
||||||
pub fn head_mut(&mut self) -> &mut RequestHead {
|
pub fn head_mut(&mut self) -> &mut RequestHead {
|
||||||
&mut *self.head
|
&mut *self.head
|
||||||
}
|
}
|
||||||
|
@ -158,10 +158,12 @@ impl<P> Request<P> {
|
||||||
self.head().method == Method::CONNECT
|
self.head().method == Method::CONNECT
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peer socket address
|
/// Peer socket address.
|
||||||
///
|
///
|
||||||
/// Peer address is actual socket address, if proxy is used in front of
|
/// Peer address is the directly connected peer's socket address. If a proxy is used in front of
|
||||||
/// actix http server, then peer address would be address of this proxy.
|
/// the Actix Web server, then it would be address of this proxy.
|
||||||
|
///
|
||||||
|
/// Will only return None when called in unit tests.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
|
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||||
self.head().peer_addr
|
self.head().peer_addr
|
||||||
|
@ -177,13 +179,17 @@ impl<P> fmt::Debug for Request<P> {
|
||||||
self.method(),
|
self.method(),
|
||||||
self.path()
|
self.path()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(q) = self.uri().query().as_ref() {
|
if let Some(q) = self.uri().query().as_ref() {
|
||||||
writeln!(f, " query: ?{:?}", q)?;
|
writeln!(f, " query: ?{:?}", q)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(f, " headers:")?;
|
writeln!(f, " headers:")?;
|
||||||
for (key, val) in self.headers() {
|
|
||||||
|
for (key, val) in self.headers().iter() {
|
||||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,13 +32,13 @@ pub struct Response<B = Body> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response<Body> {
|
impl Response<Body> {
|
||||||
/// Create http response builder with specific status.
|
/// Create HTTP response builder with specific status.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn build(status: StatusCode) -> ResponseBuilder {
|
pub fn build(status: StatusCode) -> ResponseBuilder {
|
||||||
ResponseBuilder::new(status)
|
ResponseBuilder::new(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create http response builder
|
/// Create HTTP response builder
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn build_from<T: Into<ResponseBuilder>>(source: T) -> ResponseBuilder {
|
pub fn build_from<T: Into<ResponseBuilder>>(source: T) -> ResponseBuilder {
|
||||||
source.into()
|
source.into()
|
||||||
|
@ -97,7 +97,7 @@ impl<B> Response<B> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Mutable reference to a http message part of the response
|
/// Mutable reference to a HTTP message part of the response
|
||||||
pub fn head_mut(&mut self) -> &mut ResponseHead {
|
pub fn head_mut(&mut self) -> &mut ResponseHead {
|
||||||
&mut *self.head
|
&mut *self.head
|
||||||
}
|
}
|
||||||
|
@ -363,7 +363,9 @@ impl ResponseBuilder {
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
match header.try_into_header_pair() {
|
match header.try_into_header_pair() {
|
||||||
Ok((key, value)) => parts.headers.insert(key, value),
|
Ok((key, value)) => {
|
||||||
|
parts.headers.insert(key, value);
|
||||||
|
}
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -752,9 +754,11 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
|
||||||
let mut msg = BoxedResponseHead::new(head.status);
|
let mut msg = BoxedResponseHead::new(head.status);
|
||||||
msg.version = head.version;
|
msg.version = head.version;
|
||||||
msg.reason = head.reason;
|
msg.reason = head.reason;
|
||||||
for (k, v) in &head.headers {
|
|
||||||
|
for (k, v) in head.headers.iter() {
|
||||||
msg.headers.append(k.clone(), v.clone());
|
msg.headers.append(k.clone(), v.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.no_chunking(!head.chunked());
|
msg.no_chunking(!head.chunked());
|
||||||
|
|
||||||
ResponseBuilder {
|
ResponseBuilder {
|
||||||
|
@ -863,6 +867,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::body::Body;
|
use crate::body::Body;
|
||||||
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE};
|
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE};
|
||||||
|
use crate::HttpMessage;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_debug() {
|
fn test_debug() {
|
||||||
|
@ -876,8 +881,6 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_response_cookies() {
|
fn test_response_cookies() {
|
||||||
use crate::httpmessage::HttpMessage;
|
|
||||||
|
|
||||||
let req = crate::test::TestRequest::default()
|
let req = crate::test::TestRequest::default()
|
||||||
.append_header((COOKIE, "cookie1=value1"))
|
.append_header((COOKIE, "cookie1=value1"))
|
||||||
.append_header((COOKIE, "cookie2=value2"))
|
.append_header((COOKIE, "cookie2=value2"))
|
||||||
|
@ -893,16 +896,20 @@ mod tests {
|
||||||
.max_age(time::Duration::days(1))
|
.max_age(time::Duration::days(1))
|
||||||
.finish(),
|
.finish(),
|
||||||
)
|
)
|
||||||
.del_cookie(&cookies[1])
|
.del_cookie(&cookies[0])
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
let mut val: Vec<_> = resp
|
let mut val = resp
|
||||||
.headers()
|
.headers()
|
||||||
.get_all(SET_COOKIE)
|
.get_all(SET_COOKIE)
|
||||||
.map(|v| v.to_str().unwrap().to_owned())
|
.map(|v| v.to_str().unwrap().to_owned())
|
||||||
.collect();
|
.collect::<Vec<_>>();
|
||||||
val.sort();
|
val.sort();
|
||||||
|
|
||||||
|
// the .del_cookie call
|
||||||
assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
|
assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
|
||||||
|
|
||||||
|
// the .cookie call
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
val[1],
|
val[1],
|
||||||
"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"
|
"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"
|
||||||
|
@ -927,9 +934,9 @@ mod tests {
|
||||||
|
|
||||||
let mut iter = r.cookies();
|
let mut iter = r.cookies();
|
||||||
let v = iter.next().unwrap();
|
let v = iter.next().unwrap();
|
||||||
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
|
|
||||||
let v = iter.next().unwrap();
|
|
||||||
assert_eq!((v.name(), v.value()), ("original", "val100"));
|
assert_eq!((v.name(), v.value()), ("original", "val100"));
|
||||||
|
let v = iter.next().unwrap();
|
||||||
|
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -432,7 +432,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Service` implementation for http transport
|
/// `Service` implementation for HTTP transport
|
||||||
pub struct HttpServiceHandler<T, S, B, X, U>
|
pub struct HttpServiceHandler<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
|
|
|
@ -8,35 +8,65 @@ pub fn parse_http_date(time: &str) -> Option<PrimitiveDateTime> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to parse a `time` string as a RFC 1123 formatted date time string.
|
/// Attempt to parse a `time` string as a RFC 1123 formatted date time string.
|
||||||
|
///
|
||||||
|
/// Eg: `Fri, 12 Feb 2021 00:14:29 GMT`
|
||||||
fn try_parse_rfc_1123(time: &str) -> Option<PrimitiveDateTime> {
|
fn try_parse_rfc_1123(time: &str) -> Option<PrimitiveDateTime> {
|
||||||
time::parse(time, "%a, %d %b %Y %H:%M:%S").ok()
|
time::parse(time, "%a, %d %b %Y %H:%M:%S").ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to parse a `time` string as a RFC 850 formatted date time string.
|
/// Attempt to parse a `time` string as a RFC 850 formatted date time string.
|
||||||
|
///
|
||||||
|
/// Eg: `Wednesday, 11-Jan-21 13:37:41 UTC`
|
||||||
fn try_parse_rfc_850(time: &str) -> Option<PrimitiveDateTime> {
|
fn try_parse_rfc_850(time: &str) -> Option<PrimitiveDateTime> {
|
||||||
match PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S") {
|
let dt = PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S").ok()?;
|
||||||
Ok(dt) => {
|
|
||||||
// If the `time` string contains a two-digit year, then as per RFC 2616 § 19.3,
|
|
||||||
// we consider the year as part of this century if it's within the next 50 years,
|
|
||||||
// otherwise we consider as part of the previous century.
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
let century_start_year = (now.year() / 100) * 100;
|
|
||||||
let mut expanded_year = century_start_year + dt.year();
|
|
||||||
|
|
||||||
if expanded_year > now.year() + 50 {
|
// If the `time` string contains a two-digit year, then as per RFC 2616 § 19.3,
|
||||||
expanded_year -= 100;
|
// we consider the year as part of this century if it's within the next 50 years,
|
||||||
}
|
// otherwise we consider as part of the previous century.
|
||||||
|
|
||||||
match Date::try_from_ymd(expanded_year, dt.month(), dt.day()) {
|
let now = OffsetDateTime::now_utc();
|
||||||
Ok(date) => Some(PrimitiveDateTime::new(date, dt.time())),
|
let century_start_year = (now.year() / 100) * 100;
|
||||||
Err(_) => None,
|
let mut expanded_year = century_start_year + dt.year();
|
||||||
}
|
|
||||||
}
|
if expanded_year > now.year() + 50 {
|
||||||
Err(_) => None,
|
expanded_year -= 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let date = Date::try_from_ymd(expanded_year, dt.month(), dt.day()).ok()?;
|
||||||
|
Some(PrimitiveDateTime::new(date, dt.time()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to parse a `time` string using ANSI C's `asctime` format.
|
/// Attempt to parse a `time` string using ANSI C's `asctime` format.
|
||||||
|
///
|
||||||
|
/// Eg: `Wed Feb 13 15:46:11 2013`
|
||||||
fn try_parse_asctime(time: &str) -> Option<PrimitiveDateTime> {
|
fn try_parse_asctime(time: &str) -> Option<PrimitiveDateTime> {
|
||||||
time::parse(time, "%a %b %_d %H:%M:%S %Y").ok()
|
time::parse(time, "%a %b %_d %H:%M:%S %Y").ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use time::{date, time};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rfc_850_year_shift() {
|
||||||
|
let date = try_parse_rfc_850("Friday, 19-Nov-82 16:14:55 EST").unwrap();
|
||||||
|
assert_eq!(date, date!(1982 - 11 - 19).with_time(time!(16:14:55)));
|
||||||
|
|
||||||
|
let date = try_parse_rfc_850("Wednesday, 11-Jan-62 13:37:41 EST").unwrap();
|
||||||
|
assert_eq!(date, date!(2062 - 01 - 11).with_time(time!(13:37:41)));
|
||||||
|
|
||||||
|
let date = try_parse_rfc_850("Wednesday, 11-Jan-21 13:37:41 EST").unwrap();
|
||||||
|
assert_eq!(date, date!(2021 - 01 - 11).with_time(time!(13:37:41)));
|
||||||
|
|
||||||
|
let date = try_parse_rfc_850("Wednesday, 11-Jan-23 13:37:41 EST").unwrap();
|
||||||
|
assert_eq!(date, date!(2023 - 01 - 11).with_time(time!(13:37:41)));
|
||||||
|
|
||||||
|
let date = try_parse_rfc_850("Wednesday, 11-Jan-99 13:37:41 EST").unwrap();
|
||||||
|
assert_eq!(date, date!(1999 - 01 - 11).with_time(time!(13:37:41)));
|
||||||
|
|
||||||
|
let date = try_parse_rfc_850("Wednesday, 11-Jan-00 13:37:41 EST").unwrap();
|
||||||
|
assert_eq!(date, date!(2000 - 01 - 11).with_time(time!(13:37:41)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ pub enum Frame {
|
||||||
Close(Option<CloseReason>),
|
Close(Option<CloseReason>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `WebSocket` continuation item.
|
/// A WebSocket continuation item.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Item {
|
pub enum Item {
|
||||||
FirstText(Bytes),
|
FirstText(Bytes),
|
||||||
|
@ -79,7 +79,7 @@ bitflags! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Codec {
|
impl Codec {
|
||||||
/// Create new websocket frames decoder.
|
/// Create new WebSocket frames decoder.
|
||||||
pub fn new() -> Codec {
|
pub fn new() -> Codec {
|
||||||
Codec {
|
Codec {
|
||||||
max_size: 65_536,
|
max_size: 65_536,
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::ws::mask::apply_mask;
|
||||||
use crate::ws::proto::{CloseCode, CloseReason, OpCode};
|
use crate::ws::proto::{CloseCode, CloseReason, OpCode};
|
||||||
use crate::ws::ProtocolError;
|
use crate::ws::ProtocolError;
|
||||||
|
|
||||||
/// A struct representing a `WebSocket` frame.
|
/// A struct representing a WebSocket frame.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Parser;
|
pub struct Parser;
|
||||||
|
|
||||||
|
@ -16,7 +16,8 @@ impl Parser {
|
||||||
src: &[u8],
|
src: &[u8],
|
||||||
server: bool,
|
server: bool,
|
||||||
max_size: usize,
|
max_size: usize,
|
||||||
) -> Result<Option<(usize, bool, OpCode, usize, Option<u32>)>, ProtocolError> {
|
) -> Result<Option<(usize, bool, OpCode, usize, Option<[u8; 4]>)>, ProtocolError>
|
||||||
|
{
|
||||||
let chunk_len = src.len();
|
let chunk_len = src.len();
|
||||||
|
|
||||||
let mut idx = 2;
|
let mut idx = 2;
|
||||||
|
@ -77,9 +78,10 @@ impl Parser {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mask =
|
let mask = TryFrom::try_from(&src[idx..idx + 4]).unwrap();
|
||||||
u32::from_le_bytes(TryFrom::try_from(&src[idx..idx + 4]).unwrap());
|
|
||||||
idx += 4;
|
idx += 4;
|
||||||
|
|
||||||
Some(mask)
|
Some(mask)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -187,8 +189,8 @@ impl Parser {
|
||||||
};
|
};
|
||||||
|
|
||||||
if mask {
|
if mask {
|
||||||
let mask = rand::random::<u32>();
|
let mask = rand::random::<[u8; 4]>();
|
||||||
dst.put_u32_le(mask);
|
dst.put_slice(mask.as_ref());
|
||||||
dst.put_slice(payload.as_ref());
|
dst.put_slice(payload.as_ref());
|
||||||
let pos = dst.len() - payload_len;
|
let pos = dst.len() - payload_len;
|
||||||
apply_mask(&mut dst[pos..], mask);
|
apply_mask(&mut dst[pos..], mask);
|
||||||
|
|
|
@ -1,136 +1,57 @@
|
||||||
//! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs)
|
//! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs)
|
||||||
#![allow(clippy::cast_ptr_alignment)]
|
|
||||||
use std::ptr::copy_nonoverlapping;
|
|
||||||
use std::slice;
|
|
||||||
|
|
||||||
/// Holds a slice guaranteed to be shorter than 8 bytes.
|
/// Mask/unmask a frame.
|
||||||
struct ShortSlice<'a> {
|
|
||||||
inner: &'a mut [u8],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ShortSlice<'a> {
|
|
||||||
/// # Safety
|
|
||||||
/// Given slice must be shorter than 8 bytes.
|
|
||||||
unsafe fn new(slice: &'a mut [u8]) -> Self {
|
|
||||||
// Sanity check for debug builds
|
|
||||||
debug_assert!(slice.len() < 8);
|
|
||||||
ShortSlice { inner: slice }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn len(&self) -> usize {
|
|
||||||
self.inner.len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Faster version of `apply_mask()` which operates on 8-byte blocks.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[allow(clippy::cast_lossless)]
|
pub fn apply_mask(buf: &mut [u8], mask: [u8; 4]) {
|
||||||
pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
|
apply_mask_fast32(buf, mask)
|
||||||
// Extend the mask to 64 bits
|
}
|
||||||
let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64);
|
|
||||||
// Split the buffer into three segments
|
|
||||||
let (head, mid, tail) = align_buf(buf);
|
|
||||||
|
|
||||||
// Initial unaligned segment
|
/// A safe unoptimized mask application.
|
||||||
let head_len = head.len();
|
#[inline]
|
||||||
if head_len > 0 {
|
fn apply_mask_fallback(buf: &mut [u8], mask: [u8; 4]) {
|
||||||
xor_short(head, mask_u64);
|
for (i, byte) in buf.iter_mut().enumerate() {
|
||||||
|
*byte ^= mask[i & 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Faster version of `apply_mask()` which operates on 4-byte blocks.
|
||||||
|
#[inline]
|
||||||
|
pub fn apply_mask_fast32(buf: &mut [u8], mask: [u8; 4]) {
|
||||||
|
let mask_u32 = u32::from_ne_bytes(mask);
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
//
|
||||||
|
// buf is a valid slice borrowed mutably from bytes::BytesMut.
|
||||||
|
//
|
||||||
|
// un aligned prefix and suffix would be mask/unmask per byte.
|
||||||
|
// proper aligned middle slice goes into fast path and operates on 4-byte blocks.
|
||||||
|
let (mut prefix, words, mut suffix) = unsafe { buf.align_to_mut::<u32>() };
|
||||||
|
apply_mask_fallback(&mut prefix, mask);
|
||||||
|
let head = prefix.len() & 3;
|
||||||
|
let mask_u32 = if head > 0 {
|
||||||
if cfg!(target_endian = "big") {
|
if cfg!(target_endian = "big") {
|
||||||
mask_u64 = mask_u64.rotate_left(8 * head_len as u32);
|
mask_u32.rotate_left(8 * head as u32)
|
||||||
} else {
|
} else {
|
||||||
mask_u64 = mask_u64.rotate_right(8 * head_len as u32);
|
mask_u32.rotate_right(8 * head as u32)
|
||||||
}
|
|
||||||
}
|
|
||||||
// Aligned segment
|
|
||||||
for v in mid {
|
|
||||||
*v ^= mask_u64;
|
|
||||||
}
|
|
||||||
// Final unaligned segment
|
|
||||||
if tail.len() > 0 {
|
|
||||||
xor_short(tail, mask_u64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so
|
|
||||||
// inefficient, it could be done better. The compiler does not understand that
|
|
||||||
// a `ShortSlice` must be smaller than a u64.
|
|
||||||
#[inline]
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
|
||||||
fn xor_short(buf: ShortSlice<'_>, mask: u64) {
|
|
||||||
// SAFETY: we know that a `ShortSlice` fits in a u64
|
|
||||||
unsafe {
|
|
||||||
let (ptr, len) = (buf.inner.as_mut_ptr(), buf.len());
|
|
||||||
let mut b: u64 = 0;
|
|
||||||
#[allow(trivial_casts)]
|
|
||||||
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
|
|
||||||
b ^= mask;
|
|
||||||
#[allow(trivial_casts)]
|
|
||||||
copy_nonoverlapping(&b as *const _ as *const u8, ptr, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Safety
|
|
||||||
/// Caller must ensure the buffer has the correct size and alignment.
|
|
||||||
#[inline]
|
|
||||||
unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] {
|
|
||||||
// Assert correct size and alignment in debug builds
|
|
||||||
debug_assert!(buf.len().trailing_zeros() >= 3);
|
|
||||||
debug_assert!((buf.as_ptr() as usize).trailing_zeros() >= 3);
|
|
||||||
|
|
||||||
slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Splits a slice into three parts:
|
|
||||||
/// - an unaligned short head
|
|
||||||
/// - an aligned `u64` slice mid section
|
|
||||||
/// - an unaligned short tail
|
|
||||||
#[inline]
|
|
||||||
fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) {
|
|
||||||
let start_ptr = buf.as_ptr() as usize;
|
|
||||||
let end_ptr = start_ptr + buf.len();
|
|
||||||
|
|
||||||
// Round *up* to next aligned boundary for start
|
|
||||||
let start_aligned = (start_ptr + 7) & !0x7;
|
|
||||||
// Round *down* to last aligned boundary for end
|
|
||||||
let end_aligned = end_ptr & !0x7;
|
|
||||||
|
|
||||||
if end_aligned >= start_aligned {
|
|
||||||
// We have our three segments (head, mid, tail)
|
|
||||||
let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr);
|
|
||||||
let (head, mid) = tmp.split_at_mut(start_aligned - start_ptr);
|
|
||||||
|
|
||||||
// SAFETY: we know the middle section is correctly aligned, and the outer
|
|
||||||
// sections are smaller than 8 bytes
|
|
||||||
unsafe {
|
|
||||||
(
|
|
||||||
ShortSlice::new(head),
|
|
||||||
cast_slice(mid),
|
|
||||||
ShortSlice::new(tail),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We didn't cross even one aligned boundary!
|
mask_u32
|
||||||
|
};
|
||||||
// SAFETY: The outer sections are smaller than 8 bytes
|
for word in words.iter_mut() {
|
||||||
unsafe { (ShortSlice::new(buf), &mut [], ShortSlice::new(&mut [])) }
|
*word ^= mask_u32;
|
||||||
}
|
}
|
||||||
|
apply_mask_fallback(&mut suffix, mask_u32.to_ne_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::apply_mask;
|
use super::*;
|
||||||
|
|
||||||
/// A safe unoptimized mask application.
|
|
||||||
fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) {
|
|
||||||
for (i, byte) in buf.iter_mut().enumerate() {
|
|
||||||
*byte ^= mask[i & 3];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// legacy test from old apply mask test. kept for now for back compat test.
|
||||||
|
// TODO: remove it and favor the other test.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_mask() {
|
fn test_apply_mask_legacy() {
|
||||||
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
||||||
let mask_u32 = u32::from_le_bytes(mask);
|
|
||||||
|
|
||||||
let unmasked = vec![
|
let unmasked = vec![
|
||||||
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
|
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
|
||||||
|
@ -140,10 +61,10 @@ mod tests {
|
||||||
// Check masking with proper alignment.
|
// Check masking with proper alignment.
|
||||||
{
|
{
|
||||||
let mut masked = unmasked.clone();
|
let mut masked = unmasked.clone();
|
||||||
apply_mask_fallback(&mut masked, &mask);
|
apply_mask_fallback(&mut masked, mask);
|
||||||
|
|
||||||
let mut masked_fast = unmasked.clone();
|
let mut masked_fast = unmasked.clone();
|
||||||
apply_mask(&mut masked_fast, mask_u32);
|
apply_mask(&mut masked_fast, mask);
|
||||||
|
|
||||||
assert_eq!(masked, masked_fast);
|
assert_eq!(masked, masked_fast);
|
||||||
}
|
}
|
||||||
|
@ -151,12 +72,38 @@ mod tests {
|
||||||
// Check masking without alignment.
|
// Check masking without alignment.
|
||||||
{
|
{
|
||||||
let mut masked = unmasked.clone();
|
let mut masked = unmasked.clone();
|
||||||
apply_mask_fallback(&mut masked[1..], &mask);
|
apply_mask_fallback(&mut masked[1..], mask);
|
||||||
|
|
||||||
let mut masked_fast = unmasked;
|
let mut masked_fast = unmasked;
|
||||||
apply_mask(&mut masked_fast[1..], mask_u32);
|
apply_mask(&mut masked_fast[1..], mask);
|
||||||
|
|
||||||
assert_eq!(masked, masked_fast);
|
assert_eq!(masked, masked_fast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_apply_mask() {
|
||||||
|
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
||||||
|
let unmasked = vec![
|
||||||
|
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
|
||||||
|
0x74, 0xf9, 0x12, 0x03,
|
||||||
|
];
|
||||||
|
|
||||||
|
for data_len in 0..=unmasked.len() {
|
||||||
|
let unmasked = &unmasked[0..data_len];
|
||||||
|
// Check masking with different alignment.
|
||||||
|
for off in 0..=3 {
|
||||||
|
if unmasked.len() < off {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut masked = unmasked.to_vec();
|
||||||
|
apply_mask_fallback(&mut masked[off..], mask);
|
||||||
|
|
||||||
|
let mut masked_fast = unmasked.to_vec();
|
||||||
|
apply_mask_fast32(&mut masked_fast[off..], mask);
|
||||||
|
|
||||||
|
assert_eq!(masked, masked_fast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! WebSocket protocol support.
|
//! WebSocket protocol.
|
||||||
//!
|
//!
|
||||||
//! To setup a WebSocket, first do web socket handshake then on success convert `Payload` into a
|
//! To setup a WebSocket, first perform the WebSocket handshake then on success convert `Payload` into a
|
||||||
//! `WsStream` stream and then use `WsWriter` to communicate with the peer.
|
//! `WsStream` stream and then use `WsWriter` to communicate with the peer.
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
@ -76,7 +76,7 @@ pub enum HandshakeError {
|
||||||
#[display(fmt = "Method not allowed.")]
|
#[display(fmt = "Method not allowed.")]
|
||||||
GetMethodRequired,
|
GetMethodRequired,
|
||||||
|
|
||||||
/// Upgrade header if not set to websocket.
|
/// Upgrade header if not set to WebSocket.
|
||||||
#[display(fmt = "WebSocket upgrade is expected.")]
|
#[display(fmt = "WebSocket upgrade is expected.")]
|
||||||
NoWebsocketUpgrade,
|
NoWebsocketUpgrade,
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ pub enum HandshakeError {
|
||||||
#[display(fmt = "WebSocket version header is required.")]
|
#[display(fmt = "WebSocket version header is required.")]
|
||||||
NoVersionHeader,
|
NoVersionHeader,
|
||||||
|
|
||||||
/// Unsupported websocket version.
|
/// Unsupported WebSocket version.
|
||||||
#[display(fmt = "Unsupported version.")]
|
#[display(fmt = "Unsupported version.")]
|
||||||
UnsupportedVersion,
|
UnsupportedVersion,
|
||||||
|
|
||||||
|
@ -127,20 +127,20 @@ impl ResponseError for HandshakeError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify `WebSocket` handshake request and create handshake response.
|
/// Verify WebSocket handshake request and create handshake response.
|
||||||
pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
|
pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
|
||||||
verify_handshake(req)?;
|
verify_handshake(req)?;
|
||||||
Ok(handshake_response(req))
|
Ok(handshake_response(req))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify `WebSocket` handshake request.
|
/// Verify WebSocket handshake request.
|
||||||
pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
|
pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
|
||||||
// WebSocket accepts only GET
|
// WebSocket accepts only GET
|
||||||
if req.method != Method::GET {
|
if req.method != Method::GET {
|
||||||
return Err(HandshakeError::GetMethodRequired);
|
return Err(HandshakeError::GetMethodRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for "UPGRADE" to websocket header
|
// Check for "UPGRADE" to WebSocket header
|
||||||
let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) {
|
let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) {
|
||||||
if let Ok(s) = hdr.to_str() {
|
if let Ok(s) = hdr.to_str() {
|
||||||
s.to_ascii_lowercase().contains("websocket")
|
s.to_ascii_lowercase().contains("websocket")
|
||||||
|
@ -181,7 +181,7 @@ pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create websocket handshake response
|
/// Create WebSocket handshake response.
|
||||||
///
|
///
|
||||||
/// This function returns handshake `Response`, ready to send to peer.
|
/// This function returns handshake `Response`, ready to send to peer.
|
||||||
pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
|
pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
|
||||||
|
|
|
@ -74,8 +74,7 @@ impl From<u8> for OpCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Status code used to indicate why an endpoint is closing the `WebSocket`
|
/// Status code used to indicate why an endpoint is closing the WebSocket connection.
|
||||||
/// connection.
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||||
pub enum CloseCode {
|
pub enum CloseCode {
|
||||||
/// Indicates a normal closure, meaning that the purpose for
|
/// Indicates a normal closure, meaning that the purpose for
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::io;
|
||||||
use actix_http::error::{ErrorBadRequest, PayloadError};
|
use actix_http::error::{ErrorBadRequest, PayloadError};
|
||||||
use actix_http::http::header::{self, HeaderName, HeaderValue};
|
use actix_http::http::header::{self, HeaderName, HeaderValue};
|
||||||
use actix_http::http::{Method, StatusCode, Version};
|
use actix_http::http::{Method, StatusCode, Version};
|
||||||
use actix_http::httpmessage::HttpMessage;
|
use actix_http::HttpMessage;
|
||||||
use actix_http::{body, Error, HttpService, Request, Response};
|
use actix_http::{body, Error, HttpService, Request, Response};
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
use actix_service::{fn_service, ServiceFactoryExt};
|
use actix_service::{fn_service, ServiceFactoryExt};
|
||||||
|
|
|
@ -10,7 +10,7 @@ use futures_util::future::{self, err, ok, ready, FutureExt};
|
||||||
use futures_util::stream::{once, StreamExt};
|
use futures_util::stream::{once, StreamExt};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use actix_http::httpmessage::HttpMessage;
|
use actix_http::HttpMessage;
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response,
|
body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response,
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.0-beta.2 - 2021-02-10
|
||||||
|
* No notable changes.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.1 - 2021-01-07
|
## 0.4.0-beta.1 - 2021-01-07
|
||||||
* Fix multipart consuming payload before header checks. [#1513]
|
* Fix multipart consuming payload before header checks. [#1513]
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-multipart"
|
name = "actix-multipart"
|
||||||
version = "0.4.0-beta.1"
|
version = "0.4.0-beta.2"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Multipart support for actix web framework."
|
description = "Multipart form support for Actix Web"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
|
@ -16,7 +16,7 @@ name = "actix_multipart"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.1", default-features = false }
|
actix-web = { version = "4.0.0-beta.3", default-features = false }
|
||||||
actix-utils = "3.0.0-beta.2"
|
actix-utils = "3.0.0-beta.2"
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
@ -29,4 +29,4 @@ twoway = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2"
|
actix-rt = "2"
|
||||||
actix-http = "3.0.0-beta.1"
|
actix-http = "3.0.0-beta.3"
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
# Multipart support for actix web framework [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-multipart) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
# actix-multipart
|
||||||
|
|
||||||
## Documentation & community resources
|
> Multipart form support for Actix Web.
|
||||||
|
|
||||||
* [API Documentation](https://docs.rs/actix-multipart/)
|
[](https://crates.io/crates/actix-multipart)
|
||||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
[](https://docs.rs/actix-multipart/0.4.0-beta.2)
|
||||||
* Cargo package: [actix-multipart](https://crates.io/crates/actix-multipart)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||
* Minimum supported Rust version: 1.40 or later
|

|
||||||
|
<br />
|
||||||
|
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.2)
|
||||||
|
[](https://crates.io/crates/actix-multipart)
|
||||||
|
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
## Documentation & Resources
|
||||||
|
|
||||||
|
- [API Documentation](https://docs.rs/actix-multipart)
|
||||||
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
|
- Minimum Supported Rust Version (MSRV): 1.46.0
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! Multipart form support for Actix web.
|
//! Multipart form support for Actix Web.
|
||||||
|
|
||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
#![allow(clippy::borrow_interior_mutable_const)]
|
#![allow(clippy::borrow_interior_mutable_const)]
|
||||||
|
|
|
@ -13,9 +13,7 @@ use futures_util::stream::{LocalBoxStream, Stream, StreamExt};
|
||||||
|
|
||||||
use actix_utils::task::LocalWaker;
|
use actix_utils::task::LocalWaker;
|
||||||
use actix_web::error::{ParseError, PayloadError};
|
use actix_web::error::{ParseError, PayloadError};
|
||||||
use actix_web::http::header::{
|
use actix_web::http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue};
|
||||||
self, ContentDisposition, HeaderMap, HeaderName, HeaderValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::error::MultipartError;
|
use crate::error::MultipartError;
|
||||||
|
|
||||||
|
@ -120,10 +118,7 @@ impl Multipart {
|
||||||
impl Stream for Multipart {
|
impl Stream for Multipart {
|
||||||
type Item = Result<Field, MultipartError>;
|
type Item = Result<Field, MultipartError>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Self::Item>> {
|
|
||||||
if let Some(err) = self.error.take() {
|
if let Some(err) = self.error.take() {
|
||||||
Poll::Ready(Some(Err(err)))
|
Poll::Ready(Some(Err(err)))
|
||||||
} else if self.safety.current() {
|
} else if self.safety.current() {
|
||||||
|
@ -142,9 +137,7 @@ impl Stream for Multipart {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InnerMultipart {
|
impl InnerMultipart {
|
||||||
fn read_headers(
|
fn read_headers(payload: &mut PayloadBuffer) -> Result<Option<HeaderMap>, MultipartError> {
|
||||||
payload: &mut PayloadBuffer,
|
|
||||||
) -> Result<Option<HeaderMap>, MultipartError> {
|
|
||||||
match payload.read_until(b"\r\n\r\n")? {
|
match payload.read_until(b"\r\n\r\n")? {
|
||||||
None => {
|
None => {
|
||||||
if payload.eof {
|
if payload.eof {
|
||||||
|
@ -226,8 +219,7 @@ impl InnerMultipart {
|
||||||
if chunk.len() < boundary.len() {
|
if chunk.len() < boundary.len() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if &chunk[..2] == b"--"
|
if &chunk[..2] == b"--" && &chunk[2..chunk.len() - 2] == boundary.as_bytes()
|
||||||
&& &chunk[2..chunk.len() - 2] == boundary.as_bytes()
|
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
|
@ -273,9 +265,7 @@ impl InnerMultipart {
|
||||||
match field.borrow_mut().poll(safety) {
|
match field.borrow_mut().poll(safety) {
|
||||||
Poll::Pending => return Poll::Pending,
|
Poll::Pending => return Poll::Pending,
|
||||||
Poll::Ready(Some(Ok(_))) => continue,
|
Poll::Ready(Some(Ok(_))) => continue,
|
||||||
Poll::Ready(Some(Err(e))) => {
|
Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))),
|
||||||
return Poll::Ready(Some(Err(e)))
|
|
||||||
}
|
|
||||||
Poll::Ready(None) => true,
|
Poll::Ready(None) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -311,10 +301,7 @@ impl InnerMultipart {
|
||||||
}
|
}
|
||||||
// read boundary
|
// read boundary
|
||||||
InnerState::Boundary => {
|
InnerState::Boundary => {
|
||||||
match InnerMultipart::read_boundary(
|
match InnerMultipart::read_boundary(&mut *payload, &self.boundary)? {
|
||||||
&mut *payload,
|
|
||||||
&self.boundary,
|
|
||||||
)? {
|
|
||||||
None => return Poll::Pending,
|
None => return Poll::Pending,
|
||||||
Some(eof) => {
|
Some(eof) => {
|
||||||
if eof {
|
if eof {
|
||||||
|
@ -418,8 +405,7 @@ impl Field {
|
||||||
pub fn content_disposition(&self) -> Option<ContentDisposition> {
|
pub fn content_disposition(&self) -> Option<ContentDisposition> {
|
||||||
// RFC 7578: 'Each part MUST contain a Content-Disposition header field
|
// RFC 7578: 'Each part MUST contain a Content-Disposition header field
|
||||||
// where the disposition type is "form-data".'
|
// where the disposition type is "form-data".'
|
||||||
if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION)
|
if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION) {
|
||||||
{
|
|
||||||
ContentDisposition::from_raw(content_disposition).ok()
|
ContentDisposition::from_raw(content_disposition).ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -430,15 +416,10 @@ impl Field {
|
||||||
impl Stream for Field {
|
impl Stream for Field {
|
||||||
type Item = Result<Bytes, MultipartError>;
|
type Item = Result<Bytes, MultipartError>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Self::Item>> {
|
|
||||||
if self.safety.current() {
|
if self.safety.current() {
|
||||||
let mut inner = self.inner.borrow_mut();
|
let mut inner = self.inner.borrow_mut();
|
||||||
if let Some(mut payload) =
|
if let Some(mut payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety) {
|
||||||
inner.payload.as_ref().unwrap().get_mut(&self.safety)
|
|
||||||
{
|
|
||||||
payload.poll_stream(cx)?;
|
payload.poll_stream(cx)?;
|
||||||
}
|
}
|
||||||
inner.poll(&self.safety)
|
inner.poll(&self.safety)
|
||||||
|
@ -607,8 +588,7 @@ impl InnerField {
|
||||||
return Poll::Ready(None);
|
return Poll::Ready(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s)
|
let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) {
|
||||||
{
|
|
||||||
if !self.eof {
|
if !self.eof {
|
||||||
let res = if let Some(ref mut len) = self.length {
|
let res = if let Some(ref mut len) = self.length {
|
||||||
InnerField::read_len(&mut *payload, len)
|
InnerField::read_len(&mut *payload, len)
|
||||||
|
@ -628,7 +608,9 @@ impl InnerField {
|
||||||
Ok(None) => Poll::Pending,
|
Ok(None) => Poll::Pending,
|
||||||
Ok(Some(line)) => {
|
Ok(Some(line)) => {
|
||||||
if line.as_ref() != b"\r\n" {
|
if line.as_ref() != b"\r\n" {
|
||||||
log::warn!("multipart field did not read all the data or it is malformed");
|
log::warn!(
|
||||||
|
"multipart field did not read all the data or it is malformed"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
}
|
}
|
||||||
|
@ -804,9 +786,7 @@ impl PayloadBuffer {
|
||||||
/// Read bytes until new line delimiter or eof
|
/// Read bytes until new line delimiter or eof
|
||||||
pub fn readline_or_eof(&mut self) -> Result<Option<Bytes>, MultipartError> {
|
pub fn readline_or_eof(&mut self) -> Result<Option<Bytes>, MultipartError> {
|
||||||
match self.readline() {
|
match self.readline() {
|
||||||
Err(MultipartError::Incomplete) if self.eof => {
|
Err(MultipartError::Incomplete) if self.eof => Ok(Some(self.buf.split().freeze())),
|
||||||
Ok(Some(self.buf.split().freeze()))
|
|
||||||
}
|
|
||||||
line => line,
|
line => line,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -902,10 +882,7 @@ mod tests {
|
||||||
impl Stream for SlowStream {
|
impl Stream for SlowStream {
|
||||||
type Item = Result<Bytes, PayloadError>;
|
type Item = Result<Bytes, PayloadError>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Self::Item>> {
|
|
||||||
let this = self.get_mut();
|
let this = self.get_mut();
|
||||||
if !this.ready {
|
if !this.ready {
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0-beta.2 - 2021-02-10
|
||||||
|
* No notable changes.
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.1 - 2021-01-07
|
## 4.0.0-beta.1 - 2021-01-07
|
||||||
* Update `pin-project` to `1.0`.
|
* Update `pin-project` to `1.0`.
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-web-actors"
|
name = "actix-web-actors"
|
||||||
version = "4.0.0-beta.1"
|
version = "4.0.0-beta.2"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix actors support for actix web framework."
|
description = "Actix actors support for Actix Web"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["actix", "http", "web", "framework", "async"]
|
keywords = ["actix", "http", "web", "framework", "async"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
|
@ -18,8 +18,8 @@ path = "src/lib.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = { version = "0.11.0-beta.2", default-features = false }
|
actix = { version = "0.11.0-beta.2", default-features = false }
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-http = "3.0.0-beta.1"
|
actix-http = "3.0.0-beta.3"
|
||||||
actix-web = { version = "4.0.0-beta.1", default-features = false }
|
actix-web = { version = "4.0.0-beta.3", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
Actix actors support for actix web framework [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-web-actors) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
# actix-web-actors
|
||||||
|
|
||||||
## Documentation & community resources
|
> Actix actors support for Actix Web.
|
||||||
|
|
||||||
* [API Documentation](https://docs.rs/actix-web-actors/)
|
[](https://crates.io/crates/actix-web-actors)
|
||||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
[](https://docs.rs/actix-web-actors/0.5.0)
|
||||||
* Cargo package: [actix-web-actors](https://crates.io/crates/actix-web-actors)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||
* Minimum supported Rust version: 1.40 or later
|

|
||||||
|
<br />
|
||||||
|
[](https://deps.rs/crate/actix-web-actors/0.5.0)
|
||||||
|
[](https://crates.io/crates/actix-web-actors)
|
||||||
|
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
## Documentation & Resources
|
||||||
|
|
||||||
|
- [API Documentation](https://docs.rs/actix-web-actors)
|
||||||
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
|
- Minimum supported Rust version: 1.46 or later
|
||||||
|
|
|
@ -3,9 +3,7 @@ use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use actix::dev::{
|
use actix::dev::{AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope};
|
||||||
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope,
|
|
||||||
};
|
|
||||||
use actix::fut::ActorFuture;
|
use actix::fut::ActorFuture;
|
||||||
use actix::{
|
use actix::{
|
||||||
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
|
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
|
||||||
|
@ -15,7 +13,7 @@ use bytes::Bytes;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use tokio::sync::oneshot::Sender;
|
use tokio::sync::oneshot::Sender;
|
||||||
|
|
||||||
/// Execution context for http actors
|
/// Execution context for HTTP actors
|
||||||
pub struct HttpContext<A>
|
pub struct HttpContext<A>
|
||||||
where
|
where
|
||||||
A: Actor<Context = HttpContext<A>>,
|
A: Actor<Context = HttpContext<A>>,
|
||||||
|
@ -165,10 +163,7 @@ where
|
||||||
{
|
{
|
||||||
type Item = Result<Bytes, Error>;
|
type Item = Result<Bytes, Error>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Self::Item>> {
|
|
||||||
if self.fut.alive() {
|
if self.fut.alive() {
|
||||||
let _ = Pin::new(&mut self.fut).poll(cx);
|
let _ = Pin::new(&mut self.fut).poll(cx);
|
||||||
}
|
}
|
||||||
|
@ -233,10 +228,11 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_default_resource() {
|
async fn test_default_resource() {
|
||||||
let srv = init_service(App::new().service(web::resource("/test").to(|| {
|
let srv =
|
||||||
HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 }))
|
init_service(App::new().service(web::resource("/test").to(|| {
|
||||||
})))
|
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;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! Actix actors integration for Actix web framework
|
//! Actix actors support for Actix Web.
|
||||||
|
|
||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
#![allow(clippy::borrow_interior_mutable_const)]
|
#![allow(clippy::borrow_interior_mutable_const)]
|
||||||
|
|
|
@ -7,13 +7,12 @@ use std::task::{Context, Poll};
|
||||||
use std::{collections::VecDeque, convert::TryFrom};
|
use std::{collections::VecDeque, convert::TryFrom};
|
||||||
|
|
||||||
use actix::dev::{
|
use actix::dev::{
|
||||||
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler,
|
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, ToEnvelope,
|
||||||
ToEnvelope,
|
|
||||||
};
|
};
|
||||||
use actix::fut::ActorFuture;
|
use actix::fut::ActorFuture;
|
||||||
use actix::{
|
use actix::{
|
||||||
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler,
|
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message as ActixMessage,
|
||||||
Message as ActixMessage, SpawnHandle,
|
SpawnHandle,
|
||||||
};
|
};
|
||||||
use actix_codec::{Decoder, Encoder};
|
use actix_codec::{Decoder, Encoder};
|
||||||
use actix_http::ws::{hash_key, Codec};
|
use actix_http::ws::{hash_key, Codec};
|
||||||
|
@ -32,8 +31,7 @@ use tokio::sync::oneshot::Sender;
|
||||||
/// Perform WebSocket handshake and start actor.
|
/// Perform WebSocket handshake and start actor.
|
||||||
pub fn start<A, T>(actor: A, req: &HttpRequest, stream: T) -> Result<HttpResponse, Error>
|
pub fn start<A, T>(actor: A, req: &HttpRequest, stream: T) -> Result<HttpResponse, Error>
|
||||||
where
|
where
|
||||||
A: Actor<Context = WebsocketContext<A>>
|
A: Actor<Context = WebsocketContext<A>> + StreamHandler<Result<Message, ProtocolError>>,
|
||||||
+ StreamHandler<Result<Message, ProtocolError>>,
|
|
||||||
T: Stream<Item = Result<Bytes, PayloadError>> + 'static,
|
T: Stream<Item = Result<Bytes, PayloadError>> + 'static,
|
||||||
{
|
{
|
||||||
let mut res = handshake(req)?;
|
let mut res = handshake(req)?;
|
||||||
|
@ -50,15 +48,14 @@ where
|
||||||
///
|
///
|
||||||
/// If successful, returns a pair where the first item is an address for the
|
/// If successful, returns a pair where the first item is an address for the
|
||||||
/// created actor and the second item is the response that should be returned
|
/// created actor and the second item is the response that should be returned
|
||||||
/// from the websocket request.
|
/// from the WebSocket request.
|
||||||
pub fn start_with_addr<A, T>(
|
pub fn start_with_addr<A, T>(
|
||||||
actor: A,
|
actor: A,
|
||||||
req: &HttpRequest,
|
req: &HttpRequest,
|
||||||
stream: T,
|
stream: T,
|
||||||
) -> Result<(Addr<A>, HttpResponse), Error>
|
) -> Result<(Addr<A>, HttpResponse), Error>
|
||||||
where
|
where
|
||||||
A: Actor<Context = WebsocketContext<A>>
|
A: Actor<Context = WebsocketContext<A>> + StreamHandler<Result<Message, ProtocolError>>,
|
||||||
+ StreamHandler<Result<Message, ProtocolError>>,
|
|
||||||
T: Stream<Item = Result<Bytes, PayloadError>> + 'static,
|
T: Stream<Item = Result<Bytes, PayloadError>> + 'static,
|
||||||
{
|
{
|
||||||
let mut res = handshake(req)?;
|
let mut res = handshake(req)?;
|
||||||
|
@ -66,7 +63,7 @@ where
|
||||||
Ok((addr, res.streaming(out_stream)))
|
Ok((addr, res.streaming(out_stream)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Do websocket handshake and start ws actor.
|
/// Do WebSocket handshake and start ws actor.
|
||||||
///
|
///
|
||||||
/// `protocols` is a sequence of known protocols.
|
/// `protocols` is a sequence of known protocols.
|
||||||
pub fn start_with_protocols<A, T>(
|
pub fn start_with_protocols<A, T>(
|
||||||
|
@ -76,15 +73,14 @@ pub fn start_with_protocols<A, T>(
|
||||||
stream: T,
|
stream: T,
|
||||||
) -> Result<HttpResponse, Error>
|
) -> Result<HttpResponse, Error>
|
||||||
where
|
where
|
||||||
A: Actor<Context = WebsocketContext<A>>
|
A: Actor<Context = WebsocketContext<A>> + StreamHandler<Result<Message, ProtocolError>>,
|
||||||
+ StreamHandler<Result<Message, ProtocolError>>,
|
|
||||||
T: Stream<Item = Result<Bytes, PayloadError>> + 'static,
|
T: Stream<Item = Result<Bytes, PayloadError>> + 'static,
|
||||||
{
|
{
|
||||||
let mut res = handshake_with_protocols(req, protocols)?;
|
let mut res = handshake_with_protocols(req, protocols)?;
|
||||||
Ok(res.streaming(WebsocketContext::create(actor, stream)))
|
Ok(res.streaming(WebsocketContext::create(actor, stream)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare `WebSocket` handshake response.
|
/// Prepare WebSocket handshake response.
|
||||||
///
|
///
|
||||||
/// This function returns handshake `HttpResponse`, ready to send to peer.
|
/// This function returns handshake `HttpResponse`, ready to send to peer.
|
||||||
/// It does not perform any IO.
|
/// It does not perform any IO.
|
||||||
|
@ -92,7 +88,7 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeErro
|
||||||
handshake_with_protocols(req, &[])
|
handshake_with_protocols(req, &[])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare `WebSocket` handshake response.
|
/// Prepare WebSocket handshake response.
|
||||||
///
|
///
|
||||||
/// This function returns handshake `HttpResponse`, ready to send to peer.
|
/// This function returns handshake `HttpResponse`, ready to send to peer.
|
||||||
/// It does not perform any IO.
|
/// It does not perform any IO.
|
||||||
|
@ -109,7 +105,7 @@ pub fn handshake_with_protocols(
|
||||||
return Err(HandshakeError::GetMethodRequired);
|
return Err(HandshakeError::GetMethodRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for "UPGRADE" to websocket header
|
// check for "UPGRADE" to WebSocket header
|
||||||
let has_hdr = if let Some(hdr) = req.headers().get(&header::UPGRADE) {
|
let has_hdr = if let Some(hdr) = req.headers().get(&header::UPGRADE) {
|
||||||
if let Ok(s) = hdr.to_str() {
|
if let Ok(s) = hdr.to_str() {
|
||||||
s.to_ascii_lowercase().contains("websocket")
|
s.to_ascii_lowercase().contains("websocket")
|
||||||
|
@ -301,10 +297,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new Websocket context
|
/// Create a new Websocket context
|
||||||
pub fn with_factory<S, F>(
|
pub fn with_factory<S, F>(stream: S, f: F) -> impl Stream<Item = Result<Bytes, Error>>
|
||||||
stream: S,
|
|
||||||
f: F,
|
|
||||||
) -> impl Stream<Item = Result<Bytes, Error>>
|
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> A + 'static,
|
F: FnOnce(&mut Self) -> A + 'static,
|
||||||
A: StreamHandler<Result<Message, ProtocolError>>,
|
A: StreamHandler<Result<Message, ProtocolError>>,
|
||||||
|
@ -423,10 +416,7 @@ where
|
||||||
{
|
{
|
||||||
type Item = Result<Bytes, Error>;
|
type Item = Result<Bytes, Error>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Self::Item>> {
|
|
||||||
let this = self.get_mut();
|
let this = self.get_mut();
|
||||||
|
|
||||||
if this.fut.alive() {
|
if this.fut.alive() {
|
||||||
|
@ -493,10 +483,7 @@ where
|
||||||
{
|
{
|
||||||
type Item = Result<Message, ProtocolError>;
|
type Item = Result<Message, ProtocolError>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Self::Item>> {
|
|
||||||
let mut this = self.as_mut().project();
|
let mut this = self.as_mut().project();
|
||||||
|
|
||||||
if !*this.closed {
|
if !*this.closed {
|
||||||
|
@ -512,9 +499,10 @@ where
|
||||||
}
|
}
|
||||||
Poll::Pending => break,
|
Poll::Pending => break,
|
||||||
Poll::Ready(Some(Err(e))) => {
|
Poll::Ready(Some(Err(e))) => {
|
||||||
return Poll::Ready(Some(Err(ProtocolError::Io(
|
return Poll::Ready(Some(Err(ProtocolError::Io(io::Error::new(
|
||||||
io::Error::new(io::ErrorKind::Other, format!("{}", e)),
|
io::ErrorKind::Other,
|
||||||
))));
|
format!("{}", e),
|
||||||
|
)))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,7 @@ impl Actor for Ws {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Ws {
|
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Ws {
|
||||||
fn handle(
|
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
|
||||||
&mut self,
|
|
||||||
msg: Result<ws::Message, ws::ProtocolError>,
|
|
||||||
ctx: &mut Self::Context,
|
|
||||||
) {
|
|
||||||
match msg.unwrap() {
|
match msg.unwrap() {
|
||||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||||
ws::Message::Text(text) => ctx.text(text),
|
ws::Message::Text(text) => ctx.text(text),
|
||||||
|
@ -30,9 +26,7 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Ws {
|
||||||
async fn test_simple() {
|
async fn test_simple() {
|
||||||
let mut srv = test::start(|| {
|
let mut srv = test::start(|| {
|
||||||
App::new().service(web::resource("/").to(
|
App::new().service(web::resource("/").to(
|
||||||
|req: HttpRequest, stream: web::Payload| async move {
|
|req: HttpRequest, stream: web::Payload| async move { ws::start(Ws, &req, stream) },
|
||||||
ws::start(Ws, &req, stream)
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0-beta.1 - 2021-02-10
|
||||||
|
* Use new call signature for `System::new`.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0 - 2020-09-20
|
## 0.4.0 - 2020-09-20
|
||||||
* Added compile success and failure testing. [#1677]
|
* Added compile success and failure testing. [#1677]
|
||||||
* Add `route` macro for supporting multiple HTTP methods guards. [#1674]
|
* Add `route` macro for supporting multiple HTTP methods guards. [#1674]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-web-codegen"
|
name = "actix-web-codegen"
|
||||||
version = "0.4.0"
|
version = "0.5.0-beta.1"
|
||||||
description = "Actix web proc macros"
|
description = "Routing and runtime macros for Actix Web"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
repository = "https://github.com/actix/actix-web"
|
repository = "https://github.com/actix/actix-web"
|
||||||
|
@ -20,7 +20,7 @@ proc-macro2 = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2"
|
actix-rt = "2"
|
||||||
actix-web = "4.0.0-beta.1"
|
actix-web = "4.0.0-beta.3"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
trybuild = "1"
|
trybuild = "1"
|
||||||
rustversion = "1"
|
rustversion = "1"
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
# actix-web-codegen
|
# actix-web-codegen
|
||||||
|
|
||||||
> Helper and convenience macros for Actix Web
|
> Routing and runtime macros for Actix Web.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web-codegen)
|
[](https://crates.io/crates/actix-web-codegen)
|
||||||
[](https://docs.rs/actix-web-codegen/0.4.0/actix_web_codegen/)
|
[](https://docs.rs/actix-web-codegen/0.5.0-beta.1)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||
[](https://travis-ci.org/actix/actix-web)
|

|
||||||
[](https://codecov.io/gh/actix/actix-web)
|
<br />
|
||||||
|
[](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.1)
|
||||||
|
[](https://crates.io/crates/actix-web-codegen)
|
||||||
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
||||||
- [API Documentation](https://docs.rs/actix-web-codegen)
|
- [API Documentation](https://docs.rs/actix-web-codegen)
|
||||||
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
- Cargo package: [actix-web-codegen](https://crates.io/crates/actix-web-codegen)
|
|
||||||
- Minimum supported Rust version: 1.46 or later.
|
- Minimum supported Rust version: 1.46 or later.
|
||||||
|
|
||||||
## Compile Testing
|
## Compile Testing
|
||||||
|
|
||||||
Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this.
|
Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this.
|
||||||
|
|
||||||
[`trybuild`]: https://github.com/dtolnay/trybuild
|
[`trybuild`]: https://github.com/dtolnay/trybuild
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Macros for reducing boilerplate code in Actix Web applications.
|
//! Routing and runtime macros for Actix Web.
|
||||||
//!
|
//!
|
||||||
//! ## Actix Web Re-exports
|
//! # Actix Web Re-exports
|
||||||
//! Actix Web re-exports a version of this crate in it's entirety so you usually don't have to
|
//! Actix Web re-exports a version of this crate in it's entirety so you usually don't have to
|
||||||
//! specify a dependency on this crate explicitly. Sometimes, however, updates are made to this
|
//! specify a dependency on this crate explicitly. Sometimes, however, updates are made to this
|
||||||
//! crate before the actix-web dependency is updated. Therefore, code examples here will show
|
//! crate before the actix-web dependency is updated. Therefore, code examples here will show
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
//! # Runtime Setup
|
//! # Runtime Setup
|
||||||
//! Used for setting up the actix async runtime. See [macro@main] macro docs.
|
//! Used for setting up the actix async runtime. See [macro@main] macro docs.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```
|
||||||
//! #[actix_web_codegen::main] // or `#[actix_web::main]` in Actix Web apps
|
//! #[actix_web_codegen::main] // or `#[actix_web::main]` in Actix Web apps
|
||||||
//! async fn main() {
|
//! async fn main() {
|
||||||
//! async { println!("Hello world"); }.await
|
//! async { println!("Hello world"); }.await
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
//!
|
//!
|
||||||
//! See docs for: [GET], [POST], [PATCH], [PUT], [DELETE], [HEAD], [CONNECT], [OPTIONS], [TRACE]
|
//! See docs for: [GET], [POST], [PATCH], [PUT], [DELETE], [HEAD], [CONNECT], [OPTIONS], [TRACE]
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```
|
||||||
//! # use actix_web::HttpResponse;
|
//! # use actix_web::HttpResponse;
|
||||||
//! # use actix_web_codegen::get;
|
//! # use actix_web_codegen::get;
|
||||||
//! #[get("/test")]
|
//! #[get("/test")]
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
//! Similar to the single method handler macro but takes one or more arguments for the HTTP methods
|
//! Similar to the single method handler macro but takes one or more arguments for the HTTP methods
|
||||||
//! it should respond to. See [macro@route] macro docs.
|
//! it should respond to. See [macro@route] macro docs.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```
|
||||||
//! # use actix_web::HttpResponse;
|
//! # use actix_web::HttpResponse;
|
||||||
//! # use actix_web_codegen::route;
|
//! # use actix_web_codegen::route;
|
||||||
//! #[route("/test", method="GET", method="HEAD")]
|
//! #[route("/test", method="GET", method="HEAD")]
|
||||||
|
@ -159,7 +159,7 @@ method_macro! {
|
||||||
/// # Actix Web Re-export
|
/// # Actix Web Re-export
|
||||||
/// This macro can be applied with `#[actix_web::main]` when used in Actix Web applications.
|
/// This macro can be applied with `#[actix_web::main]` when used in Actix Web applications.
|
||||||
///
|
///
|
||||||
/// # Usage
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #[actix_web_codegen::main]
|
/// #[actix_web_codegen::main]
|
||||||
/// async fn main() {
|
/// async fn main() {
|
||||||
|
|
|
@ -4,9 +4,7 @@ use std::task::{Context, Poll};
|
||||||
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
|
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
|
||||||
use actix_web::http::header::{HeaderName, HeaderValue};
|
use actix_web::http::header::{HeaderName, HeaderValue};
|
||||||
use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder};
|
use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder};
|
||||||
use actix_web_codegen::{
|
use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace};
|
||||||
connect, delete, get, head, options, patch, post, put, route, trace,
|
|
||||||
};
|
|
||||||
use futures_util::future::{self, LocalBoxFuture};
|
use futures_util::future::{self, LocalBoxFuture};
|
||||||
|
|
||||||
// Make sure that we can name function as 'config'
|
// Make sure that we can name function as 'config'
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.2 - 2021-02-10
|
||||||
### Added
|
### Added
|
||||||
* `ClientRequest::insert_header` method which allows using typed headers. [#1869]
|
* `ClientRequest::insert_header` method which allows using typed headers. [#1869]
|
||||||
* `ClientRequest::append_header` method which allows using typed headers. [#1869]
|
* `ClientRequest::append_header` method which allows using typed headers. [#1869]
|
||||||
|
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905]
|
* Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905]
|
||||||
|
@ -16,6 +20,7 @@
|
||||||
|
|
||||||
[#1869]: https://github.com/actix/actix-web/pull/1869
|
[#1869]: https://github.com/actix/actix-web/pull/1869
|
||||||
[#1905]: https://github.com/actix/actix-web/pull/1905
|
[#1905]: https://github.com/actix/actix-web/pull/1905
|
||||||
|
[#1969]: https://github.com/actix/actix-web/pull/1969
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.1 - 2021-01-07
|
## 3.0.0-beta.1 - 2021-01-07
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "awc"
|
name = "awc"
|
||||||
version = "3.0.0-beta.1"
|
version = "3.0.0-beta.2"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Async HTTP and WebSocket client library built on the Actix ecosystem"
|
description = "Async HTTP and WebSocket client library built on the Actix ecosystem"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -36,10 +36,13 @@ rustls = ["tls-rustls", "actix-http/rustls"]
|
||||||
# content-encoding support
|
# content-encoding support
|
||||||
compress = ["actix-http/compress"]
|
compress = ["actix-http/compress"]
|
||||||
|
|
||||||
|
# trust-dns as dns resolver
|
||||||
|
trust-dns = ["actix-http/trust-dns"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-service = "2.0.0-beta.4"
|
actix-service = "2.0.0-beta.4"
|
||||||
actix-http = "3.0.0-beta.1"
|
actix-http = "3.0.0-beta.3"
|
||||||
actix-rt = "2"
|
actix-rt = "2"
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
|
@ -57,10 +60,16 @@ serde_urlencoded = "0.7"
|
||||||
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||||
tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
|
tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies.tls-openssl]
|
||||||
|
version = "0.10.9"
|
||||||
|
package = "openssl"
|
||||||
|
features = ["vendored"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.1", features = ["openssl"] }
|
actix-web = { version = "4.0.0-beta.3", features = ["openssl"] }
|
||||||
actix-http = { version = "3.0.0-beta.1", features = ["openssl"] }
|
actix-http = { version = "3.0.0-beta.3", features = ["openssl"] }
|
||||||
actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] }
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-utils = "3.0.0-beta.1"
|
||||||
actix-server = "2.0.0-beta.3"
|
actix-server = "2.0.0-beta.3"
|
||||||
actix-tls = { version = "3.0.0-beta.3", features = ["openssl", "rustls"] }
|
actix-tls = { version = "3.0.0-beta.3", features = ["openssl", "rustls"] }
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
> Async HTTP and WebSocket client library.
|
> Async HTTP and WebSocket client library.
|
||||||
|
|
||||||
[](https://crates.io/crates/awc)
|
[](https://crates.io/crates/awc)
|
||||||
[](https://docs.rs/awc/2.0.3)
|
[](https://docs.rs/awc/3.0.0-beta.2)
|
||||||

|

|
||||||
[](https://deps.rs/crate/awc/2.0.3)
|
[](https://deps.rs/crate/awc/3.0.0-beta.2)
|
||||||
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
|
@ -82,8 +82,9 @@ impl ClientBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maximum supported http major version
|
/// Maximum supported HTTP major version.
|
||||||
/// Supported versions http/1.1, http/2
|
///
|
||||||
|
/// Supported versions are HTTP/1.1 and HTTP/2.
|
||||||
pub fn max_http_version(mut self, val: http::Version) -> Self {
|
pub fn max_http_version(mut self, val: http::Version) -> Self {
|
||||||
self.max_http_version = Some(val);
|
self.max_http_version = Some(val);
|
||||||
self
|
self
|
||||||
|
|
|
@ -82,8 +82,7 @@ where
|
||||||
let connection = fut.await?;
|
let connection = fut.await?;
|
||||||
|
|
||||||
// send request
|
// send request
|
||||||
let (head, framed) =
|
let (head, framed) = connection.open_tunnel(RequestHeadType::from(head)).await?;
|
||||||
connection.open_tunnel(RequestHeadType::from(head)).await?;
|
|
||||||
|
|
||||||
let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io))));
|
let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io))));
|
||||||
Ok((head, framed))
|
Ok((head, framed))
|
||||||
|
@ -142,10 +141,7 @@ impl AsyncWrite for BoxedSocket {
|
||||||
Pin::new(self.get_mut().0.as_write()).poll_flush(cx)
|
Pin::new(self.get_mut().0.as_write()).poll_flush(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_shutdown(
|
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||||
self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<io::Result<()>> {
|
|
||||||
Pin::new(self.get_mut().0.as_write()).poll_shutdown(cx)
|
Pin::new(self.get_mut().0.as_write()).poll_shutdown(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
//! Http client errors
|
//! Http client errors
|
||||||
pub use actix_http::client::{
|
pub use actix_http::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
|
||||||
ConnectError, FreezeRequestError, InvalidUrl, SendRequestError,
|
|
||||||
};
|
|
||||||
pub use actix_http::error::PayloadError;
|
pub use actix_http::error::PayloadError;
|
||||||
pub use actix_http::http::Error as HttpError;
|
pub use actix_http::http::Error as HttpError;
|
||||||
pub use actix_http::ws::HandshakeError as WsHandshakeError;
|
pub use actix_http::ws::HandshakeError as WsHandshakeError;
|
||||||
|
|
|
@ -145,7 +145,9 @@ impl FrozenSendBuilder {
|
||||||
{
|
{
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => match value.try_into_value() {
|
Ok(key) => match value.try_into_value() {
|
||||||
Ok(value) => self.extra_headers.insert(key, value),
|
Ok(value) => {
|
||||||
|
self.extra_headers.insert(key, value);
|
||||||
|
}
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
},
|
},
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
|
|
|
@ -134,7 +134,7 @@ use self::connect::{Connect, ConnectorWrapper};
|
||||||
///
|
///
|
||||||
/// let res = client.get("http://www.rust-lang.org") // <- Create request builder
|
/// let res = client.get("http://www.rust-lang.org") // <- Create request builder
|
||||||
/// .insert_header(("User-Agent", "Actix-web"))
|
/// .insert_header(("User-Agent", "Actix-web"))
|
||||||
/// .send() // <- Send http request
|
/// .send() // <- Send HTTP request
|
||||||
/// .await; // <- send request and wait for response
|
/// .await; // <- send request and wait for response
|
||||||
///
|
///
|
||||||
/// println!("Response: {:?}", res);
|
/// println!("Response: {:?}", res);
|
||||||
|
|
|
@ -11,8 +11,7 @@ use actix_http::body::Body;
|
||||||
use actix_http::cookie::{Cookie, CookieJar};
|
use actix_http::cookie::{Cookie, CookieJar};
|
||||||
use actix_http::http::header::{self, IntoHeaderPair};
|
use actix_http::http::header::{self, IntoHeaderPair};
|
||||||
use actix_http::http::{
|
use actix_http::http::{
|
||||||
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri,
|
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version,
|
||||||
Version,
|
|
||||||
};
|
};
|
||||||
use actix_http::{Error, RequestHead};
|
use actix_http::{Error, RequestHead};
|
||||||
|
|
||||||
|
@ -42,10 +41,10 @@ cfg_if::cfg_if! {
|
||||||
/// let response = awc::Client::new()
|
/// let response = awc::Client::new()
|
||||||
/// .get("http://www.rust-lang.org") // <- Create request builder
|
/// .get("http://www.rust-lang.org") // <- Create request builder
|
||||||
/// .insert_header(("User-Agent", "Actix-web"))
|
/// .insert_header(("User-Agent", "Actix-web"))
|
||||||
/// .send() // <- Send http request
|
/// .send() // <- Send HTTP request
|
||||||
/// .await;
|
/// .await;
|
||||||
///
|
///
|
||||||
/// response.and_then(|response| { // <- server http response
|
/// response.and_then(|response| { // <- server HTTP response
|
||||||
/// println!("Response: {:?}", response);
|
/// println!("Response: {:?}", response);
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// });
|
/// });
|
||||||
|
@ -159,7 +158,9 @@ impl ClientRequest {
|
||||||
H: IntoHeaderPair,
|
H: IntoHeaderPair,
|
||||||
{
|
{
|
||||||
match header.try_into_header_pair() {
|
match header.try_into_header_pair() {
|
||||||
Ok((key, value)) => self.head.headers.insert(key, value),
|
Ok((key, value)) => {
|
||||||
|
self.head.headers.insert(key, value);
|
||||||
|
}
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -217,7 +218,7 @@ impl ClientRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force close connection instead of returning it back to connections pool.
|
/// Force close connection instead of returning it back to connections pool.
|
||||||
/// This setting affect only http/1 connections.
|
/// This setting affect only HTTP/1 connections.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn force_close(mut self) -> Self {
|
pub fn force_close(mut self) -> Self {
|
||||||
self.head.set_connection_type(ConnectionType::Close);
|
self.head.set_connection_type(ConnectionType::Close);
|
||||||
|
@ -232,7 +233,9 @@ impl ClientRequest {
|
||||||
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
|
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
|
||||||
{
|
{
|
||||||
match HeaderValue::try_from(value) {
|
match HeaderValue::try_from(value) {
|
||||||
Ok(value) => self.head.headers.insert(header::CONTENT_TYPE, value),
|
Ok(value) => {
|
||||||
|
self.head.headers.insert(header::CONTENT_TYPE, value);
|
||||||
|
}
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
|
@ -516,15 +519,11 @@ impl ClientRequest {
|
||||||
.unwrap_or(true);
|
.unwrap_or(true);
|
||||||
|
|
||||||
if https {
|
if https {
|
||||||
slf =
|
slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING))
|
||||||
slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING))
|
|
||||||
} else {
|
} else {
|
||||||
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
|
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||||
{
|
{
|
||||||
slf = slf.insert_header_if_none((
|
slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, "gzip, deflate"))
|
||||||
header::ACCEPT_ENCODING,
|
|
||||||
"gzip, deflate",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,8 +85,7 @@ impl<S> HttpMessage for ClientResponse<S> {
|
||||||
if self.extensions().get::<Cookies>().is_none() {
|
if self.extensions().get::<Cookies>().is_none() {
|
||||||
let mut cookies = Vec::new();
|
let mut cookies = Vec::new();
|
||||||
for hdr in self.headers().get_all(&SET_COOKIE) {
|
for hdr in self.headers().get_all(&SET_COOKIE) {
|
||||||
let s = std::str::from_utf8(hdr.as_bytes())
|
let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
|
||||||
.map_err(CookieParseError::from)?;
|
|
||||||
cookies.push(Cookie::parse_encoded(s)?.into_owned());
|
cookies.push(Cookie::parse_encoded(s)?.into_owned());
|
||||||
}
|
}
|
||||||
self.extensions_mut().insert(Cookies(cookies));
|
self.extensions_mut().insert(Cookies(cookies));
|
||||||
|
@ -185,7 +184,7 @@ impl<S> ClientResponse<S>
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, PayloadError>>,
|
S: Stream<Item = Result<Bytes, PayloadError>>,
|
||||||
{
|
{
|
||||||
/// Loads http response's body.
|
/// Loads HTTP response's body.
|
||||||
pub fn body(&mut self) -> MessageBody<S> {
|
pub fn body(&mut self) -> MessageBody<S> {
|
||||||
MessageBody::new(self)
|
MessageBody::new(self)
|
||||||
}
|
}
|
||||||
|
@ -230,7 +229,7 @@ impl<S> fmt::Debug for ClientResponse<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Future that resolves to a complete http message body.
|
/// Future that resolves to a complete HTTP message body.
|
||||||
pub struct MessageBody<S> {
|
pub struct MessageBody<S> {
|
||||||
length: Option<usize>,
|
length: Option<usize>,
|
||||||
err: Option<PayloadError>,
|
err: Option<PayloadError>,
|
||||||
|
@ -393,9 +392,7 @@ where
|
||||||
|
|
||||||
if let Some(len) = self.length.take() {
|
if let Some(len) = self.length.take() {
|
||||||
if len > self.fut.as_ref().unwrap().limit {
|
if len > self.fut.as_ref().unwrap().limit {
|
||||||
return Poll::Ready(Err(JsonPayloadError::Payload(
|
return Poll::Ready(Err(JsonPayloadError::Payload(PayloadError::Overflow)));
|
||||||
PayloadError::Overflow,
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,8 +462,7 @@ mod tests {
|
||||||
_ => unreachable!("error"),
|
_ => unreachable!("error"),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut req =
|
let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish();
|
||||||
TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish();
|
|
||||||
match req.body().await.err().unwrap() {
|
match req.body().await.err().unwrap() {
|
||||||
PayloadError::Overflow => {}
|
PayloadError::Overflow => {}
|
||||||
_ => unreachable!("error"),
|
_ => unreachable!("error"),
|
||||||
|
|
|
@ -78,8 +78,7 @@ impl SendClientRequest {
|
||||||
|
|
||||||
#[cfg(feature = "compress")]
|
#[cfg(feature = "compress")]
|
||||||
impl Future for SendClientRequest {
|
impl Future for SendClientRequest {
|
||||||
type Output =
|
type Output = Result<ClientResponse<Decoder<Payload<PayloadStream>>>, SendRequestError>;
|
||||||
Result<ClientResponse<Decoder<Payload<PayloadStream>>>, SendRequestError>;
|
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
let this = self.get_mut();
|
let this = self.get_mut();
|
||||||
|
@ -95,15 +94,9 @@ impl Future for SendClientRequest {
|
||||||
let res = ready!(send.as_mut().poll(cx)).map(|res| {
|
let res = ready!(send.as_mut().poll(cx)).map(|res| {
|
||||||
res._timeout(delay.take()).map_body(|head, payload| {
|
res._timeout(delay.take()).map_body(|head, payload| {
|
||||||
if *response_decompress {
|
if *response_decompress {
|
||||||
Payload::Stream(Decoder::from_headers(
|
Payload::Stream(Decoder::from_headers(payload, &head.headers))
|
||||||
payload,
|
|
||||||
&head.headers,
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
Payload::Stream(Decoder::new(
|
Payload::Stream(Decoder::new(payload, ContentEncoding::Identity))
|
||||||
payload,
|
|
||||||
ContentEncoding::Identity,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -187,11 +180,11 @@ impl RequestSender {
|
||||||
B: Into<Body>,
|
B: Into<Body>,
|
||||||
{
|
{
|
||||||
let fut = match self {
|
let fut = match self {
|
||||||
RequestSender::Owned(head) => config.connector.send_request(
|
RequestSender::Owned(head) => {
|
||||||
RequestHeadType::Owned(head),
|
config
|
||||||
body.into(),
|
.connector
|
||||||
addr,
|
.send_request(RequestHeadType::Owned(head), body.into(), addr)
|
||||||
),
|
}
|
||||||
RequestSender::Rc(head, extra_headers) => config.connector.send_request(
|
RequestSender::Rc(head, extra_headers) => config.connector.send_request(
|
||||||
RequestHeadType::Rc(head, extra_headers),
|
RequestHeadType::Rc(head, extra_headers),
|
||||||
body.into(),
|
body.into(),
|
||||||
|
@ -215,8 +208,7 @@ impl RequestSender {
|
||||||
Err(e) => return Error::from(e).into(),
|
Err(e) => return Error::from(e).into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json")
|
if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") {
|
||||||
{
|
|
||||||
return e.into();
|
return e.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,10 +235,9 @@ impl RequestSender {
|
||||||
};
|
};
|
||||||
|
|
||||||
// set content-type
|
// set content-type
|
||||||
if let Err(e) = self.set_header_if_none(
|
if let Err(e) =
|
||||||
header::CONTENT_TYPE,
|
self.set_header_if_none(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||||
"application/x-www-form-urlencoded",
|
{
|
||||||
) {
|
|
||||||
return e.into();
|
return e.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,11 +281,7 @@ impl RequestSender {
|
||||||
self.send_body(addr, response_decompress, timeout, config, Body::Empty)
|
self.send_body(addr, response_decompress, timeout, config, Body::Empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_header_if_none<V>(
|
fn set_header_if_none<V>(&mut self, key: HeaderName, value: V) -> Result<(), HttpError>
|
||||||
&mut self,
|
|
||||||
key: HeaderName,
|
|
||||||
value: V,
|
|
||||||
) -> Result<(), HttpError>
|
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
{
|
{
|
||||||
|
@ -302,7 +289,9 @@ impl RequestSender {
|
||||||
RequestSender::Owned(head) => {
|
RequestSender::Owned(head) => {
|
||||||
if !head.headers.contains_key(&key) {
|
if !head.headers.contains_key(&key) {
|
||||||
match value.try_into_value() {
|
match value.try_into_value() {
|
||||||
Ok(value) => head.headers.insert(key, value),
|
Ok(value) => {
|
||||||
|
head.headers.insert(key, value);
|
||||||
|
}
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,16 +40,12 @@ pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message};
|
||||||
|
|
||||||
use crate::connect::BoxedSocket;
|
use crate::connect::BoxedSocket;
|
||||||
use crate::error::{InvalidUrl, SendRequestError, WsClientError};
|
use crate::error::{InvalidUrl, SendRequestError, WsClientError};
|
||||||
use crate::http::header::{
|
use crate::http::header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION};
|
||||||
self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION,
|
use crate::http::{ConnectionType, Error as HttpError, Method, StatusCode, Uri, Version};
|
||||||
};
|
|
||||||
use crate::http::{
|
|
||||||
ConnectionType, Error as HttpError, Method, StatusCode, Uri, Version,
|
|
||||||
};
|
|
||||||
use crate::response::ClientResponse;
|
use crate::response::ClientResponse;
|
||||||
use crate::ClientConfig;
|
use crate::ClientConfig;
|
||||||
|
|
||||||
/// `WebSocket` connection
|
/// WebSocket connection.
|
||||||
pub struct WebsocketsRequest {
|
pub struct WebsocketsRequest {
|
||||||
pub(crate) head: RequestHead,
|
pub(crate) head: RequestHead,
|
||||||
err: Option<HttpError>,
|
err: Option<HttpError>,
|
||||||
|
@ -63,7 +59,7 @@ pub struct WebsocketsRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebsocketsRequest {
|
impl WebsocketsRequest {
|
||||||
/// Create new websocket connection
|
/// Create new WebSocket connection
|
||||||
pub(crate) fn new<U>(uri: U, config: Rc<ClientConfig>) -> Self
|
pub(crate) fn new<U>(uri: U, config: Rc<ClientConfig>) -> Self
|
||||||
where
|
where
|
||||||
Uri: TryFrom<U>,
|
Uri: TryFrom<U>,
|
||||||
|
@ -106,7 +102,7 @@ impl WebsocketsRequest {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set supported websocket protocols
|
/// Set supported WebSocket protocols
|
||||||
pub fn protocols<U, V>(mut self, protos: U) -> Self
|
pub fn protocols<U, V>(mut self, protos: U) -> Self
|
||||||
where
|
where
|
||||||
U: IntoIterator<Item = V>,
|
U: IntoIterator<Item = V>,
|
||||||
|
@ -243,7 +239,7 @@ impl WebsocketsRequest {
|
||||||
self.header(AUTHORIZATION, format!("Bearer {}", token))
|
self.header(AUTHORIZATION, format!("Bearer {}", token))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Complete request construction and connect to a websockets server.
|
/// Complete request construction and connect to a WebSocket server.
|
||||||
pub async fn connect(
|
pub async fn connect(
|
||||||
mut self,
|
mut self,
|
||||||
) -> Result<(ClientResponse, Framed<BoxedSocket, Codec>), WsClientError> {
|
) -> Result<(ClientResponse, Framed<BoxedSocket, Codec>), WsClientError> {
|
||||||
|
@ -342,7 +338,7 @@ impl WebsocketsRequest {
|
||||||
return Err(WsClientError::InvalidResponseStatus(head.status));
|
return Err(WsClientError::InvalidResponseStatus(head.status));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for "UPGRADE" to websocket header
|
// check for "UPGRADE" to WebSocket header
|
||||||
let has_hdr = if let Some(hdr) = head.headers.get(&header::UPGRADE) {
|
let has_hdr = if let Some(hdr) = head.headers.get(&header::UPGRADE) {
|
||||||
if let Ok(s) = hdr.to_str() {
|
if let Ok(s) = hdr.to_str() {
|
||||||
s.to_ascii_lowercase().contains("websocket")
|
s.to_ascii_lowercase().contains("websocket")
|
||||||
|
|
|
@ -51,8 +51,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_simple() {
|
async fn test_simple() {
|
||||||
let srv = test::start(|| {
|
let srv = test::start(|| {
|
||||||
App::new()
|
App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
|
||||||
.service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let request = srv.get("/").insert_header(("x-test", "111")).send();
|
let request = srv.get("/").insert_header(("x-test", "111")).send();
|
||||||
|
@ -685,9 +684,8 @@ async fn test_client_streaming_explicit() {
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
|
||||||
let body = stream::once(async {
|
let body =
|
||||||
Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes()))
|
stream::once(async { Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) });
|
||||||
});
|
|
||||||
let req = srv.post("/").send_stream(Box::pin(body));
|
let req = srv.post("/").send_stream(Box::pin(body));
|
||||||
|
|
||||||
let mut res = req.await.unwrap();
|
let mut res = req.await.unwrap();
|
||||||
|
|
|
@ -64,8 +64,7 @@ async fn test_connection_reuse_h2() {
|
||||||
.and_then(
|
.and_then(
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(map_config(
|
.h2(map_config(
|
||||||
App::new()
|
App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))),
|
||||||
.service(web::resource("/").route(web::to(HttpResponse::Ok))),
|
|
||||||
|_| AppConfig::default(),
|
|_| AppConfig::default(),
|
||||||
))
|
))
|
||||||
.rustls(tls_config())
|
.rustls(tls_config())
|
||||||
|
|
|
@ -48,8 +48,7 @@ async fn test_connection_reuse_h2() {
|
||||||
.and_then(
|
.and_then(
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(map_config(
|
.h2(map_config(
|
||||||
App::new()
|
App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))),
|
||||||
.service(web::resource("/").route(web::to(HttpResponse::Ok))),
|
|
||||||
|_| AppConfig::default(),
|
|_| AppConfig::default(),
|
||||||
))
|
))
|
||||||
.openssl(ssl_acceptor())
|
.openssl(ssl_acceptor())
|
||||||
|
|
|
@ -31,7 +31,7 @@ async fn test_simple() {
|
||||||
.send(h1::Message::Item((res.drop_body(), BodySize::None)))
|
.send(h1::Message::Item((res.drop_body(), BodySize::None)))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// start websocket service
|
// start WebSocket service
|
||||||
let framed = framed.replace_codec(ws::Codec::new());
|
let framed = framed.replace_codec(ws::Codec::new());
|
||||||
ws::Dispatcher::with(framed, ws_service).await
|
ws::Dispatcher::with(framed, ws_service).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,8 @@ fn bench_async_burst(c: &mut Criterion) {
|
||||||
|
|
||||||
let srv = rt.block_on(async {
|
let srv = rt.block_on(async {
|
||||||
test::start(|| {
|
test::start(|| {
|
||||||
App::new().service(
|
App::new()
|
||||||
web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))),
|
.service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
|
||||||
)
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -43,24 +42,25 @@ fn bench_async_burst(c: &mut Criterion) {
|
||||||
|
|
||||||
c.bench_function("get_body_async_burst", move |b| {
|
c.bench_function("get_body_async_burst", move |b| {
|
||||||
b.iter_custom(|iters| {
|
b.iter_custom(|iters| {
|
||||||
let client =
|
rt.block_on(async {
|
||||||
rt.block_on(async { Client::new().get(url.clone()).freeze().unwrap() });
|
let client = Client::new().get(url.clone()).freeze().unwrap();
|
||||||
|
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
// benchmark body
|
||||||
|
|
||||||
let start = std::time::Instant::now();
|
|
||||||
// benchmark body
|
|
||||||
let resps = rt.block_on(async move {
|
|
||||||
let burst = (0..iters).map(|_| client.send());
|
let burst = (0..iters).map(|_| client.send());
|
||||||
join_all(burst).await
|
let resps = join_all(burst).await;
|
||||||
});
|
|
||||||
let elapsed = start.elapsed();
|
|
||||||
|
|
||||||
// if there are failed requests that might be an issue
|
let elapsed = start.elapsed();
|
||||||
let failed = resps.iter().filter(|r| r.is_err()).count();
|
|
||||||
if failed > 0 {
|
|
||||||
eprintln!("failed {} requests (might be bench timeout)", failed);
|
|
||||||
};
|
|
||||||
|
|
||||||
elapsed
|
// if there are failed requests that might be an issue
|
||||||
|
let failed = resps.iter().filter(|r| r.is_err()).count();
|
||||||
|
if failed > 0 {
|
||||||
|
eprintln!("failed {} requests (might be bench timeout)", failed);
|
||||||
|
};
|
||||||
|
|
||||||
|
elapsed
|
||||||
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
digraph {
|
digraph {
|
||||||
subgraph cluster_web {
|
subgraph cluster_web {
|
||||||
label="actix/actix-web"
|
label="actix/actix-web"
|
||||||
|
|
||||||
"awc"
|
"awc"
|
||||||
"actix-web"
|
"actix-web"
|
||||||
"actix-files"
|
"actix-files"
|
||||||
|
@ -16,7 +17,7 @@ digraph {
|
||||||
"actix-web-actors" -> { "actix" "actix-web" "actix-http" "actix-codec" }
|
"actix-web-actors" -> { "actix" "actix-web" "actix-http" "actix-codec" }
|
||||||
"actix-multipart" -> { "actix-web" "actix-service" "actix-utils" }
|
"actix-multipart" -> { "actix-web" "actix-service" "actix-utils" }
|
||||||
"actix-http" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "threadpool" }
|
"actix-http" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "threadpool" }
|
||||||
"actix-http" -> { "actix" "actix-tls" }[color=blue] // optional
|
"actix-http" -> { "actix-tls" }[color=blue] // optional
|
||||||
"actix-files" -> { "actix-web" }
|
"actix-files" -> { "actix-web" }
|
||||||
"actix-http-test" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "actix-server" "awc" }
|
"actix-http-test" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "actix-server" "awc" }
|
||||||
|
|
||||||
|
@ -27,4 +28,8 @@ digraph {
|
||||||
"actix-tls" -> { "actix-service" "actix-codec" "actix-utils" }
|
"actix-tls" -> { "actix-service" "actix-codec" "actix-utils" }
|
||||||
"actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" }
|
"actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" }
|
||||||
"actix-rt" -> { "macros" "threadpool" }
|
"actix-rt" -> { "macros" "threadpool" }
|
||||||
|
|
||||||
|
// actix
|
||||||
|
|
||||||
|
"actix" -> { "actix-rt" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ digraph {
|
||||||
"awc" -> { "actix-http" }
|
"awc" -> { "actix-http" }
|
||||||
"actix-web-actors" -> { "actix" "actix-web" "actix-http" }
|
"actix-web-actors" -> { "actix" "actix-web" "actix-http" }
|
||||||
"actix-multipart" -> { "actix-web" }
|
"actix-multipart" -> { "actix-web" }
|
||||||
"actix-http" -> { "actix" }[color=blue] // optional
|
|
||||||
"actix-files" -> { "actix-web" }
|
"actix-files" -> { "actix-web" }
|
||||||
"actix-http-test" -> { "awc" }
|
"actix-http-test" -> { "awc" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,8 @@ async fn main() -> std::io::Result<()> {
|
||||||
.service(no_params)
|
.service(no_params)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/resource2/index.html")
|
web::resource("/resource2/index.html")
|
||||||
.wrap(
|
.wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"))
|
||||||
middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"),
|
.default_service(web::route().to(|| HttpResponse::MethodNotAllowed()))
|
||||||
)
|
|
||||||
.default_service(
|
|
||||||
web::route().to(|| HttpResponse::MethodNotAllowed()),
|
|
||||||
)
|
|
||||||
.route(web::get().to(index_async)),
|
.route(web::get().to(index_async)),
|
||||||
)
|
)
|
||||||
.service(web::resource("/test1.html").to(|| async { "Test\r\n" }))
|
.service(web::resource("/test1.html").to(|| async { "Test\r\n" }))
|
||||||
|
|
|
@ -33,12 +33,8 @@ async fn main() -> std::io::Result<()> {
|
||||||
.service(no_params)
|
.service(no_params)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/resource2/index.html")
|
web::resource("/resource2/index.html")
|
||||||
.wrap(
|
.wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"))
|
||||||
middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"),
|
.default_service(web::route().to(|| HttpResponse::MethodNotAllowed()))
|
||||||
)
|
|
||||||
.default_service(
|
|
||||||
web::route().to(|| HttpResponse::MethodNotAllowed()),
|
|
||||||
)
|
|
||||||
.route(web::get().to(index_async)),
|
.route(web::get().to(index_async)),
|
||||||
)
|
)
|
||||||
.service(web::resource("/test1.html").to(|| async { "Test\r\n" }))
|
.service(web::resource("/test1.html").to(|| async { "Test\r\n" }))
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
max_width = 89
|
max_width = 96
|
||||||
reorder_imports = true
|
reorder_imports = true
|
||||||
|
|
77
src/app.rs
77
src/app.rs
|
@ -8,8 +8,7 @@ use actix_http::body::{Body, MessageBody};
|
||||||
use actix_http::{Extensions, Request};
|
use actix_http::{Extensions, Request};
|
||||||
use actix_service::boxed::{self, BoxServiceFactory};
|
use actix_service::boxed::{self, BoxServiceFactory};
|
||||||
use actix_service::{
|
use actix_service::{
|
||||||
apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt,
|
apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform,
|
||||||
Transform,
|
|
||||||
};
|
};
|
||||||
use futures_util::future::FutureExt;
|
use futures_util::future::FutureExt;
|
||||||
|
|
||||||
|
@ -72,7 +71,7 @@ where
|
||||||
/// Set application data. Application data could be accessed
|
/// Set application data. Application data could be accessed
|
||||||
/// by using `Data<T>` extractor where `T` is data type.
|
/// by using `Data<T>` extractor where `T` is data type.
|
||||||
///
|
///
|
||||||
/// **Note**: http server accepts an application factory rather than
|
/// **Note**: HTTP server accepts an application factory rather than
|
||||||
/// an application instance. Http server constructs an application
|
/// an application instance. Http server constructs an application
|
||||||
/// instance for each thread, thus application data must be constructed
|
/// instance for each thread, thus application data must be constructed
|
||||||
/// multiple times. If you want to share data between different
|
/// multiple times. If you want to share data between different
|
||||||
|
@ -207,11 +206,11 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register http service.
|
/// Register HTTP service.
|
||||||
///
|
///
|
||||||
/// Http service is any type that implements `HttpServiceFactory` trait.
|
/// Http service is any type that implements `HttpServiceFactory` trait.
|
||||||
///
|
///
|
||||||
/// Actix web provides several services implementations:
|
/// Actix Web provides several services implementations:
|
||||||
///
|
///
|
||||||
/// * *Resource* is an entry in resource table which corresponds to requested URL.
|
/// * *Resource* is an entry in resource table which corresponds to requested URL.
|
||||||
/// * *Scope* is a set of resources with common root path.
|
/// * *Scope* is a set of resources with common root path.
|
||||||
|
@ -473,17 +472,13 @@ mod tests {
|
||||||
use crate::http::{header, HeaderValue, Method, StatusCode};
|
use crate::http::{header, HeaderValue, Method, StatusCode};
|
||||||
use crate::middleware::DefaultHeaders;
|
use crate::middleware::DefaultHeaders;
|
||||||
use crate::service::ServiceRequest;
|
use crate::service::ServiceRequest;
|
||||||
use crate::test::{
|
use crate::test::{call_service, init_service, read_body, try_init_service, TestRequest};
|
||||||
call_service, init_service, read_body, try_init_service, TestRequest,
|
|
||||||
};
|
|
||||||
use crate::{web, HttpRequest, HttpResponse};
|
use crate::{web, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_default_resource() {
|
async fn test_default_resource() {
|
||||||
let srv = init_service(
|
let srv =
|
||||||
App::new().service(web::resource("/test").to(HttpResponse::Ok)),
|
init_service(App::new().service(web::resource("/test").to(HttpResponse::Ok))).await;
|
||||||
)
|
|
||||||
.await;
|
|
||||||
let req = TestRequest::with_uri("/test").to_request();
|
let req = TestRequest::with_uri("/test").to_request();
|
||||||
let resp = srv.call(req).await.unwrap();
|
let resp = srv.call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
@ -525,20 +520,22 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_data_factory() {
|
async fn test_data_factory() {
|
||||||
let srv =
|
let srv = init_service(
|
||||||
init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service(
|
App::new()
|
||||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
.data_factory(|| ok::<_, ()>(10usize))
|
||||||
))
|
.service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok())),
|
||||||
.await;
|
)
|
||||||
|
.await;
|
||||||
let req = TestRequest::default().to_request();
|
let req = TestRequest::default().to_request();
|
||||||
let resp = srv.call(req).await.unwrap();
|
let resp = srv.call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
let srv =
|
let srv = init_service(
|
||||||
init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service(
|
App::new()
|
||||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
.data_factory(|| ok::<_, ()>(10u32))
|
||||||
))
|
.service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok())),
|
||||||
.await;
|
)
|
||||||
|
.await;
|
||||||
let req = TestRequest::default().to_request();
|
let req = TestRequest::default().to_request();
|
||||||
let resp = srv.call(req).await.unwrap();
|
let resp = srv.call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
@ -546,23 +543,24 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_data_factory_errors() {
|
async fn test_data_factory_errors() {
|
||||||
let srv =
|
let srv = try_init_service(
|
||||||
try_init_service(App::new().data_factory(|| err::<u32, _>(())).service(
|
App::new()
|
||||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
.data_factory(|| err::<u32, _>(()))
|
||||||
))
|
.service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok())),
|
||||||
.await;
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
assert!(srv.is_err());
|
assert!(srv.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_extension() {
|
async fn test_extension() {
|
||||||
let srv = init_service(App::new().app_data(10usize).service(
|
let srv = init_service(App::new().app_data(10usize).service(web::resource("/").to(
|
||||||
web::resource("/").to(|req: HttpRequest| {
|
|req: HttpRequest| {
|
||||||
assert_eq!(*req.app_data::<usize>().unwrap(), 10);
|
assert_eq!(*req.app_data::<usize>().unwrap(), 10);
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
}),
|
},
|
||||||
))
|
)))
|
||||||
.await;
|
.await;
|
||||||
let req = TestRequest::default().to_request();
|
let req = TestRequest::default().to_request();
|
||||||
let resp = srv.call(req).await.unwrap();
|
let resp = srv.call(req).await.unwrap();
|
||||||
|
@ -617,10 +615,8 @@ mod tests {
|
||||||
let fut = srv.call(req);
|
let fut = srv.call(req);
|
||||||
async move {
|
async move {
|
||||||
let mut res = fut.await?;
|
let mut res = fut.await?;
|
||||||
res.headers_mut().insert(
|
res.headers_mut()
|
||||||
header::CONTENT_TYPE,
|
.insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
|
||||||
HeaderValue::from_static("0001"),
|
|
||||||
);
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -645,10 +641,8 @@ mod tests {
|
||||||
let fut = srv.call(req);
|
let fut = srv.call(req);
|
||||||
async {
|
async {
|
||||||
let mut res = fut.await?;
|
let mut res = fut.await?;
|
||||||
res.headers_mut().insert(
|
res.headers_mut()
|
||||||
header::CONTENT_TYPE,
|
.insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
|
||||||
HeaderValue::from_static("0001"),
|
|
||||||
);
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -671,9 +665,8 @@ mod tests {
|
||||||
.route(
|
.route(
|
||||||
"/test",
|
"/test",
|
||||||
web::get().to(|req: HttpRequest| {
|
web::get().to(|req: HttpRequest| {
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok()
|
||||||
req.url_for("youtube", &["12345"]).unwrap().to_string(),
|
.body(req.url_for("youtube", &["12345"]).unwrap().to_string())
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue