mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-12 13:33:41 +02:00
Compare commits
86 Commits
http-v2.1.
...
http-v3.0.
Author | SHA1 | Date | |
---|---|---|---|
c09186a2c0 | |||
d3c476b8c2 | |||
dc23559f23 | |||
6d710629af | |||
85753130d9 | |||
00ba8d5549 | |||
51e9e1500b | |||
a03dbe2dcf | |||
57a3722146 | |||
57da1d3c0f | |||
68117543ea | |||
4f5971d79e | |||
93161df141 | |||
e567873326 | |||
7d632d0b7b | |||
36aee18c64 | |||
007a145988 | |||
2d4a174420 | |||
21f6c9d7a5 | |||
e1683313ec | |||
32de9f8840 | |||
1f202d40e4 | |||
ad608aa64e | |||
a1b00b2cd0 | |||
3beb4cf2da | |||
522c9a5ea6 | |||
102bb8f9ab | |||
20b46cdaf9 | |||
2a2a20c3e7 | |||
093d3a6c59 | |||
8c9ea43e23 | |||
f9fcf56d5c | |||
cbda928a33 | |||
1032f04ded | |||
b373e1370d | |||
404b5a7709 | |||
ecf08d5156 | |||
87655b3028 | |||
3a192400a6 | |||
2a7f2c1d59 | |||
05f104c240 | |||
4dccd092f3 | |||
95ccf1c9bc | |||
6cbf27508a | |||
79de04d862 | |||
a4dbaa8ed1 | |||
c7b4c6edfa | |||
2a5215c1d6 | |||
97f615c245 | |||
1a361273e7 | |||
d7ce648445 | |||
fabc68659b | |||
542db82282 | |||
ae63eb8bb2 | |||
7a3776b770 | |||
ff79c33fd4 | |||
b75a9b7a20 | |||
d0c6ca7671 | |||
24d525d978 | |||
1f70ef155d | |||
7981e0068a | |||
32d59ca904 | |||
ea8bf36104 | |||
0b5b463cfa | |||
fe6ad816cc | |||
e72b787ba7 | |||
efc317d3b0 | |||
31057becca | |||
f1a9b45437 | |||
5af46775b8 | |||
70f4747a23 | |||
2f11ef089b | |||
4100c50c70 | |||
a929209967 | |||
49e945c88f | |||
9b42333fac | |||
e5b86d189c | |||
4bfd5c2781 | |||
9b6a089b36 | |||
ceac97bb8d | |||
61b65aa64a | |||
5468c3c410 | |||
b6385c2b4e | |||
5135c1e3a0 | |||
22b451cf2d | |||
42f51eb962 |
15
.github/ISSUE_TEMPLATE/config.yml
vendored
15
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,8 +1,15 @@
|
|||||||
blank_issues_enabled: true
|
blank_issues_enabled: true
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Gitter channel (actix-web)
|
- name: GitHub Discussions
|
||||||
|
url: https://github.com/actix/actix-web/discussions
|
||||||
|
about: Actix Web Q&A
|
||||||
|
- name: Gitter chat (actix-web)
|
||||||
url: https://gitter.im/actix/actix-web
|
url: https://gitter.im/actix/actix-web
|
||||||
about: Please ask and answer questions about the actix-web here.
|
about: Actix Web Q&A
|
||||||
- name: Gitter channel (actix)
|
- name: Gitter chat (actix)
|
||||||
url: https://gitter.im/actix/actix
|
url: https://gitter.im/actix/actix
|
||||||
about: Please ask and answer questions about the actix here.
|
about: Actix (actor framework) Q&A
|
||||||
|
- name: Actix Discord
|
||||||
|
url: https://discord.gg/NWpN5mmg3x
|
||||||
|
about: Actix developer discussion and community chat
|
||||||
|
|
||||||
|
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.42.0 # MSRV
|
- 1.46.0 # MSRV
|
||||||
- stable
|
- stable
|
||||||
- nightly
|
- nightly
|
||||||
|
|
||||||
@ -30,6 +30,13 @@ 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:
|
||||||
@ -58,12 +65,17 @@ 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' || github.event_name == 'pull_request')
|
if: matrix.version == 'stable' && github.ref == 'refs/heads/master'
|
||||||
run: |
|
run: |
|
||||||
cargo install cargo-tarpaulin --vers "^0.13"
|
cargo install cargo-tarpaulin --vers "^0.13"
|
||||||
cargo tarpaulin --out Xml
|
cargo tarpaulin --out Xml
|
||||||
- name: Upload to Codecov
|
- name: Upload to Codecov
|
||||||
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
if: matrix.version == 'stable' && github.ref == 'refs/heads/master'
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v1
|
||||||
with:
|
with:
|
||||||
file: cobertura.xml
|
file: cobertura.xml
|
||||||
|
|
||||||
|
- 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,6 +29,13 @@ 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:
|
||||||
@ -42,3 +49,8 @@ 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
|
||||||
|
4
.github/workflows/upload-doc.yml
vendored
4
.github/workflows/upload-doc.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: stable-x86_64-unknown-linux-gnu
|
toolchain: nightly-x86_64-unknown-linux-gnu
|
||||||
profile: minimal
|
profile: minimal
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ jobs:
|
|||||||
run: echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html
|
run: echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html
|
||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
uses: JamesIves/github-pages-deploy-action@3.5.8
|
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||||
with:
|
with:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
BRANCH: gh-pages
|
BRANCH: gh-pages
|
||||||
|
12
.github/workflows/windows.yml
vendored
12
.github/workflows/windows.yml
vendored
@ -41,6 +41,13 @@ 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:
|
||||||
@ -62,3 +69,8 @@ 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
|
||||||
|
61
CHANGES.md
61
CHANGES.md
@ -1,6 +1,64 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0-beta.1 - 2021-01-07
|
||||||
|
### 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
|
||||||
|
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
|
||||||
|
* 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
|
||||||
|
* 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
|
||||||
|
### Fixed
|
||||||
|
* Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762]
|
||||||
|
* Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798]
|
||||||
|
* Increase minimum `socket2` version. [#1803]
|
||||||
|
|
||||||
|
[#1762]: https://github.com/actix/actix-web/pull/1762
|
||||||
|
[#1798]: https://github.com/actix/actix-web/pull/1798
|
||||||
|
[#1803]: https://github.com/actix/actix-web/pull/1803
|
||||||
|
|
||||||
|
|
||||||
|
## 3.3.1 - 2020-11-29
|
||||||
|
* Ensure `actix-http` dependency uses same `serde_urlencoded`.
|
||||||
|
|
||||||
|
|
||||||
|
## 3.3.0 - 2020-11-25
|
||||||
|
### Added
|
||||||
|
* Add `Either<A, B>` extractor helper. [#1788]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
|
||||||
|
|
||||||
|
[#1773]: https://github.com/actix/actix-web/pull/1773
|
||||||
|
[#1788]: https://github.com/actix/actix-web/pull/1788
|
||||||
|
|
||||||
|
|
||||||
|
## 3.2.0 - 2020-10-30
|
||||||
### Added
|
### Added
|
||||||
* Implement `exclude_regex` for Logger middleware. [#1723]
|
* Implement `exclude_regex` for Logger middleware. [#1723]
|
||||||
* Add request-local data extractor `web::ReqData`. [#1748]
|
* Add request-local data extractor `web::ReqData`. [#1748]
|
||||||
@ -9,6 +67,7 @@
|
|||||||
* Expose `on_connect` for access to the connection stream before request is handled. [#1754]
|
* Expose `on_connect` for access to the connection stream before request is handled. [#1754]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
* Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro.
|
||||||
* Print non-configured `Data<T>` type when attempting extraction. [#1743]
|
* Print non-configured `Data<T>` type when attempting extraction. [#1743]
|
||||||
* Re-export bytes::Buf{Mut} in web module. [#1750]
|
* Re-export bytes::Buf{Mut} in web module. [#1750]
|
||||||
* Upgrade `pin-project` to `1.0`.
|
* Upgrade `pin-project` to `1.0`.
|
||||||
|
@ -34,10 +34,13 @@ This Code of Conduct applies both within project spaces and in public spaces whe
|
|||||||
|
|
||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at robjtede@icloud.com ([@robjtede]) or huyuumi@neet.club ([@JohnTitor]). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
|
[@robjtede]: https://github.com/robjtede
|
||||||
|
[@JohnTitor]: https://github.com/JohnTitor
|
||||||
|
|
||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
61
Cargo.toml
61
Cargo.toml
@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "3.1.0"
|
version = "4.0.0-beta.1"
|
||||||
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"
|
||||||
keywords = ["actix", "http", "web", "framework", "async"]
|
keywords = ["actix", "http", "web", "framework", "async"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
@ -34,7 +34,7 @@ members = [
|
|||||||
"actix-multipart",
|
"actix-multipart",
|
||||||
"actix-web-actors",
|
"actix-web-actors",
|
||||||
"actix-web-codegen",
|
"actix-web-codegen",
|
||||||
"test-server",
|
"actix-http-test",
|
||||||
]
|
]
|
||||||
|
|
||||||
[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/openssl", "awc/openssl", "open-ssl"]
|
openssl = ["actix-tls/accept", "actix-tls/openssl", "awc/openssl", "open-ssl"]
|
||||||
|
|
||||||
# rustls
|
# rustls
|
||||||
rustls = ["actix-tls/rustls", "awc/rustls", "rust-tls"]
|
rustls = ["actix-tls/accept", "actix-tls/rustls", "awc/rustls", "rust-tls"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "basic"
|
name = "basic"
|
||||||
@ -73,46 +73,44 @@ name = "client"
|
|||||||
required-features = ["rustls"]
|
required-features = ["rustls"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.3.0"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-service = "1.0.6"
|
|
||||||
actix-utils = "2.0.0"
|
|
||||||
actix-router = "0.2.4"
|
|
||||||
actix-rt = "1.1.1"
|
|
||||||
actix-server = "1.0.0"
|
|
||||||
actix-testing = "1.0.0"
|
|
||||||
actix-macros = "0.1.0"
|
actix-macros = "0.1.0"
|
||||||
|
actix-router = "0.2.4"
|
||||||
|
actix-rt = "2.0.0-beta.1"
|
||||||
|
actix-server = "2.0.0-beta.2"
|
||||||
|
actix-service = "2.0.0-beta.2"
|
||||||
|
actix-utils = "3.0.0-beta.1"
|
||||||
actix-threadpool = "0.3.1"
|
actix-threadpool = "0.3.1"
|
||||||
actix-tls = "2.0.0"
|
actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true }
|
||||||
|
|
||||||
actix-web-codegen = "0.3.0"
|
actix-web-codegen = "0.4.0"
|
||||||
actix-http = "2.0.0"
|
actix-http = "3.0.0-beta.1"
|
||||||
awc = { version = "2.0.0", default-features = false }
|
awc = { version = "3.0.0-beta.1", default-features = false }
|
||||||
|
|
||||||
bytes = "0.5.3"
|
ahash = "0.6"
|
||||||
derive_more = "0.99.2"
|
bytes = "1"
|
||||||
|
derive_more = "0.99.5"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
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 }
|
||||||
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"
|
socket2 = "0.3.16"
|
||||||
pin-project = "1.0.0"
|
pin-project = "1.0.0"
|
||||||
regex = "1.4"
|
regex = "1.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_urlencoded = "0.6.1"
|
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.18.0", optional = true }
|
rust-tls = { package = "rustls", version = "0.19.0", optional = true }
|
||||||
tinyvec = { version = "1", features = ["alloc"] }
|
smallvec = "1.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix = "0.10.0"
|
actix = "0.11.0-beta.1"
|
||||||
actix-http = { version = "2.0.0", features = ["actors"] }
|
actix-http = { version = "3.0.0-beta.1", features = ["actors"] }
|
||||||
rand = "0.7"
|
rand = "0.8"
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
brotli2 = "0.3.2"
|
brotli2 = "0.3.2"
|
||||||
@ -127,10 +125,11 @@ 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 = "test-server" }
|
actix-http-test = { path = "actix-http-test" }
|
||||||
|
actix-web-actors = { path = "actix-web-actors" }
|
||||||
actix-web-codegen = { path = "actix-web-codegen" }
|
actix-web-codegen = { path = "actix-web-codegen" }
|
||||||
actix-files = { path = "actix-files" }
|
|
||||||
actix-multipart = { path = "actix-multipart" }
|
actix-multipart = { path = "actix-multipart" }
|
||||||
|
actix-files = { path = "actix-files" }
|
||||||
awc = { path = "awc" }
|
awc = { path = "awc" }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
|
10
MIGRATION.md
10
MIGRATION.md
@ -1,5 +1,15 @@
|
|||||||
## 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
|
||||||
|
|
||||||
|
16
README.md
16
README.md
@ -1,19 +1,21 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<h1>Actix web</h1>
|
<h1>Actix web</h1>
|
||||||
<p>
|
<p>
|
||||||
<strong>Actix web is a powerful, pragmatic, and extremely fast web framework for Rust</strong>
|
<strong>Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust</strong>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web)
|
[](https://crates.io/crates/actix-web)
|
||||||
[](https://docs.rs/actix-web)
|
[](https://docs.rs/actix-web/3.3.2)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||

|

|
||||||
|
[](https://deps.rs/crate/actix-web/3.3.2)
|
||||||
<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)
|
||||||
[](https://crates.io/crates/actix-web)
|
[](https://crates.io/crates/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)
|
||||||
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -32,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.42+
|
* Runs on stable Rust 1.46+
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@ -105,5 +107,5 @@ at your option.
|
|||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
Contribution to the actix-web crate is organized under the terms of the Contributor Covenant, the
|
Contribution to the actix-web repo is organized under the terms of the Contributor Covenant.
|
||||||
maintainers of Actix web, promises to intervene to uphold that code of conduct.
|
The Actix team promises to intervene to uphold that code of conduct.
|
||||||
|
@ -1,6 +1,23 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2021-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
|
||||||
|
* Clarify order of parameters in `Files::new` and improve docs.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0 - 2020-10-06
|
## 0.4.0 - 2020-10-06
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.4.0"
|
version = "0.6.0-beta.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 = "3.0.0", default-features = false }
|
actix-web = { version = "4.0.0-beta.1", default-features = false }
|
||||||
actix-service = "1.0.6"
|
actix-service = "2.0.0-beta.2"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
bytes = "0.5.3"
|
bytes = "1"
|
||||||
futures-core = { version = "0.3.5", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
futures-util = { version = "0.3.5", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
derive_more = "0.99.2"
|
derive_more = "0.99.5"
|
||||||
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.10"
|
v_htmlescape = "0.12"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "1.0.0"
|
actix-rt = "2.0.0-beta.1"
|
||||||
actix-web = { version = "3.0.0", features = ["openssl"] }
|
actix-web = "4.0.0-beta.1"
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
> 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)
|
[](https://docs.rs/actix-files/0.5.0)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-files/0.4.0)
|
[](https://deps.rs/crate/actix-files/0.5.0)
|
||||||
[](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.42 or later
|
- Minimum supported Rust version: 1.46 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};
|
use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{
|
dev::{
|
||||||
AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse,
|
AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse,
|
||||||
@ -39,6 +39,7 @@ 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 {
|
||||||
@ -60,18 +61,31 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Files {
|
impl Files {
|
||||||
/// Create new `Files` instance for specified base directory.
|
/// Create new `Files` instance for a specified base directory.
|
||||||
///
|
///
|
||||||
/// `File` uses `ThreadPool` for blocking filesystem operations.
|
/// # Argument Order
|
||||||
/// By default pool with 5x threads of available cpus is used.
|
/// The first argument (`mount_path`) is the root URL at which the static files are served.
|
||||||
/// Pool size can be changed by setting ACTIX_THREADPOOL environment variable.
|
/// For example, `/assets` will serve files at `example.com/assets/...`.
|
||||||
pub fn new<T: Into<PathBuf>>(path: &str, dir: T) -> Files {
|
///
|
||||||
let orig_dir = dir.into();
|
/// The second argument (`serve_from`) is the location on disk at which files are loaded.
|
||||||
|
/// This can be a relative path. For example, `./` would serve files from the current
|
||||||
|
/// working directory.
|
||||||
|
///
|
||||||
|
/// # Implementation Notes
|
||||||
|
/// If the mount path is set as the root path `/`, services registered after this one will
|
||||||
|
/// be inaccessible. Register more specific handlers and services first.
|
||||||
|
///
|
||||||
|
/// `Files` uses a threadpool for blocking filesystem operations. By default, the pool uses a
|
||||||
|
/// number of threads equal to 5x the number of available logical CPUs. Pool size can be changed
|
||||||
|
/// by setting ACTIX_THREADPOOL environment variable.
|
||||||
|
pub fn new<T: Into<PathBuf>>(mount_path: &str, serve_from: T) -> Files {
|
||||||
|
let orig_dir = serve_from.into();
|
||||||
let dir = match orig_dir.canonicalize() {
|
let dir = match orig_dir.canonicalize() {
|
||||||
Ok(canon_dir) => canon_dir,
|
Ok(canon_dir) => canon_dir,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@ -81,7 +95,7 @@ impl Files {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Files {
|
Files {
|
||||||
path: path.to_string(),
|
path: mount_path.to_owned(),
|
||||||
directory: dir,
|
directory: dir,
|
||||||
index: None,
|
index: None,
|
||||||
show_index: false,
|
show_index: false,
|
||||||
@ -91,6 +105,7 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,10 +201,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>,
|
F: IntoServiceFactory<U, ServiceRequest>,
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<
|
||||||
|
ServiceRequest,
|
||||||
Config = (),
|
Config = (),
|
||||||
Request = ServiceRequest,
|
|
||||||
Response = ServiceResponse,
|
Response = ServiceResponse,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
> + 'static,
|
> + 'static,
|
||||||
@ -201,6 +216,13 @@ 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 {
|
||||||
@ -219,8 +241,7 @@ impl HttpServiceFactory for Files {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceFactory for Files {
|
impl ServiceFactory<ServiceRequest> for Files {
|
||||||
type Request = ServiceRequest;
|
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Config = ();
|
type Config = ();
|
||||||
@ -239,6 +260,7 @@ impl ServiceFactory 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() {
|
||||||
|
@ -193,7 +193,7 @@ impl NamedFile {
|
|||||||
/// image, and video content types, and `attachment` otherwise, and
|
/// image, and video content types, and `attachment` otherwise, and
|
||||||
/// the filename is taken from the path provided in the `open` method
|
/// the filename is taken from the path provided in the `open` method
|
||||||
/// after converting it to UTF-8 using.
|
/// after converting it to UTF-8 using.
|
||||||
/// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy).
|
/// [`std::ffi::OsStr::to_string_lossy`]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self {
|
pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self {
|
||||||
self.content_disposition = cd;
|
self.content_disposition = cd;
|
||||||
|
@ -15,12 +15,19 @@ 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 segment.starts_with('.') {
|
} else if !hidden_files && 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('*'));
|
||||||
@ -96,4 +103,17 @@ 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,3 +1,5 @@
|
|||||||
|
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 {
|
||||||
@ -11,17 +13,21 @@ 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>, ()> {
|
pub fn parse(header: &str, size: u64) -> Result<Vec<HttpRange>, ParseRangeErr> {
|
||||||
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(());
|
return Err(ParseRangeErr(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let size_sig = size as i64;
|
let size_sig = size as i64;
|
||||||
@ -34,13 +40,14 @@ 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(())?.trim();
|
let start_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.trim();
|
||||||
let end_str = start_end_iter.next().ok_or(())?.trim();
|
let end_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.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 = end_str.parse().map_err(|_| ())?;
|
let mut length: i64 =
|
||||||
|
end_str.parse().map_err(|_| ParseRangeErr(()))?;
|
||||||
|
|
||||||
if length > size_sig {
|
if length > size_sig {
|
||||||
length = size_sig;
|
length = size_sig;
|
||||||
@ -51,10 +58,10 @@ impl HttpRange {
|
|||||||
length: length as u64,
|
length: length as u64,
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
let start: i64 = start_str.parse().map_err(|_| ())?;
|
let start: i64 = start_str.parse().map_err(|_| ParseRangeErr(()))?;
|
||||||
|
|
||||||
if start < 0 {
|
if start < 0 {
|
||||||
return Err(());
|
return Err(ParseRangeErr(()));
|
||||||
}
|
}
|
||||||
if start >= size_sig {
|
if start >= size_sig {
|
||||||
no_overlap = true;
|
no_overlap = true;
|
||||||
@ -65,10 +72,11 @@ 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 = end_str.parse().map_err(|_| ())?;
|
let mut end: i64 =
|
||||||
|
end_str.parse().map_err(|_| ParseRangeErr(()))?;
|
||||||
|
|
||||||
if start > end {
|
if start > end {
|
||||||
return Err(());
|
return Err(ParseRangeErr(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if end >= size_sig {
|
if end >= size_sig {
|
||||||
@ -89,7 +97,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(());
|
return Err(ParseRangeErr(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ranges)
|
Ok(ranges)
|
||||||
@ -333,8 +341,7 @@ mod tests {
|
|||||||
if expected.is_empty() {
|
if expected.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
assert!(
|
panic!(
|
||||||
false,
|
|
||||||
"parse({}, {}) returned error {:?}",
|
"parse({}, {}) returned error {:?}",
|
||||||
header,
|
header,
|
||||||
size,
|
size,
|
||||||
@ -346,28 +353,24 @@ mod tests {
|
|||||||
let got = res.unwrap();
|
let got = res.unwrap();
|
||||||
|
|
||||||
if got.len() != expected.len() {
|
if got.len() != expected.len() {
|
||||||
assert!(
|
panic!(
|
||||||
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 {
|
||||||
assert!(
|
panic!(
|
||||||
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 {
|
||||||
assert!(
|
panic!(
|
||||||
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,6 +31,7 @@ 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<
|
||||||
@ -56,8 +57,7 @@ impl fmt::Debug for FilesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service for FilesService {
|
impl Service<ServiceRequest> for FilesService {
|
||||||
type Request = ServiceRequest;
|
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = FilesServiceFuture;
|
type Future = FilesServiceFuture;
|
||||||
@ -83,10 +83,11 @@ impl Service for FilesService {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let real_path: PathBufWrap = match req.match_info().path().parse() {
|
let real_path =
|
||||||
Ok(item) => item,
|
match PathBufWrap::parse_path(req.match_info().path(), self.hidden_files) {
|
||||||
Err(e) => return Either::Left(ok(req.error_response(e))),
|
Ok(item) => item,
|
||||||
};
|
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,15 +1,27 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
* add ability to set address for `TestServer` [#1645]
|
|
||||||
|
## 3.0.0-beta.1 - 2021-01-07
|
||||||
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
|
||||||
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
|
|
||||||
|
|
||||||
|
## 2.1.0 - 2020-11-25
|
||||||
|
* Add ability to set address for `TestServer`. [#1645]
|
||||||
* Upgrade `base64` to `0.13`.
|
* Upgrade `base64` to `0.13`.
|
||||||
|
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
|
||||||
|
|
||||||
|
[#1773]: https://github.com/actix/actix-web/pull/1773
|
||||||
[#1645]: https://github.com/actix/actix-web/pull/1645
|
[#1645]: https://github.com/actix/actix-web/pull/1645
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0 - 2020-09-11
|
## 2.0.0 - 2020-09-11
|
||||||
* Update actix-codec and actix-utils dependencies.
|
* Update actix-codec and actix-utils dependencies.
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0-alpha.1 - 2020-05-23
|
## 2.0.0-alpha.1 - 2020-05-23
|
||||||
* Update the `time` dependency to 0.2.7
|
* Update the `time` dependency to 0.2.7
|
||||||
* Update `actix-connect` dependency to 2.0.0-alpha.2
|
* Update `actix-connect` dependency to 2.0.0-alpha.2
|
||||||
@ -19,74 +31,56 @@
|
|||||||
* Update `base64` dependency to 0.12
|
* Update `base64` dependency to 0.12
|
||||||
* Update `env_logger` dependency to 0.7
|
* Update `env_logger` dependency to 0.7
|
||||||
|
|
||||||
## [1.0.0] - 2019-12-13
|
## 1.0.0 - 2019-12-13
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Replaced `TestServer::start()` with `test_server()`
|
* Replaced `TestServer::start()` with `test_server()`
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-alpha.3] - 2019-12-07
|
## 1.0.0-alpha.3 - 2019-12-07
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Migrate to `std::future`
|
* Migrate to `std::future`
|
||||||
|
|
||||||
|
|
||||||
## [0.2.5] - 2019-09-17
|
## 0.2.5 - 2019-09-17
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Update serde_urlencoded to "0.6.1"
|
* Update serde_urlencoded to "0.6.1"
|
||||||
* Increase TestServerRuntime timeouts from 500ms to 3000ms
|
* Increase TestServerRuntime timeouts from 500ms to 3000ms
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
* Do not override current `System`
|
* Do not override current `System`
|
||||||
|
|
||||||
|
|
||||||
## [0.2.4] - 2019-07-18
|
## 0.2.4 - 2019-07-18
|
||||||
|
|
||||||
* Update actix-server to 0.6
|
* Update actix-server to 0.6
|
||||||
|
|
||||||
## [0.2.3] - 2019-07-16
|
|
||||||
|
|
||||||
|
## 0.2.3 - 2019-07-16
|
||||||
* Add `delete`, `options`, `patch` methods to `TestServerRunner`
|
* Add `delete`, `options`, `patch` methods to `TestServerRunner`
|
||||||
|
|
||||||
## [0.2.2] - 2019-06-16
|
|
||||||
|
|
||||||
|
## 0.2.2 - 2019-06-16
|
||||||
* Add .put() and .sput() methods
|
* Add .put() and .sput() methods
|
||||||
|
|
||||||
## [0.2.1] - 2019-06-05
|
|
||||||
|
|
||||||
|
## 0.2.1 - 2019-06-05
|
||||||
* Add license files
|
* Add license files
|
||||||
|
|
||||||
## [0.2.0] - 2019-05-12
|
|
||||||
|
|
||||||
|
## 0.2.0 - 2019-05-12
|
||||||
* Update awc and actix-http deps
|
* Update awc and actix-http deps
|
||||||
|
|
||||||
## [0.1.1] - 2019-04-24
|
|
||||||
|
|
||||||
|
## 0.1.1 - 2019-04-24
|
||||||
* Always make new connection for http client
|
* Always make new connection for http client
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0] - 2019-04-16
|
## 0.1.0 - 2019-04-16
|
||||||
|
|
||||||
* No changes
|
* No changes
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.3] - 2019-04-02
|
## 0.1.0-alpha.3 - 2019-04-02
|
||||||
|
|
||||||
* Request functions accept path #743
|
* Request functions accept path #743
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.2] - 2019-03-29
|
## 0.1.0-alpha.2 - 2019-03-29
|
||||||
|
|
||||||
* Added TestServerRuntime::load_body() method
|
* Added TestServerRuntime::load_body() method
|
||||||
|
|
||||||
* Update actix-http and awc libraries
|
* Update actix-http and awc libraries
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.1] - 2019-03-28
|
## 0.1.0-alpha.1 - 2019-03-28
|
||||||
|
|
||||||
* Initial impl
|
* Initial impl
|
@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-http-test"
|
name = "actix-http-test"
|
||||||
version = "2.0.0"
|
version = "3.0.0-beta.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix HTTP test server"
|
description = "Various helpers for Actix applications to use during testing"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
@ -29,28 +29,27 @@ default = []
|
|||||||
openssl = ["open-ssl", "awc/openssl"]
|
openssl = ["open-ssl", "awc/openssl"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "1.0.6"
|
actix-service = "2.0.0-beta.2"
|
||||||
actix-codec = "0.3.0"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-connect = "2.0.0"
|
actix-tls = "3.0.0-beta.2"
|
||||||
actix-utils = "2.0.0"
|
actix-utils = "3.0.0-beta.1"
|
||||||
actix-rt = "1.1.1"
|
actix-rt = "2.0.0-beta.1"
|
||||||
actix-server = "1.0.0"
|
actix-server = "2.0.0-beta.2"
|
||||||
actix-testing = "1.0.0"
|
awc = "3.0.0-beta.1"
|
||||||
awc = "2.0.0"
|
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bytes = "0.5.3"
|
bytes = "1"
|
||||||
futures-core = { version = "0.3.5", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
http = "0.2.0"
|
http = "0.2.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
socket2 = "0.3"
|
socket2 = "0.3"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
serde_urlencoded = "0.6.1"
|
serde_urlencoded = "0.7"
|
||||||
time = { version = "0.2.7", default-features = false, features = ["std"] }
|
time = { version = "0.2.7", default-features = false, features = ["std"] }
|
||||||
open-ssl = { version = "0.10", package = "openssl", optional = true }
|
open-ssl = { version = "0.10", package = "openssl", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-web = "3.0.0"
|
actix-web = "4.0.0-beta.1"
|
||||||
actix-http = "2.0.0"
|
actix-http = "3.0.0-beta.1"
|
15
actix-http-test/README.md
Normal file
15
actix-http-test/README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# actix-http-test
|
||||||
|
|
||||||
|
> Various helpers for Actix applications to use during testing.
|
||||||
|
|
||||||
|
[](https://crates.io/crates/actix-http-test)
|
||||||
|
[](https://docs.rs/actix-http-test/2.1.0)
|
||||||
|

|
||||||
|
[](https://deps.rs/crate/actix-http-test/2.1.0)
|
||||||
|
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
## Documentation & Resources
|
||||||
|
|
||||||
|
- [API Documentation](https://docs.rs/actix-http-test)
|
||||||
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
|
- Minimum Supported Rust Version (MSRV): 1.46.0
|
@ -1,4 +1,9 @@
|
|||||||
//! Various helpers for Actix applications to use during testing.
|
//! Various helpers for Actix applications to use during testing.
|
||||||
|
|
||||||
|
#![deny(rust_2018_idioms)]
|
||||||
|
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||||
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::{net, thread, time};
|
use std::{net, thread, time};
|
||||||
|
|
||||||
@ -11,8 +16,6 @@ use futures_core::stream::Stream;
|
|||||||
use http::Method;
|
use http::Method;
|
||||||
use socket2::{Domain, Protocol, Socket, Type};
|
use socket2::{Domain, Protocol, Socket, Type};
|
||||||
|
|
||||||
pub use actix_testing::*;
|
|
||||||
|
|
||||||
/// Start test server
|
/// Start test server
|
||||||
///
|
///
|
||||||
/// `TestServer` is very simple test server that simplify process of writing
|
/// `TestServer` is very simple test server that simplify process of writing
|
||||||
@ -48,7 +51,7 @@ pub async fn test_server<F: ServiceFactory<TcpStream>>(factory: F) -> TestServer
|
|||||||
test_server_with_addr(tcp, factory).await
|
test_server_with_addr(tcp, factory).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start [`test server`](./fn.test_server.html) on a concrete Address
|
/// Start [`test server`](test_server()) on a concrete Address
|
||||||
pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
||||||
tcp: net::TcpListener,
|
tcp: net::TcpListener,
|
||||||
factory: F,
|
factory: F,
|
||||||
@ -60,13 +63,16 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
|||||||
let sys = System::new("actix-test-server");
|
let sys = System::new("actix-test-server");
|
||||||
let local_addr = tcp.local_addr().unwrap();
|
let local_addr = tcp.local_addr().unwrap();
|
||||||
|
|
||||||
Server::build()
|
let srv = Server::build()
|
||||||
.listen("test", tcp, factory)?
|
.listen("test", tcp, factory)?
|
||||||
.workers(1)
|
.workers(1)
|
||||||
.disable_signals()
|
.disable_signals();
|
||||||
.start();
|
|
||||||
|
sys.block_on(async {
|
||||||
|
srv.start();
|
||||||
|
tx.send((System::current(), local_addr)).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
tx.send((System::current(), local_addr)).unwrap();
|
|
||||||
sys.run()
|
sys.run()
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -100,7 +106,7 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
|||||||
|
|
||||||
Client::builder().connector(connector).finish()
|
Client::builder().connector(connector).finish()
|
||||||
};
|
};
|
||||||
actix_connect::start_default_resolver().await.unwrap();
|
actix_tls::connect::start_default_resolver().await.unwrap();
|
||||||
|
|
||||||
TestServer {
|
TestServer {
|
||||||
addr,
|
addr,
|
@ -1,6 +1,50 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.1 - 2021-01-07
|
||||||
|
### Added
|
||||||
|
* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
|
||||||
|
* 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
|
||||||
|
* 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
|
||||||
|
[#1864]: https://github.com/actix/actix-web/pull/1864
|
||||||
|
|
||||||
|
|
||||||
|
## 2.2.0 - 2020-11-25
|
||||||
|
### Added
|
||||||
|
* HttpResponse builders for 1xx status codes. [#1768]
|
||||||
|
* `Accept::mime_precedence` and `Accept::mime_preference`. [#1793]
|
||||||
|
* `TryFrom<u16>` and `TryFrom<f32>` for `http::header::Quality`. [#1797]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
|
||||||
|
|
||||||
|
[#1773]: https://github.com/actix/actix-web/pull/1773
|
||||||
|
[#1767]: https://github.com/actix/actix-web/pull/1767
|
||||||
|
[#1768]: https://github.com/actix/actix-web/pull/1768
|
||||||
|
[#1793]: https://github.com/actix/actix-web/pull/1793
|
||||||
|
[#1797]: https://github.com/actix/actix-web/pull/1797
|
||||||
|
|
||||||
|
|
||||||
## 2.1.0 - 2020-10-30
|
## 2.1.0 - 2020-10-30
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to creating a positive environment include:
|
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
|
||||||
* Being respectful of differing viewpoints and experiences
|
|
||||||
* Gracefully accepting constructive criticism
|
|
||||||
* Focusing on what is best for the community
|
|
||||||
* Showing empathy towards other community members
|
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
||||||
|
|
||||||
## Our Responsibilities
|
|
||||||
|
|
||||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
|
||||||
|
|
||||||
[homepage]: http://contributor-covenant.org
|
|
||||||
[version]: http://contributor-covenant.org/version/1/4/
|
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "2.1.0"
|
version = "3.0.0-beta.1"
|
||||||
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,10 +25,10 @@ path = "src/lib.rs"
|
|||||||
default = []
|
default = []
|
||||||
|
|
||||||
# openssl
|
# openssl
|
||||||
openssl = ["actix-tls/openssl", "actix-connect/openssl"]
|
openssl = ["actix-tls/openssl"]
|
||||||
|
|
||||||
# rustls support
|
# rustls support
|
||||||
rustls = ["actix-tls/rustls", "actix-connect/rustls"]
|
rustls = ["actix-tls/rustls"]
|
||||||
|
|
||||||
# enable compressison support
|
# enable compressison support
|
||||||
compress = ["flate2", "brotli2"]
|
compress = ["flate2", "brotli2"]
|
||||||
@ -40,29 +40,29 @@ secure-cookies = ["cookie/secure"]
|
|||||||
actors = ["actix"]
|
actors = ["actix"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "1.0.6"
|
actix-service = "2.0.0-beta.2"
|
||||||
actix-codec = "0.3.0"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-connect = "2.0.0"
|
actix-utils = "3.0.0-beta.1"
|
||||||
actix-utils = "2.0.0"
|
actix-rt = "2.0.0-beta.1"
|
||||||
actix-rt = "1.0.0"
|
|
||||||
actix-threadpool = "0.3.1"
|
actix-threadpool = "0.3.1"
|
||||||
actix-tls = { version = "2.0.0", optional = true }
|
actix-tls = "3.0.0-beta.2"
|
||||||
actix = { version = "0.10.0", optional = true }
|
actix = { version = "0.11.0-beta.1", optional = true }
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bitflags = "1.2"
|
bitflags = "1.2"
|
||||||
bytes = "0.5.3"
|
bytes = "1"
|
||||||
|
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.2"
|
derive_more = "0.99.5"
|
||||||
either = "1.5.3"
|
either = "1.5.3"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
futures-channel = { version = "0.3.5", default-features = false }
|
futures-channel = { version = "0.3.7", default-features = false }
|
||||||
futures-core = { version = "0.3.5", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
futures-util = { version = "0.3.5", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
h2 = "0.2.1"
|
h2 = "0.3.0"
|
||||||
http = "0.2.0"
|
http = "0.2.2"
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
indexmap = "1.3"
|
indexmap = "1.3"
|
||||||
itoa = "0.4"
|
itoa = "0.4"
|
||||||
@ -72,13 +72,13 @@ 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.7"
|
rand = "0.8"
|
||||||
regex = "1.3"
|
regex = "1.3"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
sha-1 = "0.9"
|
sha-1 = "0.9"
|
||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
serde_urlencoded = "0.6.1"
|
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
|
||||||
@ -86,18 +86,17 @@ brotli2 = { version="0.3.2", optional = true }
|
|||||||
flate2 = { version = "1.0.13", optional = true }
|
flate2 = { version = "1.0.13", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-server = "1.0.1"
|
actix-server = "2.0.0-beta.2"
|
||||||
actix-connect = { version = "2.0.0", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] }
|
||||||
actix-http-test = { version = "2.0.0", features = ["openssl"] }
|
actix-tls = { version = "3.0.0-beta.2", 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.18", package = "rustls" }
|
rust-tls = { version="0.19", package = "rustls" }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "content-length"
|
name = "write-camel-case"
|
||||||
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.1.0)
|
[](https://docs.rs/actix-http/2.2.0)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-http/2.1.0)
|
[](https://deps.rs/crate/actix-http/2.2.0)
|
||||||
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](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/2.1.0)
|
- [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.42.0
|
- Minimum Supported Rust Version (MSRV): 1.46.0
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
@ -1,291 +0,0 @@
|
|||||||
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;
|
||||||
|
89
actix-http/benches/write-camel-case.rs
Normal file
89
actix-http/benches/write-camel-case.rs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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,11 +1,9 @@
|
|||||||
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::Stream;
|
use futures_core::{ready, Stream};
|
||||||
use futures_util::ready;
|
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
@ -68,7 +66,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(#[pin] Body),
|
Other(Body),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseBody<Body> {
|
impl ResponseBody<Body> {
|
||||||
@ -110,7 +108,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) => body.poll_next(cx),
|
ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,12 +122,11 @@ 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) => body.poll_next(cx),
|
ResponseBodyProj::Other(body) => Pin::new(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.
|
||||||
@ -168,10 +165,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.project() {
|
match self.get_mut() {
|
||||||
BodyProj::None => Poll::Ready(None),
|
Body::None => Poll::Ready(None),
|
||||||
BodyProj::Empty => Poll::Ready(None),
|
Body::Empty => Poll::Ready(None),
|
||||||
BodyProj::Bytes(ref mut bin) => {
|
Body::Bytes(ref mut bin) => {
|
||||||
let len = bin.len();
|
let len = bin.len();
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
@ -179,7 +176,7 @@ impl MessageBody for Body {
|
|||||||
Poll::Ready(Some(Ok(mem::take(bin))))
|
Poll::Ready(Some(Ok(mem::take(bin))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BodyProj::Message(ref mut body) => Pin::new(body.as_mut()).poll_next(cx),
|
Body::Message(body) => Pin::new(&mut **body).poll_next(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -266,12 +263,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, E> From<BodyStream<S, E>> for Body
|
impl<S, E> From<BodyStream<S>> 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, E>) -> Body {
|
fn from(s: BodyStream<S>) -> Body {
|
||||||
Body::from_message(s)
|
Body::from_message(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -367,27 +364,21 @@ 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.
|
||||||
#[pin_project]
|
pub struct BodyStream<S: Unpin> {
|
||||||
pub struct BodyStream<S: Unpin, E> {
|
|
||||||
#[pin]
|
|
||||||
stream: S,
|
stream: S,
|
||||||
_t: PhantomData<E>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, E> BodyStream<S, E>
|
impl<S, E> BodyStream<S>
|
||||||
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 {
|
BodyStream { stream }
|
||||||
stream,
|
|
||||||
_t: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, E> MessageBody for BodyStream<S, E>
|
impl<S, E> MessageBody for BodyStream<S>
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + Unpin,
|
S: Stream<Item = Result<Bytes, E>> + Unpin,
|
||||||
E: Into<Error>,
|
E: Into<Error>,
|
||||||
@ -402,13 +393,12 @@ 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(
|
||||||
self: Pin<&mut Self>,
|
mut 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 = stream.as_mut();
|
let stream = &mut self.as_mut().stream;
|
||||||
return Poll::Ready(match ready!(stream.poll_next(cx)) {
|
return Poll::Ready(match ready!(Pin::new(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)),
|
||||||
});
|
});
|
||||||
@ -418,10 +408,8 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,13 +436,12 @@ 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(
|
||||||
self: Pin<&mut Self>,
|
mut 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 = stream.as_mut();
|
let stream = &mut self.as_mut().stream;
|
||||||
return Poll::Ready(match ready!(stream.poll_next(cx)) {
|
return Poll::Ready(match ready!(Pin::new(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,7 +10,6 @@ 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;
|
||||||
@ -20,7 +19,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<T>> {
|
pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
|
||||||
keep_alive: KeepAlive,
|
keep_alive: KeepAlive,
|
||||||
client_timeout: u64,
|
client_timeout: u64,
|
||||||
client_disconnect: u64,
|
client_disconnect: u64,
|
||||||
@ -28,18 +27,16 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
|
|||||||
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>>>,
|
||||||
_t: PhantomData<(T, S)>,
|
_phantom: PhantomData<S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler<T>>
|
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
<S::Service as Service>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
{
|
{
|
||||||
/// Create instance of `ServiceConfigBuilder`
|
/// Create instance of `ServiceConfigBuilder`
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@ -51,27 +48,26 @@ 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,
|
||||||
_t: PhantomData,
|
_phantom: 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<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
<S::Service as Service>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
<X::Service as Service>::Future: 'static,
|
<X::Service as Service<Request>>::Future: 'static,
|
||||||
U: ServiceFactory<Config = (), Request = (Request, Framed<T, Codec>), Response = ()>,
|
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
<U::Service as Service>::Future: 'static,
|
<U::Service as Service<(Request, Framed<T, Codec>)>>::Future: 'static,
|
||||||
{
|
{
|
||||||
/// Set server keep-alive setting.
|
/// Set server keep-alive setting.
|
||||||
///
|
///
|
||||||
@ -127,11 +123,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>,
|
F: IntoServiceFactory<X1, Request>,
|
||||||
X1: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
X1: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X1::Error: Into<Error>,
|
X1::Error: Into<Error>,
|
||||||
X1::InitError: fmt::Debug,
|
X1::InitError: fmt::Debug,
|
||||||
<X1::Service as Service>::Future: 'static,
|
<X1::Service as Service<Request>>::Future: 'static,
|
||||||
{
|
{
|
||||||
HttpServiceBuilder {
|
HttpServiceBuilder {
|
||||||
keep_alive: self.keep_alive,
|
keep_alive: self.keep_alive,
|
||||||
@ -141,9 +137,8 @@ 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,
|
||||||
_t: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,15 +148,11 @@ 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>,
|
F: IntoServiceFactory<U1, (Request, Framed<T, Codec>)>,
|
||||||
U1: ServiceFactory<
|
U1: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
|
||||||
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>::Future: 'static,
|
<U1::Service as Service<(Request, Framed<T, Codec>)>>::Future: 'static,
|
||||||
{
|
{
|
||||||
HttpServiceBuilder {
|
HttpServiceBuilder {
|
||||||
keep_alive: self.keep_alive,
|
keep_alive: self.keep_alive,
|
||||||
@ -171,26 +162,11 @@ 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,
|
||||||
_t: PhantomData,
|
_phantom: 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.
|
||||||
@ -208,7 +184,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>,
|
F: IntoServiceFactory<S, 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>>,
|
||||||
@ -224,7 +200,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,11 +207,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>,
|
F: IntoServiceFactory<S, 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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
{
|
{
|
||||||
let cfg = ServiceConfig::new(
|
let cfg = ServiceConfig::new(
|
||||||
self.keep_alive,
|
self.keep_alive,
|
||||||
@ -247,7 +222,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,11 +229,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>,
|
F: IntoServiceFactory<S, 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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
{
|
{
|
||||||
let cfg = ServiceConfig::new(
|
let cfg = ServiceConfig::new(
|
||||||
self.keep_alive,
|
self.keep_alive,
|
||||||
@ -272,7 +246,6 @@ 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, mem, time};
|
use std::{fmt, io, time};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf};
|
||||||
use bytes::{Buf, Bytes};
|
use bytes::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,23 +223,13 @@ where
|
|||||||
fn poll_read(
|
fn poll_read(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
buf: &mut [u8],
|
buf: &mut ReadBuf<'_>,
|
||||||
) -> Poll<io::Result<usize>> {
|
) -> Poll<io::Result<()>> {
|
||||||
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>
|
||||||
@ -274,18 +264,4 @@ 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_connect::{
|
use actix_rt::net::TcpStream;
|
||||||
|
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_connect::ssl::openssl::SslConnector as OpensslConnector;
|
use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector;
|
||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
use actix_connect::ssl::rustls::ClientConfig;
|
use actix_tls::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,
|
||||||
_t: PhantomData<U>,
|
_phantom: 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<
|
||||||
Request = TcpConnect<Uri>,
|
TcpConnect<Uri>,
|
||||||
Response = TcpConnection<Uri, TcpStream>,
|
Response = TcpConnection<Uri, TcpStream>,
|
||||||
Error = actix_connect::ConnectError,
|
Error = actix_tls::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(),
|
||||||
_t: PhantomData,
|
_phantom: 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_connect::ssl::openssl::SslMethod;
|
use actix_tls::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::rustls::TLS_SERVER_ROOTS);
|
.add_server_trust_anchors(&actix_tls::accept::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<
|
||||||
Request = TcpConnect<Uri>,
|
TcpConnect<Uri>,
|
||||||
Response = TcpConnection<Uri, U1>,
|
Response = TcpConnection<Uri, U1>,
|
||||||
Error = actix_connect::ConnectError,
|
Error = actix_tls::connect::ConnectError,
|
||||||
> + Clone,
|
> + Clone,
|
||||||
{
|
{
|
||||||
Connector {
|
Connector {
|
||||||
connector,
|
connector,
|
||||||
config: self.config,
|
config: self.config,
|
||||||
ssl: self.ssl,
|
ssl: self.ssl,
|
||||||
_t: PhantomData,
|
_phantom: 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<
|
||||||
Request = TcpConnect<Uri>,
|
TcpConnect<Uri>,
|
||||||
Response = TcpConnection<Uri, U>,
|
Response = TcpConnection<Uri, U>,
|
||||||
Error = actix_connect::ConnectError,
|
Error = actix_tls::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<Request = Connect, Response = impl Connection, Error = ConnectError>
|
) -> impl Service<Connect, Response = impl Connection, Error = ConnectError> + Clone
|
||||||
+ 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";
|
||||||
#[cfg(feature = "openssl")]
|
|
||||||
use actix_connect::ssl::openssl::OpensslConnector;
|
|
||||||
#[cfg(feature = "rustls")]
|
|
||||||
use actix_connect::ssl::rustls::{RustlsConnector, Session};
|
|
||||||
use actix_service::{boxed::service, pipeline};
|
use actix_service::{boxed::service, pipeline};
|
||||||
|
#[cfg(feature = "openssl")]
|
||||||
|
use actix_tls::connect::ssl::openssl::OpensslConnector;
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
|
use actix_tls::connect::ssl::rustls::{RustlsConnector, Session};
|
||||||
|
|
||||||
let ssl_service = TimeoutService::new(
|
let ssl_service = TimeoutService::new(
|
||||||
self.config.timeout,
|
self.config.timeout,
|
||||||
@ -363,8 +363,7 @@ 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<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
|
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
|
||||||
+ 'static,
|
|
||||||
{
|
{
|
||||||
pub(crate) tcp_pool: ConnectionPool<T, Io>,
|
pub(crate) tcp_pool: ConnectionPool<T, Io>,
|
||||||
}
|
}
|
||||||
@ -372,8 +371,7 @@ 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<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
|
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
|
||||||
+ 'static,
|
|
||||||
{
|
{
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
InnerConnector {
|
InnerConnector {
|
||||||
@ -382,17 +380,15 @@ mod connect_impl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, Io> Service for InnerConnector<T, Io>
|
impl<T, Io> Service<Connect> for InnerConnector<T, Io>
|
||||||
where
|
where
|
||||||
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
|
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
|
||||||
+ '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>::Future,
|
<ConnectionPool<T, Io> as Service<Connect>>::Future,
|
||||||
Ready<Result<IoConnection<Io>, ConnectError>>,
|
Ready<Result<IoConnection<Io>, ConnectError>>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@ -428,8 +424,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<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>,
|
T1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError>,
|
||||||
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>,
|
T2: Service<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>,
|
||||||
@ -439,10 +435,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<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
|
T1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
|
||||||
+ 'static,
|
T2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
|
||||||
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
|
|
||||||
+ 'static,
|
|
||||||
{
|
{
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
InnerConnector {
|
InnerConnector {
|
||||||
@ -452,16 +446,13 @@ mod connect_impl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T1, T2, Io1, Io2> Service for InnerConnector<T1, T2, Io1, Io2>
|
impl<T1, T2, Io1, Io2> Service<Connect> 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<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
|
T1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
|
||||||
+ 'static,
|
T2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + '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<
|
||||||
@ -477,11 +468,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),
|
||||||
_t: PhantomData,
|
_phantom: PhantomData,
|
||||||
}),
|
}),
|
||||||
_ => Either::Left(InnerConnectorResponseA {
|
_ => Either::Left(InnerConnectorResponseA {
|
||||||
fut: self.tcp_pool.call(req),
|
fut: self.tcp_pool.call(req),
|
||||||
_t: PhantomData,
|
_phantom: PhantomData,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -491,18 +482,16 @@ 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<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
|
T: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
|
||||||
+ 'static,
|
|
||||||
{
|
{
|
||||||
#[pin]
|
#[pin]
|
||||||
fut: <ConnectionPool<T, Io1> as Service>::Future,
|
fut: <ConnectionPool<T, Io1> as Service<Connect>>::Future,
|
||||||
_t: PhantomData<Io2>,
|
_phantom: 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<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
|
T: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
|
||||||
+ 'static,
|
|
||||||
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
{
|
{
|
||||||
@ -520,18 +509,16 @@ 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<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
|
T: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
|
||||||
+ 'static,
|
|
||||||
{
|
{
|
||||||
#[pin]
|
#[pin]
|
||||||
fut: <ConnectionPool<T, Io2> as Service>::Future,
|
fut: <ConnectionPool<T, Io2> as Service<Connect>>::Future,
|
||||||
_t: PhantomData<Io1>,
|
_phantom: 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<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
|
T: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
|
||||||
+ '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_connect::resolver::ResolveError;
|
use actix_tls::connect::resolver::ResolveError;
|
||||||
use derive_more::{Display, From};
|
use derive_more::{Display, From};
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
use actix_connect::ssl::openssl::{HandshakeError, SslError};
|
use actix_tls::accept::openssl::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,11 +21,6 @@ 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),
|
||||||
@ -57,25 +52,18 @@ pub enum ConnectError {
|
|||||||
|
|
||||||
impl std::error::Error for ConnectError {}
|
impl std::error::Error for ConnectError {}
|
||||||
|
|
||||||
impl From<actix_connect::ConnectError> for ConnectError {
|
impl From<actix_tls::connect::ConnectError> for ConnectError {
|
||||||
fn from(err: actix_connect::ConnectError) -> ConnectError {
|
fn from(err: actix_tls::connect::ConnectError) -> ConnectError {
|
||||||
match err {
|
match err {
|
||||||
actix_connect::ConnectError::Resolver(e) => ConnectError::Resolver(e),
|
actix_tls::connect::ConnectError::Resolver(e) => ConnectError::Resolver(e),
|
||||||
actix_connect::ConnectError::NoRecords => ConnectError::NoRecords,
|
actix_tls::connect::ConnectError::NoRecords => ConnectError::NoRecords,
|
||||||
actix_connect::ConnectError::InvalidInput => panic!(),
|
actix_tls::connect::ConnectError::InvalidInput => panic!(),
|
||||||
actix_connect::ConnectError::Unresolved => ConnectError::Unresolved,
|
actix_tls::connect::ConnectError::Unresolved => ConnectError::Unresolved,
|
||||||
actix_connect::ConnectError::Io(e) => ConnectError::Io(e),
|
actix_tls::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, mem, time};
|
use std::{io, time};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf};
|
||||||
use bytes::buf::BufMutExt;
|
use bytes::buf::BufMut;
|
||||||
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,18 +204,11 @@ 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 [u8],
|
buf: &mut ReadBuf<'_>,
|
||||||
) -> Poll<io::Result<usize>> {
|
) -> Poll<io::Result<()>> {
|
||||||
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,11 +6,12 @@ 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};
|
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
use actix_rt::time::{delay_for, Delay};
|
use actix_rt::time::{sleep, Sleep};
|
||||||
use actix_service::Service;
|
use actix_service::Service;
|
||||||
use actix_utils::{oneshot, task::LocalWaker};
|
use actix_utils::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};
|
||||||
@ -49,8 +50,7 @@ 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<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
|
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
|
||||||
+ '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,13 +89,11 @@ impl<T, Io> Drop for ConnectionPool<T, Io> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, Io> Service for ConnectionPool<T, Io>
|
impl<T, Io> Service<Connect> for ConnectionPool<T, Io>
|
||||||
where
|
where
|
||||||
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
|
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
|
||||||
+ '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>>;
|
||||||
@ -333,10 +331,11 @@ 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 buf) {
|
match Pin::new(s).poll_read(cx, &mut read_buf) {
|
||||||
Poll::Pending => (),
|
Poll::Pending => {}
|
||||||
Poll::Ready(Ok(n)) if n > 0 => {
|
Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => {
|
||||||
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(
|
||||||
@ -386,9 +385,11 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pin_project::pin_project]
|
||||||
struct CloseConnection<T> {
|
struct CloseConnection<T> {
|
||||||
io: T,
|
io: T,
|
||||||
timeout: Delay,
|
#[pin]
|
||||||
|
timeout: Sleep,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> CloseConnection<T>
|
impl<T> CloseConnection<T>
|
||||||
@ -398,7 +399,7 @@ where
|
|||||||
fn new(io: T, timeout: Duration) -> Self {
|
fn new(io: T, timeout: Duration) -> Self {
|
||||||
CloseConnection {
|
CloseConnection {
|
||||||
io,
|
io,
|
||||||
timeout: delay_for(timeout),
|
timeout: sleep(timeout),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -410,11 +411,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.get_mut();
|
let this = self.project();
|
||||||
|
|
||||||
match Pin::new(&mut this.timeout).poll(cx) {
|
match this.timeout.poll(cx) {
|
||||||
Poll::Ready(_) => Poll::Ready(()),
|
Poll::Ready(_) => Poll::Ready(()),
|
||||||
Poll::Pending => match Pin::new(&mut this.io).poll_shutdown(cx) {
|
Poll::Pending => match Pin::new(this.io).poll_shutdown(cx) {
|
||||||
Poll::Ready(_) => Poll::Ready(()),
|
Poll::Ready(_) => Poll::Ready(()),
|
||||||
Poll::Pending => Poll::Pending,
|
Poll::Pending => Poll::Pending,
|
||||||
},
|
},
|
||||||
@ -434,7 +435,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<Request = Connect, Response = (Io, Protocol), Error = ConnectError>,
|
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError>,
|
||||||
T::Future: 'static,
|
T::Future: 'static,
|
||||||
{
|
{
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
0
actix-http/src/clinu/mod.rs
Normal file
0
actix-http/src/clinu/mod.rs
Normal file
@ -1,40 +0,0 @@
|
|||||||
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::{delay_for, delay_until, Delay, Instant};
|
use actix_rt::time::{sleep, sleep_until, Instant, Sleep};
|
||||||
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<Delay> {
|
pub fn client_timer(&self) -> Option<Sleep> {
|
||||||
let delay_time = self.0.client_timeout;
|
let delay_time = self.0.client_timeout;
|
||||||
if delay_time != 0 {
|
if delay_time != 0 {
|
||||||
Some(delay_until(
|
Some(sleep_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<Delay> {
|
pub fn keep_alive_timer(&self) -> Option<Sleep> {
|
||||||
if let Some(ka) = self.0.keep_alive {
|
if let Some(ka) = self.0.keep_alive {
|
||||||
Some(delay_until(self.0.timer.now() + ka))
|
Some(sleep_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(delay_for(Duration::from_millis(500)).then(move |_| {
|
actix_rt::spawn(sleep(Duration::from_millis(500)).then(move |_| {
|
||||||
s.0.reset();
|
s.0.reset();
|
||||||
future::ready(())
|
future::ready(())
|
||||||
}));
|
}));
|
||||||
|
@ -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 [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
|
/// A specialized [`std::result::Result`]
|
||||||
/// 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
|
||||||
@ -178,11 +178,7 @@ impl ResponseError for FormError {}
|
|||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
/// `InternalServerError` for `openssl::ssl::Error`
|
/// `InternalServerError` for `openssl::ssl::Error`
|
||||||
impl ResponseError for actix_connect::ssl::openssl::SslError {}
|
impl ResponseError for actix_tls::accept::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 {
|
||||||
@ -956,11 +952,6 @@ 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;
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
/// A type map of request extensions.
|
/// A type map of request extensions.
|
||||||
|
#[derive(Default)]
|
||||||
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,6 +58,7 @@ impl Codec {
|
|||||||
} else {
|
} else {
|
||||||
Flags::empty()
|
Flags::empty()
|
||||||
};
|
};
|
||||||
|
|
||||||
Codec {
|
Codec {
|
||||||
config,
|
config,
|
||||||
flags,
|
flags,
|
||||||
@ -69,26 +70,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
|
||||||
@ -110,8 +111,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 self.payload.is_some() {
|
if let Some(ref mut payload) = self.payload {
|
||||||
Ok(match self.payload.as_mut().unwrap().decode(src)? {
|
Ok(match payload.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();
|
||||||
|
@ -137,7 +137,7 @@ pub(crate) trait MessageType: Sized {
|
|||||||
expect = true;
|
expect = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
headers.append(name, value);
|
headers.append(name, value);
|
||||||
@ -685,7 +685,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"),
|
||||||
}
|
}
|
||||||
|
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,
|
||||||
_t: PhantomData<T>,
|
_phantom: 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(),
|
||||||
_t: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,14 +64,17 @@ pub(crate) trait MessageType: Sized {
|
|||||||
// Content length
|
// Content length
|
||||||
if let Some(status) = self.status() {
|
if let Some(status) = self.status() {
|
||||||
match status {
|
match status {
|
||||||
StatusCode::NO_CONTENT
|
StatusCode::CONTINUE
|
||||||
| StatusCode::CONTINUE
|
| StatusCode::SWITCHING_PROTOCOLS
|
||||||
| StatusCode::PROCESSING => length = BodySize::None,
|
| StatusCode::PROCESSING
|
||||||
StatusCode::SWITCHING_PROTOCOLS => {
|
| StatusCode::NO_CONTENT => {
|
||||||
|
// skip content-length and transfer-encoding headers
|
||||||
|
// See https://tools.ietf.org/html/rfc7230#section-3.3.1
|
||||||
|
// and https://tools.ietf.org/html/rfc7230#section-3.3.2
|
||||||
skip_len = true;
|
skip_len = true;
|
||||||
length = BodySize::Stream;
|
length = BodySize::None
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match length {
|
match length {
|
||||||
@ -115,7 +118,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.
|
||||||
@ -132,7 +135,7 @@ pub(crate) trait MessageType: Sized {
|
|||||||
|
|
||||||
let mut has_date = false;
|
let mut has_date = false;
|
||||||
|
|
||||||
let mut buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
|
let mut buf = dst.chunk_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
|
||||||
@ -145,7 +148,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();
|
||||||
@ -174,7 +177,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.bytes_mut().as_mut_ptr() as *mut u8;
|
buf = dst.chunk_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
|
||||||
@ -221,7 +224,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.bytes_mut().as_mut_ptr() as *mut u8;
|
buf = dst.chunk_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
|
||||||
@ -529,30 +532,29 @@ 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]) {
|
||||||
let mut index = 0;
|
// first copy entire (potentially wrong) slice to output
|
||||||
let key = value;
|
buffer[..value.len()].copy_from_slice(value);
|
||||||
let mut key_iter = key.iter();
|
|
||||||
|
|
||||||
if let Some(c) = key_iter.next() {
|
let mut iter = value.iter();
|
||||||
if *c >= b'a' && *c <= b'z' {
|
|
||||||
buffer[index] = *c ^ b' ';
|
// first character should be uppercase
|
||||||
index += 1;
|
if let Some(c @ b'a'..=b'z') = iter.next() {
|
||||||
}
|
buffer[0] = c & 0b1101_1111;
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(c) = key_iter.next() {
|
// track 1 ahead of the current position since that's the location being assigned to
|
||||||
buffer[index] = *c;
|
let mut index = 2;
|
||||||
index += 1;
|
|
||||||
if *c == b'-' {
|
// remaining characters after hyphens should also be uppercase
|
||||||
if let Some(c) = key_iter.next() {
|
while let Some(&c) = iter.next() {
|
||||||
if *c >= b'a' && *c <= b'z' {
|
if c == b'-' {
|
||||||
buffer[index] = *c ^ b' ';
|
// advance iter by one and uppercase if needed
|
||||||
index += 1;
|
if let Some(c @ b'a'..=b'z') = iter.next() {
|
||||||
}
|
buffer[index] = c & 0b1101_1111;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
index += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -601,6 +603,8 @@ 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"));
|
||||||
@ -676,4 +680,28 @@ mod tests {
|
|||||||
assert!(data.contains("authorization: another authorization\r\n"));
|
assert!(data.contains("authorization: another authorization\r\n"));
|
||||||
assert!(data.contains("date: date\r\n"));
|
assert!(data.contains("date: date\r\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_content_length() {
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048);
|
||||||
|
|
||||||
|
let mut res: Response<()> =
|
||||||
|
Response::new(StatusCode::SWITCHING_PROTOCOLS).into_body::<()>();
|
||||||
|
res.headers_mut()
|
||||||
|
.insert(DATE, HeaderValue::from_static(&""));
|
||||||
|
res.headers_mut()
|
||||||
|
.insert(CONTENT_LENGTH, HeaderValue::from_static(&"0"));
|
||||||
|
|
||||||
|
let _ = res.encode_headers(
|
||||||
|
&mut bytes,
|
||||||
|
Version::HTTP_11,
|
||||||
|
BodySize::Stream,
|
||||||
|
ConnectionType::Upgrade,
|
||||||
|
&ServiceConfig::default(),
|
||||||
|
);
|
||||||
|
let data =
|
||||||
|
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||||
|
assert!(!data.contains("content-length: 0\r\n"));
|
||||||
|
assert!(!data.contains("transfer-encoding: chunked\r\n"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,27 @@
|
|||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use actix_service::{Service, ServiceFactory};
|
use actix_service::{Service, ServiceFactory};
|
||||||
use futures_util::future::{ok, Ready};
|
use futures_util::future::{ready, 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 for ExpectHandler {
|
impl ServiceFactory<Request> 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::Future {
|
fn new_service(&self, _: Self::Config) -> Self::Future {
|
||||||
ok(ExpectHandler)
|
ready(Ok(ExpectHandler))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service for ExpectHandler {
|
impl Service<Request> 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>>;
|
||||||
@ -33,6 +31,8 @@ impl Service for ExpectHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: Request) -> Self::Future {
|
fn call(&mut self, req: Request) -> Self::Future {
|
||||||
ok(req)
|
ready(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, OneRequest};
|
pub use self::service::{H1Service, H1ServiceHandler};
|
||||||
pub use self::upgrade::UpgradeHandler;
|
pub use self::upgrade::UpgradeHandler;
|
||||||
pub use self::utils::SendResponse;
|
pub use self::utils::SendResponse;
|
||||||
|
|
||||||
|
@ -182,9 +182,7 @@ 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;
|
||||||
if let Some(task) = self.task.take() {
|
self.task.wake();
|
||||||
task.wake()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
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;
|
||||||
@ -9,42 +10,40 @@ 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::{ok, Ready};
|
use futures_util::future::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, ParseError};
|
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::{ConnectCallback, Extensions};
|
use crate::service::HttpFlow;
|
||||||
|
use crate::{ConnectCallback, OnConnectData};
|
||||||
|
|
||||||
use super::codec::Codec;
|
use super::codec::Codec;
|
||||||
use super::dispatcher::Dispatcher;
|
use super::dispatcher::Dispatcher;
|
||||||
use super::{ExpectHandler, Message, UpgradeHandler};
|
use super::{ExpectHandler, UpgradeHandler};
|
||||||
|
|
||||||
/// `ServiceFactory` implementation for HTTP1 transport
|
/// `ServiceFactory` implementation for HTTP1 transport
|
||||||
pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler<T>> {
|
pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> {
|
||||||
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>>>,
|
||||||
_t: PhantomData<(T, B)>,
|
_phantom: PhantomData<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> H1Service<T, S, B>
|
impl<T, S, B> H1Service<T, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>>(
|
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
service: F,
|
service: F,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -53,28 +52,23 @@ 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,
|
||||||
_t: PhantomData,
|
_phantom: 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<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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<Config = (), Request = Request, Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<(Request, Framed<TcpStream, Codec>), Config = (), Response = ()>,
|
||||||
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,
|
||||||
{
|
{
|
||||||
@ -82,15 +76,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();
|
||||||
ok((io, peer_addr))
|
ready(Ok((io, peer_addr)))
|
||||||
})
|
})
|
||||||
.and_then(self)
|
.and_then(self)
|
||||||
}
|
}
|
||||||
@ -100,22 +94,23 @@ where
|
|||||||
mod openssl {
|
mod openssl {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
|
use actix_service::ServiceFactoryExt;
|
||||||
use actix_tls::{openssl::HandshakeError, TlsError};
|
use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream};
|
||||||
|
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<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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<Config = (), Request = Request, Response = Request>,
|
X: ServiceFactory<Request, Config = (), 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>,
|
||||||
@ -126,10 +121,10 @@ mod openssl {
|
|||||||
self,
|
self,
|
||||||
acceptor: SslAcceptor,
|
acceptor: SslAcceptor,
|
||||||
) -> impl ServiceFactory<
|
) -> impl ServiceFactory<
|
||||||
|
TcpStream,
|
||||||
Config = (),
|
Config = (),
|
||||||
Request = TcpStream,
|
|
||||||
Response = (),
|
Response = (),
|
||||||
Error = TlsError<HandshakeError<TcpStream>, DispatchError>,
|
Error = TlsError<SslError, DispatchError>,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
> {
|
> {
|
||||||
pipeline_factory(
|
pipeline_factory(
|
||||||
@ -139,7 +134,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();
|
||||||
ok((io, peer_addr))
|
ready(Ok((io, peer_addr)))
|
||||||
})
|
})
|
||||||
.and_then(self.map_err(TlsError::Service))
|
.and_then(self.map_err(TlsError::Service))
|
||||||
}
|
}
|
||||||
@ -149,23 +144,24 @@ mod openssl {
|
|||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
mod rustls {
|
mod rustls {
|
||||||
use super::*;
|
use super::*;
|
||||||
use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream};
|
use actix_service::ServiceFactoryExt;
|
||||||
use actix_tls::TlsError;
|
use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream};
|
||||||
|
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<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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<Config = (), Request = Request, Response = Request>,
|
X: ServiceFactory<Request, Config = (), 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>,
|
||||||
@ -176,8 +172,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 = (),
|
||||||
@ -189,7 +185,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();
|
||||||
ok((io, peer_addr))
|
ready(Ok((io, peer_addr)))
|
||||||
})
|
})
|
||||||
.and_then(self.map_err(TlsError::Service))
|
.and_then(self.map_err(TlsError::Service))
|
||||||
}
|
}
|
||||||
@ -198,7 +194,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<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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,
|
||||||
@ -206,7 +202,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 = Request, Response = Request>,
|
X1: ServiceFactory<Request, Response = Request>,
|
||||||
X1::Error: Into<Error>,
|
X1::Error: Into<Error>,
|
||||||
X1::InitError: fmt::Debug,
|
X1::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
@ -215,15 +211,14 @@ 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,
|
||||||
_t: PhantomData,
|
_phantom: 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 = (Request, Framed<T, Codec>), Response = ()>,
|
U1: ServiceFactory<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U1::Error: fmt::Display,
|
U1::Error: fmt::Display,
|
||||||
U1::InitError: fmt::Debug,
|
U1::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
@ -232,21 +227,11 @@ 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,
|
||||||
_t: PhantomData,
|
_phantom: 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;
|
||||||
@ -254,27 +239,27 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> ServiceFactory for H1Service<T, S, B, X, U>
|
impl<T, S, B, X, U> ServiceFactory<(T, Option<net::SocketAddr>)>
|
||||||
|
for H1Service<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: ServiceFactory<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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<Config = (), Request = Request, Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
U: ServiceFactory<Config = (), Request = (Request, Framed<T, Codec>), Response = ()>,
|
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), 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 InitError = ();
|
type Config = ();
|
||||||
type Service = H1ServiceHandler<T, S::Service, B, X::Service, U::Service>;
|
type Service = H1ServiceHandler<T, S::Service, B, X::Service, U::Service>;
|
||||||
|
type InitError = ();
|
||||||
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 {
|
||||||
@ -284,10 +269,9 @@ 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()),
|
||||||
_t: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -296,13 +280,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 = Request>,
|
S: ServiceFactory<Request>,
|
||||||
S::Error: Into<Error>,
|
S::Error: Into<Error>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
X: ServiceFactory<Request = Request, Response = Request>,
|
X: ServiceFactory<Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
U: ServiceFactory<Request = (Request, Framed<T, Codec>), Response = ()>,
|
U: ServiceFactory<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
@ -314,24 +298,23 @@ 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>,
|
||||||
_t: PhantomData<(T, B)>,
|
_phantom: PhantomData<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 = Request>,
|
S: ServiceFactory<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 = Request, Response = Request>,
|
X: ServiceFactory<Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
U: ServiceFactory<Request = (Request, Framed<T, Codec>), Response = ()>,
|
U: ServiceFactory<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
@ -355,7 +338,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_ex.set(None);
|
this.fut_upg.set(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = ready!(this
|
let result = ready!(this
|
||||||
@ -371,7 +354,6 @@ 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(),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
@ -379,66 +361,65 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `Service` implementation for HTTP/1 transport
|
/// `Service` implementation for HTTP/1 transport
|
||||||
pub struct H1ServiceHandler<T, S: Service, B, X: Service, U: Service> {
|
pub struct H1ServiceHandler<T, S, B, X, U>
|
||||||
srv: CloneableService<S>,
|
where
|
||||||
expect: CloneableService<X>,
|
S: Service<Request>,
|
||||||
upgrade: Option<CloneableService<U>>,
|
X: Service<Request>,
|
||||||
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
U: Service<(Request, Framed<T, Codec>)>,
|
||||||
|
{
|
||||||
|
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,
|
||||||
_t: PhantomData<(T, B)>,
|
_phantom: PhantomData<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 = Request>,
|
S: Service<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 = Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
fn new(
|
fn new(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
srv: S,
|
service: 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 {
|
||||||
srv: CloneableService::new(srv),
|
flow: HttpFlow::new(service, expect, upgrade),
|
||||||
expect: CloneableService::new(expect),
|
|
||||||
upgrade: upgrade.map(CloneableService::new),
|
|
||||||
cfg,
|
cfg,
|
||||||
on_connect,
|
|
||||||
on_connect_ext,
|
on_connect_ext,
|
||||||
_t: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> Service for H1ServiceHandler<T, S, B, X, U>
|
impl<T, S, B, X, U> Service<(T, Option<net::SocketAddr>)>
|
||||||
|
for H1ServiceHandler<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request = Request>,
|
S: Service<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 = Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
U: Service<(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 ready = self
|
let mut flow = self.flow.borrow_mut();
|
||||||
|
let ready = flow
|
||||||
.expect
|
.expect
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@ -448,8 +429,8 @@ where
|
|||||||
})?
|
})?
|
||||||
.is_ready();
|
.is_ready();
|
||||||
|
|
||||||
let ready = self
|
let ready = flow
|
||||||
.srv
|
.service
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
@ -459,7 +440,7 @@ where
|
|||||||
.is_ready()
|
.is_ready()
|
||||||
&& ready;
|
&& ready;
|
||||||
|
|
||||||
let ready = if let Some(ref mut upg) = self.upgrade {
|
let ready = if let Some(ref mut upg) = flow.upgrade {
|
||||||
upg.poll_ready(cx)
|
upg.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
@ -479,124 +460,16 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
|
fn call(&mut self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
|
||||||
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
|
let on_connect_data =
|
||||||
|
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.srv.clone(),
|
self.flow.clone(),
|
||||||
self.expect.clone(),
|
on_connect_data,
|
||||||
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,22 +1,20 @@
|
|||||||
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;
|
use futures_util::future::{ready, 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<T>(PhantomData<T>);
|
pub struct UpgradeHandler;
|
||||||
|
|
||||||
impl<T> ServiceFactory for UpgradeHandler<T> {
|
impl<T> ServiceFactory<(Request, Framed<T, Codec>)> for UpgradeHandler {
|
||||||
type Config = ();
|
|
||||||
type Request = (Request, Framed<T, Codec>);
|
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Service = UpgradeHandler<T>;
|
type Config = ();
|
||||||
|
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>>;
|
||||||
|
|
||||||
@ -25,8 +23,7 @@ impl<T> ServiceFactory for UpgradeHandler<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Service for UpgradeHandler<T> {
|
impl<T> Service<(Request, Framed<T, Codec>)> for UpgradeHandler {
|
||||||
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>>;
|
||||||
@ -35,7 +32,7 @@ impl<T> Service for UpgradeHandler<T> {
|
|||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, _: Self::Request) -> Self::Future {
|
fn call(&mut self, _: (Request, Framed<T, Codec>)) -> Self::Future {
|
||||||
unimplemented!()
|
ready(Ok(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,66 +1,66 @@
|
|||||||
use std::convert::TryFrom;
|
use std::cell::RefCell;
|
||||||
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::{Delay, Instant};
|
use actix_rt::time::{Instant, Sleep};
|
||||||
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::Extensions;
|
use crate::service::HttpFlow;
|
||||||
|
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: Service<Request = Request>, B: MessageBody>
|
pub struct Dispatcher<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
S: Service<Request>,
|
||||||
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
service: CloneableService<S>,
|
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
|
||||||
connection: Connection<T, Bytes>,
|
connection: Connection<T, Bytes>,
|
||||||
on_connect: Option<Box<dyn DataFactory>>,
|
on_connect_data: OnConnectData,
|
||||||
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<Delay>,
|
ka_timer: Option<Sleep>,
|
||||||
_t: PhantomData<B>,
|
_phantom: PhantomData<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> Dispatcher<T, S, B>
|
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request = Request>,
|
S: Service<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(
|
||||||
service: CloneableService<S>,
|
services: Rc<RefCell<HttpFlow<S, X, U>>>,
|
||||||
connection: Connection<T, Bytes>,
|
connection: Connection<T, Bytes>,
|
||||||
on_connect: Option<Box<dyn DataFactory>>,
|
on_connect_data: OnConnectData,
|
||||||
on_connect_data: Extensions,
|
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
timeout: Option<Delay>,
|
timeout: Option<Sleep>,
|
||||||
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,23 +80,22 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
Dispatcher {
|
Dispatcher {
|
||||||
service,
|
flow: services,
|
||||||
config,
|
config,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
connection,
|
connection,
|
||||||
on_connect,
|
|
||||||
on_connect_data,
|
on_connect_data,
|
||||||
ka_expire,
|
ka_expire,
|
||||||
ka_timer,
|
ka_timer,
|
||||||
_t: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> Future for Dispatcher<T, S, B>
|
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request = Request>,
|
S: Service<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,
|
||||||
@ -109,10 +108,12 @@ where
|
|||||||
let this = self.get_mut();
|
let this = self.get_mut();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match Pin::new(&mut this.connection).poll_accept(cx) {
|
match ready!(Pin::new(&mut this.connection).poll_accept(cx)) {
|
||||||
Poll::Ready(None) => return Poll::Ready(Ok(())),
|
None => return Poll::Ready(Ok(())),
|
||||||
Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())),
|
|
||||||
Poll::Ready(Some(Ok((req, res)))) => {
|
Some(Err(err)) => return Poll::Ready(Err(err.into())),
|
||||||
|
|
||||||
|
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() {
|
||||||
@ -121,11 +122,9 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (parts, body) = req.into_parts();
|
let (parts, body) = req.into_parts();
|
||||||
let mut req = Request::with_payload(Payload::<
|
let pl = crate::h2::Payload::new(body);
|
||||||
crate::payload::PayloadStream,
|
let pl = Payload::<crate::payload::PayloadStream>::H2(pl);
|
||||||
>::H2(
|
let mut req = Request::with_payload(pl);
|
||||||
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;
|
||||||
@ -134,31 +133,21 @@ where
|
|||||||
head.headers = parts.headers.into();
|
head.headers = parts.headers.into();
|
||||||
head.peer_addr = this.peer_addr;
|
head.peer_addr = this.peer_addr;
|
||||||
|
|
||||||
// DEPRECATED
|
|
||||||
// set on_connect data
|
|
||||||
if let Some(ref on_connect) = this.on_connect {
|
|
||||||
on_connect.set(&mut req.extensions_mut());
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge on_connect_ext data into request extensions
|
// merge on_connect_ext data into request extensions
|
||||||
req.extensions_mut().drain_from(&mut this.on_connect_data);
|
this.on_connect_data.merge_into(&mut req);
|
||||||
|
|
||||||
actix_rt::spawn(ServiceResponse::<
|
let svc = ServiceResponse::<S::Future, S::Response, S::Error, B> {
|
||||||
S::Future,
|
|
||||||
S::Response,
|
|
||||||
S::Error,
|
|
||||||
B,
|
|
||||||
> {
|
|
||||||
state: ServiceResponseState::ServiceCall(
|
state: ServiceResponseState::ServiceCall(
|
||||||
this.service.call(req),
|
this.flow.borrow_mut().service.call(req),
|
||||||
Some(res),
|
Some(res),
|
||||||
),
|
),
|
||||||
config: this.config.clone(),
|
config: this.config.clone(),
|
||||||
buffer: None,
|
buffer: None,
|
||||||
_t: PhantomData,
|
_phantom: PhantomData,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
actix_rt::spawn(svc);
|
||||||
}
|
}
|
||||||
Poll::Pending => return Poll::Pending,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,7 +159,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>,
|
||||||
_t: PhantomData<(I, E)>,
|
_phantom: PhantomData<(I, E)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project::pin_project(project = ServiceResponseStateProj)]
|
#[pin_project::pin_project(project = ServiceResponseStateProj)]
|
||||||
@ -207,8 +196,9 @@ 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
|
||||||
@ -223,11 +213,13 @@ where
|
|||||||
// copy headers
|
// copy headers
|
||||||
for (key, value) in head.headers.iter() {
|
for (key, value) in head.headers.iter() {
|
||||||
match *key {
|
match *key {
|
||||||
CONNECTION | TRANSFER_ENCODING => continue, // http2 specific
|
// omit HTTP/1 only headers
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,109 +251,117 @@ 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) => match call.poll(cx) {
|
ServiceResponseStateProj::ServiceCall(call, send) => {
|
||||||
Poll::Ready(Ok(res)) => {
|
match ready!(call.poll(cx)) {
|
||||||
let (res, body) = res.into().replace_body(());
|
Ok(res) => {
|
||||||
|
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 = self.as_mut().prepare_response(res.head(), &mut size);
|
let h2_res =
|
||||||
this = self.as_mut().project();
|
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()) {
|
let stream = match send.send_response(h2_res, size.is_eof()) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
trace!("Error sending h2 response: {:?}", e);
|
trace!("Error sending HTTP/2 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,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if size.is_eof() {
|
Err(e) => {
|
||||||
Poll::Ready(())
|
let res: Response = e.into().into();
|
||||||
} else {
|
let (res, body) = res.replace_body(());
|
||||||
this.state
|
|
||||||
.set(ServiceResponseState::SendPayload(stream, body));
|
let mut send = send.take().unwrap();
|
||||||
self.poll(cx)
|
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 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 {
|
||||||
if let Some(ref mut buffer) = this.buffer {
|
match this.buffer {
|
||||||
match stream.poll_capacity(cx) {
|
Some(ref mut buffer) => {
|
||||||
Poll::Pending => return Poll::Pending,
|
match ready!(stream.poll_capacity(cx)) {
|
||||||
Poll::Ready(None) => return 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));
|
|
||||||
|
|
||||||
if let Err(e) = stream.send_data(bytes, false) {
|
Some(Ok(cap)) => {
|
||||||
|
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 {
|
|
||||||
match body.as_mut().poll_next(cx) {
|
None => match ready!(body.as_mut().poll_next(cx)) {
|
||||||
Poll::Pending => return Poll::Pending,
|
None => {
|
||||||
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))) => {
|
|
||||||
stream.reserve_capacity(std::cmp::min(
|
Some(Ok(chunk)) => {
|
||||||
|
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,9 +1,12 @@
|
|||||||
//! HTTP/2 implementation
|
//! HTTP/2 implementation.
|
||||||
use std::pin::Pin;
|
|
||||||
use std::task::{Context, Poll};
|
use std::{
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::Stream;
|
use futures_core::{ready, Stream};
|
||||||
use h2::RecvStream;
|
use h2::RecvStream;
|
||||||
|
|
||||||
mod dispatcher;
|
mod dispatcher;
|
||||||
@ -13,14 +16,14 @@ pub use self::dispatcher::Dispatcher;
|
|||||||
pub use self::service::H2Service;
|
pub use self::service::H2Service;
|
||||||
use crate::error::PayloadError;
|
use crate::error::PayloadError;
|
||||||
|
|
||||||
/// H2 receive stream
|
/// HTTP/2 peer stream.
|
||||||
pub struct Payload {
|
pub struct Payload {
|
||||||
pl: RecvStream,
|
stream: RecvStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Payload {
|
impl Payload {
|
||||||
pub(crate) fn new(pl: RecvStream) -> Self {
|
pub(crate) fn new(stream: RecvStream) -> Self {
|
||||||
Self { pl }
|
Self { stream }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,18 +36,17 @@ impl Stream for Payload {
|
|||||||
) -> Poll<Option<Self::Item>> {
|
) -> Poll<Option<Self::Item>> {
|
||||||
let this = self.get_mut();
|
let this = self.get_mut();
|
||||||
|
|
||||||
match Pin::new(&mut this.pl).poll_data(cx) {
|
match ready!(Pin::new(&mut this.stream).poll_data(cx)) {
|
||||||
Poll::Ready(Some(Ok(chunk))) => {
|
Some(Ok(chunk)) => {
|
||||||
let len = chunk.len();
|
let len = chunk.len();
|
||||||
if let Err(err) = this.pl.flow_control().release_capacity(len) {
|
|
||||||
Poll::Ready(Some(Err(err.into())))
|
match this.stream.flow_control().release_capacity(len) {
|
||||||
} else {
|
Ok(()) => Poll::Ready(Some(Ok(chunk))),
|
||||||
Poll::Ready(Some(Ok(chunk)))
|
Err(err) => Poll::Ready(Some(Err(err.into()))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err.into()))),
|
Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
|
||||||
Poll::Pending => Poll::Pending,
|
None => Poll::Ready(None),
|
||||||
Poll::Ready(None) => Poll::Ready(None),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
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;
|
||||||
@ -17,57 +18,44 @@ 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::{ConnectCallback, Extensions};
|
use crate::service::HttpFlow;
|
||||||
|
use crate::{ConnectCallback, OnConnectData};
|
||||||
|
|
||||||
use super::dispatcher::Dispatcher;
|
use super::dispatcher::Dispatcher;
|
||||||
|
|
||||||
/// `ServiceFactory` implementation for HTTP2 transport
|
/// `ServiceFactory` implementation for HTTP/2 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>>>,
|
||||||
_t: PhantomData<(T, B)>,
|
_phantom: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> H2Service<T, S, B>
|
impl<T, S, B> H2Service<T, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create new `HttpService` instance with config.
|
/// Create new `H2Service` instance with config.
|
||||||
pub(crate) fn with_config<F: IntoServiceFactory<S>>(
|
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
|
||||||
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(),
|
||||||
_t: PhantomData,
|
_phantom: 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;
|
||||||
@ -77,18 +65,18 @@ where
|
|||||||
|
|
||||||
impl<S, B> H2Service<TcpStream, S, B>
|
impl<S, B> H2Service<TcpStream, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create simple tcp based service
|
/// Create plain 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,
|
||||||
@ -105,29 +93,29 @@ where
|
|||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
mod openssl {
|
mod openssl {
|
||||||
use actix_service::{fn_factory, fn_service};
|
use actix_service::{fn_factory, fn_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;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
impl<S, B> H2Service<SslStream<TcpStream>, S, B>
|
impl<S, B> H2Service<SslStream<TcpStream>, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create ssl 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<HandshakeError<TcpStream>, DispatchError>,
|
Error = TlsError<SslError, DispatchError>,
|
||||||
InitError = S::InitError,
|
InitError = S::InitError,
|
||||||
> {
|
> {
|
||||||
pipeline_factory(
|
pipeline_factory(
|
||||||
@ -149,25 +137,26 @@ mod openssl {
|
|||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
mod rustls {
|
mod rustls {
|
||||||
use super::*;
|
use super::*;
|
||||||
use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream};
|
use actix_service::ServiceFactoryExt;
|
||||||
use actix_tls::TlsError;
|
use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream};
|
||||||
|
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<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create openssl based service
|
/// Create Rustls 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,
|
||||||
@ -191,52 +180,52 @@ mod rustls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> ServiceFactory for H2Service<T, S, B>
|
impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: ServiceFactory<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>::Future: 'static,
|
<S::Service as Service<Request>>::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 InitError = S::InitError;
|
type Config = ();
|
||||||
type Service = H2ServiceHandler<T, S::Service, B>;
|
type Service = H2ServiceHandler<T, S::Service, B>;
|
||||||
|
type InitError = S::InitError;
|
||||||
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(),
|
||||||
_t: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[pin_project::pin_project]
|
#[pin_project::pin_project]
|
||||||
pub struct H2ServiceResponse<T, S: ServiceFactory, B> {
|
pub struct H2ServiceResponse<T, S, 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>>>,
|
||||||
_t: PhantomData<(T, B)>,
|
_phantom: PhantomData<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<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>::Future: 'static,
|
<S::Service as Service<Request>>::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>;
|
||||||
@ -244,30 +233,31 @@ 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();
|
||||||
|
|
||||||
Poll::Ready(ready!(this.fut.poll(cx)).map(|service| {
|
this.fut.poll(cx).map_ok(|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: Service, B> {
|
pub struct H2ServiceHandler<T, S, B>
|
||||||
srv: CloneableService<S>,
|
where
|
||||||
|
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>>>,
|
||||||
_t: PhantomData<(T, B)>,
|
_phantom: PhantomData<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> H2ServiceHandler<T, S, B>
|
impl<T, S, B> H2ServiceHandler<T, S, B>
|
||||||
where
|
where
|
||||||
S: Service<Request = Request>,
|
S: Service<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,
|
||||||
@ -275,76 +265,66 @@ 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>>>,
|
||||||
srv: S,
|
service: 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,
|
||||||
srv: CloneableService::new(srv),
|
_phantom: PhantomData,
|
||||||
_t: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> Service for H2ServiceHandler<T, S, B>
|
impl<T, S, B> Service<(T, Option<net::SocketAddr>)> for H2ServiceHandler<T, S, B>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request = Request>,
|
S: Service<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.srv.poll_ready(cx).map_err(|e| {
|
self.flow.borrow_mut().service.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): Self::Request) -> Self::Future {
|
fn call(&mut self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
|
||||||
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
|
let on_connect_data =
|
||||||
|
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.srv.clone()),
|
Some(self.flow.clone()),
|
||||||
Some(self.cfg.clone()),
|
Some(self.cfg.clone()),
|
||||||
addr,
|
addr,
|
||||||
deprecated_on_connect,
|
on_connect_data,
|
||||||
Some(connect_extensions),
|
|
||||||
server::handshake(io),
|
server::handshake(io),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum State<T, S: Service<Request = Request>, B: MessageBody>
|
enum State<T, S: Service<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<CloneableService<S>>,
|
Option<Rc<RefCell<HttpFlow<S, (), ()>>>>,
|
||||||
Option<ServiceConfig>,
|
Option<ServiceConfig>,
|
||||||
Option<net::SocketAddr>,
|
Option<net::SocketAddr>,
|
||||||
Option<Box<dyn DataFactory>>,
|
OnConnectData,
|
||||||
Option<Extensions>,
|
|
||||||
Handshake<T, Bytes>,
|
Handshake<T, Bytes>,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -352,7 +332,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 = Request>,
|
S: Service<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,
|
||||||
@ -364,7 +344,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 = Request>,
|
S: Service<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,
|
||||||
@ -379,27 +359,25 @@ 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 Pin::new(handshake).poll(cx) {
|
) => match ready!(Pin::new(handshake).poll(cx)) {
|
||||||
Poll::Ready(Ok(conn)) => {
|
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.take(),
|
on_connect_data,
|
||||||
on_connect_data.take().unwrap(),
|
|
||||||
config.take().unwrap(),
|
config.take().unwrap(),
|
||||||
None,
|
None,
|
||||||
*peer_addr,
|
*peer_addr,
|
||||||
));
|
));
|
||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
Poll::Ready(Err(err)) => {
|
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,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
|
|
||||||
use crate::header::{qitem, QualityItem};
|
use crate::header::{qitem, QualityItem};
|
||||||
@ -7,7 +9,7 @@ header! {
|
|||||||
/// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2)
|
/// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2)
|
||||||
///
|
///
|
||||||
/// The `Accept` header field can be used by user agents to specify
|
/// The `Accept` header field can be used by user agents to specify
|
||||||
/// response media types that are acceptable. Accept header fields can
|
/// response media types that are acceptable. Accept header fields can
|
||||||
/// be used to indicate that the request is specifically limited to a
|
/// be used to indicate that the request is specifically limited to a
|
||||||
/// small set of desired types, as in the case of a request for an
|
/// small set of desired types, as in the case of a request for an
|
||||||
/// in-line image
|
/// in-line image
|
||||||
@ -97,14 +99,14 @@ header! {
|
|||||||
test_header!(
|
test_header!(
|
||||||
test1,
|
test1,
|
||||||
vec![b"audio/*; q=0.2, audio/basic"],
|
vec![b"audio/*; q=0.2, audio/basic"],
|
||||||
Some(HeaderField(vec![
|
Some(Accept(vec![
|
||||||
QualityItem::new("audio/*".parse().unwrap(), q(200)),
|
QualityItem::new("audio/*".parse().unwrap(), q(200)),
|
||||||
qitem("audio/basic".parse().unwrap()),
|
qitem("audio/basic".parse().unwrap()),
|
||||||
])));
|
])));
|
||||||
test_header!(
|
test_header!(
|
||||||
test2,
|
test2,
|
||||||
vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
|
vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
|
||||||
Some(HeaderField(vec![
|
Some(Accept(vec![
|
||||||
QualityItem::new(mime::TEXT_PLAIN, q(500)),
|
QualityItem::new(mime::TEXT_PLAIN, q(500)),
|
||||||
qitem(mime::TEXT_HTML),
|
qitem(mime::TEXT_HTML),
|
||||||
QualityItem::new(
|
QualityItem::new(
|
||||||
@ -138,23 +140,148 @@ header! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Accept {
|
impl Accept {
|
||||||
/// A constructor to easily create `Accept: */*`.
|
/// Construct `Accept: */*`.
|
||||||
pub fn star() -> Accept {
|
pub fn star() -> Accept {
|
||||||
Accept(vec![qitem(mime::STAR_STAR)])
|
Accept(vec![qitem(mime::STAR_STAR)])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create `Accept: application/json`.
|
/// Construct `Accept: application/json`.
|
||||||
pub fn json() -> Accept {
|
pub fn json() -> Accept {
|
||||||
Accept(vec![qitem(mime::APPLICATION_JSON)])
|
Accept(vec![qitem(mime::APPLICATION_JSON)])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create `Accept: text/*`.
|
/// Construct `Accept: text/*`.
|
||||||
pub fn text() -> Accept {
|
pub fn text() -> Accept {
|
||||||
Accept(vec![qitem(mime::TEXT_STAR)])
|
Accept(vec![qitem(mime::TEXT_STAR)])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create `Accept: image/*`.
|
/// Construct `Accept: image/*`.
|
||||||
pub fn image() -> Accept {
|
pub fn image() -> Accept {
|
||||||
Accept(vec![qitem(mime::IMAGE_STAR)])
|
Accept(vec![qitem(mime::IMAGE_STAR)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct `Accept: text/html`.
|
||||||
|
pub fn html() -> Accept {
|
||||||
|
Accept(vec![qitem(mime::TEXT_HTML)])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a sorted list of mime types from highest to lowest preference, accounting for
|
||||||
|
/// [q-factor weighting] and specificity.
|
||||||
|
///
|
||||||
|
/// [q-factor weighting]: https://tools.ietf.org/html/rfc7231#section-5.3.2
|
||||||
|
pub fn mime_precedence(&self) -> Vec<Mime> {
|
||||||
|
let mut types = self.0.clone();
|
||||||
|
|
||||||
|
// use stable sort so items with equal q-factor and specificity retain listed order
|
||||||
|
types.sort_by(|a, b| {
|
||||||
|
// sort by q-factor descending
|
||||||
|
b.quality.cmp(&a.quality).then_with(|| {
|
||||||
|
// use specificity rules on mime types with
|
||||||
|
// same q-factor (eg. text/html > text/* > */*)
|
||||||
|
|
||||||
|
// subtypes are not comparable if main type is star, so return
|
||||||
|
match (a.item.type_(), b.item.type_()) {
|
||||||
|
(mime::STAR, mime::STAR) => return Ordering::Equal,
|
||||||
|
|
||||||
|
// a is sorted after b
|
||||||
|
(mime::STAR, _) => return Ordering::Greater,
|
||||||
|
|
||||||
|
// a is sorted before b
|
||||||
|
(_, mime::STAR) => return Ordering::Less,
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in both these match expressions, the returned ordering appears
|
||||||
|
// inverted because sort is high-to-low ("descending") precedence
|
||||||
|
match (a.item.subtype(), b.item.subtype()) {
|
||||||
|
(mime::STAR, mime::STAR) => Ordering::Equal,
|
||||||
|
|
||||||
|
// a is sorted after b
|
||||||
|
(mime::STAR, _) => Ordering::Greater,
|
||||||
|
|
||||||
|
// a is sorted before b
|
||||||
|
(_, mime::STAR) => Ordering::Less,
|
||||||
|
|
||||||
|
_ => Ordering::Equal,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
types.into_iter().map(|qitem| qitem.item).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the most preferable mime type, accounting for [q-factor weighting].
|
||||||
|
///
|
||||||
|
/// If no q-factors are provided, the first mime type is chosen. Note that items without
|
||||||
|
/// q-factors are given the maximum preference value.
|
||||||
|
///
|
||||||
|
/// Returns `None` if contained list is empty.
|
||||||
|
///
|
||||||
|
/// [q-factor weighting]: https://tools.ietf.org/html/rfc7231#section-5.3.2
|
||||||
|
pub fn mime_preference(&self) -> Option<Mime> {
|
||||||
|
let types = self.mime_precedence();
|
||||||
|
types.first().cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::header::q;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mime_precedence() {
|
||||||
|
let test = Accept(vec![]);
|
||||||
|
assert!(test.mime_precedence().is_empty());
|
||||||
|
|
||||||
|
let test = Accept(vec![qitem(mime::APPLICATION_JSON)]);
|
||||||
|
assert_eq!(test.mime_precedence(), vec!(mime::APPLICATION_JSON));
|
||||||
|
|
||||||
|
let test = Accept(vec![
|
||||||
|
qitem(mime::TEXT_HTML),
|
||||||
|
"application/xhtml+xml".parse().unwrap(),
|
||||||
|
QualityItem::new("application/xml".parse().unwrap(), q(0.9)),
|
||||||
|
QualityItem::new(mime::STAR_STAR, q(0.8)),
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
test.mime_precedence(),
|
||||||
|
vec![
|
||||||
|
mime::TEXT_HTML,
|
||||||
|
"application/xhtml+xml".parse().unwrap(),
|
||||||
|
"application/xml".parse().unwrap(),
|
||||||
|
mime::STAR_STAR,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let test = Accept(vec![
|
||||||
|
qitem(mime::STAR_STAR),
|
||||||
|
qitem(mime::IMAGE_STAR),
|
||||||
|
qitem(mime::IMAGE_PNG),
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
test.mime_precedence(),
|
||||||
|
vec![mime::IMAGE_PNG, mime::IMAGE_STAR, mime::STAR_STAR]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mime_preference() {
|
||||||
|
let test = Accept(vec![
|
||||||
|
qitem(mime::TEXT_HTML),
|
||||||
|
"application/xhtml+xml".parse().unwrap(),
|
||||||
|
QualityItem::new("application/xml".parse().unwrap(), q(0.9)),
|
||||||
|
QualityItem::new(mime::STAR_STAR, q(0.8)),
|
||||||
|
]);
|
||||||
|
assert_eq!(test.mime_preference(), Some(mime::TEXT_HTML));
|
||||||
|
|
||||||
|
let test = Accept(vec![
|
||||||
|
QualityItem::new("video/*".parse().unwrap(), q(0.8)),
|
||||||
|
qitem(mime::IMAGE_PNG),
|
||||||
|
QualityItem::new(mime::STAR_STAR, q(0.5)),
|
||||||
|
qitem(mime::IMAGE_SVG),
|
||||||
|
QualityItem::new(mime::IMAGE_STAR, q(0.8)),
|
||||||
|
]);
|
||||||
|
assert_eq!(test.mime_preference(), Some(mime::IMAGE_PNG));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,9 +318,8 @@ impl ContentDisposition {
|
|||||||
return Err(crate::error::ParseError::Header);
|
return Err(crate::error::ParseError::Header);
|
||||||
}
|
}
|
||||||
left = new_left;
|
left = new_left;
|
||||||
if param_name.ends_with('*') {
|
if let Some(param_name) = param_name.strip_suffix('*') {
|
||||||
// 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)?;
|
||||||
@ -550,8 +549,7 @@ impl fmt::Display for ContentDisposition {
|
|||||||
write!(f, "{}", self.disposition)?;
|
write!(f, "{}", self.disposition)?;
|
||||||
self.parameters
|
self.parameters
|
||||||
.iter()
|
.iter()
|
||||||
.map(|param| write!(f, "; {}", param))
|
.try_for_each(|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](https://docs.rs/mime) crate
|
//! strongly-typed theme, the [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,8 +8,6 @@ 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>,
|
||||||
@ -141,8 +139,6 @@ 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,
|
||||||
|
@ -370,9 +370,7 @@ impl fmt::Display for ExtendedValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Percent encode a sequence of bytes with a character set defined in
|
/// Percent encode a sequence of bytes with a character set defined in
|
||||||
/// [https://tools.ietf.org/html/rfc5987#section-3.2][url]
|
/// <https://tools.ietf.org/html/rfc5987#section-3.2>
|
||||||
///
|
|
||||||
/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2
|
|
||||||
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
|
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
|
||||||
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
|
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
|
||||||
fmt::Display::fmt(&encoded, f)
|
fmt::Display::fmt(&encoded, f)
|
||||||
|
@ -7,9 +7,7 @@ use self::Charset::*;
|
|||||||
///
|
///
|
||||||
/// The string representation is normalized to upper case.
|
/// The string representation is normalized to upper case.
|
||||||
///
|
///
|
||||||
/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url].
|
/// See <http://www.iana.org/assignments/character-sets/character-sets.xhtml>.
|
||||||
///
|
|
||||||
/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub enum Charset {
|
pub enum Charset {
|
||||||
|
@ -7,10 +7,12 @@ 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
|
slice.bytes().all(entity_validate_char)
|
||||||
.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,7 +3,8 @@ 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::BufMutExt, BytesMut};
|
use bytes::buf::BufMut;
|
||||||
|
use bytes::BytesMut;
|
||||||
use http::header::{HeaderValue, InvalidHeaderValue};
|
use http::header::{HeaderValue, InvalidHeaderValue};
|
||||||
use time::{offset, OffsetDateTime, PrimitiveDateTime};
|
use time::{offset, OffsetDateTime, PrimitiveDateTime};
|
||||||
|
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
use std::{cmp, fmt, str};
|
use std::{
|
||||||
|
cmp,
|
||||||
|
convert::{TryFrom, TryInto},
|
||||||
|
fmt, str,
|
||||||
|
};
|
||||||
|
|
||||||
use self::internal::IntoQuality;
|
use derive_more::{Display, Error};
|
||||||
|
|
||||||
|
const MAX_QUALITY: u16 = 1000;
|
||||||
|
const MAX_FLOAT_QUALITY: f32 = 1.0;
|
||||||
|
|
||||||
/// Represents a quality used in quality values.
|
/// Represents a quality used in quality values.
|
||||||
///
|
///
|
||||||
/// Can be created with the `q` function.
|
/// Can be created with the [`q`] function.
|
||||||
///
|
///
|
||||||
/// # Implementation notes
|
/// # Implementation notes
|
||||||
///
|
///
|
||||||
@ -18,12 +25,54 @@ use self::internal::IntoQuality;
|
|||||||
///
|
///
|
||||||
/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1)
|
/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1)
|
||||||
/// gives more information on quality values in HTTP header fields.
|
/// gives more information on quality values in HTTP header fields.
|
||||||
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Quality(u16);
|
pub struct Quality(u16);
|
||||||
|
|
||||||
|
impl Quality {
|
||||||
|
/// # Panics
|
||||||
|
/// Panics in debug mode when value is not in the range 0.0 <= n <= 1.0.
|
||||||
|
fn from_f32(value: f32) -> Self {
|
||||||
|
// Check that `value` is within range should be done before calling this method.
|
||||||
|
// Just in case, this debug_assert should catch if we were forgetful.
|
||||||
|
debug_assert!(
|
||||||
|
(0.0f32..=1.0f32).contains(&value),
|
||||||
|
"q value must be between 0.0 and 1.0"
|
||||||
|
);
|
||||||
|
|
||||||
|
Quality((value * MAX_QUALITY as f32) as u16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Quality {
|
impl Default for Quality {
|
||||||
fn default() -> Quality {
|
fn default() -> Quality {
|
||||||
Quality(1000)
|
Quality(MAX_QUALITY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Display, Error)]
|
||||||
|
pub struct QualityOutOfBounds;
|
||||||
|
|
||||||
|
impl TryFrom<u16> for Quality {
|
||||||
|
type Error = QualityOutOfBounds;
|
||||||
|
|
||||||
|
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||||
|
if (0..=MAX_QUALITY).contains(&value) {
|
||||||
|
Ok(Quality(value))
|
||||||
|
} else {
|
||||||
|
Err(QualityOutOfBounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<f32> for Quality {
|
||||||
|
type Error = QualityOutOfBounds;
|
||||||
|
|
||||||
|
fn try_from(value: f32) -> Result<Self, Self::Error> {
|
||||||
|
if (0.0..=MAX_FLOAT_QUALITY).contains(&value) {
|
||||||
|
Ok(Quality::from_f32(value))
|
||||||
|
} else {
|
||||||
|
Err(QualityOutOfBounds)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,8 +104,9 @@ impl<T: PartialEq> cmp::PartialOrd for QualityItem<T> {
|
|||||||
impl<T: fmt::Display> fmt::Display for QualityItem<T> {
|
impl<T: fmt::Display> fmt::Display for QualityItem<T> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt::Display::fmt(&self.item, f)?;
|
fmt::Display::fmt(&self.item, f)?;
|
||||||
|
|
||||||
match self.quality.0 {
|
match self.quality.0 {
|
||||||
1000 => Ok(()),
|
MAX_QUALITY => Ok(()),
|
||||||
0 => f.write_str("; q=0"),
|
0 => f.write_str("; q=0"),
|
||||||
x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')),
|
x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')),
|
||||||
}
|
}
|
||||||
@ -66,105 +116,79 @@ impl<T: fmt::Display> fmt::Display for QualityItem<T> {
|
|||||||
impl<T: str::FromStr> str::FromStr for QualityItem<T> {
|
impl<T: str::FromStr> str::FromStr for QualityItem<T> {
|
||||||
type Err = crate::error::ParseError;
|
type Err = crate::error::ParseError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<QualityItem<T>, crate::error::ParseError> {
|
fn from_str(qitem_str: &str) -> Result<QualityItem<T>, crate::error::ParseError> {
|
||||||
if !s.is_ascii() {
|
if !qitem_str.is_ascii() {
|
||||||
return Err(crate::error::ParseError::Header);
|
return Err(crate::error::ParseError::Header);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set defaults used if parsing fails.
|
// Set defaults used if parsing fails.
|
||||||
let mut raw_item = s;
|
let mut raw_item = qitem_str;
|
||||||
let mut quality = 1f32;
|
let mut quality = 1f32;
|
||||||
|
|
||||||
let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect();
|
let parts: Vec<_> = qitem_str.rsplitn(2, ';').map(str::trim).collect();
|
||||||
|
|
||||||
if parts.len() == 2 {
|
if parts.len() == 2 {
|
||||||
|
// example for item with q-factor:
|
||||||
|
//
|
||||||
|
// gzip; q=0.65
|
||||||
|
// ^^^^^^ parts[0]
|
||||||
|
// ^^ start
|
||||||
|
// ^^^^ q_val
|
||||||
|
// ^^^^ parts[1]
|
||||||
|
|
||||||
if parts[0].len() < 2 {
|
if parts[0].len() < 2 {
|
||||||
|
// Can't possibly be an attribute since an attribute needs at least a name followed
|
||||||
|
// by an equals sign. And bare identifiers are forbidden.
|
||||||
return Err(crate::error::ParseError::Header);
|
return Err(crate::error::ParseError::Header);
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = &parts[0][0..2];
|
let start = &parts[0][0..2];
|
||||||
|
|
||||||
if start == "q=" || start == "Q=" {
|
if start == "q=" || start == "Q=" {
|
||||||
let q_part = &parts[0][2..parts[0].len()];
|
let q_val = &parts[0][2..];
|
||||||
if q_part.len() > 5 {
|
if q_val.len() > 5 {
|
||||||
|
// longer than 5 indicates an over-precise q-factor
|
||||||
return Err(crate::error::ParseError::Header);
|
return Err(crate::error::ParseError::Header);
|
||||||
}
|
}
|
||||||
match q_part.parse::<f32>() {
|
|
||||||
Ok(q_value) => {
|
let q_value = q_val
|
||||||
if 0f32 <= q_value && q_value <= 1f32 {
|
.parse::<f32>()
|
||||||
quality = q_value;
|
.map_err(|_| crate::error::ParseError::Header)?;
|
||||||
raw_item = parts[1];
|
|
||||||
} else {
|
if (0f32..=1f32).contains(&q_value) {
|
||||||
return Err(crate::error::ParseError::Header);
|
quality = q_value;
|
||||||
}
|
raw_item = parts[1];
|
||||||
}
|
} else {
|
||||||
Err(_) => return Err(crate::error::ParseError::Header),
|
return Err(crate::error::ParseError::Header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match raw_item.parse::<T>() {
|
|
||||||
// we already checked above that the quality is within range
|
|
||||||
Ok(item) => Ok(QualityItem::new(item, from_f32(quality))),
|
|
||||||
Err(_) => Err(crate::error::ParseError::Header),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
let item = raw_item
|
||||||
fn from_f32(f: f32) -> Quality {
|
.parse::<T>()
|
||||||
// this function is only used internally. A check that `f` is within range
|
.map_err(|_| crate::error::ParseError::Header)?;
|
||||||
// should be done before calling this method. Just in case, this
|
|
||||||
// debug_assert should catch if we were forgetful
|
// we already checked above that the quality is within range
|
||||||
debug_assert!(
|
Ok(QualityItem::new(item, Quality::from_f32(quality)))
|
||||||
f >= 0f32 && f <= 1f32,
|
}
|
||||||
"q value must be between 0.0 and 1.0"
|
|
||||||
);
|
|
||||||
Quality((f * 1000f32) as u16)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function to wrap a value in a `QualityItem`
|
/// Convenience function to wrap a value in a `QualityItem`
|
||||||
/// Sets `q` to the default 1.0
|
/// Sets `q` to the default 1.0
|
||||||
pub fn qitem<T>(item: T) -> QualityItem<T> {
|
pub fn qitem<T>(item: T) -> QualityItem<T> {
|
||||||
QualityItem::new(item, Default::default())
|
QualityItem::new(item, Quality::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function to create a `Quality` from a float or integer.
|
/// Convenience function to create a `Quality` from a float or integer.
|
||||||
///
|
///
|
||||||
/// Implemented for `u16` and `f32`. Panics if value is out of range.
|
/// Implemented for `u16` and `f32`. Panics if value is out of range.
|
||||||
pub fn q<T: IntoQuality>(val: T) -> Quality {
|
pub fn q<T>(val: T) -> Quality
|
||||||
val.into_quality()
|
where
|
||||||
}
|
T: TryInto<Quality>,
|
||||||
|
T::Error: fmt::Debug,
|
||||||
mod internal {
|
{
|
||||||
use super::Quality;
|
// TODO: on next breaking change, handle unwrap differently
|
||||||
|
val.try_into().unwrap()
|
||||||
// TryFrom is probably better, but it's not stable. For now, we want to
|
|
||||||
// keep the functionality of the `q` function, while allowing it to be
|
|
||||||
// generic over `f32` and `u16`.
|
|
||||||
//
|
|
||||||
// `q` would panic before, so keep that behavior. `TryFrom` can be
|
|
||||||
// introduced later for a non-panicking conversion.
|
|
||||||
|
|
||||||
pub trait IntoQuality: Sealed + Sized {
|
|
||||||
fn into_quality(self) -> Quality;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoQuality for f32 {
|
|
||||||
fn into_quality(self) -> Quality {
|
|
||||||
assert!(
|
|
||||||
self >= 0f32 && self <= 1f32,
|
|
||||||
"float must be between 0.0 and 1.0"
|
|
||||||
);
|
|
||||||
super::from_f32(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoQuality for u16 {
|
|
||||||
fn into_quality(self) -> Quality {
|
|
||||||
assert!(self <= 1000, "u16 must be between 0 and 1000");
|
|
||||||
Quality(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Sealed {}
|
|
||||||
impl Sealed for u16 {}
|
|
||||||
impl Sealed for f32 {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -270,15 +294,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic] // FIXME - 32-bit msvc unwinding broken
|
#[should_panic]
|
||||||
#[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
|
|
||||||
fn test_quality_invalid() {
|
fn test_quality_invalid() {
|
||||||
q(-1.0);
|
q(-1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic] // FIXME - 32-bit msvc unwinding broken
|
#[should_panic]
|
||||||
#[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
|
|
||||||
fn test_quality_invalid2() {
|
fn test_quality_invalid2() {
|
||||||
q(2.0);
|
q(2.0);
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@ 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) {
|
||||||
@ -56,18 +54,6 @@ 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;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
//! Basic http responses
|
//! Status code based HTTP response builders.
|
||||||
|
|
||||||
#![allow(non_upper_case_globals)]
|
#![allow(non_upper_case_globals)]
|
||||||
|
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
|
|
||||||
use crate::response::{Response, ResponseBuilder};
|
use crate::response::{Response, ResponseBuilder};
|
||||||
|
|
||||||
macro_rules! STATIC_RESP {
|
macro_rules! static_resp {
|
||||||
($name:ident, $status:expr) => {
|
($name:ident, $status:expr) => {
|
||||||
#[allow(non_snake_case, missing_docs)]
|
#[allow(non_snake_case, missing_docs)]
|
||||||
pub fn $name() -> ResponseBuilder {
|
pub fn $name() -> ResponseBuilder {
|
||||||
@ -14,63 +16,67 @@ macro_rules! STATIC_RESP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
STATIC_RESP!(Ok, StatusCode::OK);
|
static_resp!(Continue, StatusCode::CONTINUE);
|
||||||
STATIC_RESP!(Created, StatusCode::CREATED);
|
static_resp!(SwitchingProtocols, StatusCode::SWITCHING_PROTOCOLS);
|
||||||
STATIC_RESP!(Accepted, StatusCode::ACCEPTED);
|
static_resp!(Processing, StatusCode::PROCESSING);
|
||||||
STATIC_RESP!(
|
|
||||||
|
static_resp!(Ok, StatusCode::OK);
|
||||||
|
static_resp!(Created, StatusCode::CREATED);
|
||||||
|
static_resp!(Accepted, StatusCode::ACCEPTED);
|
||||||
|
static_resp!(
|
||||||
NonAuthoritativeInformation,
|
NonAuthoritativeInformation,
|
||||||
StatusCode::NON_AUTHORITATIVE_INFORMATION
|
StatusCode::NON_AUTHORITATIVE_INFORMATION
|
||||||
);
|
);
|
||||||
|
|
||||||
STATIC_RESP!(NoContent, StatusCode::NO_CONTENT);
|
static_resp!(NoContent, StatusCode::NO_CONTENT);
|
||||||
STATIC_RESP!(ResetContent, StatusCode::RESET_CONTENT);
|
static_resp!(ResetContent, StatusCode::RESET_CONTENT);
|
||||||
STATIC_RESP!(PartialContent, StatusCode::PARTIAL_CONTENT);
|
static_resp!(PartialContent, StatusCode::PARTIAL_CONTENT);
|
||||||
STATIC_RESP!(MultiStatus, StatusCode::MULTI_STATUS);
|
static_resp!(MultiStatus, StatusCode::MULTI_STATUS);
|
||||||
STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED);
|
static_resp!(AlreadyReported, StatusCode::ALREADY_REPORTED);
|
||||||
|
|
||||||
STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES);
|
static_resp!(MultipleChoices, StatusCode::MULTIPLE_CHOICES);
|
||||||
STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY);
|
static_resp!(MovedPermanently, StatusCode::MOVED_PERMANENTLY);
|
||||||
STATIC_RESP!(Found, StatusCode::FOUND);
|
static_resp!(Found, StatusCode::FOUND);
|
||||||
STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER);
|
static_resp!(SeeOther, StatusCode::SEE_OTHER);
|
||||||
STATIC_RESP!(NotModified, StatusCode::NOT_MODIFIED);
|
static_resp!(NotModified, StatusCode::NOT_MODIFIED);
|
||||||
STATIC_RESP!(UseProxy, StatusCode::USE_PROXY);
|
static_resp!(UseProxy, StatusCode::USE_PROXY);
|
||||||
STATIC_RESP!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT);
|
static_resp!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT);
|
||||||
STATIC_RESP!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT);
|
static_resp!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT);
|
||||||
|
|
||||||
STATIC_RESP!(BadRequest, StatusCode::BAD_REQUEST);
|
static_resp!(BadRequest, StatusCode::BAD_REQUEST);
|
||||||
STATIC_RESP!(NotFound, StatusCode::NOT_FOUND);
|
static_resp!(NotFound, StatusCode::NOT_FOUND);
|
||||||
STATIC_RESP!(Unauthorized, StatusCode::UNAUTHORIZED);
|
static_resp!(Unauthorized, StatusCode::UNAUTHORIZED);
|
||||||
STATIC_RESP!(PaymentRequired, StatusCode::PAYMENT_REQUIRED);
|
static_resp!(PaymentRequired, StatusCode::PAYMENT_REQUIRED);
|
||||||
STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN);
|
static_resp!(Forbidden, StatusCode::FORBIDDEN);
|
||||||
STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED);
|
static_resp!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED);
|
||||||
STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE);
|
static_resp!(NotAcceptable, StatusCode::NOT_ACCEPTABLE);
|
||||||
STATIC_RESP!(
|
static_resp!(
|
||||||
ProxyAuthenticationRequired,
|
ProxyAuthenticationRequired,
|
||||||
StatusCode::PROXY_AUTHENTICATION_REQUIRED
|
StatusCode::PROXY_AUTHENTICATION_REQUIRED
|
||||||
);
|
);
|
||||||
STATIC_RESP!(RequestTimeout, StatusCode::REQUEST_TIMEOUT);
|
static_resp!(RequestTimeout, StatusCode::REQUEST_TIMEOUT);
|
||||||
STATIC_RESP!(Conflict, StatusCode::CONFLICT);
|
static_resp!(Conflict, StatusCode::CONFLICT);
|
||||||
STATIC_RESP!(Gone, StatusCode::GONE);
|
static_resp!(Gone, StatusCode::GONE);
|
||||||
STATIC_RESP!(LengthRequired, StatusCode::LENGTH_REQUIRED);
|
static_resp!(LengthRequired, StatusCode::LENGTH_REQUIRED);
|
||||||
STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED);
|
static_resp!(PreconditionFailed, StatusCode::PRECONDITION_FAILED);
|
||||||
STATIC_RESP!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED);
|
static_resp!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED);
|
||||||
STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE);
|
static_resp!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE);
|
||||||
STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG);
|
static_resp!(UriTooLong, StatusCode::URI_TOO_LONG);
|
||||||
STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
static_resp!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
||||||
STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
|
static_resp!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
|
||||||
STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
|
static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
|
||||||
STATIC_RESP!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY);
|
static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY);
|
||||||
STATIC_RESP!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS);
|
static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS);
|
||||||
|
|
||||||
STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR);
|
static_resp!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED);
|
static_resp!(NotImplemented, StatusCode::NOT_IMPLEMENTED);
|
||||||
STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY);
|
static_resp!(BadGateway, StatusCode::BAD_GATEWAY);
|
||||||
STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE);
|
static_resp!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE);
|
||||||
STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT);
|
static_resp!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT);
|
||||||
STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED);
|
static_resp!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED);
|
||||||
STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES);
|
static_resp!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES);
|
||||||
STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE);
|
static_resp!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE);
|
||||||
STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED);
|
static_resp!(LoopDetected, StatusCode::LOOP_DETECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
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")]
|
||||||
|
|
||||||
@ -20,7 +19,6 @@ 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;
|
||||||
@ -74,11 +72,49 @@ pub mod http {
|
|||||||
pub use crate::message::ConnectionType;
|
pub use crate::message::ConnectionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Http protocol
|
/// A major HTTP protocol version.
|
||||||
#[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.borrow_mut().clear();
|
self.extensions.get_mut().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pool() -> &'static MessagePool<Self> {
|
fn pool() -> &'static MessagePool<Self> {
|
||||||
@ -440,9 +440,11 @@ 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() {
|
||||||
if let Some(r) = Rc::get_mut(&mut msg) {
|
// Message is put in pool only when it's the last copy.
|
||||||
r.clear();
|
// which means it's guaranteed to be unique when popped out.
|
||||||
}
|
Rc::get_mut(&mut msg)
|
||||||
|
.expect("Multiple copies exist")
|
||||||
|
.clear();
|
||||||
Message { head: msg }
|
Message { head: msg }
|
||||||
} else {
|
} else {
|
||||||
Message {
|
Message {
|
||||||
|
@ -23,6 +23,10 @@ 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> {
|
||||||
@ -34,10 +38,6 @@ 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,3 +1,4 @@
|
|||||||
|
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};
|
||||||
@ -8,39 +9,34 @@ 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, Extensions, Protocol};
|
use crate::{h1, h2::Dispatcher, ConnectCallback, OnConnectData, 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<T>> {
|
pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler> {
|
||||||
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>>>,
|
||||||
_t: PhantomData<(T, B)>,
|
_phantom: PhantomData<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> HttpService<T, S, B>
|
impl<T, S, B> HttpService<T, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create builder for `HttpService` instance.
|
/// Create builder for `HttpService` instance.
|
||||||
@ -51,15 +47,15 @@ where
|
|||||||
|
|
||||||
impl<T, S, B> HttpService<T, S, B>
|
impl<T, S, B> HttpService<T, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
/// Create new `HttpService` instance.
|
/// Create new `HttpService` instance.
|
||||||
pub fn new<F: IntoServiceFactory<S>>(service: F) -> Self {
|
pub fn new<F: IntoServiceFactory<S, Request>>(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 {
|
||||||
@ -67,14 +63,13 @@ 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,
|
||||||
_t: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create new `HttpService` instance with config.
|
/// Create new `HttpService` instance with config.
|
||||||
pub(crate) fn with_config<F: IntoServiceFactory<S>>(
|
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
service: F,
|
service: F,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -83,20 +78,19 @@ 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,
|
||||||
_t: PhantomData,
|
_phantom: 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<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
/// Provide service for `EXPECT: 100-Continue` support.
|
/// Provide service for `EXPECT: 100-Continue` support.
|
||||||
@ -106,19 +100,18 @@ 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<Config = (), Request = Request, Response = Request>,
|
X1: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X1::Error: Into<Error>,
|
X1::Error: Into<Error>,
|
||||||
X1::InitError: fmt::Debug,
|
X1::InitError: fmt::Debug,
|
||||||
<X1::Service as Service>::Future: 'static,
|
<X1::Service as Service<Request>>::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,
|
||||||
_t: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,35 +121,21 @@ 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<
|
U1: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
|
||||||
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>::Future: 'static,
|
<U1::Service as Service<(Request, Framed<T, h1::Codec>)>>::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,
|
||||||
_t: PhantomData,
|
_phantom: 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;
|
||||||
@ -166,38 +145,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<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
<X::Service as Service>::Future: 'static,
|
<X::Service as Service<Request>>::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>::Future: 'static,
|
<U::Service as Service<(Request, Framed<TcpStream, h1::Codec>)>>::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| {
|
pipeline_factory(|io: TcpStream| async {
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -206,39 +185,40 @@ where
|
|||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
mod openssl {
|
mod openssl {
|
||||||
use super::*;
|
use super::*;
|
||||||
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
|
use actix_service::ServiceFactoryExt;
|
||||||
use actix_tls::{openssl::HandshakeError, TlsError};
|
use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream};
|
||||||
|
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<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
<X::Service as Service>::Future: 'static,
|
<X::Service as Service<Request>>::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>::Future: 'static,
|
<U::Service as Service<(Request, Framed<SslStream<TcpStream>, h1::Codec>)>>::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<HandshakeError<TcpStream>, DispatchError>,
|
Error = TlsError<SslError, DispatchError>,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
> {
|
> {
|
||||||
pipeline_factory(
|
pipeline_factory(
|
||||||
@ -246,7 +226,7 @@ mod openssl {
|
|||||||
.map_err(TlsError::Tls)
|
.map_err(TlsError::Tls)
|
||||||
.map_init_err(|_| panic!()),
|
.map_init_err(|_| panic!()),
|
||||||
)
|
)
|
||||||
.and_then(|io: SslStream<TcpStream>| {
|
.and_then(|io: SslStream<TcpStream>| async {
|
||||||
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
|
||||||
@ -257,7 +237,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))
|
||||||
}
|
}
|
||||||
@ -266,39 +246,42 @@ mod openssl {
|
|||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
mod rustls {
|
mod rustls {
|
||||||
use super::*;
|
|
||||||
use actix_tls::rustls::{Acceptor, ServerConfig, Session, TlsStream};
|
|
||||||
use actix_tls::TlsError;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
use actix_tls::accept::rustls::{Acceptor, ServerConfig, Session, TlsStream};
|
||||||
|
use actix_tls::accept::TlsError;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use actix_service::ServiceFactoryExt;
|
||||||
|
|
||||||
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<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
<X::Service as Service>::Future: 'static,
|
<X::Service as Service<Request>>::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>::Future: 'static,
|
<U::Service as Service<(Request, Framed<TlsStream<TcpStream>, h1::Codec>)>>::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 = (),
|
||||||
@ -311,7 +294,7 @@ mod rustls {
|
|||||||
.map_err(TlsError::Tls)
|
.map_err(TlsError::Tls)
|
||||||
.map_init_err(|_| panic!()),
|
.map_init_err(|_| panic!()),
|
||||||
)
|
)
|
||||||
.and_then(|io: TlsStream<TcpStream>| {
|
.and_then(|io: TlsStream<TcpStream>| async {
|
||||||
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
|
||||||
@ -322,41 +305,37 @@ 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 for HttpService<T, S, B, X, U>
|
impl<T, S, B, X, U> ServiceFactory<(T, Protocol, Option<net::SocketAddr>)>
|
||||||
|
for HttpService<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: ServiceFactory<Config = (), Request = Request>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
<X::Service as Service>::Future: 'static,
|
<X::Service as Service<Request>>::Future: 'static,
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
|
||||||
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>::Future: 'static,
|
<U::Service as Service<(Request, Framed<T, h1::Codec>)>>::Future: 'static,
|
||||||
{
|
{
|
||||||
type Config = ();
|
|
||||||
type Request = (T, Protocol, Option<net::SocketAddr>);
|
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = DispatchError;
|
type Error = DispatchError;
|
||||||
type InitError = ();
|
type Config = ();
|
||||||
type Service = HttpServiceHandler<T, S::Service, B, X::Service, U::Service>;
|
type Service = HttpServiceHandler<T, S::Service, B, X::Service, U::Service>;
|
||||||
|
type InitError = ();
|
||||||
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 {
|
||||||
@ -366,23 +345,21 @@ 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(),
|
||||||
_t: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[pin_project]
|
#[pin_project]
|
||||||
pub struct HttpServiceResponse<
|
pub struct HttpServiceResponse<T, S, B, X, U>
|
||||||
T,
|
where
|
||||||
S: ServiceFactory,
|
S: ServiceFactory<Request>,
|
||||||
B,
|
X: ServiceFactory<Request>,
|
||||||
X: ServiceFactory,
|
U: ServiceFactory<(Request, Framed<T, h1::Codec>)>,
|
||||||
U: ServiceFactory,
|
{
|
||||||
> {
|
|
||||||
#[pin]
|
#[pin]
|
||||||
fut: S::Future,
|
fut: S::Future,
|
||||||
#[pin]
|
#[pin]
|
||||||
@ -391,29 +368,28 @@ pub struct HttpServiceResponse<
|
|||||||
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,
|
||||||
_t: PhantomData<(T, B)>,
|
_phantom: PhantomData<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 = Request>,
|
S: ServiceFactory<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>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
X: ServiceFactory<Request = Request, Response = Request>,
|
X: ServiceFactory<Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
<X::Service as Service>::Future: 'static,
|
<X::Service as Service<Request>>::Future: 'static,
|
||||||
U: ServiceFactory<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
U: ServiceFactory<(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>::Future: 'static,
|
<U::Service as Service<(Request, Framed<T, h1::Codec>)>>::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>, ()>;
|
||||||
@ -436,7 +412,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_ex.set(None);
|
this.fut_upg.set(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = ready!(this
|
let result = ready!(this
|
||||||
@ -451,7 +427,6 @@ 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(),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
@ -459,68 +434,84 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `Service` implementation for http transport
|
/// `Service` implementation for http transport
|
||||||
pub struct HttpServiceHandler<T, S: Service, B, X: Service, U: Service> {
|
pub struct HttpServiceHandler<T, S, B, X, U>
|
||||||
srv: CloneableService<S>,
|
where
|
||||||
expect: CloneableService<X>,
|
S: Service<Request>,
|
||||||
upgrade: Option<CloneableService<U>>,
|
X: Service<Request>,
|
||||||
|
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>>>,
|
||||||
_t: PhantomData<(T, B, X)>,
|
_phantom: PhantomData<B>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 = Request>,
|
S: Service<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 = Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
fn new(
|
fn new(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
srv: S,
|
service: 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,
|
||||||
srv: CloneableService::new(srv),
|
flow: HttpFlow::new(service, expect, upgrade),
|
||||||
expect: CloneableService::new(expect),
|
_phantom: PhantomData,
|
||||||
upgrade: upgrade.map(CloneableService::new),
|
|
||||||
_t: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> Service for HttpServiceHandler<T, S, B, X, U>
|
impl<T, S, B, X, U> Service<(T, Protocol, Option<net::SocketAddr>)>
|
||||||
|
for HttpServiceHandler<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request = Request>,
|
S: Service<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 = Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<(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 ready = self
|
let mut flow = self.flow.borrow_mut();
|
||||||
|
let ready = flow
|
||||||
.expect
|
.expect
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@ -530,8 +521,8 @@ where
|
|||||||
})?
|
})?
|
||||||
.is_ready();
|
.is_ready();
|
||||||
|
|
||||||
let ready = self
|
let ready = flow
|
||||||
.srv
|
.service
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
@ -541,7 +532,7 @@ where
|
|||||||
.is_ready()
|
.is_ready()
|
||||||
&& ready;
|
&& ready;
|
||||||
|
|
||||||
let ready = if let Some(ref mut upg) = self.upgrade {
|
let ready = if let Some(ref mut upg) = flow.upgrade {
|
||||||
upg.poll_ready(cx)
|
upg.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
@ -561,22 +552,20 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future {
|
fn call(
|
||||||
let mut connect_extensions = Extensions::new();
|
&mut self,
|
||||||
|
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
|
||||||
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
|
) -> Self::Future {
|
||||||
if let Some(ref handler) = self.on_connect_ext {
|
let on_connect_data =
|
||||||
handler(&io, &mut connect_extensions);
|
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||||
}
|
|
||||||
|
|
||||||
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.srv.clone(),
|
self.flow.clone(),
|
||||||
deprecated_on_connect,
|
on_connect_data,
|
||||||
connect_extensions,
|
|
||||||
peer_addr,
|
peer_addr,
|
||||||
))),
|
))),
|
||||||
},
|
},
|
||||||
@ -585,14 +574,13 @@ where
|
|||||||
state: State::H1(h1::Dispatcher::new(
|
state: State::H1(h1::Dispatcher::new(
|
||||||
io,
|
io,
|
||||||
self.cfg.clone(),
|
self.cfg.clone(),
|
||||||
self.srv.clone(),
|
self.flow.clone(),
|
||||||
self.expect.clone(),
|
on_connect_data,
|
||||||
self.upgrade.clone(),
|
|
||||||
deprecated_on_connect,
|
|
||||||
connect_extensions,
|
|
||||||
peer_addr,
|
peer_addr,
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
proto => unimplemented!("Unsupported HTTP version: {:?}.", proto),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -600,25 +588,24 @@ 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 = Request>,
|
S: Service<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 = Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<(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>),
|
H2(#[pin] Dispatcher<T, S, B, X, U>),
|
||||||
H2Handshake(
|
H2Handshake(
|
||||||
Option<(
|
Option<(
|
||||||
Handshake<T, Bytes>,
|
Handshake<T, Bytes>,
|
||||||
ServiceConfig,
|
ServiceConfig,
|
||||||
CloneableService<S>,
|
Rc<RefCell<HttpFlow<S, X, U>>>,
|
||||||
Option<Box<dyn DataFactory>>,
|
OnConnectData,
|
||||||
Extensions,
|
|
||||||
Option<net::SocketAddr>,
|
Option<net::SocketAddr>,
|
||||||
)>,
|
)>,
|
||||||
),
|
),
|
||||||
@ -628,14 +615,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 = Request>,
|
S: Service<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 = Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
#[pin]
|
#[pin]
|
||||||
@ -645,67 +632,42 @@ 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 = Request>,
|
S: Service<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 = Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Error>,
|
X::Error: Into<Error>,
|
||||||
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
type Output = Result<(), DispatchError>;
|
type Output = Result<(), DispatchError>;
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
self.project().state.poll(cx)
|
match self.as_mut().project().state.project() {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(ref mut data) => {
|
StateProj::H2Handshake(data) => {
|
||||||
let conn = if let Some(ref mut item) = data {
|
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
|
||||||
match Pin::new(&mut item.0).poll(cx) {
|
Ok(conn) => {
|
||||||
Poll::Ready(Ok(conn)) => conn,
|
let (_, cfg, srv, on_connect_data, peer_addr) =
|
||||||
Poll::Ready(Err(err)) => {
|
data.take().unwrap();
|
||||||
trace!("H2 handshake error: {}", err);
|
self.as_mut().project().state.set(State::H2(Dispatcher::new(
|
||||||
return Poll::Ready(Err(err.into()));
|
srv,
|
||||||
}
|
conn,
|
||||||
Poll::Pending => return Poll::Pending,
|
on_connect_data,
|
||||||
|
cfg,
|
||||||
|
None,
|
||||||
|
peer_addr,
|
||||||
|
)));
|
||||||
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
} else {
|
Err(err) => {
|
||||||
panic!()
|
trace!("H2 handshake error: {}", err);
|
||||||
};
|
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,11 +1,16 @@
|
|||||||
//! Test Various helpers for Actix applications to use during testing.
|
//! Various testing helpers for use in internal and app tests.
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::io::{self, Read, Write};
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite};
|
use std::{
|
||||||
|
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};
|
||||||
@ -183,7 +188,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 io buffer
|
/// Async I/O test buffer.
|
||||||
pub struct TestBuffer {
|
pub struct TestBuffer {
|
||||||
pub read_buf: BytesMut,
|
pub read_buf: BytesMut,
|
||||||
pub write_buf: BytesMut,
|
pub write_buf: BytesMut,
|
||||||
@ -191,24 +196,24 @@ pub struct TestBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TestBuffer {
|
impl TestBuffer {
|
||||||
/// Create new TestBuffer instance
|
/// Create new `TestBuffer` instance with initial read buffer.
|
||||||
pub fn new<T>(data: T) -> TestBuffer
|
pub fn new<T>(data: T) -> Self
|
||||||
where
|
where
|
||||||
BytesMut: From<T>,
|
T: Into<BytesMut>,
|
||||||
{
|
{
|
||||||
TestBuffer {
|
Self {
|
||||||
read_buf: BytesMut::from(data),
|
read_buf: data.into(),
|
||||||
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() -> TestBuffer {
|
pub fn empty() -> Self {
|
||||||
TestBuffer::new("")
|
Self::new("")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add extra data to read buffer.
|
/// Add data to read buffer.
|
||||||
pub fn extend_read_buf<T: AsRef<[u8]>>(&mut self, data: T) {
|
pub fn extend_read_buf<T: AsRef<[u8]>>(&mut self, data: T) {
|
||||||
self.read_buf.extend_from_slice(data.as_ref())
|
self.read_buf.extend_from_slice(data.as_ref())
|
||||||
}
|
}
|
||||||
@ -236,6 +241,7 @@ 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(())
|
||||||
}
|
}
|
||||||
@ -245,9 +251,11 @@ impl AsyncRead for TestBuffer {
|
|||||||
fn poll_read(
|
fn poll_read(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
_: &mut Context<'_>,
|
_: &mut Context<'_>,
|
||||||
buf: &mut [u8],
|
buf: &mut ReadBuf<'_>,
|
||||||
) -> Poll<io::Result<usize>> {
|
) -> Poll<io::Result<()>> {
|
||||||
Poll::Ready(self.get_mut().read(buf))
|
let dst = buf.initialize_unfilled();
|
||||||
|
let res = self.get_mut().read(dst).map(|n| buf.advance(n));
|
||||||
|
Poll::Ready(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,3 +276,117 @@ 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,47 +1,60 @@
|
|||||||
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;
|
||||||
|
|
||||||
/// `WebSocket` Message
|
/// A WebSocket message.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
/// Text message
|
/// Text message.
|
||||||
Text(String),
|
Text(ByteString),
|
||||||
/// 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `WebSocket` frame
|
/// A WebSocket frame.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Frame {
|
pub enum Frame {
|
||||||
/// Text frame, codec does not verify utf8 encoding
|
/// Text frame. Note that the codec does not validate UTF-8 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>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `WebSocket` continuation item
|
/// A `WebSocket` continuation item.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Item {
|
pub enum Item {
|
||||||
FirstText(Bytes),
|
FirstText(Bytes),
|
||||||
@ -51,13 +64,13 @@ pub enum Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
/// WebSockets protocol codec
|
/// WebSocket 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;
|
||||||
@ -66,7 +79,7 @@ bitflags::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,
|
||||||
@ -74,9 +87,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
|
||||||
@ -184,7 +197,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<Request = Frame, Response = Message> + 'static,
|
S: Service<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<Request = Frame, Response = Message>,
|
S: Service<Frame, Response = Message>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: 'static,
|
S::Error: 'static,
|
||||||
{
|
{
|
||||||
pub fn new<F: IntoService<S>>(io: T, service: F) -> Self {
|
pub fn new<F: IntoService<S, Frame>>(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>>(framed: Framed<T, Codec>, service: F) -> Self {
|
pub fn with<F: IntoService<S, Frame>>(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<Request = Frame, Response = Message>,
|
S: Service<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
|
//! To setup a WebSocket, first do web socket handshake then on success convert `Payload` into a
|
||||||
//! convert `Payload` into a `WsStream` stream and then use `WsWriter` to
|
//! `WsStream` stream and then use `WsWriter` to communicate with the peer.
|
||||||
//! communicate with the peer.
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use derive_more::{Display, From};
|
use derive_more::{Display, Error, From};
|
||||||
use http::{header, Method, StatusCode};
|
use http::{header, Method, StatusCode};
|
||||||
|
|
||||||
use crate::error::ResponseError;
|
use crate::error::ResponseError;
|
||||||
@ -23,86 +23,103 @@ 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)]
|
#[derive(Debug, Display, From, Error)]
|
||||||
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
|
|
||||||
#[display(fmt = "Received a masked frame from server")]
|
/// Received a masked frame from server.
|
||||||
|
#[display(fmt = "Received a masked frame from server.")]
|
||||||
MaskedFrame,
|
MaskedFrame,
|
||||||
/// Encountered invalid opcode
|
|
||||||
#[display(fmt = "Invalid opcode: {}", _0)]
|
/// Encountered invalid opcode.
|
||||||
InvalidOpcode(u8),
|
#[display(fmt = "Invalid opcode: {}.", _0)]
|
||||||
|
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(usize),
|
InvalidLength(#[error(not(source))] usize),
|
||||||
/// Bad web socket op code
|
|
||||||
#[display(fmt = "Bad web socket op code")]
|
/// Bad opcode.
|
||||||
|
#[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
|
|
||||||
#[display(fmt = "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.")]
|
||||||
ContinuationStarted,
|
ContinuationStarted,
|
||||||
/// Unknown continuation fragment
|
|
||||||
#[display(fmt = "Unknown continuation fragment.")]
|
/// Unknown continuation fragment.
|
||||||
ContinuationFragment(OpCode),
|
#[display(fmt = "Unknown continuation fragment: {}.", _0)]
|
||||||
/// Io error
|
ContinuationFragment(#[error(not(source))] OpCode),
|
||||||
#[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
|
|
||||||
#[display(fmt = "Websocket upgrade is expected")]
|
/// Upgrade header if not set to websocket.
|
||||||
|
#[display(fmt = "WebSocket upgrade is expected.")]
|
||||||
NoWebsocketUpgrade,
|
NoWebsocketUpgrade,
|
||||||
/// Connection header is not set to upgrade
|
|
||||||
#[display(fmt = "Connection upgrade is expected")]
|
/// Connection header is not set to upgrade.
|
||||||
|
#[display(fmt = "Connection upgrade is expected.")]
|
||||||
NoConnectionUpgrade,
|
NoConnectionUpgrade,
|
||||||
/// Websocket version header is not set
|
|
||||||
#[display(fmt = "Websocket version header is required")]
|
/// WebSocket version header is not set.
|
||||||
|
#[display(fmt = "WebSocket version header is required.")]
|
||||||
NoVersionHeader,
|
NoVersionHeader,
|
||||||
/// Unsupported websocket version
|
|
||||||
#[display(fmt = "Unsupported version")]
|
/// Unsupported websocket version.
|
||||||
|
#[display(fmt = "Unsupported version.")]
|
||||||
UnsupportedVersion,
|
UnsupportedVersion,
|
||||||
/// Websocket key is not set or wrong
|
|
||||||
#[display(fmt = "Unknown websocket key")]
|
/// WebSocket key is not set or wrong.
|
||||||
|
#[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()
|
||||||
}
|
}
|
||||||
@ -197,13 +214,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()).err().unwrap()
|
verify_handshake(req.head()).unwrap_err(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default().finish();
|
let req = TestRequest::default().finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoWebsocketUpgrade,
|
HandshakeError::NoWebsocketUpgrade,
|
||||||
verify_handshake(req.head()).err().unwrap()
|
verify_handshake(req.head()).unwrap_err(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
@ -211,7 +228,7 @@ mod tests {
|
|||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoWebsocketUpgrade,
|
HandshakeError::NoWebsocketUpgrade,
|
||||||
verify_handshake(req.head()).err().unwrap()
|
verify_handshake(req.head()).unwrap_err(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
@ -222,7 +239,7 @@ mod tests {
|
|||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoConnectionUpgrade,
|
HandshakeError::NoConnectionUpgrade,
|
||||||
verify_handshake(req.head()).err().unwrap()
|
verify_handshake(req.head()).unwrap_err(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
@ -237,7 +254,7 @@ mod tests {
|
|||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoVersionHeader,
|
HandshakeError::NoVersionHeader,
|
||||||
verify_handshake(req.head()).err().unwrap()
|
verify_handshake(req.head()).unwrap_err(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
@ -256,7 +273,7 @@ mod tests {
|
|||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::UnsupportedVersion,
|
HandshakeError::UnsupportedVersion,
|
||||||
verify_handshake(req.head()).err().unwrap()
|
verify_handshake(req.head()).unwrap_err(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
@ -275,7 +292,7 @@ mod tests {
|
|||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::BadWebsocketKey,
|
HandshakeError::BadWebsocketKey,
|
||||||
verify_handshake(req.head()).err().unwrap()
|
verify_handshake(req.head()).unwrap_err(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
@ -1,28 +1,34 @@
|
|||||||
use std::convert::{From, Into};
|
use std::convert::{From, Into};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use self::OpCode::*;
|
/// Operation codes as part of RFC6455.
|
||||||
/// 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"),
|
||||||
@ -35,9 +41,10 @@ impl fmt::Display for OpCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<u8> for OpCode {
|
impl From<OpCode> for u8 {
|
||||||
fn into(self) -> u8 {
|
fn from(op: OpCode) -> u8 {
|
||||||
match self {
|
use self::OpCode::*;
|
||||||
|
match op {
|
||||||
Continue => 0,
|
Continue => 0,
|
||||||
Text => 1,
|
Text => 1,
|
||||||
Binary => 2,
|
Binary => 2,
|
||||||
@ -54,6 +61,7 @@ impl Into<u8> for OpCode {
|
|||||||
|
|
||||||
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,
|
||||||
@ -66,7 +74,6 @@ 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)]
|
||||||
@ -132,9 +139,10 @@ pub enum CloseCode {
|
|||||||
Other(u16),
|
Other(u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<u16> for CloseCode {
|
impl From<CloseCode> for u16 {
|
||||||
fn into(self) -> u16 {
|
fn from(code: CloseCode) -> u16 {
|
||||||
match self {
|
use self::CloseCode::*;
|
||||||
|
match code {
|
||||||
Normal => 1000,
|
Normal => 1000,
|
||||||
Away => 1001,
|
Away => 1001,
|
||||||
Protocol => 1002,
|
Protocol => 1002,
|
||||||
@ -155,6 +163,7 @@ impl Into<u16> for CloseCode {
|
|||||||
|
|
||||||
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,
|
||||||
@ -179,6 +188,7 @@ 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>,
|
||||||
}
|
}
|
||||||
@ -222,7 +232,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),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -232,7 +242,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,8 @@
|
|||||||
use actix_service::ServiceFactory;
|
|
||||||
use bytes::Bytes;
|
|
||||||
use futures_util::future::{self, ok};
|
|
||||||
|
|
||||||
use actix_http::{http, HttpService, Request, Response};
|
use actix_http::{http, HttpService, Request, Response};
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
|
use actix_service::ServiceFactoryExt;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures_util::future::{self, ok};
|
||||||
|
|
||||||
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 \
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
#![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
|
||||||
@ -410,10 +408,8 @@ 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::delay_for;
|
use actix_rt::time::sleep;
|
||||||
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| {
|
||||||
delay_for(Duration::from_millis(20)).then(move |_| {
|
sleep(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,10 +662,8 @@ 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,11 +36,10 @@ impl<T> Clone for WsService<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Service for WsService<T>
|
impl<T> Service<(Request, Framed<T, h1::Codec>)> 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>>>>;
|
||||||
@ -50,7 +49,10 @@ where
|
|||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, (req, mut framed): Self::Request) -> Self::Future {
|
fn call(
|
||||||
|
&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(());
|
||||||
|
|
||||||
@ -72,7 +74,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).to_string())
|
ws::Message::Text(String::from_utf8_lossy(&text).into_owned().into())
|
||||||
}
|
}
|
||||||
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),
|
||||||
@ -99,10 +101,7 @@ async fn test_simple() {
|
|||||||
|
|
||||||
// client service
|
// client service
|
||||||
let mut framed = srv.ws().await.unwrap();
|
let mut framed = srv.ws().await.unwrap();
|
||||||
framed
|
framed.send(ws::Message::Text("text".into())).await.unwrap();
|
||||||
.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,14 +1,21 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
* Fix multipart consuming payload before header checks #1513
|
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0 - 2020-09-11
|
## 0.4.0-beta.1 - 2021-01-07
|
||||||
* No significant changes from `3.0.0-beta.2`.
|
* Fix multipart consuming payload before header checks. [#1513]
|
||||||
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
|
||||||
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
|
[#1513]: https://github.com/actix/actix-web/pull/1513
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.2 - 2020-09-10
|
## 0.3.0 - 2020-09-11
|
||||||
|
* 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.3.0"
|
version = "0.4.0-beta.1"
|
||||||
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 = "3.0.0", default-features = false }
|
actix-web = { version = "4.0.0-beta.1", default-features = false }
|
||||||
actix-service = "1.0.6"
|
actix-utils = "3.0.0-beta.1"
|
||||||
actix-utils = "2.0.0"
|
|
||||||
bytes = "0.5.3"
|
bytes = "1"
|
||||||
derive_more = "0.99.2"
|
derive_more = "0.99.5"
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
futures-util = { version = "0.3.5", default-features = false }
|
futures-util = { version = "0.3.7", 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 = "1.0.0"
|
actix-rt = "2.0.0-beta.1"
|
||||||
actix-http = "2.0.0"
|
actix-http = "3.0.0-beta.1"
|
||||||
|
@ -326,7 +326,7 @@ impl InnerMultipart {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// read field headers for next field
|
// read field headers for next field
|
||||||
@ -725,9 +725,7 @@ 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);
|
||||||
}
|
}
|
||||||
if let Some(task) = self.task.take() {
|
self.task.wake();
|
||||||
task.wake()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -837,7 +835,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"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -848,7 +846,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"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -858,7 +856,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"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -958,17 +956,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!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -995,7 +993,7 @@ mod tests {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
match field.next().await {
|
match field.next().await {
|
||||||
None => (),
|
None => {}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1012,7 +1010,7 @@ mod tests {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
match field.next().await {
|
match field.next().await {
|
||||||
None => (),
|
None => {}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1020,7 +1018,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match multipart.next().await {
|
match multipart.next().await {
|
||||||
None => (),
|
None => {}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1068,7 +1066,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match multipart.next().await {
|
match multipart.next().await {
|
||||||
None => (),
|
None => {}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2021-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 = "3.0.0"
|
version = "4.0.0-beta.1"
|
||||||
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,16 +16,18 @@ name = "actix_web_actors"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "0.10.0"
|
actix = "0.11.0-beta.1"
|
||||||
actix-web = { version = "3.0.0", default-features = false }
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-http = "2.0.0"
|
actix-http = "3.0.0-beta.1"
|
||||||
actix-codec = "0.3.0"
|
actix-web = { version = "4.0.0-beta.1", default-features = false }
|
||||||
bytes = "0.5.2"
|
|
||||||
futures-channel = { version = "0.3.5", default-features = false }
|
bytes = "1"
|
||||||
futures-core = { version = "0.3.5", default-features = false }
|
bytestring = "1"
|
||||||
|
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 = "1.1.1"
|
actix-rt = "2.0.0-beta.1"
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
futures-util = { version = "0.3.5", default-features = false }
|
futures-util = { version = "0.3.7", 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,9 +1,10 @@
|
|||||||
//! 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,
|
||||||
@ -24,10 +25,11 @@ 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 futures_channel::oneshot::Sender;
|
use bytestring::ByteString;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
|
use tokio::sync::oneshot::Sender;
|
||||||
|
|
||||||
/// Do websocket handshake and start ws actor.
|
/// Perform WebSocket handshake and start actor.
|
||||||
pub fn start<A, T>(actor: A, req: &HttpRequest, stream: T) -> Result<HttpResponse, Error>
|
pub fn start<A, T>(actor: A, req: &HttpRequest, stream: T) -> Result<HttpResponse, Error>
|
||||||
where
|
where
|
||||||
A: Actor<Context = WebsocketContext<A>>
|
A: Actor<Context = WebsocketContext<A>>
|
||||||
@ -38,7 +40,7 @@ where
|
|||||||
Ok(res.streaming(WebsocketContext::create(actor, stream)))
|
Ok(res.streaming(WebsocketContext::create(actor, stream)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Do websocket handshake and start ws actor.
|
/// Perform WebSocket handshake and start 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
|
||||||
@ -164,7 +166,6 @@ pub fn handshake_with_protocols(
|
|||||||
|
|
||||||
let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS)
|
let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS)
|
||||||
.upgrade("websocket")
|
.upgrade("websocket")
|
||||||
.header(header::TRANSFER_ENCODING, "chunked")
|
|
||||||
.header(header::SEC_WEBSOCKET_ACCEPT, key.as_str())
|
.header(header::SEC_WEBSOCKET_ACCEPT, key.as_str())
|
||||||
.take();
|
.take();
|
||||||
|
|
||||||
@ -339,13 +340,13 @@ where
|
|||||||
|
|
||||||
/// Send text frame
|
/// Send text frame
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn text<T: Into<String>>(&mut self, text: T) {
|
pub fn text(&mut self, text: impl Into<ByteString>) {
|
||||||
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<B: Into<Bytes>>(&mut self, data: B) {
|
pub fn binary(&mut self, data: impl Into<Bytes>) {
|
||||||
self.write_raw(Message::Binary(data.into()));
|
self.write_raw(Message::Binary(data.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,16 +530,14 @@ where
|
|||||||
}
|
}
|
||||||
Some(frm) => {
|
Some(frm) => {
|
||||||
let msg = match frm {
|
let msg = match frm {
|
||||||
Frame::Text(data) => Message::Text(
|
Frame::Text(data) => {
|
||||||
std::str::from_utf8(&data)
|
Message::Text(ByteString::try_from(data).map_err(|e| {
|
||||||
.map_err(|e| {
|
ProtocolError::Io(io::Error::new(
|
||||||
ProtocolError::Io(io::Error::new(
|
io::ErrorKind::Other,
|
||||||
io::ErrorKind::Other,
|
format!("{}", e),
|
||||||
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),
|
||||||
@ -664,10 +663,10 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
|
|
||||||
assert_eq!(
|
let resp = handshake(&req).unwrap().finish();
|
||||||
StatusCode::SWITCHING_PROTOCOLS,
|
assert_eq!(StatusCode::SWITCHING_PROTOCOLS, resp.status());
|
||||||
handshake(&req).unwrap().finish().status()
|
assert_eq!(None, resp.headers().get(&header::CONTENT_LENGTH));
|
||||||
);
|
assert_eq!(None, resp.headers().get(&header::TRANSFER_ENCODING));
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.header(
|
||||||
|
@ -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,10 +38,7 @@ async fn test_simple() {
|
|||||||
|
|
||||||
// client service
|
// client service
|
||||||
let mut framed = srv.ws().await.unwrap();
|
let mut framed = srv.ws().await.unwrap();
|
||||||
framed
|
framed.send(ws::Message::Text("text".into())).await.unwrap();
|
||||||
.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 - 2020-xx-xx
|
## Unreleased - 2021-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 = "1.1.1"
|
actix-rt = "2.0.0-beta.1"
|
||||||
actix-web = "3.0.0"
|
actix-web = "4.0.0-beta.1"
|
||||||
futures-util = { version = "0.3.5", default-features = false }
|
futures-util = { version = "0.3.7", 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)
|
[](https://docs.rs/actix-web-codegen/0.4.0/actix_web_codegen/)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.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.42 or later.
|
- Minimum supported Rust version: 1.46 or later.
|
||||||
|
|
||||||
## Compile Testing
|
## Compile Testing
|
||||||
Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this.
|
Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this.
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
//! are re-exported.
|
//! are re-exported.
|
||||||
//!
|
//!
|
||||||
//! # Runtime Setup
|
//! # Runtime Setup
|
||||||
//! Used for setting up the actix async runtime. See [main] macro docs.
|
//! Used for setting up the actix async runtime. See [macro@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 [route] macro docs.
|
//! it should respond to. See [macro@route] macro docs.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # use actix_web::HttpResponse;
|
//! # use actix_web::HttpResponse;
|
||||||
@ -46,17 +46,15 @@
|
|||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! [actix-web attributes docs]: https://docs.rs/actix-web/*/actix_web/#attributes
|
//! [actix-web attributes docs]: https://docs.rs/actix-web/*/actix_web/#attributes
|
||||||
//! [main]: attr.main.html
|
//! [GET]: macro@get
|
||||||
//! [route]: attr.route.html
|
//! [POST]: macro@post
|
||||||
//! [GET]: attr.get.html
|
//! [PUT]: macro@put
|
||||||
//! [POST]: attr.post.html
|
//! [HEAD]: macro@head
|
||||||
//! [PUT]: attr.put.html
|
//! [CONNECT]: macro@macro@connect
|
||||||
//! [DELETE]: attr.delete.html
|
//! [OPTIONS]: macro@options
|
||||||
//! [HEAD]: attr.head.html
|
//! [TRACE]: macro@trace
|
||||||
//! [CONNECT]: attr.connect.html
|
//! [PATCH]: macro@patch
|
||||||
//! [OPTIONS]: attr.options.html
|
//! [DELETE]: macro@delete
|
||||||
//! [TRACE]: attr.trace.html
|
|
||||||
//! [PATCH]: attr.patch.html
|
|
||||||
|
|
||||||
#![recursion_limit = "512"]
|
#![recursion_limit = "512"]
|
||||||
|
|
||||||
|
@ -88,17 +88,16 @@ async fn route_test() -> impl Responder {
|
|||||||
|
|
||||||
pub struct ChangeStatusCode;
|
pub struct ChangeStatusCode;
|
||||||
|
|
||||||
impl<S, B> Transform<S> for ChangeStatusCode
|
impl<S, B> Transform<S, ServiceRequest> for ChangeStatusCode
|
||||||
where
|
where
|
||||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
S: Service<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 InitError = ();
|
|
||||||
type Transform = ChangeStatusCodeMiddleware<S>;
|
type Transform = ChangeStatusCodeMiddleware<S>;
|
||||||
|
type InitError = ();
|
||||||
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 {
|
||||||
@ -110,13 +109,12 @@ pub struct ChangeStatusCodeMiddleware<S> {
|
|||||||
service: S,
|
service: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, B> Service for ChangeStatusCodeMiddleware<S>
|
impl<S, B> Service<ServiceRequest> for ChangeStatusCodeMiddleware<S>
|
||||||
where
|
where
|
||||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
S: Service<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,31 +6,15 @@ 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::nightly]
|
// #[rustversion::not(nightly)]
|
||||||
fn test_route_duplicate_unexpected_method(_t: &trybuild::TestCases) {}
|
// fn skip_on_nightly(t: &trybuild::TestCases) {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[rustversion::nightly]
|
||||||
|
// fn skip_on_nightly(_t: &trybuild::TestCases) {}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
route-missing-method-fail.rs
|
|
@ -1,11 +0,0 @@
|
|||||||
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,9 +1,30 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
## 2.0.1 - 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
|
||||||
|
### Fixed
|
||||||
|
* Ensure `actix-http` dependency uses same `serde_urlencoded`.
|
||||||
|
|
||||||
|
|
||||||
|
## 2.0.2 - 2020-11-25
|
||||||
|
### Changed
|
||||||
|
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
|
||||||
|
|
||||||
|
[#1773]: https://github.com/actix/actix-web/pull/1773
|
||||||
|
|
||||||
|
|
||||||
|
## 2.0.1 - 2020-10-30
|
||||||
### Changed
|
### Changed
|
||||||
* Upgrade `base64` to `0.13`. [#1744]
|
* Upgrade `base64` to `0.13`. [#1744]
|
||||||
* Deprecate `ClientRequest::{if_some, if_true}`. [#1760]
|
* Deprecate `ClientRequest::{if_some, if_true}`. [#1760]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "awc"
|
name = "awc"
|
||||||
version = "2.0.1"
|
version = "3.0.0-beta.1"
|
||||||
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,36 +37,38 @@ rustls = ["rust-tls", "actix-http/rustls"]
|
|||||||
compress = ["actix-http/compress"]
|
compress = ["actix-http/compress"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.3.0"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-service = "1.0.6"
|
actix-service = "2.0.0-beta.2"
|
||||||
actix-http = "2.0.0"
|
actix-http = "3.0.0-beta.1"
|
||||||
actix-rt = "1.0.0"
|
actix-rt = "2.0.0-beta.1"
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bytes = "0.5.3"
|
bytes = "1"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
derive_more = "0.99.2"
|
derive_more = "0.99.5"
|
||||||
futures-core = { version = "0.3.5", default-features = false }
|
futures-core = { version = "0.3.7", 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.7"
|
rand = "0.8"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_urlencoded = "0.6.1"
|
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.18.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
|
rust-tls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-connect = { version = "2.0.0", features = ["openssl"] }
|
# TODO: actix is temporary added as dev dep for actix-macro reason.
|
||||||
actix-web = { version = "3.0.0", features = ["openssl"] }
|
# Can be removed when it does not impact tests.
|
||||||
actix-http = { version = "2.0.0", features = ["openssl"] }
|
actix = "0.11.0-beta.1"
|
||||||
actix-http-test = { version = "2.0.0", features = ["openssl"] }
|
actix-web = { version = "4.0.0-beta.1", features = ["openssl"] }
|
||||||
actix-utils = "2.0.0"
|
actix-http = { version = "3.0.0-beta.1", features = ["openssl"] }
|
||||||
actix-server = "1.0.0"
|
actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] }
|
||||||
actix-tls = { version = "2.0.0", features = ["openssl", "rustls"] }
|
actix-utils = "3.0.0-beta.1"
|
||||||
|
actix-server = "2.0.0-beta.2"
|
||||||
|
actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] }
|
||||||
brotli2 = "0.3.2"
|
brotli2 = "0.3.2"
|
||||||
flate2 = "1.0.13"
|
flate2 = "1.0.13"
|
||||||
futures-util = { version = "0.3.5", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
webpki = "0.21"
|
webpki = "0.21"
|
||||||
|
@ -3,36 +3,34 @@
|
|||||||
> Async HTTP and WebSocket client library.
|
> Async HTTP and WebSocket client library.
|
||||||
|
|
||||||
[](https://crates.io/crates/awc)
|
[](https://crates.io/crates/awc)
|
||||||
[](https://docs.rs/awc/2.0.1)
|
[](https://docs.rs/awc/2.0.3)
|
||||||

|

|
||||||
[](https://deps.rs/crate/awc/2.0.1)
|
[](https://deps.rs/crate/awc/2.0.3)
|
||||||
[](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/awc/2.0.1)
|
- [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.42.0
|
- Minimum Supported Rust Version (MSRV): 1.46.0
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use actix_rt::System;
|
use actix_rt::System;
|
||||||
use awc::Client;
|
use awc::Client;
|
||||||
use futures::future::{Future, lazy};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
System::new("test").block_on(lazy(|| {
|
System::new("test").block_on(async {
|
||||||
let mut client = Client::default();
|
let client = Client::default();
|
||||||
|
|
||||||
client.get("http://www.rust-lang.org") // <- Create request builder
|
let res = client
|
||||||
.header("User-Agent", "Actix-web")
|
.get("http://www.rust-lang.org") // <- Create request builder
|
||||||
.send() // <- Send http request
|
.header("User-Agent", "Actix-web")
|
||||||
.and_then(|response| { // <- server http response
|
.send() // <- Send http request
|
||||||
println!("Response: {:?}", response);
|
.await;
|
||||||
Ok(())
|
|
||||||
})
|
println!("Response: {:?}", res); // <- server http response
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user