mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-04 09:56:22 +02:00
Compare commits
5 Commits
files-v0.6
...
http-v2.2.
Author | SHA1 | Date | |
---|---|---|---|
bc54cdd772 | |||
ad7e3c06e7 | |||
0669ed0f06 | |||
c9c36679e4 | |||
655d7b4f05 |
18
.github/workflows/linux.yml
vendored
18
.github/workflows/linux.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
version:
|
version:
|
||||||
- 1.46.0 # MSRV
|
- 1.42.0 # MSRV
|
||||||
- stable
|
- stable
|
||||||
- nightly
|
- nightly
|
||||||
|
|
||||||
@ -30,13 +30,6 @@ jobs:
|
|||||||
profile: minimal
|
profile: minimal
|
||||||
override: true
|
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
|
- name: check build
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
@ -65,17 +58,12 @@ 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.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||||
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.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v1
|
||||||
with:
|
with:
|
||||||
file: cobertura.xml
|
file: cobertura.xml
|
||||||
|
|
||||||
- name: Clear the cargo caches
|
|
||||||
run: |
|
|
||||||
cargo install cargo-cache --no-default-features --features ci-autoclean
|
|
||||||
cargo-cache
|
|
||||||
|
12
.github/workflows/macos.yml
vendored
12
.github/workflows/macos.yml
vendored
@ -29,13 +29,6 @@ jobs:
|
|||||||
profile: minimal
|
profile: minimal
|
||||||
override: true
|
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
|
- name: check build
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
@ -49,8 +42,3 @@ jobs:
|
|||||||
args: --all --all-features --no-fail-fast -- --nocapture
|
args: --all --all-features --no-fail-fast -- --nocapture
|
||||||
--skip=test_h2_content_length
|
--skip=test_h2_content_length
|
||||||
--skip=test_reading_deflate_encoding_large_random_rustls
|
--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
|
|
||||||
|
12
.github/workflows/windows.yml
vendored
12
.github/workflows/windows.yml
vendored
@ -41,13 +41,6 @@ jobs:
|
|||||||
Get-ChildItem C:\vcpkg\installed\x64-windows\bin
|
Get-ChildItem C:\vcpkg\installed\x64-windows\bin
|
||||||
Get-ChildItem C:\vcpkg\installed\x64-windows\lib
|
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
|
- name: check build
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
@ -69,8 +62,3 @@ jobs:
|
|||||||
--skip=test_connection_force_close
|
--skip=test_connection_force_close
|
||||||
--skip=test_connection_server_close
|
--skip=test_connection_server_close
|
||||||
--skip=test_connection_wait_queue_force_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
|
|
||||||
|
30
CHANGES.md
30
CHANGES.md
@ -1,35 +1,13 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.1 - 2021-01-07
|
## 3.3.3 - 2021-12-18
|
||||||
### Added
|
|
||||||
* `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and
|
|
||||||
`Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865]
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
|
* Soft-deprecate `NormalizePath::default()`, noting upcoming behavior change in v4. [#2529]
|
||||||
* Bumped `rand` to `0.8`.
|
|
||||||
* Update `rust-tls` to `0.19`. [#1813]
|
|
||||||
* Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852]
|
|
||||||
* The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration
|
|
||||||
guide for implications. [#1875]
|
|
||||||
* Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875]
|
|
||||||
* MSRV is now 1.46.0.
|
|
||||||
|
|
||||||
### Fixed
|
[#2529]: https://github.com/actix/actix-web/pull/2529
|
||||||
* Added the underlying parse error to `test::read_body_json`'s panic message. [#1812]
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
* Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now
|
|
||||||
exposed directly by the `middleware` module.
|
|
||||||
|
|
||||||
[#1812]: https://github.com/actix/actix-web/pull/1812
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
|
||||||
[#1852]: https://github.com/actix/actix-web/pull/1852
|
|
||||||
[#1865]: https://github.com/actix/actix-web/pull/1865
|
|
||||||
[#1875]: https://github.com/actix/actix-web/pull/1875
|
|
||||||
|
|
||||||
|
|
||||||
## 3.3.2 - 2020-12-01
|
## 3.3.2 - 2020-12-01
|
||||||
|
53
Cargo.toml
53
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "4.0.0-beta.1"
|
version = "3.3.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"
|
||||||
@ -34,7 +34,7 @@ members = [
|
|||||||
"actix-multipart",
|
"actix-multipart",
|
||||||
"actix-web-actors",
|
"actix-web-actors",
|
||||||
"actix-web-codegen",
|
"actix-web-codegen",
|
||||||
"actix-http-test",
|
"test-server",
|
||||||
]
|
]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@ -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 = ["actix-tls/accept", "actix-tls/openssl", "awc/openssl", "open-ssl"]
|
openssl = ["actix-tls/openssl", "awc/openssl", "open-ssl"]
|
||||||
|
|
||||||
# rustls
|
# rustls
|
||||||
rustls = ["actix-tls/accept", "actix-tls/rustls", "awc/rustls", "rust-tls"]
|
rustls = ["actix-tls/rustls", "awc/rustls", "rust-tls"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "basic"
|
name = "basic"
|
||||||
@ -73,26 +73,28 @@ name = "client"
|
|||||||
required-features = ["rustls"]
|
required-features = ["rustls"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.3.0"
|
||||||
actix-macros = "0.1.0"
|
actix-service = "1.0.6"
|
||||||
|
actix-utils = "2.0.0"
|
||||||
actix-router = "0.2.4"
|
actix-router = "0.2.4"
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "1.1.1"
|
||||||
actix-server = "2.0.0-beta.2"
|
actix-server = "1.0.0"
|
||||||
actix-service = "2.0.0-beta.2"
|
actix-testing = "1.0.0"
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-macros = "0.1.0"
|
||||||
actix-threadpool = "0.3.1"
|
actix-threadpool = "0.3.1"
|
||||||
actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true }
|
actix-tls = "2.0.0"
|
||||||
|
|
||||||
actix-web-codegen = "0.4.0"
|
actix-web-codegen = "0.4.0"
|
||||||
actix-http = "3.0.0-beta.1"
|
actix-http = "2.2.0"
|
||||||
awc = { version = "3.0.0-beta.1", default-features = false }
|
awc = { version = "2.0.3", default-features = false }
|
||||||
|
|
||||||
ahash = "0.6"
|
bytes = "0.5.3"
|
||||||
bytes = "1"
|
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-channel = { version = "0.3.5", default-features = false }
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.5", default-features = false }
|
||||||
|
futures-util = { version = "0.3.5", default-features = false }
|
||||||
|
fxhash = "0.2.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
socket2 = "0.3.16"
|
socket2 = "0.3.16"
|
||||||
@ -104,16 +106,16 @@ serde_urlencoded = "0.7"
|
|||||||
time = { version = "0.2.7", default-features = false, features = ["std"] }
|
time = { version = "0.2.7", default-features = false, features = ["std"] }
|
||||||
url = "2.1"
|
url = "2.1"
|
||||||
open-ssl = { package = "openssl", version = "0.10", optional = true }
|
open-ssl = { package = "openssl", version = "0.10", optional = true }
|
||||||
rust-tls = { package = "rustls", version = "0.19.0", optional = true }
|
rust-tls = { package = "rustls", version = "0.18.0", optional = true }
|
||||||
smallvec = "1.6"
|
tinyvec = { version = "1", features = ["alloc"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix = "0.11.0-beta.1"
|
actix = "0.10.0"
|
||||||
actix-http = { version = "3.0.0-beta.1", features = ["actors"] }
|
actix-http = { version = "2.1.0", features = ["actors"] }
|
||||||
rand = "0.8"
|
rand = "0.7"
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
brotli2 = "0.3.2"
|
brotli = "3.3.3"
|
||||||
flate2 = "1.0.13"
|
flate2 = "1.0.13"
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
|
|
||||||
@ -125,11 +127,10 @@ codegen-units = 1
|
|||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
actix-web = { path = "." }
|
actix-web = { path = "." }
|
||||||
actix-http = { path = "actix-http" }
|
actix-http = { path = "actix-http" }
|
||||||
actix-http-test = { path = "actix-http-test" }
|
actix-http-test = { path = "test-server" }
|
||||||
actix-web-actors = { path = "actix-web-actors" }
|
|
||||||
actix-web-codegen = { path = "actix-web-codegen" }
|
actix-web-codegen = { path = "actix-web-codegen" }
|
||||||
actix-multipart = { path = "actix-multipart" }
|
|
||||||
actix-files = { path = "actix-files" }
|
actix-files = { path = "actix-files" }
|
||||||
|
actix-multipart = { path = "actix-multipart" }
|
||||||
awc = { path = "awc" }
|
awc = { path = "awc" }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
|
10
MIGRATION.md
10
MIGRATION.md
@ -1,15 +1,5 @@
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
* The default `NormalizePath` behavior now strips trailing slashes by default. This was
|
|
||||||
previously documented to be the case in v3 but the behavior now matches. The effect is that
|
|
||||||
routes defined with trailing slashes will become inaccessible when
|
|
||||||
using `NormalizePath::default()`.
|
|
||||||
|
|
||||||
Before: `#[get("/test/")`
|
|
||||||
After: `#[get("/test")`
|
|
||||||
|
|
||||||
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
|
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0
|
## 3.0.0
|
||||||
|
|
||||||
|
12
README.md
12
README.md
@ -6,10 +6,10 @@
|
|||||||
<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/3.3.3)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-web/3.3.2)
|
[](https://deps.rs/crate/actix-web/3.3.3)
|
||||||
<br />
|
<br />
|
||||||
[](https://travis-ci.org/actix/actix-web)
|
[](https://travis-ci.org/actix/actix-web)
|
||||||
[](https://codecov.io/gh/actix/actix-web)
|
[](https://codecov.io/gh/actix/actix-web)
|
||||||
@ -34,7 +34,7 @@
|
|||||||
* 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)
|
* Supports [Actix actor framework](https://github.com/actix/actix)
|
||||||
* Runs on stable Rust 1.46+
|
* Runs on stable Rust 1.42+
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@ -107,5 +107,5 @@ at your option.
|
|||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
Contribution to the actix-web repo is organized under the terms of the Contributor Covenant.
|
Contribution to the actix-web crate is organized under the terms of the Contributor Covenant, the
|
||||||
The Actix team promises to intervene to uphold that code of conduct.
|
maintainers of Actix web, promises to intervene to uphold that code of conduct.
|
||||||
|
@ -1,19 +1,6 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.1 - 2021-01-07
|
|
||||||
* `HttpRange::parse` now has its own error type.
|
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
|
||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
|
||||||
|
|
||||||
|
|
||||||
## 0.5.0 - 2020-12-26
|
|
||||||
* Optionally support hidden files/directories. [#1811]
|
|
||||||
|
|
||||||
[#1811]: https://github.com/actix/actix-web/pull/1811
|
|
||||||
|
|
||||||
|
|
||||||
## 0.4.1 - 2020-11-24
|
## 0.4.1 - 2020-11-24
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.6.0-beta.1"
|
version = "0.4.1"
|
||||||
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,19 +17,19 @@ 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 = "3.0.0", default-features = false }
|
||||||
actix-service = "2.0.0-beta.2"
|
actix-service = "1.0.6"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
bytes = "1"
|
bytes = "0.5.3"
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
mime_guess = "2.0.1"
|
mime_guess = "2.0.1"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
v_htmlescape = "0.12"
|
v_htmlescape = "0.11"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "1.0.0"
|
||||||
actix-web = "4.0.0-beta.1"
|
actix-web = { version = "3.0.0", features = ["openssl"] }
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
> Static file serving for Actix Web
|
> Static file serving for Actix Web
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://docs.rs/actix-files/0.5.0)
|
[](https://docs.rs/actix-files/0.4.1)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-files/0.5.0)
|
[](https://deps.rs/crate/actix-files/0.4.1)
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](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)
|
||||||
|
|
||||||
@ -16,4 +16,4 @@
|
|||||||
- [API Documentation](https://docs.rs/actix-files/)
|
- [API Documentation](https://docs.rs/actix-files/)
|
||||||
- [Example Project](https://github.com/actix/examples/tree/master/static_index)
|
- [Example Project](https://github.com/actix/examples/tree/master/static_index)
|
||||||
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
- Minimum supported Rust version: 1.46 or later
|
- Minimum supported Rust version: 1.42 or later
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc};
|
use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc};
|
||||||
|
|
||||||
use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt};
|
use actix_service::{boxed, IntoServiceFactory, ServiceFactory};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{
|
dev::{
|
||||||
AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse,
|
AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse,
|
||||||
@ -39,7 +39,6 @@ pub struct Files {
|
|||||||
mime_override: Option<Rc<MimeOverride>>,
|
mime_override: Option<Rc<MimeOverride>>,
|
||||||
file_flags: named::Flags,
|
file_flags: named::Flags,
|
||||||
guards: Option<Rc<dyn Guard>>,
|
guards: Option<Rc<dyn Guard>>,
|
||||||
hidden_files: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Files {
|
impl fmt::Debug for Files {
|
||||||
@ -61,7 +60,6 @@ impl Clone for Files {
|
|||||||
path: self.path.clone(),
|
path: self.path.clone(),
|
||||||
mime_override: self.mime_override.clone(),
|
mime_override: self.mime_override.clone(),
|
||||||
guards: self.guards.clone(),
|
guards: self.guards.clone(),
|
||||||
hidden_files: self.hidden_files,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,7 +103,6 @@ impl Files {
|
|||||||
mime_override: None,
|
mime_override: None,
|
||||||
file_flags: named::Flags::default(),
|
file_flags: named::Flags::default(),
|
||||||
guards: None,
|
guards: None,
|
||||||
hidden_files: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,10 +198,10 @@ impl Files {
|
|||||||
/// Sets default handler which is used when no matched file could be found.
|
/// Sets default handler which is used when no matched file could be found.
|
||||||
pub fn default_handler<F, U>(mut self, f: F) -> Self
|
pub fn default_handler<F, U>(mut self, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: IntoServiceFactory<U, ServiceRequest>,
|
F: IntoServiceFactory<U>,
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<
|
||||||
ServiceRequest,
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = ServiceRequest,
|
||||||
Response = ServiceResponse,
|
Response = ServiceResponse,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
> + 'static,
|
> + 'static,
|
||||||
@ -216,13 +213,6 @@ impl Files {
|
|||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enables serving hidden files and directories, allowing a leading dots in url fragments.
|
|
||||||
#[inline]
|
|
||||||
pub fn use_hidden_files(mut self) -> Self {
|
|
||||||
self.hidden_files = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpServiceFactory for Files {
|
impl HttpServiceFactory for Files {
|
||||||
@ -241,7 +231,8 @@ impl HttpServiceFactory for Files {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceFactory<ServiceRequest> for Files {
|
impl ServiceFactory for Files {
|
||||||
|
type Request = ServiceRequest;
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Config = ();
|
type Config = ();
|
||||||
@ -260,7 +251,6 @@ impl ServiceFactory<ServiceRequest> for Files {
|
|||||||
mime_override: self.mime_override.clone(),
|
mime_override: self.mime_override.clone(),
|
||||||
file_flags: self.file_flags,
|
file_flags: self.file_flags,
|
||||||
guards: self.guards.clone(),
|
guards: self.guards.clone(),
|
||||||
hidden_files: self.hidden_files,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(ref default) = *self.default.borrow() {
|
if let Some(ref default) = *self.default.borrow() {
|
||||||
|
@ -15,19 +15,12 @@ impl FromStr for PathBufWrap {
|
|||||||
type Err = UriSegmentError;
|
type Err = UriSegmentError;
|
||||||
|
|
||||||
fn from_str(path: &str) -> Result<Self, Self::Err> {
|
fn from_str(path: &str) -> Result<Self, Self::Err> {
|
||||||
Self::parse_path(path, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PathBufWrap {
|
|
||||||
/// Parse a path, giving the choice of allowing hidden files to be considered valid segments.
|
|
||||||
pub fn parse_path(path: &str, hidden_files: bool) -> Result<Self, UriSegmentError> {
|
|
||||||
let mut buf = PathBuf::new();
|
let mut buf = PathBuf::new();
|
||||||
|
|
||||||
for segment in path.split('/') {
|
for segment in path.split('/') {
|
||||||
if segment == ".." {
|
if segment == ".." {
|
||||||
buf.pop();
|
buf.pop();
|
||||||
} else if !hidden_files && segment.starts_with('.') {
|
} else if segment.starts_with('.') {
|
||||||
return Err(UriSegmentError::BadStart('.'));
|
return Err(UriSegmentError::BadStart('.'));
|
||||||
} else if segment.starts_with('*') {
|
} else if segment.starts_with('*') {
|
||||||
return Err(UriSegmentError::BadStart('*'));
|
return Err(UriSegmentError::BadStart('*'));
|
||||||
@ -103,17 +96,4 @@ mod tests {
|
|||||||
PathBuf::from_iter(vec!["seg2"])
|
PathBuf::from_iter(vec!["seg2"])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_path() {
|
|
||||||
assert_eq!(
|
|
||||||
PathBufWrap::parse_path("/test/.tt", false).map(|t| t.0),
|
|
||||||
Err(UriSegmentError::BadStart('.'))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
PathBufWrap::parse_path("/test/.tt", true).unwrap().0,
|
|
||||||
PathBuf::from_iter(vec!["test", ".tt"])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use derive_more::{Display, Error};
|
|
||||||
|
|
||||||
/// HTTP Range header representation.
|
/// HTTP Range header representation.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct HttpRange {
|
pub struct HttpRange {
|
||||||
@ -13,21 +11,17 @@ pub struct HttpRange {
|
|||||||
const PREFIX: &str = "bytes=";
|
const PREFIX: &str = "bytes=";
|
||||||
const PREFIX_LEN: usize = 6;
|
const PREFIX_LEN: usize = 6;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Display, Error)]
|
|
||||||
#[display(fmt = "Parse HTTP Range failed")]
|
|
||||||
pub struct ParseRangeErr(#[error(not(source))] ());
|
|
||||||
|
|
||||||
impl HttpRange {
|
impl HttpRange {
|
||||||
/// Parses Range HTTP header string as per RFC 2616.
|
/// Parses Range HTTP header string as per RFC 2616.
|
||||||
///
|
///
|
||||||
/// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`).
|
/// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`).
|
||||||
/// `size` is full size of response (file).
|
/// `size` is full size of response (file).
|
||||||
pub fn parse(header: &str, size: u64) -> Result<Vec<HttpRange>, ParseRangeErr> {
|
pub fn parse(header: &str, size: u64) -> Result<Vec<HttpRange>, ()> {
|
||||||
if header.is_empty() {
|
if header.is_empty() {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
if !header.starts_with(PREFIX) {
|
if !header.starts_with(PREFIX) {
|
||||||
return Err(ParseRangeErr(()));
|
return Err(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let size_sig = size as i64;
|
let size_sig = size as i64;
|
||||||
@ -40,14 +34,13 @@ impl HttpRange {
|
|||||||
.map(|ra| {
|
.map(|ra| {
|
||||||
let mut start_end_iter = ra.split('-');
|
let mut start_end_iter = ra.split('-');
|
||||||
|
|
||||||
let start_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.trim();
|
let start_str = start_end_iter.next().ok_or(())?.trim();
|
||||||
let end_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.trim();
|
let end_str = start_end_iter.next().ok_or(())?.trim();
|
||||||
|
|
||||||
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(|_| ())?;
|
||||||
end_str.parse().map_err(|_| ParseRangeErr(()))?;
|
|
||||||
|
|
||||||
if length > size_sig {
|
if length > size_sig {
|
||||||
length = size_sig;
|
length = size_sig;
|
||||||
@ -58,10 +51,10 @@ impl HttpRange {
|
|||||||
length: length as u64,
|
length: length as u64,
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
let start: i64 = start_str.parse().map_err(|_| ParseRangeErr(()))?;
|
let start: i64 = start_str.parse().map_err(|_| ())?;
|
||||||
|
|
||||||
if start < 0 {
|
if start < 0 {
|
||||||
return Err(ParseRangeErr(()));
|
return Err(());
|
||||||
}
|
}
|
||||||
if start >= size_sig {
|
if start >= size_sig {
|
||||||
no_overlap = true;
|
no_overlap = true;
|
||||||
@ -72,11 +65,10 @@ 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(|_| ())?;
|
||||||
end_str.parse().map_err(|_| ParseRangeErr(()))?;
|
|
||||||
|
|
||||||
if start > end {
|
if start > end {
|
||||||
return Err(ParseRangeErr(()));
|
return Err(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if end >= size_sig {
|
if end >= size_sig {
|
||||||
@ -97,7 +89,7 @@ impl HttpRange {
|
|||||||
let ranges: Vec<HttpRange> = all_ranges.into_iter().filter_map(|x| x).collect();
|
let ranges: Vec<HttpRange> = all_ranges.into_iter().filter_map(|x| x).collect();
|
||||||
|
|
||||||
if no_overlap && ranges.is_empty() {
|
if no_overlap && ranges.is_empty() {
|
||||||
return Err(ParseRangeErr(()));
|
return Err(());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ranges)
|
Ok(ranges)
|
||||||
@ -341,7 +333,8 @@ mod tests {
|
|||||||
if expected.is_empty() {
|
if expected.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
assert!(
|
||||||
|
false,
|
||||||
"parse({}, {}) returned error {:?}",
|
"parse({}, {}) returned error {:?}",
|
||||||
header,
|
header,
|
||||||
size,
|
size,
|
||||||
@ -353,24 +346,28 @@ mod tests {
|
|||||||
let got = res.unwrap();
|
let got = res.unwrap();
|
||||||
|
|
||||||
if got.len() != expected.len() {
|
if got.len() != expected.len() {
|
||||||
panic!(
|
assert!(
|
||||||
|
false,
|
||||||
"len(parseRange({}, {})) = {}, want {}",
|
"len(parseRange({}, {})) = {}, want {}",
|
||||||
header,
|
header,
|
||||||
size,
|
size,
|
||||||
got.len(),
|
got.len(),
|
||||||
expected.len()
|
expected.len()
|
||||||
);
|
);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..expected.len() {
|
for i in 0..expected.len() {
|
||||||
if got[i].start != expected[i].start {
|
if got[i].start != expected[i].start {
|
||||||
panic!(
|
assert!(
|
||||||
|
false,
|
||||||
"parseRange({}, {})[{}].start = {}, want {}",
|
"parseRange({}, {})[{}].start = {}, want {}",
|
||||||
header, size, i, got[i].start, expected[i].start
|
header, size, i, got[i].start, expected[i].start
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if got[i].length != expected[i].length {
|
if got[i].length != expected[i].length {
|
||||||
panic!(
|
assert!(
|
||||||
|
false,
|
||||||
"parseRange({}, {})[{}].length = {}, want {}",
|
"parseRange({}, {})[{}].length = {}, want {}",
|
||||||
header, size, i, got[i].length, expected[i].length
|
header, size, i, got[i].length, expected[i].length
|
||||||
)
|
)
|
||||||
|
@ -31,7 +31,6 @@ pub struct FilesService {
|
|||||||
pub(crate) mime_override: Option<Rc<MimeOverride>>,
|
pub(crate) mime_override: Option<Rc<MimeOverride>>,
|
||||||
pub(crate) file_flags: named::Flags,
|
pub(crate) file_flags: named::Flags,
|
||||||
pub(crate) guards: Option<Rc<dyn Guard>>,
|
pub(crate) guards: Option<Rc<dyn Guard>>,
|
||||||
pub(crate) hidden_files: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilesServiceFuture = Either<
|
type FilesServiceFuture = Either<
|
||||||
@ -57,7 +56,8 @@ impl fmt::Debug for FilesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<ServiceRequest> for FilesService {
|
impl Service for FilesService {
|
||||||
|
type Request = ServiceRequest;
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = FilesServiceFuture;
|
type Future = FilesServiceFuture;
|
||||||
@ -83,11 +83,10 @@ impl Service<ServiceRequest> for FilesService {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let real_path =
|
let real_path: PathBufWrap = match req.match_info().path().parse() {
|
||||||
match PathBufWrap::parse_path(req.match_info().path(), self.hidden_files) {
|
Ok(item) => item,
|
||||||
Ok(item) => item,
|
Err(e) => return Either::Left(ok(req.error_response(e))),
|
||||||
Err(e) => return Either::Left(ok(req.error_response(e))),
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// full file path
|
// full file path
|
||||||
let path = match self.directory.join(&real_path).canonicalize() {
|
let path = match self.directory.join(&real_path).canonicalize() {
|
||||||
|
@ -1,31 +1,18 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.1 - 2021-01-07
|
## 2.2.2 - 2022-01-21
|
||||||
### Added
|
|
||||||
* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`.
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
|
- Migrate to `brotli` crate. [ad7e3c06]
|
||||||
* Bumped `rand` to `0.8`.
|
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
|
||||||
* Update `h2` to `0.3`. [#1813]
|
|
||||||
* The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864]
|
|
||||||
|
|
||||||
### Removed
|
[ad7e3c06]: https://github.com/actix/actix-web/commit/ad7e3c06
|
||||||
* Deprecated `on_connect` methods have been removed. Prefer the new
|
|
||||||
`on_connect_ext` technique. [#1857]
|
|
||||||
* Remove `ResponseError` impl for `actix::actors::resolver::ResolverError`
|
|
||||||
due to deprecate of resolver actor. [#1813]
|
|
||||||
* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`.
|
|
||||||
due to the removal of this type from `tokio-openssl` crate. openssl handshake
|
|
||||||
error would return as `ConnectError::SslError`. [#1813]
|
|
||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
|
||||||
[#1857]: https://github.com/actix/actix-web/pull/1857
|
## 2.2.1 - 2021-08-09
|
||||||
[#1864]: https://github.com/actix/actix-web/pull/1864
|
### Fixed
|
||||||
|
- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
|
||||||
|
|
||||||
|
|
||||||
## 2.2.0 - 2020-11-25
|
## 2.2.0 - 2020-11-25
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "3.0.0-beta.1"
|
version = "2.2.2"
|
||||||
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"
|
||||||
@ -25,13 +25,13 @@ path = "src/lib.rs"
|
|||||||
default = []
|
default = []
|
||||||
|
|
||||||
# openssl
|
# openssl
|
||||||
openssl = ["actix-tls/openssl"]
|
openssl = ["actix-tls/openssl", "actix-connect/openssl"]
|
||||||
|
|
||||||
# rustls support
|
# rustls support
|
||||||
rustls = ["actix-tls/rustls"]
|
rustls = ["actix-tls/rustls", "actix-connect/rustls"]
|
||||||
|
|
||||||
# enable compressison support
|
# enable compressison support
|
||||||
compress = ["flate2", "brotli2"]
|
compress = ["flate2", "brotli"]
|
||||||
|
|
||||||
# support for secure cookies
|
# support for secure cookies
|
||||||
secure-cookies = ["cookie/secure"]
|
secure-cookies = ["cookie/secure"]
|
||||||
@ -40,29 +40,29 @@ secure-cookies = ["cookie/secure"]
|
|||||||
actors = ["actix"]
|
actors = ["actix"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "2.0.0-beta.2"
|
actix-service = "1.0.6"
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.3.0"
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-connect = "2.0.0"
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-utils = "2.0.0"
|
||||||
|
actix-rt = "1.0.0"
|
||||||
actix-threadpool = "0.3.1"
|
actix-threadpool = "0.3.1"
|
||||||
actix-tls = "3.0.0-beta.2"
|
actix-tls = { version = "2.0.0", optional = true }
|
||||||
actix = { version = "0.11.0-beta.1", optional = true }
|
actix = { version = "0.10.0", optional = true }
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bitflags = "1.2"
|
bitflags = "1.2"
|
||||||
bytes = "1"
|
bytes = "0.5.3"
|
||||||
bytestring = "1"
|
|
||||||
cookie = { version = "0.14.1", features = ["percent-encode"] }
|
cookie = { version = "0.14.1", features = ["percent-encode"] }
|
||||||
copyless = "0.1.4"
|
copyless = "0.1.4"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.2"
|
||||||
either = "1.5.3"
|
either = "1.5.3"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
futures-channel = { version = "0.3.7", default-features = false }
|
futures-channel = { version = "0.3.5", default-features = false }
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.5", default-features = false }
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
futures-util = { version = "0.3.5", default-features = false }
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
h2 = "0.3.0"
|
h2 = "0.2.1"
|
||||||
http = "0.2.2"
|
http = "0.2.0"
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
indexmap = "1.3"
|
indexmap = "1.3"
|
||||||
itoa = "0.4"
|
itoa = "0.4"
|
||||||
@ -72,7 +72,7 @@ log = "0.4"
|
|||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
pin-project = "1.0.0"
|
pin-project = "1.0.0"
|
||||||
rand = "0.8"
|
rand = "0.7"
|
||||||
regex = "1.3"
|
regex = "1.3"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
@ -82,21 +82,22 @@ serde_urlencoded = "0.7"
|
|||||||
time = { version = "0.2.7", default-features = false, features = ["std"] }
|
time = { version = "0.2.7", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
# compression
|
# compression
|
||||||
brotli2 = { version="0.3.2", optional = true }
|
brotli = { version = "3.3.3", optional = true }
|
||||||
flate2 = { version = "1.0.13", optional = true }
|
flate2 = { version = "1.0.13", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-server = "2.0.0-beta.2"
|
actix-server = "1.0.1"
|
||||||
actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] }
|
actix-connect = { version = "2.0.0", features = ["openssl"] }
|
||||||
actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] }
|
actix-http-test = { version = "2.0.0", features = ["openssl"] }
|
||||||
|
actix-tls = { version = "2.0.0", features = ["openssl"] }
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
open-ssl = { version="0.10", package = "openssl" }
|
open-ssl = { version="0.10", package = "openssl" }
|
||||||
rust-tls = { version="0.19", package = "rustls" }
|
rust-tls = { version="0.18", package = "rustls" }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "write-camel-case"
|
name = "content-length"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
|
@ -3,16 +3,16 @@
|
|||||||
> 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/2.2.2)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-http/2.2.0)
|
[](https://deps.rs/crate/actix-http/2.2.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
|
||||||
|
|
||||||
- [API Documentation](https://docs.rs/actix-http)
|
- [API Documentation](https://docs.rs/actix-http)
|
||||||
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.46.0
|
- Minimum Supported Rust Version (MSRV): 1.42.0
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
291
actix-http/benches/content-length.rs
Normal file
291
actix-http/benches/content-length.rs
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
|
||||||
|
// benchmark sending all requests at the same time
|
||||||
|
fn bench_write_content_length(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("write_content_length");
|
||||||
|
|
||||||
|
let sizes = [
|
||||||
|
0, 1, 11, 83, 101, 653, 1001, 6323, 10001, 56329, 100001, 123456, 98724245,
|
||||||
|
4294967202,
|
||||||
|
];
|
||||||
|
|
||||||
|
for i in sizes.iter() {
|
||||||
|
group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut b = BytesMut::with_capacity(35);
|
||||||
|
_original::write_content_length(i, &mut b)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut b = BytesMut::with_capacity(35);
|
||||||
|
_new::write_content_length(i, &mut b)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("itoa", i), i, |b, &i| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut b = BytesMut::with_capacity(35);
|
||||||
|
_itoa::write_content_length(i, &mut b)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, bench_write_content_length);
|
||||||
|
criterion_main!(benches);
|
||||||
|
|
||||||
|
mod _itoa {
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
|
|
||||||
|
pub fn write_content_length(n: usize, bytes: &mut BytesMut) {
|
||||||
|
if n == 0 {
|
||||||
|
bytes.put_slice(b"\r\ncontent-length: 0\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = itoa::Buffer::new();
|
||||||
|
|
||||||
|
bytes.put_slice(b"\r\ncontent-length: ");
|
||||||
|
bytes.put_slice(buf.format(n).as_bytes());
|
||||||
|
bytes.put_slice(b"\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod _new {
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
|
|
||||||
|
const DIGITS_START: u8 = b'0';
|
||||||
|
|
||||||
|
/// NOTE: bytes object has to contain enough space
|
||||||
|
pub fn write_content_length(n: usize, bytes: &mut BytesMut) {
|
||||||
|
if n == 0 {
|
||||||
|
bytes.put_slice(b"\r\ncontent-length: 0\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes.put_slice(b"\r\ncontent-length: ");
|
||||||
|
|
||||||
|
if n < 10 {
|
||||||
|
bytes.put_u8(DIGITS_START + (n as u8));
|
||||||
|
} else if n < 100 {
|
||||||
|
let n = n as u8;
|
||||||
|
|
||||||
|
let d10 = n / 10;
|
||||||
|
let d1 = n % 10;
|
||||||
|
|
||||||
|
bytes.put_u8(DIGITS_START + d10);
|
||||||
|
bytes.put_u8(DIGITS_START + d1);
|
||||||
|
} else if n < 1000 {
|
||||||
|
let n = n as u16;
|
||||||
|
|
||||||
|
let d100 = (n / 100) as u8;
|
||||||
|
let d10 = ((n / 10) % 10) as u8;
|
||||||
|
let d1 = (n % 10) as u8;
|
||||||
|
|
||||||
|
bytes.put_u8(DIGITS_START + d100);
|
||||||
|
bytes.put_u8(DIGITS_START + d10);
|
||||||
|
bytes.put_u8(DIGITS_START + d1);
|
||||||
|
} else if n < 10_000 {
|
||||||
|
let n = n as u16;
|
||||||
|
|
||||||
|
let d1000 = (n / 1000) as u8;
|
||||||
|
let d100 = ((n / 100) % 10) as u8;
|
||||||
|
let d10 = ((n / 10) % 10) as u8;
|
||||||
|
let d1 = (n % 10) as u8;
|
||||||
|
|
||||||
|
bytes.put_u8(DIGITS_START + d1000);
|
||||||
|
bytes.put_u8(DIGITS_START + d100);
|
||||||
|
bytes.put_u8(DIGITS_START + d10);
|
||||||
|
bytes.put_u8(DIGITS_START + d1);
|
||||||
|
} else if n < 100_000 {
|
||||||
|
let n = n as u32;
|
||||||
|
|
||||||
|
let d10000 = (n / 10000) as u8;
|
||||||
|
let d1000 = ((n / 1000) % 10) as u8;
|
||||||
|
let d100 = ((n / 100) % 10) as u8;
|
||||||
|
let d10 = ((n / 10) % 10) as u8;
|
||||||
|
let d1 = (n % 10) as u8;
|
||||||
|
|
||||||
|
bytes.put_u8(DIGITS_START + d10000);
|
||||||
|
bytes.put_u8(DIGITS_START + d1000);
|
||||||
|
bytes.put_u8(DIGITS_START + d100);
|
||||||
|
bytes.put_u8(DIGITS_START + d10);
|
||||||
|
bytes.put_u8(DIGITS_START + d1);
|
||||||
|
} else if n < 1_000_000 {
|
||||||
|
let n = n as u32;
|
||||||
|
|
||||||
|
let d100000 = (n / 100000) as u8;
|
||||||
|
let d10000 = ((n / 10000) % 10) as u8;
|
||||||
|
let d1000 = ((n / 1000) % 10) as u8;
|
||||||
|
let d100 = ((n / 100) % 10) as u8;
|
||||||
|
let d10 = ((n / 10) % 10) as u8;
|
||||||
|
let d1 = (n % 10) as u8;
|
||||||
|
|
||||||
|
bytes.put_u8(DIGITS_START + d100000);
|
||||||
|
bytes.put_u8(DIGITS_START + d10000);
|
||||||
|
bytes.put_u8(DIGITS_START + d1000);
|
||||||
|
bytes.put_u8(DIGITS_START + d100);
|
||||||
|
bytes.put_u8(DIGITS_START + d10);
|
||||||
|
bytes.put_u8(DIGITS_START + d1);
|
||||||
|
} else {
|
||||||
|
write_usize(n, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes.put_slice(b"\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_usize(n: usize, bytes: &mut BytesMut) {
|
||||||
|
let mut n = n;
|
||||||
|
|
||||||
|
// 20 chars is max length of a usize (2^64)
|
||||||
|
// digits will be added to the buffer from lsd to msd
|
||||||
|
let mut buf = BytesMut::with_capacity(20);
|
||||||
|
|
||||||
|
while n > 9 {
|
||||||
|
// "pop" the least-significant digit
|
||||||
|
let lsd = (n % 10) as u8;
|
||||||
|
|
||||||
|
// remove the lsd from n
|
||||||
|
n = n / 10;
|
||||||
|
|
||||||
|
buf.put_u8(DIGITS_START + lsd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// put msd to result buffer
|
||||||
|
bytes.put_u8(DIGITS_START + (n as u8));
|
||||||
|
|
||||||
|
// put, in reverse (msd to lsd), remaining digits to buffer
|
||||||
|
for i in (0..buf.len()).rev() {
|
||||||
|
bytes.put_u8(buf[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod _original {
|
||||||
|
use std::{mem, ptr, slice};
|
||||||
|
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
|
|
||||||
|
const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
|
||||||
|
2021222324252627282930313233343536373839\
|
||||||
|
4041424344454647484950515253545556575859\
|
||||||
|
6061626364656667686970717273747576777879\
|
||||||
|
8081828384858687888990919293949596979899";
|
||||||
|
|
||||||
|
/// NOTE: bytes object has to contain enough space
|
||||||
|
pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
|
||||||
|
if n < 10 {
|
||||||
|
let mut buf: [u8; 21] = [
|
||||||
|
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l',
|
||||||
|
b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'\r', b'\n',
|
||||||
|
];
|
||||||
|
buf[18] = (n as u8) + b'0';
|
||||||
|
bytes.put_slice(&buf);
|
||||||
|
} else if n < 100 {
|
||||||
|
let mut buf: [u8; 22] = [
|
||||||
|
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l',
|
||||||
|
b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'\r', b'\n',
|
||||||
|
];
|
||||||
|
let d1 = n << 1;
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
DEC_DIGITS_LUT.as_ptr().add(d1),
|
||||||
|
buf.as_mut_ptr().offset(18),
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
bytes.put_slice(&buf);
|
||||||
|
} else if n < 1000 {
|
||||||
|
let mut buf: [u8; 23] = [
|
||||||
|
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l',
|
||||||
|
b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'0', b'\r',
|
||||||
|
b'\n',
|
||||||
|
];
|
||||||
|
// decode 2 more chars, if > 2 chars
|
||||||
|
let d1 = (n % 100) << 1;
|
||||||
|
n /= 100;
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
DEC_DIGITS_LUT.as_ptr().add(d1),
|
||||||
|
buf.as_mut_ptr().offset(19),
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// decode last 1
|
||||||
|
buf[18] = (n as u8) + b'0';
|
||||||
|
|
||||||
|
bytes.put_slice(&buf);
|
||||||
|
} else {
|
||||||
|
bytes.put_slice(b"\r\ncontent-length: ");
|
||||||
|
convert_usize(n, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
|
||||||
|
let mut curr: isize = 39;
|
||||||
|
let mut buf: [u8; 41] = unsafe { mem::MaybeUninit::uninit().assume_init() };
|
||||||
|
buf[39] = b'\r';
|
||||||
|
buf[40] = b'\n';
|
||||||
|
let buf_ptr = buf.as_mut_ptr();
|
||||||
|
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
|
||||||
|
|
||||||
|
// eagerly decode 4 characters at a time
|
||||||
|
while n >= 10_000 {
|
||||||
|
let rem = (n % 10_000) as isize;
|
||||||
|
n /= 10_000;
|
||||||
|
|
||||||
|
let d1 = (rem / 100) << 1;
|
||||||
|
let d2 = (rem % 100) << 1;
|
||||||
|
curr -= 4;
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
lut_ptr.offset(d2),
|
||||||
|
buf_ptr.offset(curr + 2),
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we reach here numbers are <= 9999, so at most 4 chars long
|
||||||
|
let mut n = n as isize; // possibly reduce 64bit math
|
||||||
|
|
||||||
|
// decode 2 more chars, if > 2 chars
|
||||||
|
if n >= 100 {
|
||||||
|
let d1 = (n % 100) << 1;
|
||||||
|
n /= 100;
|
||||||
|
curr -= 2;
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode last 1 or 2 chars
|
||||||
|
if n < 10 {
|
||||||
|
curr -= 1;
|
||||||
|
unsafe {
|
||||||
|
*buf_ptr.offset(curr) = (n as u8) + b'0';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let d1 = n << 1;
|
||||||
|
curr -= 2;
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
bytes.extend_from_slice(slice::from_raw_parts(
|
||||||
|
buf_ptr.offset(curr),
|
||||||
|
41 - curr as usize,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -176,7 +176,7 @@ mod _original {
|
|||||||
buf[5] = b'0';
|
buf[5] = b'0';
|
||||||
buf[7] = b'9';
|
buf[7] = b'9';
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut curr: isize = 12;
|
let mut curr: isize = 12;
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
|
||||||
|
|
||||||
fn bench_write_camel_case(c: &mut Criterion) {
|
|
||||||
let mut group = c.benchmark_group("write_camel_case");
|
|
||||||
|
|
||||||
let names = ["connection", "Transfer-Encoding", "transfer-encoding"];
|
|
||||||
|
|
||||||
for &i in &names {
|
|
||||||
let bts = i.as_bytes();
|
|
||||||
|
|
||||||
group.bench_with_input(BenchmarkId::new("Original", i), bts, |b, bts| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut buf = black_box([0; 24]);
|
|
||||||
_original::write_camel_case(black_box(bts), &mut buf)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group.bench_with_input(BenchmarkId::new("New", i), bts, |b, bts| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut buf = black_box([0; 24]);
|
|
||||||
_new::write_camel_case(black_box(bts), &mut buf)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
group.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(benches, bench_write_camel_case);
|
|
||||||
criterion_main!(benches);
|
|
||||||
|
|
||||||
mod _new {
|
|
||||||
pub fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
|
|
||||||
// first copy entire (potentially wrong) slice to output
|
|
||||||
buffer[..value.len()].copy_from_slice(value);
|
|
||||||
|
|
||||||
let mut iter = value.iter();
|
|
||||||
|
|
||||||
// first character should be uppercase
|
|
||||||
if let Some(c @ b'a'..=b'z') = iter.next() {
|
|
||||||
buffer[0] = c & 0b1101_1111;
|
|
||||||
}
|
|
||||||
|
|
||||||
// track 1 ahead of the current position since that's the location being assigned to
|
|
||||||
let mut index = 2;
|
|
||||||
|
|
||||||
// remaining characters after hyphens should also be uppercase
|
|
||||||
while let Some(&c) = iter.next() {
|
|
||||||
if c == b'-' {
|
|
||||||
// advance iter by one and uppercase if needed
|
|
||||||
if let Some(c @ b'a'..=b'z') = iter.next() {
|
|
||||||
buffer[index] = c & 0b1101_1111;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod _original {
|
|
||||||
pub fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
|
|
||||||
let mut index = 0;
|
|
||||||
let key = value;
|
|
||||||
let mut key_iter = key.iter();
|
|
||||||
|
|
||||||
if let Some(c) = key_iter.next() {
|
|
||||||
if *c >= b'a' && *c <= b'z' {
|
|
||||||
buffer[index] = *c ^ b' ';
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some(c) = key_iter.next() {
|
|
||||||
buffer[index] = *c;
|
|
||||||
index += 1;
|
|
||||||
if *c == b'-' {
|
|
||||||
if let Some(c) = key_iter.next() {
|
|
||||||
if *c >= b'a' && *c <= b'z' {
|
|
||||||
buffer[index] = *c ^ b' ';
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,11 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::{fmt, mem};
|
use std::{fmt, mem};
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::Stream;
|
||||||
|
use futures_util::ready;
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
@ -66,7 +68,7 @@ impl<T: MessageBody + Unpin> MessageBody for Box<T> {
|
|||||||
#[pin_project(project = ResponseBodyProj)]
|
#[pin_project(project = ResponseBodyProj)]
|
||||||
pub enum ResponseBody<B> {
|
pub enum ResponseBody<B> {
|
||||||
Body(#[pin] B),
|
Body(#[pin] B),
|
||||||
Other(Body),
|
Other(#[pin] Body),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseBody<Body> {
|
impl ResponseBody<Body> {
|
||||||
@ -108,7 +110,7 @@ impl<B: MessageBody> MessageBody for ResponseBody<B> {
|
|||||||
) -> Poll<Option<Result<Bytes, Error>>> {
|
) -> Poll<Option<Result<Bytes, Error>>> {
|
||||||
match self.project() {
|
match self.project() {
|
||||||
ResponseBodyProj::Body(body) => body.poll_next(cx),
|
ResponseBodyProj::Body(body) => body.poll_next(cx),
|
||||||
ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx),
|
ResponseBodyProj::Other(body) => body.poll_next(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,11 +124,12 @@ impl<B: MessageBody> Stream for ResponseBody<B> {
|
|||||||
) -> Poll<Option<Self::Item>> {
|
) -> Poll<Option<Self::Item>> {
|
||||||
match self.project() {
|
match self.project() {
|
||||||
ResponseBodyProj::Body(body) => body.poll_next(cx),
|
ResponseBodyProj::Body(body) => body.poll_next(cx),
|
||||||
ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx),
|
ResponseBodyProj::Other(body) => body.poll_next(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pin_project(project = BodyProj)]
|
||||||
/// 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.
|
||||||
@ -165,10 +168,10 @@ impl MessageBody for Body {
|
|||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Option<Result<Bytes, Error>>> {
|
) -> Poll<Option<Result<Bytes, Error>>> {
|
||||||
match self.get_mut() {
|
match self.project() {
|
||||||
Body::None => Poll::Ready(None),
|
BodyProj::None => Poll::Ready(None),
|
||||||
Body::Empty => Poll::Ready(None),
|
BodyProj::Empty => Poll::Ready(None),
|
||||||
Body::Bytes(ref mut bin) => {
|
BodyProj::Bytes(ref mut bin) => {
|
||||||
let len = bin.len();
|
let len = bin.len();
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
@ -176,7 +179,7 @@ impl MessageBody for Body {
|
|||||||
Poll::Ready(Some(Ok(mem::take(bin))))
|
Poll::Ready(Some(Ok(mem::take(bin))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Body::Message(body) => Pin::new(&mut **body).poll_next(cx),
|
BodyProj::Message(ref mut body) => Pin::new(body.as_mut()).poll_next(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -263,12 +266,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, E> From<BodyStream<S>> for Body
|
impl<S, E> From<BodyStream<S, E>> for Body
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
||||||
E: Into<Error> + 'static,
|
E: Into<Error> + 'static,
|
||||||
{
|
{
|
||||||
fn from(s: BodyStream<S>) -> Body {
|
fn from(s: BodyStream<S, E>) -> Body {
|
||||||
Body::from_message(s)
|
Body::from_message(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -364,21 +367,27 @@ impl MessageBody for String {
|
|||||||
|
|
||||||
/// Type represent streaming body.
|
/// Type represent streaming body.
|
||||||
/// Response does not contain `content-length` header and appropriate transfer encoding is used.
|
/// Response does not contain `content-length` header and appropriate transfer encoding is used.
|
||||||
pub struct BodyStream<S: Unpin> {
|
#[pin_project]
|
||||||
|
pub struct BodyStream<S: Unpin, E> {
|
||||||
|
#[pin]
|
||||||
stream: S,
|
stream: S,
|
||||||
|
_t: PhantomData<E>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, E> BodyStream<S>
|
impl<S, E> BodyStream<S, E>
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + Unpin,
|
S: Stream<Item = Result<Bytes, E>> + Unpin,
|
||||||
E: Into<Error>,
|
E: Into<Error>,
|
||||||
{
|
{
|
||||||
pub fn new(stream: S) -> Self {
|
pub fn new(stream: S) -> Self {
|
||||||
BodyStream { stream }
|
BodyStream {
|
||||||
|
stream,
|
||||||
|
_t: PhantomData,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, E> MessageBody for BodyStream<S>
|
impl<S, E> MessageBody for BodyStream<S, E>
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + Unpin,
|
S: Stream<Item = Result<Bytes, E>> + Unpin,
|
||||||
E: Into<Error>,
|
E: Into<Error>,
|
||||||
@ -393,12 +402,13 @@ where
|
|||||||
/// ended on a zero-length chunk, but rather proceed until the underlying
|
/// ended on a zero-length chunk, but rather proceed until the underlying
|
||||||
/// [`Stream`] ends.
|
/// [`Stream`] ends.
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
mut self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Option<Result<Bytes, Error>>> {
|
) -> Poll<Option<Result<Bytes, Error>>> {
|
||||||
|
let mut stream = self.project().stream;
|
||||||
loop {
|
loop {
|
||||||
let stream = &mut self.as_mut().stream;
|
let stream = stream.as_mut();
|
||||||
return Poll::Ready(match ready!(Pin::new(stream).poll_next(cx)) {
|
return Poll::Ready(match ready!(stream.poll_next(cx)) {
|
||||||
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
|
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
|
||||||
opt => opt.map(|res| res.map_err(Into::into)),
|
opt => opt.map(|res| res.map_err(Into::into)),
|
||||||
});
|
});
|
||||||
@ -408,8 +418,10 @@ where
|
|||||||
|
|
||||||
/// Type represent streaming body. This body implementation should be used
|
/// Type represent streaming body. This body implementation should be used
|
||||||
/// if total size of stream is known. Data get sent as is without using transfer encoding.
|
/// if total size of stream is known. Data get sent as is without using transfer encoding.
|
||||||
|
#[pin_project]
|
||||||
pub struct SizedStream<S: Unpin> {
|
pub struct SizedStream<S: Unpin> {
|
||||||
size: u64,
|
size: u64,
|
||||||
|
#[pin]
|
||||||
stream: S,
|
stream: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,12 +448,13 @@ where
|
|||||||
/// ended on a zero-length chunk, but rather proceed until the underlying
|
/// ended on a zero-length chunk, but rather proceed until the underlying
|
||||||
/// [`Stream`] ends.
|
/// [`Stream`] ends.
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
mut self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Option<Result<Bytes, Error>>> {
|
) -> Poll<Option<Result<Bytes, Error>>> {
|
||||||
|
let mut stream: Pin<&mut S> = self.project().stream;
|
||||||
loop {
|
loop {
|
||||||
let stream = &mut self.as_mut().stream;
|
let stream = stream.as_mut();
|
||||||
return Poll::Ready(match ready!(Pin::new(stream).poll_next(cx)) {
|
return Poll::Ready(match ready!(stream.poll_next(cx)) {
|
||||||
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
|
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
|
||||||
val => val,
|
val => val,
|
||||||
});
|
});
|
||||||
|
@ -10,6 +10,7 @@ use crate::config::{KeepAlive, ServiceConfig};
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler};
|
use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler};
|
||||||
use crate::h2::H2Service;
|
use crate::h2::H2Service;
|
||||||
|
use crate::helpers::{Data, DataFactory};
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::service::HttpService;
|
use crate::service::HttpService;
|
||||||
@ -19,7 +20,7 @@ use crate::{ConnectCallback, Extensions};
|
|||||||
///
|
///
|
||||||
/// This type can be used to construct an instance of [`HttpService`] through a
|
/// This type can be used to construct an instance of [`HttpService`] through a
|
||||||
/// builder-like pattern.
|
/// builder-like pattern.
|
||||||
pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
|
pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
|
||||||
keep_alive: KeepAlive,
|
keep_alive: KeepAlive,
|
||||||
client_timeout: u64,
|
client_timeout: u64,
|
||||||
client_disconnect: u64,
|
client_disconnect: u64,
|
||||||
@ -27,16 +28,18 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
|
|||||||
local_addr: Option<net::SocketAddr>,
|
local_addr: Option<net::SocketAddr>,
|
||||||
expect: X,
|
expect: X,
|
||||||
upgrade: Option<U>,
|
upgrade: Option<U>,
|
||||||
|
// DEPRECATED: in favor of on_connect_ext
|
||||||
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_phantom: PhantomData<S>,
|
_t: PhantomData<(T, S)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
|
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler<T>>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
/// Create instance of `ServiceConfigBuilder`
|
/// Create instance of `ServiceConfigBuilder`
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@ -48,26 +51,27 @@ where
|
|||||||
local_addr: None,
|
local_addr: None,
|
||||||
expect: ExpectHandler,
|
expect: ExpectHandler,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
|
on_connect: None,
|
||||||
on_connect_ext: None,
|
on_connect_ext: None,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
|
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
<X::Service as Service<Request>>::Future: 'static,
|
<X::Service as Service>::Future: 'static,
|
||||||
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
|
U: ServiceFactory<Config = (), Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
<U::Service as Service<(Request, Framed<T, Codec>)>>::Future: 'static,
|
<U::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
/// Set server keep-alive setting.
|
/// Set server keep-alive setting.
|
||||||
///
|
///
|
||||||
@ -123,11 +127,11 @@ where
|
|||||||
/// request will be forwarded to main service.
|
/// request will be forwarded to main service.
|
||||||
pub fn expect<F, X1>(self, expect: F) -> HttpServiceBuilder<T, S, X1, U>
|
pub fn expect<F, X1>(self, expect: F) -> HttpServiceBuilder<T, S, X1, U>
|
||||||
where
|
where
|
||||||
F: IntoServiceFactory<X1, Request>,
|
F: IntoServiceFactory<X1>,
|
||||||
X1: ServiceFactory<Request, Config = (), Response = Request>,
|
X1: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
||||||
X1::Error: Into<Error>,
|
X1::Error: Into<Error>,
|
||||||
X1::InitError: fmt::Debug,
|
X1::InitError: fmt::Debug,
|
||||||
<X1::Service as Service<Request>>::Future: 'static,
|
<X1::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
HttpServiceBuilder {
|
HttpServiceBuilder {
|
||||||
keep_alive: self.keep_alive,
|
keep_alive: self.keep_alive,
|
||||||
@ -137,8 +141,9 @@ where
|
|||||||
local_addr: self.local_addr,
|
local_addr: self.local_addr,
|
||||||
expect: expect.into_factory(),
|
expect: expect.into_factory(),
|
||||||
upgrade: self.upgrade,
|
upgrade: self.upgrade,
|
||||||
|
on_connect: self.on_connect,
|
||||||
on_connect_ext: self.on_connect_ext,
|
on_connect_ext: self.on_connect_ext,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,11 +153,15 @@ where
|
|||||||
/// and this service get called with original request and framed object.
|
/// and this service get called with original request and framed object.
|
||||||
pub fn upgrade<F, U1>(self, upgrade: F) -> HttpServiceBuilder<T, S, X, U1>
|
pub fn upgrade<F, U1>(self, upgrade: F) -> HttpServiceBuilder<T, S, X, U1>
|
||||||
where
|
where
|
||||||
F: IntoServiceFactory<U1, (Request, Framed<T, Codec>)>,
|
F: IntoServiceFactory<U1>,
|
||||||
U1: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
|
U1: ServiceFactory<
|
||||||
|
Config = (),
|
||||||
|
Request = (Request, Framed<T, Codec>),
|
||||||
|
Response = (),
|
||||||
|
>,
|
||||||
U1::Error: fmt::Display,
|
U1::Error: fmt::Display,
|
||||||
U1::InitError: fmt::Debug,
|
U1::InitError: fmt::Debug,
|
||||||
<U1::Service as Service<(Request, Framed<T, Codec>)>>::Future: 'static,
|
<U1::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
HttpServiceBuilder {
|
HttpServiceBuilder {
|
||||||
keep_alive: self.keep_alive,
|
keep_alive: self.keep_alive,
|
||||||
@ -162,11 +171,26 @@ where
|
|||||||
local_addr: self.local_addr,
|
local_addr: self.local_addr,
|
||||||
expect: self.expect,
|
expect: self.expect,
|
||||||
upgrade: Some(upgrade.into_factory()),
|
upgrade: Some(upgrade.into_factory()),
|
||||||
|
on_connect: self.on_connect,
|
||||||
on_connect_ext: self.on_connect_ext,
|
on_connect_ext: self.on_connect_ext,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set on-connect callback.
|
||||||
|
///
|
||||||
|
/// Called once per connection. Return value of the call is stored in request extensions.
|
||||||
|
///
|
||||||
|
/// *SOFT DEPRECATED*: Prefer the `on_connect_ext` style callback.
|
||||||
|
pub fn on_connect<F, I>(mut self, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(&T) -> I + 'static,
|
||||||
|
I: Clone + 'static,
|
||||||
|
{
|
||||||
|
self.on_connect = Some(Rc::new(move |io| Box::new(Data(f(io)))));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the callback to be run on connection establishment.
|
/// Sets the callback to be run on connection establishment.
|
||||||
///
|
///
|
||||||
/// Has mutable access to a data container that will be merged into request extensions.
|
/// Has mutable access to a data container that will be merged into request extensions.
|
||||||
@ -184,7 +208,7 @@ where
|
|||||||
pub fn h1<F, B>(self, service: F) -> H1Service<T, S, B, X, U>
|
pub fn h1<F, B>(self, service: F) -> H1Service<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
F: IntoServiceFactory<S, Request>,
|
F: IntoServiceFactory<S>,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
@ -200,6 +224,7 @@ where
|
|||||||
H1Service::with_config(cfg, service.into_factory())
|
H1Service::with_config(cfg, service.into_factory())
|
||||||
.expect(self.expect)
|
.expect(self.expect)
|
||||||
.upgrade(self.upgrade)
|
.upgrade(self.upgrade)
|
||||||
|
.on_connect(self.on_connect)
|
||||||
.on_connect_ext(self.on_connect_ext)
|
.on_connect_ext(self.on_connect_ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,11 +232,11 @@ where
|
|||||||
pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
|
pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
|
||||||
where
|
where
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
F: IntoServiceFactory<S, Request>,
|
F: IntoServiceFactory<S>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
let cfg = ServiceConfig::new(
|
let cfg = ServiceConfig::new(
|
||||||
self.keep_alive,
|
self.keep_alive,
|
||||||
@ -222,6 +247,7 @@ where
|
|||||||
);
|
);
|
||||||
|
|
||||||
H2Service::with_config(cfg, service.into_factory())
|
H2Service::with_config(cfg, service.into_factory())
|
||||||
|
.on_connect(self.on_connect)
|
||||||
.on_connect_ext(self.on_connect_ext)
|
.on_connect_ext(self.on_connect_ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,11 +255,11 @@ where
|
|||||||
pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U>
|
pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
F: IntoServiceFactory<S, Request>,
|
F: IntoServiceFactory<S>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
let cfg = ServiceConfig::new(
|
let cfg = ServiceConfig::new(
|
||||||
self.keep_alive,
|
self.keep_alive,
|
||||||
@ -246,6 +272,7 @@ where
|
|||||||
HttpService::with_config(cfg, service.into_factory())
|
HttpService::with_config(cfg, service.into_factory())
|
||||||
.expect(self.expect)
|
.expect(self.expect)
|
||||||
.upgrade(self.upgrade)
|
.upgrade(self.upgrade)
|
||||||
|
.on_connect(self.on_connect)
|
||||||
.on_connect_ext(self.on_connect_ext)
|
.on_connect_ext(self.on_connect_ext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::{fmt, io, time};
|
use std::{fmt, io, mem, time};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
use bytes::Bytes;
|
use bytes::{Buf, Bytes};
|
||||||
use futures_util::future::{err, Either, FutureExt, LocalBoxFuture, Ready};
|
use futures_util::future::{err, Either, FutureExt, LocalBoxFuture, Ready};
|
||||||
use h2::client::SendRequest;
|
use h2::client::SendRequest;
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
@ -223,13 +223,23 @@ where
|
|||||||
fn poll_read(
|
fn poll_read(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
buf: &mut ReadBuf<'_>,
|
buf: &mut [u8],
|
||||||
) -> Poll<io::Result<()>> {
|
) -> Poll<io::Result<usize>> {
|
||||||
match self.project() {
|
match self.project() {
|
||||||
EitherIoProj::A(val) => val.poll_read(cx, buf),
|
EitherIoProj::A(val) => val.poll_read(cx, buf),
|
||||||
EitherIoProj::B(val) => val.poll_read(cx, buf),
|
EitherIoProj::B(val) => val.poll_read(cx, buf),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe fn prepare_uninitialized_buffer(
|
||||||
|
&self,
|
||||||
|
buf: &mut [mem::MaybeUninit<u8>],
|
||||||
|
) -> bool {
|
||||||
|
match self {
|
||||||
|
EitherIo::A(ref val) => val.prepare_uninitialized_buffer(buf),
|
||||||
|
EitherIo::B(ref val) => val.prepare_uninitialized_buffer(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, B> AsyncWrite for EitherIo<A, B>
|
impl<A, B> AsyncWrite for EitherIo<A, B>
|
||||||
@ -264,4 +274,18 @@ where
|
|||||||
EitherIoProj::B(val) => val.poll_shutdown(cx),
|
EitherIoProj::B(val) => val.poll_shutdown(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn poll_write_buf<U: Buf>(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &mut U,
|
||||||
|
) -> Poll<Result<usize, io::Error>>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
match self.project() {
|
||||||
|
EitherIoProj::A(val) => val.poll_write_buf(cx, buf),
|
||||||
|
EitherIoProj::B(val) => val.poll_write_buf(cx, buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@ use std::marker::PhantomData;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite};
|
use actix_codec::{AsyncRead, AsyncWrite};
|
||||||
use actix_rt::net::TcpStream;
|
use actix_connect::{
|
||||||
use actix_service::{apply_fn, Service, ServiceExt};
|
|
||||||
use actix_tls::connect::{
|
|
||||||
default_connector, Connect as TcpConnect, Connection as TcpConnection,
|
default_connector, Connect as TcpConnect, Connection as TcpConnection,
|
||||||
};
|
};
|
||||||
|
use actix_rt::net::TcpStream;
|
||||||
|
use actix_service::{apply_fn, Service};
|
||||||
use actix_utils::timeout::{TimeoutError, TimeoutService};
|
use actix_utils::timeout::{TimeoutError, TimeoutService};
|
||||||
use http::Uri;
|
use http::Uri;
|
||||||
|
|
||||||
@ -18,10 +18,10 @@ use super::pool::{ConnectionPool, Protocol};
|
|||||||
use super::Connect;
|
use super::Connect;
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector;
|
use actix_connect::ssl::openssl::SslConnector as OpensslConnector;
|
||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
use actix_tls::connect::ssl::rustls::ClientConfig;
|
use actix_connect::ssl::rustls::ClientConfig;
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ pub struct Connector<T, U> {
|
|||||||
config: ConnectorConfig,
|
config: ConnectorConfig,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
ssl: SslConnector,
|
ssl: SslConnector,
|
||||||
_phantom: PhantomData<U>,
|
_t: PhantomData<U>,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Io: AsyncRead + AsyncWrite + Unpin {}
|
trait Io: AsyncRead + AsyncWrite + Unpin {}
|
||||||
@ -62,9 +62,9 @@ impl Connector<(), ()> {
|
|||||||
#[allow(clippy::new_ret_no_self, clippy::let_unit_value)]
|
#[allow(clippy::new_ret_no_self, clippy::let_unit_value)]
|
||||||
pub fn new() -> Connector<
|
pub fn new() -> Connector<
|
||||||
impl Service<
|
impl Service<
|
||||||
TcpConnect<Uri>,
|
Request = TcpConnect<Uri>,
|
||||||
Response = TcpConnection<Uri, TcpStream>,
|
Response = TcpConnection<Uri, TcpStream>,
|
||||||
Error = actix_tls::connect::ConnectError,
|
Error = actix_connect::ConnectError,
|
||||||
> + Clone,
|
> + Clone,
|
||||||
TcpStream,
|
TcpStream,
|
||||||
> {
|
> {
|
||||||
@ -72,14 +72,14 @@ impl 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: default_connector(),
|
||||||
config: ConnectorConfig::default(),
|
config: ConnectorConfig::default(),
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build Ssl connector with openssl, based on supplied alpn protocols
|
// Build Ssl connector with openssl, based on supplied alpn protocols
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector {
|
fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector {
|
||||||
use actix_tls::connect::ssl::openssl::SslMethod;
|
use actix_connect::ssl::openssl::SslMethod;
|
||||||
use bytes::{BufMut, BytesMut};
|
use bytes::{BufMut, BytesMut};
|
||||||
|
|
||||||
let mut alpn = BytesMut::with_capacity(20);
|
let mut alpn = BytesMut::with_capacity(20);
|
||||||
@ -102,7 +102,7 @@ impl Connector<(), ()> {
|
|||||||
config.set_protocols(&protocols);
|
config.set_protocols(&protocols);
|
||||||
config
|
config
|
||||||
.root_store
|
.root_store
|
||||||
.add_server_trust_anchors(&actix_tls::accept::rustls::TLS_SERVER_ROOTS);
|
.add_server_trust_anchors(&actix_tls::rustls::TLS_SERVER_ROOTS);
|
||||||
SslConnector::Rustls(Arc::new(config))
|
SslConnector::Rustls(Arc::new(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,16 +117,16 @@ impl<T, U> Connector<T, U> {
|
|||||||
where
|
where
|
||||||
U1: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
|
U1: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
|
||||||
T1: Service<
|
T1: Service<
|
||||||
TcpConnect<Uri>,
|
Request = TcpConnect<Uri>,
|
||||||
Response = TcpConnection<Uri, U1>,
|
Response = TcpConnection<Uri, U1>,
|
||||||
Error = actix_tls::connect::ConnectError,
|
Error = actix_connect::ConnectError,
|
||||||
> + Clone,
|
> + Clone,
|
||||||
{
|
{
|
||||||
Connector {
|
Connector {
|
||||||
connector,
|
connector,
|
||||||
config: self.config,
|
config: self.config,
|
||||||
ssl: self.ssl,
|
ssl: self.ssl,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,9 +135,9 @@ impl<T, U> Connector<T, U>
|
|||||||
where
|
where
|
||||||
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
|
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
|
||||||
T: Service<
|
T: Service<
|
||||||
TcpConnect<Uri>,
|
Request = TcpConnect<Uri>,
|
||||||
Response = TcpConnection<Uri, U>,
|
Response = TcpConnection<Uri, U>,
|
||||||
Error = actix_tls::connect::ConnectError,
|
Error = actix_connect::ConnectError,
|
||||||
> + Clone
|
> + Clone
|
||||||
+ 'static,
|
+ 'static,
|
||||||
{
|
{
|
||||||
@ -241,8 +241,8 @@ where
|
|||||||
/// its combinator chain.
|
/// its combinator chain.
|
||||||
pub fn finish(
|
pub fn finish(
|
||||||
self,
|
self,
|
||||||
) -> impl Service<Connect, Response = impl Connection, Error = ConnectError> + Clone
|
) -> impl Service<Request = Connect, Response = impl Connection, Error = ConnectError>
|
||||||
{
|
+ Clone {
|
||||||
#[cfg(not(any(feature = "openssl", feature = "rustls")))]
|
#[cfg(not(any(feature = "openssl", feature = "rustls")))]
|
||||||
{
|
{
|
||||||
let connector = TimeoutService::new(
|
let connector = TimeoutService::new(
|
||||||
@ -268,11 +268,11 @@ where
|
|||||||
#[cfg(any(feature = "openssl", feature = "rustls"))]
|
#[cfg(any(feature = "openssl", feature = "rustls"))]
|
||||||
{
|
{
|
||||||
const H2: &[u8] = b"h2";
|
const H2: &[u8] = b"h2";
|
||||||
use actix_service::{boxed::service, pipeline};
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
use actix_tls::connect::ssl::openssl::OpensslConnector;
|
use actix_connect::ssl::openssl::OpensslConnector;
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
use actix_tls::connect::ssl::rustls::{RustlsConnector, Session};
|
use actix_connect::ssl::rustls::{RustlsConnector, Session};
|
||||||
|
use actix_service::{boxed::service, pipeline};
|
||||||
|
|
||||||
let ssl_service = TimeoutService::new(
|
let ssl_service = TimeoutService::new(
|
||||||
self.config.timeout,
|
self.config.timeout,
|
||||||
@ -363,7 +363,8 @@ mod connect_impl {
|
|||||||
pub(crate) struct InnerConnector<T, Io>
|
pub(crate) struct InnerConnector<T, Io>
|
||||||
where
|
where
|
||||||
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
|
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
|
||||||
|
+ 'static,
|
||||||
{
|
{
|
||||||
pub(crate) tcp_pool: ConnectionPool<T, Io>,
|
pub(crate) tcp_pool: ConnectionPool<T, Io>,
|
||||||
}
|
}
|
||||||
@ -371,7 +372,8 @@ mod connect_impl {
|
|||||||
impl<T, Io> Clone for InnerConnector<T, Io>
|
impl<T, Io> Clone for InnerConnector<T, Io>
|
||||||
where
|
where
|
||||||
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
|
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
|
||||||
|
+ 'static,
|
||||||
{
|
{
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
InnerConnector {
|
InnerConnector {
|
||||||
@ -380,15 +382,17 @@ mod connect_impl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, Io> Service<Connect> for InnerConnector<T, Io>
|
impl<T, Io> Service for InnerConnector<T, Io>
|
||||||
where
|
where
|
||||||
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
|
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
|
||||||
|
+ 'static,
|
||||||
{
|
{
|
||||||
|
type Request = Connect;
|
||||||
type Response = IoConnection<Io>;
|
type Response = IoConnection<Io>;
|
||||||
type Error = ConnectError;
|
type Error = ConnectError;
|
||||||
type Future = Either<
|
type Future = Either<
|
||||||
<ConnectionPool<T, Io> as Service<Connect>>::Future,
|
<ConnectionPool<T, Io> as Service>::Future,
|
||||||
Ready<Result<IoConnection<Io>, ConnectError>>,
|
Ready<Result<IoConnection<Io>, ConnectError>>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@ -424,8 +428,8 @@ mod connect_impl {
|
|||||||
where
|
where
|
||||||
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
T1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError>,
|
T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>,
|
||||||
T2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError>,
|
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>,
|
||||||
{
|
{
|
||||||
pub(crate) tcp_pool: ConnectionPool<T1, Io1>,
|
pub(crate) tcp_pool: ConnectionPool<T1, Io1>,
|
||||||
pub(crate) ssl_pool: ConnectionPool<T2, Io2>,
|
pub(crate) ssl_pool: ConnectionPool<T2, Io2>,
|
||||||
@ -435,8 +439,10 @@ mod connect_impl {
|
|||||||
where
|
where
|
||||||
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
T1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
|
T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
|
||||||
T2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
|
+ 'static,
|
||||||
|
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
|
||||||
|
+ 'static,
|
||||||
{
|
{
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
InnerConnector {
|
InnerConnector {
|
||||||
@ -446,13 +452,16 @@ mod connect_impl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T1, T2, Io1, Io2> Service<Connect> for InnerConnector<T1, T2, Io1, Io2>
|
impl<T1, T2, Io1, Io2> Service for InnerConnector<T1, T2, Io1, Io2>
|
||||||
where
|
where
|
||||||
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
T1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
|
T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
|
||||||
T2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
|
+ 'static,
|
||||||
|
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
|
||||||
|
+ 'static,
|
||||||
{
|
{
|
||||||
|
type Request = Connect;
|
||||||
type Response = EitherConnection<Io1, Io2>;
|
type Response = EitherConnection<Io1, Io2>;
|
||||||
type Error = ConnectError;
|
type Error = ConnectError;
|
||||||
type Future = Either<
|
type Future = Either<
|
||||||
@ -468,11 +477,11 @@ mod connect_impl {
|
|||||||
match req.uri.scheme_str() {
|
match req.uri.scheme_str() {
|
||||||
Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB {
|
Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB {
|
||||||
fut: self.ssl_pool.call(req),
|
fut: self.ssl_pool.call(req),
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}),
|
}),
|
||||||
_ => Either::Left(InnerConnectorResponseA {
|
_ => Either::Left(InnerConnectorResponseA {
|
||||||
fut: self.tcp_pool.call(req),
|
fut: self.tcp_pool.call(req),
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -482,16 +491,18 @@ mod connect_impl {
|
|||||||
pub(crate) struct InnerConnectorResponseA<T, Io1, Io2>
|
pub(crate) struct InnerConnectorResponseA<T, Io1, Io2>
|
||||||
where
|
where
|
||||||
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
T: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
|
T: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
|
||||||
|
+ 'static,
|
||||||
{
|
{
|
||||||
#[pin]
|
#[pin]
|
||||||
fut: <ConnectionPool<T, Io1> as Service<Connect>>::Future,
|
fut: <ConnectionPool<T, Io1> as Service>::Future,
|
||||||
_phantom: PhantomData<Io2>,
|
_t: PhantomData<Io2>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, Io1, Io2> Future for InnerConnectorResponseA<T, Io1, Io2>
|
impl<T, Io1, Io2> Future for InnerConnectorResponseA<T, Io1, Io2>
|
||||||
where
|
where
|
||||||
T: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
|
T: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
|
||||||
|
+ 'static,
|
||||||
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
{
|
{
|
||||||
@ -509,16 +520,18 @@ mod connect_impl {
|
|||||||
pub(crate) struct InnerConnectorResponseB<T, Io1, Io2>
|
pub(crate) struct InnerConnectorResponseB<T, Io1, Io2>
|
||||||
where
|
where
|
||||||
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
T: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
|
T: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
|
||||||
|
+ 'static,
|
||||||
{
|
{
|
||||||
#[pin]
|
#[pin]
|
||||||
fut: <ConnectionPool<T, Io2> as Service<Connect>>::Future,
|
fut: <ConnectionPool<T, Io2> as Service>::Future,
|
||||||
_phantom: PhantomData<Io1>,
|
_t: PhantomData<Io1>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, Io1, Io2> Future for InnerConnectorResponseB<T, Io1, Io2>
|
impl<T, Io1, Io2> Future for InnerConnectorResponseB<T, Io1, Io2>
|
||||||
where
|
where
|
||||||
T: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
|
T: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
|
||||||
|
+ 'static,
|
||||||
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
{
|
{
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use actix_tls::connect::resolver::ResolveError;
|
use actix_connect::resolver::ResolveError;
|
||||||
use derive_more::{Display, From};
|
use derive_more::{Display, From};
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
use actix_tls::accept::openssl::SslError;
|
use actix_connect::ssl::openssl::{HandshakeError, SslError};
|
||||||
|
|
||||||
use crate::error::{Error, ParseError, ResponseError};
|
use crate::error::{Error, ParseError, ResponseError};
|
||||||
use crate::http::{Error as HttpError, StatusCode};
|
use crate::http::{Error as HttpError, StatusCode};
|
||||||
@ -21,6 +21,11 @@ pub enum ConnectError {
|
|||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
SslError(SslError),
|
SslError(SslError),
|
||||||
|
|
||||||
|
/// SSL Handshake error
|
||||||
|
#[cfg(feature = "openssl")]
|
||||||
|
#[display(fmt = "{}", _0)]
|
||||||
|
SslHandshakeError(String),
|
||||||
|
|
||||||
/// Failed to resolve the hostname
|
/// Failed to resolve the hostname
|
||||||
#[display(fmt = "Failed resolving hostname: {}", _0)]
|
#[display(fmt = "Failed resolving hostname: {}", _0)]
|
||||||
Resolver(ResolveError),
|
Resolver(ResolveError),
|
||||||
@ -52,18 +57,25 @@ pub enum ConnectError {
|
|||||||
|
|
||||||
impl std::error::Error for ConnectError {}
|
impl std::error::Error for ConnectError {}
|
||||||
|
|
||||||
impl From<actix_tls::connect::ConnectError> for ConnectError {
|
impl From<actix_connect::ConnectError> for ConnectError {
|
||||||
fn from(err: actix_tls::connect::ConnectError) -> ConnectError {
|
fn from(err: actix_connect::ConnectError) -> ConnectError {
|
||||||
match err {
|
match err {
|
||||||
actix_tls::connect::ConnectError::Resolver(e) => ConnectError::Resolver(e),
|
actix_connect::ConnectError::Resolver(e) => ConnectError::Resolver(e),
|
||||||
actix_tls::connect::ConnectError::NoRecords => ConnectError::NoRecords,
|
actix_connect::ConnectError::NoRecords => ConnectError::NoRecords,
|
||||||
actix_tls::connect::ConnectError::InvalidInput => panic!(),
|
actix_connect::ConnectError::InvalidInput => panic!(),
|
||||||
actix_tls::connect::ConnectError::Unresolved => ConnectError::Unresolved,
|
actix_connect::ConnectError::Unresolved => ConnectError::Unresolved,
|
||||||
actix_tls::connect::ConnectError::Io(e) => ConnectError::Io(e),
|
actix_connect::ConnectError::Io(e) => ConnectError::Io(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "openssl")]
|
||||||
|
impl<T: std::fmt::Debug> From<HandshakeError<T>> for ConnectError {
|
||||||
|
fn from(err: HandshakeError<T>) -> ConnectError {
|
||||||
|
ConnectError::SslHandshakeError(format!("{:?}", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Display, From)]
|
#[derive(Debug, Display, From)]
|
||||||
pub enum InvalidUrl {
|
pub enum InvalidUrl {
|
||||||
#[display(fmt = "Missing url scheme")]
|
#[display(fmt = "Missing url scheme")]
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::{io, time};
|
use std::{io, mem, time};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
use bytes::buf::BufMut;
|
use bytes::buf::BufMutExt;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use futures_util::future::poll_fn;
|
use futures_util::future::poll_fn;
|
||||||
@ -72,7 +72,7 @@ where
|
|||||||
|
|
||||||
// send request body
|
// send request body
|
||||||
match body.size() {
|
match body.size() {
|
||||||
BodySize::None | BodySize::Empty | BodySize::Sized(0) => {}
|
BodySize::None | BodySize::Empty | BodySize::Sized(0) => (),
|
||||||
_ => send_body(body, Pin::new(&mut framed_inner)).await?,
|
_ => send_body(body, Pin::new(&mut framed_inner)).await?,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -204,11 +204,18 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsyncRead + AsyncWrite + Unpin + 'static> AsyncRead for H1Connection<T> {
|
impl<T: AsyncRead + AsyncWrite + Unpin + 'static> AsyncRead for H1Connection<T> {
|
||||||
|
unsafe fn prepare_uninitialized_buffer(
|
||||||
|
&self,
|
||||||
|
buf: &mut [mem::MaybeUninit<u8>],
|
||||||
|
) -> bool {
|
||||||
|
self.io.as_ref().unwrap().prepare_uninitialized_buffer(buf)
|
||||||
|
}
|
||||||
|
|
||||||
fn poll_read(
|
fn poll_read(
|
||||||
mut self: Pin<&mut Self>,
|
mut self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
buf: &mut ReadBuf<'_>,
|
buf: &mut [u8],
|
||||||
) -> Poll<io::Result<()>> {
|
) -> Poll<io::Result<usize>> {
|
||||||
Pin::new(&mut self.io.as_mut().unwrap()).poll_read(cx, buf)
|
Pin::new(&mut self.io.as_mut().unwrap()).poll_read(cx, buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ where
|
|||||||
CONNECTION | TRANSFER_ENCODING => continue, // http2 specific
|
CONNECTION | TRANSFER_ENCODING => continue, // http2 specific
|
||||||
CONTENT_LENGTH if skip_len => continue,
|
CONTENT_LENGTH if skip_len => continue,
|
||||||
// DATE => has_date = true,
|
// DATE => has_date = true,
|
||||||
_ => {}
|
_ => (),
|
||||||
}
|
}
|
||||||
req.headers_mut().append(key, value.clone());
|
req.headers_mut().append(key, value.clone());
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,11 @@ use std::rc::Rc;
|
|||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
use actix_codec::{AsyncRead, AsyncWrite};
|
||||||
use actix_rt::time::{sleep, Sleep};
|
use actix_rt::time::{delay_for, Delay};
|
||||||
use actix_service::Service;
|
use actix_service::Service;
|
||||||
use actix_utils::task::LocalWaker;
|
use actix_utils::{oneshot, task::LocalWaker};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_channel::oneshot;
|
|
||||||
use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture};
|
use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture};
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use h2::client::{Connection, SendRequest};
|
use h2::client::{Connection, SendRequest};
|
||||||
@ -50,7 +49,8 @@ pub(crate) struct ConnectionPool<T, Io: 'static>(Rc<RefCell<T>>, Rc<RefCell<Inne
|
|||||||
impl<T, Io> ConnectionPool<T, Io>
|
impl<T, Io> ConnectionPool<T, Io>
|
||||||
where
|
where
|
||||||
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
|
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
|
||||||
|
+ 'static,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self {
|
pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self {
|
||||||
let connector_rc = Rc::new(RefCell::new(connector));
|
let connector_rc = Rc::new(RefCell::new(connector));
|
||||||
@ -89,11 +89,13 @@ impl<T, Io> Drop for ConnectionPool<T, Io> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, Io> Service<Connect> for ConnectionPool<T, Io>
|
impl<T, Io> Service for ConnectionPool<T, Io>
|
||||||
where
|
where
|
||||||
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
|
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
|
||||||
|
+ 'static,
|
||||||
{
|
{
|
||||||
|
type Request = Connect;
|
||||||
type Response = IoConnection<Io>;
|
type Response = IoConnection<Io>;
|
||||||
type Error = ConnectError;
|
type Error = ConnectError;
|
||||||
type Future = LocalBoxFuture<'static, Result<IoConnection<Io>, ConnectError>>;
|
type Future = LocalBoxFuture<'static, Result<IoConnection<Io>, ConnectError>>;
|
||||||
@ -331,11 +333,10 @@ where
|
|||||||
} else {
|
} else {
|
||||||
let mut io = conn.io;
|
let mut io = conn.io;
|
||||||
let mut buf = [0; 2];
|
let mut buf = [0; 2];
|
||||||
let mut read_buf = ReadBuf::new(&mut buf);
|
|
||||||
if let ConnectionType::H1(ref mut s) = io {
|
if let ConnectionType::H1(ref mut s) = io {
|
||||||
match Pin::new(s).poll_read(cx, &mut read_buf) {
|
match Pin::new(s).poll_read(cx, &mut buf) {
|
||||||
Poll::Pending => {}
|
Poll::Pending => (),
|
||||||
Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => {
|
Poll::Ready(Ok(n)) if n > 0 => {
|
||||||
if let Some(timeout) = self.config.disconnect_timeout {
|
if let Some(timeout) = self.config.disconnect_timeout {
|
||||||
if let ConnectionType::H1(io) = io {
|
if let ConnectionType::H1(io) = io {
|
||||||
actix_rt::spawn(CloseConnection::new(
|
actix_rt::spawn(CloseConnection::new(
|
||||||
@ -385,11 +386,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project::pin_project]
|
|
||||||
struct CloseConnection<T> {
|
struct CloseConnection<T> {
|
||||||
io: T,
|
io: T,
|
||||||
#[pin]
|
timeout: Delay,
|
||||||
timeout: Sleep,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> CloseConnection<T>
|
impl<T> CloseConnection<T>
|
||||||
@ -399,7 +398,7 @@ where
|
|||||||
fn new(io: T, timeout: Duration) -> Self {
|
fn new(io: T, timeout: Duration) -> Self {
|
||||||
CloseConnection {
|
CloseConnection {
|
||||||
io,
|
io,
|
||||||
timeout: sleep(timeout),
|
timeout: delay_for(timeout),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -411,11 +410,11 @@ where
|
|||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||||
let this = self.project();
|
let this = self.get_mut();
|
||||||
|
|
||||||
match this.timeout.poll(cx) {
|
match Pin::new(&mut this.timeout).poll(cx) {
|
||||||
Poll::Ready(_) => Poll::Ready(()),
|
Poll::Ready(_) => Poll::Ready(()),
|
||||||
Poll::Pending => match Pin::new(this.io).poll_shutdown(cx) {
|
Poll::Pending => match Pin::new(&mut this.io).poll_shutdown(cx) {
|
||||||
Poll::Ready(_) => Poll::Ready(()),
|
Poll::Ready(_) => Poll::Ready(()),
|
||||||
Poll::Pending => Poll::Pending,
|
Poll::Pending => Poll::Pending,
|
||||||
},
|
},
|
||||||
@ -435,7 +434,7 @@ where
|
|||||||
impl<T, Io> Future for ConnectorPoolSupport<T, Io>
|
impl<T, Io> Future for ConnectorPoolSupport<T, Io>
|
||||||
where
|
where
|
||||||
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError>,
|
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>,
|
||||||
T::Future: 'static,
|
T::Future: 'static,
|
||||||
{
|
{
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
40
actix-http/src/cloneable.rs
Normal file
40
actix-http/src/cloneable.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use actix_service::Service;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
/// Service that allows to turn non-clone service to a service with `Clone` impl
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// CloneableService might panic with some creative use of thread local storage.
|
||||||
|
/// See https://github.com/actix/actix-web/issues/1295 for example
|
||||||
|
pub(crate) struct CloneableService<T: Service>(Rc<RefCell<T>>);
|
||||||
|
|
||||||
|
impl<T: Service> CloneableService<T> {
|
||||||
|
pub(crate) fn new(service: T) -> Self {
|
||||||
|
Self(Rc::new(RefCell::new(service)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Service> Clone for CloneableService<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Service> Service for CloneableService<T> {
|
||||||
|
type Request = T::Request;
|
||||||
|
type Response = T::Response;
|
||||||
|
type Error = T::Error;
|
||||||
|
type Future = T::Future;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.0.borrow_mut().poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: T::Request) -> Self::Future {
|
||||||
|
self.0.borrow_mut().call(req)
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ 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::time::{delay_for, delay_until, Delay, Instant};
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use futures_util::{future, FutureExt};
|
use futures_util::{future, FutureExt};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
@ -121,10 +121,10 @@ impl ServiceConfig {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Client timeout for first request.
|
/// Client timeout for first request.
|
||||||
pub fn client_timer(&self) -> Option<Sleep> {
|
pub fn client_timer(&self) -> Option<Delay> {
|
||||||
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(delay_until(
|
||||||
self.0.timer.now() + Duration::from_millis(delay_time),
|
self.0.timer.now() + Duration::from_millis(delay_time),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
@ -154,9 +154,9 @@ impl ServiceConfig {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// 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<Delay> {
|
||||||
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(delay_until(self.0.timer.now() + ka))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -266,7 +266,7 @@ impl DateService {
|
|||||||
|
|
||||||
// periodic date update
|
// periodic date update
|
||||||
let s = self.clone();
|
let s = self.clone();
|
||||||
actix_rt::spawn(sleep(Duration::from_millis(500)).then(move |_| {
|
actix_rt::spawn(delay_for(Duration::from_millis(500)).then(move |_| {
|
||||||
s.0.reset();
|
s.0.reset();
|
||||||
future::ready(())
|
future::ready(())
|
||||||
}));
|
}));
|
||||||
|
@ -4,7 +4,7 @@ use std::pin::Pin;
|
|||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use actix_threadpool::{run, CpuFuture};
|
use actix_threadpool::{run, CpuFuture};
|
||||||
use brotli2::write::BrotliDecoder;
|
use brotli::DecompressorWriter as BrotliDecoder;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flate2::write::{GzDecoder, ZlibDecoder};
|
use flate2::write::{GzDecoder, ZlibDecoder};
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
@ -31,7 +31,7 @@ where
|
|||||||
pub fn new(stream: S, encoding: ContentEncoding) -> Decoder<S> {
|
pub fn new(stream: S, encoding: ContentEncoding) -> Decoder<S> {
|
||||||
let decoder = match encoding {
|
let decoder = match encoding {
|
||||||
ContentEncoding::Br => Some(ContentDecoder::Br(Box::new(
|
ContentEncoding::Br => Some(ContentDecoder::Br(Box::new(
|
||||||
BrotliDecoder::new(Writer::new()),
|
BrotliDecoder::new(Writer::new(), 8 * 1024),
|
||||||
))),
|
))),
|
||||||
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
||||||
ZlibDecoder::new(Writer::new()),
|
ZlibDecoder::new(Writer::new()),
|
||||||
|
@ -5,7 +5,7 @@ use std::pin::Pin;
|
|||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use actix_threadpool::{run, CpuFuture};
|
use actix_threadpool::{run, CpuFuture};
|
||||||
use brotli2::write::BrotliEncoder;
|
use brotli::CompressorWriter as BrotliEncoder;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
@ -212,9 +212,12 @@ impl ContentEncoder {
|
|||||||
Writer::new(),
|
Writer::new(),
|
||||||
flate2::Compression::fast(),
|
flate2::Compression::fast(),
|
||||||
))),
|
))),
|
||||||
ContentEncoding::Br => {
|
ContentEncoding::Br => Some(ContentEncoder::Br(BrotliEncoder::new(
|
||||||
Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3)))
|
Writer::new(),
|
||||||
}
|
32 * 1024,
|
||||||
|
3,
|
||||||
|
22,
|
||||||
|
))),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -230,8 +233,8 @@ impl ContentEncoder {
|
|||||||
|
|
||||||
fn finish(self) -> Result<Bytes, io::Error> {
|
fn finish(self) -> Result<Bytes, io::Error> {
|
||||||
match self {
|
match self {
|
||||||
ContentEncoder::Br(encoder) => match encoder.finish() {
|
ContentEncoder::Br(mut encoder) => match encoder.flush() {
|
||||||
Ok(writer) => Ok(writer.buf.freeze()),
|
Ok(()) => Ok(encoder.into_inner().buf.freeze()),
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
},
|
},
|
||||||
ContentEncoder::Gzip(encoder) => match encoder.finish() {
|
ContentEncoder::Gzip(encoder) => match encoder.finish() {
|
||||||
|
@ -25,7 +25,7 @@ pub use crate::cookie::ParseError as CookieParseError;
|
|||||||
use crate::helpers::Writer;
|
use crate::helpers::Writer;
|
||||||
use crate::response::{Response, ResponseBuilder};
|
use crate::response::{Response, ResponseBuilder};
|
||||||
|
|
||||||
/// A specialized [`std::result::Result`]
|
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
|
||||||
/// for actix web operations
|
/// for actix web operations
|
||||||
///
|
///
|
||||||
/// This typedef is generally used to avoid writing out
|
/// This typedef is generally used to avoid writing out
|
||||||
@ -55,7 +55,7 @@ impl Error {
|
|||||||
|
|
||||||
/// Similar to `as_response_error` but downcasts.
|
/// Similar to `as_response_error` but downcasts.
|
||||||
pub fn as_error<T: ResponseError + 'static>(&self) -> Option<&T> {
|
pub fn as_error<T: ResponseError + 'static>(&self) -> Option<&T> {
|
||||||
ResponseError::downcast_ref(self.cause.as_ref())
|
<dyn ResponseError>::downcast_ref(self.cause.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +178,11 @@ impl ResponseError for FormError {}
|
|||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
/// `InternalServerError` for `openssl::ssl::Error`
|
/// `InternalServerError` for `openssl::ssl::Error`
|
||||||
impl ResponseError for actix_tls::accept::openssl::SslError {}
|
impl ResponseError for actix_connect::ssl::openssl::SslError {}
|
||||||
|
|
||||||
|
#[cfg(feature = "openssl")]
|
||||||
|
/// `InternalServerError` for `openssl::ssl::HandshakeError`
|
||||||
|
impl<T: std::fmt::Debug> ResponseError for actix_tls::openssl::HandshakeError<T> {}
|
||||||
|
|
||||||
/// Return `BAD_REQUEST` for `de::value::Error`
|
/// Return `BAD_REQUEST` for `de::value::Error`
|
||||||
impl ResponseError for DeError {
|
impl ResponseError for DeError {
|
||||||
@ -952,6 +956,11 @@ where
|
|||||||
/// This is supported on feature=`actors` only
|
/// This is supported on feature=`actors` only
|
||||||
impl ResponseError for actix::MailboxError {}
|
impl ResponseError for actix::MailboxError {}
|
||||||
|
|
||||||
|
#[cfg(feature = "actors")]
|
||||||
|
/// `InternalServerError` for `actix::ResolverError`
|
||||||
|
/// This is supported on feature=`actors` only
|
||||||
|
impl ResponseError for actix::actors::resolver::ResolverError {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -3,8 +3,8 @@ use std::{fmt, mem};
|
|||||||
|
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
|
|
||||||
/// A type map of request extensions.
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
/// A type map of request extensions.
|
||||||
pub struct Extensions {
|
pub struct Extensions {
|
||||||
/// Use FxHasher with a std HashMap with for faster
|
/// Use FxHasher with a std HashMap with for faster
|
||||||
/// lookups on the small `TypeId` (u64 equivalent) keys.
|
/// lookups on the small `TypeId` (u64 equivalent) keys.
|
||||||
|
@ -58,7 +58,6 @@ impl Codec {
|
|||||||
} else {
|
} else {
|
||||||
Flags::empty()
|
Flags::empty()
|
||||||
};
|
};
|
||||||
|
|
||||||
Codec {
|
Codec {
|
||||||
config,
|
config,
|
||||||
flags,
|
flags,
|
||||||
@ -70,26 +69,26 @@ impl Codec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if request is upgrade.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
/// Check if request is upgrade
|
||||||
pub fn upgrade(&self) -> bool {
|
pub fn upgrade(&self) -> bool {
|
||||||
self.ctype == ConnectionType::Upgrade
|
self.ctype == ConnectionType::Upgrade
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if last response is keep-alive.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
/// Check if last response is keep-alive
|
||||||
pub fn keepalive(&self) -> bool {
|
pub fn keepalive(&self) -> bool {
|
||||||
self.ctype == ConnectionType::KeepAlive
|
self.ctype == ConnectionType::KeepAlive
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if keep-alive enabled on server level.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
/// Check if keep-alive enabled on server level
|
||||||
pub fn keepalive_enabled(&self) -> bool {
|
pub fn keepalive_enabled(&self) -> bool {
|
||||||
self.flags.contains(Flags::KEEPALIVE_ENABLED)
|
self.flags.contains(Flags::KEEPALIVE_ENABLED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check last request's message type.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
/// Check last request's message type
|
||||||
pub fn message_type(&self) -> MessageType {
|
pub fn message_type(&self) -> MessageType {
|
||||||
if self.flags.contains(Flags::STREAM) {
|
if self.flags.contains(Flags::STREAM) {
|
||||||
MessageType::Stream
|
MessageType::Stream
|
||||||
@ -111,8 +110,8 @@ impl Decoder for Codec {
|
|||||||
type Error = ParseError;
|
type Error = ParseError;
|
||||||
|
|
||||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||||
if let Some(ref mut payload) = self.payload {
|
if self.payload.is_some() {
|
||||||
Ok(match payload.decode(src)? {
|
Ok(match self.payload.as_mut().unwrap().decode(src)? {
|
||||||
Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))),
|
Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))),
|
||||||
Some(PayloadItem::Eof) => {
|
Some(PayloadItem::Eof) => {
|
||||||
self.payload.take();
|
self.payload.take();
|
||||||
|
@ -67,6 +67,7 @@ pub(crate) trait MessageType: Sized {
|
|||||||
let mut has_upgrade_websocket = false;
|
let mut has_upgrade_websocket = false;
|
||||||
let mut expect = false;
|
let mut expect = false;
|
||||||
let mut chunked = false;
|
let mut chunked = false;
|
||||||
|
let mut seen_te = false;
|
||||||
let mut content_length = None;
|
let mut content_length = None;
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -85,8 +86,17 @@ pub(crate) trait MessageType: Sized {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match name {
|
match name {
|
||||||
header::CONTENT_LENGTH => {
|
header::CONTENT_LENGTH if content_length.is_some() => {
|
||||||
if let Ok(s) = value.to_str() {
|
debug!("multiple Content-Length");
|
||||||
|
return Err(ParseError::Header);
|
||||||
|
}
|
||||||
|
|
||||||
|
header::CONTENT_LENGTH => match value.to_str() {
|
||||||
|
Ok(s) if s.trim().starts_with("+") => {
|
||||||
|
debug!("illegal Content-Length: {:?}", s);
|
||||||
|
return Err(ParseError::Header);
|
||||||
|
}
|
||||||
|
Ok(s) => {
|
||||||
if let Ok(len) = s.parse::<u64>() {
|
if let Ok(len) = s.parse::<u64>() {
|
||||||
if len != 0 {
|
if len != 0 {
|
||||||
content_length = Some(len);
|
content_length = Some(len);
|
||||||
@ -95,15 +105,31 @@ pub(crate) trait MessageType: Sized {
|
|||||||
debug!("illegal Content-Length: {:?}", s);
|
debug!("illegal Content-Length: {:?}", s);
|
||||||
return Err(ParseError::Header);
|
return Err(ParseError::Header);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
Err(_) => {
|
||||||
debug!("illegal Content-Length: {:?}", value);
|
debug!("illegal Content-Length: {:?}", value);
|
||||||
return Err(ParseError::Header);
|
return Err(ParseError::Header);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
// transfer-encoding
|
// transfer-encoding
|
||||||
|
header::TRANSFER_ENCODING if seen_te => {
|
||||||
|
debug!("multiple Transfer-Encoding not allowed");
|
||||||
|
return Err(ParseError::Header);
|
||||||
|
}
|
||||||
|
|
||||||
header::TRANSFER_ENCODING => {
|
header::TRANSFER_ENCODING => {
|
||||||
|
seen_te = true;
|
||||||
|
|
||||||
if let Ok(s) = value.to_str().map(|s| s.trim()) {
|
if let Ok(s) = value.to_str().map(|s| s.trim()) {
|
||||||
chunked = s.eq_ignore_ascii_case("chunked");
|
if s.eq_ignore_ascii_case("chunked") {
|
||||||
|
chunked = true;
|
||||||
|
} else if s.eq_ignore_ascii_case("identity") {
|
||||||
|
// allow silently since multiple TE headers are already checked
|
||||||
|
} else {
|
||||||
|
debug!("illegal Transfer-Encoding: {:?}", s);
|
||||||
|
return Err(ParseError::Header);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(ParseError::Header);
|
return Err(ParseError::Header);
|
||||||
}
|
}
|
||||||
@ -137,7 +163,7 @@ pub(crate) trait MessageType: Sized {
|
|||||||
expect = true;
|
expect = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
headers.append(name, value);
|
headers.append(name, value);
|
||||||
@ -510,19 +536,11 @@ impl ChunkedState {
|
|||||||
size: &mut u64,
|
size: &mut u64,
|
||||||
) -> Poll<Result<ChunkedState, io::Error>> {
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
let radix = 16;
|
let radix = 16;
|
||||||
match byte!(rdr) {
|
|
||||||
b @ b'0'..=b'9' => {
|
let rem = match byte!(rdr) {
|
||||||
*size *= radix;
|
b @ b'0'..=b'9' => b - b'0',
|
||||||
*size += u64::from(b - b'0');
|
b @ b'a'..=b'f' => b + 10 - b'a',
|
||||||
}
|
b @ b'A'..=b'F' => b + 10 - b'A',
|
||||||
b @ b'a'..=b'f' => {
|
|
||||||
*size *= radix;
|
|
||||||
*size += u64::from(b + 10 - b'a');
|
|
||||||
}
|
|
||||||
b @ b'A'..=b'F' => {
|
|
||||||
*size *= radix;
|
|
||||||
*size += u64::from(b + 10 - b'A');
|
|
||||||
}
|
|
||||||
b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)),
|
b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)),
|
||||||
b';' => return Poll::Ready(Ok(ChunkedState::Extension)),
|
b';' => return Poll::Ready(Ok(ChunkedState::Extension)),
|
||||||
b'\r' => return Poll::Ready(Ok(ChunkedState::SizeLf)),
|
b'\r' => return Poll::Ready(Ok(ChunkedState::SizeLf)),
|
||||||
@ -532,8 +550,23 @@ impl ChunkedState {
|
|||||||
"Invalid chunk size line: Invalid Size",
|
"Invalid chunk size line: Invalid Size",
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match size.checked_mul(radix) {
|
||||||
|
Some(n) => {
|
||||||
|
*size = n as u64;
|
||||||
|
*size += rem as u64;
|
||||||
|
|
||||||
|
Poll::Ready(Ok(ChunkedState::Size))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
debug!("chunk size would overflow");
|
||||||
|
Poll::Ready(Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid chunk size line: Invalid Size",
|
||||||
|
)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Poll::Ready(Ok(ChunkedState::Size))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_size_lws(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
|
fn read_size_lws(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
@ -552,6 +585,11 @@ impl ChunkedState {
|
|||||||
fn read_extension(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
|
fn read_extension(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
match byte!(rdr) {
|
match byte!(rdr) {
|
||||||
b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
|
b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
|
||||||
|
// strictly 0x20 (space) should be disallowed but we don't parse quoted strings here
|
||||||
|
0x00..=0x08 | 0x0a..=0x1f | 0x7f => Poll::Ready(Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid character in chunk extension",
|
||||||
|
))),
|
||||||
_ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions
|
_ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -685,7 +723,7 @@ mod tests {
|
|||||||
match MessageDecoder::<Request>::default().decode($e) {
|
match MessageDecoder::<Request>::default().decode($e) {
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
ParseError::Io(_) => unreachable!("Parse error expected"),
|
ParseError::Io(_) => unreachable!("Parse error expected"),
|
||||||
_ => {}
|
_ => (),
|
||||||
},
|
},
|
||||||
_ => unreachable!("Error expected"),
|
_ => unreachable!("Error expected"),
|
||||||
}
|
}
|
||||||
@ -977,13 +1015,7 @@ mod tests {
|
|||||||
"GET /test HTTP/1.1\r\n\
|
"GET /test HTTP/1.1\r\n\
|
||||||
transfer-encoding: chnked\r\n\r\n",
|
transfer-encoding: chnked\r\n\r\n",
|
||||||
);
|
);
|
||||||
let req = parse_ready!(&mut buf);
|
expect_parse_err!(&mut buf);
|
||||||
|
|
||||||
if let Ok(val) = req.chunked() {
|
|
||||||
assert!(!val);
|
|
||||||
} else {
|
|
||||||
unreachable!("Error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -21,7 +21,7 @@ const AVERAGE_HEADER_SIZE: usize = 30;
|
|||||||
pub(crate) struct MessageEncoder<T: MessageType> {
|
pub(crate) struct MessageEncoder<T: MessageType> {
|
||||||
pub length: BodySize,
|
pub length: BodySize,
|
||||||
pub te: TransferEncoding,
|
pub te: TransferEncoding,
|
||||||
_phantom: PhantomData<T>,
|
_t: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: MessageType> Default for MessageEncoder<T> {
|
impl<T: MessageType> Default for MessageEncoder<T> {
|
||||||
@ -29,7 +29,7 @@ impl<T: MessageType> Default for MessageEncoder<T> {
|
|||||||
MessageEncoder {
|
MessageEncoder {
|
||||||
length: BodySize::None,
|
length: BodySize::None,
|
||||||
te: TransferEncoding::empty(),
|
te: TransferEncoding::empty(),
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,6 +80,7 @@ pub(crate) trait MessageType: Sized {
|
|||||||
match length {
|
match length {
|
||||||
BodySize::Stream => {
|
BodySize::Stream => {
|
||||||
if chunked {
|
if chunked {
|
||||||
|
skip_len = true;
|
||||||
if camel_case {
|
if camel_case {
|
||||||
dst.put_slice(b"\r\nTransfer-Encoding: chunked\r\n")
|
dst.put_slice(b"\r\nTransfer-Encoding: chunked\r\n")
|
||||||
} else {
|
} else {
|
||||||
@ -118,7 +119,7 @@ pub(crate) trait MessageType: Sized {
|
|||||||
dst.put_slice(b"connection: close\r\n")
|
dst.put_slice(b"connection: close\r\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
// merging headers from head and extra headers. HeaderMap::new() does not allocate.
|
// merging headers from head and extra headers. HeaderMap::new() does not allocate.
|
||||||
@ -135,7 +136,7 @@ pub(crate) trait MessageType: Sized {
|
|||||||
|
|
||||||
let mut has_date = false;
|
let mut has_date = false;
|
||||||
|
|
||||||
let mut buf = dst.chunk_mut().as_mut_ptr() as *mut u8;
|
let mut buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
|
||||||
let mut remaining = dst.capacity() - dst.len();
|
let mut remaining = dst.capacity() - dst.len();
|
||||||
|
|
||||||
// tracks bytes written since last buffer resize
|
// tracks bytes written since last buffer resize
|
||||||
@ -148,7 +149,7 @@ pub(crate) trait MessageType: Sized {
|
|||||||
CONNECTION => continue,
|
CONNECTION => continue,
|
||||||
TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue,
|
TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue,
|
||||||
DATE => has_date = true,
|
DATE => has_date = true,
|
||||||
_ => {}
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let k = key.as_str().as_bytes();
|
let k = key.as_str().as_bytes();
|
||||||
@ -177,7 +178,7 @@ pub(crate) trait MessageType: Sized {
|
|||||||
|
|
||||||
// re-assign buf raw pointer since it's possible that the buffer was
|
// re-assign buf raw pointer since it's possible that the buffer was
|
||||||
// reallocated and/or resized
|
// reallocated and/or resized
|
||||||
buf = dst.chunk_mut().as_mut_ptr() as *mut u8;
|
buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: on each write, it is enough to ensure that the advancement of the
|
// SAFETY: on each write, it is enough to ensure that the advancement of the
|
||||||
@ -224,7 +225,7 @@ pub(crate) trait MessageType: Sized {
|
|||||||
|
|
||||||
// re-assign buf raw pointer since it's possible that the buffer was
|
// re-assign buf raw pointer since it's possible that the buffer was
|
||||||
// reallocated and/or resized
|
// reallocated and/or resized
|
||||||
buf = dst.chunk_mut().as_mut_ptr() as *mut u8;
|
buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: on each write, it is enough to ensure that the advancement of
|
// SAFETY: on each write, it is enough to ensure that the advancement of
|
||||||
@ -532,29 +533,30 @@ unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
|
fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
|
||||||
// first copy entire (potentially wrong) slice to output
|
let mut index = 0;
|
||||||
buffer[..value.len()].copy_from_slice(value);
|
let key = value;
|
||||||
|
let mut key_iter = key.iter();
|
||||||
|
|
||||||
let mut iter = value.iter();
|
if let Some(c) = key_iter.next() {
|
||||||
|
if *c >= b'a' && *c <= b'z' {
|
||||||
// first character should be uppercase
|
buffer[index] = *c ^ b' ';
|
||||||
if let Some(c @ b'a'..=b'z') = iter.next() {
|
index += 1;
|
||||||
buffer[0] = c & 0b1101_1111;
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// track 1 ahead of the current position since that's the location being assigned to
|
while let Some(c) = key_iter.next() {
|
||||||
let mut index = 2;
|
buffer[index] = *c;
|
||||||
|
index += 1;
|
||||||
// remaining characters after hyphens should also be uppercase
|
if *c == b'-' {
|
||||||
while let Some(&c) = iter.next() {
|
if let Some(c) = key_iter.next() {
|
||||||
if c == b'-' {
|
if *c >= b'a' && *c <= b'z' {
|
||||||
// advance iter by one and uppercase if needed
|
buffer[index] = *c ^ b' ';
|
||||||
if let Some(c @ b'a'..=b'z') = iter.next() {
|
index += 1;
|
||||||
buffer[index] = c & 0b1101_1111;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
index += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -603,8 +605,6 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let data =
|
let data =
|
||||||
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||||
eprintln!("{}", &data);
|
|
||||||
|
|
||||||
assert!(data.contains("Content-Length: 0\r\n"));
|
assert!(data.contains("Content-Length: 0\r\n"));
|
||||||
assert!(data.contains("Connection: close\r\n"));
|
assert!(data.contains("Connection: close\r\n"));
|
||||||
assert!(data.contains("Content-Type: plain/text\r\n"));
|
assert!(data.contains("Content-Type: plain/text\r\n"));
|
||||||
|
@ -1,27 +1,29 @@
|
|||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use actix_service::{Service, ServiceFactory};
|
use actix_service::{Service, ServiceFactory};
|
||||||
use futures_util::future::{ready, Ready};
|
use futures_util::future::{ok, Ready};
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
|
|
||||||
pub struct ExpectHandler;
|
pub struct ExpectHandler;
|
||||||
|
|
||||||
impl ServiceFactory<Request> for ExpectHandler {
|
impl ServiceFactory for ExpectHandler {
|
||||||
|
type Config = ();
|
||||||
|
type Request = Request;
|
||||||
type Response = Request;
|
type Response = Request;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Config = ();
|
|
||||||
type Service = ExpectHandler;
|
type Service = ExpectHandler;
|
||||||
type InitError = Error;
|
type InitError = Error;
|
||||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
fn new_service(&self, _: Self::Config) -> Self::Future {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
ready(Ok(ExpectHandler))
|
ok(ExpectHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<Request> for ExpectHandler {
|
impl Service for ExpectHandler {
|
||||||
|
type Request = Request;
|
||||||
type Response = Request;
|
type Response = Request;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Self::Response, Self::Error>>;
|
type Future = Ready<Result<Self::Response, Self::Error>>;
|
||||||
@ -31,8 +33,6 @@ impl Service<Request> for ExpectHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: Request) -> Self::Future {
|
fn call(&mut self, req: Request) -> Self::Future {
|
||||||
ready(Ok(req))
|
ok(req)
|
||||||
// TODO: add some way to trigger error
|
|
||||||
// Err(error::ErrorExpectationFailed("test"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ pub use self::codec::Codec;
|
|||||||
pub use self::dispatcher::Dispatcher;
|
pub use self::dispatcher::Dispatcher;
|
||||||
pub use self::expect::ExpectHandler;
|
pub use self::expect::ExpectHandler;
|
||||||
pub use self::payload::Payload;
|
pub use self::payload::Payload;
|
||||||
pub use self::service::{H1Service, H1ServiceHandler};
|
pub use self::service::{H1Service, H1ServiceHandler, OneRequest};
|
||||||
pub use self::upgrade::UpgradeHandler;
|
pub use self::upgrade::UpgradeHandler;
|
||||||
pub use self::utils::SendResponse;
|
pub use self::utils::SendResponse;
|
||||||
|
|
||||||
|
@ -182,7 +182,9 @@ impl Inner {
|
|||||||
self.len += data.len();
|
self.len += data.len();
|
||||||
self.items.push_back(data);
|
self.items.push_back(data);
|
||||||
self.need_read = self.len < MAX_BUFFER_SIZE;
|
self.need_read = self.len < MAX_BUFFER_SIZE;
|
||||||
self.task.wake();
|
if let Some(task) = self.task.take() {
|
||||||
|
task.wake()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
@ -10,40 +9,42 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
|||||||
use actix_rt::net::TcpStream;
|
use actix_rt::net::TcpStream;
|
||||||
use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
|
use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
|
||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use futures_util::future::ready;
|
use futures_util::future::{ok, Ready};
|
||||||
|
|
||||||
use crate::body::MessageBody;
|
use crate::body::MessageBody;
|
||||||
|
use crate::cloneable::CloneableService;
|
||||||
use crate::config::ServiceConfig;
|
use crate::config::ServiceConfig;
|
||||||
use crate::error::{DispatchError, Error};
|
use crate::error::{DispatchError, Error, ParseError};
|
||||||
|
use crate::helpers::DataFactory;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::service::HttpFlow;
|
use crate::{ConnectCallback, Extensions};
|
||||||
use crate::{ConnectCallback, OnConnectData};
|
|
||||||
|
|
||||||
use super::codec::Codec;
|
use super::codec::Codec;
|
||||||
use super::dispatcher::Dispatcher;
|
use super::dispatcher::Dispatcher;
|
||||||
use super::{ExpectHandler, UpgradeHandler};
|
use super::{ExpectHandler, Message, UpgradeHandler};
|
||||||
|
|
||||||
/// `ServiceFactory` implementation for HTTP1 transport
|
/// `ServiceFactory` implementation for HTTP1 transport
|
||||||
pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> {
|
pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler<T>> {
|
||||||
srv: S,
|
srv: S,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
expect: X,
|
expect: X,
|
||||||
upgrade: Option<U>,
|
upgrade: Option<U>,
|
||||||
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_phantom: PhantomData<B>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> H1Service<T, S, B>
|
impl<T, S, B> H1Service<T, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
/// Create new `HttpService` instance with config.
|
/// Create new `HttpService` instance with config.
|
||||||
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
|
pub(crate) fn with_config<F: IntoServiceFactory<S>>(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
service: F,
|
service: F,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -52,23 +53,28 @@ where
|
|||||||
srv: service.into_factory(),
|
srv: service.into_factory(),
|
||||||
expect: ExpectHandler,
|
expect: ExpectHandler,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
|
on_connect: None,
|
||||||
on_connect_ext: None,
|
on_connect_ext: None,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, B, X, U> H1Service<TcpStream, S, B, X, U>
|
impl<S, B, X, U> H1Service<TcpStream, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
U: ServiceFactory<(Request, Framed<TcpStream, Codec>), Config = (), Response = ()>,
|
U: ServiceFactory<
|
||||||
|
Config = (),
|
||||||
|
Request = (Request, Framed<TcpStream, Codec>),
|
||||||
|
Response = (),
|
||||||
|
>,
|
||||||
U::Error: fmt::Display + Into<Error>,
|
U::Error: fmt::Display + Into<Error>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
@ -76,15 +82,15 @@ where
|
|||||||
pub fn tcp(
|
pub fn tcp(
|
||||||
self,
|
self,
|
||||||
) -> impl ServiceFactory<
|
) -> impl ServiceFactory<
|
||||||
TcpStream,
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = TcpStream,
|
||||||
Response = (),
|
Response = (),
|
||||||
Error = DispatchError,
|
Error = DispatchError,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
> {
|
> {
|
||||||
pipeline_factory(|io: TcpStream| {
|
pipeline_factory(|io: TcpStream| {
|
||||||
let peer_addr = io.peer_addr().ok();
|
let peer_addr = io.peer_addr().ok();
|
||||||
ready(Ok((io, peer_addr)))
|
ok((io, peer_addr))
|
||||||
})
|
})
|
||||||
.and_then(self)
|
.and_then(self)
|
||||||
}
|
}
|
||||||
@ -94,23 +100,22 @@ where
|
|||||||
mod openssl {
|
mod openssl {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use actix_service::ServiceFactoryExt;
|
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
|
||||||
use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream};
|
use actix_tls::{openssl::HandshakeError, TlsError};
|
||||||
use actix_tls::accept::TlsError;
|
|
||||||
|
|
||||||
impl<S, B, X, U> H1Service<SslStream<TcpStream>, S, B, X, U>
|
impl<S, B, X, U> H1Service<SslStream<TcpStream>, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<
|
||||||
(Request, Framed<SslStream<TcpStream>, Codec>),
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = (Request, Framed<SslStream<TcpStream>, Codec>),
|
||||||
Response = (),
|
Response = (),
|
||||||
>,
|
>,
|
||||||
U::Error: fmt::Display + Into<Error>,
|
U::Error: fmt::Display + Into<Error>,
|
||||||
@ -121,10 +126,10 @@ mod openssl {
|
|||||||
self,
|
self,
|
||||||
acceptor: SslAcceptor,
|
acceptor: SslAcceptor,
|
||||||
) -> impl ServiceFactory<
|
) -> impl ServiceFactory<
|
||||||
TcpStream,
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = TcpStream,
|
||||||
Response = (),
|
Response = (),
|
||||||
Error = TlsError<SslError, DispatchError>,
|
Error = TlsError<HandshakeError<TcpStream>, DispatchError>,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
> {
|
> {
|
||||||
pipeline_factory(
|
pipeline_factory(
|
||||||
@ -134,7 +139,7 @@ mod openssl {
|
|||||||
)
|
)
|
||||||
.and_then(|io: SslStream<TcpStream>| {
|
.and_then(|io: SslStream<TcpStream>| {
|
||||||
let peer_addr = io.get_ref().peer_addr().ok();
|
let peer_addr = io.get_ref().peer_addr().ok();
|
||||||
ready(Ok((io, peer_addr)))
|
ok((io, peer_addr))
|
||||||
})
|
})
|
||||||
.and_then(self.map_err(TlsError::Service))
|
.and_then(self.map_err(TlsError::Service))
|
||||||
}
|
}
|
||||||
@ -144,24 +149,23 @@ mod openssl {
|
|||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
mod rustls {
|
mod rustls {
|
||||||
use super::*;
|
use super::*;
|
||||||
use actix_service::ServiceFactoryExt;
|
use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream};
|
||||||
use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream};
|
use actix_tls::TlsError;
|
||||||
use actix_tls::accept::TlsError;
|
|
||||||
use std::{fmt, io};
|
use std::{fmt, io};
|
||||||
|
|
||||||
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
|
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<
|
||||||
(Request, Framed<TlsStream<TcpStream>, Codec>),
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = (Request, Framed<TlsStream<TcpStream>, Codec>),
|
||||||
Response = (),
|
Response = (),
|
||||||
>,
|
>,
|
||||||
U::Error: fmt::Display + Into<Error>,
|
U::Error: fmt::Display + Into<Error>,
|
||||||
@ -172,8 +176,8 @@ mod rustls {
|
|||||||
self,
|
self,
|
||||||
config: ServerConfig,
|
config: ServerConfig,
|
||||||
) -> impl ServiceFactory<
|
) -> impl ServiceFactory<
|
||||||
TcpStream,
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = TcpStream,
|
||||||
Response = (),
|
Response = (),
|
||||||
Error = TlsError<io::Error, DispatchError>,
|
Error = TlsError<io::Error, DispatchError>,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
@ -185,7 +189,7 @@ mod rustls {
|
|||||||
)
|
)
|
||||||
.and_then(|io: TlsStream<TcpStream>| {
|
.and_then(|io: TlsStream<TcpStream>| {
|
||||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||||
ready(Ok((io, peer_addr)))
|
ok((io, peer_addr))
|
||||||
})
|
})
|
||||||
.and_then(self.map_err(TlsError::Service))
|
.and_then(self.map_err(TlsError::Service))
|
||||||
}
|
}
|
||||||
@ -194,7 +198,7 @@ mod rustls {
|
|||||||
|
|
||||||
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
|
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
@ -202,7 +206,7 @@ where
|
|||||||
{
|
{
|
||||||
pub fn expect<X1>(self, expect: X1) -> H1Service<T, S, B, X1, U>
|
pub fn expect<X1>(self, expect: X1) -> H1Service<T, S, B, X1, U>
|
||||||
where
|
where
|
||||||
X1: ServiceFactory<Request, Response = Request>,
|
X1: ServiceFactory<Request = Request, Response = Request>,
|
||||||
X1::Error: Into<Error>,
|
X1::Error: Into<Error>,
|
||||||
X1::InitError: fmt::Debug,
|
X1::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
@ -211,14 +215,15 @@ where
|
|||||||
cfg: self.cfg,
|
cfg: self.cfg,
|
||||||
srv: self.srv,
|
srv: self.srv,
|
||||||
upgrade: self.upgrade,
|
upgrade: self.upgrade,
|
||||||
|
on_connect: self.on_connect,
|
||||||
on_connect_ext: self.on_connect_ext,
|
on_connect_ext: self.on_connect_ext,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> H1Service<T, S, B, X, U1>
|
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> H1Service<T, S, B, X, U1>
|
||||||
where
|
where
|
||||||
U1: ServiceFactory<(Request, Framed<T, Codec>), Response = ()>,
|
U1: ServiceFactory<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||||
U1::Error: fmt::Display,
|
U1::Error: fmt::Display,
|
||||||
U1::InitError: fmt::Debug,
|
U1::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
@ -227,11 +232,21 @@ where
|
|||||||
cfg: self.cfg,
|
cfg: self.cfg,
|
||||||
srv: self.srv,
|
srv: self.srv,
|
||||||
expect: self.expect,
|
expect: self.expect,
|
||||||
|
on_connect: self.on_connect,
|
||||||
on_connect_ext: self.on_connect_ext,
|
on_connect_ext: self.on_connect_ext,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set on connect callback.
|
||||||
|
pub(crate) fn on_connect(
|
||||||
|
mut self,
|
||||||
|
f: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
) -> Self {
|
||||||
|
self.on_connect = f;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set on connect callback.
|
/// Set on connect callback.
|
||||||
pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
|
pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
|
||||||
self.on_connect_ext = f;
|
self.on_connect_ext = f;
|
||||||
@ -239,27 +254,27 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> ServiceFactory<(T, Option<net::SocketAddr>)>
|
impl<T, S, B, X, U> ServiceFactory for H1Service<T, S, B, X, U>
|
||||||
for H1Service<T, S, B, X, U>
|
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
|
U: ServiceFactory<Config = (), Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display + Into<Error>,
|
U::Error: fmt::Display + Into<Error>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
|
type Config = ();
|
||||||
|
type Request = (T, Option<net::SocketAddr>);
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = DispatchError;
|
type Error = DispatchError;
|
||||||
type Config = ();
|
|
||||||
type Service = H1ServiceHandler<T, S::Service, B, X::Service, U::Service>;
|
|
||||||
type InitError = ();
|
type InitError = ();
|
||||||
|
type Service = H1ServiceHandler<T, S::Service, B, X::Service, U::Service>;
|
||||||
type Future = H1ServiceResponse<T, S, B, X, U>;
|
type Future = H1ServiceResponse<T, S, B, X, U>;
|
||||||
|
|
||||||
fn new_service(&self, _: ()) -> Self::Future {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
@ -269,9 +284,10 @@ where
|
|||||||
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())),
|
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())),
|
||||||
expect: None,
|
expect: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
|
on_connect: self.on_connect.clone(),
|
||||||
on_connect_ext: self.on_connect_ext.clone(),
|
on_connect_ext: self.on_connect_ext.clone(),
|
||||||
cfg: Some(self.cfg.clone()),
|
cfg: Some(self.cfg.clone()),
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,13 +296,13 @@ where
|
|||||||
#[pin_project::pin_project]
|
#[pin_project::pin_project]
|
||||||
pub struct H1ServiceResponse<T, S, B, X, U>
|
pub struct H1ServiceResponse<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request>,
|
S: ServiceFactory<Request = Request>,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
X: ServiceFactory<Request, Response = Request>,
|
X: ServiceFactory<Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
U: ServiceFactory<(Request, Framed<T, Codec>), Response = ()>,
|
U: ServiceFactory<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
@ -298,23 +314,24 @@ where
|
|||||||
fut_upg: Option<U::Future>,
|
fut_upg: Option<U::Future>,
|
||||||
expect: Option<X::Service>,
|
expect: Option<X::Service>,
|
||||||
upgrade: Option<U::Service>,
|
upgrade: Option<U::Service>,
|
||||||
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
cfg: Option<ServiceConfig>,
|
cfg: Option<ServiceConfig>,
|
||||||
_phantom: PhantomData<B>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> Future for H1ServiceResponse<T, S, B, X, U>
|
impl<T, S, B, X, U> Future for H1ServiceResponse<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: ServiceFactory<Request>,
|
S: ServiceFactory<Request = Request>,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
X: ServiceFactory<Request, Response = Request>,
|
X: ServiceFactory<Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
U: ServiceFactory<(Request, Framed<T, Codec>), Response = ()>,
|
U: ServiceFactory<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
@ -338,7 +355,7 @@ where
|
|||||||
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
|
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
|
||||||
this = self.as_mut().project();
|
this = self.as_mut().project();
|
||||||
*this.upgrade = Some(upgrade);
|
*this.upgrade = Some(upgrade);
|
||||||
this.fut_upg.set(None);
|
this.fut_ex.set(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = ready!(this
|
let result = ready!(this
|
||||||
@ -354,6 +371,7 @@ where
|
|||||||
service,
|
service,
|
||||||
this.expect.take().unwrap(),
|
this.expect.take().unwrap(),
|
||||||
this.upgrade.take(),
|
this.upgrade.take(),
|
||||||
|
this.on_connect.clone(),
|
||||||
this.on_connect_ext.clone(),
|
this.on_connect_ext.clone(),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
@ -361,65 +379,66 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `Service` implementation for HTTP/1 transport
|
/// `Service` implementation for HTTP/1 transport
|
||||||
pub struct H1ServiceHandler<T, S, B, X, U>
|
pub struct H1ServiceHandler<T, S: Service, B, X: Service, U: Service> {
|
||||||
where
|
srv: CloneableService<S>,
|
||||||
S: Service<Request>,
|
expect: CloneableService<X>,
|
||||||
X: Service<Request>,
|
upgrade: Option<CloneableService<U>>,
|
||||||
U: Service<(Request, Framed<T, Codec>)>,
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
{
|
|
||||||
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
|
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
_phantom: PhantomData<B>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> H1ServiceHandler<T, S, B, X, U>
|
impl<T, S, B, X, U> H1ServiceHandler<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request = Request>,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
fn new(
|
fn new(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
service: S,
|
srv: S,
|
||||||
expect: X,
|
expect: X,
|
||||||
upgrade: Option<U>,
|
upgrade: Option<U>,
|
||||||
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
) -> H1ServiceHandler<T, S, B, X, U> {
|
) -> H1ServiceHandler<T, S, B, X, U> {
|
||||||
H1ServiceHandler {
|
H1ServiceHandler {
|
||||||
flow: HttpFlow::new(service, expect, upgrade),
|
srv: CloneableService::new(srv),
|
||||||
|
expect: CloneableService::new(expect),
|
||||||
|
upgrade: upgrade.map(CloneableService::new),
|
||||||
cfg,
|
cfg,
|
||||||
|
on_connect,
|
||||||
on_connect_ext,
|
on_connect_ext,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> Service<(T, Option<net::SocketAddr>)>
|
impl<T, S, B, X, U> Service for H1ServiceHandler<T, S, B, X, U>
|
||||||
for H1ServiceHandler<T, S, B, X, U>
|
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request = Request>,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display + Into<Error>,
|
U::Error: fmt::Display + Into<Error>,
|
||||||
{
|
{
|
||||||
|
type Request = (T, Option<net::SocketAddr>);
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = DispatchError;
|
type Error = DispatchError;
|
||||||
type Future = Dispatcher<T, S, B, X, U>;
|
type Future = Dispatcher<T, S, B, X, U>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
let mut flow = self.flow.borrow_mut();
|
let ready = self
|
||||||
let ready = flow
|
|
||||||
.expect
|
.expect
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@ -429,8 +448,8 @@ where
|
|||||||
})?
|
})?
|
||||||
.is_ready();
|
.is_ready();
|
||||||
|
|
||||||
let ready = flow
|
let ready = self
|
||||||
.service
|
.srv
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
@ -440,7 +459,7 @@ where
|
|||||||
.is_ready()
|
.is_ready()
|
||||||
&& ready;
|
&& ready;
|
||||||
|
|
||||||
let ready = if let Some(ref mut upg) = flow.upgrade {
|
let ready = if let Some(ref mut upg) = self.upgrade {
|
||||||
upg.poll_ready(cx)
|
upg.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
@ -460,16 +479,124 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
|
fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
|
||||||
let on_connect_data =
|
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
|
||||||
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
|
||||||
|
let mut connect_extensions = Extensions::new();
|
||||||
|
if let Some(ref handler) = self.on_connect_ext {
|
||||||
|
// run on_connect_ext callback, populating connect extensions
|
||||||
|
handler(&io, &mut connect_extensions);
|
||||||
|
}
|
||||||
|
|
||||||
Dispatcher::new(
|
Dispatcher::new(
|
||||||
io,
|
io,
|
||||||
self.cfg.clone(),
|
self.cfg.clone(),
|
||||||
self.flow.clone(),
|
self.srv.clone(),
|
||||||
on_connect_data,
|
self.expect.clone(),
|
||||||
|
self.upgrade.clone(),
|
||||||
|
deprecated_on_connect,
|
||||||
|
connect_extensions,
|
||||||
addr,
|
addr,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `ServiceFactory` implementation for `OneRequestService` service
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct OneRequest<T> {
|
||||||
|
config: ServiceConfig,
|
||||||
|
_t: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> OneRequest<T>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
{
|
||||||
|
/// Create new `H1SimpleService` instance.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
OneRequest {
|
||||||
|
config: ServiceConfig::default(),
|
||||||
|
_t: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ServiceFactory for OneRequest<T>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
{
|
||||||
|
type Config = ();
|
||||||
|
type Request = T;
|
||||||
|
type Response = (Request, Framed<T, Codec>);
|
||||||
|
type Error = ParseError;
|
||||||
|
type InitError = ();
|
||||||
|
type Service = OneRequestService<T>;
|
||||||
|
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
|
ok(OneRequestService {
|
||||||
|
_t: PhantomData,
|
||||||
|
config: self.config.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Service` implementation for HTTP1 transport. Reads one request and returns
|
||||||
|
/// request and framed object.
|
||||||
|
pub struct OneRequestService<T> {
|
||||||
|
_t: PhantomData<T>,
|
||||||
|
config: ServiceConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Service for OneRequestService<T>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
{
|
||||||
|
type Request = T;
|
||||||
|
type Response = (Request, Framed<T, Codec>);
|
||||||
|
type Error = ParseError;
|
||||||
|
type Future = OneRequestServiceResponse<T>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||||
|
OneRequestServiceResponse {
|
||||||
|
framed: Some(Framed::new(req, Codec::new(self.config.clone()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[pin_project::pin_project]
|
||||||
|
pub struct OneRequestServiceResponse<T>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
{
|
||||||
|
#[pin]
|
||||||
|
framed: Option<Framed<T, Codec>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Future for OneRequestServiceResponse<T>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
{
|
||||||
|
type Output = Result<(Request, Framed<T, Codec>), ParseError>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = self.as_mut().project();
|
||||||
|
|
||||||
|
match ready!(this.framed.as_pin_mut().unwrap().next_item(cx)) {
|
||||||
|
Some(Ok(req)) => match req {
|
||||||
|
Message::Item(req) => {
|
||||||
|
let mut this = self.as_mut().project();
|
||||||
|
Poll::Ready(Ok((req, this.framed.take().unwrap())))
|
||||||
|
}
|
||||||
|
Message::Chunk(_) => unreachable!("Something is wrong"),
|
||||||
|
},
|
||||||
|
Some(Err(err)) => Poll::Ready(Err(err)),
|
||||||
|
None => Poll::Ready(Err(ParseError::Incomplete)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use actix_codec::Framed;
|
use actix_codec::Framed;
|
||||||
use actix_service::{Service, ServiceFactory};
|
use actix_service::{Service, ServiceFactory};
|
||||||
use futures_util::future::{ready, Ready};
|
use futures_util::future::Ready;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::h1::Codec;
|
use crate::h1::Codec;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
|
|
||||||
pub struct UpgradeHandler;
|
pub struct UpgradeHandler<T>(PhantomData<T>);
|
||||||
|
|
||||||
impl<T> ServiceFactory<(Request, Framed<T, Codec>)> for UpgradeHandler {
|
impl<T> ServiceFactory for UpgradeHandler<T> {
|
||||||
|
type Config = ();
|
||||||
|
type Request = (Request, Framed<T, Codec>);
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Config = ();
|
type Service = UpgradeHandler<T>;
|
||||||
type Service = UpgradeHandler;
|
|
||||||
type InitError = Error;
|
type InitError = Error;
|
||||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
@ -23,7 +25,8 @@ impl<T> ServiceFactory<(Request, Framed<T, Codec>)> for UpgradeHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Service<(Request, Framed<T, Codec>)> for UpgradeHandler {
|
impl<T> Service for UpgradeHandler<T> {
|
||||||
|
type Request = (Request, Framed<T, Codec>);
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Self::Response, Self::Error>>;
|
type Future = Ready<Result<Self::Response, Self::Error>>;
|
||||||
@ -32,7 +35,7 @@ impl<T> Service<(Request, Framed<T, Codec>)> for UpgradeHandler {
|
|||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, _: (Request, Framed<T, Codec>)) -> Self::Future {
|
fn call(&mut self, _: Self::Request) -> Self::Future {
|
||||||
ready(Ok(()))
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,66 +1,66 @@
|
|||||||
use std::cell::RefCell;
|
use std::convert::TryFrom;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::net;
|
use std::net;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::rc::Rc;
|
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::{cmp, convert::TryFrom};
|
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite};
|
use actix_codec::{AsyncRead, AsyncWrite};
|
||||||
use actix_rt::time::{Instant, Sleep};
|
use actix_rt::time::{Delay, Instant};
|
||||||
use actix_service::Service;
|
use actix_service::Service;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures_core::ready;
|
|
||||||
use h2::server::{Connection, SendResponse};
|
use h2::server::{Connection, SendResponse};
|
||||||
use h2::SendStream;
|
use h2::SendStream;
|
||||||
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
|
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
|
||||||
use log::{error, trace};
|
use log::{error, trace};
|
||||||
|
|
||||||
use crate::body::{BodySize, MessageBody, ResponseBody};
|
use crate::body::{BodySize, MessageBody, ResponseBody};
|
||||||
|
use crate::cloneable::CloneableService;
|
||||||
use crate::config::ServiceConfig;
|
use crate::config::ServiceConfig;
|
||||||
use crate::error::{DispatchError, Error};
|
use crate::error::{DispatchError, Error};
|
||||||
|
use crate::helpers::DataFactory;
|
||||||
|
use crate::httpmessage::HttpMessage;
|
||||||
use crate::message::ResponseHead;
|
use crate::message::ResponseHead;
|
||||||
use crate::payload::Payload;
|
use crate::payload::Payload;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::service::HttpFlow;
|
use crate::Extensions;
|
||||||
use crate::OnConnectData;
|
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 16_384;
|
const CHUNK_SIZE: usize = 16_384;
|
||||||
|
|
||||||
/// Dispatcher for HTTP/2 protocol.
|
/// Dispatcher for HTTP/2 protocol
|
||||||
#[pin_project::pin_project]
|
#[pin_project::pin_project]
|
||||||
pub struct Dispatcher<T, S, B, X, U>
|
pub struct Dispatcher<T, S: Service<Request = Request>, B: MessageBody>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
|
||||||
B: MessageBody,
|
|
||||||
{
|
{
|
||||||
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
|
service: CloneableService<S>,
|
||||||
connection: Connection<T, Bytes>,
|
connection: Connection<T, Bytes>,
|
||||||
on_connect_data: OnConnectData,
|
on_connect: Option<Box<dyn DataFactory>>,
|
||||||
|
on_connect_data: Extensions,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
ka_expire: Instant,
|
ka_expire: Instant,
|
||||||
ka_timer: Option<Sleep>,
|
ka_timer: Option<Delay>,
|
||||||
_phantom: PhantomData<B>,
|
_t: PhantomData<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
|
impl<T, S, B> Dispatcher<T, S, B>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request = Request>,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
|
// S::Future: 'static,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
services: Rc<RefCell<HttpFlow<S, X, U>>>,
|
service: CloneableService<S>,
|
||||||
connection: Connection<T, Bytes>,
|
connection: Connection<T, Bytes>,
|
||||||
on_connect_data: OnConnectData,
|
on_connect: Option<Box<dyn DataFactory>>,
|
||||||
|
on_connect_data: Extensions,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
timeout: Option<Sleep>,
|
timeout: Option<Delay>,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// let keepalive = config.keep_alive_enabled();
|
// let keepalive = config.keep_alive_enabled();
|
||||||
@ -80,22 +80,23 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
Dispatcher {
|
Dispatcher {
|
||||||
flow: services,
|
service,
|
||||||
config,
|
config,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
connection,
|
connection,
|
||||||
|
on_connect,
|
||||||
on_connect_data,
|
on_connect_data,
|
||||||
ka_expire,
|
ka_expire,
|
||||||
ka_timer,
|
ka_timer,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
|
impl<T, S, B> Future for Dispatcher<T, S, B>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
@ -108,12 +109,10 @@ where
|
|||||||
let this = self.get_mut();
|
let this = self.get_mut();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match ready!(Pin::new(&mut this.connection).poll_accept(cx)) {
|
match Pin::new(&mut this.connection).poll_accept(cx) {
|
||||||
None => return Poll::Ready(Ok(())),
|
Poll::Ready(None) => return Poll::Ready(Ok(())),
|
||||||
|
Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())),
|
||||||
Some(Err(err)) => return Poll::Ready(Err(err.into())),
|
Poll::Ready(Some(Ok((req, res)))) => {
|
||||||
|
|
||||||
Some(Ok((req, res))) => {
|
|
||||||
// update keep-alive expire
|
// update keep-alive expire
|
||||||
if this.ka_timer.is_some() {
|
if this.ka_timer.is_some() {
|
||||||
if let Some(expire) = this.config.keep_alive_expire() {
|
if let Some(expire) = this.config.keep_alive_expire() {
|
||||||
@ -122,9 +121,11 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (parts, body) = req.into_parts();
|
let (parts, body) = req.into_parts();
|
||||||
let pl = crate::h2::Payload::new(body);
|
let mut req = Request::with_payload(Payload::<
|
||||||
let pl = Payload::<crate::payload::PayloadStream>::H2(pl);
|
crate::payload::PayloadStream,
|
||||||
let mut req = Request::with_payload(pl);
|
>::H2(
|
||||||
|
crate::h2::Payload::new(body)
|
||||||
|
));
|
||||||
|
|
||||||
let head = &mut req.head_mut();
|
let head = &mut req.head_mut();
|
||||||
head.uri = parts.uri;
|
head.uri = parts.uri;
|
||||||
@ -133,21 +134,31 @@ where
|
|||||||
head.headers = parts.headers.into();
|
head.headers = parts.headers.into();
|
||||||
head.peer_addr = this.peer_addr;
|
head.peer_addr = this.peer_addr;
|
||||||
|
|
||||||
// merge on_connect_ext data into request extensions
|
// DEPRECATED
|
||||||
this.on_connect_data.merge_into(&mut req);
|
// set on_connect data
|
||||||
|
if let Some(ref on_connect) = this.on_connect {
|
||||||
|
on_connect.set(&mut req.extensions_mut());
|
||||||
|
}
|
||||||
|
|
||||||
let svc = ServiceResponse::<S::Future, S::Response, S::Error, B> {
|
// merge on_connect_ext data into request extensions
|
||||||
|
req.extensions_mut().drain_from(&mut this.on_connect_data);
|
||||||
|
|
||||||
|
actix_rt::spawn(ServiceResponse::<
|
||||||
|
S::Future,
|
||||||
|
S::Response,
|
||||||
|
S::Error,
|
||||||
|
B,
|
||||||
|
> {
|
||||||
state: ServiceResponseState::ServiceCall(
|
state: ServiceResponseState::ServiceCall(
|
||||||
this.flow.borrow_mut().service.call(req),
|
this.service.call(req),
|
||||||
Some(res),
|
Some(res),
|
||||||
),
|
),
|
||||||
config: this.config.clone(),
|
config: this.config.clone(),
|
||||||
buffer: None,
|
buffer: None,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
};
|
});
|
||||||
|
|
||||||
actix_rt::spawn(svc);
|
|
||||||
}
|
}
|
||||||
|
Poll::Pending => return Poll::Pending,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +170,7 @@ struct ServiceResponse<F, I, E, B> {
|
|||||||
state: ServiceResponseState<F, B>,
|
state: ServiceResponseState<F, B>,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
buffer: Option<Bytes>,
|
buffer: Option<Bytes>,
|
||||||
_phantom: PhantomData<(I, E)>,
|
_t: PhantomData<(I, E)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project::pin_project(project = ServiceResponseStateProj)]
|
#[pin_project::pin_project(project = ServiceResponseStateProj)]
|
||||||
@ -196,9 +207,8 @@ where
|
|||||||
skip_len = true;
|
skip_len = true;
|
||||||
*size = BodySize::Stream;
|
*size = BodySize::Stream;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = match size {
|
let _ = match size {
|
||||||
BodySize::None | BodySize::Stream => None,
|
BodySize::None | BodySize::Stream => None,
|
||||||
BodySize::Empty => res
|
BodySize::Empty => res
|
||||||
@ -213,13 +223,11 @@ where
|
|||||||
// copy headers
|
// copy headers
|
||||||
for (key, value) in head.headers.iter() {
|
for (key, value) in head.headers.iter() {
|
||||||
match *key {
|
match *key {
|
||||||
// omit HTTP/1 only headers
|
CONNECTION | TRANSFER_ENCODING => continue, // http2 specific
|
||||||
CONNECTION | TRANSFER_ENCODING => continue,
|
|
||||||
CONTENT_LENGTH if skip_len => continue,
|
CONTENT_LENGTH if skip_len => continue,
|
||||||
DATE => has_date = true,
|
DATE => has_date = true,
|
||||||
_ => {}
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
res.headers_mut().append(key, value.clone());
|
res.headers_mut().append(key, value.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,117 +259,109 @@ where
|
|||||||
let mut this = self.as_mut().project();
|
let mut this = self.as_mut().project();
|
||||||
|
|
||||||
match this.state.project() {
|
match this.state.project() {
|
||||||
ServiceResponseStateProj::ServiceCall(call, send) => {
|
ServiceResponseStateProj::ServiceCall(call, send) => match call.poll(cx) {
|
||||||
match ready!(call.poll(cx)) {
|
Poll::Ready(Ok(res)) => {
|
||||||
Ok(res) => {
|
let (res, body) = res.into().replace_body(());
|
||||||
let (res, body) = res.into().replace_body(());
|
|
||||||
|
|
||||||
let mut send = send.take().unwrap();
|
let mut send = send.take().unwrap();
|
||||||
let mut size = body.size();
|
let mut size = body.size();
|
||||||
let h2_res =
|
let h2_res = self.as_mut().prepare_response(res.head(), &mut size);
|
||||||
self.as_mut().prepare_response(res.head(), &mut size);
|
this = self.as_mut().project();
|
||||||
this = self.as_mut().project();
|
|
||||||
|
|
||||||
let stream = match send.send_response(h2_res, size.is_eof()) {
|
let stream = match send.send_response(h2_res, size.is_eof()) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
trace!("Error sending HTTP/2 response: {:?}", e);
|
trace!("Error sending h2 response: {:?}", e);
|
||||||
return Poll::Ready(());
|
return Poll::Ready(());
|
||||||
}
|
|
||||||
Ok(stream) => stream,
|
|
||||||
};
|
|
||||||
|
|
||||||
if size.is_eof() {
|
|
||||||
Poll::Ready(())
|
|
||||||
} else {
|
|
||||||
this.state
|
|
||||||
.set(ServiceResponseState::SendPayload(stream, body));
|
|
||||||
self.poll(cx)
|
|
||||||
}
|
}
|
||||||
}
|
Ok(stream) => stream,
|
||||||
|
};
|
||||||
|
|
||||||
Err(e) => {
|
if size.is_eof() {
|
||||||
let res: Response = e.into().into();
|
Poll::Ready(())
|
||||||
let (res, body) = res.replace_body(());
|
} else {
|
||||||
|
this.state
|
||||||
let mut send = send.take().unwrap();
|
.set(ServiceResponseState::SendPayload(stream, body));
|
||||||
let mut size = body.size();
|
self.poll(cx)
|
||||||
let h2_res =
|
|
||||||
self.as_mut().prepare_response(res.head(), &mut size);
|
|
||||||
this = self.as_mut().project();
|
|
||||||
|
|
||||||
let stream = match send.send_response(h2_res, size.is_eof()) {
|
|
||||||
Err(e) => {
|
|
||||||
trace!("Error sending HTTP/2 response: {:?}", e);
|
|
||||||
return Poll::Ready(());
|
|
||||||
}
|
|
||||||
Ok(stream) => stream,
|
|
||||||
};
|
|
||||||
|
|
||||||
if size.is_eof() {
|
|
||||||
Poll::Ready(())
|
|
||||||
} else {
|
|
||||||
this.state.set(ServiceResponseState::SendPayload(
|
|
||||||
stream,
|
|
||||||
body.into_body(),
|
|
||||||
));
|
|
||||||
self.poll(cx)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Poll::Pending => Poll::Pending,
|
||||||
|
Poll::Ready(Err(e)) => {
|
||||||
|
let res: Response = e.into().into();
|
||||||
|
let (res, body) = res.replace_body(());
|
||||||
|
|
||||||
|
let mut send = send.take().unwrap();
|
||||||
|
let mut size = body.size();
|
||||||
|
let h2_res = self.as_mut().prepare_response(res.head(), &mut size);
|
||||||
|
this = self.as_mut().project();
|
||||||
|
|
||||||
|
let stream = match send.send_response(h2_res, size.is_eof()) {
|
||||||
|
Err(e) => {
|
||||||
|
trace!("Error sending h2 response: {:?}", e);
|
||||||
|
return Poll::Ready(());
|
||||||
|
}
|
||||||
|
Ok(stream) => stream,
|
||||||
|
};
|
||||||
|
|
||||||
|
if size.is_eof() {
|
||||||
|
Poll::Ready(())
|
||||||
|
} else {
|
||||||
|
this.state.set(ServiceResponseState::SendPayload(
|
||||||
|
stream,
|
||||||
|
body.into_body(),
|
||||||
|
));
|
||||||
|
self.poll(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
ServiceResponseStateProj::SendPayload(ref mut stream, ref mut body) => {
|
ServiceResponseStateProj::SendPayload(ref mut stream, ref mut body) => {
|
||||||
loop {
|
loop {
|
||||||
loop {
|
loop {
|
||||||
match this.buffer {
|
if let Some(ref mut buffer) = this.buffer {
|
||||||
Some(ref mut buffer) => {
|
match stream.poll_capacity(cx) {
|
||||||
match ready!(stream.poll_capacity(cx)) {
|
Poll::Pending => return Poll::Pending,
|
||||||
None => return Poll::Ready(()),
|
Poll::Ready(None) => return Poll::Ready(()),
|
||||||
|
Poll::Ready(Some(Ok(cap))) => {
|
||||||
|
let len = buffer.len();
|
||||||
|
let bytes = buffer.split_to(std::cmp::min(cap, len));
|
||||||
|
|
||||||
Some(Ok(cap)) => {
|
if let Err(e) = stream.send_data(bytes, false) {
|
||||||
let len = buffer.len();
|
|
||||||
let bytes = buffer.split_to(cmp::min(cap, len));
|
|
||||||
|
|
||||||
if let Err(e) = stream.send_data(bytes, false) {
|
|
||||||
warn!("{:?}", e);
|
|
||||||
return Poll::Ready(());
|
|
||||||
} else if !buffer.is_empty() {
|
|
||||||
let cap = cmp::min(buffer.len(), CHUNK_SIZE);
|
|
||||||
stream.reserve_capacity(cap);
|
|
||||||
} else {
|
|
||||||
this.buffer.take();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Err(e)) => {
|
|
||||||
warn!("{:?}", e);
|
warn!("{:?}", e);
|
||||||
return Poll::Ready(());
|
return Poll::Ready(());
|
||||||
|
} else if !buffer.is_empty() {
|
||||||
|
let cap =
|
||||||
|
std::cmp::min(buffer.len(), CHUNK_SIZE);
|
||||||
|
stream.reserve_capacity(cap);
|
||||||
|
} else {
|
||||||
|
this.buffer.take();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Poll::Ready(Some(Err(e))) => {
|
||||||
|
warn!("{:?}", e);
|
||||||
|
return Poll::Ready(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
None => match ready!(body.as_mut().poll_next(cx)) {
|
match body.as_mut().poll_next(cx) {
|
||||||
None => {
|
Poll::Pending => return Poll::Pending,
|
||||||
|
Poll::Ready(None) => {
|
||||||
if let Err(e) = stream.send_data(Bytes::new(), true)
|
if let Err(e) = stream.send_data(Bytes::new(), true)
|
||||||
{
|
{
|
||||||
warn!("{:?}", e);
|
warn!("{:?}", e);
|
||||||
}
|
}
|
||||||
return Poll::Ready(());
|
return Poll::Ready(());
|
||||||
}
|
}
|
||||||
|
Poll::Ready(Some(Ok(chunk))) => {
|
||||||
Some(Ok(chunk)) => {
|
stream.reserve_capacity(std::cmp::min(
|
||||||
stream.reserve_capacity(cmp::min(
|
|
||||||
chunk.len(),
|
chunk.len(),
|
||||||
CHUNK_SIZE,
|
CHUNK_SIZE,
|
||||||
));
|
));
|
||||||
*this.buffer = Some(chunk);
|
*this.buffer = Some(chunk);
|
||||||
}
|
}
|
||||||
|
Poll::Ready(Some(Err(e))) => {
|
||||||
Some(Err(e)) => {
|
|
||||||
error!("Response payload stream error: {:?}", e);
|
error!("Response payload stream error: {:?}", e);
|
||||||
return Poll::Ready(());
|
return Poll::Ready(());
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
//! HTTP/2 implementation.
|
//! HTTP/2 implementation
|
||||||
|
use std::pin::Pin;
|
||||||
use std::{
|
use std::task::{Context, Poll};
|
||||||
pin::Pin,
|
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::Stream;
|
||||||
use h2::RecvStream;
|
use h2::RecvStream;
|
||||||
|
|
||||||
mod dispatcher;
|
mod dispatcher;
|
||||||
@ -16,14 +13,14 @@ pub use self::dispatcher::Dispatcher;
|
|||||||
pub use self::service::H2Service;
|
pub use self::service::H2Service;
|
||||||
use crate::error::PayloadError;
|
use crate::error::PayloadError;
|
||||||
|
|
||||||
/// HTTP/2 peer stream.
|
/// H2 receive stream
|
||||||
pub struct Payload {
|
pub struct Payload {
|
||||||
stream: RecvStream,
|
pl: RecvStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Payload {
|
impl Payload {
|
||||||
pub(crate) fn new(stream: RecvStream) -> Self {
|
pub(crate) fn new(pl: RecvStream) -> Self {
|
||||||
Self { stream }
|
Self { pl }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,17 +33,18 @@ impl Stream for Payload {
|
|||||||
) -> Poll<Option<Self::Item>> {
|
) -> Poll<Option<Self::Item>> {
|
||||||
let this = self.get_mut();
|
let this = self.get_mut();
|
||||||
|
|
||||||
match ready!(Pin::new(&mut this.stream).poll_data(cx)) {
|
match Pin::new(&mut this.pl).poll_data(cx) {
|
||||||
Some(Ok(chunk)) => {
|
Poll::Ready(Some(Ok(chunk))) => {
|
||||||
let len = chunk.len();
|
let len = chunk.len();
|
||||||
|
if let Err(err) = this.pl.flow_control().release_capacity(len) {
|
||||||
match this.stream.flow_control().release_capacity(len) {
|
Poll::Ready(Some(Err(err.into())))
|
||||||
Ok(()) => Poll::Ready(Some(Ok(chunk))),
|
} else {
|
||||||
Err(err) => Poll::Ready(Some(Err(err.into()))),
|
Poll::Ready(Some(Ok(chunk)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
|
Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err.into()))),
|
||||||
None => Poll::Ready(None),
|
Poll::Pending => Poll::Pending,
|
||||||
|
Poll::Ready(None) => Poll::Ready(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
@ -18,44 +17,57 @@ use h2::server::{self, Handshake};
|
|||||||
use log::error;
|
use log::error;
|
||||||
|
|
||||||
use crate::body::MessageBody;
|
use crate::body::MessageBody;
|
||||||
|
use crate::cloneable::CloneableService;
|
||||||
use crate::config::ServiceConfig;
|
use crate::config::ServiceConfig;
|
||||||
use crate::error::{DispatchError, Error};
|
use crate::error::{DispatchError, Error};
|
||||||
|
use crate::helpers::DataFactory;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::service::HttpFlow;
|
use crate::{ConnectCallback, Extensions};
|
||||||
use crate::{ConnectCallback, OnConnectData};
|
|
||||||
|
|
||||||
use super::dispatcher::Dispatcher;
|
use super::dispatcher::Dispatcher;
|
||||||
|
|
||||||
/// `ServiceFactory` implementation for HTTP/2 transport
|
/// `ServiceFactory` implementation for HTTP2 transport
|
||||||
pub struct H2Service<T, S, B> {
|
pub struct H2Service<T, S, B> {
|
||||||
srv: S,
|
srv: S,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_phantom: PhantomData<(T, B)>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> H2Service<T, S, B>
|
impl<T, S, B> H2Service<T, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create new `H2Service` instance with config.
|
/// Create new `HttpService` instance with config.
|
||||||
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
|
pub(crate) fn with_config<F: IntoServiceFactory<S>>(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
service: F,
|
service: F,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
H2Service {
|
H2Service {
|
||||||
cfg,
|
cfg,
|
||||||
|
on_connect: None,
|
||||||
on_connect_ext: None,
|
on_connect_ext: None,
|
||||||
srv: service.into_factory(),
|
srv: service.into_factory(),
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set on connect callback.
|
||||||
|
|
||||||
|
pub(crate) fn on_connect(
|
||||||
|
mut self,
|
||||||
|
f: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
) -> Self {
|
||||||
|
self.on_connect = f;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set on connect callback.
|
/// Set on connect callback.
|
||||||
pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
|
pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
|
||||||
self.on_connect_ext = f;
|
self.on_connect_ext = f;
|
||||||
@ -65,18 +77,18 @@ where
|
|||||||
|
|
||||||
impl<S, B> H2Service<TcpStream, S, B>
|
impl<S, B> H2Service<TcpStream, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create plain TCP based service
|
/// Create simple tcp based service
|
||||||
pub fn tcp(
|
pub fn tcp(
|
||||||
self,
|
self,
|
||||||
) -> impl ServiceFactory<
|
) -> impl ServiceFactory<
|
||||||
TcpStream,
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = TcpStream,
|
||||||
Response = (),
|
Response = (),
|
||||||
Error = DispatchError,
|
Error = DispatchError,
|
||||||
InitError = S::InitError,
|
InitError = S::InitError,
|
||||||
@ -93,29 +105,29 @@ where
|
|||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
mod openssl {
|
mod openssl {
|
||||||
use actix_service::{fn_factory, fn_service, ServiceFactoryExt};
|
use actix_service::{fn_factory, fn_service};
|
||||||
use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream};
|
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
|
||||||
use actix_tls::accept::TlsError;
|
use actix_tls::{openssl::HandshakeError, TlsError};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
impl<S, B> H2Service<SslStream<TcpStream>, S, B>
|
impl<S, B> H2Service<SslStream<TcpStream>, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create OpenSSL based service
|
/// Create ssl based service
|
||||||
pub fn openssl(
|
pub fn openssl(
|
||||||
self,
|
self,
|
||||||
acceptor: SslAcceptor,
|
acceptor: SslAcceptor,
|
||||||
) -> impl ServiceFactory<
|
) -> impl ServiceFactory<
|
||||||
TcpStream,
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = TcpStream,
|
||||||
Response = (),
|
Response = (),
|
||||||
Error = TlsError<SslError, DispatchError>,
|
Error = TlsError<HandshakeError<TcpStream>, DispatchError>,
|
||||||
InitError = S::InitError,
|
InitError = S::InitError,
|
||||||
> {
|
> {
|
||||||
pipeline_factory(
|
pipeline_factory(
|
||||||
@ -137,26 +149,25 @@ mod openssl {
|
|||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
mod rustls {
|
mod rustls {
|
||||||
use super::*;
|
use super::*;
|
||||||
use actix_service::ServiceFactoryExt;
|
use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream};
|
||||||
use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream};
|
use actix_tls::TlsError;
|
||||||
use actix_tls::accept::TlsError;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
|
impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create Rustls based service
|
/// Create openssl based service
|
||||||
pub fn rustls(
|
pub fn rustls(
|
||||||
self,
|
self,
|
||||||
mut config: ServerConfig,
|
mut config: ServerConfig,
|
||||||
) -> impl ServiceFactory<
|
) -> impl ServiceFactory<
|
||||||
TcpStream,
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = TcpStream,
|
||||||
Response = (),
|
Response = (),
|
||||||
Error = TlsError<io::Error, DispatchError>,
|
Error = TlsError<io::Error, DispatchError>,
|
||||||
InitError = S::InitError,
|
InitError = S::InitError,
|
||||||
@ -180,52 +191,52 @@ mod rustls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B>
|
impl<T, S, B> ServiceFactory for H2Service<T, S, B>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
|
type Config = ();
|
||||||
|
type Request = (T, Option<net::SocketAddr>);
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = DispatchError;
|
type Error = DispatchError;
|
||||||
type Config = ();
|
|
||||||
type Service = H2ServiceHandler<T, S::Service, B>;
|
|
||||||
type InitError = S::InitError;
|
type InitError = S::InitError;
|
||||||
|
type Service = H2ServiceHandler<T, S::Service, B>;
|
||||||
type Future = H2ServiceResponse<T, S, B>;
|
type Future = H2ServiceResponse<T, S, B>;
|
||||||
|
|
||||||
fn new_service(&self, _: ()) -> Self::Future {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
H2ServiceResponse {
|
H2ServiceResponse {
|
||||||
fut: self.srv.new_service(()),
|
fut: self.srv.new_service(()),
|
||||||
cfg: Some(self.cfg.clone()),
|
cfg: Some(self.cfg.clone()),
|
||||||
|
on_connect: self.on_connect.clone(),
|
||||||
on_connect_ext: self.on_connect_ext.clone(),
|
on_connect_ext: self.on_connect_ext.clone(),
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[pin_project::pin_project]
|
#[pin_project::pin_project]
|
||||||
pub struct H2ServiceResponse<T, S, B>
|
pub struct H2ServiceResponse<T, S: ServiceFactory, B> {
|
||||||
where
|
|
||||||
S: ServiceFactory<Request>,
|
|
||||||
{
|
|
||||||
#[pin]
|
#[pin]
|
||||||
fut: S::Future,
|
fut: S::Future,
|
||||||
cfg: Option<ServiceConfig>,
|
cfg: Option<ServiceConfig>,
|
||||||
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_phantom: PhantomData<B>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> Future for H2ServiceResponse<T, S, B>
|
impl<T, S, B> Future for H2ServiceResponse<T, S, B>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
type Output = Result<H2ServiceHandler<T, S::Service, B>, S::InitError>;
|
type Output = Result<H2ServiceHandler<T, S::Service, B>, S::InitError>;
|
||||||
@ -233,31 +244,30 @@ where
|
|||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
let this = self.as_mut().project();
|
let this = self.as_mut().project();
|
||||||
|
|
||||||
this.fut.poll(cx).map_ok(|service| {
|
Poll::Ready(ready!(this.fut.poll(cx)).map(|service| {
|
||||||
let this = self.as_mut().project();
|
let this = self.as_mut().project();
|
||||||
H2ServiceHandler::new(
|
H2ServiceHandler::new(
|
||||||
this.cfg.take().unwrap(),
|
this.cfg.take().unwrap(),
|
||||||
|
this.on_connect.clone(),
|
||||||
this.on_connect_ext.clone(),
|
this.on_connect_ext.clone(),
|
||||||
service,
|
service,
|
||||||
)
|
)
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Service` implementation for http/2 transport
|
/// `Service` implementation for http/2 transport
|
||||||
pub struct H2ServiceHandler<T, S, B>
|
pub struct H2ServiceHandler<T, S: Service, B> {
|
||||||
where
|
srv: CloneableService<S>,
|
||||||
S: Service<Request>,
|
|
||||||
{
|
|
||||||
flow: Rc<RefCell<HttpFlow<S, (), ()>>>,
|
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_phantom: PhantomData<B>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> H2ServiceHandler<T, S, B>
|
impl<T, S, B> H2ServiceHandler<T, S, B>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
@ -265,66 +275,76 @@ where
|
|||||||
{
|
{
|
||||||
fn new(
|
fn new(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
service: S,
|
srv: S,
|
||||||
) -> H2ServiceHandler<T, S, B> {
|
) -> H2ServiceHandler<T, S, B> {
|
||||||
H2ServiceHandler {
|
H2ServiceHandler {
|
||||||
flow: HttpFlow::new(service, (), None),
|
|
||||||
cfg,
|
cfg,
|
||||||
|
on_connect,
|
||||||
on_connect_ext,
|
on_connect_ext,
|
||||||
_phantom: PhantomData,
|
srv: CloneableService::new(srv),
|
||||||
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> Service<(T, Option<net::SocketAddr>)> for H2ServiceHandler<T, S, B>
|
impl<T, S, B> Service for H2ServiceHandler<T, S, B>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
|
type Request = (T, Option<net::SocketAddr>);
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = DispatchError;
|
type Error = DispatchError;
|
||||||
type Future = H2ServiceHandlerResponse<T, S, B>;
|
type Future = H2ServiceHandlerResponse<T, S, B>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
self.flow.borrow_mut().service.poll_ready(cx).map_err(|e| {
|
self.srv.poll_ready(cx).map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
error!("Service readiness error: {:?}", e);
|
error!("Service readiness error: {:?}", e);
|
||||||
DispatchError::Service(e)
|
DispatchError::Service(e)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
|
fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
|
||||||
let on_connect_data =
|
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
|
||||||
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
|
||||||
|
let mut connect_extensions = Extensions::new();
|
||||||
|
if let Some(ref handler) = self.on_connect_ext {
|
||||||
|
// run on_connect_ext callback, populating connect extensions
|
||||||
|
handler(&io, &mut connect_extensions);
|
||||||
|
}
|
||||||
|
|
||||||
H2ServiceHandlerResponse {
|
H2ServiceHandlerResponse {
|
||||||
state: State::Handshake(
|
state: State::Handshake(
|
||||||
Some(self.flow.clone()),
|
Some(self.srv.clone()),
|
||||||
Some(self.cfg.clone()),
|
Some(self.cfg.clone()),
|
||||||
addr,
|
addr,
|
||||||
on_connect_data,
|
deprecated_on_connect,
|
||||||
|
Some(connect_extensions),
|
||||||
server::handshake(io),
|
server::handshake(io),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum State<T, S: Service<Request>, B: MessageBody>
|
enum State<T, S: Service<Request = Request>, B: MessageBody>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
{
|
{
|
||||||
Incoming(Dispatcher<T, S, B, (), ()>),
|
Incoming(Dispatcher<T, S, B>),
|
||||||
Handshake(
|
Handshake(
|
||||||
Option<Rc<RefCell<HttpFlow<S, (), ()>>>>,
|
Option<CloneableService<S>>,
|
||||||
Option<ServiceConfig>,
|
Option<ServiceConfig>,
|
||||||
Option<net::SocketAddr>,
|
Option<net::SocketAddr>,
|
||||||
OnConnectData,
|
Option<Box<dyn DataFactory>>,
|
||||||
|
Option<Extensions>,
|
||||||
Handshake<T, Bytes>,
|
Handshake<T, Bytes>,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -332,7 +352,7 @@ where
|
|||||||
pub struct H2ServiceHandlerResponse<T, S, B>
|
pub struct H2ServiceHandlerResponse<T, S, B>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
@ -344,7 +364,7 @@ where
|
|||||||
impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
|
impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
@ -359,25 +379,27 @@ where
|
|||||||
ref mut srv,
|
ref mut srv,
|
||||||
ref mut config,
|
ref mut config,
|
||||||
ref peer_addr,
|
ref peer_addr,
|
||||||
|
ref mut on_connect,
|
||||||
ref mut on_connect_data,
|
ref mut on_connect_data,
|
||||||
ref mut handshake,
|
ref mut handshake,
|
||||||
) => match ready!(Pin::new(handshake).poll(cx)) {
|
) => match Pin::new(handshake).poll(cx) {
|
||||||
Ok(conn) => {
|
Poll::Ready(Ok(conn)) => {
|
||||||
let on_connect_data = std::mem::take(on_connect_data);
|
|
||||||
self.state = State::Incoming(Dispatcher::new(
|
self.state = State::Incoming(Dispatcher::new(
|
||||||
srv.take().unwrap(),
|
srv.take().unwrap(),
|
||||||
conn,
|
conn,
|
||||||
on_connect_data,
|
on_connect.take(),
|
||||||
|
on_connect_data.take().unwrap(),
|
||||||
config.take().unwrap(),
|
config.take().unwrap(),
|
||||||
None,
|
None,
|
||||||
*peer_addr,
|
*peer_addr,
|
||||||
));
|
));
|
||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Poll::Ready(Err(err)) => {
|
||||||
trace!("H2 handshake error: {}", err);
|
trace!("H2 handshake error: {}", err);
|
||||||
Poll::Ready(Err(err.into()))
|
Poll::Ready(Err(err.into()))
|
||||||
}
|
}
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,8 +318,9 @@ impl ContentDisposition {
|
|||||||
return Err(crate::error::ParseError::Header);
|
return Err(crate::error::ParseError::Header);
|
||||||
}
|
}
|
||||||
left = new_left;
|
left = new_left;
|
||||||
if let Some(param_name) = param_name.strip_suffix('*') {
|
if param_name.ends_with('*') {
|
||||||
// extended parameters
|
// extended parameters
|
||||||
|
let param_name = ¶m_name[..param_name.len() - 1]; // trim asterisk
|
||||||
let (ext_value, new_left) = split_once_and_trim(left, ';');
|
let (ext_value, new_left) = split_once_and_trim(left, ';');
|
||||||
left = new_left;
|
left = new_left;
|
||||||
let ext_value = header::parse_extended_value(ext_value)?;
|
let ext_value = header::parse_extended_value(ext_value)?;
|
||||||
@ -549,7 +550,8 @@ impl fmt::Display for ContentDisposition {
|
|||||||
write!(f, "{}", self.disposition)?;
|
write!(f, "{}", self.disposition)?;
|
||||||
self.parameters
|
self.parameters
|
||||||
.iter()
|
.iter()
|
||||||
.try_for_each(|param| write!(f, "; {}", param))
|
.map(|param| write!(f, "; {}", param))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
//! ## Mime
|
//! ## Mime
|
||||||
//!
|
//!
|
||||||
//! Several header fields use MIME values for their contents. Keeping with the
|
//! Several header fields use MIME values for their contents. Keeping with the
|
||||||
//! strongly-typed theme, the [mime] crate
|
//! strongly-typed theme, the [mime](https://docs.rs/mime) crate
|
||||||
//! is used, such as `ContentType(pub Mime)`.
|
//! is used, such as `ContentType(pub Mime)`.
|
||||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ use http::header::{HeaderName, HeaderValue};
|
|||||||
/// A set of HTTP headers
|
/// A set of HTTP headers
|
||||||
///
|
///
|
||||||
/// `HeaderMap` is an multi-map of [`HeaderName`] to values.
|
/// `HeaderMap` is an multi-map of [`HeaderName`] to values.
|
||||||
|
///
|
||||||
|
/// [`HeaderName`]: struct.HeaderName.html
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HeaderMap {
|
pub struct HeaderMap {
|
||||||
pub(crate) inner: FxHashMap<HeaderName, Value>,
|
pub(crate) inner: FxHashMap<HeaderName, Value>,
|
||||||
@ -139,6 +141,8 @@ impl HeaderMap {
|
|||||||
/// The returned view does not incur any allocations and allows iterating
|
/// The returned view does not incur any allocations and allows iterating
|
||||||
/// the values associated with the key. See [`GetAll`] for more details.
|
/// the values associated with the key. See [`GetAll`] for more details.
|
||||||
/// Returns `None` if there are no values associated with the key.
|
/// Returns `None` if there are no values associated with the key.
|
||||||
|
///
|
||||||
|
/// [`GetAll`]: struct.GetAll.html
|
||||||
pub fn get_all<N: AsName>(&self, name: N) -> GetAll<'_> {
|
pub fn get_all<N: AsName>(&self, name: N) -> GetAll<'_> {
|
||||||
GetAll {
|
GetAll {
|
||||||
idx: 0,
|
idx: 0,
|
||||||
|
@ -7,12 +7,10 @@ use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer};
|
|||||||
/// 1. `%x21`, or
|
/// 1. `%x21`, or
|
||||||
/// 2. in the range `%x23` to `%x7E`, or
|
/// 2. in the range `%x23` to `%x7E`, or
|
||||||
/// 3. above `%x80`
|
/// 3. above `%x80`
|
||||||
fn entity_validate_char(c: u8) -> bool {
|
|
||||||
c == 0x21 || (0x23..=0x7e).contains(&c) || (c >= 0x80)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_slice_validity(slice: &str) -> bool {
|
fn check_slice_validity(slice: &str) -> bool {
|
||||||
slice.bytes().all(entity_validate_char)
|
slice
|
||||||
|
.bytes()
|
||||||
|
.all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80'))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3)
|
/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3)
|
||||||
|
@ -3,8 +3,7 @@ use std::io::Write;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use bytes::buf::BufMut;
|
use bytes::{buf::BufMutExt, BytesMut};
|
||||||
use bytes::BytesMut;
|
|
||||||
use http::header::{HeaderValue, InvalidHeaderValue};
|
use http::header::{HeaderValue, InvalidHeaderValue};
|
||||||
use time::{offset, OffsetDateTime, PrimitiveDateTime};
|
use time::{offset, OffsetDateTime, PrimitiveDateTime};
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ use std::io;
|
|||||||
use bytes::{BufMut, BytesMut};
|
use bytes::{BufMut, BytesMut};
|
||||||
use http::Version;
|
use http::Version;
|
||||||
|
|
||||||
|
use crate::extensions::Extensions;
|
||||||
|
|
||||||
const DIGITS_START: u8 = b'0';
|
const DIGITS_START: u8 = b'0';
|
||||||
|
|
||||||
pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) {
|
pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) {
|
||||||
@ -54,6 +56,18 @@ impl<'a> io::Write for Writer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) trait DataFactory {
|
||||||
|
fn set(&self, ext: &mut Extensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Data<T>(pub(crate) T);
|
||||||
|
|
||||||
|
impl<T: Clone + 'static> DataFactory for Data<T> {
|
||||||
|
fn set(&self, ext: &mut Extensions) {
|
||||||
|
ext.insert(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
clippy::new_without_default,
|
clippy::new_without_default,
|
||||||
clippy::borrow_interior_mutable_const
|
clippy::borrow_interior_mutable_const
|
||||||
)]
|
)]
|
||||||
|
#![allow(clippy::manual_strip)] // Allow this to keep MSRV(1.42).
|
||||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ mod macros;
|
|||||||
pub mod body;
|
pub mod body;
|
||||||
mod builder;
|
mod builder;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
mod cloneable;
|
||||||
mod config;
|
mod config;
|
||||||
#[cfg(feature = "compress")]
|
#[cfg(feature = "compress")]
|
||||||
pub mod encoding;
|
pub mod encoding;
|
||||||
@ -72,49 +74,11 @@ pub mod http {
|
|||||||
pub use crate::message::ConnectionType;
|
pub use crate::message::ConnectionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A major HTTP protocol version.
|
/// Http protocol
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum Protocol {
|
pub enum Protocol {
|
||||||
Http1,
|
Http1,
|
||||||
Http2,
|
Http2,
|
||||||
Http3,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectCallback<IO> = dyn Fn(&IO, &mut Extensions);
|
type ConnectCallback<IO> = dyn Fn(&IO, &mut Extensions);
|
||||||
|
|
||||||
/// Container for data that extract with ConnectCallback.
|
|
||||||
///
|
|
||||||
/// # Implementation Details
|
|
||||||
/// Uses Option to reduce necessary allocations when merging with request extensions.
|
|
||||||
pub(crate) struct OnConnectData(Option<Extensions>);
|
|
||||||
|
|
||||||
impl Default for OnConnectData {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OnConnectData {
|
|
||||||
/// Construct by calling the on-connect callback with the underlying transport I/O.
|
|
||||||
pub(crate) fn from_io<T>(
|
|
||||||
io: &T,
|
|
||||||
on_connect_ext: Option<&ConnectCallback<T>>,
|
|
||||||
) -> Self {
|
|
||||||
let ext = on_connect_ext.map(|handler| {
|
|
||||||
let mut extensions = Extensions::new();
|
|
||||||
handler(io, &mut extensions);
|
|
||||||
extensions
|
|
||||||
});
|
|
||||||
|
|
||||||
Self(ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Merge self into given request's extensions.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn merge_into(&mut self, req: &mut Request) {
|
|
||||||
if let Some(ref mut ext) = self.0 {
|
|
||||||
req.head.extensions.get_mut().drain_from(ext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -67,7 +67,7 @@ impl Head for RequestHead {
|
|||||||
fn clear(&mut self) {
|
fn clear(&mut self) {
|
||||||
self.flags = Flags::empty();
|
self.flags = Flags::empty();
|
||||||
self.headers.clear();
|
self.headers.clear();
|
||||||
self.extensions.get_mut().clear();
|
self.extensions.borrow_mut().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pool() -> &'static MessagePool<Self> {
|
fn pool() -> &'static MessagePool<Self> {
|
||||||
@ -440,11 +440,9 @@ impl<T: Head> MessagePool<T> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn get_message(&'static self) -> Message<T> {
|
fn get_message(&'static self) -> Message<T> {
|
||||||
if let Some(mut msg) = self.0.borrow_mut().pop() {
|
if let Some(mut msg) = self.0.borrow_mut().pop() {
|
||||||
// Message is put in pool only when it's the last copy.
|
if let Some(r) = Rc::get_mut(&mut msg) {
|
||||||
// which means it's guaranteed to be unique when popped out.
|
r.clear();
|
||||||
Rc::get_mut(&mut msg)
|
}
|
||||||
.expect("Multiple copies exist")
|
|
||||||
.clear();
|
|
||||||
Message { head: msg }
|
Message { head: msg }
|
||||||
} else {
|
} else {
|
||||||
Message {
|
Message {
|
||||||
|
@ -23,10 +23,6 @@ impl<P> HttpMessage for Request<P> {
|
|||||||
&self.head().headers
|
&self.head().headers
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_payload(&mut self) -> Payload<P> {
|
|
||||||
std::mem::replace(&mut self.payload, Payload::None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Request extensions
|
/// Request extensions
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extensions(&self) -> Ref<'_, Extensions> {
|
fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
@ -38,6 +34,10 @@ impl<P> HttpMessage for Request<P> {
|
|||||||
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
||||||
self.head.extensions_mut()
|
self.head.extensions_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn take_payload(&mut self) -> Payload<P> {
|
||||||
|
std::mem::replace(&mut self.payload, Payload::None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Message<RequestHead>> for Request<PayloadStream> {
|
impl From<Message<RequestHead>> for Request<PayloadStream> {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
@ -9,34 +8,39 @@ use actix_rt::net::TcpStream;
|
|||||||
use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
|
use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::{ready, Future};
|
use futures_core::{ready, Future};
|
||||||
|
use futures_util::future::ok;
|
||||||
use h2::server::{self, Handshake};
|
use h2::server::{self, Handshake};
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
use crate::body::MessageBody;
|
use crate::body::MessageBody;
|
||||||
use crate::builder::HttpServiceBuilder;
|
use crate::builder::HttpServiceBuilder;
|
||||||
|
use crate::cloneable::CloneableService;
|
||||||
use crate::config::{KeepAlive, ServiceConfig};
|
use crate::config::{KeepAlive, ServiceConfig};
|
||||||
use crate::error::{DispatchError, Error};
|
use crate::error::{DispatchError, Error};
|
||||||
|
use crate::helpers::DataFactory;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::{h1, h2::Dispatcher, ConnectCallback, OnConnectData, Protocol};
|
use crate::{h1, h2::Dispatcher, ConnectCallback, Extensions, Protocol};
|
||||||
|
|
||||||
/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol.
|
/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol.
|
||||||
pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler> {
|
pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler<T>> {
|
||||||
srv: S,
|
srv: S,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
expect: X,
|
expect: X,
|
||||||
upgrade: Option<U>,
|
upgrade: Option<U>,
|
||||||
|
// DEPRECATED: in favor of on_connect_ext
|
||||||
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_phantom: PhantomData<B>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> HttpService<T, S, B>
|
impl<T, S, B> HttpService<T, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create builder for `HttpService` instance.
|
/// Create builder for `HttpService` instance.
|
||||||
@ -47,15 +51,15 @@ where
|
|||||||
|
|
||||||
impl<T, S, B> HttpService<T, S, B>
|
impl<T, S, B> HttpService<T, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create new `HttpService` instance.
|
/// Create new `HttpService` instance.
|
||||||
pub fn new<F: IntoServiceFactory<S, Request>>(service: F) -> Self {
|
pub fn new<F: IntoServiceFactory<S>>(service: F) -> Self {
|
||||||
let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0, false, None);
|
let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0, false, None);
|
||||||
|
|
||||||
HttpService {
|
HttpService {
|
||||||
@ -63,13 +67,14 @@ where
|
|||||||
srv: service.into_factory(),
|
srv: service.into_factory(),
|
||||||
expect: h1::ExpectHandler,
|
expect: h1::ExpectHandler,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
|
on_connect: None,
|
||||||
on_connect_ext: None,
|
on_connect_ext: None,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create new `HttpService` instance with config.
|
/// Create new `HttpService` instance with config.
|
||||||
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
|
pub(crate) fn with_config<F: IntoServiceFactory<S>>(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
service: F,
|
service: F,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -78,19 +83,20 @@ where
|
|||||||
srv: service.into_factory(),
|
srv: service.into_factory(),
|
||||||
expect: h1::ExpectHandler,
|
expect: h1::ExpectHandler,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
|
on_connect: None,
|
||||||
on_connect_ext: None,
|
on_connect_ext: None,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> HttpService<T, S, B, X, U>
|
impl<T, S, B, X, U> HttpService<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
/// Provide service for `EXPECT: 100-Continue` support.
|
/// Provide service for `EXPECT: 100-Continue` support.
|
||||||
@ -100,18 +106,19 @@ where
|
|||||||
/// request will be forwarded to main service.
|
/// request will be forwarded to main service.
|
||||||
pub fn expect<X1>(self, expect: X1) -> HttpService<T, S, B, X1, U>
|
pub fn expect<X1>(self, expect: X1) -> HttpService<T, S, B, X1, U>
|
||||||
where
|
where
|
||||||
X1: ServiceFactory<Request, Config = (), Response = Request>,
|
X1: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
||||||
X1::Error: Into<Error>,
|
X1::Error: Into<Error>,
|
||||||
X1::InitError: fmt::Debug,
|
X1::InitError: fmt::Debug,
|
||||||
<X1::Service as Service<Request>>::Future: 'static,
|
<X1::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
HttpService {
|
HttpService {
|
||||||
expect,
|
expect,
|
||||||
cfg: self.cfg,
|
cfg: self.cfg,
|
||||||
srv: self.srv,
|
srv: self.srv,
|
||||||
upgrade: self.upgrade,
|
upgrade: self.upgrade,
|
||||||
|
on_connect: self.on_connect,
|
||||||
on_connect_ext: self.on_connect_ext,
|
on_connect_ext: self.on_connect_ext,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,21 +128,35 @@ where
|
|||||||
/// and this service get called with original request and framed object.
|
/// and this service get called with original request and framed object.
|
||||||
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> HttpService<T, S, B, X, U1>
|
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> HttpService<T, S, B, X, U1>
|
||||||
where
|
where
|
||||||
U1: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
|
U1: ServiceFactory<
|
||||||
|
Config = (),
|
||||||
|
Request = (Request, Framed<T, h1::Codec>),
|
||||||
|
Response = (),
|
||||||
|
>,
|
||||||
U1::Error: fmt::Display,
|
U1::Error: fmt::Display,
|
||||||
U1::InitError: fmt::Debug,
|
U1::InitError: fmt::Debug,
|
||||||
<U1::Service as Service<(Request, Framed<T, h1::Codec>)>>::Future: 'static,
|
<U1::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
HttpService {
|
HttpService {
|
||||||
upgrade,
|
upgrade,
|
||||||
cfg: self.cfg,
|
cfg: self.cfg,
|
||||||
srv: self.srv,
|
srv: self.srv,
|
||||||
expect: self.expect,
|
expect: self.expect,
|
||||||
|
on_connect: self.on_connect,
|
||||||
on_connect_ext: self.on_connect_ext,
|
on_connect_ext: self.on_connect_ext,
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set on connect callback.
|
||||||
|
pub(crate) fn on_connect(
|
||||||
|
mut self,
|
||||||
|
f: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
) -> Self {
|
||||||
|
self.on_connect = f;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set connect callback with mutable access to request data container.
|
/// Set connect callback with mutable access to request data container.
|
||||||
pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
|
pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
|
||||||
self.on_connect_ext = f;
|
self.on_connect_ext = f;
|
||||||
@ -145,38 +166,38 @@ where
|
|||||||
|
|
||||||
impl<S, B, X, U> HttpService<TcpStream, S, B, X, U>
|
impl<S, B, X, U> HttpService<TcpStream, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
<X::Service as Service<Request>>::Future: 'static,
|
<X::Service as Service>::Future: 'static,
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<
|
||||||
(Request, Framed<TcpStream, h1::Codec>),
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = (Request, Framed<TcpStream, h1::Codec>),
|
||||||
Response = (),
|
Response = (),
|
||||||
>,
|
>,
|
||||||
U::Error: fmt::Display + Into<Error>,
|
U::Error: fmt::Display + Into<Error>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
<U::Service as Service<(Request, Framed<TcpStream, h1::Codec>)>>::Future: 'static,
|
<U::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
/// Create simple tcp stream service
|
/// Create simple tcp stream service
|
||||||
pub fn tcp(
|
pub fn tcp(
|
||||||
self,
|
self,
|
||||||
) -> impl ServiceFactory<
|
) -> impl ServiceFactory<
|
||||||
TcpStream,
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = TcpStream,
|
||||||
Response = (),
|
Response = (),
|
||||||
Error = DispatchError,
|
Error = DispatchError,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
> {
|
> {
|
||||||
pipeline_factory(|io: TcpStream| async {
|
pipeline_factory(|io: TcpStream| {
|
||||||
let peer_addr = io.peer_addr().ok();
|
let peer_addr = io.peer_addr().ok();
|
||||||
Ok((io, Protocol::Http1, peer_addr))
|
ok((io, Protocol::Http1, peer_addr))
|
||||||
})
|
})
|
||||||
.and_then(self)
|
.and_then(self)
|
||||||
}
|
}
|
||||||
@ -185,40 +206,39 @@ where
|
|||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
mod openssl {
|
mod openssl {
|
||||||
use super::*;
|
use super::*;
|
||||||
use actix_service::ServiceFactoryExt;
|
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
|
||||||
use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream};
|
use actix_tls::{openssl::HandshakeError, TlsError};
|
||||||
use actix_tls::accept::TlsError;
|
|
||||||
|
|
||||||
impl<S, B, X, U> HttpService<SslStream<TcpStream>, S, B, X, U>
|
impl<S, B, X, U> HttpService<SslStream<TcpStream>, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
<X::Service as Service<Request>>::Future: 'static,
|
<X::Service as Service>::Future: 'static,
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<
|
||||||
(Request, Framed<SslStream<TcpStream>, h1::Codec>),
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = (Request, Framed<SslStream<TcpStream>, h1::Codec>),
|
||||||
Response = (),
|
Response = (),
|
||||||
>,
|
>,
|
||||||
U::Error: fmt::Display + Into<Error>,
|
U::Error: fmt::Display + Into<Error>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
<U::Service as Service<(Request, Framed<SslStream<TcpStream>, h1::Codec>)>>::Future: 'static,
|
<U::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
/// Create openssl based service
|
/// Create openssl based service
|
||||||
pub fn openssl(
|
pub fn openssl(
|
||||||
self,
|
self,
|
||||||
acceptor: SslAcceptor,
|
acceptor: SslAcceptor,
|
||||||
) -> impl ServiceFactory<
|
) -> impl ServiceFactory<
|
||||||
TcpStream,
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = TcpStream,
|
||||||
Response = (),
|
Response = (),
|
||||||
Error = TlsError<SslError, DispatchError>,
|
Error = TlsError<HandshakeError<TcpStream>, DispatchError>,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
> {
|
> {
|
||||||
pipeline_factory(
|
pipeline_factory(
|
||||||
@ -226,7 +246,7 @@ mod openssl {
|
|||||||
.map_err(TlsError::Tls)
|
.map_err(TlsError::Tls)
|
||||||
.map_init_err(|_| panic!()),
|
.map_init_err(|_| panic!()),
|
||||||
)
|
)
|
||||||
.and_then(|io: SslStream<TcpStream>| async {
|
.and_then(|io: SslStream<TcpStream>| {
|
||||||
let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() {
|
let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() {
|
||||||
if protos.windows(2).any(|window| window == b"h2") {
|
if protos.windows(2).any(|window| window == b"h2") {
|
||||||
Protocol::Http2
|
Protocol::Http2
|
||||||
@ -237,7 +257,7 @@ mod openssl {
|
|||||||
Protocol::Http1
|
Protocol::Http1
|
||||||
};
|
};
|
||||||
let peer_addr = io.get_ref().peer_addr().ok();
|
let peer_addr = io.get_ref().peer_addr().ok();
|
||||||
Ok((io, proto, peer_addr))
|
ok((io, proto, peer_addr))
|
||||||
})
|
})
|
||||||
.and_then(self.map_err(TlsError::Service))
|
.and_then(self.map_err(TlsError::Service))
|
||||||
}
|
}
|
||||||
@ -246,42 +266,39 @@ mod openssl {
|
|||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
mod rustls {
|
mod rustls {
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use actix_tls::accept::rustls::{Acceptor, ServerConfig, Session, TlsStream};
|
|
||||||
use actix_tls::accept::TlsError;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use actix_service::ServiceFactoryExt;
|
use actix_tls::rustls::{Acceptor, ServerConfig, Session, TlsStream};
|
||||||
|
use actix_tls::TlsError;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U>
|
impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
<X::Service as Service<Request>>::Future: 'static,
|
<X::Service as Service>::Future: 'static,
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<
|
||||||
(Request, Framed<TlsStream<TcpStream>, h1::Codec>),
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = (Request, Framed<TlsStream<TcpStream>, h1::Codec>),
|
||||||
Response = (),
|
Response = (),
|
||||||
>,
|
>,
|
||||||
U::Error: fmt::Display + Into<Error>,
|
U::Error: fmt::Display + Into<Error>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
<U::Service as Service<(Request, Framed<TlsStream<TcpStream>, h1::Codec>)>>::Future: 'static,
|
<U::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
/// Create openssl based service
|
/// Create openssl based service
|
||||||
pub fn rustls(
|
pub fn rustls(
|
||||||
self,
|
self,
|
||||||
mut config: ServerConfig,
|
mut config: ServerConfig,
|
||||||
) -> impl ServiceFactory<
|
) -> impl ServiceFactory<
|
||||||
TcpStream,
|
|
||||||
Config = (),
|
Config = (),
|
||||||
|
Request = TcpStream,
|
||||||
Response = (),
|
Response = (),
|
||||||
Error = TlsError<io::Error, DispatchError>,
|
Error = TlsError<io::Error, DispatchError>,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
@ -294,7 +311,7 @@ mod rustls {
|
|||||||
.map_err(TlsError::Tls)
|
.map_err(TlsError::Tls)
|
||||||
.map_init_err(|_| panic!()),
|
.map_init_err(|_| panic!()),
|
||||||
)
|
)
|
||||||
.and_then(|io: TlsStream<TcpStream>| async {
|
.and_then(|io: TlsStream<TcpStream>| {
|
||||||
let proto = if let Some(protos) = io.get_ref().1.get_alpn_protocol() {
|
let proto = if let Some(protos) = io.get_ref().1.get_alpn_protocol() {
|
||||||
if protos.windows(2).any(|window| window == b"h2") {
|
if protos.windows(2).any(|window| window == b"h2") {
|
||||||
Protocol::Http2
|
Protocol::Http2
|
||||||
@ -305,37 +322,41 @@ mod rustls {
|
|||||||
Protocol::Http1
|
Protocol::Http1
|
||||||
};
|
};
|
||||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||||
Ok((io, proto, peer_addr))
|
ok((io, proto, peer_addr))
|
||||||
})
|
})
|
||||||
.and_then(self.map_err(TlsError::Service))
|
.and_then(self.map_err(TlsError::Service))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> ServiceFactory<(T, Protocol, Option<net::SocketAddr>)>
|
impl<T, S, B, X, U> ServiceFactory for HttpService<T, S, B, X, U>
|
||||||
for HttpService<T, S, B, X, U>
|
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Config = (), Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
<X::Service as Service<Request>>::Future: 'static,
|
<X::Service as Service>::Future: 'static,
|
||||||
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
|
U: ServiceFactory<
|
||||||
|
Config = (),
|
||||||
|
Request = (Request, Framed<T, h1::Codec>),
|
||||||
|
Response = (),
|
||||||
|
>,
|
||||||
U::Error: fmt::Display + Into<Error>,
|
U::Error: fmt::Display + Into<Error>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
<U::Service as Service<(Request, Framed<T, h1::Codec>)>>::Future: 'static,
|
<U::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
|
type Config = ();
|
||||||
|
type Request = (T, Protocol, Option<net::SocketAddr>);
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = DispatchError;
|
type Error = DispatchError;
|
||||||
type Config = ();
|
|
||||||
type Service = HttpServiceHandler<T, S::Service, B, X::Service, U::Service>;
|
|
||||||
type InitError = ();
|
type InitError = ();
|
||||||
|
type Service = HttpServiceHandler<T, S::Service, B, X::Service, U::Service>;
|
||||||
type Future = HttpServiceResponse<T, S, B, X, U>;
|
type Future = HttpServiceResponse<T, S, B, X, U>;
|
||||||
|
|
||||||
fn new_service(&self, _: ()) -> Self::Future {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
@ -345,21 +366,23 @@ where
|
|||||||
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())),
|
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())),
|
||||||
expect: None,
|
expect: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
|
on_connect: self.on_connect.clone(),
|
||||||
on_connect_ext: self.on_connect_ext.clone(),
|
on_connect_ext: self.on_connect_ext.clone(),
|
||||||
cfg: self.cfg.clone(),
|
cfg: self.cfg.clone(),
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[pin_project]
|
#[pin_project]
|
||||||
pub struct HttpServiceResponse<T, S, B, X, U>
|
pub struct HttpServiceResponse<
|
||||||
where
|
T,
|
||||||
S: ServiceFactory<Request>,
|
S: ServiceFactory,
|
||||||
X: ServiceFactory<Request>,
|
B,
|
||||||
U: ServiceFactory<(Request, Framed<T, h1::Codec>)>,
|
X: ServiceFactory,
|
||||||
{
|
U: ServiceFactory,
|
||||||
|
> {
|
||||||
#[pin]
|
#[pin]
|
||||||
fut: S::Future,
|
fut: S::Future,
|
||||||
#[pin]
|
#[pin]
|
||||||
@ -368,28 +391,29 @@ where
|
|||||||
fut_upg: Option<U::Future>,
|
fut_upg: Option<U::Future>,
|
||||||
expect: Option<X::Service>,
|
expect: Option<X::Service>,
|
||||||
upgrade: Option<U::Service>,
|
upgrade: Option<U::Service>,
|
||||||
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
_phantom: PhantomData<B>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> Future for HttpServiceResponse<T, S, B, X, U>
|
impl<T, S, B, X, U> Future for HttpServiceResponse<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: ServiceFactory<Request>,
|
S: ServiceFactory<Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
X: ServiceFactory<Request, Response = Request>,
|
X: ServiceFactory<Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
<X::Service as Service<Request>>::Future: 'static,
|
<X::Service as Service>::Future: 'static,
|
||||||
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Response = ()>,
|
U: ServiceFactory<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
<U::Service as Service<(Request, Framed<T, h1::Codec>)>>::Future: 'static,
|
<U::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
type Output =
|
type Output =
|
||||||
Result<HttpServiceHandler<T, S::Service, B, X::Service, U::Service>, ()>;
|
Result<HttpServiceHandler<T, S::Service, B, X::Service, U::Service>, ()>;
|
||||||
@ -412,7 +436,7 @@ where
|
|||||||
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
|
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
|
||||||
this = self.as_mut().project();
|
this = self.as_mut().project();
|
||||||
*this.upgrade = Some(upgrade);
|
*this.upgrade = Some(upgrade);
|
||||||
this.fut_upg.set(None);
|
this.fut_ex.set(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = ready!(this
|
let result = ready!(this
|
||||||
@ -427,6 +451,7 @@ where
|
|||||||
service,
|
service,
|
||||||
this.expect.take().unwrap(),
|
this.expect.take().unwrap(),
|
||||||
this.upgrade.take(),
|
this.upgrade.take(),
|
||||||
|
this.on_connect.clone(),
|
||||||
this.on_connect_ext.clone(),
|
this.on_connect_ext.clone(),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
@ -434,84 +459,68 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `Service` implementation for http transport
|
/// `Service` implementation for http transport
|
||||||
pub struct HttpServiceHandler<T, S, B, X, U>
|
pub struct HttpServiceHandler<T, S: Service, B, X: Service, U: Service> {
|
||||||
where
|
srv: CloneableService<S>,
|
||||||
S: Service<Request>,
|
expect: CloneableService<X>,
|
||||||
X: Service<Request>,
|
upgrade: Option<CloneableService<U>>,
|
||||||
U: Service<(Request, Framed<T, h1::Codec>)>,
|
|
||||||
{
|
|
||||||
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
|
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_phantom: PhantomData<B>,
|
_t: PhantomData<(T, B, X)>,
|
||||||
}
|
|
||||||
|
|
||||||
/// A collection of services that describe an HTTP request flow.
|
|
||||||
pub(super) struct HttpFlow<S, X, U> {
|
|
||||||
pub(super) service: S,
|
|
||||||
pub(super) expect: X,
|
|
||||||
pub(super) upgrade: Option<U>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, X, U> HttpFlow<S, X, U> {
|
|
||||||
pub(super) fn new(service: S, expect: X, upgrade: Option<U>) -> Rc<RefCell<Self>> {
|
|
||||||
Rc::new(RefCell::new(Self {
|
|
||||||
service,
|
|
||||||
expect,
|
|
||||||
upgrade,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
|
impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
fn new(
|
fn new(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
service: S,
|
srv: S,
|
||||||
expect: X,
|
expect: X,
|
||||||
upgrade: Option<U>,
|
upgrade: Option<U>,
|
||||||
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
) -> HttpServiceHandler<T, S, B, X, U> {
|
) -> HttpServiceHandler<T, S, B, X, U> {
|
||||||
HttpServiceHandler {
|
HttpServiceHandler {
|
||||||
cfg,
|
cfg,
|
||||||
|
on_connect,
|
||||||
on_connect_ext,
|
on_connect_ext,
|
||||||
flow: HttpFlow::new(service, expect, upgrade),
|
srv: CloneableService::new(srv),
|
||||||
_phantom: PhantomData,
|
expect: CloneableService::new(expect),
|
||||||
|
upgrade: upgrade.map(CloneableService::new),
|
||||||
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> Service<(T, Protocol, Option<net::SocketAddr>)>
|
impl<T, S, B, X, U> Service for HttpServiceHandler<T, S, B, X, U>
|
||||||
for HttpServiceHandler<T, S, B, X, U>
|
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display + Into<Error>,
|
U::Error: fmt::Display + Into<Error>,
|
||||||
{
|
{
|
||||||
|
type Request = (T, Protocol, Option<net::SocketAddr>);
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = DispatchError;
|
type Error = DispatchError;
|
||||||
type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
|
type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
let mut flow = self.flow.borrow_mut();
|
let ready = self
|
||||||
let ready = flow
|
|
||||||
.expect
|
.expect
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@ -521,8 +530,8 @@ where
|
|||||||
})?
|
})?
|
||||||
.is_ready();
|
.is_ready();
|
||||||
|
|
||||||
let ready = flow
|
let ready = self
|
||||||
.service
|
.srv
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
@ -532,7 +541,7 @@ where
|
|||||||
.is_ready()
|
.is_ready()
|
||||||
&& ready;
|
&& ready;
|
||||||
|
|
||||||
let ready = if let Some(ref mut upg) = flow.upgrade {
|
let ready = if let Some(ref mut upg) = self.upgrade {
|
||||||
upg.poll_ready(cx)
|
upg.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
@ -552,20 +561,22 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(
|
fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future {
|
||||||
&mut self,
|
let mut connect_extensions = Extensions::new();
|
||||||
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
|
|
||||||
) -> Self::Future {
|
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
|
||||||
let on_connect_data =
|
if let Some(ref handler) = self.on_connect_ext {
|
||||||
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
handler(&io, &mut connect_extensions);
|
||||||
|
}
|
||||||
|
|
||||||
match proto {
|
match proto {
|
||||||
Protocol::Http2 => HttpServiceHandlerResponse {
|
Protocol::Http2 => HttpServiceHandlerResponse {
|
||||||
state: State::H2Handshake(Some((
|
state: State::H2Handshake(Some((
|
||||||
server::handshake(io),
|
server::handshake(io),
|
||||||
self.cfg.clone(),
|
self.cfg.clone(),
|
||||||
self.flow.clone(),
|
self.srv.clone(),
|
||||||
on_connect_data,
|
deprecated_on_connect,
|
||||||
|
connect_extensions,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
))),
|
))),
|
||||||
},
|
},
|
||||||
@ -574,13 +585,14 @@ where
|
|||||||
state: State::H1(h1::Dispatcher::new(
|
state: State::H1(h1::Dispatcher::new(
|
||||||
io,
|
io,
|
||||||
self.cfg.clone(),
|
self.cfg.clone(),
|
||||||
self.flow.clone(),
|
self.srv.clone(),
|
||||||
on_connect_data,
|
self.expect.clone(),
|
||||||
|
self.upgrade.clone(),
|
||||||
|
deprecated_on_connect,
|
||||||
|
connect_extensions,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
|
||||||
proto => unimplemented!("Unsupported HTTP version: {:?}.", proto),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -588,24 +600,25 @@ where
|
|||||||
#[pin_project(project = StateProj)]
|
#[pin_project(project = StateProj)]
|
||||||
enum State<T, S, B, X, U>
|
enum State<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request = Request>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
H1(#[pin] h1::Dispatcher<T, S, B, X, U>),
|
H1(#[pin] h1::Dispatcher<T, S, B, X, U>),
|
||||||
H2(#[pin] Dispatcher<T, S, B, X, U>),
|
H2(#[pin] Dispatcher<T, S, B>),
|
||||||
H2Handshake(
|
H2Handshake(
|
||||||
Option<(
|
Option<(
|
||||||
Handshake<T, Bytes>,
|
Handshake<T, Bytes>,
|
||||||
ServiceConfig,
|
ServiceConfig,
|
||||||
Rc<RefCell<HttpFlow<S, X, U>>>,
|
CloneableService<S>,
|
||||||
OnConnectData,
|
Option<Box<dyn DataFactory>>,
|
||||||
|
Extensions,
|
||||||
Option<net::SocketAddr>,
|
Option<net::SocketAddr>,
|
||||||
)>,
|
)>,
|
||||||
),
|
),
|
||||||
@ -615,14 +628,14 @@ where
|
|||||||
pub struct HttpServiceHandlerResponse<T, S, B, X, U>
|
pub struct HttpServiceHandlerResponse<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
#[pin]
|
#[pin]
|
||||||
@ -632,42 +645,67 @@ where
|
|||||||
impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U>
|
impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request = Request>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request = Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
type Output = Result<(), DispatchError>;
|
type Output = Result<(), DispatchError>;
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
match self.as_mut().project().state.project() {
|
self.project().state.poll(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, S, B, X, U> State<T, S, B, X, U>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
S: Service<Request = Request>,
|
||||||
|
S::Error: Into<Error> + 'static,
|
||||||
|
S::Response: Into<Response<B>> + 'static,
|
||||||
|
B: MessageBody + 'static,
|
||||||
|
X: Service<Request = Request, Response = Request>,
|
||||||
|
X::Error: Into<Error>,
|
||||||
|
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
|
U::Error: fmt::Display,
|
||||||
|
{
|
||||||
|
fn poll(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Result<(), DispatchError>> {
|
||||||
|
match self.as_mut().project() {
|
||||||
StateProj::H1(disp) => disp.poll(cx),
|
StateProj::H1(disp) => disp.poll(cx),
|
||||||
StateProj::H2(disp) => disp.poll(cx),
|
StateProj::H2(disp) => disp.poll(cx),
|
||||||
StateProj::H2Handshake(data) => {
|
StateProj::H2Handshake(ref mut data) => {
|
||||||
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
|
let conn = if let Some(ref mut item) = data {
|
||||||
Ok(conn) => {
|
match Pin::new(&mut item.0).poll(cx) {
|
||||||
let (_, cfg, srv, on_connect_data, peer_addr) =
|
Poll::Ready(Ok(conn)) => conn,
|
||||||
data.take().unwrap();
|
Poll::Ready(Err(err)) => {
|
||||||
self.as_mut().project().state.set(State::H2(Dispatcher::new(
|
trace!("H2 handshake error: {}", err);
|
||||||
srv,
|
return Poll::Ready(Err(err.into()));
|
||||||
conn,
|
}
|
||||||
on_connect_data,
|
Poll::Pending => return Poll::Pending,
|
||||||
cfg,
|
|
||||||
None,
|
|
||||||
peer_addr,
|
|
||||||
)));
|
|
||||||
self.poll(cx)
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
} else {
|
||||||
trace!("H2 handshake error: {}", err);
|
panic!()
|
||||||
Poll::Ready(Err(err.into()))
|
};
|
||||||
}
|
let (_, cfg, srv, on_connect, on_connect_data, peer_addr) =
|
||||||
}
|
data.take().unwrap();
|
||||||
|
self.set(State::H2(Dispatcher::new(
|
||||||
|
srv,
|
||||||
|
conn,
|
||||||
|
on_connect,
|
||||||
|
on_connect_data,
|
||||||
|
cfg,
|
||||||
|
None,
|
||||||
|
peer_addr,
|
||||||
|
)));
|
||||||
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
//! Various testing helpers for use in internal and app tests.
|
//! Test Various helpers for Actix applications to use during testing.
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use std::{
|
use actix_codec::{AsyncRead, AsyncWrite};
|
||||||
cell::{Ref, RefCell},
|
|
||||||
convert::TryFrom,
|
|
||||||
io::{self, Read, Write},
|
|
||||||
pin::Pin,
|
|
||||||
rc::Rc,
|
|
||||||
str::FromStr,
|
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use http::header::{self, HeaderName, HeaderValue};
|
use http::header::{self, HeaderName, HeaderValue};
|
||||||
use http::{Error as HttpError, Method, Uri, Version};
|
use http::{Error as HttpError, Method, Uri, Version};
|
||||||
@ -188,7 +183,7 @@ fn parts(parts: &mut Option<Inner>) -> &mut Inner {
|
|||||||
parts.as_mut().expect("cannot reuse test request builder")
|
parts.as_mut().expect("cannot reuse test request builder")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Async I/O test buffer.
|
/// Async io buffer
|
||||||
pub struct TestBuffer {
|
pub struct TestBuffer {
|
||||||
pub read_buf: BytesMut,
|
pub read_buf: BytesMut,
|
||||||
pub write_buf: BytesMut,
|
pub write_buf: BytesMut,
|
||||||
@ -196,24 +191,24 @@ pub struct TestBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TestBuffer {
|
impl TestBuffer {
|
||||||
/// Create new `TestBuffer` instance with initial read buffer.
|
/// Create new TestBuffer instance
|
||||||
pub fn new<T>(data: T) -> Self
|
pub fn new<T>(data: T) -> TestBuffer
|
||||||
where
|
where
|
||||||
T: Into<BytesMut>,
|
BytesMut: From<T>,
|
||||||
{
|
{
|
||||||
Self {
|
TestBuffer {
|
||||||
read_buf: data.into(),
|
read_buf: BytesMut::from(data),
|
||||||
write_buf: BytesMut::new(),
|
write_buf: BytesMut::new(),
|
||||||
err: None,
|
err: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create new empty `TestBuffer` instance.
|
/// Create new empty TestBuffer instance
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> TestBuffer {
|
||||||
Self::new("")
|
TestBuffer::new("")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add data to read buffer.
|
/// Add extra data to read buffer.
|
||||||
pub fn extend_read_buf<T: AsRef<[u8]>>(&mut self, data: T) {
|
pub fn extend_read_buf<T: AsRef<[u8]>>(&mut self, data: T) {
|
||||||
self.read_buf.extend_from_slice(data.as_ref())
|
self.read_buf.extend_from_slice(data.as_ref())
|
||||||
}
|
}
|
||||||
@ -241,7 +236,6 @@ impl io::Write for TestBuffer {
|
|||||||
self.write_buf.extend(buf);
|
self.write_buf.extend(buf);
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -251,11 +245,9 @@ impl AsyncRead for TestBuffer {
|
|||||||
fn poll_read(
|
fn poll_read(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
_: &mut Context<'_>,
|
_: &mut Context<'_>,
|
||||||
buf: &mut ReadBuf<'_>,
|
buf: &mut [u8],
|
||||||
) -> Poll<io::Result<()>> {
|
) -> Poll<io::Result<usize>> {
|
||||||
let dst = buf.initialize_unfilled();
|
Poll::Ready(self.get_mut().read(buf))
|
||||||
let res = self.get_mut().read(dst).map(|n| buf.advance(n));
|
|
||||||
Poll::Ready(res)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,117 +268,3 @@ impl AsyncWrite for TestBuffer {
|
|||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Async I/O test buffer with ability to incrementally add to the read buffer.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct TestSeqBuffer(Rc<RefCell<TestSeqInner>>);
|
|
||||||
|
|
||||||
impl TestSeqBuffer {
|
|
||||||
/// Create new `TestBuffer` instance with initial read buffer.
|
|
||||||
pub fn new<T>(data: T) -> Self
|
|
||||||
where
|
|
||||||
T: Into<BytesMut>,
|
|
||||||
{
|
|
||||||
Self(Rc::new(RefCell::new(TestSeqInner {
|
|
||||||
read_buf: data.into(),
|
|
||||||
write_buf: BytesMut::new(),
|
|
||||||
err: None,
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create new empty `TestBuffer` instance.
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
Self::new("")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_buf(&self) -> Ref<'_, BytesMut> {
|
|
||||||
Ref::map(self.0.borrow(), |inner| &inner.read_buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_buf(&self) -> Ref<'_, BytesMut> {
|
|
||||||
Ref::map(self.0.borrow(), |inner| &inner.write_buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn err(&self) -> Ref<'_, Option<io::Error>> {
|
|
||||||
Ref::map(self.0.borrow(), |inner| &inner.err)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add data to read buffer.
|
|
||||||
pub fn extend_read_buf<T: AsRef<[u8]>>(&mut self, data: T) {
|
|
||||||
self.0
|
|
||||||
.borrow_mut()
|
|
||||||
.read_buf
|
|
||||||
.extend_from_slice(data.as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TestSeqInner {
|
|
||||||
read_buf: BytesMut,
|
|
||||||
write_buf: BytesMut,
|
|
||||||
err: Option<io::Error>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl io::Read for TestSeqBuffer {
|
|
||||||
fn read(&mut self, dst: &mut [u8]) -> Result<usize, io::Error> {
|
|
||||||
if self.0.borrow().read_buf.is_empty() {
|
|
||||||
if self.0.borrow().err.is_some() {
|
|
||||||
Err(self.0.borrow_mut().err.take().unwrap())
|
|
||||||
} else {
|
|
||||||
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let size = std::cmp::min(self.0.borrow().read_buf.len(), dst.len());
|
|
||||||
let b = self.0.borrow_mut().read_buf.split_to(size);
|
|
||||||
dst[..size].copy_from_slice(&b);
|
|
||||||
Ok(size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl io::Write for TestSeqBuffer {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
self.0.borrow_mut().write_buf.extend(buf);
|
|
||||||
Ok(buf.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncRead for TestSeqBuffer {
|
|
||||||
fn poll_read(
|
|
||||||
self: Pin<&mut Self>,
|
|
||||||
_: &mut Context<'_>,
|
|
||||||
buf: &mut ReadBuf<'_>,
|
|
||||||
) -> Poll<io::Result<()>> {
|
|
||||||
let dst = buf.initialize_unfilled();
|
|
||||||
let r = self.get_mut().read(dst);
|
|
||||||
match r {
|
|
||||||
Ok(n) => {
|
|
||||||
buf.advance(n);
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
Err(err) if err.kind() == io::ErrorKind::WouldBlock => Poll::Pending,
|
|
||||||
Err(err) => Poll::Ready(Err(err)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncWrite for TestSeqBuffer {
|
|
||||||
fn poll_write(
|
|
||||||
self: Pin<&mut Self>,
|
|
||||||
_: &mut Context<'_>,
|
|
||||||
buf: &[u8],
|
|
||||||
) -> Poll<io::Result<usize>> {
|
|
||||||
Poll::Ready(self.get_mut().write(buf))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,60 +1,47 @@
|
|||||||
use actix_codec::{Decoder, Encoder};
|
use actix_codec::{Decoder, Encoder};
|
||||||
use bitflags::bitflags;
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use bytestring::ByteString;
|
|
||||||
|
|
||||||
use super::frame::Parser;
|
use super::frame::Parser;
|
||||||
use super::proto::{CloseReason, OpCode};
|
use super::proto::{CloseReason, OpCode};
|
||||||
use super::ProtocolError;
|
use super::ProtocolError;
|
||||||
|
|
||||||
/// A WebSocket message.
|
/// `WebSocket` Message
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
/// Text message.
|
/// Text message
|
||||||
Text(ByteString),
|
Text(String),
|
||||||
|
/// Binary message
|
||||||
/// Binary message.
|
|
||||||
Binary(Bytes),
|
Binary(Bytes),
|
||||||
|
/// Continuation
|
||||||
/// Continuation.
|
|
||||||
Continuation(Item),
|
Continuation(Item),
|
||||||
|
/// Ping message
|
||||||
/// Ping message.
|
|
||||||
Ping(Bytes),
|
Ping(Bytes),
|
||||||
|
/// Pong message
|
||||||
/// Pong message.
|
|
||||||
Pong(Bytes),
|
Pong(Bytes),
|
||||||
|
/// Close message with optional reason
|
||||||
/// Close message with optional reason.
|
|
||||||
Close(Option<CloseReason>),
|
Close(Option<CloseReason>),
|
||||||
|
/// No-op. Useful for actix-net services
|
||||||
/// No-op. Useful for low-level services.
|
|
||||||
Nop,
|
Nop,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A WebSocket frame.
|
/// `WebSocket` frame
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Frame {
|
pub enum Frame {
|
||||||
/// Text frame. Note that the codec does not validate UTF-8 encoding.
|
/// Text frame, codec does not verify utf8 encoding
|
||||||
Text(Bytes),
|
Text(Bytes),
|
||||||
|
/// Binary frame
|
||||||
/// Binary frame.
|
|
||||||
Binary(Bytes),
|
Binary(Bytes),
|
||||||
|
/// Continuation
|
||||||
/// Continuation.
|
|
||||||
Continuation(Item),
|
Continuation(Item),
|
||||||
|
/// Ping message
|
||||||
/// Ping message.
|
|
||||||
Ping(Bytes),
|
Ping(Bytes),
|
||||||
|
/// Pong message
|
||||||
/// Pong message.
|
|
||||||
Pong(Bytes),
|
Pong(Bytes),
|
||||||
|
/// Close message with optional reason
|
||||||
/// Close message with optional reason.
|
|
||||||
Close(Option<CloseReason>),
|
Close(Option<CloseReason>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `WebSocket` continuation item.
|
/// `WebSocket` continuation item
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Item {
|
pub enum Item {
|
||||||
FirstText(Bytes),
|
FirstText(Bytes),
|
||||||
@ -64,13 +51,13 @@ pub enum Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
/// WebSocket protocol codec.
|
/// WebSockets protocol codec
|
||||||
pub struct Codec {
|
pub struct Codec {
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
max_size: usize,
|
max_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags::bitflags! {
|
||||||
struct Flags: u8 {
|
struct Flags: u8 {
|
||||||
const SERVER = 0b0000_0001;
|
const SERVER = 0b0000_0001;
|
||||||
const CONTINUATION = 0b0000_0010;
|
const CONTINUATION = 0b0000_0010;
|
||||||
@ -79,7 +66,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,
|
||||||
@ -87,9 +74,9 @@ impl Codec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set max frame size.
|
/// Set max frame size
|
||||||
///
|
///
|
||||||
/// By default max size is set to 64kb.
|
/// By default max size is set to 64kb
|
||||||
pub fn max_size(mut self, size: usize) -> Self {
|
pub fn max_size(mut self, size: usize) -> Self {
|
||||||
self.max_size = size;
|
self.max_size = size;
|
||||||
self
|
self
|
||||||
@ -197,7 +184,7 @@ impl Encoder<Message> for Codec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Message::Nop => {}
|
Message::Nop => (),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ use super::{Codec, Frame, Message};
|
|||||||
#[pin_project::pin_project]
|
#[pin_project::pin_project]
|
||||||
pub struct Dispatcher<S, T>
|
pub struct Dispatcher<S, T>
|
||||||
where
|
where
|
||||||
S: Service<Frame, Response = Message> + 'static,
|
S: Service<Request = Frame, Response = Message> + 'static,
|
||||||
T: AsyncRead + AsyncWrite,
|
T: AsyncRead + AsyncWrite,
|
||||||
{
|
{
|
||||||
#[pin]
|
#[pin]
|
||||||
@ -21,17 +21,17 @@ where
|
|||||||
impl<S, T> Dispatcher<S, T>
|
impl<S, T> Dispatcher<S, T>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite,
|
T: AsyncRead + AsyncWrite,
|
||||||
S: Service<Frame, Response = Message>,
|
S: Service<Request = Frame, Response = Message>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: 'static,
|
S::Error: 'static,
|
||||||
{
|
{
|
||||||
pub fn new<F: IntoService<S, Frame>>(io: T, service: F) -> Self {
|
pub fn new<F: IntoService<S>>(io: T, service: F) -> Self {
|
||||||
Dispatcher {
|
Dispatcher {
|
||||||
inner: InnerDispatcher::new(Framed::new(io, Codec::new()), service),
|
inner: InnerDispatcher::new(Framed::new(io, Codec::new()), service),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with<F: IntoService<S, Frame>>(framed: Framed<T, Codec>, service: F) -> Self {
|
pub fn with<F: IntoService<S>>(framed: Framed<T, Codec>, service: F) -> Self {
|
||||||
Dispatcher {
|
Dispatcher {
|
||||||
inner: InnerDispatcher::new(framed, service),
|
inner: InnerDispatcher::new(framed, service),
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ where
|
|||||||
impl<S, T> Future for Dispatcher<S, T>
|
impl<S, T> Future for Dispatcher<S, T>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite,
|
T: AsyncRead + AsyncWrite,
|
||||||
S: Service<Frame, Response = Message>,
|
S: Service<Request = Frame, Response = Message>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: 'static,
|
S::Error: 'static,
|
||||||
{
|
{
|
||||||
|
@ -125,7 +125,7 @@ impl Parser {
|
|||||||
debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame.");
|
debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame.");
|
||||||
return Ok(Some((true, OpCode::Close, None)));
|
return Ok(Some((true, OpCode::Close, None)));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmask
|
// unmask
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
//! WebSocket protocol support.
|
//! WebSocket protocol support.
|
||||||
//!
|
//!
|
||||||
//! To setup a WebSocket, first do web socket handshake then on success convert `Payload` into a
|
//! To setup a `WebSocket`, first do web socket handshake then on success
|
||||||
//! `WsStream` stream and then use `WsWriter` to communicate with the peer.
|
//! convert `Payload` into a `WsStream` stream and then use `WsWriter` to
|
||||||
|
//! communicate with the peer.
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use derive_more::{Display, Error, From};
|
use derive_more::{Display, From};
|
||||||
use http::{header, Method, StatusCode};
|
use http::{header, Method, StatusCode};
|
||||||
|
|
||||||
use crate::error::ResponseError;
|
use crate::error::ResponseError;
|
||||||
@ -23,103 +23,86 @@ pub use self::dispatcher::Dispatcher;
|
|||||||
pub use self::frame::Parser;
|
pub use self::frame::Parser;
|
||||||
pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode};
|
pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode};
|
||||||
|
|
||||||
/// WebSocket protocol errors.
|
/// Websocket protocol errors
|
||||||
#[derive(Debug, Display, From, Error)]
|
#[derive(Debug, Display, From)]
|
||||||
pub enum ProtocolError {
|
pub enum ProtocolError {
|
||||||
/// Received an unmasked frame from client.
|
/// Received an unmasked frame from client
|
||||||
#[display(fmt = "Received an unmasked frame from client.")]
|
#[display(fmt = "Received an unmasked frame from client")]
|
||||||
UnmaskedFrame,
|
UnmaskedFrame,
|
||||||
|
/// Received a masked frame from server
|
||||||
/// Received a masked frame from server.
|
#[display(fmt = "Received a masked frame from server")]
|
||||||
#[display(fmt = "Received a masked frame from server.")]
|
|
||||||
MaskedFrame,
|
MaskedFrame,
|
||||||
|
/// Encountered invalid opcode
|
||||||
/// Encountered invalid opcode.
|
#[display(fmt = "Invalid opcode: {}", _0)]
|
||||||
#[display(fmt = "Invalid opcode: {}.", _0)]
|
InvalidOpcode(u8),
|
||||||
InvalidOpcode(#[error(not(source))] u8),
|
|
||||||
|
|
||||||
/// Invalid control frame length
|
/// Invalid control frame length
|
||||||
#[display(fmt = "Invalid control frame length: {}.", _0)]
|
#[display(fmt = "Invalid control frame length: {}", _0)]
|
||||||
InvalidLength(#[error(not(source))] usize),
|
InvalidLength(usize),
|
||||||
|
/// Bad web socket op code
|
||||||
/// Bad opcode.
|
#[display(fmt = "Bad web socket op code")]
|
||||||
#[display(fmt = "Bad opcode.")]
|
|
||||||
BadOpCode,
|
BadOpCode,
|
||||||
|
|
||||||
/// A payload reached size limit.
|
/// A payload reached size limit.
|
||||||
#[display(fmt = "A payload reached size limit.")]
|
#[display(fmt = "A payload reached size limit.")]
|
||||||
Overflow,
|
Overflow,
|
||||||
|
/// Continuation is not started
|
||||||
/// Continuation is not started.
|
|
||||||
#[display(fmt = "Continuation is not started.")]
|
#[display(fmt = "Continuation is not started.")]
|
||||||
ContinuationNotStarted,
|
ContinuationNotStarted,
|
||||||
|
/// Received new continuation but it is already started
|
||||||
/// Received new continuation but it is already started.
|
#[display(fmt = "Received new continuation but it is already started")]
|
||||||
#[display(fmt = "Received new continuation but it is already started.")]
|
|
||||||
ContinuationStarted,
|
ContinuationStarted,
|
||||||
|
/// Unknown continuation fragment
|
||||||
/// Unknown continuation fragment.
|
#[display(fmt = "Unknown continuation fragment.")]
|
||||||
#[display(fmt = "Unknown continuation fragment: {}.", _0)]
|
ContinuationFragment(OpCode),
|
||||||
ContinuationFragment(#[error(not(source))] OpCode),
|
/// Io error
|
||||||
|
#[display(fmt = "io error: {}", _0)]
|
||||||
/// I/O error.
|
|
||||||
#[display(fmt = "I/O error: {}", _0)]
|
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ProtocolError {}
|
||||||
|
|
||||||
impl ResponseError for ProtocolError {}
|
impl ResponseError for ProtocolError {}
|
||||||
|
|
||||||
/// WebSocket handshake errors
|
/// Websocket handshake errors
|
||||||
#[derive(PartialEq, Debug, Display)]
|
#[derive(PartialEq, Debug, Display)]
|
||||||
pub enum HandshakeError {
|
pub enum HandshakeError {
|
||||||
/// Only get method is allowed.
|
/// Only get method is allowed
|
||||||
#[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,
|
||||||
|
/// Connection header is not set to upgrade
|
||||||
/// Connection header is not set to upgrade.
|
#[display(fmt = "Connection upgrade is expected")]
|
||||||
#[display(fmt = "Connection upgrade is expected.")]
|
|
||||||
NoConnectionUpgrade,
|
NoConnectionUpgrade,
|
||||||
|
/// Websocket version header is not set
|
||||||
/// WebSocket version header is not set.
|
#[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,
|
||||||
|
/// Websocket key is not set or wrong
|
||||||
/// WebSocket key is not set or wrong.
|
#[display(fmt = "Unknown websocket key")]
|
||||||
#[display(fmt = "Unknown websocket key.")]
|
|
||||||
BadWebsocketKey,
|
BadWebsocketKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseError for HandshakeError {
|
impl ResponseError for HandshakeError {
|
||||||
fn error_response(&self) -> Response {
|
fn error_response(&self) -> Response {
|
||||||
match self {
|
match *self {
|
||||||
HandshakeError::GetMethodRequired => Response::MethodNotAllowed()
|
HandshakeError::GetMethodRequired => Response::MethodNotAllowed()
|
||||||
.header(header::ALLOW, "GET")
|
.header(header::ALLOW, "GET")
|
||||||
.finish(),
|
.finish(),
|
||||||
|
|
||||||
HandshakeError::NoWebsocketUpgrade => Response::BadRequest()
|
HandshakeError::NoWebsocketUpgrade => Response::BadRequest()
|
||||||
.reason("No WebSocket UPGRADE header found")
|
.reason("No WebSocket UPGRADE header found")
|
||||||
.finish(),
|
.finish(),
|
||||||
|
|
||||||
HandshakeError::NoConnectionUpgrade => Response::BadRequest()
|
HandshakeError::NoConnectionUpgrade => Response::BadRequest()
|
||||||
.reason("No CONNECTION upgrade")
|
.reason("No CONNECTION upgrade")
|
||||||
.finish(),
|
.finish(),
|
||||||
|
|
||||||
HandshakeError::NoVersionHeader => Response::BadRequest()
|
HandshakeError::NoVersionHeader => Response::BadRequest()
|
||||||
.reason("Websocket version header is required")
|
.reason("Websocket version header is required")
|
||||||
.finish(),
|
.finish(),
|
||||||
|
|
||||||
HandshakeError::UnsupportedVersion => Response::BadRequest()
|
HandshakeError::UnsupportedVersion => Response::BadRequest()
|
||||||
.reason("Unsupported version")
|
.reason("Unsupported version")
|
||||||
.finish(),
|
.finish(),
|
||||||
|
|
||||||
HandshakeError::BadWebsocketKey => {
|
HandshakeError::BadWebsocketKey => {
|
||||||
Response::BadRequest().reason("Handshake error").finish()
|
Response::BadRequest().reason("Handshake error").finish()
|
||||||
}
|
}
|
||||||
@ -214,13 +197,13 @@ mod tests {
|
|||||||
let req = TestRequest::default().method(Method::POST).finish();
|
let req = TestRequest::default().method(Method::POST).finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::GetMethodRequired,
|
HandshakeError::GetMethodRequired,
|
||||||
verify_handshake(req.head()).unwrap_err(),
|
verify_handshake(req.head()).err().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default().finish();
|
let req = TestRequest::default().finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoWebsocketUpgrade,
|
HandshakeError::NoWebsocketUpgrade,
|
||||||
verify_handshake(req.head()).unwrap_err(),
|
verify_handshake(req.head()).err().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
@ -228,7 +211,7 @@ mod tests {
|
|||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoWebsocketUpgrade,
|
HandshakeError::NoWebsocketUpgrade,
|
||||||
verify_handshake(req.head()).unwrap_err(),
|
verify_handshake(req.head()).err().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
@ -239,7 +222,7 @@ mod tests {
|
|||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoConnectionUpgrade,
|
HandshakeError::NoConnectionUpgrade,
|
||||||
verify_handshake(req.head()).unwrap_err(),
|
verify_handshake(req.head()).err().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
@ -254,7 +237,7 @@ mod tests {
|
|||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoVersionHeader,
|
HandshakeError::NoVersionHeader,
|
||||||
verify_handshake(req.head()).unwrap_err(),
|
verify_handshake(req.head()).err().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
@ -273,7 +256,7 @@ mod tests {
|
|||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::UnsupportedVersion,
|
HandshakeError::UnsupportedVersion,
|
||||||
verify_handshake(req.head()).unwrap_err(),
|
verify_handshake(req.head()).err().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
@ -292,7 +275,7 @@ mod tests {
|
|||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::BadWebsocketKey,
|
HandshakeError::BadWebsocketKey,
|
||||||
verify_handshake(req.head()).unwrap_err(),
|
verify_handshake(req.head()).err().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
@ -1,34 +1,28 @@
|
|||||||
use std::convert::{From, Into};
|
use std::convert::{From, Into};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// Operation codes as part of RFC6455.
|
use self::OpCode::*;
|
||||||
|
/// Operation codes as part of rfc6455.
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||||
pub enum OpCode {
|
pub enum OpCode {
|
||||||
/// Indicates a continuation frame of a fragmented message.
|
/// Indicates a continuation frame of a fragmented message.
|
||||||
Continue,
|
Continue,
|
||||||
|
|
||||||
/// Indicates a text data frame.
|
/// Indicates a text data frame.
|
||||||
Text,
|
Text,
|
||||||
|
|
||||||
/// Indicates a binary data frame.
|
/// Indicates a binary data frame.
|
||||||
Binary,
|
Binary,
|
||||||
|
|
||||||
/// Indicates a close control frame.
|
/// Indicates a close control frame.
|
||||||
Close,
|
Close,
|
||||||
|
|
||||||
/// Indicates a ping control frame.
|
/// Indicates a ping control frame.
|
||||||
Ping,
|
Ping,
|
||||||
|
|
||||||
/// Indicates a pong control frame.
|
/// Indicates a pong control frame.
|
||||||
Pong,
|
Pong,
|
||||||
|
|
||||||
/// Indicates an invalid opcode was received.
|
/// Indicates an invalid opcode was received.
|
||||||
Bad,
|
Bad,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for OpCode {
|
impl fmt::Display for OpCode {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
use self::OpCode::*;
|
|
||||||
match *self {
|
match *self {
|
||||||
Continue => write!(f, "CONTINUE"),
|
Continue => write!(f, "CONTINUE"),
|
||||||
Text => write!(f, "TEXT"),
|
Text => write!(f, "TEXT"),
|
||||||
@ -41,10 +35,9 @@ impl fmt::Display for OpCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<OpCode> for u8 {
|
impl Into<u8> for OpCode {
|
||||||
fn from(op: OpCode) -> u8 {
|
fn into(self) -> u8 {
|
||||||
use self::OpCode::*;
|
match self {
|
||||||
match op {
|
|
||||||
Continue => 0,
|
Continue => 0,
|
||||||
Text => 1,
|
Text => 1,
|
||||||
Binary => 2,
|
Binary => 2,
|
||||||
@ -61,7 +54,6 @@ impl From<OpCode> for u8 {
|
|||||||
|
|
||||||
impl From<u8> for OpCode {
|
impl From<u8> for OpCode {
|
||||||
fn from(byte: u8) -> OpCode {
|
fn from(byte: u8) -> OpCode {
|
||||||
use self::OpCode::*;
|
|
||||||
match byte {
|
match byte {
|
||||||
0 => Continue,
|
0 => Continue,
|
||||||
1 => Text,
|
1 => Text,
|
||||||
@ -74,6 +66,7 @@ impl From<u8> for OpCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use self::CloseCode::*;
|
||||||
/// 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)]
|
||||||
@ -139,10 +132,9 @@ pub enum CloseCode {
|
|||||||
Other(u16),
|
Other(u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CloseCode> for u16 {
|
impl Into<u16> for CloseCode {
|
||||||
fn from(code: CloseCode) -> u16 {
|
fn into(self) -> u16 {
|
||||||
use self::CloseCode::*;
|
match self {
|
||||||
match code {
|
|
||||||
Normal => 1000,
|
Normal => 1000,
|
||||||
Away => 1001,
|
Away => 1001,
|
||||||
Protocol => 1002,
|
Protocol => 1002,
|
||||||
@ -163,7 +155,6 @@ impl From<CloseCode> for u16 {
|
|||||||
|
|
||||||
impl From<u16> for CloseCode {
|
impl From<u16> for CloseCode {
|
||||||
fn from(code: u16) -> CloseCode {
|
fn from(code: u16) -> CloseCode {
|
||||||
use self::CloseCode::*;
|
|
||||||
match code {
|
match code {
|
||||||
1000 => Normal,
|
1000 => Normal,
|
||||||
1001 => Away,
|
1001 => Away,
|
||||||
@ -188,7 +179,6 @@ impl From<u16> for CloseCode {
|
|||||||
pub struct CloseReason {
|
pub struct CloseReason {
|
||||||
/// Exit code
|
/// Exit code
|
||||||
pub code: CloseCode,
|
pub code: CloseCode,
|
||||||
|
|
||||||
/// Optional description of the exit code
|
/// Optional description of the exit code
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
}
|
}
|
||||||
@ -232,7 +222,7 @@ mod test {
|
|||||||
macro_rules! opcode_into {
|
macro_rules! opcode_into {
|
||||||
($from:expr => $opcode:pat) => {
|
($from:expr => $opcode:pat) => {
|
||||||
match OpCode::from($from) {
|
match OpCode::from($from) {
|
||||||
e @ $opcode => {}
|
e @ $opcode => (),
|
||||||
e => unreachable!("{:?}", e),
|
e => unreachable!("{:?}", e),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -242,7 +232,7 @@ mod test {
|
|||||||
($from:expr => $opcode:pat) => {
|
($from:expr => $opcode:pat) => {
|
||||||
let res: u8 = $from.into();
|
let res: u8 = $from.into();
|
||||||
match res {
|
match res {
|
||||||
e @ $opcode => {}
|
e @ $opcode => (),
|
||||||
e => unreachable!("{:?}", e),
|
e => unreachable!("{:?}", e),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use actix_http::{http, HttpService, Request, Response};
|
use actix_service::ServiceFactory;
|
||||||
use actix_http_test::test_server;
|
|
||||||
use actix_service::ServiceFactoryExt;
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::future::{self, ok};
|
use futures_util::future::{self, ok};
|
||||||
|
|
||||||
|
use actix_http::{http, HttpService, Request, Response};
|
||||||
|
use actix_http_test::test_server;
|
||||||
|
|
||||||
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
|
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
|
||||||
Hello World Hello World Hello World Hello World Hello World \
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
Hello World Hello World Hello World Hello World Hello World \
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
#![cfg(feature = "openssl")]
|
#![cfg(feature = "openssl")]
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
use actix_http_test::test_server;
|
||||||
|
use actix_service::{fn_service, ServiceFactory};
|
||||||
|
|
||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
|
use futures_util::future::{err, ok, ready};
|
||||||
|
use futures_util::stream::{once, Stream, StreamExt};
|
||||||
|
use open_ssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod};
|
||||||
|
|
||||||
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::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_service::{fn_service, ServiceFactoryExt};
|
|
||||||
use bytes::{Bytes, BytesMut};
|
|
||||||
use futures_util::future::{err, ok, ready};
|
|
||||||
use futures_util::stream::{once, Stream, StreamExt};
|
|
||||||
use open_ssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod};
|
|
||||||
|
|
||||||
async fn load_body<S>(stream: S) -> Result<BytesMut, PayloadError>
|
async fn load_body<S>(stream: S) -> Result<BytesMut, PayloadError>
|
||||||
where
|
where
|
||||||
@ -408,8 +410,10 @@ async fn test_h2_service_error() {
|
|||||||
async fn test_h2_on_connect() {
|
async fn test_h2_on_connect() {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
|
.on_connect(|_| 10usize)
|
||||||
.on_connect_ext(|_, data| data.insert(20isize))
|
.on_connect_ext(|_, data| data.insert(20isize))
|
||||||
.h2(|req: Request| {
|
.h2(|req: Request| {
|
||||||
|
assert!(req.extensions().contains::<usize>());
|
||||||
assert!(req.extensions().contains::<isize>());
|
assert!(req.extensions().contains::<isize>());
|
||||||
ok::<_, ()>(Response::Ok().finish())
|
ok::<_, ()>(Response::Ok().finish())
|
||||||
})
|
})
|
||||||
|
@ -3,7 +3,7 @@ use std::time::Duration;
|
|||||||
use std::{net, thread};
|
use std::{net, thread};
|
||||||
|
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
use actix_rt::time::sleep;
|
use actix_rt::time::delay_for;
|
||||||
use actix_service::fn_service;
|
use actix_service::fn_service;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::future::{self, err, ok, ready, FutureExt};
|
use futures_util::future::{self, err, ok, ready, FutureExt};
|
||||||
@ -88,7 +88,7 @@ async fn test_expect_continue_h1() {
|
|||||||
let srv = test_server(|| {
|
let srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.expect(fn_service(|req: Request| {
|
.expect(fn_service(|req: Request| {
|
||||||
sleep(Duration::from_millis(20)).then(move |_| {
|
delay_for(Duration::from_millis(20)).then(move |_| {
|
||||||
if req.head().uri.query() == Some("yes=") {
|
if req.head().uri.query() == Some("yes=") {
|
||||||
ok(req)
|
ok(req)
|
||||||
} else {
|
} else {
|
||||||
@ -662,8 +662,10 @@ async fn test_h1_service_error() {
|
|||||||
async fn test_h1_on_connect() {
|
async fn test_h1_on_connect() {
|
||||||
let srv = test_server(|| {
|
let srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
|
.on_connect(|_| 10usize)
|
||||||
.on_connect_ext(|_, data| data.insert(20isize))
|
.on_connect_ext(|_, data| data.insert(20isize))
|
||||||
.h1(|req: Request| {
|
.h1(|req: Request| {
|
||||||
|
assert!(req.extensions().contains::<usize>());
|
||||||
assert!(req.extensions().contains::<isize>());
|
assert!(req.extensions().contains::<isize>());
|
||||||
future::ok::<_, ()>(Response::Ok().finish())
|
future::ok::<_, ()>(Response::Ok().finish())
|
||||||
})
|
})
|
||||||
|
@ -36,10 +36,11 @@ impl<T> Clone for WsService<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Service<(Request, Framed<T, h1::Codec>)> for WsService<T>
|
impl<T> Service for WsService<T>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin + 'static,
|
T: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
{
|
{
|
||||||
|
type Request = (Request, Framed<T, h1::Codec>);
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Pin<Box<dyn Future<Output = Result<(), Error>>>>;
|
type Future = Pin<Box<dyn Future<Output = Result<(), Error>>>>;
|
||||||
@ -49,10 +50,7 @@ where
|
|||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(
|
fn call(&mut self, (req, mut framed): Self::Request) -> Self::Future {
|
||||||
&mut self,
|
|
||||||
(req, mut framed): (Request, Framed<T, h1::Codec>),
|
|
||||||
) -> Self::Future {
|
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
let res = ws::handshake(req.head()).unwrap().message_body(());
|
let res = ws::handshake(req.head()).unwrap().message_body(());
|
||||||
|
|
||||||
@ -74,7 +72,7 @@ async fn service(msg: ws::Frame) -> Result<ws::Message, Error> {
|
|||||||
let msg = match msg {
|
let msg = match msg {
|
||||||
ws::Frame::Ping(msg) => ws::Message::Pong(msg),
|
ws::Frame::Ping(msg) => ws::Message::Pong(msg),
|
||||||
ws::Frame::Text(text) => {
|
ws::Frame::Text(text) => {
|
||||||
ws::Message::Text(String::from_utf8_lossy(&text).into_owned().into())
|
ws::Message::Text(String::from_utf8_lossy(&text).to_string())
|
||||||
}
|
}
|
||||||
ws::Frame::Binary(bin) => ws::Message::Binary(bin),
|
ws::Frame::Binary(bin) => ws::Message::Binary(bin),
|
||||||
ws::Frame::Continuation(item) => ws::Message::Continuation(item),
|
ws::Frame::Continuation(item) => ws::Message::Continuation(item),
|
||||||
@ -101,7 +99,10 @@ async fn test_simple() {
|
|||||||
|
|
||||||
// client service
|
// client service
|
||||||
let mut framed = srv.ws().await.unwrap();
|
let mut framed = srv.ws().await.unwrap();
|
||||||
framed.send(ws::Message::Text("text".into())).await.unwrap();
|
framed
|
||||||
|
.send(ws::Message::Text("text".to_string()))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let (item, mut framed) = framed.into_future().await;
|
let (item, mut framed) = framed.into_future().await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
item.unwrap().unwrap(),
|
item.unwrap().unwrap(),
|
||||||
|
@ -1,21 +1,14 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
* Fix multipart consuming payload before header checks #1513
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.1 - 2021-01-07
|
## 3.0.0 - 2020-09-11
|
||||||
* Fix multipart consuming payload before header checks. [#1513]
|
* No significant changes from `3.0.0-beta.2`.
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
|
||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
|
||||||
[#1513]: https://github.com/actix/actix-web/pull/1513
|
|
||||||
|
|
||||||
|
|
||||||
## 0.3.0 - 2020-09-11
|
## 3.0.0-beta.2 - 2020-09-10
|
||||||
* No significant changes from `0.3.0-beta.2`.
|
|
||||||
|
|
||||||
|
|
||||||
## 0.3.0-beta.2 - 2020-09-10
|
|
||||||
* Update `actix-*` dependencies to latest versions.
|
* Update `actix-*` dependencies to latest versions.
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-multipart"
|
name = "actix-multipart"
|
||||||
version = "0.4.0-beta.1"
|
version = "0.3.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Multipart support for actix web framework."
|
description = "Multipart support for actix web framework."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -16,17 +16,17 @@ 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 = "3.0.0", default-features = false }
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-service = "1.0.6"
|
||||||
|
actix-utils = "2.0.0"
|
||||||
bytes = "1"
|
bytes = "0.5.3"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.2"
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.5", default-features = false }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
twoway = "0.2"
|
twoway = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "1.0.0"
|
||||||
actix-http = "3.0.0-beta.1"
|
actix-http = "2.0.0"
|
||||||
|
@ -326,7 +326,7 @@ impl InnerMultipart {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
// read field headers for next field
|
// read field headers for next field
|
||||||
@ -725,7 +725,9 @@ impl Drop for Safety {
|
|||||||
if Rc::strong_count(&self.payload) != self.level {
|
if Rc::strong_count(&self.payload) != self.level {
|
||||||
self.clean.set(true);
|
self.clean.set(true);
|
||||||
}
|
}
|
||||||
self.task.wake();
|
if let Some(task) = self.task.take() {
|
||||||
|
task.wake()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -835,7 +837,7 @@ mod tests {
|
|||||||
async fn test_boundary() {
|
async fn test_boundary() {
|
||||||
let headers = HeaderMap::new();
|
let headers = HeaderMap::new();
|
||||||
match Multipart::boundary(&headers) {
|
match Multipart::boundary(&headers) {
|
||||||
Err(MultipartError::NoContentType) => {}
|
Err(MultipartError::NoContentType) => (),
|
||||||
_ => unreachable!("should not happen"),
|
_ => unreachable!("should not happen"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -846,7 +848,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
match Multipart::boundary(&headers) {
|
match Multipart::boundary(&headers) {
|
||||||
Err(MultipartError::ParseContentType) => {}
|
Err(MultipartError::ParseContentType) => (),
|
||||||
_ => unreachable!("should not happen"),
|
_ => unreachable!("should not happen"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -856,7 +858,7 @@ mod tests {
|
|||||||
header::HeaderValue::from_static("multipart/mixed"),
|
header::HeaderValue::from_static("multipart/mixed"),
|
||||||
);
|
);
|
||||||
match Multipart::boundary(&headers) {
|
match Multipart::boundary(&headers) {
|
||||||
Err(MultipartError::Boundary) => {}
|
Err(MultipartError::Boundary) => (),
|
||||||
_ => unreachable!("should not happen"),
|
_ => unreachable!("should not happen"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -956,17 +958,17 @@ mod tests {
|
|||||||
let mut multipart = Multipart::new(&headers, payload);
|
let mut multipart = Multipart::new(&headers, payload);
|
||||||
|
|
||||||
match multipart.next().await.unwrap() {
|
match multipart.next().await.unwrap() {
|
||||||
Ok(_) => {}
|
Ok(_) => (),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
match multipart.next().await.unwrap() {
|
match multipart.next().await.unwrap() {
|
||||||
Ok(_) => {}
|
Ok(_) => (),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
match multipart.next().await {
|
match multipart.next().await {
|
||||||
None => {}
|
None => (),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -993,7 +995,7 @@ mod tests {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
match field.next().await {
|
match field.next().await {
|
||||||
None => {}
|
None => (),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1010,7 +1012,7 @@ mod tests {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
match field.next().await {
|
match field.next().await {
|
||||||
None => {}
|
None => (),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1018,7 +1020,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match multipart.next().await {
|
match multipart.next().await {
|
||||||
None => {}
|
None => (),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1066,7 +1068,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match multipart.next().await {
|
match multipart.next().await {
|
||||||
None => {}
|
None => (),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,7 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
* Upgrade `pin-project` to `1.0`.
|
||||||
|
|
||||||
## 4.0.0-beta.1 - 2021-01-07
|
|
||||||
* Update `pin-project` to `1.0`.
|
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
|
||||||
* `WebsocketContext::text` now takes an `Into<bytestring::ByteString>`. [#1864]
|
|
||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
|
||||||
[#1864]: https://github.com/actix/actix-web/pull/1864
|
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0 - 2020-09-11
|
## 3.0.0 - 2020-09-11
|
||||||
* No significant changes from `3.0.0-beta.2`.
|
* No significant changes from `3.0.0-beta.2`.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web-actors"
|
name = "actix-web-actors"
|
||||||
version = "4.0.0-beta.1"
|
version = "3.0.0"
|
||||||
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 framework."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -16,18 +16,16 @@ name = "actix_web_actors"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "0.11.0-beta.1"
|
actix = "0.10.0"
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-web = { version = "3.0.0", default-features = false }
|
||||||
actix-http = "3.0.0-beta.1"
|
actix-http = "2.0.0"
|
||||||
actix-web = { version = "4.0.0-beta.1", default-features = false }
|
actix-codec = "0.3.0"
|
||||||
|
bytes = "0.5.2"
|
||||||
bytes = "1"
|
futures-channel = { version = "0.3.5", default-features = false }
|
||||||
bytestring = "1"
|
futures-core = { version = "0.3.5", default-features = false }
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
|
||||||
pin-project = "1.0.0"
|
pin-project = "1.0.0"
|
||||||
tokio = { version = "1", features = ["sync"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "1.1.1"
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.5", default-features = false }
|
||||||
|
@ -12,8 +12,8 @@ use actix::{
|
|||||||
};
|
};
|
||||||
use actix_web::error::Error;
|
use actix_web::error::Error;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
use futures_channel::oneshot::Sender;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use tokio::sync::oneshot::Sender;
|
|
||||||
|
|
||||||
/// Execution context for http actors
|
/// Execution context for http actors
|
||||||
pub struct HttpContext<A>
|
pub struct HttpContext<A>
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
//! Websocket integration.
|
//! Websocket integration
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::{collections::VecDeque, convert::TryFrom};
|
|
||||||
|
|
||||||
use actix::dev::{
|
use actix::dev::{
|
||||||
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler,
|
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler,
|
||||||
@ -25,11 +24,10 @@ use actix_web::error::{Error, PayloadError};
|
|||||||
use actix_web::http::{header, Method, StatusCode};
|
use actix_web::http::{header, Method, StatusCode};
|
||||||
use actix_web::{HttpRequest, HttpResponse};
|
use actix_web::{HttpRequest, HttpResponse};
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use bytestring::ByteString;
|
use futures_channel::oneshot::Sender;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use tokio::sync::oneshot::Sender;
|
|
||||||
|
|
||||||
/// Perform WebSocket handshake and start actor.
|
/// Do websocket handshake and start ws 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>>
|
||||||
@ -40,7 +38,7 @@ where
|
|||||||
Ok(res.streaming(WebsocketContext::create(actor, stream)))
|
Ok(res.streaming(WebsocketContext::create(actor, stream)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform WebSocket handshake and start actor.
|
/// Do websocket handshake and start ws actor.
|
||||||
///
|
///
|
||||||
/// `req` is an HTTP Request that should be requesting a websocket protocol
|
/// `req` is an HTTP Request that should be requesting a websocket protocol
|
||||||
/// change. `stream` should be a `Bytes` stream (such as
|
/// change. `stream` should be a `Bytes` stream (such as
|
||||||
@ -340,13 +338,13 @@ where
|
|||||||
|
|
||||||
/// Send text frame
|
/// Send text frame
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn text(&mut self, text: impl Into<ByteString>) {
|
pub fn text<T: Into<String>>(&mut self, text: T) {
|
||||||
self.write_raw(Message::Text(text.into()));
|
self.write_raw(Message::Text(text.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send binary frame
|
/// Send binary frame
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn binary(&mut self, data: impl Into<Bytes>) {
|
pub fn binary<B: Into<Bytes>>(&mut self, data: B) {
|
||||||
self.write_raw(Message::Binary(data.into()));
|
self.write_raw(Message::Binary(data.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -530,14 +528,16 @@ where
|
|||||||
}
|
}
|
||||||
Some(frm) => {
|
Some(frm) => {
|
||||||
let msg = match frm {
|
let msg = match frm {
|
||||||
Frame::Text(data) => {
|
Frame::Text(data) => Message::Text(
|
||||||
Message::Text(ByteString::try_from(data).map_err(|e| {
|
std::str::from_utf8(&data)
|
||||||
ProtocolError::Io(io::Error::new(
|
.map_err(|e| {
|
||||||
io::ErrorKind::Other,
|
ProtocolError::Io(io::Error::new(
|
||||||
format!("{}", e),
|
io::ErrorKind::Other,
|
||||||
))
|
format!("{}", e),
|
||||||
})?)
|
))
|
||||||
}
|
})?
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
Frame::Binary(data) => Message::Binary(data),
|
Frame::Binary(data) => Message::Binary(data),
|
||||||
Frame::Ping(s) => Message::Ping(s),
|
Frame::Ping(s) => Message::Ping(s),
|
||||||
Frame::Pong(s) => Message::Pong(s),
|
Frame::Pong(s) => Message::Pong(s),
|
||||||
|
@ -21,7 +21,7 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Ws {
|
|||||||
ws::Message::Text(text) => ctx.text(text),
|
ws::Message::Text(text) => ctx.text(text),
|
||||||
ws::Message::Binary(bin) => ctx.binary(bin),
|
ws::Message::Binary(bin) => ctx.binary(bin),
|
||||||
ws::Message::Close(reason) => ctx.close(reason),
|
ws::Message::Close(reason) => ctx.close(reason),
|
||||||
_ => {}
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,7 +38,10 @@ async fn test_simple() {
|
|||||||
|
|
||||||
// client service
|
// client service
|
||||||
let mut framed = srv.ws().await.unwrap();
|
let mut framed = srv.ws().await.unwrap();
|
||||||
framed.send(ws::Message::Text("text".into())).await.unwrap();
|
framed
|
||||||
|
.send(ws::Message::Text("text".to_string()))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let item = framed.next().await.unwrap().unwrap();
|
let item = framed.next().await.unwrap().unwrap();
|
||||||
assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text")));
|
assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text")));
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0 - 2020-09-20
|
## 0.4.0 - 2020-09-20
|
||||||
|
@ -19,8 +19,8 @@ syn = { version = "1", features = ["full", "parsing"] }
|
|||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "1.1.1"
|
||||||
actix-web = "4.0.0-beta.1"
|
actix-web = "3.0.0"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.5", default-features = false }
|
||||||
trybuild = "1"
|
trybuild = "1"
|
||||||
rustversion = "1"
|
rustversion = "1"
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
> Helper and convenience macros for Actix Web
|
> Helper and convenience 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)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
||||||
[](https://travis-ci.org/actix/actix-web)
|
[](https://travis-ci.org/actix/actix-web)
|
||||||
[](https://codecov.io/gh/actix/actix-web)
|
[](https://codecov.io/gh/actix/actix-web)
|
||||||
[](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)
|
||||||
@ -14,7 +14,7 @@
|
|||||||
- [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)
|
- 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.42 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.
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
//! are re-exported.
|
//! are re-exported.
|
||||||
//!
|
//!
|
||||||
//! # 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 [main] macro docs.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```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
|
||||||
@ -34,7 +34,7 @@
|
|||||||
//!
|
//!
|
||||||
//! # Multiple Method Handlers
|
//! # Multiple Method Handlers
|
||||||
//! 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 [route] macro docs.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # use actix_web::HttpResponse;
|
//! # use actix_web::HttpResponse;
|
||||||
@ -46,15 +46,17 @@
|
|||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! [actix-web attributes docs]: https://docs.rs/actix-web/*/actix_web/#attributes
|
//! [actix-web attributes docs]: https://docs.rs/actix-web/*/actix_web/#attributes
|
||||||
//! [GET]: macro@get
|
//! [main]: attr.main.html
|
||||||
//! [POST]: macro@post
|
//! [route]: attr.route.html
|
||||||
//! [PUT]: macro@put
|
//! [GET]: attr.get.html
|
||||||
//! [HEAD]: macro@head
|
//! [POST]: attr.post.html
|
||||||
//! [CONNECT]: macro@macro@connect
|
//! [PUT]: attr.put.html
|
||||||
//! [OPTIONS]: macro@options
|
//! [DELETE]: attr.delete.html
|
||||||
//! [TRACE]: macro@trace
|
//! [HEAD]: attr.head.html
|
||||||
//! [PATCH]: macro@patch
|
//! [CONNECT]: attr.connect.html
|
||||||
//! [DELETE]: macro@delete
|
//! [OPTIONS]: attr.options.html
|
||||||
|
//! [TRACE]: attr.trace.html
|
||||||
|
//! [PATCH]: attr.patch.html
|
||||||
|
|
||||||
#![recursion_limit = "512"]
|
#![recursion_limit = "512"]
|
||||||
|
|
||||||
|
@ -88,16 +88,17 @@ async fn route_test() -> impl Responder {
|
|||||||
|
|
||||||
pub struct ChangeStatusCode;
|
pub struct ChangeStatusCode;
|
||||||
|
|
||||||
impl<S, B> Transform<S, ServiceRequest> for ChangeStatusCode
|
impl<S, B> Transform<S> for ChangeStatusCode
|
||||||
where
|
where
|
||||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
B: 'static,
|
B: 'static,
|
||||||
{
|
{
|
||||||
|
type Request = ServiceRequest;
|
||||||
type Response = ServiceResponse<B>;
|
type Response = ServiceResponse<B>;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Transform = ChangeStatusCodeMiddleware<S>;
|
|
||||||
type InitError = ();
|
type InitError = ();
|
||||||
|
type Transform = ChangeStatusCodeMiddleware<S>;
|
||||||
type Future = future::Ready<Result<Self::Transform, Self::InitError>>;
|
type Future = future::Ready<Result<Self::Transform, Self::InitError>>;
|
||||||
|
|
||||||
fn new_transform(&self, service: S) -> Self::Future {
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
@ -109,12 +110,13 @@ pub struct ChangeStatusCodeMiddleware<S> {
|
|||||||
service: S,
|
service: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, B> Service<ServiceRequest> for ChangeStatusCodeMiddleware<S>
|
impl<S, B> Service for ChangeStatusCodeMiddleware<S>
|
||||||
where
|
where
|
||||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
B: 'static,
|
B: 'static,
|
||||||
{
|
{
|
||||||
|
type Request = ServiceRequest;
|
||||||
type Response = ServiceResponse<B>;
|
type Response = ServiceResponse<B>;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
|
@ -6,15 +6,31 @@ fn compile_macros() {
|
|||||||
t.compile_fail("tests/trybuild/simple-fail.rs");
|
t.compile_fail("tests/trybuild/simple-fail.rs");
|
||||||
|
|
||||||
t.pass("tests/trybuild/route-ok.rs");
|
t.pass("tests/trybuild/route-ok.rs");
|
||||||
|
|
||||||
|
test_route_duplicate_unexpected_method(&t);
|
||||||
|
test_route_missing_method(&t)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustversion::stable(1.42)]
|
||||||
|
fn test_route_missing_method(t: &trybuild::TestCases) {
|
||||||
|
t.compile_fail("tests/trybuild/route-missing-method-fail-msrv.rs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustversion::not(stable(1.42))]
|
||||||
|
#[rustversion::not(nightly)]
|
||||||
|
fn test_route_missing_method(t: &trybuild::TestCases) {
|
||||||
t.compile_fail("tests/trybuild/route-missing-method-fail.rs");
|
t.compile_fail("tests/trybuild/route-missing-method-fail.rs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustversion::nightly]
|
||||||
|
fn test_route_missing_method(_t: &trybuild::TestCases) {}
|
||||||
|
|
||||||
|
// FIXME: Re-test them on nightly once rust-lang/rust#77993 is fixed.
|
||||||
|
#[rustversion::not(nightly)]
|
||||||
|
fn test_route_duplicate_unexpected_method(t: &trybuild::TestCases) {
|
||||||
t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs");
|
t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs");
|
||||||
t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs");
|
t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs");
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[rustversion::not(nightly)]
|
#[rustversion::nightly]
|
||||||
// fn skip_on_nightly(t: &trybuild::TestCases) {
|
fn test_route_duplicate_unexpected_method(_t: &trybuild::TestCases) {}
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[rustversion::nightly]
|
|
||||||
// fn skip_on_nightly(_t: &trybuild::TestCases) {}
|
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
route-missing-method-fail.rs
|
@ -0,0 +1,11 @@
|
|||||||
|
error: The #[route(..)] macro requires at least one `method` attribute
|
||||||
|
--> $DIR/route-missing-method-fail-msrv.rs:3:1
|
||||||
|
|
|
||||||
|
3 | #[route("/")]
|
||||||
|
| ^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error[E0425]: cannot find value `index` in this scope
|
||||||
|
--> $DIR/route-missing-method-fail-msrv.rs:12:49
|
||||||
|
|
|
||||||
|
12 | let srv = test::start(|| App::new().service(index));
|
||||||
|
| ^^^^^ not found in this scope
|
@ -1,15 +1,6 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.1 - 2021-01-07
|
|
||||||
### Changed
|
|
||||||
* Update `rand` to `0.8`
|
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
|
||||||
* Update `rust-tls` to `0.19`. [#1813]
|
|
||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
|
||||||
|
|
||||||
|
|
||||||
## 2.0.3 - 2020-11-29
|
## 2.0.3 - 2020-11-29
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "awc"
|
name = "awc"
|
||||||
version = "3.0.0-beta.1"
|
version = "2.0.3"
|
||||||
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"
|
||||||
@ -37,38 +37,36 @@ rustls = ["rust-tls", "actix-http/rustls"]
|
|||||||
compress = ["actix-http/compress"]
|
compress = ["actix-http/compress"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.3.0"
|
||||||
actix-service = "2.0.0-beta.2"
|
actix-service = "1.0.6"
|
||||||
actix-http = "3.0.0-beta.1"
|
actix-http = "2.2.0"
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "1.0.0"
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bytes = "1"
|
bytes = "0.5.3"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.2"
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.5", default-features = false }
|
||||||
log =" 0.4"
|
log =" 0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
rand = "0.8"
|
rand = "0.7"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
open-ssl = { version = "0.10", package = "openssl", optional = true }
|
open-ssl = { version = "0.10", package = "openssl", optional = true }
|
||||||
rust-tls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
|
rust-tls = { version = "0.18.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# TODO: actix is temporary added as dev dep for actix-macro reason.
|
actix-connect = { version = "2.0.0", features = ["openssl"] }
|
||||||
# Can be removed when it does not impact tests.
|
actix-web = { version = "3.0.0", features = ["openssl"] }
|
||||||
actix = "0.11.0-beta.1"
|
actix-http = { version = "2.0.0", features = ["openssl"] }
|
||||||
actix-web = { version = "4.0.0-beta.1", features = ["openssl"] }
|
actix-http-test = { version = "2.0.0", features = ["openssl"] }
|
||||||
actix-http = { version = "3.0.0-beta.1", features = ["openssl"] }
|
actix-utils = "2.0.0"
|
||||||
actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] }
|
actix-server = "1.0.0"
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-tls = { version = "2.0.0", features = ["openssl", "rustls"] }
|
||||||
actix-server = "2.0.0-beta.2"
|
brotli = "3.3.3"
|
||||||
actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] }
|
|
||||||
brotli2 = "0.3.2"
|
|
||||||
flate2 = "1.0.13"
|
flate2 = "1.0.13"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.5", default-features = false }
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
webpki = "0.21"
|
webpki = "0.21"
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
- [API Documentation](https://docs.rs/awc)
|
- [API Documentation](https://docs.rs/awc)
|
||||||
- [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https)
|
- [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https)
|
||||||
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.46.0
|
- Minimum Supported Rust Version (MSRV): 1.42.0
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
```rust
|
```rust
|
||||||
|
@ -51,7 +51,7 @@ impl ClientBuilder {
|
|||||||
/// Use custom connector service.
|
/// Use custom connector service.
|
||||||
pub fn connector<T>(mut self, connector: T) -> Self
|
pub fn connector<T>(mut self, connector: T) -> Self
|
||||||
where
|
where
|
||||||
T: Service<HttpConnect, Error = ConnectError> + 'static,
|
T: Service<Request = HttpConnect, Error = ConnectError> + 'static,
|
||||||
T::Response: Connection,
|
T::Response: Connection,
|
||||||
<T::Response as Connection>::Future: 'static,
|
<T::Response as Connection>::Future: 'static,
|
||||||
T::Future: 'static,
|
T::Future: 'static,
|
||||||
|
@ -2,9 +2,9 @@ use std::future::Future;
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::{fmt, io, net};
|
use std::{fmt, io, mem, net};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
use actix_http::body::Body;
|
use actix_http::body::Body;
|
||||||
use actix_http::client::{
|
use actix_http::client::{
|
||||||
Connect as ClientConnect, ConnectError, Connection, SendRequestError,
|
Connect as ClientConnect, ConnectError, Connection, SendRequestError,
|
||||||
@ -70,7 +70,7 @@ pub(crate) trait Connect {
|
|||||||
|
|
||||||
impl<T> Connect for ConnectorWrapper<T>
|
impl<T> Connect for ConnectorWrapper<T>
|
||||||
where
|
where
|
||||||
T: Service<ClientConnect, Error = ConnectError>,
|
T: Service<Request = ClientConnect, Error = ConnectError>,
|
||||||
T::Response: Connection,
|
T::Response: Connection,
|
||||||
<T::Response as Connection>::Io: 'static,
|
<T::Response as Connection>::Io: 'static,
|
||||||
<T::Response as Connection>::Future: 'static,
|
<T::Response as Connection>::Future: 'static,
|
||||||
@ -221,11 +221,18 @@ impl fmt::Debug for BoxedSocket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncRead for BoxedSocket {
|
impl AsyncRead for BoxedSocket {
|
||||||
|
unsafe fn prepare_uninitialized_buffer(
|
||||||
|
&self,
|
||||||
|
buf: &mut [mem::MaybeUninit<u8>],
|
||||||
|
) -> bool {
|
||||||
|
self.0.as_read().prepare_uninitialized_buffer(buf)
|
||||||
|
}
|
||||||
|
|
||||||
fn poll_read(
|
fn poll_read(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
buf: &mut ReadBuf<'_>,
|
buf: &mut [u8],
|
||||||
) -> Poll<io::Result<()>> {
|
) -> Poll<io::Result<usize>> {
|
||||||
Pin::new(self.get_mut().0.as_read_mut()).poll_read(cx, buf)
|
Pin::new(self.get_mut().0.as_read_mut()).poll_read(cx, buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@
|
|||||||
//! .await?;
|
//! .await?;
|
||||||
//!
|
//!
|
||||||
//! connection
|
//! connection
|
||||||
//! .send(awc::ws::Message::Text("Echo".into()))
|
//! .send(awc::ws::Message::Text("Echo".to_string()))
|
||||||
//! .await?;
|
//! .await?;
|
||||||
//! let response = connection.next().await.unwrap()?;
|
//! let response = connection.next().await.unwrap()?;
|
||||||
//! # assert_eq!(response, awc::ws::Frame::Text("Echo".as_bytes().into()));
|
//! # assert_eq!(response, awc::ws::Frame::Text("Echo".as_bytes().into()));
|
||||||
|
@ -523,7 +523,7 @@ impl ClientRequest {
|
|||||||
return Err(InvalidUrl::MissingScheme.into());
|
return Err(InvalidUrl::MissingScheme.into());
|
||||||
} else if let Some(scheme) = uri.scheme() {
|
} else if let Some(scheme) = uri.scheme() {
|
||||||
match scheme.as_str() {
|
match scheme.as_str() {
|
||||||
"http" | "ws" | "https" | "wss" => {}
|
"http" | "ws" | "https" | "wss" => (),
|
||||||
_ => return Err(InvalidUrl::UnknownScheme.into()),
|
_ => return Err(InvalidUrl::UnknownScheme.into()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -234,7 +234,7 @@ pub struct JsonBody<S, U> {
|
|||||||
length: Option<usize>,
|
length: Option<usize>,
|
||||||
err: Option<JsonPayloadError>,
|
err: Option<JsonPayloadError>,
|
||||||
fut: Option<ReadBody<S>>,
|
fut: Option<ReadBody<S>>,
|
||||||
_phantom: PhantomData<U>,
|
_t: PhantomData<U>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, U> JsonBody<S, U>
|
impl<S, U> JsonBody<S, U>
|
||||||
@ -255,7 +255,7 @@ where
|
|||||||
length: None,
|
length: None,
|
||||||
fut: None,
|
fut: None,
|
||||||
err: Some(JsonPayloadError::ContentType),
|
err: Some(JsonPayloadError::ContentType),
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +272,7 @@ where
|
|||||||
length: len,
|
length: len,
|
||||||
err: None,
|
err: None,
|
||||||
fut: Some(ReadBody::new(req.take_payload(), 65536)),
|
fut: Some(ReadBody::new(req.take_payload(), 65536)),
|
||||||
_phantom: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,14 +370,14 @@ mod tests {
|
|||||||
async fn test_body() {
|
async fn test_body() {
|
||||||
let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish();
|
let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish();
|
||||||
match req.body().await.err().unwrap() {
|
match req.body().await.err().unwrap() {
|
||||||
PayloadError::UnknownLength => {}
|
PayloadError::UnknownLength => (),
|
||||||
_ => 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"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,7 +390,7 @@ mod tests {
|
|||||||
.set_payload(Bytes::from_static(b"11111111111111"))
|
.set_payload(Bytes::from_static(b"11111111111111"))
|
||||||
.finish();
|
.finish();
|
||||||
match req.body().limit(5).await.err().unwrap() {
|
match req.body().limit(5).await.err().unwrap() {
|
||||||
PayloadError::Overflow => {}
|
PayloadError::Overflow => (),
|
||||||
_ => unreachable!("error"),
|
_ => unreachable!("error"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use std::rc::Rc;
|
|||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use actix_rt::time::{sleep, Sleep};
|
use actix_rt::time::{delay_for, Delay};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use derive_more::From;
|
use derive_more::From;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
@ -33,18 +33,18 @@ pub(crate) enum PrepForSendingError {
|
|||||||
Http(HttpError),
|
Http(HttpError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PrepForSendingError> for FreezeRequestError {
|
impl Into<FreezeRequestError> for PrepForSendingError {
|
||||||
fn from(err: PrepForSendingError) -> FreezeRequestError {
|
fn into(self) -> FreezeRequestError {
|
||||||
match err {
|
match self {
|
||||||
PrepForSendingError::Url(e) => FreezeRequestError::Url(e),
|
PrepForSendingError::Url(e) => FreezeRequestError::Url(e),
|
||||||
PrepForSendingError::Http(e) => FreezeRequestError::Http(e),
|
PrepForSendingError::Http(e) => FreezeRequestError::Http(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PrepForSendingError> for SendRequestError {
|
impl Into<SendRequestError> for PrepForSendingError {
|
||||||
fn from(err: PrepForSendingError) -> SendRequestError {
|
fn into(self) -> SendRequestError {
|
||||||
match err {
|
match self {
|
||||||
PrepForSendingError::Url(e) => SendRequestError::Url(e),
|
PrepForSendingError::Url(e) => SendRequestError::Url(e),
|
||||||
PrepForSendingError::Http(e) => SendRequestError::Http(e),
|
PrepForSendingError::Http(e) => SendRequestError::Http(e),
|
||||||
}
|
}
|
||||||
@ -56,8 +56,7 @@ impl From<PrepForSendingError> for SendRequestError {
|
|||||||
pub enum SendClientRequest {
|
pub enum SendClientRequest {
|
||||||
Fut(
|
Fut(
|
||||||
Pin<Box<dyn Future<Output = Result<ClientResponse, SendRequestError>>>>,
|
Pin<Box<dyn Future<Output = Result<ClientResponse, SendRequestError>>>>,
|
||||||
// FIXME: use a pinned Sleep instead of box.
|
Option<Delay>,
|
||||||
Option<Pin<Box<Sleep>>>,
|
|
||||||
bool,
|
bool,
|
||||||
),
|
),
|
||||||
Err(Option<SendRequestError>),
|
Err(Option<SendRequestError>),
|
||||||
@ -69,7 +68,7 @@ impl SendClientRequest {
|
|||||||
response_decompress: bool,
|
response_decompress: bool,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
) -> SendClientRequest {
|
) -> SendClientRequest {
|
||||||
let delay = timeout.map(|d| Box::pin(sleep(d)));
|
let delay = timeout.map(delay_for);
|
||||||
SendClientRequest::Fut(send, delay, response_decompress)
|
SendClientRequest::Fut(send, delay, response_decompress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,7 +85,7 @@ impl Future for SendClientRequest {
|
|||||||
SendClientRequest::Fut(send, delay, response_decompress) => {
|
SendClientRequest::Fut(send, delay, response_decompress) => {
|
||||||
if delay.is_some() {
|
if delay.is_some() {
|
||||||
match Pin::new(delay.as_mut().unwrap()).poll(cx) {
|
match Pin::new(delay.as_mut().unwrap()).poll(cx) {
|
||||||
Poll::Pending => {}
|
Poll::Pending => (),
|
||||||
_ => return Poll::Ready(Err(SendRequestError::Timeout)),
|
_ => return Poll::Ready(Err(SendRequestError::Timeout)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,7 +126,7 @@ impl Future for SendClientRequest {
|
|||||||
SendClientRequest::Fut(send, delay, _) => {
|
SendClientRequest::Fut(send, delay, _) => {
|
||||||
if delay.is_some() {
|
if delay.is_some() {
|
||||||
match Pin::new(delay.as_mut().unwrap()).poll(cx) {
|
match Pin::new(delay.as_mut().unwrap()).poll(cx) {
|
||||||
Poll::Pending => {}
|
Poll::Pending => (),
|
||||||
_ => return Poll::Ready(Err(SendRequestError::Timeout)),
|
_ => return Poll::Ready(Err(SendRequestError::Timeout)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! Websockets client
|
//! Websockets client
|
||||||
//!
|
//!
|
||||||
//! Type definitions required to use [`awc::Client`](super::Client) as a WebSocket client.
|
//! Type definitions required to use [`awc::Client`](../struct.Client.html) as a WebSocket client.
|
||||||
//!
|
//!
|
||||||
//! # Example
|
//! # Example
|
||||||
//!
|
//!
|
||||||
@ -17,7 +17,7 @@
|
|||||||
//! .unwrap();
|
//! .unwrap();
|
||||||
//!
|
//!
|
||||||
//! connection
|
//! connection
|
||||||
//! .send(ws::Message::Text("Echo".into()))
|
//! .send(ws::Message::Text("Echo".to_string()))
|
||||||
//! .await
|
//! .await
|
||||||
//! .unwrap();
|
//! .unwrap();
|
||||||
//! let response = connection.next().await.unwrap().unwrap();
|
//! let response = connection.next().await.unwrap().unwrap();
|
||||||
@ -70,14 +70,9 @@ impl WebsocketsRequest {
|
|||||||
<Uri as TryFrom<U>>::Error: Into<HttpError>,
|
<Uri as TryFrom<U>>::Error: Into<HttpError>,
|
||||||
{
|
{
|
||||||
let mut err = None;
|
let mut err = None;
|
||||||
|
let mut head = RequestHead::default();
|
||||||
#[allow(clippy::field_reassign_with_default)]
|
head.method = Method::GET;
|
||||||
let mut head = {
|
head.version = Version::HTTP_11;
|
||||||
let mut head = RequestHead::default();
|
|
||||||
head.method = Method::GET;
|
|
||||||
head.version = Version::HTTP_11;
|
|
||||||
head
|
|
||||||
};
|
|
||||||
|
|
||||||
match Uri::try_from(uri) {
|
match Uri::try_from(uri) {
|
||||||
Ok(uri) => head.uri = uri,
|
Ok(uri) => head.uri = uri,
|
||||||
@ -259,7 +254,7 @@ impl WebsocketsRequest {
|
|||||||
return Err(InvalidUrl::MissingScheme.into());
|
return Err(InvalidUrl::MissingScheme.into());
|
||||||
} else if let Some(scheme) = uri.scheme() {
|
} else if let Some(scheme) = uri.scheme() {
|
||||||
match scheme.as_str() {
|
match scheme.as_str() {
|
||||||
"http" | "ws" | "https" | "wss" => {}
|
"http" | "ws" | "https" | "wss" => (),
|
||||||
_ => return Err(InvalidUrl::UnknownScheme.into()),
|
_ => return Err(InvalidUrl::UnknownScheme.into()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use brotli2::write::BrotliEncoder;
|
use brotli::CompressorWriter as BrotliEncoder;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use flate2::write::GzEncoder;
|
use flate2::write::GzEncoder;
|
||||||
@ -108,14 +108,14 @@ async fn test_form() {
|
|||||||
async fn test_timeout() {
|
async fn test_timeout() {
|
||||||
let srv = test::start(|| {
|
let srv = test::start(|| {
|
||||||
App::new().service(web::resource("/").route(web::to(|| async {
|
App::new().service(web::resource("/").route(web::to(|| async {
|
||||||
actix_rt::time::sleep(Duration::from_millis(200)).await;
|
actix_rt::time::delay_for(Duration::from_millis(200)).await;
|
||||||
Ok::<_, Error>(HttpResponse::Ok().body(STR))
|
Ok::<_, Error>(HttpResponse::Ok().body(STR))
|
||||||
})))
|
})))
|
||||||
});
|
});
|
||||||
|
|
||||||
let connector = awc::Connector::new()
|
let connector = awc::Connector::new()
|
||||||
.connector(actix_tls::connect::new_connector(
|
.connector(actix_connect::new_connector(
|
||||||
actix_tls::connect::start_default_resolver().await.unwrap(),
|
actix_connect::start_default_resolver().await.unwrap(),
|
||||||
))
|
))
|
||||||
.timeout(Duration::from_secs(15))
|
.timeout(Duration::from_secs(15))
|
||||||
.finish();
|
.finish();
|
||||||
@ -127,7 +127,7 @@ async fn test_timeout() {
|
|||||||
|
|
||||||
let request = client.get(srv.url("/")).send();
|
let request = client.get(srv.url("/")).send();
|
||||||
match request.await {
|
match request.await {
|
||||||
Err(SendRequestError::Timeout) => {}
|
Err(SendRequestError::Timeout) => (),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,7 +136,7 @@ async fn test_timeout() {
|
|||||||
async fn test_timeout_override() {
|
async fn test_timeout_override() {
|
||||||
let srv = test::start(|| {
|
let srv = test::start(|| {
|
||||||
App::new().service(web::resource("/").route(web::to(|| async {
|
App::new().service(web::resource("/").route(web::to(|| async {
|
||||||
actix_rt::time::sleep(Duration::from_millis(200)).await;
|
actix_rt::time::delay_for(Duration::from_millis(200)).await;
|
||||||
Ok::<_, Error>(HttpResponse::Ok().body(STR))
|
Ok::<_, Error>(HttpResponse::Ok().body(STR))
|
||||||
})))
|
})))
|
||||||
});
|
});
|
||||||
@ -149,7 +149,7 @@ async fn test_timeout_override() {
|
|||||||
.timeout(Duration::from_millis(50))
|
.timeout(Duration::from_millis(50))
|
||||||
.send();
|
.send();
|
||||||
match request.await {
|
match request.await {
|
||||||
Err(SendRequestError::Timeout) => {}
|
Err(SendRequestError::Timeout) => (),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -480,7 +480,6 @@ async fn test_client_gzip_encoding_large_random() {
|
|||||||
let data = rand::thread_rng()
|
let data = rand::thread_rng()
|
||||||
.sample_iter(&rand::distributions::Alphanumeric)
|
.sample_iter(&rand::distributions::Alphanumeric)
|
||||||
.take(100_000)
|
.take(100_000)
|
||||||
.map(char::from)
|
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
|
||||||
let srv = test::start(|| {
|
let srv = test::start(|| {
|
||||||
@ -507,9 +506,10 @@ async fn test_client_gzip_encoding_large_random() {
|
|||||||
async fn test_client_brotli_encoding() {
|
async fn test_client_brotli_encoding() {
|
||||||
let srv = test::start(|| {
|
let srv = test::start(|| {
|
||||||
App::new().service(web::resource("/").route(web::to(|data: Bytes| {
|
App::new().service(web::resource("/").route(web::to(|data: Bytes| {
|
||||||
let mut e = BrotliEncoder::new(Vec::new(), 5);
|
let mut e = BrotliEncoder::new(Vec::new(), 8096, 5, 22);
|
||||||
e.write_all(&data).unwrap();
|
e.write_all(&data).unwrap();
|
||||||
let data = e.finish().unwrap();
|
e.flush().unwrap();
|
||||||
|
let data = e.into_inner();
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.header("content-encoding", "br")
|
.header("content-encoding", "br")
|
||||||
.body(data)
|
.body(data)
|
||||||
@ -530,14 +530,13 @@ async fn test_client_brotli_encoding_large_random() {
|
|||||||
let data = rand::thread_rng()
|
let data = rand::thread_rng()
|
||||||
.sample_iter(&rand::distributions::Alphanumeric)
|
.sample_iter(&rand::distributions::Alphanumeric)
|
||||||
.take(70_000)
|
.take(70_000)
|
||||||
.map(char::from)
|
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
|
||||||
let srv = test::start(|| {
|
let srv = test::start(|| {
|
||||||
App::new().service(web::resource("/").route(web::to(|data: Bytes| {
|
App::new().service(web::resource("/").route(web::to(|data: Bytes| {
|
||||||
let mut e = BrotliEncoder::new(Vec::new(), 5);
|
let mut e = BrotliEncoder::new(Vec::new(), 8096, 5, 22);
|
||||||
e.write_all(&data).unwrap();
|
e.write_all(&data).unwrap();
|
||||||
let data = e.finish().unwrap();
|
let data = e.into_inner();
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.header("content-encoding", "br")
|
.header("content-encoding", "br")
|
||||||
.body(data)
|
.body(data)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#![cfg(feature = "openssl")]
|
#![cfg(feature = "openssl")]
|
||||||
use actix_http::HttpService;
|
use actix_http::HttpService;
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
use actix_service::{map_config, ServiceFactoryExt};
|
use actix_service::{map_config, ServiceFactory};
|
||||||
use actix_web::http::Version;
|
use actix_web::http::Version;
|
||||||
use actix_web::{dev::AppConfig, web, App, HttpResponse};
|
use actix_web::{dev::AppConfig, web, App, HttpResponse};
|
||||||
use open_ssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode};
|
use open_ssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode};
|
||||||
|
@ -4,7 +4,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use actix_http::HttpService;
|
use actix_http::HttpService;
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
use actix_service::{map_config, pipeline_factory, ServiceFactoryExt};
|
use actix_service::{map_config, pipeline_factory, ServiceFactory};
|
||||||
use actix_web::http::Version;
|
use actix_web::http::Version;
|
||||||
use actix_web::{dev::AppConfig, web, App, HttpResponse};
|
use actix_web::{dev::AppConfig, web, App, HttpResponse};
|
||||||
use futures_util::future::ok;
|
use futures_util::future::ok;
|
||||||
|
@ -4,7 +4,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use actix_http::HttpService;
|
use actix_http::HttpService;
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
use actix_service::{map_config, pipeline_factory, ServiceFactoryExt};
|
use actix_service::{map_config, pipeline_factory, ServiceFactory};
|
||||||
use actix_web::http::Version;
|
use actix_web::http::Version;
|
||||||
use actix_web::{dev::AppConfig, web, App, HttpResponse};
|
use actix_web::{dev::AppConfig, web, App, HttpResponse};
|
||||||
use futures_util::future::ok;
|
use futures_util::future::ok;
|
||||||
|
@ -11,7 +11,7 @@ async fn ws_service(req: ws::Frame) -> Result<ws::Message, io::Error> {
|
|||||||
match req {
|
match req {
|
||||||
ws::Frame::Ping(msg) => Ok(ws::Message::Pong(msg)),
|
ws::Frame::Ping(msg) => Ok(ws::Message::Pong(msg)),
|
||||||
ws::Frame::Text(text) => Ok(ws::Message::Text(
|
ws::Frame::Text(text) => Ok(ws::Message::Text(
|
||||||
String::from_utf8(Vec::from(text.as_ref())).unwrap().into(),
|
String::from_utf8(Vec::from(text.as_ref())).unwrap(),
|
||||||
)),
|
)),
|
||||||
ws::Frame::Binary(bin) => Ok(ws::Message::Binary(bin)),
|
ws::Frame::Binary(bin) => Ok(ws::Message::Binary(bin)),
|
||||||
ws::Frame::Close(reason) => Ok(ws::Message::Close(reason)),
|
ws::Frame::Close(reason) => Ok(ws::Message::Close(reason)),
|
||||||
@ -43,7 +43,10 @@ async fn test_simple() {
|
|||||||
|
|
||||||
// client service
|
// client service
|
||||||
let mut framed = srv.ws().await.unwrap();
|
let mut framed = srv.ws().await.unwrap();
|
||||||
framed.send(ws::Message::Text("text".into())).await.unwrap();
|
framed
|
||||||
|
.send(ws::Message::Text("text".to_string()))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let item = framed.next().await.unwrap().unwrap();
|
let item = framed.next().await.unwrap().unwrap();
|
||||||
assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text")));
|
assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text")));
|
||||||
|
|
||||||
|
@ -29,22 +29,18 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
|
|||||||
fn bench_async_burst(c: &mut Criterion) {
|
fn bench_async_burst(c: &mut Criterion) {
|
||||||
// We are using System here, since Runtime requires preinitialized tokio
|
// We are using System here, since Runtime requires preinitialized tokio
|
||||||
// Maybe add to actix_rt docs
|
// Maybe add to actix_rt docs
|
||||||
let rt = actix_rt::System::new("test");
|
let mut rt = actix_rt::System::new("test");
|
||||||
|
|
||||||
let srv = rt.block_on(async {
|
let srv = test::start(|| {
|
||||||
test::start(|| {
|
App::new()
|
||||||
App::new().service(
|
.service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
|
||||||
web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let url = srv.url("/");
|
let url = srv.url("/");
|
||||||
|
|
||||||
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 =
|
let client = Client::new().get(url.clone()).freeze().unwrap();
|
||||||
rt.block_on(async { Client::new().get(url.clone()).freeze().unwrap() });
|
|
||||||
|
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
// benchmark body
|
// benchmark body
|
||||||
|
@ -23,9 +23,10 @@ use actix_web::test::{init_service, ok_service, TestRequest};
|
|||||||
/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us]
|
/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us]
|
||||||
pub fn bench_async_service<S>(c: &mut Criterion, srv: S, name: &str)
|
pub fn bench_async_service<S>(c: &mut Criterion, srv: S, name: &str)
|
||||||
where
|
where
|
||||||
S: Service<ServiceRequest, Response = ServiceResponse, Error = Error> + 'static,
|
S: Service<Request = ServiceRequest, Response = ServiceResponse, Error = Error>
|
||||||
|
+ 'static,
|
||||||
{
|
{
|
||||||
let rt = actix_rt::System::new("test");
|
let mut rt = actix_rt::System::new("test");
|
||||||
let srv = Rc::new(RefCell::new(srv));
|
let srv = Rc::new(RefCell::new(srv));
|
||||||
|
|
||||||
let req = TestRequest::default().to_srv_request();
|
let req = TestRequest::default().to_srv_request();
|
||||||
@ -40,15 +41,14 @@ where
|
|||||||
b.iter_custom(|iters| {
|
b.iter_custom(|iters| {
|
||||||
let srv = srv.clone();
|
let srv = srv.clone();
|
||||||
// exclude request generation, it appears it takes significant time vs call (3us vs 1us)
|
// exclude request generation, it appears it takes significant time vs call (3us vs 1us)
|
||||||
let futs = (0..iters)
|
let reqs: Vec<_> = (0..iters)
|
||||||
.map(|_| TestRequest::default().to_srv_request())
|
.map(|_| TestRequest::default().to_srv_request())
|
||||||
.map(|req| srv.borrow_mut().call(req));
|
.collect();
|
||||||
|
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
// benchmark body
|
// benchmark body
|
||||||
rt.block_on(async move {
|
rt.block_on(async move {
|
||||||
for fut in futs {
|
for req in reqs {
|
||||||
fut.await.unwrap();
|
srv.borrow_mut().call(req).await.unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
@ -67,7 +67,7 @@ async fn index(req: ServiceRequest) -> Result<ServiceResponse, Error> {
|
|||||||
// Sample results on MacBook Pro '14
|
// Sample results on MacBook Pro '14
|
||||||
// time: [2.0724 us 2.1345 us 2.2074 us]
|
// time: [2.0724 us 2.1345 us 2.2074 us]
|
||||||
fn async_web_service(c: &mut Criterion) {
|
fn async_web_service(c: &mut Criterion) {
|
||||||
let rt = actix_rt::System::new("test");
|
let mut rt = actix_rt::System::new("test");
|
||||||
let srv = Rc::new(RefCell::new(rt.block_on(init_service(
|
let srv = Rc::new(RefCell::new(rt.block_on(init_service(
|
||||||
App::new().service(web::service("/").finish(index)),
|
App::new().service(web::service("/").finish(index)),
|
||||||
))));
|
))));
|
||||||
@ -83,14 +83,13 @@ fn async_web_service(c: &mut Criterion) {
|
|||||||
c.bench_function("async_web_service_direct", move |b| {
|
c.bench_function("async_web_service_direct", move |b| {
|
||||||
b.iter_custom(|iters| {
|
b.iter_custom(|iters| {
|
||||||
let srv = srv.clone();
|
let srv = srv.clone();
|
||||||
let futs = (0..iters)
|
let reqs = (0..iters).map(|_| TestRequest::get().uri("/").to_request());
|
||||||
.map(|_| TestRequest::get().uri("/").to_request())
|
|
||||||
.map(|req| srv.borrow_mut().call(req));
|
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
// benchmark body
|
// benchmark body
|
||||||
rt.block_on(async move {
|
rt.block_on(async move {
|
||||||
for fut in futs {
|
for req in reqs {
|
||||||
fut.await.unwrap();
|
srv.borrow_mut().call(req).await.unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user