mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-05 02:16:33 +02:00
Compare commits
45 Commits
files-v0.6
...
web-v4.0.0
Author | SHA1 | Date | |
---|---|---|---|
991363a104 | |||
a290e58982 | |||
dcad9724bc | |||
949d14ae2b | |||
a6ed4aee84 | |||
519d7f2b8a | |||
dddb623a11 | |||
266cf0622c | |||
9604e249c9 | |||
dbc47c9122 | |||
4c243cbf89 | |||
deafb7c8b8 | |||
50309aa295 | |||
9eaea6a2fd | |||
830fb2cdb2 | |||
7cfed73be8 | |||
41bc04b1c4 | |||
20cf0094e5 | |||
83fb4978ad | |||
51e54dac8b | |||
c201c15f8c | |||
0c8196f8b0 | |||
ee10148444 | |||
1c95fc2654 | |||
da69bb4d12 | |||
0a506bf2e9 | |||
b2a9ba2ee4 | |||
f976150b67 | |||
b1dd8d28bc | |||
4edeb5ce47 | |||
d34a8689e5 | |||
a919d2de56 | |||
46a8f28b74 | |||
57398c6df1 | |||
7affc6878e | |||
46b2f7eaaf | |||
9e401b6ef7 | |||
fe392abeb4 | |||
f6cc829758 | |||
6575ee93f2 | |||
530d03791d | |||
d40ae8c8ca | |||
2204614134 | |||
188ee44f81 | |||
a4c9aaf337 |
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,21 +1,21 @@
|
|||||||
<!-- Thanks for considering contributing actix! -->
|
<!-- Thanks for considering contributing actix! -->
|
||||||
<!-- Please fill out the following to make our reviews easy. -->
|
<!-- Please fill out the following to get your PR reviewed quicker. -->
|
||||||
|
|
||||||
## PR Type
|
## PR Type
|
||||||
<!-- What kind of change does this PR make? -->
|
<!-- What kind of change does this PR make? -->
|
||||||
<!-- Bug Fix / Feature / Refactor / Code Style / Other -->
|
<!-- Bug Fix / Feature / Refactor / Code Style / Other -->
|
||||||
INSERT_PR_TYPE
|
PR_TYPE
|
||||||
|
|
||||||
|
|
||||||
## PR Checklist
|
## PR Checklist
|
||||||
Check your PR fulfills the following:
|
<!-- Check your PR fulfills the following items. ->>
|
||||||
|
|
||||||
<!-- For draft PRs check the boxes as you complete them. -->
|
<!-- For draft PRs check the boxes as you complete them. -->
|
||||||
|
|
||||||
- [ ] Tests for the changes have been added / updated.
|
- [ ] Tests for the changes have been added / updated.
|
||||||
- [ ] Documentation comments have been added / updated.
|
- [ ] Documentation comments have been added / updated.
|
||||||
- [ ] A changelog entry has been made for the appropriate packages.
|
- [ ] A changelog entry has been made for the appropriate packages.
|
||||||
- [ ] Format code with the latest stable rustfmt
|
- [ ] Format code with the latest stable rustfmt.
|
||||||
|
- [ ] (Team) Label with affected crates and semver status.
|
||||||
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
2
.github/workflows/bench.yml
vendored
2
.github/workflows/bench.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: Benchmark (Linux)
|
name: Benchmark
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
21
.github/workflows/clippy-fmt.yml
vendored
21
.github/workflows/clippy-fmt.yml
vendored
@ -1,32 +1,39 @@
|
|||||||
|
name: Lint
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
name: Clippy and rustfmt Check
|
|
||||||
jobs:
|
jobs:
|
||||||
clippy_check:
|
fmt:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- uses: actions-rs/toolchain@v1
|
- name: Install Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
override: true
|
|
||||||
- name: Check with rustfmt
|
- name: Check with rustfmt
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: fmt
|
command: fmt
|
||||||
args: --all -- --check
|
args: --all -- --check
|
||||||
|
|
||||||
- uses: actions-rs/toolchain@v1
|
clippy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: nightly
|
toolchain: stable
|
||||||
components: clippy
|
components: clippy
|
||||||
override: true
|
override: true
|
||||||
- name: Check with Clippy
|
- name: Check with Clippy
|
||||||
uses: actions-rs/clippy-check@v1
|
uses: actions-rs/clippy-check@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
args: --all-features --all --tests
|
args: --workspace --tests --all-features
|
||||||
|
2
.github/workflows/upload-doc.yml
vendored
2
.github/workflows/upload-doc.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
|||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: doc
|
command: doc
|
||||||
args: --no-deps --workspace --all-features
|
args: --workspace --all-features --no-deps
|
||||||
|
|
||||||
- name: Tweak HTML
|
- name: Tweak HTML
|
||||||
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
|
||||||
|
44
CHANGES.md
44
CHANGES.md
@ -3,6 +3,46 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0-beta.2 - 2021-xx-xx
|
||||||
|
### Added
|
||||||
|
* The method `Either<web::Json<T>, web::Form<T>>::into_inner()` which returns the inner type for
|
||||||
|
whichever variant was created. Also works for `Either<web::Form<T>, web::Json<T>>`. [#1894]
|
||||||
|
* Add `services!` macro for helping register multiple services to `App`. [#1933]
|
||||||
|
* Enable registering a vec of services of the same type to `App` [#1933]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly.
|
||||||
|
Making it simpler and more performant. [#1891]
|
||||||
|
* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893]
|
||||||
|
* `ServiceRequest::from_request` can no longer fail. [#1893]
|
||||||
|
* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894]
|
||||||
|
* `test::{call_service, read_response, read_response_json, send_request}` take `&Service`
|
||||||
|
in argument [#1905]
|
||||||
|
* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` provide `&Service` in closure
|
||||||
|
argument. [#1905]
|
||||||
|
* `web::block` no longer requires the output is a Result. [#1957]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Multiple calls to `App::data` with the same type now keeps the latest call's data. [#1906]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* Public field of `web::Path` has been made private. [#1894]
|
||||||
|
* Public field of `web::Query` has been made private. [#1894]
|
||||||
|
* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
|
* `AppService::set_service_data`; for custom HTTP service factories adding application data, use the
|
||||||
|
layered data model by calling `ServiceRequest::add_data_container` when handling
|
||||||
|
requests instead. [#1906]
|
||||||
|
|
||||||
|
[#1891]: https://github.com/actix/actix-web/pull/1891
|
||||||
|
[#1893]: https://github.com/actix/actix-web/pull/1893
|
||||||
|
[#1894]: https://github.com/actix/actix-web/pull/1894
|
||||||
|
[#1869]: https://github.com/actix/actix-web/pull/1869
|
||||||
|
[#1905]: https://github.com/actix/actix-web/pull/1905
|
||||||
|
[#1906]: https://github.com/actix/actix-web/pull/1906
|
||||||
|
[#1933]: https://github.com/actix/actix-web/pull/1933
|
||||||
|
[#1957]: https://github.com/actix/actix-web/pull/1957
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.1 - 2021-01-07
|
## 4.0.0-beta.1 - 2021-01-07
|
||||||
### Added
|
### Added
|
||||||
* `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and
|
* `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and
|
||||||
@ -24,13 +64,15 @@
|
|||||||
### Removed
|
### Removed
|
||||||
* Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now
|
* Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now
|
||||||
exposed directly by the `middleware` module.
|
exposed directly by the `middleware` module.
|
||||||
|
* Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported
|
||||||
|
from `actix_web::error` module. [#1878]
|
||||||
|
|
||||||
[#1812]: https://github.com/actix/actix-web/pull/1812
|
[#1812]: https://github.com/actix/actix-web/pull/1812
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
[#1852]: https://github.com/actix/actix-web/pull/1852
|
[#1852]: https://github.com/actix/actix-web/pull/1852
|
||||||
[#1865]: https://github.com/actix/actix-web/pull/1865
|
[#1865]: https://github.com/actix/actix-web/pull/1865
|
||||||
[#1875]: https://github.com/actix/actix-web/pull/1875
|
[#1875]: https://github.com/actix/actix-web/pull/1875
|
||||||
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
|
||||||
## 3.3.2 - 2020-12-01
|
## 3.3.2 - 2020-12-01
|
||||||
### Fixed
|
### Fixed
|
||||||
|
41
Cargo.toml
41
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "4.0.0-beta.1"
|
version = "4.0.0-beta.2"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -47,10 +47,10 @@ compress = ["actix-http/compress", "awc/compress"]
|
|||||||
secure-cookies = ["actix-http/secure-cookies"]
|
secure-cookies = ["actix-http/secure-cookies"]
|
||||||
|
|
||||||
# openssl
|
# openssl
|
||||||
openssl = ["actix-tls/accept", "actix-tls/openssl", "awc/openssl", "open-ssl"]
|
openssl = ["tls_openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"]
|
||||||
|
|
||||||
# rustls
|
# rustls
|
||||||
rustls = ["actix-tls/accept", "actix-tls/rustls", "awc/rustls", "rust-tls"]
|
rustls = ["tls_rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "basic"
|
name = "basic"
|
||||||
@ -74,22 +74,22 @@ required-features = ["rustls"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-macros = "0.1.0"
|
actix-macros = "0.2.0"
|
||||||
actix-router = "0.2.4"
|
actix-router = "0.2.7"
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2"
|
||||||
actix-server = "2.0.0-beta.2"
|
actix-server = "2.0.0-beta.3"
|
||||||
actix-service = "2.0.0-beta.2"
|
actix-service = "2.0.0-beta.4"
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-utils = "3.0.0-beta.2"
|
||||||
actix-threadpool = "0.3.1"
|
actix-tls = { version = "3.0.0-beta.3", default-features = false, optional = true }
|
||||||
actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true }
|
|
||||||
|
|
||||||
actix-web-codegen = "0.4.0"
|
actix-web-codegen = "0.4.0"
|
||||||
actix-http = "3.0.0-beta.1"
|
actix-http = "3.0.0-beta.2"
|
||||||
awc = { version = "3.0.0-beta.1", default-features = false }
|
awc = { version = "3.0.0-beta.2", default-features = false }
|
||||||
|
|
||||||
ahash = "0.6"
|
ahash = "0.7"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
|
either = "1.5.3"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
@ -101,15 +101,14 @@ 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.7"
|
serde_urlencoded = "0.7"
|
||||||
time = { version = "0.2.7", default-features = false, features = ["std"] }
|
time = { version = "0.2.23", default-features = false, features = ["std"] }
|
||||||
url = "2.1"
|
url = "2.1"
|
||||||
open-ssl = { package = "openssl", version = "0.10", optional = true }
|
tls_openssl = { package = "openssl", version = "0.10.9", optional = true }
|
||||||
rust-tls = { package = "rustls", version = "0.19.0", optional = true }
|
tls_rustls = { package = "rustls", version = "0.19.0", optional = true }
|
||||||
smallvec = "1.6"
|
smallvec = "1.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix = "0.11.0-beta.1"
|
actix = { version = "0.11.0-beta.2", default-features = false }
|
||||||
actix-http = { version = "3.0.0-beta.1", features = ["actors"] }
|
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
@ -139,3 +138,7 @@ harness = false
|
|||||||
[[bench]]
|
[[bench]]
|
||||||
name = "service"
|
name = "service"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "responder"
|
||||||
|
harness = false
|
||||||
|
17
README.md
17
README.md
@ -1,20 +1,19 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<h1>Actix web</h1>
|
<h1>Actix Web</h1>
|
||||||
<p>
|
<p>
|
||||||
<strong>Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust</strong>
|
<strong>Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust</strong>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web)
|
[](https://crates.io/crates/actix-web)
|
||||||
[](https://docs.rs/actix-web/3.3.2)
|
[](https://docs.rs/actix-web/4.0.0-beta.2)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-web/3.3.2)
|
[](https://deps.rs/crate/actix-web/4.0.0-beta.2)
|
||||||
<br />
|
<br />
|
||||||
[](https://travis-ci.org/actix/actix-web)
|
[](https://github.com/actix/actix-web/actions)
|
||||||
[](https://codecov.io/gh/actix/actix-web)
|
[](https://codecov.io/gh/actix/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://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
@ -99,9 +98,9 @@ One of the fastest web frameworks available according to the
|
|||||||
This project is licensed under either of
|
This project is licensed under either of
|
||||||
|
|
||||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
|
[http://www.apache.org/licenses/LICENSE-2.0])
|
||||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||||
[http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
|
[http://opensource.org/licenses/MIT])
|
||||||
|
|
||||||
at your option.
|
at your option.
|
||||||
|
|
||||||
|
@ -3,6 +3,14 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0-beta.2 - 2021-02-10
|
||||||
|
* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
|
||||||
|
* Replace `v_htmlescape` with `askama_escape`. [#1953]
|
||||||
|
|
||||||
|
[#1887]: https://github.com/actix/actix-web/pull/1887
|
||||||
|
[#1953]: https://github.com/actix/actix-web/pull/1953
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.1 - 2021-01-07
|
## 0.6.0-beta.1 - 2021-01-07
|
||||||
* `HttpRange::parse` now has its own error type.
|
* `HttpRange::parse` now has its own error type.
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.6.0-beta.1"
|
version = "0.6.0-beta.2"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Static file serving for Actix Web"
|
description = "Static file serving for Actix Web"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -17,8 +17,10 @@ name = "actix_files"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.1", default-features = false }
|
actix-web = { version = "4.0.0-beta.2", default-features = false }
|
||||||
actix-service = "2.0.0-beta.2"
|
actix-service = "2.0.0-beta.4"
|
||||||
|
|
||||||
|
askama_escape = "0.10"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
@ -28,8 +30,7 @@ log = "0.4"
|
|||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
mime_guess = "2.0.1"
|
mime_guess = "2.0.1"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
v_htmlescape = "0.12"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2"
|
||||||
actix-web = "4.0.0-beta.1"
|
actix-web = "4.0.0-beta.2"
|
||||||
|
@ -9,26 +9,35 @@ use std::{
|
|||||||
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
error::{BlockingError, Error},
|
error::{BlockingError, Error},
|
||||||
web,
|
rt::task::{spawn_blocking, JoinHandle},
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
use futures_util::future::{FutureExt, LocalBoxFuture};
|
|
||||||
|
|
||||||
use crate::handle_error;
|
|
||||||
|
|
||||||
type ChunkedBoxFuture =
|
|
||||||
LocalBoxFuture<'static, Result<(File, Bytes), BlockingError<io::Error>>>;
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// A helper created from a `std::fs::File` which reads the file
|
/// A helper created from a `std::fs::File` which reads the file
|
||||||
/// chunk-by-chunk on a `ThreadPool`.
|
/// chunk-by-chunk on a `ThreadPool`.
|
||||||
pub struct ChunkedReadFile {
|
pub struct ChunkedReadFile {
|
||||||
pub(crate) size: u64,
|
size: u64,
|
||||||
pub(crate) offset: u64,
|
offset: u64,
|
||||||
pub(crate) file: Option<File>,
|
state: ChunkedReadFileState,
|
||||||
pub(crate) fut: Option<ChunkedBoxFuture>,
|
counter: u64,
|
||||||
pub(crate) counter: u64,
|
}
|
||||||
|
|
||||||
|
enum ChunkedReadFileState {
|
||||||
|
File(Option<File>),
|
||||||
|
Future(JoinHandle<Result<(File, Bytes), io::Error>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChunkedReadFile {
|
||||||
|
pub(crate) fn new(size: u64, offset: u64, file: File) -> Self {
|
||||||
|
Self {
|
||||||
|
size,
|
||||||
|
offset,
|
||||||
|
state: ChunkedReadFileState::File(Some(file)),
|
||||||
|
counter: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for ChunkedReadFile {
|
impl fmt::Debug for ChunkedReadFile {
|
||||||
@ -44,51 +53,52 @@ impl Stream for ChunkedReadFile {
|
|||||||
mut self: Pin<&mut Self>,
|
mut self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Option<Self::Item>> {
|
) -> Poll<Option<Self::Item>> {
|
||||||
if let Some(ref mut fut) = self.fut {
|
let this = self.as_mut().get_mut();
|
||||||
return match ready!(Pin::new(fut).poll(cx)) {
|
match this.state {
|
||||||
Ok((file, bytes)) => {
|
ChunkedReadFileState::File(ref mut file) => {
|
||||||
self.fut.take();
|
let size = this.size;
|
||||||
self.file = Some(file);
|
let offset = this.offset;
|
||||||
|
let counter = this.counter;
|
||||||
|
|
||||||
self.offset += bytes.len() as u64;
|
if size == counter {
|
||||||
self.counter += bytes.len() as u64;
|
Poll::Ready(None)
|
||||||
|
} else {
|
||||||
|
let mut file = file
|
||||||
|
.take()
|
||||||
|
.expect("ChunkedReadFile polled after completion");
|
||||||
|
|
||||||
Poll::Ready(Some(Ok(bytes)))
|
let fut = spawn_blocking(move || {
|
||||||
|
let max_bytes =
|
||||||
|
cmp::min(size.saturating_sub(counter), 65_536) as usize;
|
||||||
|
|
||||||
|
let mut buf = Vec::with_capacity(max_bytes);
|
||||||
|
file.seek(io::SeekFrom::Start(offset))?;
|
||||||
|
|
||||||
|
let n_bytes = file
|
||||||
|
.by_ref()
|
||||||
|
.take(max_bytes as u64)
|
||||||
|
.read_to_end(&mut buf)?;
|
||||||
|
|
||||||
|
if n_bytes == 0 {
|
||||||
|
return Err(io::ErrorKind::UnexpectedEof.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((file, Bytes::from(buf)))
|
||||||
|
});
|
||||||
|
this.state = ChunkedReadFileState::Future(fut);
|
||||||
|
self.poll_next(cx)
|
||||||
}
|
}
|
||||||
Err(e) => Poll::Ready(Some(Err(handle_error(e)))),
|
}
|
||||||
};
|
ChunkedReadFileState::Future(ref mut fut) => {
|
||||||
}
|
let (file, bytes) =
|
||||||
|
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
||||||
|
this.state = ChunkedReadFileState::File(Some(file));
|
||||||
|
|
||||||
let size = self.size;
|
this.offset += bytes.len() as u64;
|
||||||
let offset = self.offset;
|
this.counter += bytes.len() as u64;
|
||||||
let counter = self.counter;
|
|
||||||
|
|
||||||
if size == counter {
|
Poll::Ready(Some(Ok(bytes)))
|
||||||
Poll::Ready(None)
|
}
|
||||||
} else {
|
|
||||||
let mut file = self.file.take().expect("Use after completion");
|
|
||||||
|
|
||||||
self.fut = Some(
|
|
||||||
web::block(move || {
|
|
||||||
let max_bytes =
|
|
||||||
cmp::min(size.saturating_sub(counter), 65_536) as usize;
|
|
||||||
|
|
||||||
let mut buf = Vec::with_capacity(max_bytes);
|
|
||||||
file.seek(io::SeekFrom::Start(offset))?;
|
|
||||||
|
|
||||||
let n_bytes =
|
|
||||||
file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?;
|
|
||||||
|
|
||||||
if n_bytes == 0 {
|
|
||||||
return Err(io::ErrorKind::UnexpectedEof.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((file, Bytes::from(buf)))
|
|
||||||
})
|
|
||||||
.boxed_local(),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.poll_next(cx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use std::{fmt::Write, fs::DirEntry, io, path::Path, path::PathBuf};
|
use std::{fmt::Write, fs::DirEntry, io, path::Path, path::PathBuf};
|
||||||
|
|
||||||
use actix_web::{dev::ServiceResponse, HttpRequest, HttpResponse};
|
use actix_web::{dev::ServiceResponse, HttpRequest, HttpResponse};
|
||||||
|
use askama_escape::{escape as escape_html_entity, Html};
|
||||||
use percent_encoding::{utf8_percent_encode, CONTROLS};
|
use percent_encoding::{utf8_percent_encode, CONTROLS};
|
||||||
use v_htmlescape::escape as escape_html_entity;
|
|
||||||
|
|
||||||
/// A directory; responds with the generated directory listing.
|
/// A directory; responds with the generated directory listing.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -50,7 +50,7 @@ macro_rules! encode_file_url {
|
|||||||
// " -- " & -- & ' -- ' < -- < > -- > / -- /
|
// " -- " & -- & ' -- ' < -- < > -- > / -- /
|
||||||
macro_rules! encode_file_name {
|
macro_rules! encode_file_name {
|
||||||
($entry:ident) => {
|
($entry:ident) => {
|
||||||
escape_html_entity(&$entry.file_name().to_string_lossy())
|
escape_html_entity(&$entry.file_name().to_string_lossy(), Html)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,8 +82,9 @@ impl Files {
|
|||||||
/// be inaccessible. Register more specific handlers and services first.
|
/// be inaccessible. Register more specific handlers and services first.
|
||||||
///
|
///
|
||||||
/// `Files` uses a threadpool for blocking filesystem operations. By default, the pool uses a
|
/// `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
|
/// max number of threads equal to `512 * HttpServer::worker`. Real time thread count are
|
||||||
/// by setting ACTIX_THREADPOOL environment variable.
|
/// adjusted with work load. More threads would spawn when need and threads goes idle for a
|
||||||
|
/// period of time would be de-spawned.
|
||||||
pub fn new<T: Into<PathBuf>>(mount_path: &str, serve_from: T) -> Files {
|
pub fn new<T: Into<PathBuf>>(mount_path: &str, serve_from: T) -> Files {
|
||||||
let orig_dir = serve_from.into();
|
let orig_dir = serve_from.into();
|
||||||
let dir = match orig_dir.canonicalize() {
|
let dir = match orig_dir.canonicalize() {
|
||||||
|
@ -14,12 +14,10 @@
|
|||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
#![warn(missing_docs, missing_debug_implementations)]
|
#![warn(missing_docs, missing_debug_implementations)]
|
||||||
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use actix_service::boxed::{BoxService, BoxServiceFactory};
|
use actix_service::boxed::{BoxService, BoxServiceFactory};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{ServiceRequest, ServiceResponse},
|
dev::{ServiceRequest, ServiceResponse},
|
||||||
error::{BlockingError, Error, ErrorInternalServerError},
|
error::Error,
|
||||||
http::header::DispositionType,
|
http::header::DispositionType,
|
||||||
};
|
};
|
||||||
use mime_guess::from_ext;
|
use mime_guess::from_ext;
|
||||||
@ -56,13 +54,6 @@ pub fn file_extension_to_mime(ext: &str) -> mime::Mime {
|
|||||||
from_ext(ext).first_or_octet_stream()
|
from_ext(ext).first_or_octet_stream()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_error(err: BlockingError<io::Error>) -> Error {
|
|
||||||
match err {
|
|
||||||
BlockingError::Error(err) => err.into(),
|
|
||||||
BlockingError::Canceled => ErrorInternalServerError("Unexpected error"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MimeOverride = dyn Fn(&mime::Name<'_>) -> DispositionType;
|
type MimeOverride = dyn Fn(&mime::Name<'_>) -> DispositionType;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -82,7 +73,8 @@ mod tests {
|
|||||||
},
|
},
|
||||||
middleware::Compress,
|
middleware::Compress,
|
||||||
test::{self, TestRequest},
|
test::{self, TestRequest},
|
||||||
web, App, HttpResponse, Responder,
|
web::{self, Bytes},
|
||||||
|
App, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
use futures_util::future::ok;
|
use futures_util::future::ok;
|
||||||
|
|
||||||
@ -110,7 +102,19 @@ mod tests {
|
|||||||
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(header::IF_MODIFIED_SINCE, since)
|
.insert_header((header::IF_MODIFIED_SINCE, since))
|
||||||
|
.to_http_request();
|
||||||
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_if_modified_since_without_if_none_match_same() {
|
||||||
|
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||||
|
let since = file.last_modified().unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.insert_header((header::IF_MODIFIED_SINCE, since))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
||||||
@ -123,13 +127,37 @@ mod tests {
|
|||||||
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(header::IF_NONE_MATCH, "miss_etag")
|
.insert_header((header::IF_NONE_MATCH, "miss_etag"))
|
||||||
.header(header::IF_MODIFIED_SINCE, since)
|
.insert_header((header::IF_MODIFIED_SINCE, since))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
|
assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_if_unmodified_since() {
|
||||||
|
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||||
|
let since = file.last_modified().unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.insert_header((header::IF_UNMODIFIED_SINCE, since))
|
||||||
|
.to_http_request();
|
||||||
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_if_unmodified_since_failed() {
|
||||||
|
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||||
|
let since = header::HttpDate::from(SystemTime::UNIX_EPOCH);
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.insert_header((header::IF_UNMODIFIED_SINCE, since))
|
||||||
|
.to_http_request();
|
||||||
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_named_file_text() {
|
async fn test_named_file_text() {
|
||||||
assert!(NamedFile::open("test--").is_err());
|
assert!(NamedFile::open("test--").is_err());
|
||||||
@ -338,7 +366,7 @@ mod tests {
|
|||||||
DispositionType::Attachment
|
DispositionType::Attachment
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut srv = test::init_service(
|
let srv = test::init_service(
|
||||||
App::new().service(
|
App::new().service(
|
||||||
Files::new("/", ".")
|
Files::new("/", ".")
|
||||||
.mime_override(all_attachment)
|
.mime_override(all_attachment)
|
||||||
@ -348,7 +376,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let request = TestRequest::get().uri("/").to_request();
|
let request = TestRequest::get().uri("/").to_request();
|
||||||
let response = test::call_service(&mut srv, request).await;
|
let response = test::call_service(&srv, request).await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
let content_disposition = response
|
let content_disposition = response
|
||||||
@ -363,7 +391,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_named_file_ranges_status_code() {
|
async fn test_named_file_ranges_status_code() {
|
||||||
let mut srv = test::init_service(
|
let srv = test::init_service(
|
||||||
App::new().service(Files::new("/test", ".").index_file("Cargo.toml")),
|
App::new().service(Files::new("/test", ".").index_file("Cargo.toml")),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
@ -371,17 +399,17 @@ mod tests {
|
|||||||
// Valid range header
|
// Valid range header
|
||||||
let request = TestRequest::get()
|
let request = TestRequest::get()
|
||||||
.uri("/t%65st/Cargo.toml")
|
.uri("/t%65st/Cargo.toml")
|
||||||
.header(header::RANGE, "bytes=10-20")
|
.insert_header((header::RANGE, "bytes=10-20"))
|
||||||
.to_request();
|
.to_request();
|
||||||
let response = test::call_service(&mut srv, request).await;
|
let response = test::call_service(&srv, request).await;
|
||||||
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
|
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
|
||||||
|
|
||||||
// Invalid range header
|
// Invalid range header
|
||||||
let request = TestRequest::get()
|
let request = TestRequest::get()
|
||||||
.uri("/t%65st/Cargo.toml")
|
.uri("/t%65st/Cargo.toml")
|
||||||
.header(header::RANGE, "bytes=1-0")
|
.insert_header((header::RANGE, "bytes=1-0"))
|
||||||
.to_request();
|
.to_request();
|
||||||
let response = test::call_service(&mut srv, request).await;
|
let response = test::call_service(&srv, request).await;
|
||||||
|
|
||||||
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
|
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
|
||||||
}
|
}
|
||||||
@ -393,7 +421,7 @@ mod tests {
|
|||||||
// Valid range header
|
// Valid range header
|
||||||
let response = srv
|
let response = srv
|
||||||
.get("/tests/test.binary")
|
.get("/tests/test.binary")
|
||||||
.header(header::RANGE, "bytes=10-20")
|
.insert_header((header::RANGE, "bytes=10-20"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -403,7 +431,7 @@ mod tests {
|
|||||||
// Invalid range header
|
// Invalid range header
|
||||||
let response = srv
|
let response = srv
|
||||||
.get("/tests/test.binary")
|
.get("/tests/test.binary")
|
||||||
.header(header::RANGE, "bytes=10-5")
|
.insert_header((header::RANGE, "bytes=10-5"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -418,7 +446,7 @@ mod tests {
|
|||||||
// Valid range header
|
// Valid range header
|
||||||
let response = srv
|
let response = srv
|
||||||
.get("/tests/test.binary")
|
.get("/tests/test.binary")
|
||||||
.header(header::RANGE, "bytes=10-20")
|
.insert_header((header::RANGE, "bytes=10-20"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -428,7 +456,7 @@ mod tests {
|
|||||||
// Valid range header, starting from 0
|
// Valid range header, starting from 0
|
||||||
let response = srv
|
let response = srv
|
||||||
.get("/tests/test.binary")
|
.get("/tests/test.binary")
|
||||||
.header(header::RANGE, "bytes=0-20")
|
.insert_header((header::RANGE, "bytes=0-20"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -468,14 +496,14 @@ mod tests {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_static_files_with_spaces() {
|
async fn test_static_files_with_spaces() {
|
||||||
let mut srv = test::init_service(
|
let srv = test::init_service(
|
||||||
App::new().service(Files::new("/", ".").index_file("Cargo.toml")),
|
App::new().service(Files::new("/", ".").index_file("Cargo.toml")),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let request = TestRequest::get()
|
let request = TestRequest::get()
|
||||||
.uri("/tests/test%20space.binary")
|
.uri("/tests/test%20space.binary")
|
||||||
.to_request();
|
.to_request();
|
||||||
let response = test::call_service(&mut srv, request).await;
|
let response = test::call_service(&srv, request).await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
let bytes = test::read_body(response).await;
|
let bytes = test::read_body(response).await;
|
||||||
@ -485,28 +513,28 @@ mod tests {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_files_not_allowed() {
|
async fn test_files_not_allowed() {
|
||||||
let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await;
|
let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.uri("/Cargo.toml")
|
.uri("/Cargo.toml")
|
||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
.to_request();
|
.to_request();
|
||||||
|
|
||||||
let resp = test::call_service(&mut srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||||
|
|
||||||
let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await;
|
let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.method(Method::PUT)
|
.method(Method::PUT)
|
||||||
.uri("/Cargo.toml")
|
.uri("/Cargo.toml")
|
||||||
.to_request();
|
.to_request();
|
||||||
let resp = test::call_service(&mut srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_files_guards() {
|
async fn test_files_guards() {
|
||||||
let mut srv = test::init_service(
|
let srv = test::init_service(
|
||||||
App::new().service(Files::new("/", ".").use_guards(guard::Post())),
|
App::new().service(Files::new("/", ".").use_guards(guard::Post())),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
@ -516,13 +544,13 @@ mod tests {
|
|||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
.to_request();
|
.to_request();
|
||||||
|
|
||||||
let resp = test::call_service(&mut srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_named_file_content_encoding() {
|
async fn test_named_file_content_encoding() {
|
||||||
let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
|
let srv = test::init_service(App::new().wrap(Compress::default()).service(
|
||||||
web::resource("/").to(|| async {
|
web::resource("/").to(|| async {
|
||||||
NamedFile::open("Cargo.toml")
|
NamedFile::open("Cargo.toml")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -533,16 +561,16 @@ mod tests {
|
|||||||
|
|
||||||
let request = TestRequest::get()
|
let request = TestRequest::get()
|
||||||
.uri("/")
|
.uri("/")
|
||||||
.header(header::ACCEPT_ENCODING, "gzip")
|
.insert_header((header::ACCEPT_ENCODING, "gzip"))
|
||||||
.to_request();
|
.to_request();
|
||||||
let res = test::call_service(&mut srv, request).await;
|
let res = test::call_service(&srv, request).await;
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
|
assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_named_file_content_encoding_gzip() {
|
async fn test_named_file_content_encoding_gzip() {
|
||||||
let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
|
let srv = test::init_service(App::new().wrap(Compress::default()).service(
|
||||||
web::resource("/").to(|| async {
|
web::resource("/").to(|| async {
|
||||||
NamedFile::open("Cargo.toml")
|
NamedFile::open("Cargo.toml")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -553,9 +581,9 @@ mod tests {
|
|||||||
|
|
||||||
let request = TestRequest::get()
|
let request = TestRequest::get()
|
||||||
.uri("/")
|
.uri("/")
|
||||||
.header(header::ACCEPT_ENCODING, "gzip")
|
.insert_header((header::ACCEPT_ENCODING, "gzip"))
|
||||||
.to_request();
|
.to_request();
|
||||||
let res = test::call_service(&mut srv, request).await;
|
let res = test::call_service(&srv, request).await;
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.headers()
|
res.headers()
|
||||||
@ -577,27 +605,27 @@ mod tests {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_static_files() {
|
async fn test_static_files() {
|
||||||
let mut srv = test::init_service(
|
let srv = test::init_service(
|
||||||
App::new().service(Files::new("/", ".").show_files_listing()),
|
App::new().service(Files::new("/", ".").show_files_listing()),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let req = TestRequest::with_uri("/missing").to_request();
|
let req = TestRequest::with_uri("/missing").to_request();
|
||||||
|
|
||||||
let resp = test::call_service(&mut srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await;
|
let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
|
||||||
|
|
||||||
let req = TestRequest::default().to_request();
|
let req = TestRequest::default().to_request();
|
||||||
let resp = test::call_service(&mut srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
let mut srv = test::init_service(
|
let srv = test::init_service(
|
||||||
App::new().service(Files::new("/", ".").show_files_listing()),
|
App::new().service(Files::new("/", ".").show_files_listing()),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let req = TestRequest::with_uri("/tests").to_request();
|
let req = TestRequest::with_uri("/tests").to_request();
|
||||||
let resp = test::call_service(&mut srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
"text/html; charset=utf-8"
|
"text/html; charset=utf-8"
|
||||||
@ -610,16 +638,16 @@ mod tests {
|
|||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_redirect_to_slash_directory() {
|
async fn test_redirect_to_slash_directory() {
|
||||||
// should not redirect if no index
|
// should not redirect if no index
|
||||||
let mut srv = test::init_service(
|
let srv = test::init_service(
|
||||||
App::new().service(Files::new("/", ".").redirect_to_slash_directory()),
|
App::new().service(Files::new("/", ".").redirect_to_slash_directory()),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let req = TestRequest::with_uri("/tests").to_request();
|
let req = TestRequest::with_uri("/tests").to_request();
|
||||||
let resp = test::call_service(&mut srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
// should redirect if index present
|
// should redirect if index present
|
||||||
let mut srv = test::init_service(
|
let srv = test::init_service(
|
||||||
App::new().service(
|
App::new().service(
|
||||||
Files::new("/", ".")
|
Files::new("/", ".")
|
||||||
.index_file("test.png")
|
.index_file("test.png")
|
||||||
@ -628,12 +656,12 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let req = TestRequest::with_uri("/tests").to_request();
|
let req = TestRequest::with_uri("/tests").to_request();
|
||||||
let resp = test::call_service(&mut srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||||
|
|
||||||
// should not redirect if the path is wrong
|
// should not redirect if the path is wrong
|
||||||
let req = TestRequest::with_uri("/not_existing").to_request();
|
let req = TestRequest::with_uri("/not_existing").to_request();
|
||||||
let resp = test::call_service(&mut srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -645,7 +673,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_default_handler_file_missing() {
|
async fn test_default_handler_file_missing() {
|
||||||
let mut st = Files::new("/", ".")
|
let st = Files::new("/", ".")
|
||||||
.default_handler(|req: ServiceRequest| {
|
.default_handler(|req: ServiceRequest| {
|
||||||
ok(req.into_response(HttpResponse::Ok().body("default content")))
|
ok(req.into_response(HttpResponse::Ok().body("default content")))
|
||||||
})
|
})
|
||||||
@ -654,7 +682,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let req = TestRequest::with_uri("/missing").to_srv_request();
|
let req = TestRequest::with_uri("/missing").to_srv_request();
|
||||||
|
|
||||||
let resp = test::call_service(&mut st, req).await;
|
let resp = test::call_service(&st, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
let bytes = test::read_body(resp).await;
|
let bytes = test::read_body(resp).await;
|
||||||
assert_eq!(bytes, web::Bytes::from_static(b"default content"));
|
assert_eq!(bytes, web::Bytes::from_static(b"default content"));
|
||||||
@ -723,54 +751,49 @@ mod tests {
|
|||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// #[actix_rt::test]
|
#[actix_rt::test]
|
||||||
// fn integration_serve_index() {
|
async fn integration_serve_index() {
|
||||||
// let mut srv = test::TestServer::with_factory(|| {
|
let srv = test::init_service(
|
||||||
// App::new().handler(
|
App::new().service(Files::new("test", ".").index_file("Cargo.toml")),
|
||||||
// "test",
|
)
|
||||||
// Files::new(".").index_file("Cargo.toml"),
|
.await;
|
||||||
// )
|
|
||||||
// });
|
|
||||||
|
|
||||||
// let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
let req = TestRequest::get().uri("/test").to_request();
|
||||||
// let response = srv.execute(request.send()).unwrap();
|
let res = test::call_service(&srv, req).await;
|
||||||
// assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
// let bytes = srv.execute(response.body()).unwrap();
|
|
||||||
// let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
|
||||||
// assert_eq!(bytes, data);
|
|
||||||
|
|
||||||
// let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
|
let bytes = test::read_body(res).await;
|
||||||
// let response = srv.execute(request.send()).unwrap();
|
|
||||||
// assert_eq!(response.status(), StatusCode::OK);
|
|
||||||
// let bytes = srv.execute(response.body()).unwrap();
|
|
||||||
// let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
|
||||||
// assert_eq!(bytes, data);
|
|
||||||
|
|
||||||
// // nonexistent index file
|
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||||
// let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap();
|
assert_eq!(bytes, data);
|
||||||
// let response = srv.execute(request.send()).unwrap();
|
|
||||||
// assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
|
||||||
|
|
||||||
// let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap();
|
let req = TestRequest::get().uri("/test/").to_request();
|
||||||
// let response = srv.execute(request.send()).unwrap();
|
let res = test::call_service(&srv, req).await;
|
||||||
// assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
// }
|
|
||||||
|
|
||||||
// #[actix_rt::test]
|
let bytes = test::read_body(res).await;
|
||||||
// fn integration_percent_encoded() {
|
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||||
// let mut srv = test::TestServer::with_factory(|| {
|
assert_eq!(bytes, data);
|
||||||
// App::new().handler(
|
|
||||||
// "test",
|
|
||||||
// Files::new(".").index_file("Cargo.toml"),
|
|
||||||
// )
|
|
||||||
// });
|
|
||||||
|
|
||||||
// let request = srv
|
// nonexistent index file
|
||||||
// .get()
|
let req = TestRequest::get().uri("/test/unknown").to_request();
|
||||||
// .uri(srv.url("/test/%43argo.toml"))
|
let res = test::call_service(&srv, req).await;
|
||||||
// .finish()
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
// .unwrap();
|
|
||||||
// let response = srv.execute(request.send()).unwrap();
|
let req = TestRequest::get().uri("/test/unknown/").to_request();
|
||||||
// assert_eq!(response.status(), StatusCode::OK);
|
let res = test::call_service(&srv, req).await;
|
||||||
// }
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn integration_percent_encoded() {
|
||||||
|
let srv = test::init_service(
|
||||||
|
App::new().service(Files::new("test", ".").index_file("Cargo.toml")),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = TestRequest::get().uri("/test/%43argo.toml").to_request();
|
||||||
|
let res = test::call_service(&srv, req).await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,9 @@ use actix_web::{
|
|||||||
},
|
},
|
||||||
ContentEncoding, StatusCode,
|
ContentEncoding, StatusCode,
|
||||||
},
|
},
|
||||||
Error, HttpMessage, HttpRequest, HttpResponse, Responder,
|
HttpMessage, HttpRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use futures_util::future::{ready, Ready};
|
|
||||||
use mime_guess::from_path;
|
use mime_guess::from_path;
|
||||||
|
|
||||||
use crate::ChunkedReadFile;
|
use crate::ChunkedReadFile;
|
||||||
@ -277,37 +276,31 @@ impl NamedFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an `HttpResponse` with file as a streaming body.
|
/// Creates an `HttpResponse` with file as a streaming body.
|
||||||
pub fn into_response(self, req: &HttpRequest) -> Result<HttpResponse, Error> {
|
pub fn into_response(self, req: &HttpRequest) -> HttpResponse {
|
||||||
if self.status_code != StatusCode::OK {
|
if self.status_code != StatusCode::OK {
|
||||||
let mut res = HttpResponse::build(self.status_code);
|
let mut res = HttpResponse::build(self.status_code);
|
||||||
|
|
||||||
if self.flags.contains(Flags::PREFER_UTF8) {
|
if self.flags.contains(Flags::PREFER_UTF8) {
|
||||||
let ct = equiv_utf8_text(self.content_type.clone());
|
let ct = equiv_utf8_text(self.content_type.clone());
|
||||||
res.header(header::CONTENT_TYPE, ct.to_string());
|
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
|
||||||
} else {
|
} else {
|
||||||
res.header(header::CONTENT_TYPE, self.content_type.to_string());
|
res.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
|
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
|
||||||
res.header(
|
res.insert_header((
|
||||||
header::CONTENT_DISPOSITION,
|
header::CONTENT_DISPOSITION,
|
||||||
self.content_disposition.to_string(),
|
self.content_disposition.to_string(),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(current_encoding) = self.encoding {
|
if let Some(current_encoding) = self.encoding {
|
||||||
res.encoding(current_encoding);
|
res.encoding(current_encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
let reader = ChunkedReadFile {
|
let reader = ChunkedReadFile::new(self.md.len(), 0, self.file);
|
||||||
size: self.md.len(),
|
|
||||||
offset: 0,
|
|
||||||
file: Some(self.file),
|
|
||||||
fut: None,
|
|
||||||
counter: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Ok(res.streaming(reader));
|
return res.streaming(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
let etag = if self.flags.contains(Flags::ETAG) {
|
let etag = if self.flags.contains(Flags::ETAG) {
|
||||||
@ -332,7 +325,7 @@ impl NamedFile {
|
|||||||
let t2: SystemTime = since.clone().into();
|
let t2: SystemTime = since.clone().into();
|
||||||
|
|
||||||
match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) {
|
match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) {
|
||||||
(Ok(t1), Ok(t2)) => t1 > t2,
|
(Ok(t1), Ok(t2)) => t1.as_secs() > t2.as_secs(),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -351,7 +344,7 @@ impl NamedFile {
|
|||||||
let t2: SystemTime = since.clone().into();
|
let t2: SystemTime = since.clone().into();
|
||||||
|
|
||||||
match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) {
|
match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) {
|
||||||
(Ok(t1), Ok(t2)) => t1 <= t2,
|
(Ok(t1), Ok(t2)) => t1.as_secs() <= t2.as_secs(),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -362,16 +355,16 @@ impl NamedFile {
|
|||||||
|
|
||||||
if self.flags.contains(Flags::PREFER_UTF8) {
|
if self.flags.contains(Flags::PREFER_UTF8) {
|
||||||
let ct = equiv_utf8_text(self.content_type.clone());
|
let ct = equiv_utf8_text(self.content_type.clone());
|
||||||
resp.header(header::CONTENT_TYPE, ct.to_string());
|
resp.insert_header((header::CONTENT_TYPE, ct.to_string()));
|
||||||
} else {
|
} else {
|
||||||
resp.header(header::CONTENT_TYPE, self.content_type.to_string());
|
resp.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
|
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
|
||||||
resp.header(
|
resp.insert_header((
|
||||||
header::CONTENT_DISPOSITION,
|
header::CONTENT_DISPOSITION,
|
||||||
self.content_disposition.to_string(),
|
self.content_disposition.to_string(),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// default compressing
|
// default compressing
|
||||||
@ -380,14 +373,14 @@ impl NamedFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(lm) = last_modified {
|
if let Some(lm) = last_modified {
|
||||||
resp.header(header::LAST_MODIFIED, lm.to_string());
|
resp.insert_header((header::LAST_MODIFIED, lm.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(etag) = etag {
|
if let Some(etag) = etag {
|
||||||
resp.header(header::ETAG, etag.to_string());
|
resp.insert_header((header::ETAG, etag.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.header(header::ACCEPT_RANGES, "bytes");
|
resp.insert_header((header::ACCEPT_RANGES, "bytes"));
|
||||||
|
|
||||||
let mut length = self.md.len();
|
let mut length = self.md.len();
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
@ -400,7 +393,7 @@ impl NamedFile {
|
|||||||
offset = ranges[0].start;
|
offset = ranges[0].start;
|
||||||
|
|
||||||
resp.encoding(ContentEncoding::Identity);
|
resp.encoding(ContentEncoding::Identity);
|
||||||
resp.header(
|
resp.insert_header((
|
||||||
header::CONTENT_RANGE,
|
header::CONTENT_RANGE,
|
||||||
format!(
|
format!(
|
||||||
"bytes {}-{}/{}",
|
"bytes {}-{}/{}",
|
||||||
@ -408,35 +401,32 @@ impl NamedFile {
|
|||||||
offset + length - 1,
|
offset + length - 1,
|
||||||
self.md.len()
|
self.md.len()
|
||||||
),
|
),
|
||||||
);
|
));
|
||||||
} else {
|
} else {
|
||||||
resp.header(header::CONTENT_RANGE, format!("bytes */{}", length));
|
resp.insert_header((
|
||||||
return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish());
|
header::CONTENT_RANGE,
|
||||||
|
format!("bytes */{}", length),
|
||||||
|
));
|
||||||
|
return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return Ok(resp.status(StatusCode::BAD_REQUEST).finish());
|
return resp.status(StatusCode::BAD_REQUEST).finish();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if precondition_failed {
|
if precondition_failed {
|
||||||
return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish());
|
return resp.status(StatusCode::PRECONDITION_FAILED).finish();
|
||||||
} else if not_modified {
|
} else if not_modified {
|
||||||
return Ok(resp.status(StatusCode::NOT_MODIFIED).finish());
|
return resp.status(StatusCode::NOT_MODIFIED).finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
let reader = ChunkedReadFile {
|
let reader = ChunkedReadFile::new(length, offset, self.file);
|
||||||
offset,
|
|
||||||
size: length,
|
|
||||||
file: Some(self.file),
|
|
||||||
fut: None,
|
|
||||||
counter: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
if offset != 0 || length != self.md.len() {
|
if offset != 0 || length != self.md.len() {
|
||||||
resp.status(StatusCode::PARTIAL_CONTENT);
|
resp.status(StatusCode::PARTIAL_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(resp.body(SizedStream::new(length, reader)))
|
resp.body(SizedStream::new(length, reader))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,10 +485,7 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for NamedFile {
|
impl Responder for NamedFile {
|
||||||
type Error = Error;
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||||
type Future = Ready<Result<HttpResponse, Error>>;
|
self.into_response(req)
|
||||||
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
|
||||||
ready(self.into_response(req))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
use std::{
|
use std::{fmt, io, path::PathBuf, rc::Rc, task::Poll};
|
||||||
fmt, io,
|
|
||||||
path::PathBuf,
|
|
||||||
rc::Rc,
|
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
|
||||||
|
|
||||||
use actix_service::Service;
|
use actix_service::Service;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
@ -40,10 +35,10 @@ type FilesServiceFuture = Either<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
impl FilesService {
|
impl FilesService {
|
||||||
fn handle_err(&mut self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture {
|
fn handle_err(&self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture {
|
||||||
log::debug!("Failed to handle {}: {}", req.path(), e);
|
log::debug!("Failed to handle {}: {}", req.path(), e);
|
||||||
|
|
||||||
if let Some(ref mut default) = self.default {
|
if let Some(ref default) = self.default {
|
||||||
Either::Right(default.call(req))
|
Either::Right(default.call(req))
|
||||||
} else {
|
} else {
|
||||||
Either::Left(ok(req.error_response(e)))
|
Either::Left(ok(req.error_response(e)))
|
||||||
@ -62,11 +57,9 @@ impl Service<ServiceRequest> for FilesService {
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = FilesServiceFuture;
|
type Future = FilesServiceFuture;
|
||||||
|
|
||||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
actix_service::always_ready!();
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
let is_method_valid = if let Some(guard) = &self.guards {
|
let is_method_valid = if let Some(guard) = &self.guards {
|
||||||
// execute user defined guards
|
// execute user defined guards
|
||||||
(**guard).check(req.head())
|
(**guard).check(req.head())
|
||||||
@ -78,7 +71,7 @@ impl Service<ServiceRequest> for FilesService {
|
|||||||
if !is_method_valid {
|
if !is_method_valid {
|
||||||
return Either::Left(ok(req.into_response(
|
return Either::Left(ok(req.into_response(
|
||||||
actix_web::HttpResponse::MethodNotAllowed()
|
actix_web::HttpResponse::MethodNotAllowed()
|
||||||
.header(header::CONTENT_TYPE, "text/plain")
|
.insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8))
|
||||||
.body("Request did not meet this resource's requirements."),
|
.body("Request did not meet this resource's requirements."),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
@ -102,7 +95,7 @@ impl Service<ServiceRequest> for FilesService {
|
|||||||
|
|
||||||
return Either::Left(ok(req.into_response(
|
return Either::Left(ok(req.into_response(
|
||||||
HttpResponse::Found()
|
HttpResponse::Found()
|
||||||
.header(header::LOCATION, redirect_to)
|
.insert_header((header::LOCATION, redirect_to))
|
||||||
.body("")
|
.body("")
|
||||||
.into_body(),
|
.into_body(),
|
||||||
)));
|
)));
|
||||||
@ -120,10 +113,8 @@ impl Service<ServiceRequest> for FilesService {
|
|||||||
named_file.flags = self.file_flags;
|
named_file.flags = self.file_flags;
|
||||||
|
|
||||||
let (req, _) = req.into_parts();
|
let (req, _) = req.into_parts();
|
||||||
Either::Left(ok(match named_file.into_response(&req) {
|
let res = named_file.into_response(&req);
|
||||||
Ok(item) => ServiceResponse::new(req, item),
|
Either::Left(ok(ServiceResponse::new(req, res)))
|
||||||
Err(e) => ServiceResponse::from_err(e, req),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
Err(e) => self.handle_err(e, req),
|
Err(e) => self.handle_err(e, req),
|
||||||
}
|
}
|
||||||
@ -154,12 +145,8 @@ impl Service<ServiceRequest> for FilesService {
|
|||||||
named_file.flags = self.file_flags;
|
named_file.flags = self.file_flags;
|
||||||
|
|
||||||
let (req, _) = req.into_parts();
|
let (req, _) = req.into_parts();
|
||||||
match named_file.into_response(&req) {
|
let res = named_file.into_response(&req);
|
||||||
Ok(item) => {
|
Either::Left(ok(ServiceResponse::new(req, res)))
|
||||||
Either::Left(ok(ServiceResponse::new(req.clone(), item)))
|
|
||||||
}
|
|
||||||
Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => self.handle_err(e, req),
|
Err(e) => self.handle_err(e, req),
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,10 @@ use actix_web::{
|
|||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_utf8_file_contents() {
|
async fn test_utf8_file_contents() {
|
||||||
// use default ISO-8859-1 encoding
|
// use default ISO-8859-1 encoding
|
||||||
let mut srv =
|
let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await;
|
||||||
test::init_service(App::new().service(Files::new("/", "./tests"))).await;
|
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/utf8.txt").to_request();
|
let req = TestRequest::with_uri("/utf8.txt").to_request();
|
||||||
let res = test::call_service(&mut srv, req).await;
|
let res = test::call_service(&srv, req).await;
|
||||||
|
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -24,13 +23,13 @@ async fn test_utf8_file_contents() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// prefer UTF-8 encoding
|
// prefer UTF-8 encoding
|
||||||
let mut srv = test::init_service(
|
let srv = test::init_service(
|
||||||
App::new().service(Files::new("/", "./tests").prefer_utf8(true)),
|
App::new().service(Files::new("/", "./tests").prefer_utf8(true)),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/utf8.txt").to_request();
|
let req = TestRequest::with_uri("/utf8.txt").to_request();
|
||||||
let res = test::call_service(&mut srv, req).await;
|
let res = test::call_service(&srv, req).await;
|
||||||
|
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.2 - 2021-02-10
|
||||||
|
* No notable changes.
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.1 - 2021-01-07
|
## 3.0.0-beta.1 - 2021-01-07
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-http-test"
|
name = "actix-http-test"
|
||||||
version = "3.0.0-beta.1"
|
version = "3.0.0-beta.2"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Various helpers for Actix applications to use during testing"
|
description = "Various helpers for Actix applications to use during testing"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -26,16 +26,16 @@ path = "src/lib.rs"
|
|||||||
default = []
|
default = []
|
||||||
|
|
||||||
# openssl
|
# openssl
|
||||||
openssl = ["open-ssl", "awc/openssl"]
|
openssl = ["tls-openssl", "awc/openssl"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "2.0.0-beta.2"
|
actix-service = "2.0.0-beta.4"
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-tls = "3.0.0-beta.2"
|
actix-tls = "3.0.0-beta.3"
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-utils = "3.0.0-beta.2"
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2"
|
||||||
actix-server = "2.0.0-beta.2"
|
actix-server = "2.0.0-beta.3"
|
||||||
awc = "3.0.0-beta.1"
|
awc = "3.0.0-beta.2"
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
@ -47,9 +47,9 @@ serde = "1.0"
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
time = { version = "0.2.7", default-features = false, features = ["std"] }
|
time = { version = "0.2.23", default-features = false, features = ["std"] }
|
||||||
open-ssl = { version = "0.10", package = "openssl", optional = true }
|
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-web = "4.0.0-beta.1"
|
actix-web = "4.0.0-beta.2"
|
||||||
actix-http = "3.0.0-beta.1"
|
actix-http = "3.0.0-beta.2"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
[](https://crates.io/crates/actix-http-test)
|
[](https://crates.io/crates/actix-http-test)
|
||||||
[](https://docs.rs/actix-http-test/2.1.0)
|
[](https://docs.rs/actix-http-test/2.1.0)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-http-test/2.1.0)
|
[](https://deps.rs/crate/actix-http-test/2.1.0)
|
||||||
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
#![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")]
|
||||||
|
|
||||||
|
#[cfg(feature = "openssl")]
|
||||||
|
extern crate tls_openssl as openssl;
|
||||||
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::{net, thread, time};
|
use std::{net, thread, time};
|
||||||
|
|
||||||
@ -60,7 +63,7 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
|||||||
|
|
||||||
// run server in separate thread
|
// run server in separate thread
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let sys = System::new("actix-test-server");
|
let sys = System::new();
|
||||||
let local_addr = tcp.local_addr().unwrap();
|
let local_addr = tcp.local_addr().unwrap();
|
||||||
|
|
||||||
let srv = Server::build()
|
let srv = Server::build()
|
||||||
@ -69,7 +72,7 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
|||||||
.disable_signals();
|
.disable_signals();
|
||||||
|
|
||||||
sys.block_on(async {
|
sys.block_on(async {
|
||||||
srv.start();
|
srv.run();
|
||||||
tx.send((System::current(), local_addr)).unwrap();
|
tx.send((System::current(), local_addr)).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -82,7 +85,7 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
|||||||
let connector = {
|
let connector = {
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
{
|
{
|
||||||
use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
|
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
|
||||||
|
|
||||||
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||||
builder.set_verify(SslVerifyMode::NONE);
|
builder.set_verify(SslVerifyMode::NONE);
|
||||||
@ -106,7 +109,6 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
|||||||
|
|
||||||
Client::builder().connector(connector).finish()
|
Client::builder().connector(connector).finish()
|
||||||
};
|
};
|
||||||
actix_tls::connect::start_default_resolver().await.unwrap();
|
|
||||||
|
|
||||||
TestServer {
|
TestServer {
|
||||||
addr,
|
addr,
|
||||||
|
@ -3,6 +3,57 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.2 - 2021-02-19
|
||||||
|
### Added
|
||||||
|
* `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
|
||||||
|
* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869]
|
||||||
|
* `ResponseBuilder::append_header` method which allows using typed headers. [#1869]
|
||||||
|
* `TestRequest::insert_header` method which allows using typed headers. [#1869]
|
||||||
|
* `ContentEncoding` implements all necessary header traits. [#1912]
|
||||||
|
* `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964]
|
||||||
|
* `HeaderMap::drain` as an efficient draining iterator. [#1964]
|
||||||
|
* Implement `IntoIterator` for owned `HeaderMap`. [#1964]
|
||||||
|
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed
|
||||||
|
`mime` types. [#1894]
|
||||||
|
* Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
|
||||||
|
`TryInto` trait. [#1894]
|
||||||
|
* `Extensions::insert` returns Option of replaced item. [#1904]
|
||||||
|
* Remove `HttpResponseBuilder::json2()`. [#1903]
|
||||||
|
* Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903]
|
||||||
|
* `client::error::ConnectError` Resolver variant contains `Box<dyn std::error::Error>` type. [#1905]
|
||||||
|
* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905]
|
||||||
|
* Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool
|
||||||
|
is dead. [#1957]
|
||||||
|
* `HeaderMap::len` now returns number of values instead of number of keys. [#1964]
|
||||||
|
* `HeaderMap::insert` now returns iterator of removed values. [#1964]
|
||||||
|
* `HeaderMap::remove` now returns iterator of removed values. [#1964]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869]
|
||||||
|
* `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869]
|
||||||
|
* `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869]
|
||||||
|
* `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
|
* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
|
* `actors` optional feature. [#1969]
|
||||||
|
* `ResponseError` impl for `actix::MailboxError`. [#1969]
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
* Vastly improve docs and add examples for `HeaderMap`. [#1964]
|
||||||
|
|
||||||
|
[#1869]: https://github.com/actix/actix-web/pull/1869
|
||||||
|
[#1894]: https://github.com/actix/actix-web/pull/1894
|
||||||
|
[#1903]: https://github.com/actix/actix-web/pull/1903
|
||||||
|
[#1904]: https://github.com/actix/actix-web/pull/1904
|
||||||
|
[#1905]: https://github.com/actix/actix-web/pull/1905
|
||||||
|
[#1912]: https://github.com/actix/actix-web/pull/1912
|
||||||
|
[#1957]: https://github.com/actix/actix-web/pull/1957
|
||||||
|
[#1964]: https://github.com/actix/actix-web/pull/1964
|
||||||
|
[#1969]: https://github.com/actix/actix-web/pull/1969
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.1 - 2021-01-07
|
## 3.0.0-beta.1 - 2021-01-07
|
||||||
### Added
|
### Added
|
||||||
* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`.
|
* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`.
|
||||||
@ -22,10 +73,14 @@
|
|||||||
* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`.
|
* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`.
|
||||||
due to the removal of this type from `tokio-openssl` crate. openssl handshake
|
due to the removal of this type from `tokio-openssl` crate. openssl handshake
|
||||||
error would return as `ConnectError::SslError`. [#1813]
|
error would return as `ConnectError::SslError`. [#1813]
|
||||||
|
* Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`.
|
||||||
|
Due to this change `actix_threadpool::BlockingError` type is moved into
|
||||||
|
`actix_http::error` module. [#1878]
|
||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
[#1857]: https://github.com/actix/actix-web/pull/1857
|
[#1857]: https://github.com/actix/actix-web/pull/1857
|
||||||
[#1864]: https://github.com/actix/actix-web/pull/1864
|
[#1864]: https://github.com/actix/actix-web/pull/1864
|
||||||
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
|
||||||
|
|
||||||
## 2.2.0 - 2020-11-25
|
## 2.2.0 - 2020-11-25
|
||||||
@ -72,15 +127,14 @@
|
|||||||
* Update actix-connect and actix-tls dependencies.
|
* Update actix-connect and actix-tls dependencies.
|
||||||
|
|
||||||
|
|
||||||
## [2.0.0-beta.3] - 2020-08-14
|
## 2.0.0-beta.3 - 2020-08-14
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626]
|
* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626]
|
||||||
|
|
||||||
[#1626]: https://github.com/actix/actix-web/pull/1626
|
[#1626]: https://github.com/actix/actix-web/pull/1626
|
||||||
|
|
||||||
|
|
||||||
## [2.0.0-beta.2] - 2020-07-21
|
## 2.0.0-beta.2 - 2020-07-21
|
||||||
### Fixed
|
### Fixed
|
||||||
* Potential UB in h1 decoder using uninitialized memory. [#1614]
|
* Potential UB in h1 decoder using uninitialized memory. [#1614]
|
||||||
|
|
||||||
@ -91,10 +145,8 @@
|
|||||||
[#1615]: https://github.com/actix/actix-web/pull/1615
|
[#1615]: https://github.com/actix/actix-web/pull/1615
|
||||||
|
|
||||||
|
|
||||||
## [2.0.0-beta.1] - 2020-07-11
|
## 2.0.0-beta.1 - 2020-07-11
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Migrate cookie handling to `cookie` crate. [#1558]
|
* Migrate cookie handling to `cookie` crate. [#1558]
|
||||||
* Update `sha-1` to 0.9. [#1586]
|
* Update `sha-1` to 0.9. [#1586]
|
||||||
* Fix leak in client pool. [#1580]
|
* Fix leak in client pool. [#1580]
|
||||||
@ -104,33 +156,30 @@
|
|||||||
[#1586]: https://github.com/actix/actix-web/pull/1586
|
[#1586]: https://github.com/actix/actix-web/pull/1586
|
||||||
[#1580]: https://github.com/actix/actix-web/pull/1580
|
[#1580]: https://github.com/actix/actix-web/pull/1580
|
||||||
|
|
||||||
## [2.0.0-alpha.4] - 2020-05-21
|
|
||||||
|
|
||||||
|
## 2.0.0-alpha.4 - 2020-05-21
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Bump minimum supported Rust version to 1.40
|
* Bump minimum supported Rust version to 1.40
|
||||||
* content_length function is removed, and you can set Content-Length by calling no_chunking function [#1439]
|
* content_length function is removed, and you can set Content-Length by calling
|
||||||
|
no_chunking function [#1439]
|
||||||
* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
|
* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
|
||||||
`u64` instead of a `usize`.
|
`u64` instead of a `usize`.
|
||||||
* Update `base64` dependency to 0.12
|
* Update `base64` dependency to 0.12
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Support parsing of `SameSite=None` [#1503]
|
* Support parsing of `SameSite=None` [#1503]
|
||||||
|
|
||||||
[#1439]: https://github.com/actix/actix-web/pull/1439
|
[#1439]: https://github.com/actix/actix-web/pull/1439
|
||||||
[#1503]: https://github.com/actix/actix-web/pull/1503
|
[#1503]: https://github.com/actix/actix-web/pull/1503
|
||||||
|
|
||||||
## [2.0.0-alpha.3] - 2020-05-08
|
|
||||||
|
|
||||||
|
## 2.0.0-alpha.3 - 2020-05-08
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Correct spelling of ConnectError::Unresolved [#1487]
|
* Correct spelling of ConnectError::Unresolved [#1487]
|
||||||
* Fix a mistake in the encoding of websocket continuation messages wherein
|
* Fix a mistake in the encoding of websocket continuation messages wherein
|
||||||
Item::FirstText and Item::FirstBinary are each encoded as the other.
|
Item::FirstText and Item::FirstBinary are each encoded as the other.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Implement `std::error::Error` for our custom errors [#1422]
|
* Implement `std::error::Error` for our custom errors [#1422]
|
||||||
* Remove `failure` support for `ResponseError` since that crate
|
* Remove `failure` support for `ResponseError` since that crate
|
||||||
will be deprecated in the near future.
|
will be deprecated in the near future.
|
||||||
@ -138,338 +187,247 @@
|
|||||||
[#1422]: https://github.com/actix/actix-web/pull/1422
|
[#1422]: https://github.com/actix/actix-web/pull/1422
|
||||||
[#1487]: https://github.com/actix/actix-web/pull/1487
|
[#1487]: https://github.com/actix/actix-web/pull/1487
|
||||||
|
|
||||||
## [2.0.0-alpha.2] - 2020-03-07
|
|
||||||
|
|
||||||
|
## 2.0.0-alpha.2 - 2020-03-07
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395]
|
* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395]
|
||||||
|
* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB
|
||||||
* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB respectively
|
respectively to improve download speed for awc when downloading large objects. [#1394]
|
||||||
to improve download speed for awc when downloading large objects. [#1394]
|
* client::Connector accepts initial_window_size and initial_connection_window_size
|
||||||
|
HTTP2 configuration. [#1394]
|
||||||
* client::Connector accepts initial_window_size and initial_connection_window_size HTTP2 configuration. [#1394]
|
|
||||||
|
|
||||||
* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394]
|
* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394]
|
||||||
|
|
||||||
[#1394]: https://github.com/actix/actix-web/pull/1394
|
[#1394]: https://github.com/actix/actix-web/pull/1394
|
||||||
[#1395]: https://github.com/actix/actix-web/pull/1395
|
[#1395]: https://github.com/actix/actix-web/pull/1395
|
||||||
|
|
||||||
## [2.0.0-alpha.1] - 2020-02-27
|
|
||||||
|
|
||||||
|
## 2.0.0-alpha.1 - 2020-02-27
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Update the `time` dependency to 0.2.7.
|
* Update the `time` dependency to 0.2.7.
|
||||||
* Moved actors messages support from actix crate, enabled with feature `actors`.
|
* Moved actors messages support from actix crate, enabled with feature `actors`.
|
||||||
* Breaking change: trait MessageBody requires Unpin and accepting Pin<&mut Self> instead of &mut self in the poll_next().
|
* Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of
|
||||||
|
`&mut self` in the poll_next().
|
||||||
* MessageBody is not implemented for &'static [u8] anymore.
|
* MessageBody is not implemented for &'static [u8] anymore.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Allow `SameSite=None` cookies to be sent in a response.
|
* Allow `SameSite=None` cookies to be sent in a response.
|
||||||
|
|
||||||
## [1.0.1] - 2019-12-20
|
|
||||||
|
|
||||||
|
## 1.0.1 - 2019-12-20
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Poll upgrade service's readiness from HTTP service handlers
|
* Poll upgrade service's readiness from HTTP service handlers
|
||||||
|
|
||||||
* Replace brotli with brotli2 #1224
|
* Replace brotli with brotli2 #1224
|
||||||
|
|
||||||
## [1.0.0] - 2019-12-13
|
|
||||||
|
|
||||||
|
## 1.0.0 - 2019-12-13
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Add websockets continuation frame support
|
* Add websockets continuation frame support
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Replace `flate2-xxx` features with `compress`
|
* Replace `flate2-xxx` features with `compress`
|
||||||
|
|
||||||
## [1.0.0-alpha.5] - 2019-12-09
|
|
||||||
|
|
||||||
|
## 1.0.0-alpha.5 - 2019-12-09
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Check `Upgrade` service readiness before calling it
|
* Check `Upgrade` service readiness before calling it
|
||||||
|
* Fix buffer remaining capacity calculation
|
||||||
* Fix buffer remaining capacity calcualtion
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Websockets: Ping and Pong should have binary data #1049
|
* Websockets: Ping and Pong should have binary data #1049
|
||||||
|
|
||||||
## [1.0.0-alpha.4] - 2019-12-08
|
|
||||||
|
|
||||||
|
## 1.0.0-alpha.4 - 2019-12-08
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Add impl ResponseBuilder for Error
|
* Add impl ResponseBuilder for Error
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Use rust based brotli compression library
|
* Use rust based brotli compression library
|
||||||
|
|
||||||
## [1.0.0-alpha.3] - 2019-12-07
|
## 1.0.0-alpha.3 - 2019-12-07
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Migrate to tokio 0.2
|
* Migrate to tokio 0.2
|
||||||
|
|
||||||
* Migrate to `std::future`
|
* Migrate to `std::future`
|
||||||
|
|
||||||
|
|
||||||
## [0.2.11] - 2019-11-06
|
## 0.2.11 - 2019-11-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()
|
* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()
|
||||||
|
* Add an additional `filename*` param in the `Content-Disposition` header of
|
||||||
* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151)
|
`actix_files::NamedFile` to be more compatible. (#1151)
|
||||||
|
|
||||||
* Allow to use `std::convert::Infallible` as `actix_http::error::Error`
|
* Allow to use `std::convert::Infallible` as `actix_http::error::Error`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain;
|
||||||
|
charset=utf-8` header [#1118]
|
||||||
|
|
||||||
* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header #1118
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
|
||||||
|
|
||||||
## [0.2.10] - 2019-09-11
|
## 0.2.10 - 2019-09-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
* Add support for sending HTTP requests with `Rc<RequestHead>` in addition to sending HTTP requests
|
||||||
* Add support for sending HTTP requests with `Rc<RequestHead>` in addition to sending HTTP requests with `RequestHead`
|
with `RequestHead`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* h2 will use error response #1080
|
* h2 will use error response #1080
|
||||||
|
|
||||||
* on_connect result isn't added to request extensions for http2 requests #1009
|
* on_connect result isn't added to request extensions for http2 requests #1009
|
||||||
|
|
||||||
|
|
||||||
## [0.2.9] - 2019-08-13
|
## 0.2.9 - 2019-08-13
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation
|
* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation
|
||||||
|
|
||||||
* Update percent-encoding to 2.1
|
* Update percent-encoding to 2.1
|
||||||
|
|
||||||
* Update serde_urlencoded to 0.6.1
|
* Update serde_urlencoded to 0.6.1
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031)
|
* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031)
|
||||||
|
|
||||||
|
|
||||||
## [0.2.8] - 2019-08-01
|
## 0.2.8 - 2019-08-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Add `rustls` support
|
* Add `rustls` support
|
||||||
|
|
||||||
* Add `Clone` impl for `HeaderMap`
|
* Add `Clone` impl for `HeaderMap`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* awc client panic #1016
|
* awc client panic #1016
|
||||||
|
* Invalid response with compression middleware enabled, but compression-related features
|
||||||
* Invalid response with compression middleware enabled, but compression-related features disabled #997
|
disabled #997
|
||||||
|
|
||||||
|
|
||||||
## [0.2.7] - 2019-07-18
|
## 0.2.7 - 2019-07-18
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Add support for downcasting response errors #986
|
* Add support for downcasting response errors #986
|
||||||
|
|
||||||
|
|
||||||
## [0.2.6] - 2019-07-17
|
## 0.2.6 - 2019-07-17
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Replace `ClonableService` with local copy
|
* Replace `ClonableService` with local copy
|
||||||
|
|
||||||
* Upgrade `rand` dependency version to 0.7
|
* Upgrade `rand` dependency version to 0.7
|
||||||
|
|
||||||
|
|
||||||
## [0.2.5] - 2019-06-28
|
## 0.2.5 - 2019-06-28
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946
|
* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Use `encoding_rs` crate instead of unmaintained `encoding` crate
|
* Use `encoding_rs` crate instead of unmaintained `encoding` crate
|
||||||
|
|
||||||
* Add `Copy` and `Clone` impls for `ws::Codec`
|
* Add `Copy` and `Clone` impls for `ws::Codec`
|
||||||
|
|
||||||
|
|
||||||
## [0.2.4] - 2019-06-16
|
## 0.2.4 - 2019-06-16
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Do not compress NoContent (204) responses #918
|
* Do not compress NoContent (204) responses #918
|
||||||
|
|
||||||
|
|
||||||
## [0.2.3] - 2019-06-02
|
## 0.2.3 - 2019-06-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Debug impl for ResponseBuilder
|
* Debug impl for ResponseBuilder
|
||||||
|
|
||||||
* From SizedStream and BodyStream for Body
|
* From SizedStream and BodyStream for Body
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* SizedStream uses u64
|
* SizedStream uses u64
|
||||||
|
|
||||||
|
|
||||||
## [0.2.2] - 2019-05-29
|
## 0.2.2 - 2019-05-29
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Parse incoming stream before closing stream on disconnect #868
|
* Parse incoming stream before closing stream on disconnect #868
|
||||||
|
|
||||||
|
|
||||||
## [0.2.1] - 2019-05-25
|
## 0.2.1 - 2019-05-25
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Handle socket read disconnect
|
* Handle socket read disconnect
|
||||||
|
|
||||||
|
|
||||||
## [0.2.0] - 2019-05-12
|
## 0.2.0 - 2019-05-12
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Update actix-service to 0.4
|
* Update actix-service to 0.4
|
||||||
|
|
||||||
* Expect and upgrade services accept `ServerConfig` config.
|
* Expect and upgrade services accept `ServerConfig` config.
|
||||||
|
|
||||||
### Deleted
|
### Deleted
|
||||||
|
|
||||||
* `OneRequest` service
|
* `OneRequest` service
|
||||||
|
|
||||||
|
|
||||||
## [0.1.5] - 2019-05-04
|
## 0.1.5 - 2019-05-04
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Clean up response extensions in response pool #817
|
* Clean up response extensions in response pool #817
|
||||||
|
|
||||||
|
|
||||||
## [0.1.4] - 2019-04-24
|
## 0.1.4 - 2019-04-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Allow to render h1 request headers in `Camel-Case`
|
* Allow to render h1 request headers in `Camel-Case`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Read until eof for http/1.0 responses #771
|
* Read until eof for http/1.0 responses #771
|
||||||
|
|
||||||
|
|
||||||
## [0.1.3] - 2019-04-23
|
## 0.1.3 - 2019-04-23
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Fix http client pool management
|
* Fix http client pool management
|
||||||
|
|
||||||
* Fix http client wait queue management #794
|
* Fix http client wait queue management #794
|
||||||
|
|
||||||
|
|
||||||
## [0.1.2] - 2019-04-23
|
## 0.1.2 - 2019-04-23
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Fix BorrowMutError panic in client connector #793
|
* Fix BorrowMutError panic in client connector #793
|
||||||
|
|
||||||
|
|
||||||
## [0.1.1] - 2019-04-19
|
## 0.1.1 - 2019-04-19
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Cookie::max_age() accepts value in seconds
|
* Cookie::max_age() accepts value in seconds
|
||||||
|
|
||||||
* Cookie::max_age_time() accepts value in time::Duration
|
* Cookie::max_age_time() accepts value in time::Duration
|
||||||
|
|
||||||
* Allow to specify server address for client connector
|
* Allow to specify server address for client connector
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0] - 2019-04-16
|
## 0.1.0 - 2019-04-16
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr`
|
* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr`
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* `actix_http::encoding` always available
|
* `actix_http::encoding` always available
|
||||||
|
|
||||||
* use trust-dns-resolver 0.11.0
|
* use trust-dns-resolver 0.11.0
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.5] - 2019-04-12
|
## 0.1.0-alpha.5 - 2019-04-12
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Allow to use custom service for upgrade requests
|
* Allow to use custom service for upgrade requests
|
||||||
|
|
||||||
* Added `h1::SendResponse` future.
|
* Added `h1::SendResponse` future.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* MessageBody::length() renamed to MessageBody::size() for consistency
|
* MessageBody::length() renamed to MessageBody::size() for consistency
|
||||||
|
|
||||||
* ws handshake verification functions take RequestHead instead of Request
|
* ws handshake verification functions take RequestHead instead of Request
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.4] - 2019-04-08
|
## 0.1.0-alpha.4 - 2019-04-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Allow to use custom `Expect` handler
|
* Allow to use custom `Expect` handler
|
||||||
|
|
||||||
* Add minimal `std::error::Error` impl for `Error`
|
* Add minimal `std::error::Error` impl for `Error`
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Export IntoHeaderValue
|
* Export IntoHeaderValue
|
||||||
|
|
||||||
* Render error and return as response body
|
* Render error and return as response body
|
||||||
|
* Use thread pool for response body compression
|
||||||
* Use thread pool for response body comression
|
|
||||||
|
|
||||||
### Deleted
|
### Deleted
|
||||||
|
|
||||||
* Removed PayloadBuffer
|
* Removed PayloadBuffer
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.3] - 2019-04-02
|
## 0.1.0-alpha.3 - 2019-04-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Warn when an unsealed private cookie isn't valid UTF-8
|
* Warn when an unsealed private cookie isn't valid UTF-8
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Rust 1.31.0 compatibility
|
* Rust 1.31.0 compatibility
|
||||||
|
|
||||||
* Preallocate read buffer for h1 codec
|
* Preallocate read buffer for h1 codec
|
||||||
|
|
||||||
* Detect socket disconnection during protocol selection
|
* Detect socket disconnection during protocol selection
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.2] - 2019-03-29
|
## 0.1.0-alpha.2 - 2019-03-29
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Added ws::Message::Nop, no-op websockets message
|
* Added ws::Message::Nop, no-op websockets message
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
* Do not use thread pool for decompression if chunk size is smaller than 2048.
|
||||||
* Do not use thread pool for decomression if chunk size is smaller than 2048.
|
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.1] - 2019-03-28
|
## 0.1.0-alpha.1 - 2019-03-28
|
||||||
|
|
||||||
* Initial impl
|
* Initial impl
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "3.0.0-beta.1"
|
version = "3.0.0-beta.2"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "HTTP primitives for the Actix ecosystem"
|
description = "HTTP primitives for the Actix ecosystem"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -36,31 +36,27 @@ compress = ["flate2", "brotli2"]
|
|||||||
# support for secure cookies
|
# support for secure cookies
|
||||||
secure-cookies = ["cookie/secure"]
|
secure-cookies = ["cookie/secure"]
|
||||||
|
|
||||||
# support for actix Actor messages
|
# trust-dns as client dns resolver
|
||||||
actors = ["actix"]
|
trust-dns = ["trust-dns-resolver"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "2.0.0-beta.2"
|
actix-service = "2.0.0-beta.4"
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-utils = "3.0.0-beta.2"
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2"
|
||||||
actix-threadpool = "0.3.1"
|
|
||||||
actix-tls = "3.0.0-beta.2"
|
actix-tls = "3.0.0-beta.2"
|
||||||
actix = { version = "0.11.0-beta.1", optional = true }
|
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bitflags = "1.2"
|
bitflags = "1.2"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
cookie = { version = "0.14.1", features = ["percent-encode"] }
|
cookie = { version = "0.14.1", features = ["percent-encode"] }
|
||||||
copyless = "0.1.4"
|
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
either = "1.5.3"
|
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
futures-channel = { version = "0.3.7", default-features = false }
|
futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
|
||||||
fxhash = "0.2.1"
|
ahash = "0.7"
|
||||||
h2 = "0.3.0"
|
h2 = "0.3.0"
|
||||||
http = "0.2.2"
|
http = "0.2.2"
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
@ -77,23 +73,26 @@ 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"
|
||||||
|
smallvec = "1.6"
|
||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
time = { version = "0.2.7", default-features = false, features = ["std"] }
|
time = { version = "0.2.23", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
# compression
|
# compression
|
||||||
brotli2 = { version="0.3.2", optional = true }
|
brotli2 = { version="0.3.2", optional = true }
|
||||||
flate2 = { version = "1.0.13", optional = true }
|
flate2 = { version = "1.0.13", optional = true }
|
||||||
|
|
||||||
|
trust-dns-resolver = { version = "0.20.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-server = "2.0.0-beta.2"
|
actix-server = "2.0.0-beta.3"
|
||||||
actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] }
|
||||||
actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] }
|
actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] }
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
env_logger = "0.7"
|
env_logger = "0.8"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
open-ssl = { version="0.10", package = "openssl" }
|
tls-openssl = { version = "0.10", package = "openssl" }
|
||||||
rust-tls = { version="0.19", package = "rustls" }
|
tls-rustls = { version = "0.19", package = "rustls" }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "write-camel-case"
|
name = "write-camel-case"
|
||||||
|
@ -3,10 +3,13 @@
|
|||||||
> HTTP primitives for the Actix ecosystem.
|
> HTTP primitives for the Actix ecosystem.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http)
|
[](https://crates.io/crates/actix-http)
|
||||||
[](https://docs.rs/actix-http/2.2.0)
|
[](https://docs.rs/actix-http/3.0.0-beta.2)
|
||||||

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

|
||||||
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
<br />
|
||||||
|
[](https://deps.rs/crate/actix-http/3.0.0-beta.2)
|
||||||
|
[](https://crates.io/crates/actix-http)
|
||||||
|
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
||||||
|
@ -26,7 +26,10 @@ async fn main() -> io::Result<()> {
|
|||||||
info!("request body: {:?}", body);
|
info!("request body: {:?}", body);
|
||||||
Ok::<_, Error>(
|
Ok::<_, Error>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header("x-head", HeaderValue::from_static("dummy value!"))
|
.insert_header((
|
||||||
|
"x-head",
|
||||||
|
HeaderValue::from_static("dummy value!"),
|
||||||
|
))
|
||||||
.body(body),
|
.body(body),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -15,7 +15,7 @@ async fn handle_request(mut req: Request) -> Result<Response, Error> {
|
|||||||
|
|
||||||
info!("request body: {:?}", body);
|
info!("request body: {:?}", body);
|
||||||
Ok(Response::Ok()
|
Ok(Response::Ok()
|
||||||
.header("x-head", HeaderValue::from_static("dummy value!"))
|
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
|
||||||
.body(body))
|
.body(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,10 @@ async fn main() -> io::Result<()> {
|
|||||||
.finish(|_req| {
|
.finish(|_req| {
|
||||||
info!("{:?}", _req);
|
info!("{:?}", _req);
|
||||||
let mut res = Response::Ok();
|
let mut res = Response::Ok();
|
||||||
res.header("x-head", HeaderValue::from_static("dummy value!"));
|
res.insert_header((
|
||||||
|
"x-head",
|
||||||
|
HeaderValue::from_static("dummy value!"),
|
||||||
|
));
|
||||||
future::ok::<_, ()>(res.body("Hello world!"))
|
future::ok::<_, ()>(res.body("Hello world!"))
|
||||||
})
|
})
|
||||||
.tcp()
|
.tcp()
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
// These values are taken from hyper/src/proto/h2/client.rs
|
const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2MB
|
||||||
const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2mb
|
const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1MB
|
||||||
const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1mb
|
|
||||||
|
|
||||||
/// Connector configuration
|
/// Connector configuration
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -19,7 +18,7 @@ pub(crate) struct ConnectorConfig {
|
|||||||
impl Default for ConnectorConfig {
|
impl Default for ConnectorConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
timeout: Duration::from_secs(1),
|
timeout: Duration::from_secs(5),
|
||||||
conn_lifetime: Duration::from_secs(75),
|
conn_lifetime: Duration::from_secs(75),
|
||||||
conn_keep_alive: Duration::from_secs(15),
|
conn_keep_alive: Duration::from_secs(15),
|
||||||
disconnect_timeout: Some(Duration::from_millis(3000)),
|
disconnect_timeout: Some(Duration::from_millis(3000)),
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::{fmt, io, time};
|
use std::{fmt, io, time};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf};
|
||||||
|
use actix_rt::task::JoinHandle;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::future::{err, Either, FutureExt, LocalBoxFuture, Ready};
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
use futures_util::future::{err, Either, FutureExt, Ready};
|
||||||
use h2::client::SendRequest;
|
use h2::client::SendRequest;
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
@ -20,7 +23,53 @@ use super::{h1proto, h2proto};
|
|||||||
|
|
||||||
pub(crate) enum ConnectionType<Io> {
|
pub(crate) enum ConnectionType<Io> {
|
||||||
H1(Io),
|
H1(Io),
|
||||||
H2(SendRequest<Bytes>),
|
H2(H2Connection),
|
||||||
|
}
|
||||||
|
|
||||||
|
// h2 connection has two parts: SendRequest and Connection.
|
||||||
|
// Connection is spawned as async task on runtime and H2Connection would hold a handle for
|
||||||
|
// this task. So it can wake up and quit the task when SendRequest is dropped.
|
||||||
|
pub(crate) struct H2Connection {
|
||||||
|
handle: JoinHandle<()>,
|
||||||
|
sender: SendRequest<Bytes>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl H2Connection {
|
||||||
|
pub(crate) fn new<Io>(
|
||||||
|
sender: SendRequest<Bytes>,
|
||||||
|
connection: h2::client::Connection<Io>,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
|
{
|
||||||
|
let handle = actix_rt::spawn(async move {
|
||||||
|
let _ = connection.await;
|
||||||
|
});
|
||||||
|
|
||||||
|
Self { handle, sender }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wake up waker when drop
|
||||||
|
impl Drop for H2Connection {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.handle.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only expose sender type to public.
|
||||||
|
impl Deref for H2Connection {
|
||||||
|
type Target = SendRequest<Bytes>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.sender
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for H2Connection {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.sender
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Connection {
|
pub trait Connection {
|
||||||
@ -265,3 +314,35 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::net;
|
||||||
|
|
||||||
|
use actix_rt::net::TcpStream;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_h2_connection_drop() {
|
||||||
|
let addr = "127.0.0.1:0".parse::<net::SocketAddr>().unwrap();
|
||||||
|
let listener = net::TcpListener::bind(addr).unwrap();
|
||||||
|
let local = listener.local_addr().unwrap();
|
||||||
|
|
||||||
|
std::thread::spawn(move || while listener.accept().is_ok() {});
|
||||||
|
|
||||||
|
let tcp = TcpStream::connect(local).await.unwrap();
|
||||||
|
let (sender, connection) = h2::client::handshake(tcp).await.unwrap();
|
||||||
|
let conn = H2Connection::new(sender.clone(), connection);
|
||||||
|
|
||||||
|
assert!(sender.clone().ready().await.is_ok());
|
||||||
|
assert!(h2::client::SendRequest::clone(&*conn).ready().await.is_ok());
|
||||||
|
|
||||||
|
drop(conn);
|
||||||
|
|
||||||
|
match sender.ready().await {
|
||||||
|
Ok(_) => panic!("connection should be gone and can not be ready"),
|
||||||
|
Err(e) => assert!(e.is_io()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,7 +6,7 @@ use actix_codec::{AsyncRead, AsyncWrite};
|
|||||||
use actix_rt::net::TcpStream;
|
use actix_rt::net::TcpStream;
|
||||||
use actix_service::{apply_fn, Service, ServiceExt};
|
use actix_service::{apply_fn, Service, ServiceExt};
|
||||||
use actix_tls::connect::{
|
use actix_tls::connect::{
|
||||||
default_connector, Connect as TcpConnect, Connection as TcpConnection,
|
new_connector, Connect as TcpConnect, Connection as TcpConnection, Resolver,
|
||||||
};
|
};
|
||||||
use actix_utils::timeout::{TimeoutError, TimeoutService};
|
use actix_utils::timeout::{TimeoutError, TimeoutService};
|
||||||
use http::Uri;
|
use http::Uri;
|
||||||
@ -19,7 +19,6 @@ use super::Connect;
|
|||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector;
|
use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector;
|
||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
use actix_tls::connect::ssl::rustls::ClientConfig;
|
use actix_tls::connect::ssl::rustls::ClientConfig;
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
@ -70,7 +69,7 @@ impl Connector<(), ()> {
|
|||||||
> {
|
> {
|
||||||
Connector {
|
Connector {
|
||||||
ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
|
ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
|
||||||
connector: default_connector(),
|
connector: new_connector(resolver::resolver()),
|
||||||
config: ConnectorConfig::default(),
|
config: ConnectorConfig::default(),
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
@ -100,9 +99,9 @@ impl Connector<(), ()> {
|
|||||||
fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector {
|
fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector {
|
||||||
let mut config = ClientConfig::new();
|
let mut config = ClientConfig::new();
|
||||||
config.set_protocols(&protocols);
|
config.set_protocols(&protocols);
|
||||||
config
|
config.root_store.add_server_trust_anchors(
|
||||||
.root_store
|
&actix_tls::connect::ssl::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))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,11 +391,11 @@ mod connect_impl {
|
|||||||
Ready<Result<IoConnection<Io>, ConnectError>>,
|
Ready<Result<IoConnection<Io>, ConnectError>>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
self.tcp_pool.poll_ready(cx)
|
self.tcp_pool.poll_ready(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: Connect) -> Self::Future {
|
fn call(&self, req: Connect) -> Self::Future {
|
||||||
match req.uri.scheme_str() {
|
match req.uri.scheme_str() {
|
||||||
Some("https") | Some("wss") => {
|
Some("https") | Some("wss") => {
|
||||||
Either::Right(err(ConnectError::SslIsNotSupported))
|
Either::Right(err(ConnectError::SslIsNotSupported))
|
||||||
@ -460,11 +459,11 @@ mod connect_impl {
|
|||||||
InnerConnectorResponseB<T2, Io1, Io2>,
|
InnerConnectorResponseB<T2, Io1, Io2>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
self.tcp_pool.poll_ready(cx)
|
self.tcp_pool.poll_ready(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: Connect) -> Self::Future {
|
fn call(&self, req: Connect) -> Self::Future {
|
||||||
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),
|
||||||
@ -532,3 +531,82 @@ mod connect_impl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "trust-dns"))]
|
||||||
|
mod resolver {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) fn resolver() -> Resolver {
|
||||||
|
Resolver::Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "trust-dns")]
|
||||||
|
mod resolver {
|
||||||
|
use std::{cell::RefCell, net::SocketAddr};
|
||||||
|
|
||||||
|
use actix_tls::connect::Resolve;
|
||||||
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
use trust_dns_resolver::{
|
||||||
|
config::{ResolverConfig, ResolverOpts},
|
||||||
|
system_conf::read_system_conf,
|
||||||
|
TokioAsyncResolver,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) fn resolver() -> Resolver {
|
||||||
|
// new type for impl Resolve trait for TokioAsyncResolver.
|
||||||
|
struct TrustDnsResolver(TokioAsyncResolver);
|
||||||
|
|
||||||
|
impl Resolve for TrustDnsResolver {
|
||||||
|
fn lookup<'a>(
|
||||||
|
&'a self,
|
||||||
|
host: &'a str,
|
||||||
|
port: u16,
|
||||||
|
) -> LocalBoxFuture<'a, Result<Vec<SocketAddr>, Box<dyn std::error::Error>>>
|
||||||
|
{
|
||||||
|
Box::pin(async move {
|
||||||
|
let res = self
|
||||||
|
.0
|
||||||
|
.lookup_ip(host)
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|ip| SocketAddr::new(ip, port))
|
||||||
|
.collect();
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dns struct is cached in thread local.
|
||||||
|
// so new client constructor can reuse the existing dns resolver.
|
||||||
|
thread_local! {
|
||||||
|
static TRUST_DNS_RESOLVER: RefCell<Option<Resolver>> = RefCell::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get from thread local or construct a new trust-dns resolver.
|
||||||
|
TRUST_DNS_RESOLVER.with(|local| {
|
||||||
|
let resolver = local.borrow().as_ref().map(Clone::clone);
|
||||||
|
match resolver {
|
||||||
|
Some(resolver) => resolver,
|
||||||
|
None => {
|
||||||
|
let (cfg, opts) = match read_system_conf() {
|
||||||
|
Ok((cfg, opts)) => (cfg, opts),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("TRust-DNS can not load system config: {}", e);
|
||||||
|
(ResolverConfig::default(), ResolverOpts::default())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let resolver = TokioAsyncResolver::tokio(cfg, opts).unwrap();
|
||||||
|
|
||||||
|
// box trust dns resolver and put it in thread local.
|
||||||
|
let resolver = Resolver::new_custom(TrustDnsResolver(resolver));
|
||||||
|
*local.borrow_mut() = Some(resolver.clone());
|
||||||
|
resolver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use actix_tls::connect::resolver::ResolveError;
|
|
||||||
use derive_more::{Display, From};
|
use derive_more::{Display, From};
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
@ -23,7 +22,7 @@ pub enum ConnectError {
|
|||||||
|
|
||||||
/// 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(Box<dyn std::error::Error>),
|
||||||
|
|
||||||
/// No dns records
|
/// No dns records
|
||||||
#[display(fmt = "No dns records found for the input")]
|
#[display(fmt = "No dns records found for the input")]
|
||||||
|
@ -45,14 +45,14 @@ where
|
|||||||
Some(port) => write!(wrt, "{}:{}", host, port),
|
Some(port) => write!(wrt, "{}:{}", host, port),
|
||||||
};
|
};
|
||||||
|
|
||||||
match wrt.get_mut().split().freeze().try_into() {
|
match wrt.get_mut().split().freeze().try_into_value() {
|
||||||
Ok(value) => match head {
|
Ok(value) => match head {
|
||||||
RequestHeadType::Owned(ref mut head) => {
|
RequestHeadType::Owned(ref mut head) => {
|
||||||
head.headers.insert(HOST, value)
|
head.headers.insert(HOST, value);
|
||||||
}
|
}
|
||||||
RequestHeadType::Rc(_, ref mut extra_headers) => {
|
RequestHeadType::Rc(_, ref mut extra_headers) => {
|
||||||
let headers = extra_headers.get_or_insert(HeaderMap::new());
|
let headers = extra_headers.get_or_insert(HeaderMap::new());
|
||||||
headers.insert(HOST, value)
|
headers.insert(HOST, value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => log::error!("Can not set HOST header {}", e),
|
Err(e) => log::error!("Can not set HOST header {}", e),
|
||||||
|
@ -22,9 +22,10 @@ use super::config::ConnectorConfig;
|
|||||||
use super::connection::{ConnectionType, IoConnection};
|
use super::connection::{ConnectionType, IoConnection};
|
||||||
use super::error::SendRequestError;
|
use super::error::SendRequestError;
|
||||||
use super::pool::Acquired;
|
use super::pool::Acquired;
|
||||||
|
use crate::client::connection::H2Connection;
|
||||||
|
|
||||||
pub(crate) async fn send_request<T, B>(
|
pub(crate) async fn send_request<T, B>(
|
||||||
mut io: SendRequest<Bytes>,
|
mut io: H2Connection,
|
||||||
head: RequestHeadType,
|
head: RequestHeadType,
|
||||||
body: B,
|
body: B,
|
||||||
created: time::Instant,
|
created: time::Instant,
|
||||||
@ -171,9 +172,9 @@ async fn send_body<B: MessageBody>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// release SendRequest object
|
/// release SendRequest object
|
||||||
fn release<T: AsyncRead + AsyncWrite + Unpin + 'static>(
|
fn release<T: AsyncRead + AsyncWrite + Unpin + 'static>(
|
||||||
io: SendRequest<Bytes>,
|
io: H2Connection,
|
||||||
pool: Option<Acquired<T>>,
|
pool: Option<Acquired<T>>,
|
||||||
created: time::Instant,
|
created: time::Instant,
|
||||||
close: bool,
|
close: bool,
|
||||||
|
@ -10,10 +10,11 @@ use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
|||||||
use actix_rt::time::{sleep, Sleep};
|
use actix_rt::time::{sleep, Sleep};
|
||||||
use actix_service::Service;
|
use actix_service::Service;
|
||||||
use actix_utils::task::LocalWaker;
|
use actix_utils::task::LocalWaker;
|
||||||
|
use ahash::AHashMap;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_channel::oneshot;
|
use futures_channel::oneshot;
|
||||||
use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture};
|
use futures_core::future::LocalBoxFuture;
|
||||||
use fxhash::FxHashMap;
|
use futures_util::future::{poll_fn, FutureExt};
|
||||||
use h2::client::{Connection, SendRequest};
|
use h2::client::{Connection, SendRequest};
|
||||||
use http::uri::Authority;
|
use http::uri::Authority;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
@ -25,6 +26,7 @@ use super::connection::{ConnectionType, IoConnection};
|
|||||||
use super::error::ConnectError;
|
use super::error::ConnectError;
|
||||||
use super::h2proto::handshake;
|
use super::h2proto::handshake;
|
||||||
use super::Connect;
|
use super::Connect;
|
||||||
|
use crate::client::connection::H2Connection;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
/// Protocol version
|
/// Protocol version
|
||||||
@ -45,7 +47,7 @@ impl From<Authority> for Key {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Connections pool
|
/// Connections pool
|
||||||
pub(crate) struct ConnectionPool<T, Io: 'static>(Rc<RefCell<T>>, Rc<RefCell<Inner<Io>>>);
|
pub(crate) struct ConnectionPool<T, Io: 'static>(Rc<T>, Rc<RefCell<Inner<Io>>>);
|
||||||
|
|
||||||
impl<T, Io> ConnectionPool<T, Io>
|
impl<T, Io> ConnectionPool<T, Io>
|
||||||
where
|
where
|
||||||
@ -53,13 +55,13 @@ where
|
|||||||
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
|
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self {
|
pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self {
|
||||||
let connector_rc = Rc::new(RefCell::new(connector));
|
let connector_rc = Rc::new(connector);
|
||||||
let inner_rc = Rc::new(RefCell::new(Inner {
|
let inner_rc = Rc::new(RefCell::new(Inner {
|
||||||
config,
|
config,
|
||||||
acquired: 0,
|
acquired: 0,
|
||||||
waiters: Slab::new(),
|
waiters: Slab::new(),
|
||||||
waiters_queue: IndexSet::new(),
|
waiters_queue: IndexSet::new(),
|
||||||
available: FxHashMap::default(),
|
available: AHashMap::default(),
|
||||||
waker: LocalWaker::new(),
|
waker: LocalWaker::new(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -98,12 +100,12 @@ where
|
|||||||
type Error = ConnectError;
|
type Error = ConnectError;
|
||||||
type Future = LocalBoxFuture<'static, Result<IoConnection<Io>, ConnectError>>;
|
type Future = LocalBoxFuture<'static, Result<IoConnection<Io>, ConnectError>>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
self.0.poll_ready(cx)
|
self.0.poll_ready(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: Connect) -> Self::Future {
|
fn call(&self, req: Connect) -> Self::Future {
|
||||||
let mut connector = self.0.clone();
|
let connector = self.0.clone();
|
||||||
let inner = self.1.clone();
|
let inner = self.1.clone();
|
||||||
|
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
@ -138,10 +140,9 @@ where
|
|||||||
Some(guard.consume()),
|
Some(guard.consume()),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
let (snd, connection) = handshake(io, &config).await?;
|
let (sender, connection) = handshake(io, &config).await?;
|
||||||
actix_rt::spawn(connection.map(|_| ()));
|
|
||||||
Ok(IoConnection::new(
|
Ok(IoConnection::new(
|
||||||
ConnectionType::H2(snd),
|
ConnectionType::H2(H2Connection::new(sender, connection)),
|
||||||
Instant::now(),
|
Instant::now(),
|
||||||
Some(guard.consume()),
|
Some(guard.consume()),
|
||||||
))
|
))
|
||||||
@ -257,7 +258,7 @@ struct AvailableConnection<Io> {
|
|||||||
pub(crate) struct Inner<Io> {
|
pub(crate) struct Inner<Io> {
|
||||||
config: ConnectorConfig,
|
config: ConnectorConfig,
|
||||||
acquired: usize,
|
acquired: usize,
|
||||||
available: FxHashMap<Key, VecDeque<AvailableConnection<Io>>>,
|
available: AHashMap<Key, VecDeque<AvailableConnection<Io>>>,
|
||||||
waiters: Slab<
|
waiters: Slab<
|
||||||
Option<(
|
Option<(
|
||||||
Connect,
|
Connect,
|
||||||
@ -325,7 +326,7 @@ where
|
|||||||
{
|
{
|
||||||
if let Some(timeout) = self.config.disconnect_timeout {
|
if let Some(timeout) = self.config.disconnect_timeout {
|
||||||
if let ConnectionType::H1(io) = conn.io {
|
if let ConnectionType::H1(io) = conn.io {
|
||||||
actix_rt::spawn(CloseConnection::new(io, timeout))
|
actix_rt::spawn(CloseConnection::new(io, timeout));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -340,7 +341,7 @@ where
|
|||||||
if let ConnectionType::H1(io) = io {
|
if let ConnectionType::H1(io) = io {
|
||||||
actix_rt::spawn(CloseConnection::new(
|
actix_rt::spawn(CloseConnection::new(
|
||||||
io, timeout,
|
io, timeout,
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@ -372,7 +373,7 @@ where
|
|||||||
self.acquired -= 1;
|
self.acquired -= 1;
|
||||||
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(io, timeout))
|
actix_rt::spawn(CloseConnection::new(io, timeout));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.check_availability();
|
self.check_availability();
|
||||||
@ -428,7 +429,7 @@ struct ConnectorPoolSupport<T, Io>
|
|||||||
where
|
where
|
||||||
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
{
|
{
|
||||||
connector: T,
|
connector: Rc<T>,
|
||||||
inner: Rc<RefCell<Inner<Io>>>,
|
inner: Rc<RefCell<Inner<Io>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,7 +536,7 @@ where
|
|||||||
rx: Some(rx),
|
rx: Some(rx),
|
||||||
inner: Some(inner),
|
inner: Some(inner),
|
||||||
config,
|
config,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,11 +566,10 @@ where
|
|||||||
|
|
||||||
if let Some(ref mut h2) = this.h2 {
|
if let Some(ref mut h2) = this.h2 {
|
||||||
return match Pin::new(h2).poll(cx) {
|
return match Pin::new(h2).poll(cx) {
|
||||||
Poll::Ready(Ok((snd, connection))) => {
|
Poll::Ready(Ok((sender, connection))) => {
|
||||||
actix_rt::spawn(connection.map(|_| ()));
|
|
||||||
let rx = this.rx.take().unwrap();
|
let rx = this.rx.take().unwrap();
|
||||||
let _ = rx.send(Ok(IoConnection::new(
|
let _ = rx.send(Ok(IoConnection::new(
|
||||||
ConnectionType::H2(snd),
|
ConnectionType::H2(H2Connection::new(sender, connection)),
|
||||||
Instant::now(),
|
Instant::now(),
|
||||||
Some(Acquired(this.key.clone(), this.inner.take())),
|
Some(Acquired(this.key.clone(), this.inner.take())),
|
||||||
)));
|
)));
|
||||||
|
@ -9,7 +9,7 @@ use bytes::BytesMut;
|
|||||||
use futures_util::{future, FutureExt};
|
use futures_util::{future, FutureExt};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
/// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
||||||
const DATE_VALUE_LENGTH: usize = 29;
|
const DATE_VALUE_LENGTH: usize = 29;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
@ -3,14 +3,14 @@ use std::io::{self, Write};
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use actix_threadpool::{run, CpuFuture};
|
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||||
use brotli2::write::BrotliDecoder;
|
use brotli2::write::BrotliDecoder;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flate2::write::{GzDecoder, ZlibDecoder};
|
use flate2::write::{GzDecoder, ZlibDecoder};
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
|
|
||||||
use super::Writer;
|
use super::Writer;
|
||||||
use crate::error::PayloadError;
|
use crate::error::{BlockingError, PayloadError};
|
||||||
use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING};
|
use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING};
|
||||||
|
|
||||||
const INPLACE: usize = 2049;
|
const INPLACE: usize = 2049;
|
||||||
@ -19,7 +19,7 @@ pub struct Decoder<S> {
|
|||||||
decoder: Option<ContentDecoder>,
|
decoder: Option<ContentDecoder>,
|
||||||
stream: S,
|
stream: S,
|
||||||
eof: bool,
|
eof: bool,
|
||||||
fut: Option<CpuFuture<(Option<Bytes>, ContentDecoder), io::Error>>,
|
fut: Option<JoinHandle<Result<(Option<Bytes>, ContentDecoder), io::Error>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Decoder<S>
|
impl<S> Decoder<S>
|
||||||
@ -79,10 +79,8 @@ where
|
|||||||
) -> Poll<Option<Self::Item>> {
|
) -> Poll<Option<Self::Item>> {
|
||||||
loop {
|
loop {
|
||||||
if let Some(ref mut fut) = self.fut {
|
if let Some(ref mut fut) = self.fut {
|
||||||
let (chunk, decoder) = match ready!(Pin::new(fut).poll(cx)) {
|
let (chunk, decoder) =
|
||||||
Ok(item) => item,
|
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
||||||
Err(e) => return Poll::Ready(Some(Err(e.into()))),
|
|
||||||
};
|
|
||||||
self.decoder = Some(decoder);
|
self.decoder = Some(decoder);
|
||||||
self.fut.take();
|
self.fut.take();
|
||||||
if let Some(chunk) = chunk {
|
if let Some(chunk) = chunk {
|
||||||
@ -105,7 +103,7 @@ where
|
|||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.fut = Some(run(move || {
|
self.fut = Some(spawn_blocking(move || {
|
||||||
let chunk = decoder.feed_data(chunk)?;
|
let chunk = decoder.feed_data(chunk)?;
|
||||||
Ok((chunk, decoder))
|
Ok((chunk, decoder))
|
||||||
}));
|
}));
|
||||||
|
@ -4,7 +4,7 @@ use std::io::{self, Write};
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use actix_threadpool::{run, CpuFuture};
|
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||||
use brotli2::write::BrotliEncoder;
|
use brotli2::write::BrotliEncoder;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||||
@ -17,6 +17,7 @@ use crate::http::{HeaderValue, StatusCode};
|
|||||||
use crate::{Error, ResponseHead};
|
use crate::{Error, ResponseHead};
|
||||||
|
|
||||||
use super::Writer;
|
use super::Writer;
|
||||||
|
use crate::error::BlockingError;
|
||||||
|
|
||||||
const INPLACE: usize = 1024;
|
const INPLACE: usize = 1024;
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ pub struct Encoder<B> {
|
|||||||
#[pin]
|
#[pin]
|
||||||
body: EncoderBody<B>,
|
body: EncoderBody<B>,
|
||||||
encoder: Option<ContentEncoder>,
|
encoder: Option<ContentEncoder>,
|
||||||
fut: Option<CpuFuture<ContentEncoder, io::Error>>,
|
fut: Option<JoinHandle<Result<ContentEncoder, io::Error>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: MessageBody> Encoder<B> {
|
impl<B: MessageBody> Encoder<B> {
|
||||||
@ -135,10 +136,8 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref mut fut) = this.fut {
|
if let Some(ref mut fut) = this.fut {
|
||||||
let mut encoder = match ready!(Pin::new(fut).poll(cx)) {
|
let mut encoder =
|
||||||
Ok(item) => item,
|
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
||||||
Err(e) => return Poll::Ready(Some(Err(e.into()))),
|
|
||||||
};
|
|
||||||
let chunk = encoder.take();
|
let chunk = encoder.take();
|
||||||
*this.encoder = Some(encoder);
|
*this.encoder = Some(encoder);
|
||||||
this.fut.take();
|
this.fut.take();
|
||||||
@ -160,7 +159,7 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
|
|||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
*this.fut = Some(run(move || {
|
*this.fut = Some(spawn_blocking(move || {
|
||||||
encoder.write(&chunk)?;
|
encoder.write(&chunk)?;
|
||||||
Ok(encoder)
|
Ok(encoder)
|
||||||
}));
|
}));
|
||||||
|
@ -7,7 +7,6 @@ use std::string::FromUtf8Error;
|
|||||||
use std::{fmt, io, result};
|
use std::{fmt, io, result};
|
||||||
|
|
||||||
use actix_codec::{Decoder, Encoder};
|
use actix_codec::{Decoder, Encoder};
|
||||||
pub use actix_threadpool::BlockingError;
|
|
||||||
use actix_utils::dispatcher::DispatcherError as FramedDispatcherError;
|
use actix_utils::dispatcher::DispatcherError as FramedDispatcherError;
|
||||||
use actix_utils::timeout::TimeoutError;
|
use actix_utils::timeout::TimeoutError;
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
@ -19,7 +18,6 @@ use serde::de::value::Error as DeError;
|
|||||||
use serde_json::error::Error as JsonError;
|
use serde_json::error::Error as JsonError;
|
||||||
use serde_urlencoded::ser::Error as FormError;
|
use serde_urlencoded::ser::Error as FormError;
|
||||||
|
|
||||||
// re-export for convenience
|
|
||||||
use crate::body::Body;
|
use crate::body::Body;
|
||||||
pub use crate::cookie::ParseError as CookieParseError;
|
pub use crate::cookie::ParseError as CookieParseError;
|
||||||
use crate::helpers::Writer;
|
use crate::helpers::Writer;
|
||||||
@ -100,10 +98,6 @@ impl fmt::Debug for Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for Error {
|
impl std::error::Error for Error {
|
||||||
fn cause(&self) -> Option<&dyn std::error::Error> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -153,7 +147,10 @@ impl From<ResponseBuilder> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `GATEWAY_TIMEOUT` for `TimeoutError`
|
/// Inspects the underlying enum and returns an appropriate status code.
|
||||||
|
///
|
||||||
|
/// If the variant is [`TimeoutError::Service`], the error code of the service is returned.
|
||||||
|
/// Otherwise, [`StatusCode::GATEWAY_TIMEOUT`] is returned.
|
||||||
impl<E: ResponseError> ResponseError for TimeoutError<E> {
|
impl<E: ResponseError> ResponseError for TimeoutError<E> {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
match self {
|
match self {
|
||||||
@ -167,44 +164,44 @@ impl<E: ResponseError> ResponseError for TimeoutError<E> {
|
|||||||
#[display(fmt = "UnknownError")]
|
#[display(fmt = "UnknownError")]
|
||||||
struct UnitError;
|
struct UnitError;
|
||||||
|
|
||||||
/// `InternalServerError` for `UnitError`
|
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`UnitError`].
|
||||||
impl ResponseError for UnitError {}
|
impl ResponseError for UnitError {}
|
||||||
|
|
||||||
/// `InternalServerError` for `JsonError`
|
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`JsonError`].
|
||||||
impl ResponseError for JsonError {}
|
impl ResponseError for JsonError {}
|
||||||
|
|
||||||
/// `InternalServerError` for `FormError`
|
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`FormError`].
|
||||||
impl ResponseError for FormError {}
|
impl ResponseError for FormError {}
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
/// `InternalServerError` for `openssl::ssl::Error`
|
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix_tls::accept::openssl::SslError`].
|
||||||
impl ResponseError for actix_tls::accept::openssl::SslError {}
|
impl ResponseError for actix_tls::accept::openssl::SslError {}
|
||||||
|
|
||||||
/// Return `BAD_REQUEST` for `de::value::Error`
|
/// Returns [`StatusCode::BAD_REQUEST`] for [`DeError`].
|
||||||
impl ResponseError for DeError {
|
impl ResponseError for DeError {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
StatusCode::BAD_REQUEST
|
StatusCode::BAD_REQUEST
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `InternalServerError` for `Canceled`
|
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`Canceled`].
|
||||||
impl ResponseError for Canceled {}
|
impl ResponseError for Canceled {}
|
||||||
|
|
||||||
/// `InternalServerError` for `BlockingError`
|
/// Returns [`StatusCode::BAD_REQUEST`] for [`Utf8Error`].
|
||||||
impl<E: fmt::Debug> ResponseError for BlockingError<E> {}
|
|
||||||
|
|
||||||
/// Return `BAD_REQUEST` for `Utf8Error`
|
|
||||||
impl ResponseError for Utf8Error {
|
impl ResponseError for Utf8Error {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
StatusCode::BAD_REQUEST
|
StatusCode::BAD_REQUEST
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `InternalServerError` for `HttpError`,
|
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`HttpError`].
|
||||||
/// Response generation can return `HttpError`, so it is internal error
|
|
||||||
impl ResponseError for HttpError {}
|
impl ResponseError for HttpError {}
|
||||||
|
|
||||||
/// Return `InternalServerError` for `io::Error`
|
/// Inspects the underlying [`io::ErrorKind`] and returns an appropriate status code.
|
||||||
|
///
|
||||||
|
/// If the error is [`io::ErrorKind::NotFound`], [`StatusCode::NOT_FOUND`] is returned. If the
|
||||||
|
/// error is [`io::ErrorKind::PermissionDenied`], [`StatusCode::FORBIDDEN`] is returned. Otherwise,
|
||||||
|
/// [`StatusCode::INTERNAL_SERVER_ERROR`] is returned.
|
||||||
impl ResponseError for io::Error {
|
impl ResponseError for io::Error {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
match self.kind() {
|
match self.kind() {
|
||||||
@ -215,7 +212,7 @@ impl ResponseError for io::Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `BadRequest` for `InvalidHeaderValue`
|
/// Returns [`StatusCode::BAD_REQUEST`] for [`header::InvalidHeaderValue`].
|
||||||
impl ResponseError for header::InvalidHeaderValue {
|
impl ResponseError for header::InvalidHeaderValue {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
StatusCode::BAD_REQUEST
|
StatusCode::BAD_REQUEST
|
||||||
@ -304,33 +301,60 @@ impl From<httparse::Error> for ParseError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A set of errors that can occur running blocking tasks in thread pool.
|
||||||
|
#[derive(Debug, Display)]
|
||||||
|
#[display(fmt = "Blocking thread pool is gone")]
|
||||||
|
pub struct BlockingError;
|
||||||
|
|
||||||
|
impl std::error::Error for BlockingError {}
|
||||||
|
|
||||||
|
/// `InternalServerError` for `BlockingError`
|
||||||
|
impl ResponseError for BlockingError {}
|
||||||
|
|
||||||
#[derive(Display, Debug)]
|
#[derive(Display, Debug)]
|
||||||
/// A set of errors that can occur during payload parsing
|
/// A set of errors that can occur during payload parsing
|
||||||
pub enum PayloadError {
|
pub enum PayloadError {
|
||||||
/// A payload reached EOF, but is not complete.
|
/// A payload reached EOF, but is not complete.
|
||||||
#[display(
|
#[display(
|
||||||
fmt = "A payload reached EOF, but is not complete. With error: {:?}",
|
fmt = "A payload reached EOF, but is not complete. Inner error: {:?}",
|
||||||
_0
|
_0
|
||||||
)]
|
)]
|
||||||
Incomplete(Option<io::Error>),
|
Incomplete(Option<io::Error>),
|
||||||
/// Content encoding stream corruption
|
|
||||||
|
/// Content encoding stream corruption.
|
||||||
#[display(fmt = "Can not decode content-encoding.")]
|
#[display(fmt = "Can not decode content-encoding.")]
|
||||||
EncodingCorrupted,
|
EncodingCorrupted,
|
||||||
/// A payload reached size limit.
|
|
||||||
#[display(fmt = "A payload reached size limit.")]
|
/// Payload reached size limit.
|
||||||
|
#[display(fmt = "Payload reached size limit.")]
|
||||||
Overflow,
|
Overflow,
|
||||||
/// A payload length is unknown.
|
|
||||||
#[display(fmt = "A payload length is unknown.")]
|
/// Payload length is unknown.
|
||||||
|
#[display(fmt = "Payload length is unknown.")]
|
||||||
UnknownLength,
|
UnknownLength,
|
||||||
/// Http2 payload error
|
|
||||||
|
/// HTTP/2 payload error.
|
||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
Http2Payload(h2::Error),
|
Http2Payload(h2::Error),
|
||||||
/// Io error
|
|
||||||
|
/// Generic I/O error.
|
||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for PayloadError {}
|
impl std::error::Error for PayloadError {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
match self {
|
||||||
|
PayloadError::Incomplete(None) => None,
|
||||||
|
PayloadError::Incomplete(Some(err)) => Some(err as &dyn std::error::Error),
|
||||||
|
PayloadError::EncodingCorrupted => None,
|
||||||
|
PayloadError::Overflow => None,
|
||||||
|
PayloadError::UnknownLength => None,
|
||||||
|
PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error),
|
||||||
|
PayloadError::Io(err) => Some(err as &dyn std::error::Error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<h2::Error> for PayloadError {
|
impl From<h2::Error> for PayloadError {
|
||||||
fn from(err: h2::Error) -> Self {
|
fn from(err: h2::Error) -> Self {
|
||||||
@ -350,15 +374,12 @@ impl From<io::Error> for PayloadError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BlockingError<io::Error>> for PayloadError {
|
impl From<BlockingError> for PayloadError {
|
||||||
fn from(err: BlockingError<io::Error>) -> Self {
|
fn from(_: BlockingError) -> Self {
|
||||||
match err {
|
PayloadError::Io(io::Error::new(
|
||||||
BlockingError::Error(e) => PayloadError::Io(e),
|
io::ErrorKind::Other,
|
||||||
BlockingError::Canceled => PayloadError::Io(io::Error::new(
|
"Operation is canceled",
|
||||||
io::ErrorKind::Other,
|
))
|
||||||
"Operation is canceled",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -947,11 +968,6 @@ where
|
|||||||
InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into()
|
InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "actors")]
|
|
||||||
/// `InternalServerError` for `actix::MailboxError`
|
|
||||||
/// This is supported on feature=`actors` only
|
|
||||||
impl ResponseError for actix::MailboxError {}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -1009,22 +1025,22 @@ mod tests {
|
|||||||
fn test_payload_error() {
|
fn test_payload_error() {
|
||||||
let err: PayloadError =
|
let err: PayloadError =
|
||||||
io::Error::new(io::ErrorKind::Other, "ParseError").into();
|
io::Error::new(io::ErrorKind::Other, "ParseError").into();
|
||||||
assert!(format!("{}", err).contains("ParseError"));
|
assert!(err.to_string().contains("ParseError"));
|
||||||
|
|
||||||
let err = PayloadError::Incomplete(None);
|
let err = PayloadError::Incomplete(None);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{}", err),
|
err.to_string(),
|
||||||
"A payload reached EOF, but is not complete. With error: None"
|
"A payload reached EOF, but is not complete. Inner error: None"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! from {
|
macro_rules! from {
|
||||||
($from:expr => $error:pat) => {
|
($from:expr => $error:pat) => {
|
||||||
match ParseError::from($from) {
|
match ParseError::from($from) {
|
||||||
e @ $error => {
|
err @ $error => {
|
||||||
assert!(format!("{}", e).len() >= 5);
|
assert!(err.to_string().len() >= 5);
|
||||||
}
|
}
|
||||||
e => unreachable!("{:?}", e),
|
err => unreachable!("{:?}", err),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1067,7 +1083,7 @@ mod tests {
|
|||||||
let err = PayloadError::Overflow;
|
let err = PayloadError::Overflow;
|
||||||
let resp_err: &dyn ResponseError = &err;
|
let resp_err: &dyn ResponseError = &err;
|
||||||
let err = resp_err.downcast_ref::<PayloadError>().unwrap();
|
let err = resp_err.downcast_ref::<PayloadError>().unwrap();
|
||||||
assert_eq!(err.to_string(), "A payload reached size limit.");
|
assert_eq!(err.to_string(), "Payload reached size limit.");
|
||||||
let not_err = resp_err.downcast_ref::<ContentTypeError>();
|
let not_err = resp_err.downcast_ref::<ContentTypeError>();
|
||||||
assert!(not_err.is_none());
|
assert!(not_err.is_none());
|
||||||
}
|
}
|
||||||
|
@ -1,62 +1,119 @@
|
|||||||
use std::any::{Any, TypeId};
|
use std::{
|
||||||
use std::{fmt, mem};
|
any::{Any, TypeId},
|
||||||
|
fmt, mem,
|
||||||
|
};
|
||||||
|
|
||||||
use fxhash::FxHashMap;
|
use ahash::AHashMap;
|
||||||
|
|
||||||
/// A type map of request extensions.
|
/// A type map for request extensions.
|
||||||
|
///
|
||||||
|
/// All entries into this map must be owned types (or static references).
|
||||||
#[derive(Default)]
|
#[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.
|
||||||
map: FxHashMap<TypeId, Box<dyn Any>>,
|
map: AHashMap<TypeId, Box<dyn Any>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Extensions {
|
impl Extensions {
|
||||||
/// Create an empty `Extensions`.
|
/// Creates an empty `Extensions`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> Extensions {
|
pub fn new() -> Extensions {
|
||||||
Extensions {
|
Extensions {
|
||||||
map: FxHashMap::default(),
|
map: AHashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a type into this `Extensions`.
|
/// Insert an item into the map.
|
||||||
///
|
///
|
||||||
/// If a extension of this type already existed, it will
|
/// If an item of this type was already stored, it will be replaced and returned.
|
||||||
/// be returned.
|
///
|
||||||
pub fn insert<T: 'static>(&mut self, val: T) {
|
/// ```
|
||||||
self.map.insert(TypeId::of::<T>(), Box::new(val));
|
/// # use actix_http::Extensions;
|
||||||
|
/// let mut map = Extensions::new();
|
||||||
|
/// assert_eq!(map.insert(""), None);
|
||||||
|
/// assert_eq!(map.insert(1u32), None);
|
||||||
|
/// assert_eq!(map.insert(2u32), Some(1u32));
|
||||||
|
/// assert_eq!(*map.get::<u32>().unwrap(), 2u32);
|
||||||
|
/// ```
|
||||||
|
pub fn insert<T: 'static>(&mut self, val: T) -> Option<T> {
|
||||||
|
self.map
|
||||||
|
.insert(TypeId::of::<T>(), Box::new(val))
|
||||||
|
.and_then(downcast_owned)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if container contains entry
|
/// Check if map contains an item of a given type.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use actix_http::Extensions;
|
||||||
|
/// let mut map = Extensions::new();
|
||||||
|
/// assert!(!map.contains::<u32>());
|
||||||
|
///
|
||||||
|
/// assert_eq!(map.insert(1u32), None);
|
||||||
|
/// assert!(map.contains::<u32>());
|
||||||
|
/// ```
|
||||||
pub fn contains<T: 'static>(&self) -> bool {
|
pub fn contains<T: 'static>(&self) -> bool {
|
||||||
self.map.contains_key(&TypeId::of::<T>())
|
self.map.contains_key(&TypeId::of::<T>())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to a type previously inserted on this `Extensions`.
|
/// Get a reference to an item of a given type.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use actix_http::Extensions;
|
||||||
|
/// let mut map = Extensions::new();
|
||||||
|
/// map.insert(1u32);
|
||||||
|
/// assert_eq!(map.get::<u32>(), Some(&1u32));
|
||||||
|
/// ```
|
||||||
pub fn get<T: 'static>(&self) -> Option<&T> {
|
pub fn get<T: 'static>(&self) -> Option<&T> {
|
||||||
self.map
|
self.map
|
||||||
.get(&TypeId::of::<T>())
|
.get(&TypeId::of::<T>())
|
||||||
.and_then(|boxed| boxed.downcast_ref())
|
.and_then(|boxed| boxed.downcast_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to a type previously inserted on this `Extensions`.
|
/// Get a mutable reference to an item of a given type.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use actix_http::Extensions;
|
||||||
|
/// let mut map = Extensions::new();
|
||||||
|
/// map.insert(1u32);
|
||||||
|
/// assert_eq!(map.get_mut::<u32>(), Some(&mut 1u32));
|
||||||
|
/// ```
|
||||||
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||||
self.map
|
self.map
|
||||||
.get_mut(&TypeId::of::<T>())
|
.get_mut(&TypeId::of::<T>())
|
||||||
.and_then(|boxed| boxed.downcast_mut())
|
.and_then(|boxed| boxed.downcast_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a type from this `Extensions`.
|
/// Remove an item from the map of a given type.
|
||||||
///
|
///
|
||||||
/// If a extension of this type existed, it will be returned.
|
/// If an item of this type was already stored, it will be returned.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use actix_http::Extensions;
|
||||||
|
/// let mut map = Extensions::new();
|
||||||
|
///
|
||||||
|
/// map.insert(1u32);
|
||||||
|
/// assert_eq!(map.get::<u32>(), Some(&1u32));
|
||||||
|
///
|
||||||
|
/// assert_eq!(map.remove::<u32>(), Some(1u32));
|
||||||
|
/// assert!(!map.contains::<u32>());
|
||||||
|
/// ```
|
||||||
pub fn remove<T: 'static>(&mut self) -> Option<T> {
|
pub fn remove<T: 'static>(&mut self) -> Option<T> {
|
||||||
self.map
|
self.map.remove(&TypeId::of::<T>()).and_then(downcast_owned)
|
||||||
.remove(&TypeId::of::<T>())
|
|
||||||
.and_then(|boxed| boxed.downcast().ok().map(|boxed| *boxed))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear the `Extensions` of all inserted extensions.
|
/// Clear the `Extensions` of all inserted extensions.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use actix_http::Extensions;
|
||||||
|
/// let mut map = Extensions::new();
|
||||||
|
///
|
||||||
|
/// map.insert(1u32);
|
||||||
|
/// assert!(map.contains::<u32>());
|
||||||
|
///
|
||||||
|
/// map.clear();
|
||||||
|
/// assert!(!map.contains::<u32>());
|
||||||
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.map.clear();
|
self.map.clear();
|
||||||
@ -79,6 +136,10 @@ impl fmt::Debug for Extensions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn downcast_owned<T: 'static>(boxed: Box<dyn Any>) -> Option<T> {
|
||||||
|
boxed.downcast().ok().map(|boxed| *boxed)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -223,15 +223,3 @@ impl Encoder<Message<(RequestHeadType, BodySize)>> for ClientCodec {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Writer<'a>(pub &'a mut BytesMut);
|
|
||||||
|
|
||||||
impl<'a> io::Write for Writer<'a> {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
self.0.extend_from_slice(buf);
|
|
||||||
Ok(buf.len())
|
|
||||||
}
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -14,7 +14,7 @@ use crate::header::HeaderMap;
|
|||||||
use crate::message::{ConnectionType, ResponseHead};
|
use crate::message::{ConnectionType, ResponseHead};
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
|
|
||||||
const MAX_BUFFER_SIZE: usize = 131_072;
|
pub(crate) const MAX_BUFFER_SIZE: usize = 131_072;
|
||||||
const MAX_HEADERS: usize = 96;
|
const MAX_HEADERS: usize = 96;
|
||||||
|
|
||||||
/// Incoming message decoder
|
/// Incoming message decoder
|
||||||
@ -203,7 +203,15 @@ impl MessageType for Request {
|
|||||||
|
|
||||||
(len, method, uri, version, req.headers.len())
|
(len, method, uri, version, req.headers.len())
|
||||||
}
|
}
|
||||||
httparse::Status::Partial => return Ok(None),
|
httparse::Status::Partial => {
|
||||||
|
return if src.len() >= MAX_BUFFER_SIZE {
|
||||||
|
trace!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
||||||
|
Err(ParseError::TooLarge)
|
||||||
|
} else {
|
||||||
|
// Return None to notify more read are needed for parsing request
|
||||||
|
Ok(None)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -222,9 +230,6 @@ impl MessageType for Request {
|
|||||||
PayloadLength::None => {
|
PayloadLength::None => {
|
||||||
if method == Method::CONNECT {
|
if method == Method::CONNECT {
|
||||||
PayloadType::Stream(PayloadDecoder::eof())
|
PayloadType::Stream(PayloadDecoder::eof())
|
||||||
} else if src.len() >= MAX_BUFFER_SIZE {
|
|
||||||
trace!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
|
||||||
return Err(ParseError::TooLarge);
|
|
||||||
} else {
|
} else {
|
||||||
PayloadType::None
|
PayloadType::None
|
||||||
}
|
}
|
||||||
@ -273,7 +278,14 @@ impl MessageType for ResponseHead {
|
|||||||
|
|
||||||
(len, version, status, res.headers.len())
|
(len, version, status, res.headers.len())
|
||||||
}
|
}
|
||||||
httparse::Status::Partial => return Ok(None),
|
httparse::Status::Partial => {
|
||||||
|
return if src.len() >= MAX_BUFFER_SIZE {
|
||||||
|
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
||||||
|
Err(ParseError::TooLarge)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -289,9 +301,6 @@ impl MessageType for ResponseHead {
|
|||||||
} else if status == StatusCode::SWITCHING_PROTOCOLS {
|
} else if status == StatusCode::SWITCHING_PROTOCOLS {
|
||||||
// switching protocol or connect
|
// switching protocol or connect
|
||||||
PayloadType::Stream(PayloadDecoder::eof())
|
PayloadType::Stream(PayloadDecoder::eof())
|
||||||
} else if src.len() >= MAX_BUFFER_SIZE {
|
|
||||||
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
|
||||||
return Err(ParseError::TooLarge);
|
|
||||||
} else {
|
} else {
|
||||||
// for HTTP/1.0 read to eof and close connection
|
// for HTTP/1.0 read to eof and close connection
|
||||||
if msg.version == Version::HTTP_10 {
|
if msg.version == Version::HTTP_10 {
|
||||||
@ -821,8 +830,8 @@ mod tests {
|
|||||||
.get_all(SET_COOKIE)
|
.get_all(SET_COOKIE)
|
||||||
.map(|v| v.to_str().unwrap().to_owned())
|
.map(|v| v.to_str().unwrap().to_owned())
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(val[1], "c1=cookie1");
|
assert_eq!(val[0], "c1=cookie1");
|
||||||
assert_eq!(val[0], "c2=cookie2");
|
assert_eq!(val[1], "c2=cookie2");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
fmt,
|
fmt,
|
||||||
future::Future,
|
future::Future,
|
||||||
@ -30,8 +29,8 @@ use super::codec::Codec;
|
|||||||
use super::payload::{Payload, PayloadSender, PayloadStatus};
|
use super::payload::{Payload, PayloadSender, PayloadStatus};
|
||||||
use super::{Message, MessageType};
|
use super::{Message, MessageType};
|
||||||
|
|
||||||
const LW_BUFFER_SIZE: usize = 4096;
|
const LW_BUFFER_SIZE: usize = 1024;
|
||||||
const HW_BUFFER_SIZE: usize = 32_768;
|
const HW_BUFFER_SIZE: usize = 1024 * 8;
|
||||||
const MAX_PIPELINED_MESSAGES: usize = 16;
|
const MAX_PIPELINED_MESSAGES: usize = 16;
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
@ -91,7 +90,7 @@ where
|
|||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
|
flow: Rc<HttpFlow<S, X, U>>,
|
||||||
on_connect_data: OnConnectData,
|
on_connect_data: OnConnectData,
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
@ -177,7 +176,7 @@ where
|
|||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
stream: T,
|
stream: T,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
services: Rc<RefCell<HttpFlow<S, X, U>>>,
|
services: Rc<HttpFlow<S, X, U>>,
|
||||||
on_connect_data: OnConnectData,
|
on_connect_data: OnConnectData,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -200,7 +199,7 @@ where
|
|||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
read_buf: BytesMut,
|
read_buf: BytesMut,
|
||||||
timeout: Option<Sleep>,
|
timeout: Option<Sleep>,
|
||||||
services: Rc<RefCell<HttpFlow<S, X, U>>>,
|
services: Rc<HttpFlow<S, X, U>>,
|
||||||
on_connect_data: OnConnectData,
|
on_connect_data: OnConnectData,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -287,44 +286,39 @@ where
|
|||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Result<bool, DispatchError> {
|
) -> Result<bool, DispatchError> {
|
||||||
if self.write_buf.is_empty() {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let len = self.write_buf.len();
|
|
||||||
let mut written = 0;
|
|
||||||
let InnerDispatcherProj { io, write_buf, .. } = self.project();
|
let InnerDispatcherProj { io, write_buf, .. } = self.project();
|
||||||
let mut io = Pin::new(io.as_mut().unwrap());
|
let mut io = Pin::new(io.as_mut().unwrap());
|
||||||
|
|
||||||
|
let len = write_buf.len();
|
||||||
|
let mut written = 0;
|
||||||
|
|
||||||
while written < len {
|
while written < len {
|
||||||
match io.as_mut().poll_write(cx, &write_buf[written..]) {
|
match io.as_mut().poll_write(cx, &write_buf[written..]) {
|
||||||
Poll::Ready(Ok(0)) => {
|
Poll::Ready(Ok(0)) => {
|
||||||
return Err(DispatchError::Io(io::Error::new(
|
return Err(DispatchError::Io(io::Error::new(
|
||||||
io::ErrorKind::WriteZero,
|
io::ErrorKind::WriteZero,
|
||||||
"",
|
"",
|
||||||
)));
|
)))
|
||||||
}
|
|
||||||
Poll::Ready(Ok(n)) => {
|
|
||||||
written += n;
|
|
||||||
}
|
}
|
||||||
|
Poll::Ready(Ok(n)) => written += n,
|
||||||
Poll::Pending => {
|
Poll::Pending => {
|
||||||
if written > 0 {
|
write_buf.advance(written);
|
||||||
write_buf.advance(written);
|
|
||||||
}
|
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)),
|
Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if written == write_buf.len() {
|
// SAFETY: setting length to 0 is safe
|
||||||
// SAFETY: setting length to 0 is safe
|
// skips one length check vs truncate
|
||||||
// skips one length check vs truncate
|
unsafe {
|
||||||
unsafe { write_buf.set_len(0) }
|
write_buf.set_len(0);
|
||||||
} else {
|
|
||||||
write_buf.advance(written);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(false)
|
// flush the io and check if get blocked.
|
||||||
|
let blocked = io.poll_flush(cx)?.is_pending();
|
||||||
|
|
||||||
|
Ok(blocked)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_response(
|
fn send_response(
|
||||||
@ -384,7 +378,7 @@ where
|
|||||||
Poll::Ready(Ok(req)) => {
|
Poll::Ready(Ok(req)) => {
|
||||||
self.as_mut().send_continue();
|
self.as_mut().send_continue();
|
||||||
this = self.as_mut().project();
|
this = self.as_mut().project();
|
||||||
let fut = this.flow.borrow_mut().service.call(req);
|
let fut = this.flow.service.call(req);
|
||||||
this.state.set(State::ServiceCall(fut));
|
this.state.set(State::ServiceCall(fut));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -412,7 +406,7 @@ where
|
|||||||
},
|
},
|
||||||
StateProj::SendPayload(mut stream) => {
|
StateProj::SendPayload(mut stream) => {
|
||||||
loop {
|
loop {
|
||||||
if this.write_buf.len() < HW_BUFFER_SIZE {
|
if this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
|
||||||
match stream.as_mut().poll_next(cx) {
|
match stream.as_mut().poll_next(cx) {
|
||||||
Poll::Ready(Some(Ok(item))) => {
|
Poll::Ready(Some(Ok(item))) => {
|
||||||
this.codec.encode(
|
this.codec.encode(
|
||||||
@ -474,12 +468,12 @@ where
|
|||||||
if req.head().expect() {
|
if req.head().expect() {
|
||||||
// set dispatcher state so the future is pinned.
|
// set dispatcher state so the future is pinned.
|
||||||
let mut this = self.as_mut().project();
|
let mut this = self.as_mut().project();
|
||||||
let task = this.flow.borrow_mut().expect.call(req);
|
let task = this.flow.expect.call(req);
|
||||||
this.state.set(State::ExpectCall(task));
|
this.state.set(State::ExpectCall(task));
|
||||||
} else {
|
} else {
|
||||||
// the same as above.
|
// the same as above.
|
||||||
let mut this = self.as_mut().project();
|
let mut this = self.as_mut().project();
|
||||||
let task = this.flow.borrow_mut().service.call(req);
|
let task = this.flow.service.call(req);
|
||||||
this.state.set(State::ServiceCall(task));
|
this.state.set(State::ServiceCall(task));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -492,7 +486,7 @@ where
|
|||||||
Poll::Ready(Ok(req)) => {
|
Poll::Ready(Ok(req)) => {
|
||||||
self.as_mut().send_continue();
|
self.as_mut().send_continue();
|
||||||
let mut this = self.as_mut().project();
|
let mut this = self.as_mut().project();
|
||||||
let task = this.flow.borrow_mut().service.call(req);
|
let task = this.flow.service.call(req);
|
||||||
this.state.set(State::ServiceCall(task));
|
this.state.set(State::ServiceCall(task));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -563,9 +557,7 @@ where
|
|||||||
// merge on_connect_ext data into request extensions
|
// merge on_connect_ext data into request extensions
|
||||||
this.on_connect_data.merge_into(&mut req);
|
this.on_connect_data.merge_into(&mut req);
|
||||||
|
|
||||||
if pl == MessageType::Stream
|
if pl == MessageType::Stream && this.flow.upgrade.is_some() {
|
||||||
&& this.flow.borrow().upgrade.is_some()
|
|
||||||
{
|
|
||||||
this.messages.push_back(DispatcherMessage::Upgrade(req));
|
this.messages.push_back(DispatcherMessage::Upgrade(req));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -615,6 +607,8 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// decode is partial and buffer is not full yet.
|
||||||
|
// break and wait for more read.
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
Err(ParseError::Io(e)) => {
|
Err(ParseError::Io(e)) => {
|
||||||
self.as_mut().client_disconnected();
|
self.as_mut().client_disconnected();
|
||||||
@ -622,6 +616,18 @@ where
|
|||||||
*this.error = Some(DispatchError::Io(e));
|
*this.error = Some(DispatchError::Io(e));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Err(ParseError::TooLarge) => {
|
||||||
|
if let Some(mut payload) = this.payload.take() {
|
||||||
|
payload.set_error(PayloadError::Overflow);
|
||||||
|
}
|
||||||
|
// Requests overflow buffer size should be responded with 413
|
||||||
|
this.messages.push_back(DispatcherMessage::Error(
|
||||||
|
Response::PayloadTooLarge().finish().drop_body(),
|
||||||
|
));
|
||||||
|
this.flags.insert(Flags::READ_DISCONNECT);
|
||||||
|
*this.error = Some(ParseError::TooLarge.into());
|
||||||
|
break;
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let Some(mut payload) = this.payload.take() {
|
if let Some(mut payload) = this.payload.take() {
|
||||||
payload.set_error(PayloadError::EncodingCorrupted);
|
payload.set_error(PayloadError::EncodingCorrupted);
|
||||||
@ -652,90 +658,156 @@ where
|
|||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Result<(), DispatchError> {
|
) -> Result<(), DispatchError> {
|
||||||
let mut this = self.as_mut().project();
|
let mut this = self.as_mut().project();
|
||||||
if this.ka_timer.is_none() {
|
|
||||||
// shutdown timeout
|
|
||||||
if this.flags.contains(Flags::SHUTDOWN) {
|
|
||||||
if let Some(interval) = this.codec.config().client_disconnect_timer() {
|
|
||||||
this.ka_timer.set(Some(sleep_until(interval)));
|
|
||||||
} else {
|
|
||||||
this.flags.insert(Flags::READ_DISCONNECT);
|
|
||||||
if let Some(mut payload) = this.payload.take() {
|
|
||||||
payload.set_error(PayloadError::Incomplete(None));
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx) {
|
// when a branch is not explicit return early it's meant to fall through
|
||||||
Poll::Ready(()) => {
|
// and return as Ok(())
|
||||||
// if we get timeout during shutdown, drop connection
|
match this.ka_timer.as_mut().as_pin_mut() {
|
||||||
|
None => {
|
||||||
|
// conditionally go into shutdown timeout
|
||||||
if this.flags.contains(Flags::SHUTDOWN) {
|
if this.flags.contains(Flags::SHUTDOWN) {
|
||||||
return Err(DispatchError::DisconnectTimeout);
|
if let Some(deadline) = this.codec.config().client_disconnect_timer()
|
||||||
} else if this.ka_timer.as_mut().as_pin_mut().unwrap().deadline()
|
{
|
||||||
>= *this.ka_expire
|
// write client disconnect time out and poll again to
|
||||||
{
|
// go into Some<Pin<&mut Sleep>> branch
|
||||||
// check for any outstanding tasks
|
this.ka_timer.set(Some(sleep_until(deadline)));
|
||||||
if this.state.is_empty() && this.write_buf.is_empty() {
|
return self.poll_keepalive(cx);
|
||||||
if this.flags.contains(Flags::STARTED) {
|
} else {
|
||||||
trace!("Keep-alive timeout, close connection");
|
this.flags.insert(Flags::READ_DISCONNECT);
|
||||||
this.flags.insert(Flags::SHUTDOWN);
|
if let Some(mut payload) = this.payload.take() {
|
||||||
|
payload.set_error(PayloadError::Incomplete(None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(mut timer) => {
|
||||||
|
// only operate when keep-alive timer is resolved.
|
||||||
|
if timer.as_mut().poll(cx).is_ready() {
|
||||||
|
// got timeout during shutdown, drop connection
|
||||||
|
if this.flags.contains(Flags::SHUTDOWN) {
|
||||||
|
return Err(DispatchError::DisconnectTimeout);
|
||||||
|
// exceed deadline. check for any outstanding tasks
|
||||||
|
} else if timer.deadline() >= *this.ka_expire {
|
||||||
|
// have no task at hand.
|
||||||
|
if this.state.is_empty() && this.write_buf.is_empty() {
|
||||||
|
if this.flags.contains(Flags::STARTED) {
|
||||||
|
trace!("Keep-alive timeout, close connection");
|
||||||
|
this.flags.insert(Flags::SHUTDOWN);
|
||||||
|
|
||||||
// start shutdown timer
|
// start shutdown timeout
|
||||||
if let Some(deadline) =
|
if let Some(deadline) =
|
||||||
this.codec.config().client_disconnect_timer()
|
this.codec.config().client_disconnect_timer()
|
||||||
{
|
|
||||||
if let Some(timer) = this.ka_timer.as_mut().as_pin_mut()
|
|
||||||
{
|
{
|
||||||
timer.reset(deadline);
|
timer.as_mut().reset(deadline);
|
||||||
let _ = this
|
let _ = timer.poll(cx);
|
||||||
.ka_timer
|
} else {
|
||||||
.as_mut()
|
// no shutdown timeout, drop socket
|
||||||
.as_pin_mut()
|
this.flags.insert(Flags::WRITE_DISCONNECT);
|
||||||
.unwrap()
|
|
||||||
.poll(cx);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// no shutdown timeout, drop socket
|
// timeout on first request (slow request) return 408
|
||||||
this.flags.insert(Flags::WRITE_DISCONNECT);
|
if !this.flags.contains(Flags::STARTED) {
|
||||||
return Ok(());
|
trace!("Slow request timeout");
|
||||||
|
let _ = self.as_mut().send_response(
|
||||||
|
Response::RequestTimeout().finish().drop_body(),
|
||||||
|
ResponseBody::Other(Body::Empty),
|
||||||
|
);
|
||||||
|
this = self.project();
|
||||||
|
} else {
|
||||||
|
trace!("Keep-alive connection timeout");
|
||||||
|
}
|
||||||
|
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
|
||||||
|
this.state.set(State::None);
|
||||||
}
|
}
|
||||||
} else {
|
// still have unfinished task. try to reset and register keep-alive.
|
||||||
// timeout on first request (slow request) return 408
|
} else if let Some(deadline) =
|
||||||
if !this.flags.contains(Flags::STARTED) {
|
this.codec.config().keep_alive_expire()
|
||||||
trace!("Slow request timeout");
|
{
|
||||||
let _ = self.as_mut().send_response(
|
timer.as_mut().reset(deadline);
|
||||||
Response::RequestTimeout().finish().drop_body(),
|
let _ = timer.poll(cx);
|
||||||
ResponseBody::Other(Body::Empty),
|
|
||||||
);
|
|
||||||
this = self.as_mut().project();
|
|
||||||
} else {
|
|
||||||
trace!("Keep-alive connection timeout");
|
|
||||||
}
|
|
||||||
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
|
|
||||||
this.state.set(State::None);
|
|
||||||
}
|
|
||||||
} else if let Some(deadline) =
|
|
||||||
this.codec.config().keep_alive_expire()
|
|
||||||
{
|
|
||||||
if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() {
|
|
||||||
timer.reset(deadline);
|
|
||||||
let _ =
|
|
||||||
this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx);
|
|
||||||
}
|
}
|
||||||
|
// timer resolved but still have not met the keep-alive expire deadline.
|
||||||
|
// reset and register for later wakeup.
|
||||||
|
} else {
|
||||||
|
timer.as_mut().reset(*this.ka_expire);
|
||||||
|
let _ = timer.poll(cx);
|
||||||
}
|
}
|
||||||
} else if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() {
|
|
||||||
timer.reset(*this.ka_expire);
|
|
||||||
let _ = this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Pending => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true when io stream can be disconnected after write to it.
|
||||||
|
///
|
||||||
|
/// It covers these conditions:
|
||||||
|
///
|
||||||
|
/// - `std::io::ErrorKind::ConnectionReset` after partial read.
|
||||||
|
/// - all data read done.
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_available(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Result<bool, DispatchError> {
|
||||||
|
let this = self.project();
|
||||||
|
|
||||||
|
if this.flags.contains(Flags::READ_DISCONNECT) {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut io = Pin::new(this.io.as_mut().unwrap());
|
||||||
|
|
||||||
|
let mut read_some = false;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// grow buffer if necessary.
|
||||||
|
let remaining = this.read_buf.capacity() - this.read_buf.len();
|
||||||
|
if remaining < LW_BUFFER_SIZE {
|
||||||
|
this.read_buf.reserve(HW_BUFFER_SIZE - remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
match actix_codec::poll_read_buf(io.as_mut(), cx, this.read_buf) {
|
||||||
|
Poll::Pending => return Ok(false),
|
||||||
|
Poll::Ready(Ok(n)) => {
|
||||||
|
if n == 0 {
|
||||||
|
return Ok(true);
|
||||||
|
} else {
|
||||||
|
// Return early when read buf exceed decoder's max buffer size.
|
||||||
|
if this.read_buf.len() >= super::decoder::MAX_BUFFER_SIZE {
|
||||||
|
// at this point it's not known io is still scheduled to
|
||||||
|
// be waked up. so force wake up dispatcher just in case.
|
||||||
|
// TODO: figure out the overhead.
|
||||||
|
cx.waker().wake_by_ref();
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
read_some = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Poll::Ready(Err(err)) => {
|
||||||
|
return if err.kind() == io::ErrorKind::WouldBlock {
|
||||||
|
Ok(false)
|
||||||
|
} else if err.kind() == io::ErrorKind::ConnectionReset && read_some {
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Err(DispatchError::Io(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// call upgrade service with request.
|
||||||
|
fn upgrade(self: Pin<&mut Self>, req: Request) -> U::Future {
|
||||||
|
let this = self.project();
|
||||||
|
let mut parts = FramedParts::with_read_buf(
|
||||||
|
this.io.take().unwrap(),
|
||||||
|
mem::take(this.codec),
|
||||||
|
mem::take(this.read_buf),
|
||||||
|
);
|
||||||
|
parts.write_buf = mem::take(this.write_buf);
|
||||||
|
let framed = Framed::from_parts(parts);
|
||||||
|
this.flow.upgrade.as_ref().unwrap().call((req, framed))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
|
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
|
||||||
@ -769,79 +841,46 @@ where
|
|||||||
if inner.flags.contains(Flags::WRITE_DISCONNECT) {
|
if inner.flags.contains(Flags::WRITE_DISCONNECT) {
|
||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
} else {
|
} else {
|
||||||
// flush buffer
|
// flush buffer and wait on block.
|
||||||
inner.as_mut().poll_flush(cx)?;
|
if inner.as_mut().poll_flush(cx)? {
|
||||||
if !inner.write_buf.is_empty() || inner.io.is_none() {
|
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
} else {
|
} else {
|
||||||
match Pin::new(inner.project().io)
|
Pin::new(inner.project().io.as_mut().unwrap())
|
||||||
.as_pin_mut()
|
|
||||||
.unwrap()
|
|
||||||
.poll_shutdown(cx)
|
.poll_shutdown(cx)
|
||||||
{
|
.map_err(DispatchError::from)
|
||||||
Poll::Ready(res) => {
|
|
||||||
Poll::Ready(res.map_err(DispatchError::from))
|
|
||||||
}
|
|
||||||
Poll::Pending => Poll::Pending,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// read socket into a buf
|
// read from io stream and fill read buffer.
|
||||||
let should_disconnect =
|
let should_disconnect = inner.as_mut().read_available(cx)?;
|
||||||
if !inner.flags.contains(Flags::READ_DISCONNECT) {
|
|
||||||
let mut inner_p = inner.as_mut().project();
|
|
||||||
read_available(
|
|
||||||
cx,
|
|
||||||
inner_p.io.as_mut().unwrap(),
|
|
||||||
&mut inner_p.read_buf,
|
|
||||||
)?
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
inner.as_mut().poll_request(cx)?;
|
inner.as_mut().poll_request(cx)?;
|
||||||
if let Some(true) = should_disconnect {
|
|
||||||
let inner_p = inner.as_mut().project();
|
// io stream should to be closed.
|
||||||
inner_p.flags.insert(Flags::READ_DISCONNECT);
|
if should_disconnect {
|
||||||
if let Some(mut payload) = inner_p.payload.take() {
|
let inner = inner.as_mut().project();
|
||||||
|
inner.flags.insert(Flags::READ_DISCONNECT);
|
||||||
|
if let Some(mut payload) = inner.payload.take() {
|
||||||
payload.feed_eof();
|
payload.feed_eof();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let inner_p = inner.as_mut().project();
|
// poll_response and populate write buffer.
|
||||||
let remaining =
|
// drain indicate if write buffer should be emptied before next run.
|
||||||
inner_p.write_buf.capacity() - inner_p.write_buf.len();
|
let drain = match inner.as_mut().poll_response(cx)? {
|
||||||
if remaining < LW_BUFFER_SIZE {
|
PollResponse::DrainWriteBuf => true,
|
||||||
inner_p.write_buf.reserve(HW_BUFFER_SIZE - remaining);
|
PollResponse::DoNothing => false,
|
||||||
}
|
// upgrade request and goes Upgrade variant of DispatcherState.
|
||||||
let result = inner.as_mut().poll_response(cx)?;
|
PollResponse::Upgrade(req) => {
|
||||||
let drain = result == PollResponse::DrainWriteBuf;
|
let upgrade = inner.upgrade(req);
|
||||||
|
self.as_mut()
|
||||||
// switch to upgrade handler
|
.project()
|
||||||
if let PollResponse::Upgrade(req) = result {
|
.inner
|
||||||
let inner_p = inner.as_mut().project();
|
.set(DispatcherState::Upgrade(upgrade));
|
||||||
let mut parts = FramedParts::with_read_buf(
|
return self.poll(cx);
|
||||||
inner_p.io.take().unwrap(),
|
}
|
||||||
mem::take(inner_p.codec),
|
};
|
||||||
mem::take(inner_p.read_buf),
|
|
||||||
);
|
|
||||||
parts.write_buf = mem::take(inner_p.write_buf);
|
|
||||||
let framed = Framed::from_parts(parts);
|
|
||||||
let upgrade = inner_p
|
|
||||||
.flow
|
|
||||||
.borrow_mut()
|
|
||||||
.upgrade
|
|
||||||
.take()
|
|
||||||
.unwrap()
|
|
||||||
.call((req, framed));
|
|
||||||
self.as_mut()
|
|
||||||
.project()
|
|
||||||
.inner
|
|
||||||
.set(DispatcherState::Upgrade(upgrade));
|
|
||||||
return self.poll(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// we didn't get WouldBlock from write operation,
|
// we didn't get WouldBlock from write operation,
|
||||||
// so data get written to kernel completely (macOS)
|
// so data get written to kernel completely (macOS)
|
||||||
@ -898,72 +937,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns either:
|
|
||||||
/// - `Ok(Some(true))` - data was read and done reading all data.
|
|
||||||
/// - `Ok(Some(false))` - data was read but there should be more to read.
|
|
||||||
/// - `Ok(None)` - no data was read but there should be more to read later.
|
|
||||||
/// - Unhandled Errors
|
|
||||||
fn read_available<T>(
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
io: &mut T,
|
|
||||||
buf: &mut BytesMut,
|
|
||||||
) -> Result<Option<bool>, io::Error>
|
|
||||||
where
|
|
||||||
T: AsyncRead + Unpin,
|
|
||||||
{
|
|
||||||
let mut read_some = false;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// If buf is full return but do not disconnect since
|
|
||||||
// there is more reading to be done
|
|
||||||
if buf.len() >= HW_BUFFER_SIZE {
|
|
||||||
return Ok(Some(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
let remaining = buf.capacity() - buf.len();
|
|
||||||
if remaining < LW_BUFFER_SIZE {
|
|
||||||
buf.reserve(HW_BUFFER_SIZE - remaining);
|
|
||||||
}
|
|
||||||
|
|
||||||
match read(cx, io, buf) {
|
|
||||||
Poll::Pending => {
|
|
||||||
return if read_some { Ok(Some(false)) } else { Ok(None) };
|
|
||||||
}
|
|
||||||
Poll::Ready(Ok(n)) => {
|
|
||||||
if n == 0 {
|
|
||||||
return Ok(Some(true));
|
|
||||||
} else {
|
|
||||||
read_some = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Poll::Ready(Err(err)) => {
|
|
||||||
return if err.kind() == io::ErrorKind::WouldBlock {
|
|
||||||
if read_some {
|
|
||||||
Ok(Some(false))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
} else if err.kind() == io::ErrorKind::ConnectionReset && read_some {
|
|
||||||
Ok(Some(true))
|
|
||||||
} else {
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read<T>(
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
io: &mut T,
|
|
||||||
buf: &mut BytesMut,
|
|
||||||
) -> Poll<Result<usize, io::Error>>
|
|
||||||
where
|
|
||||||
T: AsyncRead + Unpin,
|
|
||||||
{
|
|
||||||
actix_codec::poll_read_buf(Pin::new(io), cx, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str;
|
use std::str;
|
||||||
|
@ -8,7 +8,7 @@ use bytes::{BufMut, BytesMut};
|
|||||||
|
|
||||||
use crate::body::BodySize;
|
use crate::body::BodySize;
|
||||||
use crate::config::ServiceConfig;
|
use crate::config::ServiceConfig;
|
||||||
use crate::header::map;
|
use crate::header::{map::Value, HeaderName};
|
||||||
use crate::helpers;
|
use crate::helpers;
|
||||||
use crate::http::header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
|
use crate::http::header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
|
||||||
use crate::http::{HeaderMap, StatusCode, Version};
|
use crate::http::{HeaderMap, StatusCode, Version};
|
||||||
@ -121,21 +121,11 @@ pub(crate) trait MessageType: Sized {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// merging headers from head and extra headers. HeaderMap::new() does not allocate.
|
|
||||||
let empty_headers = HeaderMap::new();
|
|
||||||
let extra_headers = self.extra_headers().unwrap_or(&empty_headers);
|
|
||||||
let headers = self
|
|
||||||
.headers()
|
|
||||||
.inner
|
|
||||||
.iter()
|
|
||||||
.filter(|(name, _)| !extra_headers.contains_key(*name))
|
|
||||||
.chain(extra_headers.inner.iter());
|
|
||||||
|
|
||||||
// write headers
|
// write headers
|
||||||
|
|
||||||
let mut has_date = false;
|
let mut has_date = false;
|
||||||
|
|
||||||
let mut buf = dst.chunk_mut().as_mut_ptr() as *mut u8;
|
let mut buf = dst.chunk_mut().as_mut_ptr();
|
||||||
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
|
||||||
@ -143,10 +133,10 @@ pub(crate) trait MessageType: Sized {
|
|||||||
// container's knowledge, this is used to sync the containers cursor after data is written
|
// container's knowledge, this is used to sync the containers cursor after data is written
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
|
|
||||||
for (key, value) in headers {
|
self.write_headers(|key, value| {
|
||||||
match *key {
|
match *key {
|
||||||
CONNECTION => continue,
|
CONNECTION => return,
|
||||||
TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue,
|
TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => return,
|
||||||
DATE => has_date = true,
|
DATE => has_date = true,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -154,106 +144,56 @@ pub(crate) trait MessageType: Sized {
|
|||||||
let k = key.as_str().as_bytes();
|
let k = key.as_str().as_bytes();
|
||||||
let k_len = k.len();
|
let k_len = k.len();
|
||||||
|
|
||||||
match value {
|
// TODO: drain?
|
||||||
map::Value::One(ref val) => {
|
for val in value.iter() {
|
||||||
let v = val.as_ref();
|
let v = val.as_ref();
|
||||||
let v_len = v.len();
|
let v_len = v.len();
|
||||||
|
|
||||||
// key length + value length + colon + space + \r\n
|
// key length + value length + colon + space + \r\n
|
||||||
let len = k_len + v_len + 4;
|
let len = k_len + v_len + 4;
|
||||||
|
|
||||||
if len > remaining {
|
if len > remaining {
|
||||||
// not enough room in buffer for this header; reserve more space
|
// SAFETY: all the bytes written up to position "pos" are initialized
|
||||||
|
// the written byte count and pointer advancement are kept in sync
|
||||||
// SAFETY: all the bytes written up to position "pos" are initialized
|
|
||||||
// the written byte count and pointer advancement are kept in sync
|
|
||||||
unsafe {
|
|
||||||
dst.advance_mut(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
pos = 0;
|
|
||||||
dst.reserve(len * 2);
|
|
||||||
remaining = dst.capacity() - dst.len();
|
|
||||||
|
|
||||||
// re-assign buf raw pointer since it's possible that the buffer was
|
|
||||||
// reallocated and/or resized
|
|
||||||
buf = dst.chunk_mut().as_mut_ptr() as *mut u8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: on each write, it is enough to ensure that the advancement of the
|
|
||||||
// cursor matches the number of bytes written
|
|
||||||
unsafe {
|
unsafe {
|
||||||
// use upper Camel-Case
|
dst.advance_mut(pos);
|
||||||
if camel_case {
|
|
||||||
write_camel_case(k, from_raw_parts_mut(buf, k_len))
|
|
||||||
} else {
|
|
||||||
write_data(k, buf, k_len)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = buf.add(k_len);
|
|
||||||
|
|
||||||
write_data(b": ", buf, 2);
|
|
||||||
buf = buf.add(2);
|
|
||||||
|
|
||||||
write_data(v, buf, v_len);
|
|
||||||
buf = buf.add(v_len);
|
|
||||||
|
|
||||||
write_data(b"\r\n", buf, 2);
|
|
||||||
buf = buf.add(2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pos += len;
|
pos = 0;
|
||||||
remaining -= len;
|
dst.reserve(len * 2);
|
||||||
|
remaining = dst.capacity() - dst.len();
|
||||||
|
|
||||||
|
// re-assign buf raw pointer since it's possible that the buffer was
|
||||||
|
// reallocated and/or resized
|
||||||
|
buf = dst.chunk_mut().as_mut_ptr();
|
||||||
}
|
}
|
||||||
|
|
||||||
map::Value::Multi(ref vec) => {
|
// SAFETY: on each write, it is enough to ensure that the advancement of
|
||||||
for val in vec {
|
// the cursor matches the number of bytes written
|
||||||
let v = val.as_ref();
|
unsafe {
|
||||||
let v_len = v.len();
|
if camel_case {
|
||||||
let len = k_len + v_len + 4;
|
// use Camel-Case headers
|
||||||
|
write_camel_case(k, from_raw_parts_mut(buf, k_len));
|
||||||
if len > remaining {
|
} else {
|
||||||
// SAFETY: all the bytes written up to position "pos" are initialized
|
write_data(k, buf, k_len);
|
||||||
// the written byte count and pointer advancement are kept in sync
|
|
||||||
unsafe {
|
|
||||||
dst.advance_mut(pos);
|
|
||||||
}
|
|
||||||
pos = 0;
|
|
||||||
dst.reserve(len * 2);
|
|
||||||
remaining = dst.capacity() - dst.len();
|
|
||||||
|
|
||||||
// re-assign buf raw pointer since it's possible that the buffer was
|
|
||||||
// reallocated and/or resized
|
|
||||||
buf = dst.chunk_mut().as_mut_ptr() as *mut u8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: on each write, it is enough to ensure that the advancement of
|
|
||||||
// the cursor matches the number of bytes written
|
|
||||||
unsafe {
|
|
||||||
if camel_case {
|
|
||||||
write_camel_case(k, from_raw_parts_mut(buf, k_len));
|
|
||||||
} else {
|
|
||||||
write_data(k, buf, k_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = buf.add(k_len);
|
|
||||||
|
|
||||||
write_data(b": ", buf, 2);
|
|
||||||
buf = buf.add(2);
|
|
||||||
|
|
||||||
write_data(v, buf, v_len);
|
|
||||||
buf = buf.add(v_len);
|
|
||||||
|
|
||||||
write_data(b"\r\n", buf, 2);
|
|
||||||
buf = buf.add(2);
|
|
||||||
};
|
|
||||||
|
|
||||||
pos += len;
|
|
||||||
remaining -= len;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
buf = buf.add(k_len);
|
||||||
|
|
||||||
|
write_data(b": ", buf, 2);
|
||||||
|
buf = buf.add(2);
|
||||||
|
|
||||||
|
write_data(v, buf, v_len);
|
||||||
|
buf = buf.add(v_len);
|
||||||
|
|
||||||
|
write_data(b"\r\n", buf, 2);
|
||||||
|
buf = buf.add(2);
|
||||||
|
};
|
||||||
|
|
||||||
|
pos += len;
|
||||||
|
remaining -= len;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
// final cursor synchronization with the bytes container
|
// final cursor synchronization with the bytes container
|
||||||
//
|
//
|
||||||
@ -273,6 +213,24 @@ pub(crate) trait MessageType: Sized {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_headers<F>(&mut self, mut f: F)
|
||||||
|
where
|
||||||
|
F: FnMut(&HeaderName, &Value),
|
||||||
|
{
|
||||||
|
match self.extra_headers() {
|
||||||
|
Some(headers) => {
|
||||||
|
// merging headers from head and extra headers.
|
||||||
|
self.headers()
|
||||||
|
.inner
|
||||||
|
.iter()
|
||||||
|
.filter(|(name, _)| !headers.contains_key(*name))
|
||||||
|
.chain(headers.inner.iter())
|
||||||
|
.for_each(|(k, v)| f(k, v))
|
||||||
|
}
|
||||||
|
None => self.headers().inner.iter().for_each(|(k, v)| f(k, v)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageType for Response<()> {
|
impl MessageType for Response<()> {
|
||||||
@ -329,7 +287,7 @@ impl MessageType for RequestHeadType {
|
|||||||
let head = self.as_ref();
|
let head = self.as_ref();
|
||||||
dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE);
|
dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE);
|
||||||
write!(
|
write!(
|
||||||
Writer(dst),
|
helpers::Writer(dst),
|
||||||
"{} {} {}",
|
"{} {} {}",
|
||||||
head.method,
|
head.method,
|
||||||
head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"),
|
head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"),
|
||||||
@ -462,7 +420,7 @@ impl TransferEncoding {
|
|||||||
*eof = true;
|
*eof = true;
|
||||||
buf.extend_from_slice(b"0\r\n\r\n");
|
buf.extend_from_slice(b"0\r\n\r\n");
|
||||||
} else {
|
} else {
|
||||||
writeln!(Writer(buf), "{:X}\r", msg.len())
|
writeln!(helpers::Writer(buf), "{:X}\r", msg.len())
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
|
|
||||||
buf.reserve(msg.len() + 2);
|
buf.reserve(msg.len() + 2);
|
||||||
@ -512,18 +470,6 @@ impl TransferEncoding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Writer<'a>(pub &'a mut BytesMut);
|
|
||||||
|
|
||||||
impl<'a> io::Write for Writer<'a> {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
self.0.extend_from_slice(buf);
|
|
||||||
Ok(buf.len())
|
|
||||||
}
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// Callers must ensure that the given length matches given value length.
|
/// Callers must ensure that the given length matches given value length.
|
||||||
unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) {
|
unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::task::{Context, Poll};
|
use std::task::Poll;
|
||||||
|
|
||||||
use actix_service::{Service, ServiceFactory};
|
use actix_service::{Service, ServiceFactory};
|
||||||
use futures_util::future::{ready, Ready};
|
use futures_util::future::{ready, Ready};
|
||||||
@ -26,11 +26,9 @@ impl Service<Request> for ExpectHandler {
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Self::Response, Self::Error>>;
|
type Future = Ready<Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
actix_service::always_ready!();
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, req: Request) -> Self::Future {
|
fn call(&self, req: Request) -> Self::Future {
|
||||||
ready(Ok(req))
|
ready(Ok(req))
|
||||||
// TODO: add some way to trigger error
|
// TODO: add some way to trigger error
|
||||||
// Err(error::ErrorExpectationFailed("test"))
|
// Err(error::ErrorExpectationFailed("test"))
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
@ -367,7 +366,7 @@ where
|
|||||||
X: Service<Request>,
|
X: Service<Request>,
|
||||||
U: Service<(Request, Framed<T, Codec>)>,
|
U: Service<(Request, Framed<T, Codec>)>,
|
||||||
{
|
{
|
||||||
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
|
flow: Rc<HttpFlow<S, X, U>>,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
_phantom: PhantomData<B>,
|
_phantom: PhantomData<B>,
|
||||||
@ -417,9 +416,9 @@ where
|
|||||||
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(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
let mut flow = self.flow.borrow_mut();
|
let ready = self
|
||||||
let ready = flow
|
.flow
|
||||||
.expect
|
.expect
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@ -429,7 +428,8 @@ where
|
|||||||
})?
|
})?
|
||||||
.is_ready();
|
.is_ready();
|
||||||
|
|
||||||
let ready = flow
|
let ready = self
|
||||||
|
.flow
|
||||||
.service
|
.service
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@ -440,7 +440,7 @@ where
|
|||||||
.is_ready()
|
.is_ready()
|
||||||
&& ready;
|
&& ready;
|
||||||
|
|
||||||
let ready = if let Some(ref mut upg) = flow.upgrade {
|
let ready = if let Some(ref upg) = self.flow.upgrade {
|
||||||
upg.poll_ready(cx)
|
upg.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
@ -460,7 +460,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
|
fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
|
||||||
let on_connect_data =
|
let on_connect_data =
|
||||||
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::task::{Context, Poll};
|
use std::task::Poll;
|
||||||
|
|
||||||
use actix_codec::Framed;
|
use actix_codec::Framed;
|
||||||
use actix_service::{Service, ServiceFactory};
|
use actix_service::{Service, ServiceFactory};
|
||||||
@ -28,11 +28,9 @@ impl<T> Service<(Request, Framed<T, Codec>)> for UpgradeHandler {
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Self::Response, Self::Error>>;
|
type Future = Ready<Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
actix_service::always_ready!();
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, _: (Request, Framed<T, Codec>)) -> Self::Future {
|
fn call(&self, _: (Request, Framed<T, Codec>)) -> Self::Future {
|
||||||
ready(Ok(()))
|
ready(Ok(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::net;
|
use std::net;
|
||||||
@ -37,7 +36,7 @@ where
|
|||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
|
flow: Rc<HttpFlow<S, X, U>>,
|
||||||
connection: Connection<T, Bytes>,
|
connection: Connection<T, Bytes>,
|
||||||
on_connect_data: OnConnectData,
|
on_connect_data: OnConnectData,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
@ -56,7 +55,7 @@ where
|
|||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
services: Rc<RefCell<HttpFlow<S, X, U>>>,
|
flow: Rc<HttpFlow<S, X, U>>,
|
||||||
connection: Connection<T, Bytes>,
|
connection: Connection<T, Bytes>,
|
||||||
on_connect_data: OnConnectData,
|
on_connect_data: OnConnectData,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
@ -80,7 +79,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
Dispatcher {
|
Dispatcher {
|
||||||
flow: services,
|
flow,
|
||||||
config,
|
config,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
connection,
|
connection,
|
||||||
@ -138,7 +137,7 @@ where
|
|||||||
|
|
||||||
let svc = ServiceResponse::<S::Future, S::Response, S::Error, B> {
|
let svc = ServiceResponse::<S::Future, S::Response, S::Error, B> {
|
||||||
state: ServiceResponseState::ServiceCall(
|
state: ServiceResponseState::ServiceCall(
|
||||||
this.flow.borrow_mut().service.call(req),
|
this.flow.service.call(req),
|
||||||
Some(res),
|
Some(res),
|
||||||
),
|
),
|
||||||
config: this.config.clone(),
|
config: this.config.clone(),
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
@ -249,7 +248,7 @@ pub struct H2ServiceHandler<T, S, B>
|
|||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
{
|
{
|
||||||
flow: Rc<RefCell<HttpFlow<S, (), ()>>>,
|
flow: Rc<HttpFlow<S, (), ()>>,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_phantom: PhantomData<B>,
|
_phantom: PhantomData<B>,
|
||||||
@ -290,15 +289,15 @@ where
|
|||||||
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(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
self.flow.borrow_mut().service.poll_ready(cx).map_err(|e| {
|
self.flow.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): (T, Option<net::SocketAddr>)) -> Self::Future {
|
fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
|
||||||
let on_connect_data =
|
let on_connect_data =
|
||||||
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||||
|
|
||||||
@ -321,7 +320,7 @@ where
|
|||||||
{
|
{
|
||||||
Incoming(Dispatcher<T, S, B, (), ()>),
|
Incoming(Dispatcher<T, S, B, (), ()>),
|
||||||
Handshake(
|
Handshake(
|
||||||
Option<Rc<RefCell<HttpFlow<S, (), ()>>>>,
|
Option<Rc<HttpFlow<S, (), ()>>>,
|
||||||
Option<ServiceConfig>,
|
Option<ServiceConfig>,
|
||||||
Option<net::SocketAddr>,
|
Option<net::SocketAddr>,
|
||||||
OnConnectData,
|
OnConnectData,
|
||||||
|
46
actix-http/src/header/as_name.rs
Normal file
46
actix-http/src/header/as_name.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
//! Helper trait for types that can be effectively borrowed as a [HeaderValue].
|
||||||
|
|
||||||
|
use std::{borrow::Cow, str::FromStr};
|
||||||
|
|
||||||
|
use http::header::{HeaderName, InvalidHeaderName};
|
||||||
|
|
||||||
|
pub trait AsHeaderName: Sealed {}
|
||||||
|
|
||||||
|
pub trait Sealed {
|
||||||
|
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sealed for HeaderName {
|
||||||
|
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
|
Ok(Cow::Borrowed(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsHeaderName for HeaderName {}
|
||||||
|
|
||||||
|
impl Sealed for &HeaderName {
|
||||||
|
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
|
Ok(Cow::Borrowed(*self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsHeaderName for &HeaderName {}
|
||||||
|
|
||||||
|
impl Sealed for &str {
|
||||||
|
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
|
HeaderName::from_str(self).map(Cow::Owned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsHeaderName for &str {}
|
||||||
|
|
||||||
|
impl Sealed for String {
|
||||||
|
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
|
HeaderName::from_str(self).map(Cow::Owned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsHeaderName for String {}
|
||||||
|
|
||||||
|
impl Sealed for &String {
|
||||||
|
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
|
HeaderName::from_str(self).map(Cow::Owned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsHeaderName for &String {}
|
@ -32,50 +32,36 @@ header! {
|
|||||||
/// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c`
|
/// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c`
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// extern crate mime;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{Accept, qitem};
|
/// use actix_http::http::header::{Accept, qitem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
///
|
/// builder.insert_header(
|
||||||
/// builder.set(
|
|
||||||
/// Accept(vec![
|
/// Accept(vec![
|
||||||
/// qitem(mime::TEXT_HTML),
|
/// qitem(mime::TEXT_HTML),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// extern crate mime;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{Accept, qitem};
|
/// use actix_http::http::header::{Accept, qitem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
///
|
/// builder.insert_header(
|
||||||
/// builder.set(
|
|
||||||
/// Accept(vec![
|
/// Accept(vec![
|
||||||
/// qitem(mime::APPLICATION_JSON),
|
/// qitem(mime::APPLICATION_JSON),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// extern crate mime;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{Accept, QualityItem, q, qitem};
|
/// use actix_http::http::header::{Accept, QualityItem, q, qitem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
///
|
/// builder.insert_header(
|
||||||
/// builder.set(
|
|
||||||
/// Accept(vec![
|
/// Accept(vec![
|
||||||
/// qitem(mime::TEXT_HTML),
|
/// qitem(mime::TEXT_HTML),
|
||||||
/// qitem("application/xhtml+xml".parse().unwrap()),
|
/// qitem("application/xhtml+xml".parse().unwrap()),
|
||||||
@ -90,7 +76,6 @@ header! {
|
|||||||
/// ),
|
/// ),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
(Accept, header::ACCEPT) => (QualityItem<Mime>)+
|
(Accept, header::ACCEPT) => (QualityItem<Mime>)+
|
||||||
|
|
||||||
@ -132,7 +117,7 @@ header! {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_fuzzing1() {
|
fn test_fuzzing1() {
|
||||||
use crate::test::TestRequest;
|
use crate::test::TestRequest;
|
||||||
let req = TestRequest::with_header(crate::header::ACCEPT, "chunk#;e").finish();
|
let req = TestRequest::default().insert_header((crate::header::ACCEPT, "chunk#;e")).finish();
|
||||||
let header = Accept::parse(&req);
|
let header = Accept::parse(&req);
|
||||||
assert!(header.is_ok());
|
assert!(header.is_ok());
|
||||||
}
|
}
|
||||||
|
@ -21,44 +21,37 @@ header! {
|
|||||||
/// * `iso-8859-5, unicode-1-1;q=0.8`
|
/// * `iso-8859-5, unicode-1-1;q=0.8`
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{AcceptCharset, Charset, qitem};
|
/// use actix_http::http::header::{AcceptCharset, Charset, qitem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// AcceptCharset(vec![qitem(Charset::Us_Ascii)])
|
/// AcceptCharset(vec![qitem(Charset::Us_Ascii)])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
/// ```rust
|
///
|
||||||
/// # extern crate actix_http;
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem};
|
/// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// AcceptCharset(vec![
|
/// AcceptCharset(vec![
|
||||||
/// QualityItem::new(Charset::Us_Ascii, q(900)),
|
/// QualityItem::new(Charset::Us_Ascii, q(900)),
|
||||||
/// QualityItem::new(Charset::Iso_8859_10, q(200)),
|
/// QualityItem::new(Charset::Iso_8859_10, q(200)),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
/// ```rust
|
///
|
||||||
/// # extern crate actix_http;
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{AcceptCharset, Charset, qitem};
|
/// use actix_http::http::header::{AcceptCharset, Charset, qitem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))])
|
/// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
(AcceptCharset, ACCEPT_CHARSET) => (QualityItem<Charset>)+
|
(AcceptCharset, ACCEPT_CHARSET) => (QualityItem<Charset>)+
|
||||||
|
|
||||||
|
@ -22,41 +22,35 @@ header! {
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
/// use language_tags::langtag;
|
||||||
/// # extern crate language_tags;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem};
|
/// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// let mut langtag: LanguageTag = Default::default();
|
/// let mut langtag: LanguageTag = Default::default();
|
||||||
/// langtag.language = Some("en".to_owned());
|
/// langtag.language = Some("en".to_owned());
|
||||||
/// langtag.region = Some("US".to_owned());
|
/// langtag.region = Some("US".to_owned());
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// AcceptLanguage(vec![
|
/// AcceptLanguage(vec![
|
||||||
/// qitem(langtag),
|
/// qitem(langtag),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
/// use language_tags::langtag;
|
||||||
/// # #[macro_use] extern crate language_tags;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem};
|
/// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem};
|
||||||
/// #
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// AcceptLanguage(vec![
|
/// AcceptLanguage(vec![
|
||||||
/// qitem(langtag!(da)),
|
/// qitem(langtag!(da)),
|
||||||
/// QualityItem::new(langtag!(en;;;GB), q(800)),
|
/// QualityItem::new(langtag!(en;;;GB), q(800)),
|
||||||
/// QualityItem::new(langtag!(en), q(700)),
|
/// QualityItem::new(langtag!(en), q(700)),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
(AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem<LanguageTag>)+
|
(AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem<LanguageTag>)+
|
||||||
|
|
||||||
|
@ -22,38 +22,28 @@ header! {
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate http;
|
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::Allow;
|
/// use actix_http::http::{header::Allow, Method};
|
||||||
/// use http::Method;
|
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// Allow(vec![Method::GET])
|
/// Allow(vec![Method::GET])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate http;
|
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::Allow;
|
/// use actix_http::http::{header::Allow, Method};
|
||||||
/// use http::Method;
|
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// Allow(vec![
|
/// Allow(vec![
|
||||||
/// Method::GET,
|
/// Method::GET,
|
||||||
/// Method::POST,
|
/// Method::POST,
|
||||||
/// Method::PATCH,
|
/// Method::PATCH,
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
(Allow, header::ALLOW) => (Method)*
|
(Allow, header::ALLOW) => (Method)*
|
||||||
|
|
||||||
|
@ -28,12 +28,12 @@ use crate::header::{
|
|||||||
/// * `max-age=30`
|
/// * `max-age=30`
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{CacheControl, CacheDirective};
|
/// use actix_http::http::header::{CacheControl, CacheDirective};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)]));
|
/// builder.insert_header(CacheControl(vec![CacheDirective::MaxAge(86400u32)]));
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@ -41,7 +41,7 @@ use crate::header::{
|
|||||||
/// use actix_http::http::header::{CacheControl, CacheDirective};
|
/// use actix_http::http::header::{CacheControl, CacheDirective};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(CacheControl(vec![
|
/// builder.insert_header(CacheControl(vec![
|
||||||
/// CacheDirective::NoCache,
|
/// CacheDirective::NoCache,
|
||||||
/// CacheDirective::Private,
|
/// CacheDirective::Private,
|
||||||
/// CacheDirective::MaxAge(360u32),
|
/// CacheDirective::MaxAge(360u32),
|
||||||
@ -82,7 +82,7 @@ impl fmt::Display for CacheControl {
|
|||||||
impl IntoHeaderValue for CacheControl {
|
impl IntoHeaderValue for CacheControl {
|
||||||
type Error = header::InvalidHeaderValue;
|
type Error = header::InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<header::HeaderValue, Self::Error> {
|
||||||
let mut writer = Writer::new();
|
let mut writer = Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
header::HeaderValue::from_maybe_shared(writer.take())
|
header::HeaderValue::from_maybe_shared(writer.take())
|
||||||
@ -196,7 +196,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_multiple_headers() {
|
fn test_parse_multiple_headers() {
|
||||||
let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private")
|
let req = TestRequest::default()
|
||||||
|
.insert_header((header::CACHE_CONTROL, "no-cache, private"))
|
||||||
.finish();
|
.finish();
|
||||||
let cache = Header::parse(&req);
|
let cache = Header::parse(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -210,9 +211,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_argument() {
|
fn test_parse_argument() {
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private")
|
.insert_header((header::CACHE_CONTROL, "max-age=100, private"))
|
||||||
.finish();
|
.finish();
|
||||||
let cache = Header::parse(&req);
|
let cache = Header::parse(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.ok(),
|
cache.ok(),
|
||||||
@ -225,8 +226,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_quote_form() {
|
fn test_parse_quote_form() {
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish();
|
.insert_header((header::CACHE_CONTROL, "max-age=\"200\""))
|
||||||
|
.finish();
|
||||||
let cache = Header::parse(&req);
|
let cache = Header::parse(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.ok(),
|
cache.ok(),
|
||||||
@ -236,8 +238,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_extension() {
|
fn test_parse_extension() {
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish();
|
.insert_header((header::CACHE_CONTROL, "foo, bar=baz"))
|
||||||
|
.finish();
|
||||||
let cache = Header::parse(&req);
|
let cache = Header::parse(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.ok(),
|
cache.ok(),
|
||||||
@ -250,7 +253,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_bad_syntax() {
|
fn test_parse_bad_syntax() {
|
||||||
let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish();
|
let req = TestRequest::default()
|
||||||
|
.insert_header((header::CACHE_CONTROL, "foo="))
|
||||||
|
.finish();
|
||||||
let cache: Result<CacheControl, _> = Header::parse(&req);
|
let cache: Result<CacheControl, _> = Header::parse(&req);
|
||||||
assert_eq!(cache.ok(), None)
|
assert_eq!(cache.ok(), None)
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
// # References
|
//! # References
|
||||||
//
|
//!
|
||||||
// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt
|
//! "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt
|
||||||
// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt
|
//! "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt
|
||||||
// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt
|
//! "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt
|
||||||
// Browser conformance tests at: http://greenbytes.de/tech/tc2231/
|
//! Browser conformance tests at: http://greenbytes.de/tech/tc2231/
|
||||||
// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml
|
//! IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@ -454,7 +454,7 @@ impl ContentDisposition {
|
|||||||
impl IntoHeaderValue for ContentDisposition {
|
impl IntoHeaderValue for ContentDisposition {
|
||||||
type Error = header::InvalidHeaderValue;
|
type Error = header::InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<header::HeaderValue, Self::Error> {
|
||||||
let mut writer = Writer::new();
|
let mut writer = Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
header::HeaderValue::from_maybe_shared(writer.take())
|
header::HeaderValue::from_maybe_shared(writer.take())
|
||||||
|
106
actix-http/src/header/common/content_encoding.rs
Normal file
106
actix-http/src/header/common/content_encoding.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use std::{convert::Infallible, str::FromStr};
|
||||||
|
|
||||||
|
use http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::ParseError,
|
||||||
|
header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, IntoHeaderValue},
|
||||||
|
HttpMessage,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents a supported content encoding.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum ContentEncoding {
|
||||||
|
/// Automatically select encoding based on encoding negotiation.
|
||||||
|
Auto,
|
||||||
|
|
||||||
|
/// A format using the Brotli algorithm.
|
||||||
|
Br,
|
||||||
|
|
||||||
|
/// A format using the zlib structure with deflate algorithm.
|
||||||
|
Deflate,
|
||||||
|
|
||||||
|
/// Gzip algorithm.
|
||||||
|
Gzip,
|
||||||
|
|
||||||
|
/// Indicates the identity function (i.e. no compression, nor modification).
|
||||||
|
Identity,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContentEncoding {
|
||||||
|
/// Is the content compressed?
|
||||||
|
#[inline]
|
||||||
|
pub fn is_compression(self) -> bool {
|
||||||
|
matches!(self, ContentEncoding::Identity | ContentEncoding::Auto)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert content encoding to string
|
||||||
|
#[inline]
|
||||||
|
pub fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ContentEncoding::Br => "br",
|
||||||
|
ContentEncoding::Gzip => "gzip",
|
||||||
|
ContentEncoding::Deflate => "deflate",
|
||||||
|
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default Q-factor (quality) value.
|
||||||
|
#[inline]
|
||||||
|
pub fn quality(self) -> f64 {
|
||||||
|
match self {
|
||||||
|
ContentEncoding::Br => 1.1,
|
||||||
|
ContentEncoding::Gzip => 1.0,
|
||||||
|
ContentEncoding::Deflate => 0.9,
|
||||||
|
ContentEncoding::Identity | ContentEncoding::Auto => 0.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ContentEncoding {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Identity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ContentEncoding {
|
||||||
|
type Err = Infallible;
|
||||||
|
|
||||||
|
fn from_str(val: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Self::from(val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for ContentEncoding {
|
||||||
|
fn from(val: &str) -> ContentEncoding {
|
||||||
|
let val = val.trim();
|
||||||
|
|
||||||
|
if val.eq_ignore_ascii_case("br") {
|
||||||
|
ContentEncoding::Br
|
||||||
|
} else if val.eq_ignore_ascii_case("gzip") {
|
||||||
|
ContentEncoding::Gzip
|
||||||
|
} else if val.eq_ignore_ascii_case("deflate") {
|
||||||
|
ContentEncoding::Deflate
|
||||||
|
} else {
|
||||||
|
ContentEncoding::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for ContentEncoding {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
fn try_into_value(self) -> Result<http::HeaderValue, Self::Error> {
|
||||||
|
Ok(HeaderValue::from_static(self.as_str()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header for ContentEncoding {
|
||||||
|
fn name() -> HeaderName {
|
||||||
|
header::CONTENT_ENCODING
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError> {
|
||||||
|
from_one_raw_str(msg.headers().get(Self::name()))
|
||||||
|
}
|
||||||
|
}
|
@ -23,38 +23,31 @@ header! {
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
/// use language_tags::langtag;
|
||||||
/// # #[macro_use] extern crate language_tags;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// # use actix_http::http::header::{ContentLanguage, qitem};
|
/// use actix_http::http::header::{ContentLanguage, qitem};
|
||||||
/// #
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// ContentLanguage(vec![
|
/// ContentLanguage(vec![
|
||||||
/// qitem(langtag!(en)),
|
/// qitem(langtag!(en)),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
/// use language_tags::langtag;
|
||||||
/// # #[macro_use] extern crate language_tags;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// # use actix_http::http::header::{ContentLanguage, qitem};
|
/// use actix_http::http::header::{ContentLanguage, qitem};
|
||||||
/// #
|
|
||||||
/// # fn main() {
|
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// ContentLanguage(vec![
|
/// ContentLanguage(vec![
|
||||||
/// qitem(langtag!(da)),
|
/// qitem(langtag!(da)),
|
||||||
/// qitem(langtag!(en;;;GB)),
|
/// qitem(langtag!(en;;;GB)),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
(ContentLanguage, CONTENT_LANGUAGE) => (QualityItem<LanguageTag>)+
|
(ContentLanguage, CONTENT_LANGUAGE) => (QualityItem<LanguageTag>)+
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ impl Display for ContentRangeSpec {
|
|||||||
impl IntoHeaderValue for ContentRangeSpec {
|
impl IntoHeaderValue for ContentRangeSpec {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
let mut writer = Writer::new();
|
let mut writer = Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
HeaderValue::from_maybe_shared(writer.take())
|
HeaderValue::from_maybe_shared(writer.take())
|
||||||
|
@ -30,31 +30,24 @@ header! {
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::ContentType;
|
/// use actix_http::http::header::ContentType;
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// ContentType::json()
|
/// ContentType::json()
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate mime;
|
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// use mime::TEXT_HTML;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::ContentType;
|
/// use actix_http::http::header::ContentType;
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// ContentType(TEXT_HTML)
|
/// ContentType(mime::TEXT_HTML)
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
(ContentType, CONTENT_TYPE) => [Mime]
|
(ContentType, CONTENT_TYPE) => [Mime]
|
||||||
|
|
||||||
@ -99,6 +92,7 @@ impl ContentType {
|
|||||||
pub fn form_url_encoded() -> ContentType {
|
pub fn form_url_encoded() -> ContentType {
|
||||||
ContentType(mime::APPLICATION_WWW_FORM_URLENCODED)
|
ContentType(mime::APPLICATION_WWW_FORM_URLENCODED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create a `Content-Type: image/jpeg` header.
|
/// A constructor to easily create a `Content-Type: image/jpeg` header.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn jpeg() -> ContentType {
|
pub fn jpeg() -> ContentType {
|
||||||
|
@ -19,13 +19,15 @@ header! {
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
|
/// use std::time::SystemTime;
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::Date;
|
/// use actix_http::http::header::Date;
|
||||||
/// use std::time::SystemTime;
|
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(Date(SystemTime::now().into()));
|
/// builder.insert_header(
|
||||||
|
/// Date(SystemTime::now().into())
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
(Date, DATE) => [HttpDate]
|
(Date, DATE) => [HttpDate]
|
||||||
|
|
||||||
|
@ -27,20 +27,24 @@ header! {
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{ETag, EntityTag};
|
/// use actix_http::http::header::{ETag, EntityTag};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned())));
|
/// builder.insert_header(
|
||||||
|
/// ETag(EntityTag::new(false, "xyzzy".to_owned()))
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{ETag, EntityTag};
|
/// use actix_http::http::header::{ETag, EntityTag};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned())));
|
/// builder.insert_header(
|
||||||
|
/// ETag(EntityTag::new(true, "xyzzy".to_owned()))
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
(ETag, ETAG) => [EntityTag]
|
(ETag, ETAG) => [EntityTag]
|
||||||
|
|
||||||
|
@ -21,14 +21,16 @@ header! {
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
|
/// use std::time::{SystemTime, Duration};
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::Expires;
|
/// use actix_http::http::header::Expires;
|
||||||
/// use std::time::{SystemTime, Duration};
|
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24);
|
/// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24);
|
||||||
/// builder.set(Expires(expiration.into()));
|
/// builder.insert_header(
|
||||||
|
/// Expires(expiration.into())
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
(Expires, EXPIRES) => [HttpDate]
|
(Expires, EXPIRES) => [HttpDate]
|
||||||
|
|
||||||
|
@ -29,20 +29,20 @@ header! {
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::IfMatch;
|
/// use actix_http::http::header::IfMatch;
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(IfMatch::Any);
|
/// builder.insert_header(IfMatch::Any);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{IfMatch, EntityTag};
|
/// use actix_http::http::header::{IfMatch, EntityTag};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// IfMatch::Items(vec![
|
/// IfMatch::Items(vec![
|
||||||
/// EntityTag::new(false, "xyzzy".to_owned()),
|
/// EntityTag::new(false, "xyzzy".to_owned()),
|
||||||
/// EntityTag::new(false, "foobar".to_owned()),
|
/// EntityTag::new(false, "foobar".to_owned()),
|
||||||
|
@ -21,14 +21,16 @@ header! {
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
|
/// use std::time::{SystemTime, Duration};
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::IfModifiedSince;
|
/// use actix_http::http::header::IfModifiedSince;
|
||||||
/// use std::time::{SystemTime, Duration};
|
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||||
/// builder.set(IfModifiedSince(modified.into()));
|
/// builder.insert_header(
|
||||||
|
/// IfModifiedSince(modified.into())
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
(IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate]
|
(IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate]
|
||||||
|
|
||||||
|
@ -31,20 +31,20 @@ header! {
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::IfNoneMatch;
|
/// use actix_http::http::header::IfNoneMatch;
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(IfNoneMatch::Any);
|
/// builder.insert_header(IfNoneMatch::Any);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{IfNoneMatch, EntityTag};
|
/// use actix_http::http::header::{IfNoneMatch, EntityTag};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// IfNoneMatch::Items(vec![
|
/// IfNoneMatch::Items(vec![
|
||||||
/// EntityTag::new(false, "xyzzy".to_owned()),
|
/// EntityTag::new(false, "xyzzy".to_owned()),
|
||||||
/// EntityTag::new(false, "foobar".to_owned()),
|
/// EntityTag::new(false, "foobar".to_owned()),
|
||||||
@ -73,13 +73,15 @@ mod tests {
|
|||||||
fn test_if_none_match() {
|
fn test_if_none_match() {
|
||||||
let mut if_none_match: Result<IfNoneMatch, _>;
|
let mut if_none_match: Result<IfNoneMatch, _>;
|
||||||
|
|
||||||
let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish();
|
let req = TestRequest::default()
|
||||||
|
.insert_header((IF_NONE_MATCH, "*"))
|
||||||
|
.finish();
|
||||||
if_none_match = Header::parse(&req);
|
if_none_match = Header::parse(&req);
|
||||||
assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any));
|
assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any));
|
||||||
|
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..])
|
.insert_header((IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]))
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
if_none_match = Header::parse(&req);
|
if_none_match = Header::parse(&req);
|
||||||
let mut entities: Vec<EntityTag> = Vec::new();
|
let mut entities: Vec<EntityTag> = Vec::new();
|
||||||
|
@ -35,31 +35,34 @@ use crate::httpmessage::HttpMessage;
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{EntityTag, IfRange};
|
/// use actix_http::http::header::{EntityTag, IfRange};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(IfRange::EntityTag(EntityTag::new(
|
/// builder.insert_header(
|
||||||
/// false,
|
/// IfRange::EntityTag(
|
||||||
/// "xyzzy".to_owned(),
|
/// EntityTag::new(false, "abc".to_owned())
|
||||||
/// )));
|
/// )
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
|
||||||
/// use actix_http::http::header::IfRange;
|
|
||||||
/// use std::time::{Duration, SystemTime};
|
/// use std::time::{Duration, SystemTime};
|
||||||
|
/// use actix_http::{http::header::IfRange, Response};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||||
/// builder.set(IfRange::Date(fetched.into()));
|
/// builder.insert_header(
|
||||||
|
/// IfRange::Date(fetched.into())
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum IfRange {
|
pub enum IfRange {
|
||||||
/// The entity-tag the client has of the resource
|
/// The entity-tag the client has of the resource.
|
||||||
EntityTag(EntityTag),
|
EntityTag(EntityTag),
|
||||||
/// The date when the client retrieved the resource
|
|
||||||
|
/// The date when the client retrieved the resource.
|
||||||
Date(HttpDate),
|
Date(HttpDate),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +101,7 @@ impl Display for IfRange {
|
|||||||
impl IntoHeaderValue for IfRange {
|
impl IntoHeaderValue for IfRange {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
let mut writer = Writer::new();
|
let mut writer = Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
HeaderValue::from_maybe_shared(writer.take())
|
HeaderValue::from_maybe_shared(writer.take())
|
||||||
@ -110,7 +113,8 @@ mod test_if_range {
|
|||||||
use super::IfRange as HeaderField;
|
use super::IfRange as HeaderField;
|
||||||
use crate::header::*;
|
use crate::header::*;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
|
test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
|
||||||
test_header!(test2, vec![b"\"xyzzy\""]);
|
test_header!(test2, vec![b"\"abc\""]);
|
||||||
test_header!(test3, vec![b"this-is-invalid"], None::<IfRange>);
|
test_header!(test3, vec![b"this-is-invalid"], None::<IfRange>);
|
||||||
}
|
}
|
||||||
|
@ -22,14 +22,16 @@ header! {
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
|
/// use std::time::{SystemTime, Duration};
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::IfUnmodifiedSince;
|
/// use actix_http::http::header::IfUnmodifiedSince;
|
||||||
/// use std::time::{SystemTime, Duration};
|
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||||
/// builder.set(IfUnmodifiedSince(modified.into()));
|
/// builder.insert_header(
|
||||||
|
/// IfUnmodifiedSince(modified.into())
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
(IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate]
|
(IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate]
|
||||||
|
|
||||||
|
@ -21,14 +21,16 @@ header! {
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
|
/// use std::time::{SystemTime, Duration};
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::LastModified;
|
/// use actix_http::http::header::LastModified;
|
||||||
/// use std::time::{SystemTime, Duration};
|
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||||
/// builder.set(LastModified(modified.into()));
|
/// builder.insert_header(
|
||||||
|
/// LastModified(modified.into())
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
(LastModified, LAST_MODIFIED) => [HttpDate]
|
(LastModified, LAST_MODIFIED) => [HttpDate]
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ pub use self::content_disposition::{
|
|||||||
};
|
};
|
||||||
pub use self::content_language::ContentLanguage;
|
pub use self::content_language::ContentLanguage;
|
||||||
pub use self::content_range::{ContentRange, ContentRangeSpec};
|
pub use self::content_range::{ContentRange, ContentRangeSpec};
|
||||||
|
pub use self::content_encoding::{ContentEncoding};
|
||||||
pub use self::content_type::ContentType;
|
pub use self::content_type::ContentType;
|
||||||
pub use self::date::Date;
|
pub use self::date::Date;
|
||||||
pub use self::etag::ETag;
|
pub use self::etag::ETag;
|
||||||
@ -83,7 +84,7 @@ macro_rules! test_header {
|
|||||||
let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect();
|
let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect();
|
||||||
let mut req = test::TestRequest::default();
|
let mut req = test::TestRequest::default();
|
||||||
for item in a {
|
for item in a {
|
||||||
req = req.header(HeaderField::name(), item).take();
|
req = req.insert_header((HeaderField::name(), item)).take();
|
||||||
}
|
}
|
||||||
let req = req.finish();
|
let req = req.finish();
|
||||||
let value = HeaderField::parse(&req);
|
let value = HeaderField::parse(&req);
|
||||||
@ -110,7 +111,7 @@ macro_rules! test_header {
|
|||||||
let a: Vec<Vec<u8>> = $raw.iter().map(|x| x.to_vec()).collect();
|
let a: Vec<Vec<u8>> = $raw.iter().map(|x| x.to_vec()).collect();
|
||||||
let mut req = test::TestRequest::default();
|
let mut req = test::TestRequest::default();
|
||||||
for item in a {
|
for item in a {
|
||||||
req.header(HeaderField::name(), item);
|
req.insert_header((HeaderField::name(), item));
|
||||||
}
|
}
|
||||||
let req = req.finish();
|
let req = req.finish();
|
||||||
let val = HeaderField::parse(&req);
|
let val = HeaderField::parse(&req);
|
||||||
@ -168,7 +169,7 @@ macro_rules! header {
|
|||||||
impl $crate::http::header::IntoHeaderValue for $id {
|
impl $crate::http::header::IntoHeaderValue for $id {
|
||||||
type Error = $crate::http::header::InvalidHeaderValue;
|
type Error = $crate::http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let mut writer = $crate::http::header::Writer::new();
|
let mut writer = $crate::http::header::Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
@ -204,7 +205,7 @@ macro_rules! header {
|
|||||||
impl $crate::http::header::IntoHeaderValue for $id {
|
impl $crate::http::header::IntoHeaderValue for $id {
|
||||||
type Error = $crate::http::header::InvalidHeaderValue;
|
type Error = $crate::http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let mut writer = $crate::http::header::Writer::new();
|
let mut writer = $crate::http::header::Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
@ -240,8 +241,8 @@ macro_rules! header {
|
|||||||
impl $crate::http::header::IntoHeaderValue for $id {
|
impl $crate::http::header::IntoHeaderValue for $id {
|
||||||
type Error = $crate::http::header::InvalidHeaderValue;
|
type Error = $crate::http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
||||||
self.0.try_into()
|
self.0.try_into_value()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -289,7 +290,7 @@ macro_rules! header {
|
|||||||
impl $crate::http::header::IntoHeaderValue for $id {
|
impl $crate::http::header::IntoHeaderValue for $id {
|
||||||
type Error = $crate::http::header::InvalidHeaderValue;
|
type Error = $crate::http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let mut writer = $crate::http::header::Writer::new();
|
let mut writer = $crate::http::header::Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
@ -333,13 +334,14 @@ macro_rules! header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod accept_charset;
|
mod accept_charset;
|
||||||
//mod accept_encoding;
|
// mod accept_encoding;
|
||||||
mod accept;
|
mod accept;
|
||||||
mod accept_language;
|
mod accept_language;
|
||||||
mod allow;
|
mod allow;
|
||||||
mod cache_control;
|
mod cache_control;
|
||||||
mod content_disposition;
|
mod content_disposition;
|
||||||
mod content_language;
|
mod content_language;
|
||||||
|
mod content_encoding;
|
||||||
mod content_range;
|
mod content_range;
|
||||||
mod content_type;
|
mod content_type;
|
||||||
mod date;
|
mod date;
|
||||||
|
117
actix-http/src/header/into_pair.rs
Normal file
117
actix-http/src/header/into_pair.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use http::{
|
||||||
|
header::{HeaderName, InvalidHeaderName, InvalidHeaderValue},
|
||||||
|
Error as HttpError, HeaderValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Header, IntoHeaderValue};
|
||||||
|
|
||||||
|
/// Transforms structures into header K/V pairs for inserting into `HeaderMap`s.
|
||||||
|
pub trait IntoHeaderPair: Sized {
|
||||||
|
type Error: Into<HttpError>;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InvalidHeaderPart {
|
||||||
|
Name(InvalidHeaderName),
|
||||||
|
Value(InvalidHeaderValue),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InvalidHeaderPart> for HttpError {
|
||||||
|
fn from(part_err: InvalidHeaderPart) -> Self {
|
||||||
|
match part_err {
|
||||||
|
InvalidHeaderPart::Name(err) => err.into(),
|
||||||
|
InvalidHeaderPart::Value(err) => err.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> IntoHeaderPair for (HeaderName, V)
|
||||||
|
where
|
||||||
|
V: IntoHeaderValue,
|
||||||
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
{
|
||||||
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
|
let (name, value) = self;
|
||||||
|
let value = value
|
||||||
|
.try_into_value()
|
||||||
|
.map_err(|err| InvalidHeaderPart::Value(err.into()))?;
|
||||||
|
Ok((name, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> IntoHeaderPair for (&HeaderName, V)
|
||||||
|
where
|
||||||
|
V: IntoHeaderValue,
|
||||||
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
{
|
||||||
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
|
let (name, value) = self;
|
||||||
|
let value = value
|
||||||
|
.try_into_value()
|
||||||
|
.map_err(|err| InvalidHeaderPart::Value(err.into()))?;
|
||||||
|
Ok((name.clone(), value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> IntoHeaderPair for (&[u8], V)
|
||||||
|
where
|
||||||
|
V: IntoHeaderValue,
|
||||||
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
{
|
||||||
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
|
let (name, value) = self;
|
||||||
|
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
||||||
|
let value = value
|
||||||
|
.try_into_value()
|
||||||
|
.map_err(|err| InvalidHeaderPart::Value(err.into()))?;
|
||||||
|
Ok((name, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> IntoHeaderPair for (&str, V)
|
||||||
|
where
|
||||||
|
V: IntoHeaderValue,
|
||||||
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
{
|
||||||
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
|
let (name, value) = self;
|
||||||
|
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
||||||
|
let value = value
|
||||||
|
.try_into_value()
|
||||||
|
.map_err(|err| InvalidHeaderPart::Value(err.into()))?;
|
||||||
|
Ok((name, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> IntoHeaderPair for (String, V)
|
||||||
|
where
|
||||||
|
V: IntoHeaderValue,
|
||||||
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
{
|
||||||
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
|
let (name, value) = self;
|
||||||
|
(name.as_str(), value).try_into_header_pair()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Header> IntoHeaderPair for T {
|
||||||
|
type Error = <T as IntoHeaderValue>::Error;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
|
Ok((T::name(), self.try_into_value()?))
|
||||||
|
}
|
||||||
|
}
|
131
actix-http/src/header/into_value.rs
Normal file
131
actix-http/src/header/into_value.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue};
|
||||||
|
use mime::Mime;
|
||||||
|
|
||||||
|
/// A trait for any object that can be Converted to a `HeaderValue`
|
||||||
|
pub trait IntoHeaderValue: Sized {
|
||||||
|
/// The type returned in the event of a conversion error.
|
||||||
|
type Error: Into<HttpError>;
|
||||||
|
|
||||||
|
/// Try to convert value to a HeaderValue.
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for HeaderValue {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for &HeaderValue {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
Ok(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for &str {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
self.parse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for &[u8] {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::from_bytes(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for Bytes {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::from_maybe_shared(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for Vec<u8> {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for String {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for usize {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for i64 {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for u64 {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for i32 {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for u32 {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for Mime {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::from_str(self.as_ref())
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,9 @@
|
|||||||
//! Various http headers
|
//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing and conversion, and other
|
||||||
// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header)
|
//! header utility methods.
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
use std::fmt;
|
||||||
use std::{fmt, str::FromStr};
|
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use http::Error as HttpError;
|
|
||||||
use mime::Mime;
|
|
||||||
use percent_encoding::{AsciiSet, CONTROLS};
|
use percent_encoding::{AsciiSet, CONTROLS};
|
||||||
|
|
||||||
pub use http::header::*;
|
pub use http::header::*;
|
||||||
@ -14,22 +11,29 @@ pub use http::header::*;
|
|||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use crate::httpmessage::HttpMessage;
|
use crate::httpmessage::HttpMessage;
|
||||||
|
|
||||||
|
mod as_name;
|
||||||
|
mod into_pair;
|
||||||
|
mod into_value;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
pub(crate) mod map;
|
pub(crate) mod map;
|
||||||
mod shared;
|
mod shared;
|
||||||
|
|
||||||
pub use self::common::*;
|
pub use self::common::*;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use self::shared::*;
|
pub use self::shared::*;
|
||||||
|
|
||||||
|
pub use self::as_name::AsHeaderName;
|
||||||
|
pub use self::into_pair::IntoHeaderPair;
|
||||||
|
pub use self::into_value::IntoHeaderValue;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use self::map::GetAll;
|
pub use self::map::GetAll;
|
||||||
pub use self::map::HeaderMap;
|
pub use self::map::HeaderMap;
|
||||||
|
pub use self::utils::*;
|
||||||
|
|
||||||
/// A trait for any object that will represent a header field and value.
|
/// A trait for any object that already represents a valid header field and value.
|
||||||
pub trait Header
|
pub trait Header: IntoHeaderValue {
|
||||||
where
|
|
||||||
Self: IntoHeaderValue,
|
|
||||||
{
|
|
||||||
/// Returns the name of the header field
|
/// Returns the name of the header field
|
||||||
fn name() -> HeaderName;
|
fn name() -> HeaderName;
|
||||||
|
|
||||||
@ -37,170 +41,16 @@ where
|
|||||||
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
|
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for any object that can be Converted to a `HeaderValue`
|
#[derive(Debug, Default)]
|
||||||
pub trait IntoHeaderValue: Sized {
|
|
||||||
/// The type returned in the event of a conversion error.
|
|
||||||
type Error: Into<HttpError>;
|
|
||||||
|
|
||||||
/// Try to convert value to a Header value.
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for HeaderValue {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoHeaderValue for &'a str {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
self.parse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoHeaderValue for &'a [u8] {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
HeaderValue::from_bytes(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for Bytes {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
HeaderValue::from_maybe_shared(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for Vec<u8> {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
HeaderValue::try_from(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for String {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
HeaderValue::try_from(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for usize {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
let s = format!("{}", self);
|
|
||||||
HeaderValue::try_from(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for u64 {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
let s = format!("{}", self);
|
|
||||||
HeaderValue::try_from(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for Mime {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
HeaderValue::try_from(format!("{}", self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents supported types of content encodings
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum ContentEncoding {
|
|
||||||
/// Automatically select encoding based on encoding negotiation
|
|
||||||
Auto,
|
|
||||||
/// A format using the Brotli algorithm
|
|
||||||
Br,
|
|
||||||
/// A format using the zlib structure with deflate algorithm
|
|
||||||
Deflate,
|
|
||||||
/// Gzip algorithm
|
|
||||||
Gzip,
|
|
||||||
/// Indicates the identity function (i.e. no compression, nor modification)
|
|
||||||
Identity,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContentEncoding {
|
|
||||||
#[inline]
|
|
||||||
/// Is the content compressed?
|
|
||||||
pub fn is_compression(self) -> bool {
|
|
||||||
matches!(self, ContentEncoding::Identity | ContentEncoding::Auto)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Convert content encoding to string
|
|
||||||
pub fn as_str(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
ContentEncoding::Br => "br",
|
|
||||||
ContentEncoding::Gzip => "gzip",
|
|
||||||
ContentEncoding::Deflate => "deflate",
|
|
||||||
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// default quality value
|
|
||||||
pub fn quality(self) -> f64 {
|
|
||||||
match self {
|
|
||||||
ContentEncoding::Br => 1.1,
|
|
||||||
ContentEncoding::Gzip => 1.0,
|
|
||||||
ContentEncoding::Deflate => 0.9,
|
|
||||||
ContentEncoding::Identity | ContentEncoding::Auto => 0.1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for ContentEncoding {
|
|
||||||
fn from(s: &'a str) -> ContentEncoding {
|
|
||||||
let s = s.trim();
|
|
||||||
|
|
||||||
if s.eq_ignore_ascii_case("br") {
|
|
||||||
ContentEncoding::Br
|
|
||||||
} else if s.eq_ignore_ascii_case("gzip") {
|
|
||||||
ContentEncoding::Gzip
|
|
||||||
} else if s.eq_ignore_ascii_case("deflate") {
|
|
||||||
ContentEncoding::Deflate
|
|
||||||
} else {
|
|
||||||
ContentEncoding::Identity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub(crate) struct Writer {
|
pub(crate) struct Writer {
|
||||||
buf: BytesMut,
|
buf: BytesMut,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Writer {
|
impl Writer {
|
||||||
fn new() -> Writer {
|
fn new() -> Writer {
|
||||||
Writer {
|
Writer::default()
|
||||||
buf: BytesMut::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take(&mut self) -> Bytes {
|
fn take(&mut self) -> Bytes {
|
||||||
self.buf.split().freeze()
|
self.buf.split().freeze()
|
||||||
}
|
}
|
||||||
@ -219,176 +69,15 @@ impl fmt::Write for Writer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
/// Convert `http::HeaderMap` to our `HeaderMap`.
|
||||||
#[doc(hidden)]
|
|
||||||
/// Reads a comma-delimited raw header into a Vec.
|
|
||||||
pub fn from_comma_delimited<'a, I: Iterator<Item = &'a HeaderValue> + 'a, T: FromStr>(
|
|
||||||
all: I,
|
|
||||||
) -> Result<Vec<T>, ParseError> {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
for h in all {
|
|
||||||
let s = h.to_str().map_err(|_| ParseError::Header)?;
|
|
||||||
result.extend(
|
|
||||||
s.split(',')
|
|
||||||
.filter_map(|x| match x.trim() {
|
|
||||||
"" => None,
|
|
||||||
y => Some(y),
|
|
||||||
})
|
|
||||||
.filter_map(|x| x.trim().parse().ok()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[doc(hidden)]
|
|
||||||
/// Reads a single string when parsing a header.
|
|
||||||
pub fn from_one_raw_str<T: FromStr>(val: Option<&HeaderValue>) -> Result<T, ParseError> {
|
|
||||||
if let Some(line) = val {
|
|
||||||
let line = line.to_str().map_err(|_| ParseError::Header)?;
|
|
||||||
if !line.is_empty() {
|
|
||||||
return T::from_str(line).or(Err(ParseError::Header));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(ParseError::Header)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[doc(hidden)]
|
|
||||||
/// Format an array into a comma-delimited string.
|
|
||||||
pub fn fmt_comma_delimited<T>(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result
|
|
||||||
where
|
|
||||||
T: fmt::Display,
|
|
||||||
{
|
|
||||||
let mut iter = parts.iter();
|
|
||||||
if let Some(part) = iter.next() {
|
|
||||||
fmt::Display::fmt(part, f)?;
|
|
||||||
}
|
|
||||||
for part in iter {
|
|
||||||
f.write_str(", ")?;
|
|
||||||
fmt::Display::fmt(part, f)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// From hyper v0.11.27 src/header/parsing.rs
|
|
||||||
|
|
||||||
/// The value part of an extended parameter consisting of three parts:
|
|
||||||
/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`),
|
|
||||||
/// and a character sequence representing the actual value (`value`), separated by single quote
|
|
||||||
/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct ExtendedValue {
|
|
||||||
/// The character set that is used to encode the `value` to a string.
|
|
||||||
pub charset: Charset,
|
|
||||||
/// The human language details of the `value`, if available.
|
|
||||||
pub language_tag: Option<LanguageTag>,
|
|
||||||
/// The parameter value, as expressed in octets.
|
|
||||||
pub value: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses extended header parameter values (`ext-value`), as defined in
|
|
||||||
/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
|
||||||
///
|
|
||||||
/// Extended values are denoted by parameter names that end with `*`.
|
|
||||||
///
|
|
||||||
/// ## ABNF
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// ext-value = charset "'" [ language ] "'" value-chars
|
|
||||||
/// ; like RFC 2231's <extended-initial-value>
|
|
||||||
/// ; (see [RFC2231], Section 7)
|
|
||||||
///
|
|
||||||
/// charset = "UTF-8" / "ISO-8859-1" / mime-charset
|
|
||||||
///
|
|
||||||
/// mime-charset = 1*mime-charsetc
|
|
||||||
/// mime-charsetc = ALPHA / DIGIT
|
|
||||||
/// / "!" / "#" / "$" / "%" / "&"
|
|
||||||
/// / "+" / "-" / "^" / "_" / "`"
|
|
||||||
/// / "{" / "}" / "~"
|
|
||||||
/// ; as <mime-charset> in Section 2.3 of [RFC2978]
|
|
||||||
/// ; except that the single quote is not included
|
|
||||||
/// ; SHOULD be registered in the IANA charset registry
|
|
||||||
///
|
|
||||||
/// language = <Language-Tag, defined in [RFC5646], Section 2.1>
|
|
||||||
///
|
|
||||||
/// value-chars = *( pct-encoded / attr-char )
|
|
||||||
///
|
|
||||||
/// pct-encoded = "%" HEXDIG HEXDIG
|
|
||||||
/// ; see [RFC3986], Section 2.1
|
|
||||||
///
|
|
||||||
/// attr-char = ALPHA / DIGIT
|
|
||||||
/// / "!" / "#" / "$" / "&" / "+" / "-" / "."
|
|
||||||
/// / "^" / "_" / "`" / "|" / "~"
|
|
||||||
/// ; token except ( "*" / "'" / "%" )
|
|
||||||
/// ```
|
|
||||||
pub fn parse_extended_value(
|
|
||||||
val: &str,
|
|
||||||
) -> Result<ExtendedValue, crate::error::ParseError> {
|
|
||||||
// Break into three pieces separated by the single-quote character
|
|
||||||
let mut parts = val.splitn(3, '\'');
|
|
||||||
|
|
||||||
// Interpret the first piece as a Charset
|
|
||||||
let charset: Charset = match parts.next() {
|
|
||||||
None => return Err(crate::error::ParseError::Header),
|
|
||||||
Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Interpret the second piece as a language tag
|
|
||||||
let language_tag: Option<LanguageTag> = match parts.next() {
|
|
||||||
None => return Err(crate::error::ParseError::Header),
|
|
||||||
Some("") => None,
|
|
||||||
Some(s) => match s.parse() {
|
|
||||||
Ok(lt) => Some(lt),
|
|
||||||
Err(_) => return Err(crate::error::ParseError::Header),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Interpret the third piece as a sequence of value characters
|
|
||||||
let value: Vec<u8> = match parts.next() {
|
|
||||||
None => return Err(crate::error::ParseError::Header),
|
|
||||||
Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(ExtendedValue {
|
|
||||||
value,
|
|
||||||
charset,
|
|
||||||
language_tag,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ExtendedValue {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let encoded_value =
|
|
||||||
percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
|
|
||||||
if let Some(ref lang) = self.language_tag {
|
|
||||||
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
|
|
||||||
} else {
|
|
||||||
write!(f, "{}''{}", self.charset, encoded_value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Percent encode a sequence of bytes with a character set defined in
|
|
||||||
/// <https://tools.ietf.org/html/rfc5987#section-3.2>
|
|
||||||
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
|
|
||||||
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
|
|
||||||
fmt::Display::fmt(&encoded, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert http::HeaderMap to a HeaderMap
|
|
||||||
impl From<http::HeaderMap> for HeaderMap {
|
impl From<http::HeaderMap> for HeaderMap {
|
||||||
fn from(map: http::HeaderMap) -> HeaderMap {
|
fn from(mut map: http::HeaderMap) -> HeaderMap {
|
||||||
let mut new_map = HeaderMap::with_capacity(map.capacity());
|
HeaderMap::from_drain(map.drain())
|
||||||
for (h, v) in map.iter() {
|
|
||||||
new_map.append(h.clone(), v.clone());
|
|
||||||
}
|
|
||||||
new_map
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This encode set is used for HTTP header values and is defined at
|
/// This encode set is used for HTTP header values and is defined at
|
||||||
// https://tools.ietf.org/html/rfc5987#section-3.2
|
/// https://tools.ietf.org/html/rfc5987#section-3.2.
|
||||||
pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS
|
pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS
|
||||||
.add(b' ')
|
.add(b' ')
|
||||||
.add(b'"')
|
.add(b'"')
|
||||||
@ -410,91 +99,3 @@ pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS
|
|||||||
.add(b']')
|
.add(b']')
|
||||||
.add(b'{')
|
.add(b'{')
|
||||||
.add(b'}');
|
.add(b'}');
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::shared::Charset;
|
|
||||||
use super::{parse_extended_value, ExtendedValue};
|
|
||||||
use language_tags::LanguageTag;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_extended_value_with_encoding_and_language_tag() {
|
|
||||||
let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
|
|
||||||
// RFC 5987, Section 3.2.2
|
|
||||||
// Extended notation, using the Unicode character U+00A3 (POUND SIGN)
|
|
||||||
let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
|
|
||||||
assert!(result.is_ok());
|
|
||||||
let extended_value = result.unwrap();
|
|
||||||
assert_eq!(Charset::Iso_8859_1, extended_value.charset);
|
|
||||||
assert!(extended_value.language_tag.is_some());
|
|
||||||
assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
|
|
||||||
assert_eq!(
|
|
||||||
vec![163, b' ', b'r', b'a', b't', b'e', b's'],
|
|
||||||
extended_value.value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_extended_value_with_encoding() {
|
|
||||||
// RFC 5987, Section 3.2.2
|
|
||||||
// Extended notation, using the Unicode characters U+00A3 (POUND SIGN)
|
|
||||||
// and U+20AC (EURO SIGN)
|
|
||||||
let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
|
|
||||||
assert!(result.is_ok());
|
|
||||||
let extended_value = result.unwrap();
|
|
||||||
assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
|
|
||||||
assert!(extended_value.language_tag.is_none());
|
|
||||||
assert_eq!(
|
|
||||||
vec![
|
|
||||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
|
||||||
b't', b'e', b's',
|
|
||||||
],
|
|
||||||
extended_value.value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_extended_value_missing_language_tag_and_encoding() {
|
|
||||||
// From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2
|
|
||||||
let result = parse_extended_value("foo%20bar.html");
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_extended_value_partially_formatted() {
|
|
||||||
let result = parse_extended_value("UTF-8'missing third part");
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_extended_value_partially_formatted_blank() {
|
|
||||||
let result = parse_extended_value("blank second part'");
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fmt_extended_value_with_encoding_and_language_tag() {
|
|
||||||
let extended_value = ExtendedValue {
|
|
||||||
charset: Charset::Iso_8859_1,
|
|
||||||
language_tag: Some("en".parse().expect("Could not parse language tag")),
|
|
||||||
value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
|
|
||||||
};
|
|
||||||
assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fmt_extended_value_with_encoding() {
|
|
||||||
let extended_value = ExtendedValue {
|
|
||||||
charset: Charset::Ext("UTF-8".to_string()),
|
|
||||||
language_tag: None,
|
|
||||||
value: vec![
|
|
||||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
|
||||||
b't', b'e', b's',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
"UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
|
|
||||||
format!("{}", extended_value)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -161,7 +161,7 @@ impl FromStr for EntityTag {
|
|||||||
impl IntoHeaderValue for EntityTag {
|
impl IntoHeaderValue for EntityTag {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
let mut wrt = Writer::new();
|
let mut wrt = Writer::new();
|
||||||
write!(wrt, "{}", self).unwrap();
|
write!(wrt, "{}", self).unwrap();
|
||||||
HeaderValue::from_maybe_shared(wrt.take())
|
HeaderValue::from_maybe_shared(wrt.take())
|
||||||
|
193
actix-http/src/header/shared/extended.rs
Normal file
193
actix-http/src/header/shared/extended.rs
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
|
use language_tags::LanguageTag;
|
||||||
|
|
||||||
|
use crate::header::{Charset, HTTP_VALUE};
|
||||||
|
|
||||||
|
// From hyper v0.11.27 src/header/parsing.rs
|
||||||
|
|
||||||
|
/// The value part of an extended parameter consisting of three parts:
|
||||||
|
/// - The REQUIRED character set name (`charset`).
|
||||||
|
/// - The OPTIONAL language information (`language_tag`).
|
||||||
|
/// - A character sequence representing the actual value (`value`), separated by single quotes.
|
||||||
|
///
|
||||||
|
/// It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct ExtendedValue {
|
||||||
|
/// The character set that is used to encode the `value` to a string.
|
||||||
|
pub charset: Charset,
|
||||||
|
|
||||||
|
/// The human language details of the `value`, if available.
|
||||||
|
pub language_tag: Option<LanguageTag>,
|
||||||
|
|
||||||
|
/// The parameter value, as expressed in octets.
|
||||||
|
pub value: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses extended header parameter values (`ext-value`), as defined in
|
||||||
|
/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
||||||
|
///
|
||||||
|
/// Extended values are denoted by parameter names that end with `*`.
|
||||||
|
///
|
||||||
|
/// ## ABNF
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// ext-value = charset "'" [ language ] "'" value-chars
|
||||||
|
/// ; like RFC 2231's <extended-initial-value>
|
||||||
|
/// ; (see [RFC2231], Section 7)
|
||||||
|
///
|
||||||
|
/// charset = "UTF-8" / "ISO-8859-1" / mime-charset
|
||||||
|
///
|
||||||
|
/// mime-charset = 1*mime-charsetc
|
||||||
|
/// mime-charsetc = ALPHA / DIGIT
|
||||||
|
/// / "!" / "#" / "$" / "%" / "&"
|
||||||
|
/// / "+" / "-" / "^" / "_" / "`"
|
||||||
|
/// / "{" / "}" / "~"
|
||||||
|
/// ; as <mime-charset> in Section 2.3 of [RFC2978]
|
||||||
|
/// ; except that the single quote is not included
|
||||||
|
/// ; SHOULD be registered in the IANA charset registry
|
||||||
|
///
|
||||||
|
/// language = <Language-Tag, defined in [RFC5646], Section 2.1>
|
||||||
|
///
|
||||||
|
/// value-chars = *( pct-encoded / attr-char )
|
||||||
|
///
|
||||||
|
/// pct-encoded = "%" HEXDIG HEXDIG
|
||||||
|
/// ; see [RFC3986], Section 2.1
|
||||||
|
///
|
||||||
|
/// attr-char = ALPHA / DIGIT
|
||||||
|
/// / "!" / "#" / "$" / "&" / "+" / "-" / "."
|
||||||
|
/// / "^" / "_" / "`" / "|" / "~"
|
||||||
|
/// ; token except ( "*" / "'" / "%" )
|
||||||
|
/// ```
|
||||||
|
pub fn parse_extended_value(
|
||||||
|
val: &str,
|
||||||
|
) -> Result<ExtendedValue, crate::error::ParseError> {
|
||||||
|
// Break into three pieces separated by the single-quote character
|
||||||
|
let mut parts = val.splitn(3, '\'');
|
||||||
|
|
||||||
|
// Interpret the first piece as a Charset
|
||||||
|
let charset: Charset = match parts.next() {
|
||||||
|
None => return Err(crate::error::ParseError::Header),
|
||||||
|
Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interpret the second piece as a language tag
|
||||||
|
let language_tag: Option<LanguageTag> = match parts.next() {
|
||||||
|
None => return Err(crate::error::ParseError::Header),
|
||||||
|
Some("") => None,
|
||||||
|
Some(s) => match s.parse() {
|
||||||
|
Ok(lt) => Some(lt),
|
||||||
|
Err(_) => return Err(crate::error::ParseError::Header),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interpret the third piece as a sequence of value characters
|
||||||
|
let value: Vec<u8> = match parts.next() {
|
||||||
|
None => return Err(crate::error::ParseError::Header),
|
||||||
|
Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ExtendedValue {
|
||||||
|
value,
|
||||||
|
charset,
|
||||||
|
language_tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ExtendedValue {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let encoded_value =
|
||||||
|
percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
|
||||||
|
if let Some(ref lang) = self.language_tag {
|
||||||
|
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
|
||||||
|
} else {
|
||||||
|
write!(f, "{}''{}", self.charset, encoded_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_extended_value_with_encoding_and_language_tag() {
|
||||||
|
let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
|
||||||
|
// RFC 5987, Section 3.2.2
|
||||||
|
// Extended notation, using the Unicode character U+00A3 (POUND SIGN)
|
||||||
|
let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let extended_value = result.unwrap();
|
||||||
|
assert_eq!(Charset::Iso_8859_1, extended_value.charset);
|
||||||
|
assert!(extended_value.language_tag.is_some());
|
||||||
|
assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
vec![163, b' ', b'r', b'a', b't', b'e', b's'],
|
||||||
|
extended_value.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_extended_value_with_encoding() {
|
||||||
|
// RFC 5987, Section 3.2.2
|
||||||
|
// Extended notation, using the Unicode characters U+00A3 (POUND SIGN)
|
||||||
|
// and U+20AC (EURO SIGN)
|
||||||
|
let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let extended_value = result.unwrap();
|
||||||
|
assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
|
||||||
|
assert!(extended_value.language_tag.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
vec![
|
||||||
|
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
||||||
|
b't', b'e', b's',
|
||||||
|
],
|
||||||
|
extended_value.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_extended_value_missing_language_tag_and_encoding() {
|
||||||
|
// From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2
|
||||||
|
let result = parse_extended_value("foo%20bar.html");
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_extended_value_partially_formatted() {
|
||||||
|
let result = parse_extended_value("UTF-8'missing third part");
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_extended_value_partially_formatted_blank() {
|
||||||
|
let result = parse_extended_value("blank second part'");
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fmt_extended_value_with_encoding_and_language_tag() {
|
||||||
|
let extended_value = ExtendedValue {
|
||||||
|
charset: Charset::Iso_8859_1,
|
||||||
|
language_tag: Some("en".parse().expect("Could not parse language tag")),
|
||||||
|
value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
|
||||||
|
};
|
||||||
|
assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fmt_extended_value_with_encoding() {
|
||||||
|
let extended_value = ExtendedValue {
|
||||||
|
charset: Charset::Ext("UTF-8".to_string()),
|
||||||
|
language_tag: None,
|
||||||
|
value: vec![
|
||||||
|
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
||||||
|
b't', b'e', b's',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
"UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
|
||||||
|
format!("{}", extended_value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -48,7 +48,7 @@ impl From<SystemTime> for HttpDate {
|
|||||||
impl IntoHeaderValue for HttpDate {
|
impl IntoHeaderValue for HttpDate {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
let mut wrt = BytesMut::with_capacity(29).writer();
|
let mut wrt = BytesMut::with_capacity(29).writer();
|
||||||
write!(
|
write!(
|
||||||
wrt,
|
wrt,
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
//! Copied for `hyper::header::shared`;
|
//! Originally taken from `hyper::header::shared`.
|
||||||
|
|
||||||
pub use self::charset::Charset;
|
|
||||||
pub use self::encoding::Encoding;
|
|
||||||
pub use self::entity::EntityTag;
|
|
||||||
pub use self::httpdate::HttpDate;
|
|
||||||
pub use self::quality_item::{q, qitem, Quality, QualityItem};
|
|
||||||
pub use language_tags::LanguageTag;
|
|
||||||
|
|
||||||
mod charset;
|
mod charset;
|
||||||
mod encoding;
|
mod encoding;
|
||||||
mod entity;
|
mod entity;
|
||||||
|
mod extended;
|
||||||
mod httpdate;
|
mod httpdate;
|
||||||
mod quality_item;
|
mod quality_item;
|
||||||
|
|
||||||
|
pub use self::charset::Charset;
|
||||||
|
pub use self::encoding::Encoding;
|
||||||
|
pub use self::entity::EntityTag;
|
||||||
|
pub use self::extended::{parse_extended_value, ExtendedValue};
|
||||||
|
pub use self::httpdate::HttpDate;
|
||||||
|
pub use self::quality_item::{q, qitem, Quality, QualityItem};
|
||||||
|
pub use language_tags::LanguageTag;
|
||||||
|
63
actix-http/src/header/utils.rs
Normal file
63
actix-http/src/header/utils.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
|
use http::HeaderValue;
|
||||||
|
|
||||||
|
use crate::{error::ParseError, header::HTTP_VALUE};
|
||||||
|
|
||||||
|
/// Reads a comma-delimited raw header into a Vec.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_comma_delimited<'a, I, T>(all: I) -> Result<Vec<T>, ParseError>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a HeaderValue> + 'a,
|
||||||
|
T: FromStr,
|
||||||
|
{
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for h in all {
|
||||||
|
let s = h.to_str().map_err(|_| ParseError::Header)?;
|
||||||
|
result.extend(
|
||||||
|
s.split(',')
|
||||||
|
.filter_map(|x| match x.trim() {
|
||||||
|
"" => None,
|
||||||
|
y => Some(y),
|
||||||
|
})
|
||||||
|
.filter_map(|x| x.trim().parse().ok()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a single string when parsing a header.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_one_raw_str<T: FromStr>(val: Option<&HeaderValue>) -> Result<T, ParseError> {
|
||||||
|
if let Some(line) = val {
|
||||||
|
let line = line.to_str().map_err(|_| ParseError::Header)?;
|
||||||
|
if !line.is_empty() {
|
||||||
|
return T::from_str(line).or(Err(ParseError::Header));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ParseError::Header)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format an array into a comma-delimited string.
|
||||||
|
#[inline]
|
||||||
|
pub fn fmt_comma_delimited<T>(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result
|
||||||
|
where
|
||||||
|
T: fmt::Display,
|
||||||
|
{
|
||||||
|
let mut iter = parts.iter();
|
||||||
|
if let Some(part) = iter.next() {
|
||||||
|
fmt::Display::fmt(part, f)?;
|
||||||
|
}
|
||||||
|
for part in iter {
|
||||||
|
f.write_str(", ")?;
|
||||||
|
fmt::Display::fmt(part, f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Percent encode a sequence of bytes with a character set defined in
|
||||||
|
/// <https://tools.ietf.org/html/rfc5987#section-3.2>
|
||||||
|
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
|
||||||
|
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
|
||||||
|
fmt::Display::fmt(&encoded, f)
|
||||||
|
}
|
@ -13,7 +13,7 @@ use crate::payload::Payload;
|
|||||||
|
|
||||||
struct Cookies(Vec<Cookie<'static>>);
|
struct Cookies(Vec<Cookie<'static>>);
|
||||||
|
|
||||||
/// Trait that implements general purpose operations on http messages
|
/// Trait that implements general purpose operations on HTTP messages.
|
||||||
pub trait HttpMessage: Sized {
|
pub trait HttpMessage: Sized {
|
||||||
/// Type of message payload stream
|
/// Type of message payload stream
|
||||||
type Stream;
|
type Stream;
|
||||||
@ -30,8 +30,8 @@ pub trait HttpMessage: Sized {
|
|||||||
/// Mutable reference to a the request's extensions container
|
/// Mutable reference to a the request's extensions container
|
||||||
fn extensions_mut(&self) -> RefMut<'_, Extensions>;
|
fn extensions_mut(&self) -> RefMut<'_, Extensions>;
|
||||||
|
|
||||||
|
/// Get a header.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Get a header
|
|
||||||
fn get_header<H: Header>(&self) -> Option<H>
|
fn get_header<H: Header>(&self) -> Option<H>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
@ -43,8 +43,8 @@ pub trait HttpMessage: Sized {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the request content type. If request does not contain
|
/// Read the request content type. If request did not contain a *Content-Type* header, an empty
|
||||||
/// *Content-Type* header, empty str get returned.
|
/// string is returned.
|
||||||
fn content_type(&self) -> &str {
|
fn content_type(&self) -> &str {
|
||||||
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
|
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
|
||||||
if let Ok(content_type) = content_type.to_str() {
|
if let Ok(content_type) = content_type.to_str() {
|
||||||
@ -90,7 +90,7 @@ pub trait HttpMessage: Sized {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if request has chunked transfer encoding
|
/// Check if request has chunked transfer encoding.
|
||||||
fn chunked(&self) -> Result<bool, ParseError> {
|
fn chunked(&self) -> Result<bool, ParseError> {
|
||||||
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
|
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
|
||||||
if let Ok(s) = encodings.to_str() {
|
if let Ok(s) = encodings.to_str() {
|
||||||
@ -173,11 +173,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_content_type() {
|
fn test_content_type() {
|
||||||
let req = TestRequest::with_header("content-type", "text/plain").finish();
|
let req = TestRequest::default()
|
||||||
|
.insert_header(("content-type", "text/plain"))
|
||||||
|
.finish();
|
||||||
assert_eq!(req.content_type(), "text/plain");
|
assert_eq!(req.content_type(), "text/plain");
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header("content-type", "application/json; charset=utf=8")
|
.insert_header(("content-type", "application/json; charset=utf=8"))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(req.content_type(), "application/json");
|
assert_eq!(req.content_type(), "application/json");
|
||||||
let req = TestRequest::default().finish();
|
let req = TestRequest::default().finish();
|
||||||
assert_eq!(req.content_type(), "");
|
assert_eq!(req.content_type(), "");
|
||||||
@ -185,13 +187,15 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mime_type() {
|
fn test_mime_type() {
|
||||||
let req = TestRequest::with_header("content-type", "application/json").finish();
|
let req = TestRequest::default()
|
||||||
|
.insert_header(("content-type", "application/json"))
|
||||||
|
.finish();
|
||||||
assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON));
|
assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON));
|
||||||
let req = TestRequest::default().finish();
|
let req = TestRequest::default().finish();
|
||||||
assert_eq!(req.mime_type().unwrap(), None);
|
assert_eq!(req.mime_type().unwrap(), None);
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header("content-type", "application/json; charset=utf-8")
|
.insert_header(("content-type", "application/json; charset=utf-8"))
|
||||||
.finish();
|
.finish();
|
||||||
let mt = req.mime_type().unwrap().unwrap();
|
let mt = req.mime_type().unwrap().unwrap();
|
||||||
assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8));
|
assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8));
|
||||||
assert_eq!(mt.type_(), mime::APPLICATION);
|
assert_eq!(mt.type_(), mime::APPLICATION);
|
||||||
@ -200,11 +204,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mime_type_error() {
|
fn test_mime_type_error() {
|
||||||
let req = TestRequest::with_header(
|
let req = TestRequest::default()
|
||||||
"content-type",
|
.insert_header(("content-type", "applicationadfadsfasdflknadsfklnadsfjson"))
|
||||||
"applicationadfadsfasdflknadsfklnadsfjson",
|
.finish();
|
||||||
)
|
|
||||||
.finish();
|
|
||||||
assert_eq!(Err(ContentTypeError::ParseError), req.mime_type());
|
assert_eq!(Err(ContentTypeError::ParseError), req.mime_type());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,27 +215,27 @@ mod tests {
|
|||||||
let req = TestRequest::default().finish();
|
let req = TestRequest::default().finish();
|
||||||
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
|
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
|
||||||
|
|
||||||
let req = TestRequest::with_header("content-type", "application/json").finish();
|
let req = TestRequest::default()
|
||||||
|
.insert_header(("content-type", "application/json"))
|
||||||
|
.finish();
|
||||||
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
|
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
|
||||||
|
|
||||||
let req = TestRequest::with_header(
|
let req = TestRequest::default()
|
||||||
"content-type",
|
.insert_header(("content-type", "application/json; charset=ISO-8859-2"))
|
||||||
"application/json; charset=ISO-8859-2",
|
.finish();
|
||||||
)
|
|
||||||
.finish();
|
|
||||||
assert_eq!(ISO_8859_2, req.encoding().unwrap());
|
assert_eq!(ISO_8859_2, req.encoding().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encoding_error() {
|
fn test_encoding_error() {
|
||||||
let req = TestRequest::with_header("content-type", "applicatjson").finish();
|
let req = TestRequest::default()
|
||||||
|
.insert_header(("content-type", "applicatjson"))
|
||||||
|
.finish();
|
||||||
assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
|
assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
|
||||||
|
|
||||||
let req = TestRequest::with_header(
|
let req = TestRequest::default()
|
||||||
"content-type",
|
.insert_header(("content-type", "application/json; charset=kkkttktk"))
|
||||||
"application/json; charset=kkkttktk",
|
.finish();
|
||||||
)
|
|
||||||
.finish();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(ContentTypeError::UnknownEncoding),
|
Some(ContentTypeError::UnknownEncoding),
|
||||||
req.encoding().err()
|
req.encoding().err()
|
||||||
@ -245,15 +247,16 @@ mod tests {
|
|||||||
let req = TestRequest::default().finish();
|
let req = TestRequest::default().finish();
|
||||||
assert!(!req.chunked().unwrap());
|
assert!(!req.chunked().unwrap());
|
||||||
|
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
|
.finish();
|
||||||
assert!(req.chunked().unwrap());
|
assert!(req.chunked().unwrap());
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::TRANSFER_ENCODING,
|
header::TRANSFER_ENCODING,
|
||||||
Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
|
Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
|
||||||
)
|
))
|
||||||
.finish();
|
.finish();
|
||||||
assert!(req.chunked().is_err());
|
assert!(req.chunked().is_err());
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ pub use self::response::{Response, ResponseBuilder};
|
|||||||
pub use self::service::HttpService;
|
pub use self::service::HttpService;
|
||||||
|
|
||||||
pub mod http {
|
pub mod http {
|
||||||
//! Various HTTP related types
|
//! Various HTTP related types.
|
||||||
|
|
||||||
// re-exports
|
// re-exports
|
||||||
pub use http::header::{HeaderName, HeaderValue};
|
pub use http::header::{HeaderName, HeaderValue};
|
||||||
@ -64,7 +64,7 @@ pub mod http {
|
|||||||
pub use crate::cookie::{Cookie, CookieBuilder};
|
pub use crate::cookie::{Cookie, CookieBuilder};
|
||||||
pub use crate::header::HeaderMap;
|
pub use crate::header::HeaderMap;
|
||||||
|
|
||||||
/// Various http headers
|
/// A collection of HTTP headers and helpers.
|
||||||
pub mod header {
|
pub mod header {
|
||||||
pub use crate::header::*;
|
pub use crate::header::*;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ use std::net;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use copyless::BoxHelper;
|
|
||||||
|
|
||||||
use crate::extensions::Extensions;
|
use crate::extensions::Extensions;
|
||||||
use crate::header::HeaderMap;
|
use crate::header::HeaderMap;
|
||||||
@ -35,7 +34,9 @@ bitflags! {
|
|||||||
pub trait Head: Default + 'static {
|
pub trait Head: Default + 'static {
|
||||||
fn clear(&mut self);
|
fn clear(&mut self);
|
||||||
|
|
||||||
fn pool() -> &'static MessagePool<Self>;
|
fn with_pool<F, R>(f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&MessagePool<Self>) -> R;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -70,8 +71,11 @@ impl Head for RequestHead {
|
|||||||
self.extensions.get_mut().clear();
|
self.extensions.get_mut().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pool() -> &'static MessagePool<Self> {
|
fn with_pool<F, R>(f: F) -> R
|
||||||
REQUEST_POOL.with(|p| *p)
|
where
|
||||||
|
F: FnOnce(&MessagePool<Self>) -> R,
|
||||||
|
{
|
||||||
|
REQUEST_POOL.with(|p| f(p))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,21 +343,15 @@ impl ResponseHead {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Message<T: Head> {
|
pub struct Message<T: Head> {
|
||||||
|
// Rc here should not be cloned by anyone.
|
||||||
|
// It's used to reuse allocation of T and no shared ownership is allowed.
|
||||||
head: Rc<T>,
|
head: Rc<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Head> Message<T> {
|
impl<T: Head> Message<T> {
|
||||||
/// Get new message from the pool of objects
|
/// Get new message from the pool of objects
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
T::pool().get_message()
|
T::with_pool(|p| p.get_message())
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Head> Clone for Message<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Message {
|
|
||||||
head: self.head.clone(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,9 +371,7 @@ impl<T: Head> std::ops::DerefMut for Message<T> {
|
|||||||
|
|
||||||
impl<T: Head> Drop for Message<T> {
|
impl<T: Head> Drop for Message<T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if Rc::strong_count(&self.head) == 1 {
|
T::with_pool(|p| p.release(self.head.clone()))
|
||||||
T::pool().release(self.head.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,18 +423,17 @@ pub struct MessagePool<T: Head>(RefCell<Vec<Rc<T>>>);
|
|||||||
/// Request's objects pool
|
/// Request's objects pool
|
||||||
pub struct BoxedResponsePool(RefCell<Vec<Box<ResponseHead>>>);
|
pub struct BoxedResponsePool(RefCell<Vec<Box<ResponseHead>>>);
|
||||||
|
|
||||||
thread_local!(static REQUEST_POOL: &'static MessagePool<RequestHead> = MessagePool::<RequestHead>::create());
|
thread_local!(static REQUEST_POOL: MessagePool<RequestHead> = MessagePool::<RequestHead>::create());
|
||||||
thread_local!(static RESPONSE_POOL: &'static BoxedResponsePool = BoxedResponsePool::create());
|
thread_local!(static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create());
|
||||||
|
|
||||||
impl<T: Head> MessagePool<T> {
|
impl<T: Head> MessagePool<T> {
|
||||||
fn create() -> &'static MessagePool<T> {
|
fn create() -> MessagePool<T> {
|
||||||
let pool = MessagePool(RefCell::new(Vec::with_capacity(128)));
|
MessagePool(RefCell::new(Vec::with_capacity(128)))
|
||||||
Box::leak(Box::new(pool))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get message from the pool
|
/// Get message from the pool
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_message(&'static self) -> Message<T> {
|
fn get_message(&self) -> Message<T> {
|
||||||
if let Some(mut msg) = self.0.borrow_mut().pop() {
|
if let Some(mut msg) = self.0.borrow_mut().pop() {
|
||||||
// Message is put in pool only when it's the last copy.
|
// Message is put in pool only when it's the last copy.
|
||||||
// which means it's guaranteed to be unique when popped out.
|
// which means it's guaranteed to be unique when popped out.
|
||||||
@ -464,14 +459,13 @@ impl<T: Head> MessagePool<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BoxedResponsePool {
|
impl BoxedResponsePool {
|
||||||
fn create() -> &'static BoxedResponsePool {
|
fn create() -> BoxedResponsePool {
|
||||||
let pool = BoxedResponsePool(RefCell::new(Vec::with_capacity(128)));
|
BoxedResponsePool(RefCell::new(Vec::with_capacity(128)))
|
||||||
Box::leak(Box::new(pool))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get message from the pool
|
/// Get message from the pool
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_message(&'static self, status: StatusCode) -> BoxedResponseHead {
|
fn get_message(&self, status: StatusCode) -> BoxedResponseHead {
|
||||||
if let Some(mut head) = self.0.borrow_mut().pop() {
|
if let Some(mut head) = self.0.borrow_mut().pop() {
|
||||||
head.reason = None;
|
head.reason = None;
|
||||||
head.status = status;
|
head.status = status;
|
||||||
@ -480,17 +474,17 @@ impl BoxedResponsePool {
|
|||||||
BoxedResponseHead { head: Some(head) }
|
BoxedResponseHead { head: Some(head) }
|
||||||
} else {
|
} else {
|
||||||
BoxedResponseHead {
|
BoxedResponseHead {
|
||||||
head: Some(Box::alloc().init(ResponseHead::new(status))),
|
head: Some(Box::new(ResponseHead::new(status))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Release request instance
|
/// Release request instance
|
||||||
fn release(&self, msg: Box<ResponseHead>) {
|
fn release(&self, mut msg: Box<ResponseHead>) {
|
||||||
let v = &mut self.0.borrow_mut();
|
let v = &mut self.0.borrow_mut();
|
||||||
if v.len() < 128 {
|
if v.len() < 128 {
|
||||||
msg.extensions.borrow_mut().clear();
|
msg.extensions.get_mut().clear();
|
||||||
v.push(msg);
|
v.push(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
use std::cell::{Ref, RefMut};
|
//! HTTP requests.
|
||||||
use std::{fmt, net};
|
|
||||||
|
use std::{
|
||||||
|
cell::{Ref, RefMut},
|
||||||
|
fmt, net,
|
||||||
|
};
|
||||||
|
|
||||||
use http::{header, Method, Uri, Version};
|
use http::{header, Method, Uri, Version};
|
||||||
|
|
||||||
@ -173,13 +177,17 @@ impl<P> fmt::Debug for Request<P> {
|
|||||||
self.method(),
|
self.method(),
|
||||||
self.path()
|
self.path()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(q) = self.uri().query().as_ref() {
|
if let Some(q) = self.uri().query().as_ref() {
|
||||||
writeln!(f, " query: ?{:?}", q)?;
|
writeln!(f, " query: ?{:?}", q)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(f, " headers:")?;
|
writeln!(f, " headers:")?;
|
||||||
for (key, val) in self.headers() {
|
|
||||||
|
for (key, val) in self.headers().iter() {
|
||||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
//! Http response
|
//! HTTP responses.
|
||||||
use std::cell::{Ref, RefMut};
|
|
||||||
use std::convert::TryFrom;
|
use std::{
|
||||||
use std::future::Future;
|
cell::{Ref, RefMut},
|
||||||
use std::pin::Pin;
|
convert::TryInto,
|
||||||
use std::task::{Context, Poll};
|
fmt,
|
||||||
use std::{fmt, str};
|
future::Future,
|
||||||
|
ops,
|
||||||
|
pin::Pin,
|
||||||
|
str,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
@ -14,7 +19,7 @@ use crate::body::{Body, BodyStream, MessageBody, ResponseBody};
|
|||||||
use crate::cookie::{Cookie, CookieJar};
|
use crate::cookie::{Cookie, CookieJar};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::extensions::Extensions;
|
use crate::extensions::Extensions;
|
||||||
use crate::header::{Header, IntoHeaderValue};
|
use crate::header::{IntoHeaderPair, IntoHeaderValue};
|
||||||
use crate::http::header::{self, HeaderName, HeaderValue};
|
use crate::http::header::{self, HeaderName, HeaderValue};
|
||||||
use crate::http::{Error as HttpError, HeaderMap, StatusCode};
|
use crate::http::{Error as HttpError, HeaderMap, StatusCode};
|
||||||
use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead};
|
use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead};
|
||||||
@ -341,93 +346,98 @@ impl ResponseBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a header.
|
/// Insert a header, replacing any that were set with an equivalent field name.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_http::{http, Request, Response, Result};
|
/// # use actix_http::Response;
|
||||||
|
/// use actix_http::http::header::ContentType;
|
||||||
///
|
///
|
||||||
/// fn index(req: Request) -> Result<Response> {
|
/// Response::Ok()
|
||||||
/// Ok(Response::Ok()
|
/// .insert_header(ContentType(mime::APPLICATION_JSON))
|
||||||
/// .set(http::header::IfModifiedSince(
|
/// .insert_header(("X-TEST", "value"))
|
||||||
/// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?,
|
/// .finish();
|
||||||
/// ))
|
|
||||||
/// .finish())
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
#[doc(hidden)]
|
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
|
||||||
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
|
where
|
||||||
|
H: IntoHeaderPair,
|
||||||
|
{
|
||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
match hdr.try_into() {
|
match header.try_into_header_pair() {
|
||||||
Ok(value) => {
|
Ok((key, value)) => {
|
||||||
parts.headers.append(H::name(), value);
|
parts.headers.insert(key, value);
|
||||||
}
|
}
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a header to existing headers.
|
/// Append a header, keeping any that were set with an equivalent field name.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_http::{http, Request, Response};
|
/// # use actix_http::Response;
|
||||||
|
/// use actix_http::http::header::ContentType;
|
||||||
///
|
///
|
||||||
/// fn index(req: Request) -> Response {
|
/// Response::Ok()
|
||||||
/// Response::Ok()
|
/// .append_header(ContentType(mime::APPLICATION_JSON))
|
||||||
/// .header("X-TEST", "value")
|
/// .append_header(("X-TEST", "value1"))
|
||||||
/// .header(http::header::CONTENT_TYPE, "application/json")
|
/// .append_header(("X-TEST", "value2"))
|
||||||
/// .finish()
|
/// .finish();
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
pub fn append_header<H>(&mut self, header: H) -> &mut Self
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
H: IntoHeaderPair,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: IntoHeaderValue,
|
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
match HeaderName::try_from(key) {
|
match header.try_into_header_pair() {
|
||||||
Ok(key) => match value.try_into() {
|
Ok((key, value)) => parts.headers.append(key, value),
|
||||||
Ok(value) => {
|
|
||||||
parts.headers.append(key, value);
|
|
||||||
}
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
},
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a header.
|
/// Replaced with [`Self::insert_header()`].
|
||||||
///
|
#[deprecated = "Replaced with `insert_header((key, value))`."]
|
||||||
/// ```rust
|
|
||||||
/// use actix_http::{http, Request, Response};
|
|
||||||
///
|
|
||||||
/// fn index(req: Request) -> Response {
|
|
||||||
/// Response::Ok()
|
|
||||||
/// .set_header("X-TEST", "value")
|
|
||||||
/// .set_header(http::header::CONTENT_TYPE, "application/json")
|
|
||||||
/// .finish()
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
K: TryInto<HeaderName>,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
K::Error: Into<HttpError>,
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
if self.err.is_some() {
|
||||||
match HeaderName::try_from(key) {
|
return self;
|
||||||
Ok(key) => match value.try_into() {
|
|
||||||
Ok(value) => {
|
|
||||||
parts.headers.insert(key, value);
|
|
||||||
}
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
},
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match (key.try_into(), value.try_into_value()) {
|
||||||
|
(Ok(name), Ok(value)) => return self.insert_header((name, value)),
|
||||||
|
(Err(err), _) => self.err = Some(err.into()),
|
||||||
|
(_, Err(err)) => self.err = Some(err.into()),
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaced with [`Self::append_header()`].
|
||||||
|
#[deprecated = "Replaced with `append_header((key, value))`."]
|
||||||
|
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||||
|
where
|
||||||
|
K: TryInto<HeaderName>,
|
||||||
|
K::Error: Into<HttpError>,
|
||||||
|
V: IntoHeaderValue,
|
||||||
|
{
|
||||||
|
if self.err.is_some() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
match (key.try_into(), value.try_into_value()) {
|
||||||
|
(Ok(name), Ok(value)) => return self.append_header((name, value)),
|
||||||
|
(Err(err), _) => self.err = Some(err.into()),
|
||||||
|
(_, Err(err)) => self.err = Some(err.into()),
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,7 +468,12 @@ impl ResponseBuilder {
|
|||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
parts.set_connection_type(ConnectionType::Upgrade);
|
parts.set_connection_type(ConnectionType::Upgrade);
|
||||||
}
|
}
|
||||||
self.set_header(header::UPGRADE, value)
|
|
||||||
|
if let Ok(value) = value.try_into_value() {
|
||||||
|
self.insert_header((header::UPGRADE, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force close connection, even if it is marked as keep-alive
|
/// Force close connection, even if it is marked as keep-alive
|
||||||
@ -473,7 +488,7 @@ impl ResponseBuilder {
|
|||||||
/// Disable chunked transfer encoding for HTTP/1.1 streaming responses.
|
/// Disable chunked transfer encoding for HTTP/1.1 streaming responses.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn no_chunking(&mut self, len: u64) -> &mut Self {
|
pub fn no_chunking(&mut self, len: u64) -> &mut Self {
|
||||||
self.header(header::CONTENT_LENGTH, len);
|
self.insert_header((header::CONTENT_LENGTH, len));
|
||||||
|
|
||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
parts.no_chunking(true);
|
parts.no_chunking(true);
|
||||||
@ -481,15 +496,14 @@ impl ResponseBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set response content type
|
/// Set response content type.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
||||||
where
|
where
|
||||||
HeaderValue: TryFrom<V>,
|
V: IntoHeaderValue,
|
||||||
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
|
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
match HeaderValue::try_from(value) {
|
match value.try_into_value() {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
parts.headers.insert(header::CONTENT_TYPE, value);
|
parts.headers.insert(header::CONTENT_TYPE, value);
|
||||||
}
|
}
|
||||||
@ -640,27 +654,24 @@ impl ResponseBuilder {
|
|||||||
self.body(Body::from_message(BodyStream::new(stream)))
|
self.body(Body::from_message(BodyStream::new(stream)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Set a json body and generate `Response`
|
/// Set a json body and generate `Response`
|
||||||
///
|
///
|
||||||
/// `ResponseBuilder` can not be used after this call.
|
/// `ResponseBuilder` can not be used after this call.
|
||||||
pub fn json<T: Serialize>(&mut self, value: T) -> Response {
|
pub fn json<T>(&mut self, value: T) -> Response
|
||||||
self.json2(&value)
|
where
|
||||||
}
|
T: ops::Deref,
|
||||||
|
T::Target: Serialize,
|
||||||
/// Set a json body and generate `Response`
|
{
|
||||||
///
|
match serde_json::to_string(&*value) {
|
||||||
/// `ResponseBuilder` can not be used after this call.
|
|
||||||
pub fn json2<T: Serialize>(&mut self, value: &T) -> Response {
|
|
||||||
match serde_json::to_string(value) {
|
|
||||||
Ok(body) => {
|
Ok(body) => {
|
||||||
let contains = if let Some(parts) = parts(&mut self.head, &self.err) {
|
let contains = if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
parts.headers.contains_key(header::CONTENT_TYPE)
|
parts.headers.contains_key(header::CONTENT_TYPE)
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
|
||||||
if !contains {
|
if !contains {
|
||||||
self.header(header::CONTENT_TYPE, "application/json");
|
self.insert_header(header::ContentType(mime::APPLICATION_JSON));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.body(Body::from(body))
|
self.body(Body::from(body))
|
||||||
@ -743,9 +754,11 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
|
|||||||
let mut msg = BoxedResponseHead::new(head.status);
|
let mut msg = BoxedResponseHead::new(head.status);
|
||||||
msg.version = head.version;
|
msg.version = head.version;
|
||||||
msg.reason = head.reason;
|
msg.reason = head.reason;
|
||||||
for (k, v) in &head.headers {
|
|
||||||
|
for (k, v) in head.headers.iter() {
|
||||||
msg.headers.append(k.clone(), v.clone());
|
msg.headers.append(k.clone(), v.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.no_chunking(!head.chunked());
|
msg.no_chunking(!head.chunked());
|
||||||
|
|
||||||
ResponseBuilder {
|
ResponseBuilder {
|
||||||
@ -802,7 +815,7 @@ impl From<ResponseBuilder> for Response {
|
|||||||
impl From<&'static str> for Response {
|
impl From<&'static str> for Response {
|
||||||
fn from(val: &'static str) -> Self {
|
fn from(val: &'static str) -> Self {
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.content_type("text/plain; charset=utf-8")
|
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||||
.body(val)
|
.body(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -810,7 +823,7 @@ impl From<&'static str> for Response {
|
|||||||
impl From<&'static [u8]> for Response {
|
impl From<&'static [u8]> for Response {
|
||||||
fn from(val: &'static [u8]) -> Self {
|
fn from(val: &'static [u8]) -> Self {
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.content_type("application/octet-stream")
|
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||||
.body(val)
|
.body(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -818,7 +831,7 @@ impl From<&'static [u8]> for Response {
|
|||||||
impl From<String> for Response {
|
impl From<String> for Response {
|
||||||
fn from(val: String) -> Self {
|
fn from(val: String) -> Self {
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.content_type("text/plain; charset=utf-8")
|
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||||
.body(val)
|
.body(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -826,7 +839,7 @@ impl From<String> for Response {
|
|||||||
impl<'a> From<&'a String> for Response {
|
impl<'a> From<&'a String> for Response {
|
||||||
fn from(val: &'a String) -> Self {
|
fn from(val: &'a String) -> Self {
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.content_type("text/plain; charset=utf-8")
|
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||||
.body(val)
|
.body(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -834,7 +847,7 @@ impl<'a> From<&'a String> for Response {
|
|||||||
impl From<Bytes> for Response {
|
impl From<Bytes> for Response {
|
||||||
fn from(val: Bytes) -> Self {
|
fn from(val: Bytes) -> Self {
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.content_type("application/octet-stream")
|
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||||
.body(val)
|
.body(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -842,13 +855,15 @@ impl From<Bytes> for Response {
|
|||||||
impl From<BytesMut> for Response {
|
impl From<BytesMut> for Response {
|
||||||
fn from(val: BytesMut) -> Self {
|
fn from(val: BytesMut) -> Self {
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.content_type("application/octet-stream")
|
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||||
.body(val)
|
.body(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::body::Body;
|
use crate::body::Body;
|
||||||
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE};
|
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE};
|
||||||
@ -856,8 +871,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_debug() {
|
fn test_debug() {
|
||||||
let resp = Response::Ok()
|
let resp = Response::Ok()
|
||||||
.header(COOKIE, HeaderValue::from_static("cookie1=value1; "))
|
.append_header((COOKIE, HeaderValue::from_static("cookie1=value1; ")))
|
||||||
.header(COOKIE, HeaderValue::from_static("cookie2=value2; "))
|
.append_header((COOKIE, HeaderValue::from_static("cookie2=value2; ")))
|
||||||
.finish();
|
.finish();
|
||||||
let dbg = format!("{:?}", resp);
|
let dbg = format!("{:?}", resp);
|
||||||
assert!(dbg.contains("Response"));
|
assert!(dbg.contains("Response"));
|
||||||
@ -868,8 +883,8 @@ mod tests {
|
|||||||
use crate::httpmessage::HttpMessage;
|
use crate::httpmessage::HttpMessage;
|
||||||
|
|
||||||
let req = crate::test::TestRequest::default()
|
let req = crate::test::TestRequest::default()
|
||||||
.header(COOKIE, "cookie1=value1")
|
.append_header((COOKIE, "cookie1=value1"))
|
||||||
.header(COOKIE, "cookie2=value2")
|
.append_header((COOKIE, "cookie2=value2"))
|
||||||
.finish();
|
.finish();
|
||||||
let cookies = req.cookies().unwrap();
|
let cookies = req.cookies().unwrap();
|
||||||
|
|
||||||
@ -882,16 +897,20 @@ mod tests {
|
|||||||
.max_age(time::Duration::days(1))
|
.max_age(time::Duration::days(1))
|
||||||
.finish(),
|
.finish(),
|
||||||
)
|
)
|
||||||
.del_cookie(&cookies[1])
|
.del_cookie(&cookies[0])
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
let mut val: Vec<_> = resp
|
let mut val = resp
|
||||||
.headers()
|
.headers()
|
||||||
.get_all(SET_COOKIE)
|
.get_all(SET_COOKIE)
|
||||||
.map(|v| v.to_str().unwrap().to_owned())
|
.map(|v| v.to_str().unwrap().to_owned())
|
||||||
.collect();
|
.collect::<Vec<_>>();
|
||||||
val.sort();
|
val.sort();
|
||||||
|
|
||||||
|
// the .del_cookie call
|
||||||
assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
|
assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
|
||||||
|
|
||||||
|
// the .cookie call
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
val[1],
|
val[1],
|
||||||
"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"
|
"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"
|
||||||
@ -916,14 +935,14 @@ mod tests {
|
|||||||
|
|
||||||
let mut iter = r.cookies();
|
let mut iter = r.cookies();
|
||||||
let v = iter.next().unwrap();
|
let v = iter.next().unwrap();
|
||||||
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
|
|
||||||
let v = iter.next().unwrap();
|
|
||||||
assert_eq!((v.name(), v.value()), ("original", "val100"));
|
assert_eq!((v.name(), v.value()), ("original", "val100"));
|
||||||
|
let v = iter.next().unwrap();
|
||||||
|
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic_builder() {
|
fn test_basic_builder() {
|
||||||
let resp = Response::Ok().header("X-TEST", "value").finish();
|
let resp = Response::Ok().insert_header(("X-TEST", "value")).finish();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -964,26 +983,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_json_ct() {
|
fn test_json_ct() {
|
||||||
let resp = Response::build(StatusCode::OK)
|
let resp = Response::build(StatusCode::OK)
|
||||||
.header(CONTENT_TYPE, "text/json")
|
.insert_header((CONTENT_TYPE, "text/json"))
|
||||||
.json(vec!["v1", "v2", "v3"]);
|
.json(&vec!["v1", "v2", "v3"]);
|
||||||
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
|
||||||
assert_eq!(ct, HeaderValue::from_static("text/json"));
|
|
||||||
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_json2() {
|
|
||||||
let resp = Response::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]);
|
|
||||||
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
|
||||||
assert_eq!(ct, HeaderValue::from_static("application/json"));
|
|
||||||
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_json2_ct() {
|
|
||||||
let resp = Response::build(StatusCode::OK)
|
|
||||||
.header(CONTENT_TYPE, "text/json")
|
|
||||||
.json2(&vec!["v1", "v2", "v3"]);
|
|
||||||
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
||||||
assert_eq!(ct, HeaderValue::from_static("text/json"));
|
assert_eq!(ct, HeaderValue::from_static("text/json"));
|
||||||
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
|
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
|
||||||
@ -1082,4 +1083,54 @@ mod tests {
|
|||||||
let cookie = resp.cookies().next().unwrap();
|
let cookie = resp.cookies().next().unwrap();
|
||||||
assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100"));
|
assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_builder_header_insert_kv() {
|
||||||
|
let mut res = Response::Ok();
|
||||||
|
res.insert_header(("Content-Type", "application/octet-stream"));
|
||||||
|
let res = res.finish();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
res.headers().get("Content-Type"),
|
||||||
|
Some(&HeaderValue::from_static("application/octet-stream"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_builder_header_insert_typed() {
|
||||||
|
let mut res = Response::Ok();
|
||||||
|
res.insert_header(header::ContentType(mime::APPLICATION_OCTET_STREAM));
|
||||||
|
let res = res.finish();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
res.headers().get("Content-Type"),
|
||||||
|
Some(&HeaderValue::from_static("application/octet-stream"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_builder_header_append_kv() {
|
||||||
|
let mut res = Response::Ok();
|
||||||
|
res.append_header(("Content-Type", "application/octet-stream"));
|
||||||
|
res.append_header(("Content-Type", "application/json"));
|
||||||
|
let res = res.finish();
|
||||||
|
|
||||||
|
let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
|
||||||
|
assert_eq!(headers.len(), 2);
|
||||||
|
assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
|
||||||
|
assert!(headers.contains(&HeaderValue::from_static("application/json")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_builder_header_append_typed() {
|
||||||
|
let mut res = Response::Ok();
|
||||||
|
res.append_header(header::ContentType(mime::APPLICATION_OCTET_STREAM));
|
||||||
|
res.append_header(header::ContentType(mime::APPLICATION_JSON));
|
||||||
|
let res = res.finish();
|
||||||
|
|
||||||
|
let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
|
||||||
|
assert_eq!(headers.len(), 2);
|
||||||
|
assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
|
||||||
|
assert!(headers.contains(&HeaderValue::from_static("application/json")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
@ -440,7 +439,7 @@ where
|
|||||||
X: Service<Request>,
|
X: Service<Request>,
|
||||||
U: Service<(Request, Framed<T, h1::Codec>)>,
|
U: Service<(Request, Framed<T, h1::Codec>)>,
|
||||||
{
|
{
|
||||||
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
|
flow: Rc<HttpFlow<S, X, U>>,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_phantom: PhantomData<B>,
|
_phantom: PhantomData<B>,
|
||||||
@ -454,12 +453,12 @@ pub(super) struct HttpFlow<S, X, U> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<S, X, U> HttpFlow<S, X, U> {
|
impl<S, X, U> HttpFlow<S, X, U> {
|
||||||
pub(super) fn new(service: S, expect: X, upgrade: Option<U>) -> Rc<RefCell<Self>> {
|
pub(super) fn new(service: S, expect: X, upgrade: Option<U>) -> Rc<Self> {
|
||||||
Rc::new(RefCell::new(Self {
|
Rc::new(Self {
|
||||||
service,
|
service,
|
||||||
expect,
|
expect,
|
||||||
upgrade,
|
upgrade,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,9 +508,9 @@ where
|
|||||||
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(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
let mut flow = self.flow.borrow_mut();
|
let ready = self
|
||||||
let ready = flow
|
.flow
|
||||||
.expect
|
.expect
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@ -521,7 +520,8 @@ where
|
|||||||
})?
|
})?
|
||||||
.is_ready();
|
.is_ready();
|
||||||
|
|
||||||
let ready = flow
|
let ready = self
|
||||||
|
.flow
|
||||||
.service
|
.service
|
||||||
.poll_ready(cx)
|
.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@ -532,7 +532,7 @@ where
|
|||||||
.is_ready()
|
.is_ready()
|
||||||
&& ready;
|
&& ready;
|
||||||
|
|
||||||
let ready = if let Some(ref mut upg) = flow.upgrade {
|
let ready = if let Some(ref upg) = self.flow.upgrade {
|
||||||
upg.poll_ready(cx)
|
upg.poll_ready(cx)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = e.into();
|
let e = e.into();
|
||||||
@ -553,7 +553,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call(
|
fn call(
|
||||||
&mut self,
|
&self,
|
||||||
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
|
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
|
||||||
) -> Self::Future {
|
) -> Self::Future {
|
||||||
let on_connect_data =
|
let on_connect_data =
|
||||||
@ -604,7 +604,7 @@ where
|
|||||||
Option<(
|
Option<(
|
||||||
Handshake<T, Bytes>,
|
Handshake<T, Bytes>,
|
||||||
ServiceConfig,
|
ServiceConfig,
|
||||||
Rc<RefCell<HttpFlow<S, X, U>>>,
|
Rc<HttpFlow<S, X, U>>,
|
||||||
OnConnectData,
|
OnConnectData,
|
||||||
Option<net::SocketAddr>,
|
Option<net::SocketAddr>,
|
||||||
)>,
|
)>,
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefCell},
|
cell::{Ref, RefCell},
|
||||||
convert::TryFrom,
|
|
||||||
io::{self, Read, Write},
|
io::{self, Read, Write},
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
@ -12,14 +11,17 @@ use std::{
|
|||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use http::header::{self, HeaderName, HeaderValue};
|
use http::{
|
||||||
use http::{Error as HttpError, Method, Uri, Version};
|
header::{self, HeaderValue},
|
||||||
|
Method, Uri, Version,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::cookie::{Cookie, CookieJar};
|
use crate::{
|
||||||
use crate::header::HeaderMap;
|
cookie::{Cookie, CookieJar},
|
||||||
use crate::header::{Header, IntoHeaderValue};
|
header::{HeaderMap, IntoHeaderPair},
|
||||||
use crate::payload::Payload;
|
payload::Payload,
|
||||||
use crate::Request;
|
Request,
|
||||||
|
};
|
||||||
|
|
||||||
/// Test `Request` builder
|
/// Test `Request` builder
|
||||||
///
|
///
|
||||||
@ -36,7 +38,7 @@ use crate::Request;
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let resp = TestRequest::with_header("content-type", "text/plain")
|
/// let resp = TestRequest::default().insert_header("content-type", "text/plain")
|
||||||
/// .run(&index)
|
/// .run(&index)
|
||||||
/// .unwrap();
|
/// .unwrap();
|
||||||
/// assert_eq!(resp.status(), StatusCode::OK);
|
/// assert_eq!(resp.status(), StatusCode::OK);
|
||||||
@ -69,76 +71,73 @@ impl Default for TestRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TestRequest {
|
impl TestRequest {
|
||||||
/// Create TestRequest and set request uri
|
/// Create a default TestRequest and then set its URI.
|
||||||
pub fn with_uri(path: &str) -> TestRequest {
|
pub fn with_uri(path: &str) -> TestRequest {
|
||||||
TestRequest::default().uri(path).take()
|
TestRequest::default().uri(path).take()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create TestRequest and set header
|
/// Set HTTP version of this request.
|
||||||
pub fn with_hdr<H: Header>(hdr: H) -> TestRequest {
|
|
||||||
TestRequest::default().set(hdr).take()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create TestRequest and set header
|
|
||||||
pub fn with_header<K, V>(key: K, value: V) -> TestRequest
|
|
||||||
where
|
|
||||||
HeaderName: TryFrom<K>,
|
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: IntoHeaderValue,
|
|
||||||
{
|
|
||||||
TestRequest::default().header(key, value).take()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set HTTP version of this request
|
|
||||||
pub fn version(&mut self, ver: Version) -> &mut Self {
|
pub fn version(&mut self, ver: Version) -> &mut Self {
|
||||||
parts(&mut self.0).version = ver;
|
parts(&mut self.0).version = ver;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set HTTP method of this request
|
/// Set HTTP method of this request.
|
||||||
pub fn method(&mut self, meth: Method) -> &mut Self {
|
pub fn method(&mut self, meth: Method) -> &mut Self {
|
||||||
parts(&mut self.0).method = meth;
|
parts(&mut self.0).method = meth;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set HTTP Uri of this request
|
/// Set URI of this request.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If provided URI is invalid.
|
||||||
pub fn uri(&mut self, path: &str) -> &mut Self {
|
pub fn uri(&mut self, path: &str) -> &mut Self {
|
||||||
parts(&mut self.0).uri = Uri::from_str(path).unwrap();
|
parts(&mut self.0).uri = Uri::from_str(path).unwrap();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a header
|
/// Insert a header, replacing any that were set with an equivalent field name.
|
||||||
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
|
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
|
||||||
if let Ok(value) = hdr.try_into() {
|
|
||||||
parts(&mut self.0).headers.append(H::name(), value);
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
panic!("Can not set header");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a header
|
|
||||||
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
H: IntoHeaderPair,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: IntoHeaderValue,
|
|
||||||
{
|
{
|
||||||
if let Ok(key) = HeaderName::try_from(key) {
|
match header.try_into_header_pair() {
|
||||||
if let Ok(value) = value.try_into() {
|
Ok((key, value)) => {
|
||||||
parts(&mut self.0).headers.append(key, value);
|
parts(&mut self.0).headers.insert(key, value);
|
||||||
return self;
|
}
|
||||||
|
Err(err) => {
|
||||||
|
panic!("Error inserting test header: {}.", err.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic!("Can not create header");
|
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set cookie for this request
|
/// Append a header, keeping any that were set with an equivalent field name.
|
||||||
|
pub fn append_header<H>(&mut self, header: H) -> &mut Self
|
||||||
|
where
|
||||||
|
H: IntoHeaderPair,
|
||||||
|
{
|
||||||
|
match header.try_into_header_pair() {
|
||||||
|
Ok((key, value)) => {
|
||||||
|
parts(&mut self.0).headers.append(key, value);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
panic!("Error inserting test header: {}.", err.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set cookie for this request.
|
||||||
pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self {
|
pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self {
|
||||||
parts(&mut self.0).cookies.add(cookie.into_owned());
|
parts(&mut self.0).cookies.add(cookie.into_owned());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set request payload
|
/// Set request payload.
|
||||||
pub fn set_payload<B: Into<Bytes>>(&mut self, data: B) -> &mut Self {
|
pub fn set_payload<B: Into<Bytes>>(&mut self, data: B) -> &mut Self {
|
||||||
let mut payload = crate::h1::Payload::empty();
|
let mut payload = crate::h1::Payload::empty();
|
||||||
payload.unread_data(data.into());
|
payload.unread_data(data.into());
|
||||||
@ -150,7 +149,7 @@ impl TestRequest {
|
|||||||
TestRequest(self.0.take())
|
TestRequest(self.0.take())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Complete request creation and generate `Request` instance
|
/// Complete request creation and generate `Request` instance.
|
||||||
pub fn finish(&mut self) -> Request {
|
pub fn finish(&mut self) -> Request {
|
||||||
let inner = self.0.take().expect("cannot reuse test request builder");
|
let inner = self.0.take().expect("cannot reuse test request builder");
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ 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
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use std::ptr::copy_nonoverlapping;
|
use std::ptr::copy_nonoverlapping;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
|
||||||
// Holds a slice guaranteed to be shorter than 8 bytes
|
/// Holds a slice guaranteed to be shorter than 8 bytes.
|
||||||
struct ShortSlice<'a> {
|
struct ShortSlice<'a> {
|
||||||
inner: &'a mut [u8],
|
inner: &'a mut [u8],
|
||||||
}
|
}
|
||||||
@ -80,8 +80,10 @@ unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] {
|
|||||||
slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3)
|
slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Splits a slice into three parts: an unaligned short head and tail, plus an aligned
|
/// Splits a slice into three parts:
|
||||||
// u64 mid section.
|
/// - an unaligned short head
|
||||||
|
/// - an aligned `u64` slice mid section
|
||||||
|
/// - an unaligned short tail
|
||||||
#[inline]
|
#[inline]
|
||||||
fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) {
|
fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) {
|
||||||
let start_ptr = buf.as_ptr() as usize;
|
let start_ptr = buf.as_ptr() as usize;
|
||||||
|
@ -101,7 +101,7 @@ 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")
|
.insert_header((header::ALLOW, "GET"))
|
||||||
.finish(),
|
.finish(),
|
||||||
|
|
||||||
HandshakeError::NoWebsocketUpgrade => Response::BadRequest()
|
HandshakeError::NoWebsocketUpgrade => Response::BadRequest()
|
||||||
@ -128,18 +128,12 @@ impl ResponseError for HandshakeError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Verify `WebSocket` handshake request and create handshake response.
|
/// Verify `WebSocket` handshake request and create handshake response.
|
||||||
// /// `protocols` is a sequence of known protocols. On successful handshake,
|
|
||||||
// /// the returned response headers contain the first protocol in this list
|
|
||||||
// /// which the server also knows.
|
|
||||||
pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
|
pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
|
||||||
verify_handshake(req)?;
|
verify_handshake(req)?;
|
||||||
Ok(handshake_response(req))
|
Ok(handshake_response(req))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify `WebSocket` handshake request.
|
/// Verify `WebSocket` handshake request.
|
||||||
// /// `protocols` is a sequence of known protocols. On successful handshake,
|
|
||||||
// /// the returned response headers contain the first protocol in this list
|
|
||||||
// /// which the server also knows.
|
|
||||||
pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
|
pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
|
||||||
// WebSocket accepts only GET
|
// WebSocket accepts only GET
|
||||||
if req.method != Method::GET {
|
if req.method != Method::GET {
|
||||||
@ -198,8 +192,8 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
|
|||||||
|
|
||||||
Response::build(StatusCode::SWITCHING_PROTOCOLS)
|
Response::build(StatusCode::SWITCHING_PROTOCOLS)
|
||||||
.upgrade("websocket")
|
.upgrade("websocket")
|
||||||
.header(header::TRANSFER_ENCODING, "chunked")
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
.header(header::SEC_WEBSOCKET_ACCEPT, key.as_str())
|
.insert_header((header::SEC_WEBSOCKET_ACCEPT, key))
|
||||||
.take()
|
.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +218,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(header::UPGRADE, header::HeaderValue::from_static("test"))
|
.insert_header((header::UPGRADE, header::HeaderValue::from_static("test")))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoWebsocketUpgrade,
|
HandshakeError::NoWebsocketUpgrade,
|
||||||
@ -232,10 +226,10 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoConnectionUpgrade,
|
HandshakeError::NoConnectionUpgrade,
|
||||||
@ -243,14 +237,14 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoVersionHeader,
|
HandshakeError::NoVersionHeader,
|
||||||
@ -258,18 +252,18 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_VERSION,
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
header::HeaderValue::from_static("5"),
|
header::HeaderValue::from_static("5"),
|
||||||
)
|
))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::UnsupportedVersion,
|
HandshakeError::UnsupportedVersion,
|
||||||
@ -277,18 +271,18 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_VERSION,
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::BadWebsocketKey,
|
HandshakeError::BadWebsocketKey,
|
||||||
@ -296,22 +290,22 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_VERSION,
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_KEY,
|
header::SEC_WEBSOCKET_KEY,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
StatusCode::SWITCHING_PROTOCOLS,
|
StatusCode::SWITCHING_PROTOCOLS,
|
||||||
|
@ -38,7 +38,7 @@ async fn test_h1_v2() {
|
|||||||
let response = srv.get("/").send().await.unwrap();
|
let response = srv.get("/").send().await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
let request = srv.get("/").header("x-test", "111").send();
|
let request = srv.get("/").insert_header(("x-test", "111")).send();
|
||||||
let mut response = request.await.unwrap();
|
let mut response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
#![cfg(feature = "openssl")]
|
#![cfg(feature = "openssl")]
|
||||||
|
|
||||||
|
extern crate tls_openssl as openssl;
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use actix_http::error::{ErrorBadRequest, PayloadError};
|
use actix_http::error::{ErrorBadRequest, PayloadError};
|
||||||
@ -11,7 +14,7 @@ use actix_service::{fn_service, ServiceFactoryExt};
|
|||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures_util::future::{err, ok, ready};
|
use futures_util::future::{err, ok, ready};
|
||||||
use futures_util::stream::{once, Stream, StreamExt};
|
use futures_util::stream::{once, Stream, StreamExt};
|
||||||
use open_ssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod};
|
use openssl::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
|
||||||
@ -173,8 +176,8 @@ async fn test_h2_headers() {
|
|||||||
HttpService::build().h2(move |_| {
|
HttpService::build().h2(move |_| {
|
||||||
let mut builder = Response::Ok();
|
let mut builder = Response::Ok();
|
||||||
for idx in 0..90 {
|
for idx in 0..90 {
|
||||||
builder.header(
|
builder.insert_header(
|
||||||
format!("X-TEST-{}", idx).as_str(),
|
(format!("X-TEST-{}", idx).as_str(),
|
||||||
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
@ -188,7 +191,7 @@ async fn test_h2_headers() {
|
|||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
ok::<_, ()>(builder.body(data.clone()))
|
ok::<_, ()>(builder.body(data.clone()))
|
||||||
})
|
})
|
||||||
@ -341,7 +344,7 @@ async fn test_h2_body_chunked_explicit() {
|
|||||||
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
||||||
ok::<_, ()>(
|
ok::<_, ()>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header(header::TRANSFER_ENCODING, "chunked")
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
.streaming(body),
|
.streaming(body),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -369,7 +372,7 @@ async fn test_h2_response_http_error_handling() {
|
|||||||
let broken_header = Bytes::from_static(b"\0\0\0");
|
let broken_header = Bytes::from_static(b"\0\0\0");
|
||||||
ok::<_, ()>(
|
ok::<_, ()>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header(header::CONTENT_TYPE, broken_header)
|
.insert_header((header::CONTENT_TYPE, broken_header))
|
||||||
.body(STR),
|
.body(STR),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
@ -408,7 +411,9 @@ 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_ext(|_, data| data.insert(20isize))
|
.on_connect_ext(|_, data| {
|
||||||
|
data.insert(20isize);
|
||||||
|
})
|
||||||
.h2(|req: Request| {
|
.h2(|req: Request| {
|
||||||
assert!(req.extensions().contains::<isize>());
|
assert!(req.extensions().contains::<isize>());
|
||||||
ok::<_, ()>(Response::Ok().finish())
|
ok::<_, ()>(Response::Ok().finish())
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
#![cfg(feature = "rustls")]
|
#![cfg(feature = "rustls")]
|
||||||
|
|
||||||
|
extern crate tls_rustls as rustls;
|
||||||
|
|
||||||
use actix_http::error::PayloadError;
|
use actix_http::error::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};
|
||||||
@ -9,7 +12,7 @@ use actix_service::{fn_factory_with_config, fn_service};
|
|||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures_util::future::{self, err, ok};
|
use futures_util::future::{self, err, ok};
|
||||||
use futures_util::stream::{once, Stream, StreamExt};
|
use futures_util::stream::{once, Stream, StreamExt};
|
||||||
use rust_tls::{
|
use rustls::{
|
||||||
internal::pemfile::{certs, pkcs8_private_keys},
|
internal::pemfile::{certs, pkcs8_private_keys},
|
||||||
NoClientAuth, ServerConfig as RustlsServerConfig,
|
NoClientAuth, ServerConfig as RustlsServerConfig,
|
||||||
};
|
};
|
||||||
@ -181,7 +184,7 @@ async fn test_h2_headers() {
|
|||||||
HttpService::build().h2(move |_| {
|
HttpService::build().h2(move |_| {
|
||||||
let mut config = Response::Ok();
|
let mut config = Response::Ok();
|
||||||
for idx in 0..90 {
|
for idx in 0..90 {
|
||||||
config.header(
|
config.insert_header((
|
||||||
format!("X-TEST-{}", idx).as_str(),
|
format!("X-TEST-{}", idx).as_str(),
|
||||||
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
@ -196,7 +199,7 @@ async fn test_h2_headers() {
|
|||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
future::ok::<_, ()>(config.body(data.clone()))
|
future::ok::<_, ()>(config.body(data.clone()))
|
||||||
})
|
})
|
||||||
@ -352,7 +355,7 @@ async fn test_h2_body_chunked_explicit() {
|
|||||||
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
||||||
ok::<_, ()>(
|
ok::<_, ()>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header(header::TRANSFER_ENCODING, "chunked")
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
.streaming(body),
|
.streaming(body),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -380,7 +383,7 @@ async fn test_h2_response_http_error_handling() {
|
|||||||
let broken_header = Bytes::from_static(b"\0\0\0");
|
let broken_header = Bytes::from_static(b"\0\0\0");
|
||||||
ok::<_, ()>(
|
ok::<_, ()>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header(http::header::CONTENT_TYPE, broken_header)
|
.insert_header((http::header::CONTENT_TYPE, broken_header))
|
||||||
.body(STR),
|
.body(STR),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
|
@ -392,7 +392,7 @@ async fn test_h1_headers() {
|
|||||||
HttpService::build().h1(move |_| {
|
HttpService::build().h1(move |_| {
|
||||||
let mut builder = Response::Ok();
|
let mut builder = Response::Ok();
|
||||||
for idx in 0..90 {
|
for idx in 0..90 {
|
||||||
builder.header(
|
builder.insert_header((
|
||||||
format!("X-TEST-{}", idx).as_str(),
|
format!("X-TEST-{}", idx).as_str(),
|
||||||
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
@ -407,7 +407,7 @@ async fn test_h1_headers() {
|
|||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
future::ok::<_, ()>(builder.body(data.clone()))
|
future::ok::<_, ()>(builder.body(data.clone()))
|
||||||
}).tcp()
|
}).tcp()
|
||||||
@ -561,7 +561,7 @@ async fn test_h1_body_chunked_explicit() {
|
|||||||
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
||||||
ok::<_, ()>(
|
ok::<_, ()>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header(header::TRANSFER_ENCODING, "chunked")
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
.streaming(body),
|
.streaming(body),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -625,7 +625,7 @@ async fn test_h1_response_http_error_handling() {
|
|||||||
let broken_header = Bytes::from_static(b"\0\0\0");
|
let broken_header = Bytes::from_static(b"\0\0\0");
|
||||||
ok::<_, ()>(
|
ok::<_, ()>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header(http::header::CONTENT_TYPE, broken_header)
|
.insert_header((http::header::CONTENT_TYPE, broken_header))
|
||||||
.body(STR),
|
.body(STR),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
@ -662,7 +662,9 @@ 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_ext(|_, data| data.insert(20isize))
|
.on_connect_ext(|_, data| {
|
||||||
|
data.insert(20isize);
|
||||||
|
})
|
||||||
.h1(|req: Request| {
|
.h1(|req: Request| {
|
||||||
assert!(req.extensions().contains::<isize>());
|
assert!(req.extensions().contains::<isize>());
|
||||||
future::ok::<_, ()>(Response::Ok().finish())
|
future::ok::<_, ()>(Response::Ok().finish())
|
||||||
|
@ -21,7 +21,7 @@ impl<T> WsService<T> {
|
|||||||
WsService(Arc::new(Mutex::new((PhantomData, Cell::new(false)))))
|
WsService(Arc::new(Mutex::new((PhantomData, Cell::new(false)))))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_polled(&mut self) {
|
fn set_polled(&self) {
|
||||||
*self.0.lock().unwrap().1.get_mut() = true;
|
*self.0.lock().unwrap().1.get_mut() = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,15 +44,12 @@ where
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Pin<Box<dyn Future<Output = Result<(), Error>>>>;
|
type Future = Pin<Box<dyn Future<Output = Result<(), Error>>>>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, _ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&self, _ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
self.set_polled();
|
self.set_polled();
|
||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(
|
fn call(&self, (req, mut framed): (Request, Framed<T, h1::Codec>)) -> Self::Future {
|
||||||
&mut self,
|
|
||||||
(req, mut framed): (Request, Framed<T, h1::Codec>),
|
|
||||||
) -> Self::Future {
|
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
let res = ws::handshake(req.head()).unwrap().message_body(());
|
let res = ws::handshake(req.head()).unwrap().message_body(());
|
||||||
|
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.0-beta.2 - 2021-02-10
|
||||||
|
* No notable changes.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.1 - 2021-01-07
|
## 0.4.0-beta.1 - 2021-01-07
|
||||||
* Fix multipart consuming payload before header checks. [#1513]
|
* Fix multipart consuming payload before header checks. [#1513]
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-multipart"
|
name = "actix-multipart"
|
||||||
version = "0.4.0-beta.1"
|
version = "0.4.0-beta.2"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Multipart support for actix web framework."
|
description = "Multipart form support for Actix Web"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
@ -16,8 +16,8 @@ name = "actix_multipart"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.1", default-features = false }
|
actix-web = { version = "4.0.0-beta.2", default-features = false }
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-utils = "3.0.0-beta.2"
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
@ -28,5 +28,5 @@ mime = "0.3"
|
|||||||
twoway = "0.2"
|
twoway = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2"
|
||||||
actix-http = "3.0.0-beta.1"
|
actix-http = "3.0.0-beta.2"
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
# Multipart support for actix web framework [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-multipart) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
# actix-multipart
|
||||||
|
|
||||||
## Documentation & community resources
|
> Multipart form support for Actix Web.
|
||||||
|
|
||||||
* [API Documentation](https://docs.rs/actix-multipart/)
|
[](https://crates.io/crates/actix-multipart)
|
||||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
[](https://docs.rs/actix-multipart/0.4.0-beta.2)
|
||||||
* Cargo package: [actix-multipart](https://crates.io/crates/actix-multipart)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||
* Minimum supported Rust version: 1.40 or later
|

|
||||||
|
<br />
|
||||||
|
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.2)
|
||||||
|
[](https://crates.io/crates/actix-multipart)
|
||||||
|
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
## Documentation & Resources
|
||||||
|
|
||||||
|
- [API Documentation](https://docs.rs/actix-multipart)
|
||||||
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
|
- Minimum Supported Rust Version (MSRV): 1.46.0
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! Multipart form support for Actix web.
|
//! Multipart form support for Actix Web.
|
||||||
|
|
||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
#![allow(clippy::borrow_interior_mutable_const)]
|
#![allow(clippy::borrow_interior_mutable_const)]
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0-beta.2 - 2021-02-10
|
||||||
|
* No notable changes.
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.1 - 2021-01-07
|
## 4.0.0-beta.1 - 2021-01-07
|
||||||
* Update `pin-project` to `1.0`.
|
* Update `pin-project` to `1.0`.
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
* Update `bytes` to `1.0`. [#1813]
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web-actors"
|
name = "actix-web-actors"
|
||||||
version = "4.0.0-beta.1"
|
version = "4.0.0-beta.2"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix actors support for actix web framework."
|
description = "Actix actors support for Actix Web"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["actix", "http", "web", "framework", "async"]
|
keywords = ["actix", "http", "web", "framework", "async"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
@ -16,10 +16,10 @@ name = "actix_web_actors"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "0.11.0-beta.1"
|
actix = { version = "0.11.0-beta.2", default-features = false }
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-http = "3.0.0-beta.1"
|
actix-http = "3.0.0-beta.2"
|
||||||
actix-web = { version = "4.0.0-beta.1", default-features = false }
|
actix-web = { version = "4.0.0-beta.2", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
@ -28,6 +28,6 @@ pin-project = "1.0.0"
|
|||||||
tokio = { version = "1", features = ["sync"] }
|
tokio = { version = "1", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.0.0-beta.1"
|
actix-rt = "2"
|
||||||
env_logger = "0.7"
|
env_logger = "0.8"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
Actix actors support for actix web framework [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-web-actors) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
# actix-web-actors
|
||||||
|
|
||||||
## Documentation & community resources
|
> Actix actors support for Actix Web.
|
||||||
|
|
||||||
* [API Documentation](https://docs.rs/actix-web-actors/)
|
[](https://crates.io/crates/actix-web-actors)
|
||||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
[](https://docs.rs/actix-web-actors/0.5.0)
|
||||||
* Cargo package: [actix-web-actors](https://crates.io/crates/actix-web-actors)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||
* Minimum supported Rust version: 1.40 or later
|

|
||||||
|
<br />
|
||||||
|
[](https://deps.rs/crate/actix-web-actors/0.5.0)
|
||||||
|
[](https://crates.io/crates/actix-web-actors)
|
||||||
|
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
## Documentation & Resources
|
||||||
|
|
||||||
|
- [API Documentation](https://docs.rs/actix-web-actors)
|
||||||
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
|
- Minimum supported Rust version: 1.46 or later
|
||||||
|
@ -233,14 +233,13 @@ mod tests {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_default_resource() {
|
async fn test_default_resource() {
|
||||||
let mut srv =
|
let srv = init_service(App::new().service(web::resource("/test").to(|| {
|
||||||
init_service(App::new().service(web::resource("/test").to(|| {
|
HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 }))
|
||||||
HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 }))
|
})))
|
||||||
})))
|
.await;
|
||||||
.await;
|
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/test").to_request();
|
let req = TestRequest::with_uri("/test").to_request();
|
||||||
let resp = call_service(&mut srv, req).await;
|
let resp = call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
let body = read_body(resp).await;
|
let body = read_body(resp).await;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user