mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into files/fix/percent-encoding
This commit is contained in:
commit
5de174ae4d
|
@ -0,0 +1,3 @@
|
||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [robjtede]
|
|
@ -33,5 +33,5 @@ Please search on the [Actix Web issue tracker](https://github.com/actix/actix-we
|
||||||
## Your Environment
|
## Your Environment
|
||||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||||
|
|
||||||
* Rust Version (I.e, output of `rustc -V`):
|
- Rust Version (I.e, output of `rustc -V`):
|
||||||
* Actix Web Version:
|
- Actix Web Version:
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
name: CI (master only)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci_feature_powerset_check:
|
||||||
|
name: Verify Feature Combinations
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install stable
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable-x86_64-unknown-linux-gnu
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Generate Cargo.lock
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with: { command: generate-lockfile }
|
||||||
|
- name: Cache Dependencies
|
||||||
|
uses: Swatinem/rust-cache@v1.2.0
|
||||||
|
|
||||||
|
- name: Install cargo-hack
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: install
|
||||||
|
args: cargo-hack
|
||||||
|
|
||||||
|
- name: check feature combinations
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with: { command: ci-check-all-feature-powerset }
|
||||||
|
|
||||||
|
- name: check feature combinations
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with: { command: ci-check-all-feature-powerset-linux }
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
name: coverage
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install stable
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable-x86_64-unknown-linux-gnu
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Generate Cargo.lock
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with: { command: generate-lockfile }
|
||||||
|
- name: Cache Dependencies
|
||||||
|
uses: Swatinem/rust-cache@v1.2.0
|
||||||
|
|
||||||
|
- name: Generate coverage file
|
||||||
|
run: |
|
||||||
|
cargo install cargo-tarpaulin --vers "^0.13"
|
||||||
|
cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose
|
||||||
|
- name: Upload to Codecov
|
||||||
|
uses: codecov/codecov-action@v1
|
||||||
|
with: { file: cobertura.xml }
|
|
@ -96,68 +96,6 @@ jobs:
|
||||||
cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean
|
cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean
|
||||||
cargo-cache
|
cargo-cache
|
||||||
|
|
||||||
ci_feature_powerset_check:
|
|
||||||
name: Verify Feature Combinations
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Install stable
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable-x86_64-unknown-linux-gnu
|
|
||||||
profile: minimal
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Generate Cargo.lock
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with: { command: generate-lockfile }
|
|
||||||
- name: Cache Dependencies
|
|
||||||
uses: Swatinem/rust-cache@v1.2.0
|
|
||||||
|
|
||||||
- name: Install cargo-hack
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: install
|
|
||||||
args: cargo-hack
|
|
||||||
|
|
||||||
- name: check feature combinations
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with: { command: ci-check-all-feature-powerset }
|
|
||||||
|
|
||||||
- name: check feature combinations
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with: { command: ci-check-all-feature-powerset-linux }
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
name: coverage
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Install stable
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable-x86_64-unknown-linux-gnu
|
|
||||||
profile: minimal
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Generate Cargo.lock
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with: { command: generate-lockfile }
|
|
||||||
- name: Cache Dependencies
|
|
||||||
uses: Swatinem/rust-cache@v1.2.0
|
|
||||||
|
|
||||||
- name: Generate coverage file
|
|
||||||
if: github.ref == 'refs/heads/master'
|
|
||||||
run: |
|
|
||||||
cargo install cargo-tarpaulin --vers "^0.13"
|
|
||||||
cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose
|
|
||||||
- name: Upload to Codecov
|
|
||||||
if: github.ref == 'refs/heads/master'
|
|
||||||
uses: codecov/codecov-action@v1
|
|
||||||
with: { file: cobertura.xml }
|
|
||||||
|
|
||||||
rustdoc:
|
rustdoc:
|
||||||
name: doc tests
|
name: doc tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
591
CHANGES.md
591
CHANGES.md
File diff suppressed because it is too large
Load Diff
|
@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo
|
||||||
|
|
||||||
Examples of behavior that contributes to creating a positive environment include:
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
- Using welcoming and inclusive language
|
||||||
* Being respectful of differing viewpoints and experiences
|
- Being respectful of differing viewpoints and experiences
|
||||||
* Gracefully accepting constructive criticism
|
- Gracefully accepting constructive criticism
|
||||||
* Focusing on what is best for the community
|
- Focusing on what is best for the community
|
||||||
* Showing empathy towards other community members
|
- Showing empathy towards other community members
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
- Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
## Our Responsibilities
|
## Our Responsibilities
|
||||||
|
|
||||||
|
|
19
Cargo.toml
19
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "4.0.0-beta.13"
|
version = "4.0.0-beta.17"
|
||||||
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"
|
||||||
keywords = ["actix", "http", "web", "framework", "async"]
|
keywords = ["actix", "http", "web", "framework", "async"]
|
||||||
|
@ -72,25 +72,24 @@ experimental-io-uring = ["actix-server/io-uring"]
|
||||||
actix-codec = "0.4.1"
|
actix-codec = "0.4.1"
|
||||||
actix-macros = "0.2.3"
|
actix-macros = "0.2.3"
|
||||||
actix-rt = "2.3"
|
actix-rt = "2.3"
|
||||||
actix-server = "2.0.0-rc.1"
|
actix-server = "2.0.0-rc.2"
|
||||||
actix-service = "2.0.0"
|
actix-service = "2.0.0"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true }
|
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
||||||
|
|
||||||
actix-http = "3.0.0-beta.14"
|
actix-http = "3.0.0-beta.17"
|
||||||
actix-router = "0.5.0-beta.2"
|
actix-router = "0.5.0-beta.3"
|
||||||
actix-web-codegen = "0.5.0-beta.5"
|
actix-web-codegen = "0.5.0-beta.6"
|
||||||
|
|
||||||
ahash = "0.7"
|
ahash = "0.7"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
cfg-if = "1"
|
cfg-if = "1"
|
||||||
cookie = { version = "0.15", features = ["percent-encode"], optional = true }
|
cookie = { version = "0.15", features = ["percent-encode"], optional = true }
|
||||||
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 }
|
||||||
itoa = "0.4"
|
itoa = "1"
|
||||||
language-tags = "0.3"
|
language-tags = "0.3"
|
||||||
once_cell = "1.5"
|
once_cell = "1.5"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
@ -107,8 +106,8 @@ time = { version = "0.3", default-features = false, features = ["formatting"] }
|
||||||
url = "2.1"
|
url = "2.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] }
|
actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] }
|
||||||
awc = { version = "3.0.0-beta.11", features = ["openssl"] }
|
awc = { version = "3.0.0-beta.16", features = ["openssl"] }
|
||||||
|
|
||||||
brotli2 = "0.3.2"
|
brotli2 = "0.3.2"
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
|
|
156
MIGRATION.md
156
MIGRATION.md
|
@ -1,6 +1,6 @@
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
* The default `NormalizePath` behavior now strips trailing slashes by default. This was
|
- The default `NormalizePath` behavior now strips trailing slashes by default. This was
|
||||||
previously documented to be the case in v3 but the behavior now matches. The effect is that
|
previously documented to be the case in v3 but the behavior now matches. The effect is that
|
||||||
routes defined with trailing slashes will become inaccessible when
|
routes defined with trailing slashes will become inaccessible when
|
||||||
using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning.
|
using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning.
|
||||||
|
@ -11,9 +11,9 @@
|
||||||
|
|
||||||
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
|
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
|
||||||
|
|
||||||
* The `type Config` of `FromRequest` was removed.
|
- The `type Config` of `FromRequest` was removed.
|
||||||
|
|
||||||
* Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd).
|
- Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd).
|
||||||
By default all compression algorithms are enabled.
|
By default all compression algorithms are enabled.
|
||||||
To select algorithm you want to include with `middleware::Compress` use following flags:
|
To select algorithm you want to include with `middleware::Compress` use following flags:
|
||||||
- `compress-brotli`
|
- `compress-brotli`
|
||||||
|
@ -28,30 +28,30 @@
|
||||||
|
|
||||||
## 3.0.0
|
## 3.0.0
|
||||||
|
|
||||||
* The return type for `ServiceRequest::app_data::<T>()` was changed from returning a `Data<T>` to
|
- The return type for `ServiceRequest::app_data::<T>()` was changed from returning a `Data<T>` to
|
||||||
simply a `T`. To access a `Data<T>` use `ServiceRequest::app_data::<Data<T>>()`.
|
simply a `T`. To access a `Data<T>` use `ServiceRequest::app_data::<Data<T>>()`.
|
||||||
|
|
||||||
* Cookie handling has been offloaded to the `cookie` crate:
|
- Cookie handling has been offloaded to the `cookie` crate:
|
||||||
* `USERINFO_ENCODE_SET` is no longer exposed. Percent-encoding is still supported; check docs.
|
* `USERINFO_ENCODE_SET` is no longer exposed. Percent-encoding is still supported; check docs.
|
||||||
* Some types now require lifetime parameters.
|
* Some types now require lifetime parameters.
|
||||||
|
|
||||||
* The time crate was updated to `v0.2`, a major breaking change to the time crate, which affects
|
- The time crate was updated to `v0.2`, a major breaking change to the time crate, which affects
|
||||||
any `actix-web` method previously expecting a time v0.1 input.
|
any `actix-web` method previously expecting a time v0.1 input.
|
||||||
|
|
||||||
* Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now
|
- Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now
|
||||||
result in `SameSite=None` being sent with the response Set-Cookie header.
|
result in `SameSite=None` being sent with the response Set-Cookie header.
|
||||||
To create a cookie without a SameSite attribute, remove any calls setting same_site.
|
To create a cookie without a SameSite attribute, remove any calls setting same_site.
|
||||||
|
|
||||||
* actix-http support for Actors messages was moved to actix-http crate and is enabled
|
- actix-http support for Actors messages was moved to actix-http crate and is enabled
|
||||||
with feature `actors`
|
with feature `actors`
|
||||||
|
|
||||||
* content_length function is removed from actix-http.
|
- content_length function is removed from actix-http.
|
||||||
You can set Content-Length by normally setting the response body or calling no_chunking function.
|
You can set Content-Length by normally setting the response body or calling no_chunking function.
|
||||||
|
|
||||||
* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
|
- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
|
||||||
`u64` instead of a `usize`.
|
`u64` instead of a `usize`.
|
||||||
|
|
||||||
* Code that was using `path.<index>` to access a `web::Path<(A, B, C)>`s elements now needs to use
|
- Code that was using `path.<index>` to access a `web::Path<(A, B, C)>`s elements now needs to use
|
||||||
destructuring or `.into_inner()`. For example:
|
destructuring or `.into_inner()`. For example:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
@ -71,35 +71,35 @@
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one.
|
- `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one.
|
||||||
It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`,
|
It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`,
|
||||||
or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly))`.
|
or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly))`.
|
||||||
|
|
||||||
* `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`.
|
- `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`.
|
||||||
|
|
||||||
* `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`.
|
- `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`.
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0
|
## 2.0.0
|
||||||
|
|
||||||
* `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to
|
- `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to
|
||||||
`.await` on `run` method result, in that case it awaits server exit.
|
`.await` on `run` method result, in that case it awaits server exit.
|
||||||
|
|
||||||
* `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`.
|
- `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`.
|
||||||
Stored data is available via `HttpRequest::app_data()` method at runtime.
|
Stored data is available via `HttpRequest::app_data()` method at runtime.
|
||||||
|
|
||||||
* Extractor configuration must be registered with `App::app_data()` instead of `App::data()`
|
- Extractor configuration must be registered with `App::app_data()` instead of `App::data()`
|
||||||
|
|
||||||
* Sync handlers has been removed. `.to_async()` method has been renamed to `.to()`
|
- Sync handlers has been removed. `.to_async()` method has been renamed to `.to()`
|
||||||
replace `fn` with `async fn` to convert sync handler to async
|
replace `fn` with `async fn` to convert sync handler to async
|
||||||
|
|
||||||
* `actix_http_test::TestServer` moved to `actix_web::test` module. To start
|
- `actix_http_test::TestServer` moved to `actix_web::test` module. To start
|
||||||
test server use `test::start()` or `test_start_with_config()` methods
|
test server use `test::start()` or `test_start_with_config()` methods
|
||||||
|
|
||||||
* `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders
|
- `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders
|
||||||
http response.
|
http response.
|
||||||
|
|
||||||
* Feature `rust-tls` renamed to `rustls`
|
- Feature `rust-tls` renamed to `rustls`
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
actix-web = { version = "2.0.0", features = ["rustls"] }
|
actix-web = { version = "2.0.0", features = ["rustls"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
* Feature `ssl` renamed to `openssl`
|
- Feature `ssl` renamed to `openssl`
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
|
||||||
|
@ -126,11 +126,11 @@
|
||||||
```rust
|
```rust
|
||||||
actix-web = { version = "2.0.0", features = ["openssl"] }
|
actix-web = { version = "2.0.0", features = ["openssl"] }
|
||||||
```
|
```
|
||||||
* `Cors` builder now requires that you call `.finish()` to construct the middleware
|
- `Cors` builder now requires that you call `.finish()` to construct the middleware
|
||||||
|
|
||||||
## 1.0.1
|
## 1.0.1
|
||||||
|
|
||||||
* Cors middleware has been moved to `actix-cors` crate
|
- Cors middleware has been moved to `actix-cors` crate
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@
|
||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
```
|
```
|
||||||
|
|
||||||
* Identity middleware has been moved to `actix-identity` crate
|
- Identity middleware has been moved to `actix-identity` crate
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@
|
||||||
|
|
||||||
## 1.0.0
|
## 1.0.0
|
||||||
|
|
||||||
* Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration
|
- Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
|
||||||
|
@ -219,7 +219,7 @@
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
* Resource registration. 1.0 version uses generalized resource
|
- Resource registration. 1.0 version uses generalized resource
|
||||||
registration via `.service()` method.
|
registration via `.service()` method.
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
@ -239,7 +239,7 @@
|
||||||
.route(web::post().to(post_handler))
|
.route(web::post().to(post_handler))
|
||||||
```
|
```
|
||||||
|
|
||||||
* Scope registration.
|
- Scope registration.
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
|
||||||
|
@ -263,7 +263,7 @@
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
* `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`.
|
- `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`.
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
|
||||||
|
@ -277,7 +277,7 @@
|
||||||
App.new().service(web::resource("/welcome").to(welcome))
|
App.new().service(web::resource("/welcome").to(welcome))
|
||||||
```
|
```
|
||||||
|
|
||||||
* Passing arguments to handler with extractors, multiple arguments are allowed
|
- Passing arguments to handler with extractors, multiple arguments are allowed
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
|
||||||
|
@ -295,7 +295,7 @@
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* `.f()`, `.a()` and `.h()` handler registration methods have been removed.
|
- `.f()`, `.a()` and `.h()` handler registration methods have been removed.
|
||||||
Use `.to()` for handlers and `.to_async()` for async handlers. Handler function
|
Use `.to()` for handlers and `.to_async()` for async handlers. Handler function
|
||||||
must use extractors.
|
must use extractors.
|
||||||
|
|
||||||
|
@ -311,7 +311,7 @@
|
||||||
App.new().service(web::resource("/welcome").to(welcome))
|
App.new().service(web::resource("/welcome").to(welcome))
|
||||||
```
|
```
|
||||||
|
|
||||||
* `HttpRequest` does not provide access to request's payload stream.
|
- `HttpRequest` does not provide access to request's payload stream.
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
|
||||||
|
@ -341,7 +341,7 @@
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* `State` is now `Data`. You register Data during the App initialization process
|
- `State` is now `Data`. You register Data during the App initialization process
|
||||||
and then access it from handlers either using a Data extractor or using
|
and then access it from handlers either using a Data extractor or using
|
||||||
HttpRequest's api.
|
HttpRequest's api.
|
||||||
|
|
||||||
|
@ -377,7 +377,7 @@
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
* AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type.
|
- AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type.
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
|
||||||
|
@ -393,7 +393,7 @@
|
||||||
.. simply omit AsyncResponder and the corresponding responder() finish method
|
.. simply omit AsyncResponder and the corresponding responder() finish method
|
||||||
|
|
||||||
|
|
||||||
* Middleware
|
- Middleware
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
|
||||||
|
@ -410,7 +410,7 @@
|
||||||
.route("/index.html", web::get().to(index));
|
.route("/index.html", web::get().to(index));
|
||||||
```
|
```
|
||||||
|
|
||||||
* `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()`
|
- `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()`
|
||||||
method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead.
|
method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead.
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
@ -432,9 +432,9 @@
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type
|
- `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type
|
||||||
|
|
||||||
* StaticFiles and NamedFile have been moved to a separate crate.
|
- StaticFiles and NamedFile have been moved to a separate crate.
|
||||||
|
|
||||||
instead of `use actix_web::fs::StaticFile`
|
instead of `use actix_web::fs::StaticFile`
|
||||||
|
|
||||||
|
@ -444,20 +444,20 @@
|
||||||
|
|
||||||
use `use actix_files::NamedFile`
|
use `use actix_files::NamedFile`
|
||||||
|
|
||||||
* Multipart has been moved to a separate crate.
|
- Multipart has been moved to a separate crate.
|
||||||
|
|
||||||
instead of `use actix_web::multipart::Multipart`
|
instead of `use actix_web::multipart::Multipart`
|
||||||
|
|
||||||
use `use actix_multipart::Multipart`
|
use `use actix_multipart::Multipart`
|
||||||
|
|
||||||
* Response compression is not enabled by default.
|
- Response compression is not enabled by default.
|
||||||
To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`.
|
To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`.
|
||||||
|
|
||||||
* Session middleware moved to actix-session crate
|
- Session middleware moved to actix-session crate
|
||||||
|
|
||||||
* Actors support have been moved to `actix-web-actors` crate
|
- Actors support have been moved to `actix-web-actors` crate
|
||||||
|
|
||||||
* Custom Error
|
- Custom Error
|
||||||
|
|
||||||
Instead of error_response method alone, ResponseError now provides two methods: error_response and render_response respectively. Where, error_response creates the error response and render_response returns the error response to the caller.
|
Instead of error_response method alone, ResponseError now provides two methods: error_response and render_response respectively. Where, error_response creates the error response and render_response returns the error response to the caller.
|
||||||
|
|
||||||
|
@ -471,7 +471,7 @@
|
||||||
|
|
||||||
## 0.7.15
|
## 0.7.15
|
||||||
|
|
||||||
* The `' '` character is not percent decoded anymore before matching routes. If you need to use it in
|
- The `' '` character is not percent decoded anymore before matching routes. If you need to use it in
|
||||||
your routes, you should use `%20`.
|
your routes, you should use `%20`.
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
@ -496,18 +496,18 @@
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* If you used `AsyncResult::async` you need to replace it with `AsyncResult::future`
|
- If you used `AsyncResult::async` you need to replace it with `AsyncResult::future`
|
||||||
|
|
||||||
|
|
||||||
## 0.7.4
|
## 0.7.4
|
||||||
|
|
||||||
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
|
- `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
|
||||||
even for handler with one parameter.
|
even for handler with one parameter.
|
||||||
|
|
||||||
|
|
||||||
## 0.7
|
## 0.7
|
||||||
|
|
||||||
* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload
|
- `HttpRequest` does not implement `Stream` anymore. If you need to read request payload
|
||||||
use `HttpMessage::payload()` method.
|
use `HttpMessage::payload()` method.
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
@ -533,10 +533,10 @@
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html)
|
- [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html)
|
||||||
trait uses `&HttpRequest` instead of `&mut HttpRequest`.
|
trait uses `&HttpRequest` instead of `&mut HttpRequest`.
|
||||||
|
|
||||||
* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead.
|
- Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead.
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
|
||||||
|
@ -550,17 +550,17 @@
|
||||||
fn index((query, json): (Query<..>, Json<MyStruct)) -> impl Responder {}
|
fn index((query, json): (Query<..>, Json<MyStruct)) -> impl Responder {}
|
||||||
```
|
```
|
||||||
|
|
||||||
* `Handler::handle()` uses `&self` instead of `&mut self`
|
- `Handler::handle()` uses `&self` instead of `&mut self`
|
||||||
|
|
||||||
* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value
|
- `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value
|
||||||
|
|
||||||
* Removed deprecated `HttpServer::threads()`, use
|
- Removed deprecated `HttpServer::threads()`, use
|
||||||
[HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead.
|
[HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead.
|
||||||
|
|
||||||
* Renamed `client::ClientConnectorError::Connector` to
|
- Renamed `client::ClientConnectorError::Connector` to
|
||||||
`client::ClientConnectorError::Resolver`
|
`client::ClientConnectorError::Resolver`
|
||||||
|
|
||||||
* `Route::with()` does not return `ExtractorConfig`, to configure
|
- `Route::with()` does not return `ExtractorConfig`, to configure
|
||||||
extractor use `Route::with_config()`
|
extractor use `Route::with_config()`
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
@ -589,26 +589,26 @@
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* `Route::with_async()` does not return `ExtractorConfig`, to configure
|
- `Route::with_async()` does not return `ExtractorConfig`, to configure
|
||||||
extractor use `Route::with_async_config()`
|
extractor use `Route::with_async_config()`
|
||||||
|
|
||||||
|
|
||||||
## 0.6
|
## 0.6
|
||||||
|
|
||||||
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
|
- `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
|
||||||
|
|
||||||
* `ws::Message::Close` now includes optional close reason.
|
- `ws::Message::Close` now includes optional close reason.
|
||||||
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
|
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
|
||||||
|
|
||||||
* `HttpServer::threads()` renamed to `HttpServer::workers()`.
|
- `HttpServer::threads()` renamed to `HttpServer::workers()`.
|
||||||
|
|
||||||
* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated.
|
- `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated.
|
||||||
Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead.
|
Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead.
|
||||||
|
|
||||||
* `HttpRequest::extensions()` returns read only reference to the request's Extension
|
- `HttpRequest::extensions()` returns read only reference to the request's Extension
|
||||||
`HttpRequest::extensions_mut()` returns mutable reference.
|
`HttpRequest::extensions_mut()` returns mutable reference.
|
||||||
|
|
||||||
* Instead of
|
- Instead of
|
||||||
|
|
||||||
`use actix_web::middleware::{
|
`use actix_web::middleware::{
|
||||||
CookieSessionBackend, CookieSessionError, RequestSession,
|
CookieSessionBackend, CookieSessionError, RequestSession,
|
||||||
|
@ -619,15 +619,15 @@
|
||||||
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,
|
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,
|
||||||
RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
|
RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
|
||||||
|
|
||||||
* `FromRequest::from_request()` accepts mutable reference to a request
|
- `FromRequest::from_request()` accepts mutable reference to a request
|
||||||
|
|
||||||
* `FromRequest::Result` has to implement `Into<Reply<Self>>`
|
- `FromRequest::Result` has to implement `Into<Reply<Self>>`
|
||||||
|
|
||||||
* [`Responder::respond_to()`](
|
- [`Responder::respond_to()`](
|
||||||
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
|
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
|
||||||
is generic over `S`
|
is generic over `S`
|
||||||
|
|
||||||
* Use `Query` extractor instead of HttpRequest::query()`.
|
- Use `Query` extractor instead of HttpRequest::query()`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
|
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
|
||||||
|
@ -641,37 +641,37 @@
|
||||||
let q = Query::<HashMap<String, String>>::extract(req);
|
let q = Query::<HashMap<String, String>>::extract(req);
|
||||||
```
|
```
|
||||||
|
|
||||||
* Websocket operations are implemented as `WsWriter` trait.
|
- Websocket operations are implemented as `WsWriter` trait.
|
||||||
you need to use `use actix_web::ws::WsWriter`
|
you need to use `use actix_web::ws::WsWriter`
|
||||||
|
|
||||||
|
|
||||||
## 0.5
|
## 0.5
|
||||||
|
|
||||||
* `HttpResponseBuilder::body()`, `.finish()`, `.json()`
|
- `HttpResponseBuilder::body()`, `.finish()`, `.json()`
|
||||||
methods return `HttpResponse` instead of `Result<HttpResponse>`
|
methods return `HttpResponse` instead of `Result<HttpResponse>`
|
||||||
|
|
||||||
* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version`
|
- `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version`
|
||||||
moved to `actix_web::http` module
|
moved to `actix_web::http` module
|
||||||
|
|
||||||
* `actix_web::header` moved to `actix_web::http::header`
|
- `actix_web::header` moved to `actix_web::http::header`
|
||||||
|
|
||||||
* `NormalizePath` moved to `actix_web::http` module
|
- `NormalizePath` moved to `actix_web::http` module
|
||||||
|
|
||||||
* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function,
|
- `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function,
|
||||||
shortcut for `actix_web::server::HttpServer::new()`
|
shortcut for `actix_web::server::HttpServer::new()`
|
||||||
|
|
||||||
* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself
|
- `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself
|
||||||
|
|
||||||
* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead.
|
- `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead.
|
||||||
|
|
||||||
* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type
|
- `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type
|
||||||
|
|
||||||
* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()`
|
- `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()`
|
||||||
functions should be used instead
|
functions should be used instead
|
||||||
|
|
||||||
* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>`
|
- `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>`
|
||||||
instead of `Result<_, http::Error>`
|
instead of `Result<_, http::Error>`
|
||||||
|
|
||||||
* `Application` renamed to a `App`
|
- `Application` renamed to a `App`
|
||||||
|
|
||||||
* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev`
|
- `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev`
|
||||||
|
|
64
README.md
64
README.md
|
@ -6,10 +6,10 @@
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web)
|
[](https://crates.io/crates/actix-web)
|
||||||
[](https://docs.rs/actix-web/4.0.0-beta.13)
|
[](https://docs.rs/actix-web/4.0.0-beta.17)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-web/4.0.0-beta.13)
|
[](https://deps.rs/crate/actix-web/4.0.0-beta.17)
|
||||||
<br />
|
<br />
|
||||||
[](https://github.com/actix/actix-web/actions)
|
[](https://github.com/actix/actix-web/actions)
|
||||||
[](https://codecov.io/gh/actix/actix-web)
|
[](https://codecov.io/gh/actix/actix-web)
|
||||||
|
@ -21,25 +21,25 @@
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Supports *HTTP/1.x* and *HTTP/2*
|
- Supports *HTTP/1.x* and *HTTP/2*
|
||||||
* Streaming and pipelining
|
- Streaming and pipelining
|
||||||
* Keep-alive and slow requests handling
|
- Keep-alive and slow requests handling
|
||||||
* Client/server [WebSockets](https://actix.rs/docs/websockets/) support
|
- Client/server [WebSockets](https://actix.rs/docs/websockets/) support
|
||||||
* Transparent content compression/decompression (br, gzip, deflate, zstd)
|
- Transparent content compression/decompression (br, gzip, deflate, zstd)
|
||||||
* Powerful [request routing](https://actix.rs/docs/url-dispatch/)
|
- Powerful [request routing](https://actix.rs/docs/url-dispatch/)
|
||||||
* Multipart streams
|
- Multipart streams
|
||||||
* Static assets
|
- Static assets
|
||||||
* SSL support using OpenSSL or Rustls
|
- SSL support using OpenSSL or Rustls
|
||||||
* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
|
- Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
|
||||||
* Includes an async [HTTP client](https://docs.rs/awc/)
|
- Includes an async [HTTP client](https://docs.rs/awc/)
|
||||||
* Runs on stable Rust 1.52+
|
- Runs on stable Rust 1.52+
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
* [Website & User Guide](https://actix.rs)
|
- [Website & User Guide](https://actix.rs)
|
||||||
* [Examples Repository](https://github.com/actix/examples)
|
- [Examples Repository](https://github.com/actix/examples)
|
||||||
* [API Documentation](https://docs.rs/actix-web)
|
- [API Documentation](https://docs.rs/actix-web)
|
||||||
* [API Documentation (master branch)](https://actix.rs/actix-web/actix_web)
|
- [API Documentation (master branch)](https://actix.rs/actix-web/actix_web)
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
@ -71,18 +71,18 @@ async fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
### More examples
|
### More examples
|
||||||
|
|
||||||
* [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/)
|
- [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/)
|
||||||
* [Application State](https://github.com/actix/examples/tree/master/basics/state/)
|
- [Application State](https://github.com/actix/examples/tree/master/basics/state/)
|
||||||
* [JSON Handling](https://github.com/actix/examples/tree/master/json/json/)
|
- [JSON Handling](https://github.com/actix/examples/tree/master/json/json/)
|
||||||
* [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/)
|
- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/)
|
||||||
* [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/)
|
- [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/)
|
||||||
* [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/)
|
- [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/)
|
||||||
* [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/)
|
- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/)
|
||||||
* [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/)
|
- [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/)
|
||||||
* [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/)
|
- [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/)
|
||||||
* [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/)
|
- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/)
|
||||||
* [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/)
|
- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/)
|
||||||
* [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/)
|
- [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/)
|
||||||
|
|
||||||
You may consider checking out
|
You may consider checking out
|
||||||
[this directory](https://github.com/actix/examples/tree/master/) for more examples.
|
[this directory](https://github.com/actix/examples/tree/master/) for more examples.
|
||||||
|
@ -96,9 +96,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])
|
||||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
- MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||||
[http://opensource.org/licenses/MIT])
|
[http://opensource.org/licenses/MIT])
|
||||||
|
|
||||||
at your option.
|
at your option.
|
||||||
|
|
|
@ -3,39 +3,51 @@
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0-beta.12 - 2021-12-29
|
||||||
|
* No significant changes since `0.6.0-beta.11`.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0-beta.11 - 2021-12-27
|
||||||
|
* No significant changes since `0.6.0-beta.10`.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0-beta.10 - 2021-12-11
|
||||||
|
- No significant changes since `0.6.0-beta.9`.
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.9 - 2021-11-22
|
## 0.6.0-beta.9 - 2021-11-22
|
||||||
* Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408]
|
- Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408]
|
||||||
* Add `NamedFile::open_async`. [#2408]
|
- Add `NamedFile::open_async`. [#2408]
|
||||||
* Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453]
|
- Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453]
|
||||||
* The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408]
|
- The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408]
|
||||||
* The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408]
|
- The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408]
|
||||||
* Add `impl Clone` for `FilesService`. [#2408]
|
- Add `impl Clone` for `FilesService`. [#2408]
|
||||||
|
|
||||||
[#2408]: https://github.com/actix/actix-web/pull/2408
|
[#2408]: https://github.com/actix/actix-web/pull/2408
|
||||||
[#2453]: https://github.com/actix/actix-web/pull/2453
|
[#2453]: https://github.com/actix/actix-web/pull/2453
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.8 - 2021-10-20
|
## 0.6.0-beta.8 - 2021-10-20
|
||||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.7 - 2021-09-09
|
## 0.6.0-beta.7 - 2021-09-09
|
||||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.6 - 2021-06-26
|
## 0.6.0-beta.6 - 2021-06-26
|
||||||
* Added `Files::path_filter()`. [#2274]
|
- Added `Files::path_filter()`. [#2274]
|
||||||
* `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228]
|
- `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228]
|
||||||
|
|
||||||
[#2274]: https://github.com/actix/actix-web/pull/2274
|
[#2274]: https://github.com/actix/actix-web/pull/2274
|
||||||
[#2228]: https://github.com/actix/actix-web/pull/2228
|
[#2228]: https://github.com/actix/actix-web/pull/2228
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.5 - 2021-06-17
|
## 0.6.0-beta.5 - 2021-06-17
|
||||||
* `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135]
|
- `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135]
|
||||||
* For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156]
|
- For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156]
|
||||||
* `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225]
|
- `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225]
|
||||||
* `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257]
|
- `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257]
|
||||||
|
|
||||||
[#2135]: https://github.com/actix/actix-web/pull/2135
|
[#2135]: https://github.com/actix/actix-web/pull/2135
|
||||||
[#2156]: https://github.com/actix/actix-web/pull/2156
|
[#2156]: https://github.com/actix/actix-web/pull/2156
|
||||||
|
@ -44,130 +56,130 @@
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.4 - 2021-04-02
|
## 0.6.0-beta.4 - 2021-04-02
|
||||||
* Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046]
|
- Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046]
|
||||||
|
|
||||||
[#2046]: https://github.com/actix/actix-web/pull/2046
|
[#2046]: https://github.com/actix/actix-web/pull/2046
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.3 - 2021-03-09
|
## 0.6.0-beta.3 - 2021-03-09
|
||||||
* No notable changes.
|
- No notable changes.
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.2 - 2021-02-10
|
## 0.6.0-beta.2 - 2021-02-10
|
||||||
* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
|
- Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
|
||||||
* Replace `v_htmlescape` with `askama_escape`. [#1953]
|
- Replace `v_htmlescape` with `askama_escape`. [#1953]
|
||||||
|
|
||||||
[#1887]: https://github.com/actix/actix-web/pull/1887
|
[#1887]: https://github.com/actix/actix-web/pull/1887
|
||||||
[#1953]: https://github.com/actix/actix-web/pull/1953
|
[#1953]: https://github.com/actix/actix-web/pull/1953
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.1 - 2021-01-07
|
## 0.6.0-beta.1 - 2021-01-07
|
||||||
* `HttpRange::parse` now has its own error type.
|
- `HttpRange::parse` now has its own error type.
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
- Update `bytes` to `1.0`. [#1813]
|
||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
|
|
||||||
|
|
||||||
## 0.5.0 - 2020-12-26
|
## 0.5.0 - 2020-12-26
|
||||||
* Optionally support hidden files/directories. [#1811]
|
- Optionally support hidden files/directories. [#1811]
|
||||||
|
|
||||||
[#1811]: https://github.com/actix/actix-web/pull/1811
|
[#1811]: https://github.com/actix/actix-web/pull/1811
|
||||||
|
|
||||||
|
|
||||||
## 0.4.1 - 2020-11-24
|
## 0.4.1 - 2020-11-24
|
||||||
* Clarify order of parameters in `Files::new` and improve docs.
|
- Clarify order of parameters in `Files::new` and improve docs.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0 - 2020-10-06
|
## 0.4.0 - 2020-10-06
|
||||||
* Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714]
|
- Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714]
|
||||||
|
|
||||||
[#1714]: https://github.com/actix/actix-web/pull/1714
|
[#1714]: https://github.com/actix/actix-web/pull/1714
|
||||||
|
|
||||||
|
|
||||||
## 0.3.0 - 2020-09-11
|
## 0.3.0 - 2020-09-11
|
||||||
* No significant changes from 0.3.0-beta.1.
|
- No significant changes from 0.3.0-beta.1.
|
||||||
|
|
||||||
|
|
||||||
## 0.3.0-beta.1 - 2020-07-15
|
## 0.3.0-beta.1 - 2020-07-15
|
||||||
* Update `v_htmlescape` to 0.10
|
- Update `v_htmlescape` to 0.10
|
||||||
* Update `actix-web` and `actix-http` dependencies to beta.1
|
- Update `actix-web` and `actix-http` dependencies to beta.1
|
||||||
|
|
||||||
|
|
||||||
## 0.3.0-alpha.1 - 2020-05-23
|
## 0.3.0-alpha.1 - 2020-05-23
|
||||||
* Update `actix-web` and `actix-http` dependencies to alpha
|
- Update `actix-web` and `actix-http` dependencies to alpha
|
||||||
* Fix some typos in the docs
|
- Fix some typos in the docs
|
||||||
* Bump minimum supported Rust version to 1.40
|
- Bump minimum supported Rust version to 1.40
|
||||||
* Support sending Content-Length when Content-Range is specified [#1384]
|
- Support sending Content-Length when Content-Range is specified [#1384]
|
||||||
|
|
||||||
[#1384]: https://github.com/actix/actix-web/pull/1384
|
[#1384]: https://github.com/actix/actix-web/pull/1384
|
||||||
|
|
||||||
|
|
||||||
## 0.2.1 - 2019-12-22
|
## 0.2.1 - 2019-12-22
|
||||||
* Use the same format for file URLs regardless of platforms
|
- Use the same format for file URLs regardless of platforms
|
||||||
|
|
||||||
|
|
||||||
## 0.2.0 - 2019-12-20
|
## 0.2.0 - 2019-12-20
|
||||||
* Fix BodyEncoding trait import #1220
|
- Fix BodyEncoding trait import #1220
|
||||||
|
|
||||||
|
|
||||||
## 0.2.0-alpha.1 - 2019-12-07
|
## 0.2.0-alpha.1 - 2019-12-07
|
||||||
* Migrate to `std::future`
|
- Migrate to `std::future`
|
||||||
|
|
||||||
|
|
||||||
## 0.1.7 - 2019-11-06
|
## 0.1.7 - 2019-11-06
|
||||||
* 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)
|
||||||
|
|
||||||
## 0.1.6 - 2019-10-14
|
## 0.1.6 - 2019-10-14
|
||||||
* Add option to redirect to a slash-ended path `Files` #1132
|
- Add option to redirect to a slash-ended path `Files` #1132
|
||||||
|
|
||||||
|
|
||||||
## 0.1.5 - 2019-10-08
|
## 0.1.5 - 2019-10-08
|
||||||
* Bump up `mime_guess` crate version to 2.0.1
|
- Bump up `mime_guess` crate version to 2.0.1
|
||||||
* Bump up `percent-encoding` crate version to 2.1
|
- Bump up `percent-encoding` crate version to 2.1
|
||||||
* Allow user defined request guards for `Files` #1113
|
- Allow user defined request guards for `Files` #1113
|
||||||
|
|
||||||
|
|
||||||
## 0.1.4 - 2019-07-20
|
## 0.1.4 - 2019-07-20
|
||||||
* Allow to disable `Content-Disposition` header #686
|
- Allow to disable `Content-Disposition` header #686
|
||||||
|
|
||||||
|
|
||||||
## 0.1.3 - 2019-06-28
|
## 0.1.3 - 2019-06-28
|
||||||
* Do not set `Content-Length` header, let actix-http set it #930
|
- Do not set `Content-Length` header, let actix-http set it #930
|
||||||
|
|
||||||
|
|
||||||
## 0.1.2 - 2019-06-13
|
## 0.1.2 - 2019-06-13
|
||||||
* Content-Length is 0 for NamedFile HEAD request #914
|
- Content-Length is 0 for NamedFile HEAD request #914
|
||||||
* Fix ring dependency from actix-web default features for #741
|
- Fix ring dependency from actix-web default features for #741
|
||||||
|
|
||||||
|
|
||||||
## 0.1.1 - 2019-06-01
|
## 0.1.1 - 2019-06-01
|
||||||
* Static files are incorrectly served as both chunked and with length #812
|
- Static files are incorrectly served as both chunked and with length #812
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0 - 2019-05-25
|
## 0.1.0 - 2019-05-25
|
||||||
* NamedFile last-modified check always fails due to nano-seconds in file modified date #820
|
- NamedFile last-modified check always fails due to nano-seconds in file modified date #820
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0-beta.4 - 2019-05-12
|
## 0.1.0-beta.4 - 2019-05-12
|
||||||
* Update actix-web to beta.4
|
- Update actix-web to beta.4
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0-beta.1 - 2019-04-20
|
## 0.1.0-beta.1 - 2019-04-20
|
||||||
* Update actix-web to beta.1
|
- Update actix-web to beta.1
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0-alpha.6 - 2019-04-14
|
## 0.1.0-alpha.6 - 2019-04-14
|
||||||
* Update actix-web to alpha6
|
- Update actix-web to alpha6
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0-alpha.4 - 2019-04-08
|
## 0.1.0-alpha.4 - 2019-04-08
|
||||||
* Update actix-web to alpha4
|
- Update actix-web to alpha4
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0-alpha.2 - 2019-04-02
|
## 0.1.0-alpha.2 - 2019-04-02
|
||||||
* Add default handler support
|
- Add default handler support
|
||||||
|
|
||||||
|
|
||||||
## 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-files"
|
name = "actix-files"
|
||||||
version = "0.6.0-beta.9"
|
version = "0.6.0-beta.12"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"fakeshadow <24548779@qq.com>",
|
"fakeshadow <24548779@qq.com>",
|
||||||
|
@ -22,17 +22,17 @@ path = "src/lib.rs"
|
||||||
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
|
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.11", default-features = false }
|
actix-http = "3.0.0-beta.17"
|
||||||
actix-http = "3.0.0-beta.14"
|
|
||||||
actix-service = "2"
|
actix-service = "2"
|
||||||
actix-utils = "3"
|
actix-utils = "3"
|
||||||
|
actix-web = { version = "4.0.0-beta.17", default-features = false }
|
||||||
|
|
||||||
askama_escape = "0.10"
|
askama_escape = "0.10"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
derive_more = "0.99.5"
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
http-range = "0.1.4"
|
http-range = "0.1.4"
|
||||||
derive_more = "0.99.5"
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
mime_guess = "2.0.1"
|
mime_guess = "2.0.1"
|
||||||
|
@ -43,6 +43,6 @@ tokio-uring = { version = "0.1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-web = "4.0.0-beta.11"
|
actix-test = "0.1.0-beta.10"
|
||||||
actix-test = "0.1.0-beta.7"
|
actix-web = "4.0.0-beta.17"
|
||||||
tempfile = "3.2"
|
tempfile = "3.2"
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
> Static file serving for Actix Web
|
> Static file serving for Actix Web
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://docs.rs/actix-files/0.6.0-beta.9)
|
[](https://docs.rs/actix-files/0.6.0-beta.12)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-files/0.6.0-beta.9)
|
[](https://deps.rs/crate/actix-files/0.6.0-beta.12)
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
|
|
@ -262,9 +262,9 @@ impl Files {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See [`Files::method_guard`].
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[deprecated(since = "0.6.0", note = "Renamed to `method_guard`.")]
|
#[deprecated(since = "0.6.0", note = "Renamed to `method_guard`.")]
|
||||||
/// See [`Files::method_guard`].
|
|
||||||
pub fn use_guards<G: Guard + 'static>(self, guard: G) -> Self {
|
pub fn use_guards<G: Guard + 'static>(self, guard: G) -> Self {
|
||||||
self.method_guard(guard)
|
self.method_guard(guard)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
//! .service(Files::new("/static", ".").prefer_utf8(true));
|
//! .service(Files::new("/static", ".").prefer_utf8(true));
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
#![warn(missing_docs, missing_debug_implementations)]
|
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
|
||||||
|
|
||||||
use actix_service::boxed::{BoxService, BoxServiceFactory};
|
use actix_service::boxed::{BoxService, BoxServiceFactory};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
|
|
|
@ -2,14 +2,10 @@ use std::{
|
||||||
fmt,
|
fmt,
|
||||||
fs::Metadata,
|
fs::Metadata,
|
||||||
io,
|
io,
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
use std::os::unix::fs::MetadataExt;
|
|
||||||
|
|
||||||
use actix_service::{Service, ServiceFactory};
|
use actix_service::{Service, ServiceFactory};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
body::{self, BoxBody, SizedStream},
|
body::{self, BoxBody, SizedStream},
|
||||||
|
@ -27,6 +23,7 @@ use actix_web::{
|
||||||
Error, HttpMessage, HttpRequest, HttpResponse, Responder,
|
Error, HttpMessage, HttpRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use mime_guess::from_path;
|
use mime_guess::from_path;
|
||||||
|
|
||||||
|
@ -71,8 +68,11 @@ impl Default for Flags {
|
||||||
/// NamedFile::open_async("./static/index.html").await
|
/// NamedFile::open_async("./static/index.html").await
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[derive(Deref, DerefMut)]
|
||||||
pub struct NamedFile {
|
pub struct NamedFile {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
#[deref]
|
||||||
|
#[deref_mut]
|
||||||
file: File,
|
file: File,
|
||||||
modified: Option<SystemTime>,
|
modified: Option<SystemTime>,
|
||||||
pub(crate) md: Metadata,
|
pub(crate) md: Metadata,
|
||||||
|
@ -364,14 +364,18 @@ impl NamedFile {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a etag in a format is similar to Apache's.
|
||||||
pub(crate) fn etag(&self) -> Option<header::EntityTag> {
|
pub(crate) fn etag(&self) -> Option<header::EntityTag> {
|
||||||
// This etag format is similar to Apache's.
|
|
||||||
self.modified.as_ref().map(|mtime| {
|
self.modified.as_ref().map(|mtime| {
|
||||||
let ino = {
|
let ino = {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::fs::MetadataExt as _;
|
||||||
|
|
||||||
self.md.ino()
|
self.md.ino()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
{
|
{
|
||||||
0
|
0
|
||||||
|
@ -472,17 +476,17 @@ impl NamedFile {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut resp = 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());
|
||||||
resp.insert_header((header::CONTENT_TYPE, ct.to_string()));
|
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
|
||||||
} else {
|
} else {
|
||||||
resp.insert_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) {
|
||||||
resp.insert_header((
|
res.insert_header((
|
||||||
header::CONTENT_DISPOSITION,
|
header::CONTENT_DISPOSITION,
|
||||||
self.content_disposition.to_string(),
|
self.content_disposition.to_string(),
|
||||||
));
|
));
|
||||||
|
@ -490,18 +494,18 @@ impl NamedFile {
|
||||||
|
|
||||||
// default compressing
|
// default compressing
|
||||||
if let Some(current_encoding) = self.encoding {
|
if let Some(current_encoding) = self.encoding {
|
||||||
resp.encoding(current_encoding);
|
res.encoding(current_encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(lm) = last_modified {
|
if let Some(lm) = last_modified {
|
||||||
resp.insert_header((header::LAST_MODIFIED, lm.to_string()));
|
res.insert_header((header::LAST_MODIFIED, lm.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(etag) = etag {
|
if let Some(etag) = etag {
|
||||||
resp.insert_header((header::ETAG, etag.to_string()));
|
res.insert_header((header::ETAG, etag.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.insert_header((header::ACCEPT_RANGES, "bytes"));
|
res.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;
|
||||||
|
@ -513,24 +517,24 @@ impl NamedFile {
|
||||||
length = ranges[0].length;
|
length = ranges[0].length;
|
||||||
offset = ranges[0].start;
|
offset = ranges[0].start;
|
||||||
|
|
||||||
resp.encoding(ContentEncoding::Identity);
|
res.encoding(ContentEncoding::Identity);
|
||||||
resp.insert_header((
|
res.insert_header((
|
||||||
header::CONTENT_RANGE,
|
header::CONTENT_RANGE,
|
||||||
format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()),
|
format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
resp.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length)));
|
res.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length)));
|
||||||
return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
|
return res.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return resp.status(StatusCode::BAD_REQUEST).finish();
|
return res.status(StatusCode::BAD_REQUEST).finish();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if precondition_failed {
|
if precondition_failed {
|
||||||
return resp.status(StatusCode::PRECONDITION_FAILED).finish();
|
return res.status(StatusCode::PRECONDITION_FAILED).finish();
|
||||||
} else if not_modified {
|
} else if not_modified {
|
||||||
return resp
|
return res
|
||||||
.status(StatusCode::NOT_MODIFIED)
|
.status(StatusCode::NOT_MODIFIED)
|
||||||
.body(body::None::new())
|
.body(body::None::new())
|
||||||
.map_into_boxed_body();
|
.map_into_boxed_body();
|
||||||
|
@ -539,10 +543,10 @@ impl NamedFile {
|
||||||
let reader = chunked::new_chunked_read(length, offset, self.file);
|
let reader = chunked::new_chunked_read(length, offset, self.file);
|
||||||
|
|
||||||
if offset != 0 || length != self.md.len() {
|
if offset != 0 || length != self.md.len() {
|
||||||
resp.status(StatusCode::PARTIAL_CONTENT);
|
res.status(StatusCode::PARTIAL_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.body(SizedStream::new(length, reader))
|
res.body(SizedStream::new(length, reader))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,20 +590,6 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for NamedFile {
|
|
||||||
type Target = File;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for NamedFile {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Responder for NamedFile {
|
impl Responder for NamedFile {
|
||||||
type Body = BoxBody;
|
type Body = BoxBody;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::{fmt, io, ops::Deref, path::PathBuf, rc::Rc};
|
use std::{fmt, io, ops::Deref, path::PathBuf, rc::Rc};
|
||||||
|
|
||||||
use actix_service::Service;
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{ServiceRequest, ServiceResponse},
|
body::BoxBody,
|
||||||
|
dev::{Service, ServiceRequest, ServiceResponse},
|
||||||
error::Error,
|
error::Error,
|
||||||
guard::Guard,
|
guard::Guard,
|
||||||
http::{header, Method},
|
http::{header, Method},
|
||||||
|
@ -94,7 +94,7 @@ impl fmt::Debug for FilesService {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<ServiceRequest> for FilesService {
|
impl Service<ServiceRequest> for FilesService {
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse<BoxBody>;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ impl Service<ServiceRequest> for FilesService {
|
||||||
fn call(&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.guard_ctx())
|
||||||
} else {
|
} else {
|
||||||
// default behavior
|
// default behavior
|
||||||
matches!(*req.method(), Method::HEAD | Method::GET)
|
matches!(*req.method(), Method::HEAD | Method::GET)
|
||||||
|
|
|
@ -3,122 +3,132 @@
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.10 - 2021-12-27
|
||||||
|
- Update `actix-server` to `2.0.0-rc.2`. [#2550]
|
||||||
|
|
||||||
|
[#2550]: https://github.com/actix/actix-web/pull/2550
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.9 - 2021-12-11
|
||||||
|
- No significant changes since `3.0.0-beta.8`.
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.8 - 2021-11-30
|
## 3.0.0-beta.8 - 2021-11-30
|
||||||
* Update `actix-tls` to `3.0.0-rc.1`. [#2474]
|
- Update `actix-tls` to `3.0.0-rc.1`. [#2474]
|
||||||
|
|
||||||
[#2474]: https://github.com/actix/actix-web/pull/2474
|
[#2474]: https://github.com/actix/actix-web/pull/2474
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.7 - 2021-11-22
|
## 3.0.0-beta.7 - 2021-11-22
|
||||||
* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]
|
- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]
|
||||||
|
|
||||||
[#2408]: https://github.com/actix/actix-web/pull/2408
|
[#2408]: https://github.com/actix/actix-web/pull/2408
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.6 - 2021-11-15
|
## 3.0.0-beta.6 - 2021-11-15
|
||||||
* `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442]
|
- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442]
|
||||||
* Update `actix-server` to `2.0.0-beta.9`. [#2442]
|
- Update `actix-server` to `2.0.0-beta.9`. [#2442]
|
||||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||||
|
|
||||||
[#2442]: https://github.com/actix/actix-web/pull/2442
|
[#2442]: https://github.com/actix/actix-web/pull/2442
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.5 - 2021-09-09
|
## 3.0.0-beta.5 - 2021-09-09
|
||||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.4 - 2021-04-02
|
## 3.0.0-beta.4 - 2021-04-02
|
||||||
* Added `TestServer::client_headers` method. [#2097]
|
- Added `TestServer::client_headers` method. [#2097]
|
||||||
|
|
||||||
[#2097]: https://github.com/actix/actix-web/pull/2097
|
[#2097]: https://github.com/actix/actix-web/pull/2097
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.3 - 2021-03-09
|
## 3.0.0-beta.3 - 2021-03-09
|
||||||
* No notable changes.
|
- No notable changes.
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.2 - 2021-02-10
|
## 3.0.0-beta.2 - 2021-02-10
|
||||||
* No notable changes.
|
- 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]
|
||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
|
|
||||||
|
|
||||||
## 2.1.0 - 2020-11-25
|
## 2.1.0 - 2020-11-25
|
||||||
* Add ability to set address for `TestServer`. [#1645]
|
- Add ability to set address for `TestServer`. [#1645]
|
||||||
* Upgrade `base64` to `0.13`.
|
- Upgrade `base64` to `0.13`.
|
||||||
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
|
- Upgrade `serde_urlencoded` to `0.7`. [#1773]
|
||||||
|
|
||||||
[#1773]: https://github.com/actix/actix-web/pull/1773
|
[#1773]: https://github.com/actix/actix-web/pull/1773
|
||||||
[#1645]: https://github.com/actix/actix-web/pull/1645
|
[#1645]: https://github.com/actix/actix-web/pull/1645
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0 - 2020-09-11
|
## 2.0.0 - 2020-09-11
|
||||||
* Update actix-codec and actix-utils dependencies.
|
- Update actix-codec and actix-utils dependencies.
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0-alpha.1 - 2020-05-23
|
## 2.0.0-alpha.1 - 2020-05-23
|
||||||
* Update the `time` dependency to 0.2.7
|
- Update the `time` dependency to 0.2.7
|
||||||
* Update `actix-connect` dependency to 2.0.0-alpha.2
|
- Update `actix-connect` dependency to 2.0.0-alpha.2
|
||||||
* Make `test_server` `async` fn.
|
- Make `test_server` `async` fn.
|
||||||
* Bump minimum supported Rust version to 1.40
|
- Bump minimum supported Rust version to 1.40
|
||||||
* Replace deprecated `net2` crate with `socket2`
|
- Replace deprecated `net2` crate with `socket2`
|
||||||
* Update `base64` dependency to 0.12
|
- Update `base64` dependency to 0.12
|
||||||
* Update `env_logger` dependency to 0.7
|
- Update `env_logger` dependency to 0.7
|
||||||
|
|
||||||
## 1.0.0 - 2019-12-13
|
## 1.0.0 - 2019-12-13
|
||||||
* Replaced `TestServer::start()` with `test_server()`
|
- Replaced `TestServer::start()` with `test_server()`
|
||||||
|
|
||||||
|
|
||||||
## 1.0.0-alpha.3 - 2019-12-07
|
## 1.0.0-alpha.3 - 2019-12-07
|
||||||
* Migrate to `std::future`
|
- Migrate to `std::future`
|
||||||
|
|
||||||
|
|
||||||
## 0.2.5 - 2019-09-17
|
## 0.2.5 - 2019-09-17
|
||||||
* Update serde_urlencoded to "0.6.1"
|
- Update serde_urlencoded to "0.6.1"
|
||||||
* Increase TestServerRuntime timeouts from 500ms to 3000ms
|
- Increase TestServerRuntime timeouts from 500ms to 3000ms
|
||||||
* Do not override current `System`
|
- Do not override current `System`
|
||||||
|
|
||||||
|
|
||||||
## 0.2.4 - 2019-07-18
|
## 0.2.4 - 2019-07-18
|
||||||
* Update actix-server to 0.6
|
- Update actix-server to 0.6
|
||||||
|
|
||||||
|
|
||||||
## 0.2.3 - 2019-07-16
|
## 0.2.3 - 2019-07-16
|
||||||
* Add `delete`, `options`, `patch` methods to `TestServerRunner`
|
- Add `delete`, `options`, `patch` methods to `TestServerRunner`
|
||||||
|
|
||||||
|
|
||||||
## 0.2.2 - 2019-06-16
|
## 0.2.2 - 2019-06-16
|
||||||
* Add .put() and .sput() methods
|
- Add .put() and .sput() methods
|
||||||
|
|
||||||
|
|
||||||
## 0.2.1 - 2019-06-05
|
## 0.2.1 - 2019-06-05
|
||||||
* Add license files
|
- Add license files
|
||||||
|
|
||||||
|
|
||||||
## 0.2.0 - 2019-05-12
|
## 0.2.0 - 2019-05-12
|
||||||
* Update awc and actix-http deps
|
- Update awc and actix-http deps
|
||||||
|
|
||||||
|
|
||||||
## 0.1.1 - 2019-04-24
|
## 0.1.1 - 2019-04-24
|
||||||
* Always make new connection for http client
|
- Always make new connection for http client
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0 - 2019-04-16
|
## 0.1.0 - 2019-04-16
|
||||||
* No changes
|
- No changes
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0-alpha.3 - 2019-04-02
|
## 0.1.0-alpha.3 - 2019-04-02
|
||||||
* Request functions accept path #743
|
- Request functions accept path #743
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0-alpha.2 - 2019-03-29
|
## 0.1.0-alpha.2 - 2019-03-29
|
||||||
* Added TestServerRuntime::load_body() method
|
- Added TestServerRuntime::load_body() method
|
||||||
* Update actix-http and awc libraries
|
- Update actix-http and awc libraries
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0-alpha.1 - 2019-03-28
|
## 0.1.0-alpha.1 - 2019-03-28
|
||||||
* Initial impl
|
- Initial impl
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-http-test"
|
name = "actix-http-test"
|
||||||
version = "3.0.0-beta.8"
|
version = "3.0.0-beta.10"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Various helpers for Actix applications to use during testing"
|
description = "Various helpers for Actix applications to use during testing"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
|
@ -31,11 +31,11 @@ openssl = ["tls-openssl", "awc/openssl"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "2.0.0"
|
actix-service = "2.0.0"
|
||||||
actix-codec = "0.4.1"
|
actix-codec = "0.4.1"
|
||||||
actix-tls = "3.0.0-rc.1"
|
actix-tls = "3.0.0"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-server = "2.0.0-rc.1"
|
actix-server = "2.0.0-rc.2"
|
||||||
awc = { version = "3.0.0-beta.11", default-features = false }
|
awc = { version = "3.0.0-beta.16", default-features = false }
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
@ -48,8 +48,8 @@ serde_json = "1.0"
|
||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||||
tokio = { version = "1.2", features = ["sync"] }
|
tokio = { version = "1.8.4", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4.0.0-beta.17", default-features = false, features = ["cookies"] }
|
||||||
actix-http = "3.0.0-beta.14"
|
actix-http = "3.0.0-beta.17"
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
> Various helpers for Actix applications to use during testing.
|
> Various helpers for Actix applications to use during testing.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http-test)
|
[](https://crates.io/crates/actix-http-test)
|
||||||
[](https://docs.rs/actix-http-test/3.0.0-beta.8)
|
[](https://docs.rs/actix-http-test/3.0.0-beta.10)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||||

|

|
||||||
<br>
|
<br>
|
||||||
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.8)
|
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.10)
|
||||||
[](https://crates.io/crates/actix-http-test)
|
[](https://crates.io/crates/actix-http-test)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! Various helpers for Actix applications to use during testing.
|
//! Various helpers for Actix applications to use during testing.
|
||||||
|
|
||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
|
#![warn(future_incompatible)]
|
||||||
#![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")]
|
||||||
|
|
||||||
|
@ -11,7 +12,7 @@ use std::{net, thread, time::Duration};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
use actix_rt::{net::TcpStream, System};
|
use actix_rt::{net::TcpStream, System};
|
||||||
use actix_server::{Server, ServiceFactory};
|
use actix_server::{Server, ServerServiceFactory};
|
||||||
use awc::{
|
use awc::{
|
||||||
error::PayloadError, http::header::HeaderMap, ws, Client, ClientRequest, ClientResponse,
|
error::PayloadError, http::header::HeaderMap, ws, Client, ClientRequest, ClientResponse,
|
||||||
Connector,
|
Connector,
|
||||||
|
@ -50,13 +51,13 @@ use tokio::sync::mpsc;
|
||||||
/// assert!(response.status().is_success());
|
/// assert!(response.status().is_success());
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn test_server<F: ServiceFactory<TcpStream>>(factory: F) -> TestServer {
|
pub async fn test_server<F: ServerServiceFactory<TcpStream>>(factory: F) -> TestServer {
|
||||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
test_server_with_addr(tcp, factory).await
|
test_server_with_addr(tcp, factory).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start [`test server`](test_server()) on an existing address binding.
|
/// Start [`test server`](test_server()) on an existing address binding.
|
||||||
pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
pub async fn test_server_with_addr<F: ServerServiceFactory<TcpStream>>(
|
||||||
tcp: net::TcpListener,
|
tcp: net::TcpListener,
|
||||||
factory: F,
|
factory: F,
|
||||||
) -> TestServer {
|
) -> TestServer {
|
||||||
|
@ -106,7 +107,7 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
||||||
Connector::new()
|
Connector::new()
|
||||||
.conn_lifetime(Duration::from_secs(0))
|
.conn_lifetime(Duration::from_secs(0))
|
||||||
.timeout(Duration::from_millis(30000))
|
.timeout(Duration::from_millis(30000))
|
||||||
.ssl(builder.build())
|
.openssl(builder.build())
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "openssl"))]
|
#[cfg(not(feature = "openssl"))]
|
||||||
|
|
|
@ -1,52 +1,97 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
### Added
|
|
||||||
* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483]
|
|
||||||
* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
|
|
||||||
* `Response::map_into_boxed_body`. [#2468]
|
|
||||||
* `body::EitherBody` enum. [#2468]
|
|
||||||
* `body::None` struct. [#2468]
|
|
||||||
* Impl `MessageBody` for `bytestring::ByteString`. [#2468]
|
|
||||||
* `impl Clone for ws::HandshakeError`. [#2468]
|
|
||||||
* `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920]
|
|
||||||
* `impl Default ` for `ws::Codec`. [#1920]
|
|
||||||
* `header::QualityItem::{max, min}`. [#2486]
|
|
||||||
* `header::Quality::{MAX, MIN}`. [#2486]
|
|
||||||
* `impl Display` for `header::Quality`. [#2486]
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
* Rename `body::BoxBody::{from_body => new}`. [#2468]
|
## 3.0.0-beta.17 - 2021-12-27
|
||||||
* Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468]
|
### Changes
|
||||||
* The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468]
|
- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527]
|
||||||
* Error types using in service builders now require `Into<Response<BoxBody>>`. [#2468]
|
- `Payload` inner fields are now named. [#2545]
|
||||||
* `From` implementations on error types now return a `Response<BoxBody>`. [#2468]
|
- `impl Stream` for `Payload` no longer requires the `Stream` variant be `Unpin`. [#2545]
|
||||||
* `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468]
|
- `impl Future` for `h1::SendResponse` no longer requires the body type be `Unpin`. [#2545]
|
||||||
* `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468]
|
- `impl Stream` for `encoding::Decoder` no longer requires the stream type be `Unpin`. [#2545]
|
||||||
|
- Rename `PayloadStream` to `BoxedPayloadStream`. [#2545]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* `ResponseBuilder::streaming`. [#2468]
|
- `h1::Payload::readany`. [#2545]
|
||||||
* `impl Future` for `ResponseBuilder`. [#2468]
|
|
||||||
* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468]
|
[#2527]: https://github.com/actix/actix-web/pull/2527
|
||||||
* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468]
|
[#2545]: https://github.com/actix/actix-web/pull/2545
|
||||||
* `impl Copy` for `ws::Codec`. [#1920]
|
|
||||||
* `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486]
|
|
||||||
* `impl TryFrom<u16>` for `header::Quality`. [#2486]
|
## 3.0.0-beta.16 - 2021-12-17
|
||||||
* `http` module. Most everything it contained is exported at the crate root. [#2488]
|
### Added
|
||||||
|
- New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510]
|
||||||
|
- Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510]
|
||||||
|
- Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `MessageBody::{is_complete_body,take_complete_body}`. [#2522]
|
||||||
|
|
||||||
|
[#2510]: https://github.com/actix/actix-web/pull/2510
|
||||||
|
[#2522]: https://github.com/actix/actix-web/pull/2522
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.15 - 2021-12-11
|
||||||
|
### Added
|
||||||
|
- Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483]
|
||||||
|
- HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
|
||||||
|
- `Response::map_into_boxed_body`. [#2468]
|
||||||
|
- `body::EitherBody` enum. [#2468]
|
||||||
|
- `body::None` struct. [#2468]
|
||||||
|
- Impl `MessageBody` for `bytestring::ByteString`. [#2468]
|
||||||
|
- `impl Clone for ws::HandshakeError`. [#2468]
|
||||||
|
- `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920]
|
||||||
|
- `impl Default ` for `ws::Codec`. [#1920]
|
||||||
|
- `header::QualityItem::{max, min}`. [#2486]
|
||||||
|
- `header::Quality::{MAX, MIN}`. [#2486]
|
||||||
|
- `impl Display` for `header::Quality`. [#2486]
|
||||||
|
- Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491]
|
||||||
|
- `Request::take_conn_data()`. [#2491]
|
||||||
|
- `Request::take_req_data()`. [#2487]
|
||||||
|
- `impl Clone` for `RequestHead`. [#2487]
|
||||||
|
- New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497]
|
||||||
|
- New `boxed` method on `MessageBody` trait for wrapping body type. [#2520]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Rename `body::BoxBody::{from_body => new}`. [#2468]
|
||||||
|
- Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468]
|
||||||
|
- The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468]
|
||||||
|
- Error types using in service builders now require `Into<Response<BoxBody>>`. [#2468]
|
||||||
|
- `From` implementations on error types now return a `Response<BoxBody>`. [#2468]
|
||||||
|
- `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468]
|
||||||
|
- `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `ResponseBuilder::streaming`. [#2468]
|
||||||
|
- `impl Future` for `ResponseBuilder`. [#2468]
|
||||||
|
- Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468]
|
||||||
|
- Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468]
|
||||||
|
- `impl Copy` for `ws::Codec`. [#1920]
|
||||||
|
- `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486]
|
||||||
|
- `impl TryFrom<u16>` for `header::Quality`. [#2486]
|
||||||
|
- `http` module. Most everything it contained is exported at the crate root. [#2488]
|
||||||
|
|
||||||
[#2483]: https://github.com/actix/actix-web/pull/2483
|
[#2483]: https://github.com/actix/actix-web/pull/2483
|
||||||
[#2468]: https://github.com/actix/actix-web/pull/2468
|
[#2468]: https://github.com/actix/actix-web/pull/2468
|
||||||
[#1920]: https://github.com/actix/actix-web/pull/1920
|
[#1920]: https://github.com/actix/actix-web/pull/1920
|
||||||
[#2486]: https://github.com/actix/actix-web/pull/2486
|
[#2486]: https://github.com/actix/actix-web/pull/2486
|
||||||
|
[#2487]: https://github.com/actix/actix-web/pull/2487
|
||||||
[#2488]: https://github.com/actix/actix-web/pull/2488
|
[#2488]: https://github.com/actix/actix-web/pull/2488
|
||||||
|
[#2491]: https://github.com/actix/actix-web/pull/2491
|
||||||
|
[#2497]: https://github.com/actix/actix-web/pull/2497
|
||||||
|
[#2520]: https://github.com/actix/actix-web/pull/2520
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.14 - 2021-11-30
|
## 3.0.0-beta.14 - 2021-11-30
|
||||||
### Changed
|
### Changed
|
||||||
* Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467]
|
- Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467]
|
||||||
* Expose `header::map` module. [#2467]
|
- Expose `header::map` module. [#2467]
|
||||||
* Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470]
|
- Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470]
|
||||||
* Update `actix-tls` to `3.0.0-rc.1`. [#2474]
|
- Update `actix-tls` to `3.0.0-rc.1`. [#2474]
|
||||||
|
|
||||||
[#2467]: https://github.com/actix/actix-web/pull/2467
|
[#2467]: https://github.com/actix/actix-web/pull/2467
|
||||||
[#2470]: https://github.com/actix/actix-web/pull/2470
|
[#2470]: https://github.com/actix/actix-web/pull/2470
|
||||||
|
@ -55,24 +100,24 @@
|
||||||
|
|
||||||
## 3.0.0-beta.13 - 2021-11-22
|
## 3.0.0-beta.13 - 2021-11-22
|
||||||
### Added
|
### Added
|
||||||
* `body::AnyBody::empty` for quickly creating an empty body. [#2446]
|
- `body::AnyBody::empty` for quickly creating an empty body. [#2446]
|
||||||
* `body::AnyBody::none` for quickly creating a "none" body. [#2456]
|
- `body::AnyBody::none` for quickly creating a "none" body. [#2456]
|
||||||
* `impl Clone` for `body::AnyBody<S> where S: Clone`. [#2448]
|
- `impl Clone` for `body::AnyBody<S> where S: Clone`. [#2448]
|
||||||
* `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448]
|
- `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Rename `body::AnyBody::{Message => Body}`. [#2446]
|
- Rename `body::AnyBody::{Message => Body}`. [#2446]
|
||||||
* Rename `body::AnyBody::{from_message => new_boxed}`. [#2448]
|
- Rename `body::AnyBody::{from_message => new_boxed}`. [#2448]
|
||||||
* Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448]
|
- Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448]
|
||||||
* Rename `body::{BoxAnyBody => BoxBody}`. [#2448]
|
- Rename `body::{BoxAnyBody => BoxBody}`. [#2448]
|
||||||
* Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448]
|
- Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448]
|
||||||
* `Encoder::response` now returns `AnyBody<Encoder<B>>`. [#2448]
|
- `Encoder::response` now returns `AnyBody<Encoder<B>>`. [#2448]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446]
|
- `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446]
|
||||||
* `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446]
|
- `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446]
|
||||||
* `EncoderError::Boxed`; it is no longer required. [#2446]
|
- `EncoderError::Boxed`; it is no longer required. [#2446]
|
||||||
* `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446]
|
- `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446]
|
||||||
|
|
||||||
[#2446]: https://github.com/actix/actix-web/pull/2446
|
[#2446]: https://github.com/actix/actix-web/pull/2446
|
||||||
[#2448]: https://github.com/actix/actix-web/pull/2448
|
[#2448]: https://github.com/actix/actix-web/pull/2448
|
||||||
|
@ -81,11 +126,11 @@
|
||||||
|
|
||||||
## 3.0.0-beta.12 - 2021-11-15
|
## 3.0.0-beta.12 - 2021-11-15
|
||||||
### Changed
|
### Changed
|
||||||
* Update `actix-server` to `2.0.0-beta.9`. [#2442]
|
- Update `actix-server` to `2.0.0-beta.9`. [#2442]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* `client` module. [#2425]
|
- `client` module. [#2425]
|
||||||
* `trust-dns` feature. [#2425]
|
- `trust-dns` feature. [#2425]
|
||||||
|
|
||||||
[#2425]: https://github.com/actix/actix-web/pull/2425
|
[#2425]: https://github.com/actix/actix-web/pull/2425
|
||||||
[#2442]: https://github.com/actix/actix-web/pull/2442
|
[#2442]: https://github.com/actix/actix-web/pull/2442
|
||||||
|
@ -93,21 +138,21 @@
|
||||||
|
|
||||||
## 3.0.0-beta.11 - 2021-10-20
|
## 3.0.0-beta.11 - 2021-10-20
|
||||||
### Changed
|
### Changed
|
||||||
* Updated rustls to v0.20. [#2414]
|
- Updated rustls to v0.20. [#2414]
|
||||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||||
|
|
||||||
[#2414]: https://github.com/actix/actix-web/pull/2414
|
[#2414]: https://github.com/actix/actix-web/pull/2414
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.10 - 2021-09-09
|
## 3.0.0-beta.10 - 2021-09-09
|
||||||
### Changed
|
### Changed
|
||||||
* `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377]
|
- `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377]
|
||||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364]
|
- Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364]
|
||||||
* Remove `Into<Error>` bound on `Encoder` body types. [#2375]
|
- Remove `Into<Error>` bound on `Encoder` body types. [#2375]
|
||||||
* Fix quality parse error in Accept-Encoding header. [#2344]
|
- Fix quality parse error in Accept-Encoding header. [#2344]
|
||||||
|
|
||||||
[#2364]: https://github.com/actix/actix-web/pull/2364
|
[#2364]: https://github.com/actix/actix-web/pull/2364
|
||||||
[#2375]: https://github.com/actix/actix-web/pull/2375
|
[#2375]: https://github.com/actix/actix-web/pull/2375
|
||||||
|
@ -117,15 +162,15 @@
|
||||||
|
|
||||||
## 3.0.0-beta.9 - 2021-08-09
|
## 3.0.0-beta.9 - 2021-08-09
|
||||||
### Fixed
|
### Fixed
|
||||||
* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
|
- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.8 - 2021-06-26
|
## 3.0.0-beta.8 - 2021-06-26
|
||||||
### Changed
|
### Changed
|
||||||
* Change compression algorithm features flags. [#2250]
|
- Change compression algorithm features flags. [#2250]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* `downcast` and `downcast_get_type_id` macros. [#2291]
|
- `downcast` and `downcast_get_type_id` macros. [#2291]
|
||||||
|
|
||||||
[#2291]: https://github.com/actix/actix-web/pull/2291
|
[#2291]: https://github.com/actix/actix-web/pull/2291
|
||||||
[#2250]: https://github.com/actix/actix-web/pull/2250
|
[#2250]: https://github.com/actix/actix-web/pull/2250
|
||||||
|
@ -133,37 +178,37 @@
|
||||||
|
|
||||||
## 3.0.0-beta.7 - 2021-06-17
|
## 3.0.0-beta.7 - 2021-06-17
|
||||||
### Added
|
### Added
|
||||||
* Alias `body::Body` as `body::AnyBody`. [#2215]
|
- Alias `body::Body` as `body::AnyBody`. [#2215]
|
||||||
* `BoxAnyBody`: a boxed message body with boxed errors. [#2183]
|
- `BoxAnyBody`: a boxed message body with boxed errors. [#2183]
|
||||||
* Re-export `http` crate's `Error` type as `error::HttpError`. [#2171]
|
- Re-export `http` crate's `Error` type as `error::HttpError`. [#2171]
|
||||||
* Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171]
|
- Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171]
|
||||||
* Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171]
|
- Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171]
|
||||||
* `Response::into_body` that consumes response and returns body type. [#2201]
|
- `Response::into_body` that consumes response and returns body type. [#2201]
|
||||||
* `impl Default` for `Response`. [#2201]
|
- `impl Default` for `Response`. [#2201]
|
||||||
* Add zstd support for `ContentEncoding`. [#2244]
|
- Add zstd support for `ContentEncoding`. [#2244]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* The `MessageBody` trait now has an associated `Error` type. [#2183]
|
- The `MessageBody` trait now has an associated `Error` type. [#2183]
|
||||||
* All error trait bounds in server service builders have changed from `Into<Error>` to `Into<Response<AnyBody>>`. [#2253]
|
- All error trait bounds in server service builders have changed from `Into<Error>` to `Into<Response<AnyBody>>`. [#2253]
|
||||||
* All error trait bounds in message body and stream impls changed from `Into<Error>` to `Into<Box<dyn std::error::Error>>`. [#2253]
|
- All error trait bounds in message body and stream impls changed from `Into<Error>` to `Into<Box<dyn std::error::Error>>`. [#2253]
|
||||||
* Places in `Response` where `ResponseBody<B>` was received or returned now simply use `B`. [#2201]
|
- Places in `Response` where `ResponseBody<B>` was received or returned now simply use `B`. [#2201]
|
||||||
* `header` mod is now public. [#2171]
|
- `header` mod is now public. [#2171]
|
||||||
* `uri` mod is now public. [#2171]
|
- `uri` mod is now public. [#2171]
|
||||||
* Update `language-tags` to `0.3`.
|
- Update `language-tags` to `0.3`.
|
||||||
* Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201]
|
- Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201]
|
||||||
* `ResponseBuilder::message_body` now returns a `Result`. [#2201]
|
- `ResponseBuilder::message_body` now returns a `Result`. [#2201]
|
||||||
* Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253]
|
- Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253]
|
||||||
* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226]
|
- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171]
|
- Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171]
|
||||||
* Down-casting for `MessageBody` types. [#2183]
|
- Down-casting for `MessageBody` types. [#2183]
|
||||||
* `error::Result` alias. [#2201]
|
- `error::Result` alias. [#2201]
|
||||||
* Error field from `Response` and `Response::error`. [#2205]
|
- Error field from `Response` and `Response::error`. [#2205]
|
||||||
* `impl Future` for `Response`. [#2201]
|
- `impl Future` for `Response`. [#2201]
|
||||||
* `Response::take_body` and old `Response::into_body` method that casted body type. [#2201]
|
- `Response::take_body` and old `Response::into_body` method that casted body type. [#2201]
|
||||||
* `InternalError` and all the error types it constructed. [#2215]
|
- `InternalError` and all the error types it constructed. [#2215]
|
||||||
* Conversion (`impl Into`) of `Response<Body>` and `ResponseBuilder` to `Error`. [#2215]
|
- Conversion (`impl Into`) of `Response<Body>` and `ResponseBuilder` to `Error`. [#2215]
|
||||||
|
|
||||||
[#2171]: https://github.com/actix/actix-web/pull/2171
|
[#2171]: https://github.com/actix/actix-web/pull/2171
|
||||||
[#2183]: https://github.com/actix/actix-web/pull/2183
|
[#2183]: https://github.com/actix/actix-web/pull/2183
|
||||||
|
@ -178,27 +223,27 @@
|
||||||
|
|
||||||
## 3.0.0-beta.6 - 2021-04-17
|
## 3.0.0-beta.6 - 2021-04-17
|
||||||
### Added
|
### Added
|
||||||
* `impl<T: MessageBody> MessageBody for Pin<Box<T>>`. [#2152]
|
- `impl<T: MessageBody> MessageBody for Pin<Box<T>>`. [#2152]
|
||||||
* `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159]
|
- `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159]
|
||||||
* Helper `body::to_bytes` for async collecting message body into Bytes. [#2158]
|
- Helper `body::to_bytes` for async collecting message body into Bytes. [#2158]
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
* The type parameter of `Response` no longer has a default. [#2152]
|
- The type parameter of `Response` no longer has a default. [#2152]
|
||||||
* The `Message` variant of `body::Body` is now `Pin<Box<dyn MessageBody>>`. [#2152]
|
- The `Message` variant of `body::Body` is now `Pin<Box<dyn MessageBody>>`. [#2152]
|
||||||
* `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152]
|
- `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152]
|
||||||
* Error enum types are marked `#[non_exhaustive]`. [#2161]
|
- Error enum types are marked `#[non_exhaustive]`. [#2161]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* `cookies` feature flag. [#2065]
|
- `cookies` feature flag. [#2065]
|
||||||
* Top-level `cookies` mod (re-export). [#2065]
|
- Top-level `cookies` mod (re-export). [#2065]
|
||||||
* `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065]
|
- `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065]
|
||||||
* `impl ResponseError for CookieParseError`. [#2065]
|
- `impl ResponseError for CookieParseError`. [#2065]
|
||||||
* Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148]
|
- Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148]
|
||||||
* `ResponseBuilder::json`. [#2148]
|
- `ResponseBuilder::json`. [#2148]
|
||||||
* `ResponseBuilder::{set_header, header}`. [#2148]
|
- `ResponseBuilder::{set_header, header}`. [#2148]
|
||||||
* `impl From<serde_json::Value> for Body`. [#2148]
|
- `impl From<serde_json::Value> for Body`. [#2148]
|
||||||
* `Response::build_from`. [#2159]
|
- `Response::build_from`. [#2159]
|
||||||
* Most of the status code builders on `Response`. [#2159]
|
- Most of the status code builders on `Response`. [#2159]
|
||||||
|
|
||||||
[#2065]: https://github.com/actix/actix-web/pull/2065
|
[#2065]: https://github.com/actix/actix-web/pull/2065
|
||||||
[#2148]: https://github.com/actix/actix-web/pull/2148
|
[#2148]: https://github.com/actix/actix-web/pull/2148
|
||||||
|
@ -210,16 +255,16 @@
|
||||||
|
|
||||||
## 3.0.0-beta.5 - 2021-04-02
|
## 3.0.0-beta.5 - 2021-04-02
|
||||||
### Added
|
### Added
|
||||||
* `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081]
|
- `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081]
|
||||||
* `client::ConnectorService` as `client::Connector::finish` method's return type [#2081]
|
- `client::ConnectorService` as `client::Connector::finish` method's return type [#2081]
|
||||||
* `client::ConnectionIo` trait alias [#2081]
|
- `client::ConnectionIo` trait alias [#2081]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063]
|
- `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* Common typed HTTP headers were moved to actix-web. [2094]
|
- Common typed HTTP headers were moved to actix-web. [2094]
|
||||||
* `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127]
|
- `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127]
|
||||||
|
|
||||||
[#2063]: https://github.com/actix/actix-web/pull/2063
|
[#2063]: https://github.com/actix/actix-web/pull/2063
|
||||||
[#2081]: https://github.com/actix/actix-web/pull/2081
|
[#2081]: https://github.com/actix/actix-web/pull/2081
|
||||||
|
@ -229,13 +274,13 @@
|
||||||
|
|
||||||
## 3.0.0-beta.4 - 2021-03-08
|
## 3.0.0-beta.4 - 2021-03-08
|
||||||
### Changed
|
### Changed
|
||||||
* Feature `cookies` is now optional and disabled by default. [#1981]
|
- Feature `cookies` is now optional and disabled by default. [#1981]
|
||||||
* `ws::hash_key` now returns array. [#2035]
|
- `ws::hash_key` now returns array. [#2035]
|
||||||
* `ResponseBuilder::json` now takes `impl Serialize`. [#2052]
|
- `ResponseBuilder::json` now takes `impl Serialize`. [#2052]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994]
|
- Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994]
|
||||||
* `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994]
|
- `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994]
|
||||||
|
|
||||||
[#1981]: https://github.com/actix/actix-web/pull/1981
|
[#1981]: https://github.com/actix/actix-web/pull/1981
|
||||||
[#1994]: https://github.com/actix/actix-web/pull/1994
|
[#1994]: https://github.com/actix/actix-web/pull/1994
|
||||||
|
@ -244,48 +289,48 @@
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.3 - 2021-02-10
|
## 3.0.0-beta.3 - 2021-02-10
|
||||||
* No notable changes.
|
- No notable changes.
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.2 - 2021-02-10
|
## 3.0.0-beta.2 - 2021-02-10
|
||||||
### Added
|
### Added
|
||||||
* `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
|
- `TryIntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
|
||||||
* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869]
|
- `ResponseBuilder::insert_header` method which allows using typed headers. [#1869]
|
||||||
* `ResponseBuilder::append_header` method which allows using typed headers. [#1869]
|
- `ResponseBuilder::append_header` method which allows using typed headers. [#1869]
|
||||||
* `TestRequest::insert_header` method which allows using typed headers. [#1869]
|
- `TestRequest::insert_header` method which allows using typed headers. [#1869]
|
||||||
* `ContentEncoding` implements all necessary header traits. [#1912]
|
- `ContentEncoding` implements all necessary header traits. [#1912]
|
||||||
* `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964]
|
- `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964]
|
||||||
* `HeaderMap::drain` as an efficient draining iterator. [#1964]
|
- `HeaderMap::drain` as an efficient draining iterator. [#1964]
|
||||||
* Implement `IntoIterator` for owned `HeaderMap`. [#1964]
|
- Implement `IntoIterator` for owned `HeaderMap`. [#1964]
|
||||||
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
|
- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed
|
- `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed
|
||||||
`mime` types. [#1894]
|
`mime` types. [#1894]
|
||||||
* Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
|
- Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
|
||||||
`TryInto` trait. [#1894]
|
`TryInto` trait. [#1894]
|
||||||
* `Extensions::insert` returns Option of replaced item. [#1904]
|
- `Extensions::insert` returns Option of replaced item. [#1904]
|
||||||
* Remove `HttpResponseBuilder::json2()`. [#1903]
|
- Remove `HttpResponseBuilder::json2()`. [#1903]
|
||||||
* Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903]
|
- Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903]
|
||||||
* `client::error::ConnectError` Resolver variant contains `Box<dyn std::error::Error>` type. [#1905]
|
- `client::error::ConnectError` Resolver variant contains `Box<dyn std::error::Error>` type. [#1905]
|
||||||
* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905]
|
- `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905]
|
||||||
* Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool
|
- Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool
|
||||||
is dead. [#1957]
|
is dead. [#1957]
|
||||||
* `HeaderMap::len` now returns number of values instead of number of keys. [#1964]
|
- `HeaderMap::len` now returns number of values instead of number of keys. [#1964]
|
||||||
* `HeaderMap::insert` now returns iterator of removed values. [#1964]
|
- `HeaderMap::insert` now returns iterator of removed values. [#1964]
|
||||||
* `HeaderMap::remove` now returns iterator of removed values. [#1964]
|
- `HeaderMap::remove` now returns iterator of removed values. [#1964]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869]
|
- `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869]
|
||||||
* `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869]
|
- `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869]
|
||||||
* `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869]
|
- `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869]
|
||||||
* `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869]
|
- `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869]
|
- `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
* `actors` optional feature. [#1969]
|
- `actors` optional feature. [#1969]
|
||||||
* `ResponseError` impl for `actix::MailboxError`. [#1969]
|
- `ResponseError` impl for `actix::MailboxError`. [#1969]
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
* Vastly improve docs and add examples for `HeaderMap`. [#1964]
|
- Vastly improve docs and add examples for `HeaderMap`. [#1964]
|
||||||
|
|
||||||
[#1869]: https://github.com/actix/actix-web/pull/1869
|
[#1869]: https://github.com/actix/actix-web/pull/1869
|
||||||
[#1894]: https://github.com/actix/actix-web/pull/1894
|
[#1894]: https://github.com/actix/actix-web/pull/1894
|
||||||
|
@ -300,24 +345,24 @@
|
||||||
|
|
||||||
## 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]`.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
|
- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
|
||||||
* Bumped `rand` to `0.8`.
|
- Bumped `rand` to `0.8`.
|
||||||
* Update `bytes` to `1.0`. [#1813]
|
- Update `bytes` to `1.0`. [#1813]
|
||||||
* Update `h2` to `0.3`. [#1813]
|
- Update `h2` to `0.3`. [#1813]
|
||||||
* The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864]
|
- The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* Deprecated `on_connect` methods have been removed. Prefer the new
|
- Deprecated `on_connect` methods have been removed. Prefer the new
|
||||||
`on_connect_ext` technique. [#1857]
|
`on_connect_ext` technique. [#1857]
|
||||||
* Remove `ResponseError` impl for `actix::actors::resolver::ResolverError`
|
- Remove `ResponseError` impl for `actix::actors::resolver::ResolverError`
|
||||||
due to deprecate of resolver actor. [#1813]
|
due to deprecate of resolver actor. [#1813]
|
||||||
* 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`.
|
- Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`.
|
||||||
Due to this change `actix_threadpool::BlockingError` type is moved into
|
Due to this change `actix_threadpool::BlockingError` type is moved into
|
||||||
`actix_http::error` module. [#1878]
|
`actix_http::error` module. [#1878]
|
||||||
|
|
||||||
|
@ -329,20 +374,20 @@
|
||||||
|
|
||||||
## 2.2.1 - 2021-08-09
|
## 2.2.1 - 2021-08-09
|
||||||
### Fixed
|
### Fixed
|
||||||
* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
|
- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
|
||||||
|
|
||||||
|
|
||||||
## 2.2.0 - 2020-11-25
|
## 2.2.0 - 2020-11-25
|
||||||
### Added
|
### Added
|
||||||
* HttpResponse builders for 1xx status codes. [#1768]
|
- HttpResponse builders for 1xx status codes. [#1768]
|
||||||
* `Accept::mime_precedence` and `Accept::mime_preference`. [#1793]
|
- `Accept::mime_precedence` and `Accept::mime_preference`. [#1793]
|
||||||
* `TryFrom<u16>` and `TryFrom<f32>` for `http::header::Quality`. [#1797]
|
- `TryFrom<u16>` and `TryFrom<f32>` for `http::header::Quality`. [#1797]
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767]
|
- Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
|
- Upgrade `serde_urlencoded` to `0.7`. [#1773]
|
||||||
|
|
||||||
[#1773]: https://github.com/actix/actix-web/pull/1773
|
[#1773]: https://github.com/actix/actix-web/pull/1773
|
||||||
[#1767]: https://github.com/actix/actix-web/pull/1767
|
[#1767]: https://github.com/actix/actix-web/pull/1767
|
||||||
|
@ -353,12 +398,12 @@
|
||||||
|
|
||||||
## 2.1.0 - 2020-10-30
|
## 2.1.0 - 2020-10-30
|
||||||
### Added
|
### Added
|
||||||
* Added more flexible `on_connect_ext` methods for on-connect handling. [#1754]
|
- Added more flexible `on_connect_ext` methods for on-connect handling. [#1754]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Upgrade `base64` to `0.13`. [#1744]
|
- Upgrade `base64` to `0.13`. [#1744]
|
||||||
* Upgrade `pin-project` to `1.0`. [#1733]
|
- Upgrade `pin-project` to `1.0`. [#1733]
|
||||||
* Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760]
|
- Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760]
|
||||||
|
|
||||||
[#1760]: https://github.com/actix/actix-web/pull/1760
|
[#1760]: https://github.com/actix/actix-web/pull/1760
|
||||||
[#1754]: https://github.com/actix/actix-web/pull/1754
|
[#1754]: https://github.com/actix/actix-web/pull/1754
|
||||||
|
@ -367,28 +412,28 @@
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0 - 2020-09-11
|
## 2.0.0 - 2020-09-11
|
||||||
* No significant changes from `2.0.0-beta.4`.
|
- No significant changes from `2.0.0-beta.4`.
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0-beta.4 - 2020-09-09
|
## 2.0.0-beta.4 - 2020-09-09
|
||||||
### Changed
|
### Changed
|
||||||
* Update actix-codec and actix-utils dependencies.
|
- Update actix-codec and actix-utils dependencies.
|
||||||
* 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]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Fix illegal chunked encoding. [#1615]
|
- Fix illegal chunked encoding. [#1615]
|
||||||
|
|
||||||
[#1614]: https://github.com/actix/actix-web/pull/1614
|
[#1614]: https://github.com/actix/actix-web/pull/1614
|
||||||
[#1615]: https://github.com/actix/actix-web/pull/1615
|
[#1615]: https://github.com/actix/actix-web/pull/1615
|
||||||
|
@ -396,10 +441,10 @@
|
||||||
|
|
||||||
## 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]
|
||||||
* MSRV is now 1.41.1.
|
- MSRV is now 1.41.1.
|
||||||
|
|
||||||
[#1558]: https://github.com/actix/actix-web/pull/1558
|
[#1558]: https://github.com/actix/actix-web/pull/1558
|
||||||
[#1586]: https://github.com/actix/actix-web/pull/1586
|
[#1586]: https://github.com/actix/actix-web/pull/1586
|
||||||
|
@ -408,15 +453,15 @@
|
||||||
|
|
||||||
## 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
|
- content_length function is removed, and you can set Content-Length by calling
|
||||||
no_chunking function [#1439]
|
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
|
||||||
|
@ -424,13 +469,13 @@
|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||
[#1422]: https://github.com/actix/actix-web/pull/1422
|
[#1422]: https://github.com/actix/actix-web/pull/1422
|
||||||
|
@ -439,12 +484,12 @@
|
||||||
|
|
||||||
## 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 to improve download speed for awc when downloading large objects. [#1394]
|
respectively to improve download speed for awc when downloading large objects. [#1394]
|
||||||
* client::Connector accepts initial_window_size and initial_connection_window_size
|
- client::Connector accepts initial_window_size and initial_connection_window_size
|
||||||
HTTP2 configuration. [#1394]
|
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
|
||||||
|
@ -452,61 +497,61 @@
|
||||||
|
|
||||||
## 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
|
- Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of
|
||||||
`&mut self` in the poll_next().
|
`&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 calculation
|
||||||
|
|
||||||
### 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;
|
- To be compatible with non-English error responses, `ResponseError` rendered with `text/plain;
|
||||||
charset=utf-8` header [#1118]
|
charset=utf-8` header [#1118]
|
||||||
|
|
||||||
[#1878]: https://github.com/actix/actix-web/pull/1878
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
@ -514,169 +559,169 @@
|
||||||
|
|
||||||
## 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 compression
|
||||||
|
|
||||||
### 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 decompression 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.14"
|
version = "3.0.0-beta.17"
|
||||||
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"
|
||||||
keywords = ["actix", "http", "framework", "async", "futures"]
|
keywords = ["actix", "http", "framework", "async", "futures"]
|
||||||
|
@ -45,7 +45,7 @@ __compress = []
|
||||||
actix-service = "2.0.0"
|
actix-service = "2.0.0"
|
||||||
actix-codec = "0.4.1"
|
actix-codec = "0.4.1"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-rt = "2.2"
|
actix-rt = { version = "2.2", default-features = false }
|
||||||
|
|
||||||
ahash = "0.7"
|
ahash = "0.7"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
|
@ -55,25 +55,23 @@ bytestring = "1"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
|
h2 = "0.3.9"
|
||||||
h2 = "0.3.1"
|
|
||||||
http = "0.2.5"
|
http = "0.2.5"
|
||||||
httparse = "1.5.1"
|
httparse = "1.5.1"
|
||||||
httpdate = "1.0.1"
|
httpdate = "1.0.1"
|
||||||
itoa = "0.4"
|
itoa = "1"
|
||||||
language-tags = "0.3"
|
language-tags = "0.3"
|
||||||
local-channel = "0.1"
|
local-channel = "0.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
pin-project = "1.0.0"
|
|
||||||
pin-project-lite = "0.2"
|
pin-project-lite = "0.2"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
sha-1 = "0.9"
|
sha-1 = "0.10"
|
||||||
smallvec = "1.6.1"
|
smallvec = "1.6.1"
|
||||||
|
|
||||||
# tls
|
# tls
|
||||||
actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true }
|
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
||||||
|
|
||||||
# compression
|
# compression
|
||||||
brotli2 = { version="0.3.2", optional = true }
|
brotli2 = { version="0.3.2", optional = true }
|
||||||
|
@ -81,12 +79,15 @@ flate2 = { version = "1.0.13", optional = true }
|
||||||
zstd = { version = "0.9", optional = true }
|
zstd = { version = "0.9", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-server = "2.0.0-rc.1"
|
actix-http-test = { version = "3.0.0-beta.10", features = ["openssl"] }
|
||||||
actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] }
|
actix-server = "2.0.0-rc.2"
|
||||||
actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] }
|
actix-tls = { version = "3.0.0", features = ["openssl"] }
|
||||||
|
actix-web = "4.0.0-beta.17"
|
||||||
|
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
|
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
rcgen = "0.8"
|
rcgen = "0.8"
|
||||||
regex = "1.3"
|
regex = "1.3"
|
||||||
rustls-pemfile = "0.2"
|
rustls-pemfile = "0.2"
|
||||||
|
@ -95,7 +96,7 @@ serde_json = "1.0"
|
||||||
static_assertions = "1"
|
static_assertions = "1"
|
||||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
tls-openssl = { package = "openssl", version = "0.10.9" }
|
||||||
tls-rustls = { package = "rustls", version = "0.20.0" }
|
tls-rustls = { package = "rustls", version = "0.20.0" }
|
||||||
tokio = { version = "1.2", features = ["net", "rt"] }
|
tokio = { version = "1.8.4", features = ["net", "rt", "macros"] }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "ws"
|
name = "ws"
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
> HTTP primitives for the Actix ecosystem.
|
> HTTP primitives for the Actix ecosystem.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http)
|
[](https://crates.io/crates/actix-http)
|
||||||
[](https://docs.rs/actix-http/3.0.0-beta.14)
|
[](https://docs.rs/actix-http/3.0.0-beta.17)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-http/3.0.0-beta.14)
|
[](https://deps.rs/crate/actix-http/3.0.0-beta.17)
|
||||||
[](https://crates.io/crates/actix-http)
|
[](https://crates.io/crates/actix-http)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -54,8 +54,8 @@ async fn main() -> io::Result<()> {
|
||||||
|
|
||||||
This project is licensed under either of
|
This project is licensed under either of
|
||||||
|
|
||||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
|
- 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))
|
||||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
|
- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
|
||||||
|
|
||||||
at your option.
|
at your option.
|
||||||
|
|
||||||
|
|
|
@ -189,11 +189,7 @@ mod _original {
|
||||||
n /= 100;
|
n /= 100;
|
||||||
curr -= 2;
|
curr -= 2;
|
||||||
unsafe {
|
unsafe {
|
||||||
ptr::copy_nonoverlapping(
|
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2);
|
||||||
lut_ptr.offset(d1 as isize),
|
|
||||||
buf_ptr.offset(curr),
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode last 1 or 2 chars
|
// decode last 1 or 2 chars
|
||||||
|
@ -206,11 +202,7 @@ mod _original {
|
||||||
let d1 = n << 1;
|
let d1 = n << 1;
|
||||||
curr -= 2;
|
curr -= 2;
|
||||||
unsafe {
|
unsafe {
|
||||||
ptr::copy_nonoverlapping(
|
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2);
|
||||||
lut_ptr.offset(d1 as isize),
|
|
||||||
buf_ptr.offset(curr),
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,15 +54,10 @@ const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex {
|
||||||
value: (0, 0),
|
value: (0, 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] =
|
const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] = [EMPTY_HEADER_INDEX; MAX_HEADERS];
|
||||||
[EMPTY_HEADER_INDEX; MAX_HEADERS];
|
|
||||||
|
|
||||||
impl HeaderIndex {
|
impl HeaderIndex {
|
||||||
fn record(
|
fn record(bytes: &[u8], headers: &[httparse::Header<'_>], indices: &mut [HeaderIndex]) {
|
||||||
bytes: &[u8],
|
|
||||||
headers: &[httparse::Header<'_>],
|
|
||||||
indices: &mut [HeaderIndex],
|
|
||||||
) {
|
|
||||||
let bytes_ptr = bytes.as_ptr() as usize;
|
let bytes_ptr = bytes.as_ptr() as usize;
|
||||||
for (header, indices) in headers.iter().zip(indices.iter_mut()) {
|
for (header, indices) in headers.iter().zip(indices.iter_mut()) {
|
||||||
let name_start = header.name.as_ptr() as usize - bytes_ptr;
|
let name_start = header.name.as_ptr() as usize - bytes_ptr;
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
use actix_http::HttpService;
|
||||||
|
use actix_server::Server;
|
||||||
|
use actix_service::map_config;
|
||||||
|
use actix_web::{dev::AppConfig, get, App};
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
async fn index() -> &'static str {
|
||||||
|
"Hello, world. From Actix Web!"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
Server::build()
|
||||||
|
.bind("hello-world", "127.0.0.1:8080", || {
|
||||||
|
// construct actix-web app
|
||||||
|
let app = App::new().service(index);
|
||||||
|
|
||||||
|
HttpService::build()
|
||||||
|
// pass the app to service builder
|
||||||
|
// map_config is used to map App's configuration to ServiceBuilder
|
||||||
|
.finish(map_config(app, |_| AppConfig::default()))
|
||||||
|
.tcp()
|
||||||
|
})?
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
|
@ -25,10 +25,7 @@ async fn main() -> io::Result<()> {
|
||||||
|
|
||||||
Ok::<_, Error>(
|
Ok::<_, Error>(
|
||||||
Response::build(StatusCode::OK)
|
Response::build(StatusCode::OK)
|
||||||
.insert_header((
|
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
|
||||||
"x-head",
|
|
||||||
HeaderValue::from_static("dummy value!"),
|
|
||||||
))
|
|
||||||
.body(body),
|
.body(body),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response,
|
body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response, StatusCode,
|
||||||
StatusCode,
|
|
||||||
};
|
};
|
||||||
use actix_server::Server;
|
use actix_server::Server;
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use std::{convert::Infallible, io};
|
use std::{convert::Infallible, io};
|
||||||
|
|
||||||
use actix_http::{HttpService, Response, StatusCode};
|
use actix_http::{
|
||||||
|
header::HeaderValue, HttpMessage, HttpService, Request, Response, StatusCode,
|
||||||
|
};
|
||||||
use actix_server::Server;
|
use actix_server::Server;
|
||||||
use http::header::HeaderValue;
|
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
|
@ -13,13 +14,19 @@ async fn main() -> io::Result<()> {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(1000)
|
.client_timeout(1000)
|
||||||
.client_disconnect(1000)
|
.client_disconnect(1000)
|
||||||
.finish(|req| async move {
|
.on_connect_ext(|_, ext| {
|
||||||
|
ext.insert(42u32);
|
||||||
|
})
|
||||||
|
.finish(|req: Request| async move {
|
||||||
log::info!("{:?}", req);
|
log::info!("{:?}", req);
|
||||||
|
|
||||||
let mut res = Response::build(StatusCode::OK);
|
let mut res = Response::build(StatusCode::OK);
|
||||||
|
res.insert_header(("x-head", HeaderValue::from_static("dummy value!")));
|
||||||
|
|
||||||
|
let forty_two = req.extensions().get::<u32>().unwrap().to_string();
|
||||||
res.insert_header((
|
res.insert_header((
|
||||||
"x-head",
|
"x-forty-two",
|
||||||
HeaderValue::from_static("dummy value!"),
|
HeaderValue::from_str(&forty_two).unwrap(),
|
||||||
));
|
));
|
||||||
|
|
||||||
Ok::<_, Infallible>(res.body("Hello world!"))
|
Ok::<_, Infallible>(res.body("Hello world!"))
|
||||||
|
|
|
@ -60,10 +60,7 @@ impl Heartbeat {
|
||||||
impl Stream for Heartbeat {
|
impl Stream for Heartbeat {
|
||||||
type Item = Result<Bytes, Error>;
|
type Item = Result<Bytes, Error>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Self::Item>> {
|
|
||||||
log::trace!("poll");
|
log::trace!("poll");
|
||||||
|
|
||||||
ready!(self.as_mut().interval.poll_tick(cx));
|
ready!(self.as_mut().interval.poll_tick(cx));
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
max_width = 89
|
|
||||||
reorder_imports = true
|
|
||||||
#wrap_comments = true
|
|
||||||
#fn_args_density = "Compressed"
|
|
||||||
#use_small_heuristics = false
|
|
|
@ -27,6 +27,7 @@ where
|
||||||
S: Stream<Item = Result<Bytes, E>>,
|
S: Stream<Item = Result<Bytes, E>>,
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
E: Into<Box<dyn StdError>> + 'static,
|
||||||
{
|
{
|
||||||
|
#[inline]
|
||||||
pub fn new(stream: S) -> Self {
|
pub fn new(stream: S) -> Self {
|
||||||
BodyStream { stream }
|
BodyStream { stream }
|
||||||
}
|
}
|
||||||
|
@ -39,6 +40,7 @@ where
|
||||||
{
|
{
|
||||||
type Error = E;
|
type Error = E;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
BodySize::Stream
|
BodySize::Stream
|
||||||
}
|
}
|
||||||
|
@ -165,8 +167,7 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn stream_delayed_error() {
|
async fn stream_delayed_error() {
|
||||||
let body =
|
let body = BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)]));
|
||||||
BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)]));
|
|
||||||
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
|
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
|
||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
|
|
|
@ -8,50 +8,97 @@ use std::{
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
||||||
use super::{BodySize, MessageBody, MessageBodyMapErr};
|
use super::{BodySize, MessageBody, MessageBodyMapErr};
|
||||||
use crate::Error;
|
use crate::body;
|
||||||
|
|
||||||
/// A boxed message body with boxed errors.
|
/// A boxed message body with boxed errors.
|
||||||
pub struct BoxBody(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>);
|
#[derive(Debug)]
|
||||||
|
pub struct BoxBody(BoxBodyInner);
|
||||||
|
|
||||||
|
enum BoxBodyInner {
|
||||||
|
None(body::None),
|
||||||
|
Bytes(Bytes),
|
||||||
|
Stream(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for BoxBodyInner {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::None(arg0) => f.debug_tuple("None").field(arg0).finish(),
|
||||||
|
Self::Bytes(arg0) => f.debug_tuple("Bytes").field(arg0).finish(),
|
||||||
|
Self::Stream(_) => f.debug_tuple("Stream").field(&"dyn MessageBody").finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BoxBody {
|
impl BoxBody {
|
||||||
/// Boxes a `MessageBody` and any errors it generates.
|
/// Same as `MessageBody::boxed`.
|
||||||
|
///
|
||||||
|
/// If the body type to wrap is unknown or generic it is better to use [`MessageBody::boxed`] to
|
||||||
|
/// avoid double boxing.
|
||||||
|
#[inline]
|
||||||
pub fn new<B>(body: B) -> Self
|
pub fn new<B>(body: B) -> Self
|
||||||
where
|
where
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
let body = MessageBodyMapErr::new(body, Into::into);
|
match body.size() {
|
||||||
Self(Box::pin(body))
|
BodySize::None => Self(BoxBodyInner::None(body::None)),
|
||||||
|
_ => match body.try_into_bytes() {
|
||||||
|
Ok(bytes) => Self(BoxBodyInner::Bytes(bytes)),
|
||||||
|
Err(body) => {
|
||||||
|
let body = MessageBodyMapErr::new(body, Into::into);
|
||||||
|
Self(BoxBodyInner::Stream(Box::pin(body)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable pinned reference to the inner message body type.
|
/// Returns a mutable pinned reference to the inner message body type.
|
||||||
pub fn as_pin_mut(
|
#[inline]
|
||||||
&mut self,
|
pub fn as_pin_mut(&mut self) -> Pin<&mut Self> {
|
||||||
) -> Pin<&mut (dyn MessageBody<Error = Box<dyn StdError>>)> {
|
Pin::new(self)
|
||||||
self.0.as_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for BoxBody {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_str("BoxBody(dyn MessageBody)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageBody for BoxBody {
|
impl MessageBody for BoxBody {
|
||||||
type Error = Error;
|
type Error = Box<dyn StdError>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
self.0.size()
|
match &self.0 {
|
||||||
|
BoxBodyInner::None(none) => none.size(),
|
||||||
|
BoxBodyInner::Bytes(bytes) => bytes.size(),
|
||||||
|
BoxBodyInner::Stream(stream) => stream.size(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
mut self: Pin<&mut Self>,
|
mut self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
self.0
|
match &mut self.0 {
|
||||||
.as_mut()
|
BoxBodyInner::None(body) => {
|
||||||
.poll_next(cx)
|
Pin::new(body).poll_next(cx).map_err(|err| match err {})
|
||||||
.map_err(|err| Error::new_body().with_cause(err))
|
}
|
||||||
|
BoxBodyInner::Bytes(body) => {
|
||||||
|
Pin::new(body).poll_next(cx).map_err(|err| match err {})
|
||||||
|
}
|
||||||
|
BoxBodyInner::Stream(body) => Pin::new(body).poll_next(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||||
|
match self.0 {
|
||||||
|
BoxBodyInner::None(body) => Ok(body.try_into_bytes().unwrap()),
|
||||||
|
BoxBodyInner::Bytes(body) => Ok(body.try_into_bytes().unwrap()),
|
||||||
|
_ => Err(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn boxed(self) -> BoxBody {
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ pin_project! {
|
||||||
|
|
||||||
impl<L> EitherBody<L, BoxBody> {
|
impl<L> EitherBody<L, BoxBody> {
|
||||||
/// Creates new `EitherBody` using left variant and boxed right variant.
|
/// Creates new `EitherBody` using left variant and boxed right variant.
|
||||||
|
#[inline]
|
||||||
pub fn new(body: L) -> Self {
|
pub fn new(body: L) -> Self {
|
||||||
Self::Left { body }
|
Self::Left { body }
|
||||||
}
|
}
|
||||||
|
@ -30,11 +31,13 @@ impl<L> EitherBody<L, BoxBody> {
|
||||||
|
|
||||||
impl<L, R> EitherBody<L, R> {
|
impl<L, R> EitherBody<L, R> {
|
||||||
/// Creates new `EitherBody` using left variant.
|
/// Creates new `EitherBody` using left variant.
|
||||||
|
#[inline]
|
||||||
pub fn left(body: L) -> Self {
|
pub fn left(body: L) -> Self {
|
||||||
Self::Left { body }
|
Self::Left { body }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates new `EitherBody` using right variant.
|
/// Creates new `EitherBody` using right variant.
|
||||||
|
#[inline]
|
||||||
pub fn right(body: R) -> Self {
|
pub fn right(body: R) -> Self {
|
||||||
Self::Right { body }
|
Self::Right { body }
|
||||||
}
|
}
|
||||||
|
@ -47,6 +50,7 @@ where
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
match self {
|
match self {
|
||||||
EitherBody::Left { body } => body.size(),
|
EitherBody::Left { body } => body.size(),
|
||||||
|
@ -54,6 +58,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
|
@ -67,6 +72,26 @@ where
|
||||||
.map_err(|err| Error::new_body().with_cause(err)),
|
.map_err(|err| Error::new_body().with_cause(err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||||
|
match self {
|
||||||
|
EitherBody::Left { body } => body
|
||||||
|
.try_into_bytes()
|
||||||
|
.map_err(|body| EitherBody::Left { body }),
|
||||||
|
EitherBody::Right { body } => body
|
||||||
|
.try_into_bytes()
|
||||||
|
.map_err(|body| EitherBody::Right { body }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn boxed(self) -> BoxBody {
|
||||||
|
match self {
|
||||||
|
EitherBody::Left { body } => body.boxed(),
|
||||||
|
EitherBody::Right { body } => body.boxed(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -12,23 +12,56 @@ use bytes::{Bytes, BytesMut};
|
||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use super::BodySize;
|
use super::{BodySize, BoxBody};
|
||||||
|
|
||||||
/// An interface types that can converted to bytes and used as response bodies.
|
/// An interface types that can converted to bytes and used as response bodies.
|
||||||
// TODO: examples
|
// TODO: examples
|
||||||
pub trait MessageBody {
|
pub trait MessageBody {
|
||||||
// TODO: consider this bound to only fmt::Display since the error type is not really used
|
/// The type of error that will be returned if streaming body fails.
|
||||||
// and there is an impl for Into<Box<StdError>> on String
|
///
|
||||||
|
/// Since it is not appropriate to generate a response mid-stream, it only requires `Error` for
|
||||||
|
/// internal use and logging.
|
||||||
type Error: Into<Box<dyn StdError>>;
|
type Error: Into<Box<dyn StdError>>;
|
||||||
|
|
||||||
/// Body size hint.
|
/// Body size hint.
|
||||||
|
///
|
||||||
|
/// If [`BodySize::None`] is returned, optimizations that skip reading the body are allowed.
|
||||||
fn size(&self) -> BodySize;
|
fn size(&self) -> BodySize;
|
||||||
|
|
||||||
/// Attempt to pull out the next chunk of body bytes.
|
/// Attempt to pull out the next chunk of body bytes.
|
||||||
|
// TODO: expand documentation
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>>;
|
) -> Poll<Option<Result<Bytes, Self::Error>>>;
|
||||||
|
|
||||||
|
/// Try to convert into the complete chunk of body bytes.
|
||||||
|
///
|
||||||
|
/// Implement this method if the entire body can be trivially extracted. This is useful for
|
||||||
|
/// optimizations where `poll_next` calls can be avoided.
|
||||||
|
///
|
||||||
|
/// Body types with [`BodySize::None`] are allowed to return empty `Bytes`. Although, if calling
|
||||||
|
/// this method, it is recommended to check `size` first and return early.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// The default implementation will error and return the original type back to the caller for
|
||||||
|
/// further use.
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Err(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this body into `BoxBody`.
|
||||||
|
#[inline]
|
||||||
|
fn boxed(self) -> BoxBody
|
||||||
|
where
|
||||||
|
Self: Sized + 'static,
|
||||||
|
{
|
||||||
|
BoxBody::new(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod foreign_impls {
|
mod foreign_impls {
|
||||||
|
@ -37,12 +70,10 @@ mod foreign_impls {
|
||||||
impl MessageBody for Infallible {
|
impl MessageBody for Infallible {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
match *self {}
|
match *self {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
_cx: &mut Context<'_>,
|
_cx: &mut Context<'_>,
|
||||||
|
@ -66,11 +97,16 @@ mod foreign_impls {
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||||
|
Ok(Bytes::new())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B> MessageBody for Box<B>
|
impl<B> MessageBody for Box<B>
|
||||||
where
|
where
|
||||||
B: MessageBody + Unpin,
|
B: MessageBody + Unpin + ?Sized,
|
||||||
{
|
{
|
||||||
type Error = B::Error;
|
type Error = B::Error;
|
||||||
|
|
||||||
|
@ -90,7 +126,7 @@ mod foreign_impls {
|
||||||
|
|
||||||
impl<B> MessageBody for Pin<Box<B>>
|
impl<B> MessageBody for Pin<Box<B>>
|
||||||
where
|
where
|
||||||
B: MessageBody,
|
B: MessageBody + ?Sized,
|
||||||
{
|
{
|
||||||
type Error = B::Error;
|
type Error = B::Error;
|
||||||
|
|
||||||
|
@ -101,20 +137,22 @@ mod foreign_impls {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
mut self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
self.as_mut().poll_next(cx)
|
self.get_mut().as_mut().poll_next(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageBody for &'static [u8] {
|
impl MessageBody for &'static [u8] {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
BodySize::Sized(self.len() as u64)
|
BodySize::Sized(self.len() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
_cx: &mut Context<'_>,
|
_cx: &mut Context<'_>,
|
||||||
|
@ -122,20 +160,25 @@ mod foreign_impls {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
} else {
|
} else {
|
||||||
let bytes = mem::take(self.get_mut());
|
Poll::Ready(Some(Ok(Bytes::from_static(mem::take(self.get_mut())))))
|
||||||
let bytes = Bytes::from_static(bytes);
|
|
||||||
Poll::Ready(Some(Ok(bytes)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||||
|
Ok(Bytes::from_static(self))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageBody for Bytes {
|
impl MessageBody for Bytes {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
BodySize::Sized(self.len() as u64)
|
BodySize::Sized(self.len() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
_cx: &mut Context<'_>,
|
_cx: &mut Context<'_>,
|
||||||
|
@ -143,19 +186,25 @@ mod foreign_impls {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
} else {
|
} else {
|
||||||
let bytes = mem::take(self.get_mut());
|
Poll::Ready(Some(Ok(mem::take(self.get_mut()))))
|
||||||
Poll::Ready(Some(Ok(bytes)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageBody for BytesMut {
|
impl MessageBody for BytesMut {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
BodySize::Sized(self.len() as u64)
|
BodySize::Sized(self.len() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
_cx: &mut Context<'_>,
|
_cx: &mut Context<'_>,
|
||||||
|
@ -163,19 +212,25 @@ mod foreign_impls {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
} else {
|
} else {
|
||||||
let bytes = mem::take(self.get_mut()).freeze();
|
Poll::Ready(Some(Ok(mem::take(self.get_mut()).freeze())))
|
||||||
Poll::Ready(Some(Ok(bytes)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||||
|
Ok(self.freeze())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageBody for Vec<u8> {
|
impl MessageBody for Vec<u8> {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
BodySize::Sized(self.len() as u64)
|
BodySize::Sized(self.len() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
_cx: &mut Context<'_>,
|
_cx: &mut Context<'_>,
|
||||||
|
@ -183,19 +238,25 @@ mod foreign_impls {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
} else {
|
} else {
|
||||||
let bytes = mem::take(self.get_mut());
|
Poll::Ready(Some(Ok(mem::take(self.get_mut()).into())))
|
||||||
Poll::Ready(Some(Ok(Bytes::from(bytes))))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||||
|
Ok(Bytes::from(self))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageBody for &'static str {
|
impl MessageBody for &'static str {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
BodySize::Sized(self.len() as u64)
|
BodySize::Sized(self.len() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
_cx: &mut Context<'_>,
|
_cx: &mut Context<'_>,
|
||||||
|
@ -208,15 +269,22 @@ mod foreign_impls {
|
||||||
Poll::Ready(Some(Ok(bytes)))
|
Poll::Ready(Some(Ok(bytes)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||||
|
Ok(Bytes::from_static(self.as_bytes()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageBody for String {
|
impl MessageBody for String {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
BodySize::Sized(self.len() as u64)
|
BodySize::Sized(self.len() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
_cx: &mut Context<'_>,
|
_cx: &mut Context<'_>,
|
||||||
|
@ -228,15 +296,22 @@ mod foreign_impls {
|
||||||
Poll::Ready(Some(Ok(Bytes::from(string))))
|
Poll::Ready(Some(Ok(Bytes::from(string))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||||
|
Ok(Bytes::from(self))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageBody for bytestring::ByteString {
|
impl MessageBody for bytestring::ByteString {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
BodySize::Sized(self.len() as u64)
|
BodySize::Sized(self.len() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
_cx: &mut Context<'_>,
|
_cx: &mut Context<'_>,
|
||||||
|
@ -244,6 +319,11 @@ mod foreign_impls {
|
||||||
let string = mem::take(self.get_mut());
|
let string = mem::take(self.get_mut());
|
||||||
Poll::Ready(Some(Ok(string.into_bytes())))
|
Poll::Ready(Some(Ok(string.into_bytes())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||||
|
Ok(self.into_bytes())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,6 +356,7 @@ where
|
||||||
{
|
{
|
||||||
type Error = E;
|
type Error = E;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
self.body.size()
|
self.body.size()
|
||||||
}
|
}
|
||||||
|
@ -296,6 +377,12 @@ where
|
||||||
None => Poll::Ready(None),
|
None => Poll::Ready(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||||
|
let Self { body, mapper } = self;
|
||||||
|
body.try_into_bytes().map_err(|body| Self { body, mapper })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -305,6 +392,7 @@ mod tests {
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::body::{self, EitherBody};
|
||||||
|
|
||||||
macro_rules! assert_poll_next {
|
macro_rules! assert_poll_next {
|
||||||
($pin:expr, $exp:expr) => {
|
($pin:expr, $exp:expr) => {
|
||||||
|
@ -406,6 +494,47 @@ mod tests {
|
||||||
assert_poll_next!(pl, Bytes::from("test"));
|
assert_poll_next!(pl, Bytes::from("test"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn complete_body_combinators() {
|
||||||
|
let body = Bytes::from_static(b"test");
|
||||||
|
let body = BoxBody::new(body);
|
||||||
|
let body = EitherBody::<_, ()>::left(body);
|
||||||
|
let body = EitherBody::<(), _>::right(body);
|
||||||
|
// Do not support try_into_bytes:
|
||||||
|
// let body = Box::new(body);
|
||||||
|
// let body = Box::pin(body);
|
||||||
|
|
||||||
|
assert_eq!(body.try_into_bytes().unwrap(), Bytes::from("test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn complete_body_combinators_poll() {
|
||||||
|
let body = Bytes::from_static(b"test");
|
||||||
|
let body = BoxBody::new(body);
|
||||||
|
let body = EitherBody::<_, ()>::left(body);
|
||||||
|
let body = EitherBody::<(), _>::right(body);
|
||||||
|
let mut body = body;
|
||||||
|
|
||||||
|
assert_eq!(body.size(), BodySize::Sized(4));
|
||||||
|
assert_poll_next!(Pin::new(&mut body), Bytes::from("test"));
|
||||||
|
assert_poll_next_none!(Pin::new(&mut body));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn none_body_combinators() {
|
||||||
|
fn none_body() -> BoxBody {
|
||||||
|
let body = body::None;
|
||||||
|
let body = BoxBody::new(body);
|
||||||
|
let body = EitherBody::<_, ()>::left(body);
|
||||||
|
let body = EitherBody::<(), _>::right(body);
|
||||||
|
body.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(none_body().size(), BodySize::None);
|
||||||
|
assert_eq!(none_body().try_into_bytes().unwrap(), Bytes::new());
|
||||||
|
assert_poll_next_none!(Pin::new(&mut none_body()));
|
||||||
|
}
|
||||||
|
|
||||||
// down-casting used to be done with a method on MessageBody trait
|
// down-casting used to be done with a method on MessageBody trait
|
||||||
// test is kept to demonstrate equivalence of Any trait
|
// test is kept to demonstrate equivalence of Any trait
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
|
|
@ -40,4 +40,9 @@ impl MessageBody for None {
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
Poll::Ready(Option::None)
|
Poll::Ready(Option::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||||
|
Ok(Bytes::new())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
/// Body size hint.
|
/// Body size hint.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum BodySize {
|
pub enum BodySize {
|
||||||
/// Absence of body can be assumed from method or status code.
|
/// Implicitly empty body.
|
||||||
///
|
///
|
||||||
/// Will skip writing Content-Length header.
|
/// Will omit the Content-Length header. Used for responses to certain methods (e.g., `HEAD`) or
|
||||||
|
/// with particular status codes (e.g., 204 No Content). Consumers that read this as a body size
|
||||||
|
/// hint are allowed to make optimizations that skip reading or writing the payload.
|
||||||
None,
|
None,
|
||||||
|
|
||||||
/// Known size body.
|
/// Known size body.
|
||||||
|
@ -18,6 +20,9 @@ pub enum BodySize {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BodySize {
|
impl BodySize {
|
||||||
|
/// Equivalent to `BodySize::Sized(0)`;
|
||||||
|
pub const ZERO: Self = Self::Sized(0);
|
||||||
|
|
||||||
/// Returns true if size hint indicates omitted or empty body.
|
/// Returns true if size hint indicates omitted or empty body.
|
||||||
///
|
///
|
||||||
/// Streams will return false because it cannot be known without reading the stream.
|
/// Streams will return false because it cannot be known without reading the stream.
|
||||||
|
|
|
@ -27,6 +27,7 @@ where
|
||||||
S: Stream<Item = Result<Bytes, E>>,
|
S: Stream<Item = Result<Bytes, E>>,
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
E: Into<Box<dyn StdError>> + 'static,
|
||||||
{
|
{
|
||||||
|
#[inline]
|
||||||
pub fn new(size: u64, stream: S) -> Self {
|
pub fn new(size: u64, stream: S) -> Self {
|
||||||
SizedStream { size, stream }
|
SizedStream { size, stream }
|
||||||
}
|
}
|
||||||
|
@ -41,6 +42,7 @@ where
|
||||||
{
|
{
|
||||||
type Error = E;
|
type Error = E;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
BodySize::Sized(self.size as u64)
|
BodySize::Sized(self.size as u64)
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,9 +68,8 @@ mod test {
|
||||||
let bytes = to_bytes(body).await.unwrap();
|
let bytes = to_bytes(body).await.unwrap();
|
||||||
assert_eq!(bytes, b"123"[..]);
|
assert_eq!(bytes, b"123"[..]);
|
||||||
|
|
||||||
let stream =
|
let stream = stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")])
|
||||||
stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")])
|
.map(Ok::<_, Error>);
|
||||||
.map(Ok::<_, Error>);
|
|
||||||
let body = BodyStream::new(stream);
|
let body = BodyStream::new(stream);
|
||||||
let bytes = to_bytes(body).await.unwrap();
|
let bytes = to_bytes(body).await.unwrap();
|
||||||
assert_eq!(bytes, b"123abc"[..]);
|
assert_eq!(bytes, b"123abc"[..]);
|
||||||
|
|
|
@ -36,6 +36,7 @@ where
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
{
|
{
|
||||||
/// Create instance of `ServiceConfigBuilder`
|
/// Create instance of `ServiceConfigBuilder`
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
HttpServiceBuilder {
|
HttpServiceBuilder {
|
||||||
keep_alive: KeepAlive::Timeout(5),
|
keep_alive: KeepAlive::Timeout(5),
|
||||||
|
@ -214,8 +215,7 @@ where
|
||||||
self.local_addr,
|
self.local_addr,
|
||||||
);
|
);
|
||||||
|
|
||||||
H2Service::with_config(cfg, service.into_factory())
|
H2Service::with_config(cfg, service.into_factory()).on_connect_ext(self.on_connect_ext)
|
||||||
.on_connect_ext(self.on_connect_ext)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish service configuration and create `HttpService` instance.
|
/// Finish service configuration and create `HttpService` instance.
|
||||||
|
|
|
@ -28,11 +28,14 @@ use crate::{
|
||||||
|
|
||||||
const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049;
|
const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049;
|
||||||
|
|
||||||
pub struct Decoder<S> {
|
pin_project_lite::pin_project! {
|
||||||
decoder: Option<ContentDecoder>,
|
pub struct Decoder<S> {
|
||||||
stream: S,
|
decoder: Option<ContentDecoder>,
|
||||||
eof: bool,
|
#[pin]
|
||||||
fut: Option<JoinHandle<Result<(Option<Bytes>, ContentDecoder), io::Error>>>,
|
stream: S,
|
||||||
|
eof: bool,
|
||||||
|
fut: Option<JoinHandle<Result<(Option<Bytes>, ContentDecoder), io::Error>>>,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Decoder<S>
|
impl<S> Decoder<S>
|
||||||
|
@ -44,17 +47,17 @@ where
|
||||||
pub fn new(stream: S, encoding: ContentEncoding) -> Decoder<S> {
|
pub fn new(stream: S, encoding: ContentEncoding) -> Decoder<S> {
|
||||||
let decoder = match encoding {
|
let decoder = match encoding {
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
ContentEncoding::Br => Some(ContentDecoder::Br(Box::new(
|
ContentEncoding::Br => Some(ContentDecoder::Br(Box::new(BrotliDecoder::new(
|
||||||
BrotliDecoder::new(Writer::new()),
|
Writer::new(),
|
||||||
))),
|
)))),
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
||||||
ZlibDecoder::new(Writer::new()),
|
ZlibDecoder::new(Writer::new()),
|
||||||
))),
|
))),
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(
|
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new(
|
||||||
GzDecoder::new(Writer::new()),
|
Writer::new(),
|
||||||
))),
|
)))),
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new(
|
ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new(
|
||||||
ZstdDecoder::new(Writer::new()).expect(
|
ZstdDecoder::new(Writer::new()).expect(
|
||||||
|
@ -89,45 +92,44 @@ where
|
||||||
|
|
||||||
impl<S> Stream for Decoder<S>
|
impl<S> Stream for Decoder<S>
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
|
S: Stream<Item = Result<Bytes, PayloadError>>,
|
||||||
{
|
{
|
||||||
type Item = Result<Bytes, PayloadError>;
|
type Item = Result<Bytes, PayloadError>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
mut self: Pin<&mut Self>,
|
let mut this = self.project();
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Self::Item>> {
|
|
||||||
loop {
|
loop {
|
||||||
if let Some(ref mut fut) = self.fut {
|
if let Some(ref mut fut) = this.fut {
|
||||||
let (chunk, decoder) =
|
let (chunk, decoder) =
|
||||||
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
||||||
|
|
||||||
self.decoder = Some(decoder);
|
*this.decoder = Some(decoder);
|
||||||
self.fut.take();
|
this.fut.take();
|
||||||
|
|
||||||
if let Some(chunk) = chunk {
|
if let Some(chunk) = chunk {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.eof {
|
if *this.eof {
|
||||||
return Poll::Ready(None);
|
return Poll::Ready(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
match ready!(Pin::new(&mut self.stream).poll_next(cx)) {
|
match ready!(this.stream.as_mut().poll_next(cx)) {
|
||||||
Some(Err(err)) => return Poll::Ready(Some(Err(err))),
|
Some(Err(err)) => return Poll::Ready(Some(Err(err))),
|
||||||
|
|
||||||
Some(Ok(chunk)) => {
|
Some(Ok(chunk)) => {
|
||||||
if let Some(mut decoder) = self.decoder.take() {
|
if let Some(mut decoder) = this.decoder.take() {
|
||||||
if chunk.len() < MAX_CHUNK_SIZE_DECODE_IN_PLACE {
|
if chunk.len() < MAX_CHUNK_SIZE_DECODE_IN_PLACE {
|
||||||
let chunk = decoder.feed_data(chunk)?;
|
let chunk = decoder.feed_data(chunk)?;
|
||||||
self.decoder = Some(decoder);
|
*this.decoder = Some(decoder);
|
||||||
|
|
||||||
if let Some(chunk) = chunk {
|
if let Some(chunk) = chunk {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.fut = Some(spawn_blocking(move || {
|
*this.fut = Some(spawn_blocking(move || {
|
||||||
let chunk = decoder.feed_data(chunk)?;
|
let chunk = decoder.feed_data(chunk)?;
|
||||||
Ok((chunk, decoder))
|
Ok((chunk, decoder))
|
||||||
}));
|
}));
|
||||||
|
@ -140,9 +142,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
None => {
|
None => {
|
||||||
self.eof = true;
|
*this.eof = true;
|
||||||
|
|
||||||
return if let Some(mut decoder) = self.decoder.take() {
|
return if let Some(mut decoder) = this.decoder.take() {
|
||||||
match decoder.feed_eof() {
|
match decoder.feed_eof() {
|
||||||
Ok(Some(res)) => Poll::Ready(Some(Ok(res))),
|
Ok(Some(res)) => Poll::Ready(Some(Ok(res))),
|
||||||
Ok(None) => Poll::Ready(None),
|
Ok(None) => Poll::Ready(None),
|
||||||
|
|
|
@ -25,7 +25,7 @@ use zstd::stream::write::Encoder as ZstdEncoder;
|
||||||
|
|
||||||
use super::Writer;
|
use super::Writer;
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BodySize, MessageBody},
|
body::{self, BodySize, MessageBody},
|
||||||
error::BlockingError,
|
error::BlockingError,
|
||||||
header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING},
|
header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING},
|
||||||
ResponseHead, StatusCode,
|
ResponseHead, StatusCode,
|
||||||
|
@ -46,43 +46,39 @@ pin_project! {
|
||||||
impl<B: MessageBody> Encoder<B> {
|
impl<B: MessageBody> Encoder<B> {
|
||||||
fn none() -> Self {
|
fn none() -> Self {
|
||||||
Encoder {
|
Encoder {
|
||||||
body: EncoderBody::None,
|
body: EncoderBody::None {
|
||||||
|
body: body::None::new(),
|
||||||
|
},
|
||||||
encoder: None,
|
encoder: None,
|
||||||
fut: None,
|
fut: None,
|
||||||
eof: true,
|
eof: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn response(
|
pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, body: B) -> Self {
|
||||||
encoding: ContentEncoding,
|
|
||||||
head: &mut ResponseHead,
|
|
||||||
body: B,
|
|
||||||
) -> Self {
|
|
||||||
let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|
let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|
||||||
|| head.status == StatusCode::SWITCHING_PROTOCOLS
|
|| head.status == StatusCode::SWITCHING_PROTOCOLS
|
||||||
|| head.status == StatusCode::NO_CONTENT
|
|| head.status == StatusCode::NO_CONTENT
|
||||||
|| encoding == ContentEncoding::Identity
|
|| encoding == ContentEncoding::Identity
|
||||||
|| encoding == ContentEncoding::Auto);
|
|| encoding == ContentEncoding::Auto);
|
||||||
|
|
||||||
match body.size() {
|
// no need to compress an empty body
|
||||||
// no need to compress an empty body
|
if matches!(body.size(), BodySize::None) {
|
||||||
BodySize::None => return Self::none(),
|
return Self::none();
|
||||||
|
|
||||||
// we cannot assume that Sized is not a stream
|
|
||||||
BodySize::Sized(_) | BodySize::Stream => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO potentially some optimisation for single-chunk responses here by trying to read the
|
let body = match body.try_into_bytes() {
|
||||||
// payload eagerly, stopping after 2 polls if the first is a chunk and the second is None
|
Ok(body) => EncoderBody::Full { body },
|
||||||
|
Err(body) => EncoderBody::Stream { body },
|
||||||
|
};
|
||||||
|
|
||||||
if can_encode {
|
if can_encode {
|
||||||
// Modify response body only if encoder is set
|
// Modify response body only if encoder is set
|
||||||
if let Some(enc) = ContentEncoder::encoder(encoding) {
|
if let Some(enc) = ContentEncoder::encoder(encoding) {
|
||||||
update_head(encoding, head);
|
update_head(encoding, head);
|
||||||
head.no_chunking(false);
|
|
||||||
|
|
||||||
return Encoder {
|
return Encoder {
|
||||||
body: EncoderBody::Stream { body },
|
body,
|
||||||
encoder: Some(enc),
|
encoder: Some(enc),
|
||||||
fut: None,
|
fut: None,
|
||||||
eof: false,
|
eof: false,
|
||||||
|
@ -91,7 +87,7 @@ impl<B: MessageBody> Encoder<B> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Encoder {
|
Encoder {
|
||||||
body: EncoderBody::Stream { body },
|
body,
|
||||||
encoder: None,
|
encoder: None,
|
||||||
fut: None,
|
fut: None,
|
||||||
eof: false,
|
eof: false,
|
||||||
|
@ -102,7 +98,8 @@ impl<B: MessageBody> Encoder<B> {
|
||||||
pin_project! {
|
pin_project! {
|
||||||
#[project = EncoderBodyProj]
|
#[project = EncoderBodyProj]
|
||||||
enum EncoderBody<B> {
|
enum EncoderBody<B> {
|
||||||
None,
|
None { body: body::None },
|
||||||
|
Full { body: Bytes },
|
||||||
Stream { #[pin] body: B },
|
Stream { #[pin] body: B },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,9 +110,11 @@ where
|
||||||
{
|
{
|
||||||
type Error = EncoderError;
|
type Error = EncoderError;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
match self {
|
match self {
|
||||||
EncoderBody::None => BodySize::None,
|
EncoderBody::None { body } => body.size(),
|
||||||
|
EncoderBody::Full { body } => body.size(),
|
||||||
EncoderBody::Stream { body } => body.size(),
|
EncoderBody::Stream { body } => body.size(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,13 +124,29 @@ where
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
match self.project() {
|
match self.project() {
|
||||||
EncoderBodyProj::None => Poll::Ready(None),
|
EncoderBodyProj::None { body } => {
|
||||||
|
Pin::new(body).poll_next(cx).map_err(|err| match err {})
|
||||||
|
}
|
||||||
|
EncoderBodyProj::Full { body } => {
|
||||||
|
Pin::new(body).poll_next(cx).map_err(|err| match err {})
|
||||||
|
}
|
||||||
EncoderBodyProj::Stream { body } => body
|
EncoderBodyProj::Stream { body } => body
|
||||||
.poll_next(cx)
|
.poll_next(cx)
|
||||||
.map_err(|err| EncoderError::Body(err.into())),
|
.map_err(|err| EncoderError::Body(err.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(self) -> Result<Bytes, Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
EncoderBody::None { body } => Ok(body.try_into_bytes().unwrap()),
|
||||||
|
EncoderBody::Full { body } => Ok(body.try_into_bytes().unwrap()),
|
||||||
|
_ => Err(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B> MessageBody for Encoder<B>
|
impl<B> MessageBody for Encoder<B>
|
||||||
|
@ -140,11 +155,12 @@ where
|
||||||
{
|
{
|
||||||
type Error = EncoderError;
|
type Error = EncoderError;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
if self.encoder.is_none() {
|
if self.encoder.is_some() {
|
||||||
self.body.size()
|
|
||||||
} else {
|
|
||||||
BodySize::Stream
|
BodySize::Stream
|
||||||
|
} else {
|
||||||
|
self.body.size()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,6 +231,24 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_bytes(mut self) -> Result<Bytes, Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
if self.encoder.is_some() {
|
||||||
|
Err(self)
|
||||||
|
} else {
|
||||||
|
match self.body.try_into_bytes() {
|
||||||
|
Ok(body) => Ok(body),
|
||||||
|
Err(body) => {
|
||||||
|
self.body = body;
|
||||||
|
Err(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
|
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
|
||||||
|
@ -222,6 +256,8 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
|
||||||
header::CONTENT_ENCODING,
|
header::CONTENT_ENCODING,
|
||||||
HeaderValue::from_static(encoding.as_str()),
|
HeaderValue::from_static(encoding.as_str()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
head.no_chunking(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ContentEncoder {
|
enum ContentEncoder {
|
||||||
|
|
|
@ -332,31 +332,28 @@ impl From<PayloadError> for Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A set of errors that can occur during dispatching HTTP requests.
|
/// A set of errors that can occur during dispatching HTTP requests.
|
||||||
#[derive(Debug, Display, Error, From)]
|
#[derive(Debug, Display, From)]
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum DispatchError {
|
pub enum DispatchError {
|
||||||
/// Service error
|
/// Service error.
|
||||||
// FIXME: display and error type
|
|
||||||
#[display(fmt = "Service Error")]
|
#[display(fmt = "Service Error")]
|
||||||
Service(#[error(not(source))] Response<BoxBody>),
|
Service(Response<BoxBody>),
|
||||||
|
|
||||||
/// Body error
|
/// Body streaming error.
|
||||||
// FIXME: display and error type
|
#[display(fmt = "Body error: {}", _0)]
|
||||||
#[display(fmt = "Body Error")]
|
Body(Box<dyn StdError>),
|
||||||
Body(#[error(not(source))] Box<dyn StdError>),
|
|
||||||
|
|
||||||
/// Upgrade service error
|
/// Upgrade service error.
|
||||||
Upgrade,
|
Upgrade,
|
||||||
|
|
||||||
/// An `io::Error` that occurred while trying to read or write to a network stream.
|
/// An `io::Error` that occurred while trying to read or write to a network stream.
|
||||||
#[display(fmt = "IO error: {}", _0)]
|
#[display(fmt = "IO error: {}", _0)]
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
|
|
||||||
/// Http request parse error.
|
/// Request parse error.
|
||||||
#[display(fmt = "Parse error: {}", _0)]
|
#[display(fmt = "Request parse error: {}", _0)]
|
||||||
Parse(ParseError),
|
Parse(ParseError),
|
||||||
|
|
||||||
/// Http/2 error
|
/// HTTP/2 error.
|
||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
H2(h2::Error),
|
H2(h2::Error),
|
||||||
|
|
||||||
|
@ -368,21 +365,23 @@ pub enum DispatchError {
|
||||||
#[display(fmt = "Connection shutdown timeout")]
|
#[display(fmt = "Connection shutdown timeout")]
|
||||||
DisconnectTimeout,
|
DisconnectTimeout,
|
||||||
|
|
||||||
/// Payload is not consumed
|
/// Internal error.
|
||||||
#[display(fmt = "Task is completed but request's payload is not consumed")]
|
|
||||||
PayloadIsNotConsumed,
|
|
||||||
|
|
||||||
/// Malformed request
|
|
||||||
#[display(fmt = "Malformed request")]
|
|
||||||
MalformedRequest,
|
|
||||||
|
|
||||||
/// Internal error
|
|
||||||
#[display(fmt = "Internal error")]
|
#[display(fmt = "Internal error")]
|
||||||
InternalError,
|
InternalError,
|
||||||
|
}
|
||||||
|
|
||||||
/// Unknown error
|
impl StdError for DispatchError {
|
||||||
#[display(fmt = "Unknown error")]
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
Unknown,
|
match self {
|
||||||
|
// TODO: error source extraction?
|
||||||
|
DispatchError::Service(_res) => None,
|
||||||
|
DispatchError::Body(err) => Some(&**err),
|
||||||
|
DispatchError::Io(err) => Some(err),
|
||||||
|
DispatchError::Parse(err) => Some(err),
|
||||||
|
DispatchError::H2(err) => Some(err),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A set of error that can occur during parsing content type.
|
/// A set of error that can occur during parsing content type.
|
||||||
|
@ -457,8 +456,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
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!(err.to_string().contains("ParseError"));
|
assert!(err.to_string().contains("ParseError"));
|
||||||
|
|
||||||
let err = PayloadError::Incomplete(None);
|
let err = PayloadError::Incomplete(None);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
fmt, mem,
|
fmt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ahash::AHashMap;
|
use ahash::AHashMap;
|
||||||
|
@ -10,8 +10,7 @@ use ahash::AHashMap;
|
||||||
/// All entries into this map must be owned types (or static references).
|
/// 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 AHasher with a std HashMap with for faster lookups on the small `TypeId` keys.
|
||||||
/// lookups on the small `TypeId` (u64 equivalent) keys.
|
|
||||||
map: AHashMap<TypeId, Box<dyn Any>>,
|
map: AHashMap<TypeId, Box<dyn Any>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +19,7 @@ impl Extensions {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> Extensions {
|
pub fn new() -> Extensions {
|
||||||
Extensions {
|
Extensions {
|
||||||
map: AHashMap::default(),
|
map: AHashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,11 +122,6 @@ impl Extensions {
|
||||||
pub fn extend(&mut self, other: Extensions) {
|
pub fn extend(&mut self, other: Extensions) {
|
||||||
self.map.extend(other.map);
|
self.map.extend(other.map);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets (or overrides) items from `other` into this map.
|
|
||||||
pub(crate) fn drain_from(&mut self, other: &mut Self) {
|
|
||||||
self.map.extend(mem::take(&mut other.map));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Extensions {
|
impl fmt::Debug for Extensions {
|
||||||
|
@ -179,6 +173,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_integers() {
|
fn test_integers() {
|
||||||
|
static A: u32 = 8;
|
||||||
|
|
||||||
let mut map = Extensions::new();
|
let mut map = Extensions::new();
|
||||||
|
|
||||||
map.insert::<i8>(8);
|
map.insert::<i8>(8);
|
||||||
|
@ -191,6 +187,7 @@ mod tests {
|
||||||
map.insert::<u32>(32);
|
map.insert::<u32>(32);
|
||||||
map.insert::<u64>(64);
|
map.insert::<u64>(64);
|
||||||
map.insert::<u128>(128);
|
map.insert::<u128>(128);
|
||||||
|
map.insert::<&'static u32>(&A);
|
||||||
assert!(map.get::<i8>().is_some());
|
assert!(map.get::<i8>().is_some());
|
||||||
assert!(map.get::<i16>().is_some());
|
assert!(map.get::<i16>().is_some());
|
||||||
assert!(map.get::<i32>().is_some());
|
assert!(map.get::<i32>().is_some());
|
||||||
|
@ -201,6 +198,7 @@ mod tests {
|
||||||
assert!(map.get::<u32>().is_some());
|
assert!(map.get::<u32>().is_some());
|
||||||
assert!(map.get::<u64>().is_some());
|
assert!(map.get::<u64>().is_some());
|
||||||
assert!(map.get::<u128>().is_some());
|
assert!(map.get::<u128>().is_some());
|
||||||
|
assert!(map.get::<&'static u32>().is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -279,27 +277,4 @@ mod tests {
|
||||||
assert_eq!(extensions.get(), Some(&20u8));
|
assert_eq!(extensions.get(), Some(&20u8));
|
||||||
assert_eq!(extensions.get_mut(), Some(&mut 20u8));
|
assert_eq!(extensions.get_mut(), Some(&mut 20u8));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_drain_from() {
|
|
||||||
let mut ext = Extensions::new();
|
|
||||||
ext.insert(2isize);
|
|
||||||
|
|
||||||
let mut more_ext = Extensions::new();
|
|
||||||
|
|
||||||
more_ext.insert(5isize);
|
|
||||||
more_ext.insert(5usize);
|
|
||||||
|
|
||||||
assert_eq!(ext.get::<isize>(), Some(&2isize));
|
|
||||||
assert_eq!(ext.get::<usize>(), None);
|
|
||||||
assert_eq!(more_ext.get::<isize>(), Some(&5isize));
|
|
||||||
assert_eq!(more_ext.get::<usize>(), Some(&5usize));
|
|
||||||
|
|
||||||
ext.drain_from(&mut more_ext);
|
|
||||||
|
|
||||||
assert_eq!(ext.get::<isize>(), Some(&5isize));
|
|
||||||
assert_eq!(ext.get::<usize>(), Some(&5usize));
|
|
||||||
assert_eq!(more_ext.get::<isize>(), None);
|
|
||||||
assert_eq!(more_ext.get::<usize>(), None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,10 +50,7 @@ impl ChunkedState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_size(
|
fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
rdr: &mut BytesMut,
|
|
||||||
size: &mut u64,
|
|
||||||
) -> Poll<Result<ChunkedState, io::Error>> {
|
|
||||||
let radix = 16;
|
let radix = 16;
|
||||||
|
|
||||||
let rem = match byte!(rdr) {
|
let rem = match byte!(rdr) {
|
||||||
|
@ -111,10 +108,7 @@ impl ChunkedState {
|
||||||
_ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions
|
_ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn read_size_lf(
|
fn read_size_lf(rdr: &mut BytesMut, size: u64) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
rdr: &mut BytesMut,
|
|
||||||
size: u64,
|
|
||||||
) -> Poll<Result<ChunkedState, io::Error>> {
|
|
||||||
match byte!(rdr) {
|
match byte!(rdr) {
|
||||||
b'\n' if size > 0 => Poll::Ready(Ok(ChunkedState::Body)),
|
b'\n' if size > 0 => Poll::Ready(Ok(ChunkedState::Body)),
|
||||||
b'\n' if size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)),
|
b'\n' if size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)),
|
||||||
|
|
|
@ -5,13 +5,15 @@ use bitflags::bitflags;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use http::{Method, Version};
|
use http::{Method, Version};
|
||||||
|
|
||||||
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
|
use super::{
|
||||||
use super::{decoder, encoder, reserve_readbuf};
|
decoder::{self, PayloadDecoder, PayloadItem, PayloadType},
|
||||||
use super::{Message, MessageType};
|
encoder, reserve_readbuf, Message, MessageType,
|
||||||
use crate::body::BodySize;
|
};
|
||||||
use crate::config::ServiceConfig;
|
use crate::{
|
||||||
use crate::error::{ParseError, PayloadError};
|
body::BodySize,
|
||||||
use crate::message::{ConnectionType, RequestHeadType, ResponseHead};
|
error::{ParseError, PayloadError},
|
||||||
|
ConnectionType, RequestHeadType, ResponseHead, ServiceConfig,
|
||||||
|
};
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
struct Flags: u8 {
|
struct Flags: u8 {
|
||||||
|
|
|
@ -5,15 +5,13 @@ use bitflags::bitflags;
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use http::{Method, Version};
|
use http::{Method, Version};
|
||||||
|
|
||||||
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
|
use super::{
|
||||||
use super::{decoder, encoder};
|
decoder::{self, PayloadDecoder, PayloadItem, PayloadType},
|
||||||
use super::{Message, MessageType};
|
encoder, Message, MessageType,
|
||||||
use crate::body::BodySize;
|
};
|
||||||
use crate::config::ServiceConfig;
|
use crate::{
|
||||||
use crate::error::ParseError;
|
body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig,
|
||||||
use crate::message::ConnectionType;
|
};
|
||||||
use crate::request::Request;
|
|
||||||
use crate::response::Response;
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
struct Flags: u8 {
|
struct Flags: u8 {
|
||||||
|
@ -199,7 +197,7 @@ mod tests {
|
||||||
use http::Method;
|
use http::Method;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::HttpMessage;
|
use crate::HttpMessage as _;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_http_request_chunked_payload_and_next_message() {
|
async fn test_http_request_chunked_payload_and_next_message() {
|
||||||
|
|
|
@ -2,17 +2,14 @@ use std::{convert::TryFrom, io, marker::PhantomData, mem::MaybeUninit, task::Pol
|
||||||
|
|
||||||
use actix_codec::Decoder;
|
use actix_codec::Decoder;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use http::header::{HeaderName, HeaderValue};
|
use http::{
|
||||||
use http::{header, Method, StatusCode, Uri, Version};
|
header::{self, HeaderName, HeaderValue},
|
||||||
|
Method, StatusCode, Uri, Version,
|
||||||
|
};
|
||||||
use log::{debug, error, trace};
|
use log::{debug, error, trace};
|
||||||
|
|
||||||
use super::chunked::ChunkedState;
|
use super::chunked::ChunkedState;
|
||||||
use crate::{
|
use crate::{error::ParseError, header::HeaderMap, ConnectionType, Request, ResponseHead};
|
||||||
error::ParseError,
|
|
||||||
header::HeaderMap,
|
|
||||||
message::{ConnectionType, ResponseHead},
|
|
||||||
request::Request,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) 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;
|
||||||
|
@ -50,7 +47,7 @@ pub(crate) enum PayloadLength {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait MessageType: Sized {
|
pub(crate) trait MessageType: Sized {
|
||||||
fn set_connection_type(&mut self, ctype: Option<ConnectionType>);
|
fn set_connection_type(&mut self, conn_type: Option<ConnectionType>);
|
||||||
|
|
||||||
fn set_expect(&mut self);
|
fn set_expect(&mut self);
|
||||||
|
|
||||||
|
@ -74,8 +71,7 @@ pub(crate) trait MessageType: Sized {
|
||||||
let headers = self.headers_mut();
|
let headers = self.headers_mut();
|
||||||
|
|
||||||
for idx in raw_headers.iter() {
|
for idx in raw_headers.iter() {
|
||||||
let name =
|
let name = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap();
|
||||||
HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap();
|
|
||||||
|
|
||||||
// SAFETY: httparse already checks header value is only visible ASCII bytes
|
// SAFETY: httparse already checks header value is only visible ASCII bytes
|
||||||
// from_maybe_shared_unchecked contains debug assertions so they are omitted here
|
// from_maybe_shared_unchecked contains debug assertions so they are omitted here
|
||||||
|
@ -194,8 +190,8 @@ pub(crate) trait MessageType: Sized {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageType for Request {
|
impl MessageType for Request {
|
||||||
fn set_connection_type(&mut self, ctype: Option<ConnectionType>) {
|
fn set_connection_type(&mut self, conn_type: Option<ConnectionType>) {
|
||||||
if let Some(ctype) = ctype {
|
if let Some(ctype) = conn_type {
|
||||||
self.head_mut().set_connection_type(ctype);
|
self.head_mut().set_connection_type(ctype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -279,8 +275,8 @@ impl MessageType for Request {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageType for ResponseHead {
|
impl MessageType for ResponseHead {
|
||||||
fn set_connection_type(&mut self, ctype: Option<ConnectionType>) {
|
fn set_connection_type(&mut self, conn_type: Option<ConnectionType>) {
|
||||||
if let Some(ctype) = ctype {
|
if let Some(ctype) = conn_type {
|
||||||
ResponseHead::set_connection_type(self, ctype);
|
ResponseHead::set_connection_type(self, ctype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -605,8 +601,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_body() {
|
fn test_parse_body() {
|
||||||
let mut buf =
|
let mut buf = BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
|
||||||
BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
|
|
||||||
|
|
||||||
let mut reader = MessageDecoder::<Request>::default();
|
let mut reader = MessageDecoder::<Request>::default();
|
||||||
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
|
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
|
||||||
|
@ -622,8 +617,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_body_crlf() {
|
fn test_parse_body_crlf() {
|
||||||
let mut buf =
|
let mut buf = BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
|
||||||
BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
|
|
||||||
|
|
||||||
let mut reader = MessageDecoder::<Request>::default();
|
let mut reader = MessageDecoder::<Request>::default();
|
||||||
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
|
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
|
||||||
|
|
|
@ -15,18 +15,19 @@ use bitflags::bitflags;
|
||||||
use bytes::{Buf, BytesMut};
|
use bytes::{Buf, BytesMut};
|
||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use log::{error, trace};
|
use log::{error, trace};
|
||||||
use pin_project::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BodySize, BoxBody, MessageBody},
|
body::{BodySize, BoxBody, MessageBody},
|
||||||
config::ServiceConfig,
|
config::ServiceConfig,
|
||||||
error::{DispatchError, ParseError, PayloadError},
|
error::{DispatchError, ParseError, PayloadError},
|
||||||
service::HttpFlow,
|
service::HttpFlow,
|
||||||
OnConnectData, Request, Response, StatusCode,
|
Error, Extensions, OnConnectData, Request, Response, StatusCode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
codec::Codec,
|
codec::Codec,
|
||||||
|
decoder::MAX_BUFFER_SIZE,
|
||||||
payload::{Payload, PayloadSender, PayloadStatus},
|
payload::{Payload, PayloadSender, PayloadStatus},
|
||||||
Message, MessageType,
|
Message, MessageType,
|
||||||
};
|
};
|
||||||
|
@ -45,79 +46,111 @@ bitflags! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project]
|
// there's 2 versions of Dispatcher state because of:
|
||||||
/// Dispatcher for HTTP/1.1 protocol
|
// https://github.com/taiki-e/pin-project-lite/issues/3
|
||||||
pub struct Dispatcher<T, S, B, X, U>
|
//
|
||||||
where
|
// tl;dr: pin-project-lite doesn't play well with other attribute macros
|
||||||
S: Service<Request>,
|
|
||||||
S::Error: Into<Response<BoxBody>>,
|
|
||||||
|
|
||||||
B: MessageBody,
|
#[cfg(not(test))]
|
||||||
|
pin_project! {
|
||||||
|
/// Dispatcher for HTTP/1.1 protocol
|
||||||
|
pub struct Dispatcher<T, S, B, X, U>
|
||||||
|
where
|
||||||
|
S: Service<Request>,
|
||||||
|
S::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
B: MessageBody,
|
||||||
X::Error: Into<Response<BoxBody>>,
|
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
X: Service<Request, Response = Request>,
|
||||||
U::Error: fmt::Display,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
{
|
|
||||||
#[pin]
|
|
||||||
inner: DispatcherState<T, S, B, X, U>,
|
|
||||||
|
|
||||||
#[cfg(test)]
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
poll_count: u64,
|
U::Error: fmt::Display,
|
||||||
|
{
|
||||||
|
#[pin]
|
||||||
|
inner: DispatcherState<T, S, B, X, U>,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project(project = DispatcherStateProj)]
|
#[cfg(test)]
|
||||||
enum DispatcherState<T, S, B, X, U>
|
pin_project! {
|
||||||
where
|
/// Dispatcher for HTTP/1.1 protocol
|
||||||
S: Service<Request>,
|
pub struct Dispatcher<T, S, B, X, U>
|
||||||
S::Error: Into<Response<BoxBody>>,
|
where
|
||||||
|
S: Service<Request>,
|
||||||
|
S::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Response<BoxBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
Normal(#[pin] InnerDispatcher<T, S, B, X, U>),
|
#[pin]
|
||||||
Upgrade(#[pin] U::Future),
|
inner: DispatcherState<T, S, B, X, U>,
|
||||||
|
|
||||||
|
// used in tests
|
||||||
|
poll_count: u64,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project(project = InnerDispatcherProj)]
|
pin_project! {
|
||||||
struct InnerDispatcher<T, S, B, X, U>
|
#[project = DispatcherStateProj]
|
||||||
where
|
enum DispatcherState<T, S, B, X, U>
|
||||||
S: Service<Request>,
|
where
|
||||||
S::Error: Into<Response<BoxBody>>,
|
S: Service<Request>,
|
||||||
|
S::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Response<BoxBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
flow: Rc<HttpFlow<S, X, U>>,
|
Normal { #[pin] inner: InnerDispatcher<T, S, B, X, U> },
|
||||||
on_connect_data: OnConnectData,
|
Upgrade { #[pin] fut: U::Future },
|
||||||
flags: Flags,
|
}
|
||||||
peer_addr: Option<net::SocketAddr>,
|
}
|
||||||
error: Option<DispatchError>,
|
|
||||||
|
|
||||||
#[pin]
|
pin_project! {
|
||||||
state: State<S, B, X>,
|
#[project = InnerDispatcherProj]
|
||||||
payload: Option<PayloadSender>,
|
struct InnerDispatcher<T, S, B, X, U>
|
||||||
messages: VecDeque<DispatcherMessage>,
|
where
|
||||||
|
S: Service<Request>,
|
||||||
|
S::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
ka_expire: Instant,
|
B: MessageBody,
|
||||||
#[pin]
|
|
||||||
ka_timer: Option<Sleep>,
|
|
||||||
|
|
||||||
io: Option<T>,
|
X: Service<Request, Response = Request>,
|
||||||
read_buf: BytesMut,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
write_buf: BytesMut,
|
|
||||||
codec: Codec,
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
|
U::Error: fmt::Display,
|
||||||
|
{
|
||||||
|
flow: Rc<HttpFlow<S, X, U>>,
|
||||||
|
flags: Flags,
|
||||||
|
peer_addr: Option<net::SocketAddr>,
|
||||||
|
conn_data: Option<Rc<Extensions>>,
|
||||||
|
error: Option<DispatchError>,
|
||||||
|
|
||||||
|
#[pin]
|
||||||
|
state: State<S, B, X>,
|
||||||
|
payload: Option<PayloadSender>,
|
||||||
|
messages: VecDeque<DispatcherMessage>,
|
||||||
|
|
||||||
|
ka_expire: Instant,
|
||||||
|
#[pin]
|
||||||
|
ka_timer: Option<Sleep>,
|
||||||
|
|
||||||
|
io: Option<T>,
|
||||||
|
read_buf: BytesMut,
|
||||||
|
write_buf: BytesMut,
|
||||||
|
codec: Codec,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DispatcherMessage {
|
enum DispatcherMessage {
|
||||||
|
@ -126,19 +159,21 @@ enum DispatcherMessage {
|
||||||
Error(Response<()>),
|
Error(Response<()>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project(project = StateProj)]
|
pin_project! {
|
||||||
enum State<S, B, X>
|
#[project = StateProj]
|
||||||
where
|
enum State<S, B, X>
|
||||||
S: Service<Request>,
|
where
|
||||||
X: Service<Request, Response = Request>,
|
S: Service<Request>,
|
||||||
|
X: Service<Request, Response = Request>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
ExpectCall(#[pin] X::Future),
|
ExpectCall { #[pin] fut: X::Future },
|
||||||
ServiceCall(#[pin] S::Future),
|
ServiceCall { #[pin] fut: S::Future },
|
||||||
SendPayload(#[pin] B),
|
SendPayload { #[pin] body: B },
|
||||||
SendErrorPayload(#[pin] BoxBody),
|
SendErrorPayload { #[pin] body: BoxBody },
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, B, X> State<S, B, X>
|
impl<S, B, X> State<S, B, X>
|
||||||
|
@ -179,10 +214,10 @@ where
|
||||||
/// Create HTTP/1 dispatcher.
|
/// Create HTTP/1 dispatcher.
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
io: T,
|
io: T,
|
||||||
config: ServiceConfig,
|
|
||||||
flow: Rc<HttpFlow<S, X, U>>,
|
flow: Rc<HttpFlow<S, X, U>>,
|
||||||
on_connect_data: OnConnectData,
|
config: ServiceConfig,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
|
conn_data: OnConnectData,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let flags = if config.keep_alive_enabled() {
|
let flags = if config.keep_alive_enabled() {
|
||||||
Flags::KEEPALIVE
|
Flags::KEEPALIVE
|
||||||
|
@ -197,22 +232,27 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
Dispatcher {
|
Dispatcher {
|
||||||
inner: DispatcherState::Normal(InnerDispatcher {
|
inner: DispatcherState::Normal {
|
||||||
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
inner: InnerDispatcher {
|
||||||
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
flow,
|
||||||
payload: None,
|
flags,
|
||||||
state: State::None,
|
peer_addr,
|
||||||
error: None,
|
conn_data: conn_data.0.map(Rc::new),
|
||||||
messages: VecDeque::new(),
|
error: None,
|
||||||
io: Some(io),
|
|
||||||
codec: Codec::new(config),
|
state: State::None,
|
||||||
flow,
|
payload: None,
|
||||||
on_connect_data,
|
messages: VecDeque::new(),
|
||||||
flags,
|
|
||||||
peer_addr,
|
ka_expire,
|
||||||
ka_expire,
|
ka_timer,
|
||||||
ka_timer,
|
|
||||||
}),
|
io: Some(io),
|
||||||
|
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||||
|
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||||
|
codec: Codec::new(config),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
poll_count: 0,
|
poll_count: 0,
|
||||||
|
@ -256,10 +296,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_flush(
|
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||||
self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Result<(), io::Error>> {
|
|
||||||
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());
|
||||||
|
|
||||||
|
@ -269,10 +306,7 @@ where
|
||||||
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(0) => {
|
Poll::Ready(0) => {
|
||||||
return Poll::Ready(Err(io::Error::new(
|
return Poll::Ready(Err(io::Error::new(io::ErrorKind::WriteZero, "")))
|
||||||
io::ErrorKind::WriteZero,
|
|
||||||
"",
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
Poll::Ready(n) => written += n,
|
Poll::Ready(n) => written += n,
|
||||||
Poll::Pending => {
|
Poll::Pending => {
|
||||||
|
@ -318,7 +352,7 @@ where
|
||||||
let size = self.as_mut().send_response_inner(message, &body)?;
|
let size = self.as_mut().send_response_inner(message, &body)?;
|
||||||
let state = match size {
|
let state = match size {
|
||||||
BodySize::None | BodySize::Sized(0) => State::None,
|
BodySize::None | BodySize::Sized(0) => State::None,
|
||||||
_ => State::SendPayload(body),
|
_ => State::SendPayload { body },
|
||||||
};
|
};
|
||||||
self.project().state.set(state);
|
self.project().state.set(state);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -332,7 +366,7 @@ where
|
||||||
let size = self.as_mut().send_response_inner(message, &body)?;
|
let size = self.as_mut().send_response_inner(message, &body)?;
|
||||||
let state = match size {
|
let state = match size {
|
||||||
BodySize::None | BodySize::Sized(0) => State::None,
|
BodySize::None | BodySize::Sized(0) => State::None,
|
||||||
_ => State::SendErrorPayload(body),
|
_ => State::SendErrorPayload { body },
|
||||||
};
|
};
|
||||||
self.project().state.set(state);
|
self.project().state.set(state);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -358,12 +392,12 @@ where
|
||||||
// Handle `EXPECT: 100-Continue` header
|
// Handle `EXPECT: 100-Continue` header
|
||||||
if req.head().expect() {
|
if req.head().expect() {
|
||||||
// set InnerDispatcher state and continue loop to poll it.
|
// set InnerDispatcher state and continue loop to poll it.
|
||||||
let task = this.flow.expect.call(req);
|
let fut = this.flow.expect.call(req);
|
||||||
this.state.set(State::ExpectCall(task));
|
this.state.set(State::ExpectCall { fut });
|
||||||
} else {
|
} else {
|
||||||
// the same as expect call.
|
// the same as expect call.
|
||||||
let task = this.flow.service.call(req);
|
let fut = this.flow.service.call(req);
|
||||||
this.state.set(State::ServiceCall(task));
|
this.state.set(State::ServiceCall { fut });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,7 +417,7 @@ where
|
||||||
// all messages are dealt with.
|
// all messages are dealt with.
|
||||||
None => return Ok(PollResponse::DoNothing),
|
None => return Ok(PollResponse::DoNothing),
|
||||||
},
|
},
|
||||||
StateProj::ServiceCall(fut) => match fut.poll(cx) {
|
StateProj::ServiceCall { fut } => match fut.poll(cx) {
|
||||||
// service call resolved. send response.
|
// service call resolved. send response.
|
||||||
Poll::Ready(Ok(res)) => {
|
Poll::Ready(Ok(res)) => {
|
||||||
let (res, body) = res.into().replace_body(());
|
let (res, body) = res.into().replace_body(());
|
||||||
|
@ -409,21 +443,18 @@ where
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
StateProj::SendPayload(mut stream) => {
|
StateProj::SendPayload { mut body } => {
|
||||||
// keep populate writer buffer until buffer size limit hit,
|
// keep populate writer buffer until buffer size limit hit,
|
||||||
// get blocked or finished.
|
// get blocked or finished.
|
||||||
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
|
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
|
||||||
match stream.as_mut().poll_next(cx) {
|
match body.as_mut().poll_next(cx) {
|
||||||
Poll::Ready(Some(Ok(item))) => {
|
Poll::Ready(Some(Ok(item))) => {
|
||||||
this.codec.encode(
|
this.codec
|
||||||
Message::Chunk(Some(item)),
|
.encode(Message::Chunk(Some(item)), this.write_buf)?;
|
||||||
this.write_buf,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Poll::Ready(None) => {
|
Poll::Ready(None) => {
|
||||||
this.codec
|
this.codec.encode(Message::Chunk(None), this.write_buf)?;
|
||||||
.encode(Message::Chunk(None), this.write_buf)?;
|
|
||||||
// payload stream finished.
|
// payload stream finished.
|
||||||
// set state to None and handle next message
|
// set state to None and handle next message
|
||||||
this.state.set(State::None);
|
this.state.set(State::None);
|
||||||
|
@ -442,23 +473,20 @@ where
|
||||||
return Ok(PollResponse::DrainWriteBuf);
|
return Ok(PollResponse::DrainWriteBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
StateProj::SendErrorPayload(mut stream) => {
|
StateProj::SendErrorPayload { mut body } => {
|
||||||
// TODO: de-dupe impl with SendPayload
|
// TODO: de-dupe impl with SendPayload
|
||||||
|
|
||||||
// keep populate writer buffer until buffer size limit hit,
|
// keep populate writer buffer until buffer size limit hit,
|
||||||
// get blocked or finished.
|
// get blocked or finished.
|
||||||
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
|
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
|
||||||
match stream.as_mut().poll_next(cx) {
|
match body.as_mut().poll_next(cx) {
|
||||||
Poll::Ready(Some(Ok(item))) => {
|
Poll::Ready(Some(Ok(item))) => {
|
||||||
this.codec.encode(
|
this.codec
|
||||||
Message::Chunk(Some(item)),
|
.encode(Message::Chunk(Some(item)), this.write_buf)?;
|
||||||
this.write_buf,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Poll::Ready(None) => {
|
Poll::Ready(None) => {
|
||||||
this.codec
|
this.codec.encode(Message::Chunk(None), this.write_buf)?;
|
||||||
.encode(Message::Chunk(None), this.write_buf)?;
|
|
||||||
// payload stream finished.
|
// payload stream finished.
|
||||||
// set state to None and handle next message
|
// set state to None and handle next message
|
||||||
this.state.set(State::None);
|
this.state.set(State::None);
|
||||||
|
@ -466,7 +494,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
Poll::Ready(Some(Err(err))) => {
|
Poll::Ready(Some(Err(err))) => {
|
||||||
return Err(DispatchError::Service(err.into()))
|
return Err(DispatchError::Body(
|
||||||
|
Error::new_body().with_cause(err).into(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
Poll::Pending => return Ok(PollResponse::DoNothing),
|
Poll::Pending => return Ok(PollResponse::DoNothing),
|
||||||
|
@ -477,14 +507,14 @@ where
|
||||||
return Ok(PollResponse::DrainWriteBuf);
|
return Ok(PollResponse::DrainWriteBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
StateProj::ExpectCall(fut) => match fut.poll(cx) {
|
StateProj::ExpectCall { fut } => match fut.poll(cx) {
|
||||||
// expect resolved. write continue to buffer and set InnerDispatcher state
|
// expect resolved. write continue to buffer and set InnerDispatcher state
|
||||||
// to service call.
|
// to service call.
|
||||||
Poll::Ready(Ok(req)) => {
|
Poll::Ready(Ok(req)) => {
|
||||||
this.write_buf
|
this.write_buf
|
||||||
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
|
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
|
||||||
let fut = this.flow.service.call(req);
|
let fut = this.flow.service.call(req);
|
||||||
this.state.set(State::ServiceCall(fut));
|
this.state.set(State::ServiceCall { fut });
|
||||||
}
|
}
|
||||||
|
|
||||||
// send expect error as response
|
// send expect error as response
|
||||||
|
@ -510,25 +540,25 @@ where
|
||||||
let mut this = self.as_mut().project();
|
let mut this = self.as_mut().project();
|
||||||
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 task = this.flow.expect.call(req);
|
let fut = this.flow.expect.call(req);
|
||||||
this.state.set(State::ExpectCall(task));
|
this.state.set(State::ExpectCall { fut });
|
||||||
} else {
|
} else {
|
||||||
// the same as above.
|
// the same as above.
|
||||||
let task = this.flow.service.call(req);
|
let fut = this.flow.service.call(req);
|
||||||
this.state.set(State::ServiceCall(task));
|
this.state.set(State::ServiceCall { fut });
|
||||||
};
|
};
|
||||||
|
|
||||||
// eagerly poll the future for once(or twice if expect is resolved immediately).
|
// eagerly poll the future for once(or twice if expect is resolved immediately).
|
||||||
loop {
|
loop {
|
||||||
match self.as_mut().project().state.project() {
|
match self.as_mut().project().state.project() {
|
||||||
StateProj::ExpectCall(fut) => {
|
StateProj::ExpectCall { fut } => {
|
||||||
match fut.poll(cx) {
|
match fut.poll(cx) {
|
||||||
// expect is resolved. continue loop and poll the service call branch.
|
// expect is resolved. continue loop and poll the service call branch.
|
||||||
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.service.call(req);
|
let fut = this.flow.service.call(req);
|
||||||
this.state.set(State::ServiceCall(task));
|
this.state.set(State::ServiceCall { fut });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// future is pending. return Ok(()) to notify that a new state is
|
// future is pending. return Ok(()) to notify that a new state is
|
||||||
|
@ -544,7 +574,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StateProj::ServiceCall(fut) => {
|
StateProj::ServiceCall { fut } => {
|
||||||
// return no matter the service call future's result.
|
// return no matter the service call future's result.
|
||||||
return match fut.poll(cx) {
|
return match fut.poll(cx) {
|
||||||
// future is resolved. send response and return a result. On success
|
// future is resolved. send response and return a result. On success
|
||||||
|
@ -564,9 +594,11 @@ where
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
_ => unreachable!(
|
_ => {
|
||||||
"State must be set to ServiceCall or ExceptCall in handle_request"
|
unreachable!(
|
||||||
),
|
"State must be set to ServiceCall or ExceptCall in handle_request"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -593,16 +625,14 @@ where
|
||||||
Message::Item(mut req) => {
|
Message::Item(mut req) => {
|
||||||
req.head_mut().peer_addr = *this.peer_addr;
|
req.head_mut().peer_addr = *this.peer_addr;
|
||||||
|
|
||||||
// merge on_connect_ext data into request extensions
|
req.conn_data = this.conn_data.as_ref().map(Rc::clone);
|
||||||
this.on_connect_data.merge_into(&mut req);
|
|
||||||
|
|
||||||
match this.codec.message_type() {
|
match this.codec.message_type() {
|
||||||
// Request is upgradable. add upgrade message and break.
|
// Request is upgradable. add upgrade message and break.
|
||||||
// everything remain in read buffer would be handed to
|
// everything remain in read buffer would be handed to
|
||||||
// upgraded Request.
|
// upgraded Request.
|
||||||
MessageType::Stream if this.flow.upgrade.is_some() => {
|
MessageType::Stream if this.flow.upgrade.is_some() => {
|
||||||
this.messages
|
this.messages.push_back(DispatcherMessage::Upgrade(req));
|
||||||
.push_back(DispatcherMessage::Upgrade(req));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,11 +646,11 @@ where
|
||||||
Payload is attached to Request and passed to Service::call
|
Payload is attached to Request and passed to Service::call
|
||||||
where the state can be collected and consumed.
|
where the state can be collected and consumed.
|
||||||
*/
|
*/
|
||||||
let (ps, pl) = Payload::create(false);
|
let (sender, payload) = Payload::create(false);
|
||||||
let (req1, _) =
|
let (req1, _) =
|
||||||
req.replace_payload(crate::Payload::H1(pl));
|
req.replace_payload(crate::Payload::H1 { payload });
|
||||||
req = req1;
|
req = req1;
|
||||||
*this.payload = Some(ps);
|
*this.payload = Some(sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request has no payload.
|
// Request has no payload.
|
||||||
|
@ -639,9 +669,7 @@ where
|
||||||
if let Some(ref mut payload) = this.payload {
|
if let Some(ref mut payload) = this.payload {
|
||||||
payload.feed_data(chunk);
|
payload.feed_data(chunk);
|
||||||
} else {
|
} else {
|
||||||
error!(
|
error!("Internal server error: unexpected payload chunk");
|
||||||
"Internal server error: unexpected payload chunk"
|
|
||||||
);
|
|
||||||
this.flags.insert(Flags::READ_DISCONNECT);
|
this.flags.insert(Flags::READ_DISCONNECT);
|
||||||
this.messages.push_back(DispatcherMessage::Error(
|
this.messages.push_back(DispatcherMessage::Error(
|
||||||
Response::internal_server_error().drop_body(),
|
Response::internal_server_error().drop_body(),
|
||||||
|
@ -679,12 +707,11 @@ where
|
||||||
payload.set_error(PayloadError::Overflow);
|
payload.set_error(PayloadError::Overflow);
|
||||||
}
|
}
|
||||||
// Requests overflow buffer size should be responded with 431
|
// Requests overflow buffer size should be responded with 431
|
||||||
this.messages.push_back(DispatcherMessage::Error(
|
this.messages
|
||||||
Response::with_body(
|
.push_back(DispatcherMessage::Error(Response::with_body(
|
||||||
StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
|
StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
|
||||||
(),
|
(),
|
||||||
),
|
)));
|
||||||
));
|
|
||||||
this.flags.insert(Flags::READ_DISCONNECT);
|
this.flags.insert(Flags::READ_DISCONNECT);
|
||||||
*this.error = Some(ParseError::TooLarge.into());
|
*this.error = Some(ParseError::TooLarge.into());
|
||||||
break;
|
break;
|
||||||
|
@ -726,8 +753,7 @@ where
|
||||||
None => {
|
None => {
|
||||||
// conditionally go into shutdown timeout
|
// conditionally go into shutdown timeout
|
||||||
if this.flags.contains(Flags::SHUTDOWN) {
|
if this.flags.contains(Flags::SHUTDOWN) {
|
||||||
if let Some(deadline) = this.codec.config().client_disconnect_timer()
|
if let Some(deadline) = this.codec.config().client_disconnect_timer() {
|
||||||
{
|
|
||||||
// write client disconnect time out and poll again to
|
// write client disconnect time out and poll again to
|
||||||
// go into Some<Pin<&mut Sleep>> branch
|
// go into Some<Pin<&mut Sleep>> branch
|
||||||
this.ka_timer.set(Some(sleep_until(deadline)));
|
this.ka_timer.set(Some(sleep_until(deadline)));
|
||||||
|
@ -770,9 +796,7 @@ where
|
||||||
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
|
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
|
||||||
}
|
}
|
||||||
// still have unfinished task. try to reset and register keep-alive.
|
// still have unfinished task. try to reset and register keep-alive.
|
||||||
} else if let Some(deadline) =
|
} else if let Some(deadline) = this.codec.config().keep_alive_expire() {
|
||||||
this.codec.config().keep_alive_expire()
|
|
||||||
{
|
|
||||||
timer.as_mut().reset(deadline);
|
timer.as_mut().reset(deadline);
|
||||||
let _ = timer.poll(cx);
|
let _ = timer.poll(cx);
|
||||||
}
|
}
|
||||||
|
@ -791,7 +815,6 @@ where
|
||||||
/// Returns true when io stream can be disconnected after write to it.
|
/// Returns true when io stream can be disconnected after write to it.
|
||||||
///
|
///
|
||||||
/// It covers these conditions:
|
/// It covers these conditions:
|
||||||
///
|
|
||||||
/// - `std::io::ErrorKind::ConnectionReset` after partial read.
|
/// - `std::io::ErrorKind::ConnectionReset` after partial read.
|
||||||
/// - all data read done.
|
/// - all data read done.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -811,46 +834,39 @@ where
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Return early when read buf exceed decoder's max buffer size.
|
// Return early when read buf exceed decoder's max buffer size.
|
||||||
if this.read_buf.len() >= super::decoder::MAX_BUFFER_SIZE {
|
if this.read_buf.len() >= MAX_BUFFER_SIZE {
|
||||||
/*
|
// At this point it's not known IO stream is still scheduled to be waked up so
|
||||||
At this point it's not known IO stream is still scheduled
|
// force wake up dispatcher just in case.
|
||||||
to be waked up. so force wake up dispatcher just in case.
|
//
|
||||||
|
// Reason:
|
||||||
|
// AsyncRead mostly would only have guarantee wake up when the poll_read
|
||||||
|
// return Poll::Pending.
|
||||||
|
//
|
||||||
|
// Case:
|
||||||
|
// When read_buf is beyond max buffer size the early return could be successfully
|
||||||
|
// be parsed as a new Request. This case would not generate ParseError::TooLarge and
|
||||||
|
// at this point IO stream is not fully read to Pending and would result in
|
||||||
|
// dispatcher stuck until timeout (KA)
|
||||||
|
//
|
||||||
|
// Note:
|
||||||
|
// This is a perf choice to reduce branch on <Request as MessageType>::decode.
|
||||||
|
//
|
||||||
|
// A Request head too large to parse is only checked on
|
||||||
|
// `httparse::Status::Partial` condition.
|
||||||
|
|
||||||
Reason:
|
|
||||||
AsyncRead mostly would only have guarantee wake up
|
|
||||||
when the poll_read return Poll::Pending.
|
|
||||||
|
|
||||||
Case:
|
|
||||||
When read_buf is beyond max buffer size the early return
|
|
||||||
could be successfully be parsed as a new Request.
|
|
||||||
This case would not generate ParseError::TooLarge
|
|
||||||
and at this point IO stream is not fully read to Pending
|
|
||||||
and would result in dispatcher stuck until timeout (KA)
|
|
||||||
|
|
||||||
Note:
|
|
||||||
This is a perf choice to reduce branch on
|
|
||||||
<Request as MessageType>::decode.
|
|
||||||
|
|
||||||
A Request head too large to parse is only checked on
|
|
||||||
httparse::Status::Partial condition.
|
|
||||||
*/
|
|
||||||
if this.payload.is_none() {
|
if this.payload.is_none() {
|
||||||
/*
|
// When dispatcher has a payload the responsibility of wake up it would be shift
|
||||||
When dispatcher has a payload the responsibility of
|
// to h1::payload::Payload.
|
||||||
wake up it would be shift to h1::payload::Payload.
|
//
|
||||||
|
// Reason:
|
||||||
Reason:
|
// Self wake up when there is payload would waste poll and/or result in
|
||||||
Self wake up when there is payload would waste poll
|
// over read.
|
||||||
and/or result in over read.
|
//
|
||||||
|
// Case:
|
||||||
Case:
|
// When payload is (partial) dropped by user there is no need to do
|
||||||
When payload is (partial) dropped by user there is
|
// read anymore. At this case read_buf could always remain beyond
|
||||||
no need to do read anymore.
|
// MAX_BUFFER_SIZE and self wake up would be busy poll dispatcher and
|
||||||
At this case read_buf could always remain beyond
|
// waste resources.
|
||||||
MAX_BUFFER_SIZE and self wake up would be busy poll
|
|
||||||
dispatcher and waste resource.
|
|
||||||
|
|
||||||
*/
|
|
||||||
cx.waker().wake_by_ref();
|
cx.waker().wake_by_ref();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -924,7 +940,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
match this.inner.project() {
|
match this.inner.project() {
|
||||||
DispatcherStateProj::Normal(mut inner) => {
|
DispatcherStateProj::Normal { mut inner } => {
|
||||||
inner.as_mut().poll_keepalive(cx)?;
|
inner.as_mut().poll_keepalive(cx)?;
|
||||||
|
|
||||||
if inner.flags.contains(Flags::SHUTDOWN) {
|
if inner.flags.contains(Flags::SHUTDOWN) {
|
||||||
|
@ -964,7 +980,7 @@ where
|
||||||
self.as_mut()
|
self.as_mut()
|
||||||
.project()
|
.project()
|
||||||
.inner
|
.inner
|
||||||
.set(DispatcherState::Upgrade(upgrade));
|
.set(DispatcherState::Upgrade { fut: upgrade });
|
||||||
return self.poll(cx);
|
return self.poll(cx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1016,8 +1032,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DispatcherStateProj::Upgrade(fut) => fut.poll(cx).map_err(|e| {
|
DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| {
|
||||||
error!("Upgrade handler error: {}", e);
|
error!("Upgrade handler error: {}", err);
|
||||||
DispatchError::Upgrade
|
DispatchError::Upgrade
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -1058,14 +1074,12 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ok_service(
|
fn ok_service(
|
||||||
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
|
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
|
||||||
{
|
|
||||||
fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok())))
|
fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok())))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn echo_path_service(
|
fn echo_path_service(
|
||||||
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
|
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
|
||||||
{
|
|
||||||
fn_service(|req: Request| {
|
fn_service(|req: Request| {
|
||||||
let path = req.path().as_bytes();
|
let path = req.path().as_bytes();
|
||||||
ready(Ok::<_, Error>(
|
ready(Ok::<_, Error>(
|
||||||
|
@ -1074,8 +1088,8 @@ mod tests {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn echo_payload_service(
|
fn echo_payload_service() -> impl Service<Request, Response = Response<Bytes>, Error = Error>
|
||||||
) -> impl Service<Request, Response = Response<Bytes>, Error = Error> {
|
{
|
||||||
fn_service(|mut req: Request| {
|
fn_service(|mut req: Request| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
use futures_util::stream::StreamExt as _;
|
use futures_util::stream::StreamExt as _;
|
||||||
|
@ -1100,10 +1114,10 @@ mod tests {
|
||||||
|
|
||||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
buf,
|
buf,
|
||||||
ServiceConfig::default(),
|
|
||||||
services,
|
services,
|
||||||
OnConnectData::default(),
|
ServiceConfig::default(),
|
||||||
None,
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
actix_rt::pin!(h1);
|
actix_rt::pin!(h1);
|
||||||
|
@ -1113,7 +1127,7 @@ mod tests {
|
||||||
Poll::Ready(res) => assert!(res.is_err()),
|
Poll::Ready(res) => assert!(res.is_err()),
|
||||||
}
|
}
|
||||||
|
|
||||||
if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() {
|
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() {
|
||||||
assert!(inner.flags.contains(Flags::READ_DISCONNECT));
|
assert!(inner.flags.contains(Flags::READ_DISCONNECT));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&inner.project().io.take().unwrap().write_buf[..26],
|
&inner.project().io.take().unwrap().write_buf[..26],
|
||||||
|
@ -1140,15 +1154,15 @@ mod tests {
|
||||||
|
|
||||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
buf,
|
buf,
|
||||||
cfg,
|
|
||||||
services,
|
services,
|
||||||
OnConnectData::default(),
|
cfg,
|
||||||
None,
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
actix_rt::pin!(h1);
|
actix_rt::pin!(h1);
|
||||||
|
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
match h1.as_mut().poll(cx) {
|
match h1.as_mut().poll(cx) {
|
||||||
Poll::Pending => panic!("first poll should not be pending"),
|
Poll::Pending => panic!("first poll should not be pending"),
|
||||||
|
@ -1158,7 +1172,7 @@ mod tests {
|
||||||
// polls: initial => shutdown
|
// polls: initial => shutdown
|
||||||
assert_eq!(h1.poll_count, 2);
|
assert_eq!(h1.poll_count, 2);
|
||||||
|
|
||||||
if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() {
|
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() {
|
||||||
let res = &mut inner.project().io.take().unwrap().write_buf[..];
|
let res = &mut inner.project().io.take().unwrap().write_buf[..];
|
||||||
stabilize_date_header(res);
|
stabilize_date_header(res);
|
||||||
|
|
||||||
|
@ -1194,15 +1208,15 @@ mod tests {
|
||||||
|
|
||||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
buf,
|
buf,
|
||||||
cfg,
|
|
||||||
services,
|
services,
|
||||||
OnConnectData::default(),
|
cfg,
|
||||||
None,
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
actix_rt::pin!(h1);
|
actix_rt::pin!(h1);
|
||||||
|
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
match h1.as_mut().poll(cx) {
|
match h1.as_mut().poll(cx) {
|
||||||
Poll::Pending => panic!("first poll should not be pending"),
|
Poll::Pending => panic!("first poll should not be pending"),
|
||||||
|
@ -1212,7 +1226,7 @@ mod tests {
|
||||||
// polls: initial => shutdown
|
// polls: initial => shutdown
|
||||||
assert_eq!(h1.poll_count, 1);
|
assert_eq!(h1.poll_count, 1);
|
||||||
|
|
||||||
if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() {
|
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() {
|
||||||
let res = &mut inner.project().io.take().unwrap().write_buf[..];
|
let res = &mut inner.project().io.take().unwrap().write_buf[..];
|
||||||
stabilize_date_header(res);
|
stabilize_date_header(res);
|
||||||
|
|
||||||
|
@ -1244,10 +1258,10 @@ mod tests {
|
||||||
|
|
||||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
buf.clone(),
|
buf.clone(),
|
||||||
cfg,
|
|
||||||
services,
|
services,
|
||||||
OnConnectData::default(),
|
cfg,
|
||||||
None,
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
buf.extend_read_buf(
|
buf.extend_read_buf(
|
||||||
|
@ -1262,13 +1276,13 @@ mod tests {
|
||||||
actix_rt::pin!(h1);
|
actix_rt::pin!(h1);
|
||||||
|
|
||||||
assert!(h1.as_mut().poll(cx).is_pending());
|
assert!(h1.as_mut().poll(cx).is_pending());
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
// polls: manual
|
// polls: manual
|
||||||
assert_eq!(h1.poll_count, 1);
|
assert_eq!(h1.poll_count, 1);
|
||||||
eprintln!("poll count: {}", h1.poll_count);
|
eprintln!("poll count: {}", h1.poll_count);
|
||||||
|
|
||||||
if let DispatcherState::Normal(ref inner) = h1.inner {
|
if let DispatcherState::Normal { ref inner } = h1.inner {
|
||||||
let io = inner.io.as_ref().unwrap();
|
let io = inner.io.as_ref().unwrap();
|
||||||
let res = &io.write_buf()[..];
|
let res = &io.write_buf()[..];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1283,7 +1297,7 @@ mod tests {
|
||||||
// polls: manual manual shutdown
|
// polls: manual manual shutdown
|
||||||
assert_eq!(h1.poll_count, 3);
|
assert_eq!(h1.poll_count, 3);
|
||||||
|
|
||||||
if let DispatcherState::Normal(ref inner) = h1.inner {
|
if let DispatcherState::Normal { ref inner } = h1.inner {
|
||||||
let io = inner.io.as_ref().unwrap();
|
let io = inner.io.as_ref().unwrap();
|
||||||
let mut res = (&io.write_buf()[..]).to_owned();
|
let mut res = (&io.write_buf()[..]).to_owned();
|
||||||
stabilize_date_header(&mut res);
|
stabilize_date_header(&mut res);
|
||||||
|
@ -1316,10 +1330,10 @@ mod tests {
|
||||||
|
|
||||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||||
buf.clone(),
|
buf.clone(),
|
||||||
cfg,
|
|
||||||
services,
|
services,
|
||||||
OnConnectData::default(),
|
cfg,
|
||||||
None,
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
buf.extend_read_buf(
|
buf.extend_read_buf(
|
||||||
|
@ -1334,12 +1348,12 @@ mod tests {
|
||||||
actix_rt::pin!(h1);
|
actix_rt::pin!(h1);
|
||||||
|
|
||||||
assert!(h1.as_mut().poll(cx).is_ready());
|
assert!(h1.as_mut().poll(cx).is_ready());
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
|
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||||
|
|
||||||
// polls: manual shutdown
|
// polls: manual shutdown
|
||||||
assert_eq!(h1.poll_count, 2);
|
assert_eq!(h1.poll_count, 2);
|
||||||
|
|
||||||
if let DispatcherState::Normal(ref inner) = h1.inner {
|
if let DispatcherState::Normal { ref inner } = h1.inner {
|
||||||
let io = inner.io.as_ref().unwrap();
|
let io = inner.io.as_ref().unwrap();
|
||||||
let mut res = (&io.write_buf()[..]).to_owned();
|
let mut res = (&io.write_buf()[..]).to_owned();
|
||||||
stabilize_date_header(&mut res);
|
stabilize_date_header(&mut res);
|
||||||
|
@ -1393,10 +1407,10 @@ mod tests {
|
||||||
|
|
||||||
let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new(
|
let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new(
|
||||||
buf.clone(),
|
buf.clone(),
|
||||||
cfg,
|
|
||||||
services,
|
services,
|
||||||
OnConnectData::default(),
|
cfg,
|
||||||
None,
|
None,
|
||||||
|
OnConnectData::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
buf.extend_read_buf(
|
buf.extend_read_buf(
|
||||||
|
@ -1411,7 +1425,7 @@ mod tests {
|
||||||
actix_rt::pin!(h1);
|
actix_rt::pin!(h1);
|
||||||
|
|
||||||
assert!(h1.as_mut().poll(cx).is_ready());
|
assert!(h1.as_mut().poll(cx).is_ready());
|
||||||
assert!(matches!(&h1.inner, DispatcherState::Upgrade(_)));
|
assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. }));
|
||||||
|
|
||||||
// polls: manual shutdown
|
// polls: manual shutdown
|
||||||
assert_eq!(h1.poll_count, 2);
|
assert_eq!(h1.poll_count, 2);
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
use std::io::Write;
|
use std::{
|
||||||
use std::marker::PhantomData;
|
cmp,
|
||||||
use std::ptr::copy_nonoverlapping;
|
io::{self, Write as _},
|
||||||
use std::slice::from_raw_parts_mut;
|
marker::PhantomData,
|
||||||
use std::{cmp, io};
|
ptr::copy_nonoverlapping,
|
||||||
|
slice::from_raw_parts_mut,
|
||||||
|
};
|
||||||
|
|
||||||
use bytes::{BufMut, BytesMut};
|
use bytes::{BufMut, BytesMut};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::BodySize,
|
body::BodySize,
|
||||||
config::ServiceConfig,
|
header::{
|
||||||
header::{map::Value, HeaderMap, HeaderName},
|
map::Value, HeaderMap, HeaderName, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
|
||||||
header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
|
},
|
||||||
helpers,
|
helpers, ConnectionType, RequestHeadType, Response, ServiceConfig, StatusCode, Version,
|
||||||
message::{ConnectionType, RequestHeadType},
|
|
||||||
Response, StatusCode, Version,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const AVERAGE_HEADER_SIZE: usize = 30;
|
const AVERAGE_HEADER_SIZE: usize = 30;
|
||||||
|
@ -103,9 +103,7 @@ pub(crate) trait MessageType: Sized {
|
||||||
dst.put_slice(b"\r\n");
|
dst.put_slice(b"\r\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BodySize::Sized(0) if camel_case => {
|
BodySize::Sized(0) if camel_case => dst.put_slice(b"\r\nContent-Length: 0\r\n"),
|
||||||
dst.put_slice(b"\r\nContent-Length: 0\r\n")
|
|
||||||
}
|
|
||||||
BodySize::Sized(0) => dst.put_slice(b"\r\ncontent-length: 0\r\n"),
|
BodySize::Sized(0) => dst.put_slice(b"\r\ncontent-length: 0\r\n"),
|
||||||
BodySize::Sized(len) => helpers::write_content_length(len, dst),
|
BodySize::Sized(len) => helpers::write_content_length(len, dst),
|
||||||
BodySize::None => dst.put_slice(b"\r\n"),
|
BodySize::None => dst.put_slice(b"\r\n"),
|
||||||
|
@ -307,11 +305,7 @@ impl MessageType for RequestHeadType {
|
||||||
Version::HTTP_11 => "HTTP/1.1",
|
Version::HTTP_11 => "HTTP/1.1",
|
||||||
Version::HTTP_2 => "HTTP/2.0",
|
Version::HTTP_2 => "HTTP/2.0",
|
||||||
Version::HTTP_3 => "HTTP/3.0",
|
Version::HTTP_3 => "HTTP/3.0",
|
||||||
_ =>
|
_ => return Err(io::Error::new(io::ErrorKind::Other, "unsupported version")),
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"unsupported version"
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||||
|
@ -568,8 +562,7 @@ mod tests {
|
||||||
ConnectionType::Close,
|
ConnectionType::Close,
|
||||||
&ServiceConfig::default(),
|
&ServiceConfig::default(),
|
||||||
);
|
);
|
||||||
let data =
|
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||||
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
|
||||||
|
|
||||||
assert!(data.contains("Content-Length: 0\r\n"));
|
assert!(data.contains("Content-Length: 0\r\n"));
|
||||||
assert!(data.contains("Connection: close\r\n"));
|
assert!(data.contains("Connection: close\r\n"));
|
||||||
|
@ -583,8 +576,7 @@ mod tests {
|
||||||
ConnectionType::KeepAlive,
|
ConnectionType::KeepAlive,
|
||||||
&ServiceConfig::default(),
|
&ServiceConfig::default(),
|
||||||
);
|
);
|
||||||
let data =
|
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||||
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
|
||||||
assert!(data.contains("Transfer-Encoding: chunked\r\n"));
|
assert!(data.contains("Transfer-Encoding: chunked\r\n"));
|
||||||
assert!(data.contains("Content-Type: plain/text\r\n"));
|
assert!(data.contains("Content-Type: plain/text\r\n"));
|
||||||
assert!(data.contains("Date: date\r\n"));
|
assert!(data.contains("Date: date\r\n"));
|
||||||
|
@ -605,8 +597,7 @@ mod tests {
|
||||||
ConnectionType::KeepAlive,
|
ConnectionType::KeepAlive,
|
||||||
&ServiceConfig::default(),
|
&ServiceConfig::default(),
|
||||||
);
|
);
|
||||||
let data =
|
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||||
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
|
||||||
assert!(data.contains("transfer-encoding: chunked\r\n"));
|
assert!(data.contains("transfer-encoding: chunked\r\n"));
|
||||||
assert!(data.contains("content-type: xml\r\n"));
|
assert!(data.contains("content-type: xml\r\n"));
|
||||||
assert!(data.contains("content-type: plain/text\r\n"));
|
assert!(data.contains("content-type: plain/text\r\n"));
|
||||||
|
@ -639,8 +630,7 @@ mod tests {
|
||||||
ConnectionType::Close,
|
ConnectionType::Close,
|
||||||
&ServiceConfig::default(),
|
&ServiceConfig::default(),
|
||||||
);
|
);
|
||||||
let data =
|
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||||
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
|
||||||
assert!(data.contains("content-length: 0\r\n"));
|
assert!(data.contains("content-length: 0\r\n"));
|
||||||
assert!(data.contains("connection: close\r\n"));
|
assert!(data.contains("connection: close\r\n"));
|
||||||
assert!(data.contains("authorization: another authorization\r\n"));
|
assert!(data.contains("authorization: another authorization\r\n"));
|
||||||
|
@ -663,8 +653,7 @@ mod tests {
|
||||||
ConnectionType::Upgrade,
|
ConnectionType::Upgrade,
|
||||||
&ServiceConfig::default(),
|
&ServiceConfig::default(),
|
||||||
);
|
);
|
||||||
let data =
|
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||||
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
|
||||||
assert!(!data.contains("content-length: 0\r\n"));
|
assert!(!data.contains("content-length: 0\r\n"));
|
||||||
assert!(!data.contains("transfer-encoding: chunked\r\n"));
|
assert!(!data.contains("transfer-encoding: chunked\r\n"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use actix_service::{Service, ServiceFactory};
|
use actix_service::{Service, ServiceFactory};
|
||||||
use actix_utils::future::{ready, Ready};
|
use actix_utils::future::{ready, Ready};
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::{Error, Request};
|
||||||
use crate::request::Request;
|
|
||||||
|
|
||||||
pub struct ExpectHandler;
|
pub struct ExpectHandler;
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ pub(crate) fn reserve_readbuf(src: &mut BytesMut) {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::request::Request;
|
use crate::Request;
|
||||||
|
|
||||||
impl Message<Request> {
|
impl Message<Request> {
|
||||||
pub fn message(self) -> Request {
|
pub fn message(self) -> Request {
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
//! Payload stream
|
//! Payload stream
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::VecDeque;
|
use std::{
|
||||||
use std::pin::Pin;
|
cell::RefCell,
|
||||||
use std::rc::{Rc, Weak};
|
collections::VecDeque,
|
||||||
use std::task::{Context, Poll, Waker};
|
pin::Pin,
|
||||||
|
rc::{Rc, Weak},
|
||||||
|
task::{Context, Poll, Waker},
|
||||||
|
};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
|
@ -22,39 +25,32 @@ pub enum PayloadStatus {
|
||||||
|
|
||||||
/// Buffered stream of bytes chunks
|
/// Buffered stream of bytes chunks
|
||||||
///
|
///
|
||||||
/// Payload stores chunks in a vector. First chunk can be received with
|
/// Payload stores chunks in a vector. First chunk can be received with `poll_next`. Payload does
|
||||||
/// `.readany()` method. Payload stream is not thread safe. Payload does not
|
/// not notify current task when new data is available.
|
||||||
/// notify current task when new data is available.
|
|
||||||
///
|
///
|
||||||
/// Payload stream can be used as `Response` body stream.
|
/// Payload can be used as `Response` body stream.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Payload {
|
pub struct Payload {
|
||||||
inner: Rc<RefCell<Inner>>,
|
inner: Rc<RefCell<Inner>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Payload {
|
impl Payload {
|
||||||
/// Create payload stream.
|
/// Creates a payload stream.
|
||||||
///
|
///
|
||||||
/// This method construct two objects responsible for bytes stream
|
/// This method construct two objects responsible for bytes stream generation:
|
||||||
/// generation.
|
/// - `PayloadSender` - *Sender* side of the stream
|
||||||
///
|
/// - `Payload` - *Receiver* side of the stream
|
||||||
/// * `PayloadSender` - *Sender* side of the stream
|
|
||||||
///
|
|
||||||
/// * `Payload` - *Receiver* side of the stream
|
|
||||||
pub fn create(eof: bool) -> (PayloadSender, Payload) {
|
pub fn create(eof: bool) -> (PayloadSender, Payload) {
|
||||||
let shared = Rc::new(RefCell::new(Inner::new(eof)));
|
let shared = Rc::new(RefCell::new(Inner::new(eof)));
|
||||||
|
|
||||||
(
|
(
|
||||||
PayloadSender {
|
PayloadSender::new(Rc::downgrade(&shared)),
|
||||||
inner: Rc::downgrade(&shared),
|
|
||||||
},
|
|
||||||
Payload { inner: shared },
|
Payload { inner: shared },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create empty payload
|
/// Creates an empty payload.
|
||||||
#[doc(hidden)]
|
pub(crate) fn empty() -> Payload {
|
||||||
pub fn empty() -> Payload {
|
|
||||||
Payload {
|
Payload {
|
||||||
inner: Rc::new(RefCell::new(Inner::new(true))),
|
inner: Rc::new(RefCell::new(Inner::new(true))),
|
||||||
}
|
}
|
||||||
|
@ -77,14 +73,6 @@ impl Payload {
|
||||||
pub fn unread_data(&mut self, data: Bytes) {
|
pub fn unread_data(&mut self, data: Bytes) {
|
||||||
self.inner.borrow_mut().unread_data(data);
|
self.inner.borrow_mut().unread_data(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn readany(
|
|
||||||
&mut self,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Result<Bytes, PayloadError>>> {
|
|
||||||
self.inner.borrow_mut().readany(cx)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for Payload {
|
impl Stream for Payload {
|
||||||
|
@ -94,7 +82,7 @@ impl Stream for Payload {
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Option<Result<Bytes, PayloadError>>> {
|
) -> Poll<Option<Result<Bytes, PayloadError>>> {
|
||||||
self.inner.borrow_mut().readany(cx)
|
Pin::new(&mut *self.inner.borrow_mut()).poll_next(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +92,10 @@ pub struct PayloadSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PayloadSender {
|
impl PayloadSender {
|
||||||
|
fn new(inner: Weak<RefCell<Inner>>) -> Self {
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_error(&mut self, err: PayloadError) {
|
pub fn set_error(&mut self, err: PayloadError) {
|
||||||
if let Some(shared) = self.inner.upgrade() {
|
if let Some(shared) = self.inner.upgrade() {
|
||||||
|
@ -227,8 +219,8 @@ impl Inner {
|
||||||
self.len
|
self.len
|
||||||
}
|
}
|
||||||
|
|
||||||
fn readany(
|
fn poll_next(
|
||||||
&mut self,
|
mut self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Option<Result<Bytes, PayloadError>>> {
|
) -> Poll<Option<Result<Bytes, PayloadError>>> {
|
||||||
if let Some(data) = self.items.pop_front() {
|
if let Some(data) = self.items.pop_front() {
|
||||||
|
@ -260,8 +252,18 @@ impl Inner {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||||
|
|
||||||
use actix_utils::future::poll_fn;
|
use actix_utils::future::poll_fn;
|
||||||
|
use static_assertions::{assert_impl_all, assert_not_impl_any};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
assert_impl_all!(Payload: Unpin);
|
||||||
|
assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe);
|
||||||
|
|
||||||
|
assert_impl_all!(Inner: Unpin, Send, Sync);
|
||||||
|
assert_not_impl_any!(Inner: UnwindSafe, RefUnwindSafe);
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_unread_data() {
|
async fn test_unread_data() {
|
||||||
|
@ -273,7 +275,10 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Bytes::from("data"),
|
Bytes::from("data"),
|
||||||
poll_fn(|cx| payload.readany(cx)).await.unwrap().unwrap()
|
poll_fn(|cx| Pin::new(&mut payload).poll_next(cx))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,8 +266,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> ServiceFactory<(T, Option<net::SocketAddr>)>
|
impl<T, S, B, X, U> ServiceFactory<(T, Option<net::SocketAddr>)> for H1Service<T, S, B, X, U>
|
||||||
for H1Service<T, S, B, X, U>
|
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin + 'static,
|
T: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
|
|
||||||
|
@ -310,9 +309,9 @@ where
|
||||||
|
|
||||||
let upgrade = match upgrade {
|
let upgrade = match upgrade {
|
||||||
Some(upgrade) => {
|
Some(upgrade) => {
|
||||||
let upgrade = upgrade.await.map_err(|e| {
|
let upgrade = upgrade
|
||||||
log::error!("Init http upgrade service error: {:?}", e)
|
.await
|
||||||
})?;
|
.map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?;
|
||||||
Some(upgrade)
|
Some(upgrade)
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
|
@ -336,8 +335,7 @@ where
|
||||||
/// `Service` implementation for HTTP/1 transport
|
/// `Service` implementation for HTTP/1 transport
|
||||||
pub type H1ServiceHandler<T, S, B, X, U> = HttpServiceHandler<T, S, B, X, U>;
|
pub type H1ServiceHandler<T, S, B, X, U> = HttpServiceHandler<T, S, B, X, U>;
|
||||||
|
|
||||||
impl<T, S, B, X, U> Service<(T, Option<net::SocketAddr>)>
|
impl<T, S, B, X, U> Service<(T, Option<net::SocketAddr>)> for HttpServiceHandler<T, S, B, X, U>
|
||||||
for HttpServiceHandler<T, S, B, X, U>
|
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
|
||||||
|
@ -358,22 +356,14 @@ where
|
||||||
type Future = Dispatcher<T, S, B, X, U>;
|
type Future = Dispatcher<T, S, B, X, U>;
|
||||||
|
|
||||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
self._poll_ready(cx).map_err(|e| {
|
self._poll_ready(cx).map_err(|err| {
|
||||||
log::error!("HTTP/1 service readiness error: {:?}", e);
|
log::error!("HTTP/1 service readiness error: {:?}", err);
|
||||||
DispatchError::Service(e)
|
DispatchError::Service(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&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 conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||||
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
Dispatcher::new(io, self.flow.clone(), self.cfg.clone(), addr, conn_data)
|
||||||
|
|
||||||
Dispatcher::new(
|
|
||||||
io,
|
|
||||||
self.cfg.clone(),
|
|
||||||
self.flow.clone(),
|
|
||||||
on_connect_data,
|
|
||||||
addr,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,7 @@ use actix_codec::Framed;
|
||||||
use actix_service::{Service, ServiceFactory};
|
use actix_service::{Service, ServiceFactory};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::{h1::Codec, Error, Request};
|
||||||
use crate::h1::Codec;
|
|
||||||
use crate::request::Request;
|
|
||||||
|
|
||||||
pub struct UpgradeHandler;
|
pub struct UpgradeHandler;
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,8 @@ use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BodySize, MessageBody},
|
body::{BodySize, MessageBody},
|
||||||
error::Error,
|
|
||||||
h1::{Codec, Message},
|
h1::{Codec, Message},
|
||||||
response::Response,
|
Error, Response,
|
||||||
};
|
};
|
||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
|
@ -46,7 +45,7 @@ where
|
||||||
impl<T, B> Future for SendResponse<T, B>
|
impl<T, B> Future for SendResponse<T, B>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
B: MessageBody + Unpin,
|
B: MessageBody,
|
||||||
B::Error: Into<Error>,
|
B::Error: Into<Error>,
|
||||||
{
|
{
|
||||||
type Output = Result<Framed<T, Codec>, Error>;
|
type Output = Result<Framed<T, Codec>, Error>;
|
||||||
|
@ -70,27 +69,24 @@ where
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.is_write_buf_full()
|
.is_write_buf_full()
|
||||||
{
|
{
|
||||||
let next =
|
let next = match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx) {
|
||||||
match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx) {
|
Poll::Ready(Some(Ok(item))) => Poll::Ready(Some(item)),
|
||||||
Poll::Ready(Some(Ok(item))) => Poll::Ready(Some(item)),
|
Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())),
|
||||||
Poll::Ready(Some(Err(err))) => {
|
Poll::Ready(None) => Poll::Ready(None),
|
||||||
return Poll::Ready(Err(err.into()))
|
Poll::Pending => Poll::Pending,
|
||||||
}
|
};
|
||||||
Poll::Ready(None) => Poll::Ready(None),
|
|
||||||
Poll::Pending => Poll::Pending,
|
|
||||||
};
|
|
||||||
|
|
||||||
match next {
|
match next {
|
||||||
Poll::Ready(item) => {
|
Poll::Ready(item) => {
|
||||||
// body is done when item is None
|
// body is done when item is None
|
||||||
body_done = item.is_none();
|
body_done = item.is_none();
|
||||||
if body_done {
|
if body_done {
|
||||||
let _ = this.body.take();
|
this.body.set(None);
|
||||||
}
|
}
|
||||||
let framed = this.framed.as_mut().as_pin_mut().unwrap();
|
let framed = this.framed.as_mut().as_pin_mut().unwrap();
|
||||||
framed.write(Message::Chunk(item)).map_err(|err| {
|
framed
|
||||||
Error::new_send_response().with_cause(err)
|
.write(Message::Chunk(item))
|
||||||
})?;
|
.map_err(|err| Error::new_send_response().with_cause(err))?;
|
||||||
}
|
}
|
||||||
Poll::Pending => body_ready = false,
|
Poll::Pending => body_ready = false,
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,15 +19,15 @@ use h2::{
|
||||||
server::{Connection, SendResponse},
|
server::{Connection, SendResponse},
|
||||||
Ping, PingPong,
|
Ping, PingPong,
|
||||||
};
|
};
|
||||||
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
|
|
||||||
use log::{error, trace};
|
use log::{error, trace};
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BodySize, BoxBody, MessageBody},
|
body::{BodySize, BoxBody, MessageBody},
|
||||||
config::ServiceConfig,
|
config::ServiceConfig,
|
||||||
|
header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
|
||||||
service::HttpFlow,
|
service::HttpFlow,
|
||||||
OnConnectData, Payload, Request, Response, ResponseHead,
|
Extensions, OnConnectData, Payload, Request, Response, ResponseHead,
|
||||||
};
|
};
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 16_384;
|
const CHUNK_SIZE: usize = 16_384;
|
||||||
|
@ -37,7 +37,7 @@ pin_project! {
|
||||||
pub struct Dispatcher<T, S, B, X, U> {
|
pub struct Dispatcher<T, S, B, X, U> {
|
||||||
flow: Rc<HttpFlow<S, X, U>>,
|
flow: Rc<HttpFlow<S, X, U>>,
|
||||||
connection: Connection<T, Bytes>,
|
connection: Connection<T, Bytes>,
|
||||||
on_connect_data: OnConnectData,
|
conn_data: Option<Rc<Extensions>>,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
ping_pong: Option<H2PingPong>,
|
ping_pong: Option<H2PingPong>,
|
||||||
|
@ -50,11 +50,11 @@ where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
flow: Rc<HttpFlow<S, X, U>>,
|
|
||||||
mut conn: Connection<T, Bytes>,
|
mut conn: Connection<T, Bytes>,
|
||||||
on_connect_data: OnConnectData,
|
flow: Rc<HttpFlow<S, X, U>>,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
|
conn_data: OnConnectData,
|
||||||
timer: Option<Pin<Box<Sleep>>>,
|
timer: Option<Pin<Box<Sleep>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ping_pong = config.keep_alive().map(|dur| H2PingPong {
|
let ping_pong = config.keep_alive().map(|dur| H2PingPong {
|
||||||
|
@ -74,7 +74,7 @@ where
|
||||||
config,
|
config,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
connection: conn,
|
connection: conn,
|
||||||
on_connect_data,
|
conn_data: conn_data.0.map(Rc::new),
|
||||||
ping_pong,
|
ping_pong,
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
|
@ -108,8 +108,8 @@ where
|
||||||
match Pin::new(&mut this.connection).poll_accept(cx)? {
|
match Pin::new(&mut this.connection).poll_accept(cx)? {
|
||||||
Poll::Ready(Some((req, tx))) => {
|
Poll::Ready(Some((req, tx))) => {
|
||||||
let (parts, body) = req.into_parts();
|
let (parts, body) = req.into_parts();
|
||||||
let pl = crate::h2::Payload::new(body);
|
let payload = crate::h2::Payload::new(body);
|
||||||
let pl = Payload::<crate::payload::PayloadStream>::H2(pl);
|
let pl = Payload::H2 { payload };
|
||||||
let mut req = Request::with_payload(pl);
|
let mut req = Request::with_payload(pl);
|
||||||
|
|
||||||
let head = req.head_mut();
|
let head = req.head_mut();
|
||||||
|
@ -119,8 +119,7 @@ where
|
||||||
head.headers = parts.headers.into();
|
head.headers = parts.headers.into();
|
||||||
head.peer_addr = this.peer_addr;
|
head.peer_addr = this.peer_addr;
|
||||||
|
|
||||||
// merge on_connect_ext data into request extensions
|
req.conn_data = this.conn_data.as_ref().map(Rc::clone);
|
||||||
this.on_connect_data.merge_into(&mut req);
|
|
||||||
|
|
||||||
let fut = this.flow.service.call(req);
|
let fut = this.flow.service.call(req);
|
||||||
let config = this.config.clone();
|
let config = this.config.clone();
|
||||||
|
@ -161,16 +160,11 @@ where
|
||||||
Poll::Ready(_) => {
|
Poll::Ready(_) => {
|
||||||
ping_pong.on_flight = false;
|
ping_pong.on_flight = false;
|
||||||
|
|
||||||
let dead_line =
|
let dead_line = this.config.keep_alive_expire().unwrap();
|
||||||
this.config.keep_alive_expire().unwrap();
|
|
||||||
ping_pong.timer.as_mut().reset(dead_line);
|
ping_pong.timer.as_mut().reset(dead_line);
|
||||||
}
|
}
|
||||||
Poll::Pending => {
|
Poll::Pending => {
|
||||||
return ping_pong
|
return ping_pong.timer.as_mut().poll(cx).map(|_| Ok(()))
|
||||||
.timer
|
|
||||||
.as_mut()
|
|
||||||
.poll(cx)
|
|
||||||
.map(|_| Ok(()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -223,25 +217,28 @@ where
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// poll response body and send chunks to client.
|
// poll response body and send chunks to client
|
||||||
actix_rt::pin!(body);
|
actix_rt::pin!(body);
|
||||||
|
|
||||||
while let Some(res) = poll_fn(|cx| body.as_mut().poll_next(cx)).await {
|
while let Some(res) = poll_fn(|cx| body.as_mut().poll_next(cx)).await {
|
||||||
let mut chunk = res.map_err(|err| DispatchError::ResponseBody(err.into()))?;
|
let mut chunk = res.map_err(|err| DispatchError::ResponseBody(err.into()))?;
|
||||||
|
|
||||||
'send: loop {
|
'send: loop {
|
||||||
|
let chunk_size = cmp::min(chunk.len(), CHUNK_SIZE);
|
||||||
|
|
||||||
// reserve enough space and wait for stream ready.
|
// reserve enough space and wait for stream ready.
|
||||||
stream.reserve_capacity(cmp::min(chunk.len(), CHUNK_SIZE));
|
stream.reserve_capacity(chunk_size);
|
||||||
|
|
||||||
match poll_fn(|cx| stream.poll_capacity(cx)).await {
|
match poll_fn(|cx| stream.poll_capacity(cx)).await {
|
||||||
// No capacity left. drop body and return.
|
// No capacity left. drop body and return.
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
Some(res) => {
|
|
||||||
// Split chuck to writeable size and send to client.
|
|
||||||
let cap = res.map_err(DispatchError::SendData)?;
|
|
||||||
|
|
||||||
|
Some(Err(err)) => return Err(DispatchError::SendData(err)),
|
||||||
|
|
||||||
|
Some(Ok(cap)) => {
|
||||||
|
// split chunk to writeable size and send to client
|
||||||
let len = chunk.len();
|
let len = chunk.len();
|
||||||
let bytes = chunk.split_to(cmp::min(cap, len));
|
let bytes = chunk.split_to(cmp::min(len, cap));
|
||||||
|
|
||||||
stream
|
stream
|
||||||
.send_data(bytes, false)
|
.send_data(bytes, false)
|
||||||
|
@ -291,9 +288,11 @@ fn prepare_response(
|
||||||
let _ = match size {
|
let _ = match size {
|
||||||
BodySize::None | BodySize::Stream => None,
|
BodySize::None | BodySize::Stream => None,
|
||||||
|
|
||||||
BodySize::Sized(0) => res
|
BodySize::Sized(0) => {
|
||||||
.headers_mut()
|
#[allow(clippy::declare_interior_mutable_const)]
|
||||||
.insert(CONTENT_LENGTH, HeaderValue::from_static("0")),
|
const HV_ZERO: HeaderValue = HeaderValue::from_static("0");
|
||||||
|
res.headers_mut().insert(CONTENT_LENGTH, HV_ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
BodySize::Sized(len) => {
|
BodySize::Sized(len) => {
|
||||||
let mut buf = itoa::Buffer::new();
|
let mut buf = itoa::Buffer::new();
|
||||||
|
|
|
@ -40,10 +40,7 @@ impl Payload {
|
||||||
impl Stream for Payload {
|
impl Stream for Payload {
|
||||||
type Item = Result<Bytes, PayloadError>;
|
type Item = Result<Bytes, PayloadError>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Self::Item>> {
|
|
||||||
let this = self.get_mut();
|
let this = self.get_mut();
|
||||||
|
|
||||||
match ready!(Pin::new(&mut this.stream).poll_data(cx)) {
|
match ready!(Pin::new(&mut this.stream).poll_data(cx)) {
|
||||||
|
@ -101,3 +98,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||||
|
|
||||||
|
use static_assertions::assert_impl_all;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
assert_impl_all!(Payload: Unpin, Send, Sync, UnwindSafe, RefUnwindSafe);
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
future::Future,
|
future::Future,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
net,
|
mem, net,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
|
@ -10,8 +10,7 @@ use std::{
|
||||||
use actix_codec::{AsyncRead, AsyncWrite};
|
use actix_codec::{AsyncRead, AsyncWrite};
|
||||||
use actix_rt::net::TcpStream;
|
use actix_rt::net::TcpStream;
|
||||||
use actix_service::{
|
use actix_service::{
|
||||||
fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory,
|
fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _,
|
||||||
ServiceFactoryExt as _,
|
|
||||||
};
|
};
|
||||||
use actix_utils::future::ready;
|
use actix_utils::future::ready;
|
||||||
use futures_core::{future::LocalBoxFuture, ready};
|
use futures_core::{future::LocalBoxFuture, ready};
|
||||||
|
@ -271,16 +270,15 @@ where
|
||||||
type Future = H2ServiceHandlerResponse<T, S, B>;
|
type Future = H2ServiceHandlerResponse<T, S, B>;
|
||||||
|
|
||||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
self.flow.service.poll_ready(cx).map_err(|e| {
|
self.flow.service.poll_ready(cx).map_err(|err| {
|
||||||
let e = e.into();
|
let err = err.into();
|
||||||
error!("Service readiness error: {:?}", e);
|
error!("Service readiness error: {:?}", err);
|
||||||
DispatchError::Service(e)
|
DispatchError::Service(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&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());
|
|
||||||
|
|
||||||
H2ServiceHandlerResponse {
|
H2ServiceHandlerResponse {
|
||||||
state: State::Handshake(
|
state: State::Handshake(
|
||||||
|
@ -299,7 +297,6 @@ where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
{
|
{
|
||||||
Incoming(Dispatcher<T, S, B, (), ()>),
|
|
||||||
Handshake(
|
Handshake(
|
||||||
Option<Rc<HttpFlow<S, (), ()>>>,
|
Option<Rc<HttpFlow<S, (), ()>>>,
|
||||||
Option<ServiceConfig>,
|
Option<ServiceConfig>,
|
||||||
|
@ -307,6 +304,7 @@ where
|
||||||
OnConnectData,
|
OnConnectData,
|
||||||
HandshakeWithTimeout<T>,
|
HandshakeWithTimeout<T>,
|
||||||
),
|
),
|
||||||
|
Established(Dispatcher<T, S, B, (), ()>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct H2ServiceHandlerResponse<T, S, B>
|
pub struct H2ServiceHandlerResponse<T, S, B>
|
||||||
|
@ -334,31 +332,35 @@ where
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
match self.state {
|
match self.state {
|
||||||
State::Incoming(ref mut disp) => Pin::new(disp).poll(cx),
|
|
||||||
State::Handshake(
|
State::Handshake(
|
||||||
ref mut srv,
|
ref mut srv,
|
||||||
ref mut config,
|
ref mut config,
|
||||||
ref peer_addr,
|
ref peer_addr,
|
||||||
ref mut on_connect_data,
|
ref mut conn_data,
|
||||||
ref mut handshake,
|
ref mut handshake,
|
||||||
) => match ready!(Pin::new(handshake).poll(cx)) {
|
) => match ready!(Pin::new(handshake).poll(cx)) {
|
||||||
Ok((conn, timer)) => {
|
Ok((conn, timer)) => {
|
||||||
let on_connect_data = std::mem::take(on_connect_data);
|
let on_connect_data = mem::take(conn_data);
|
||||||
self.state = State::Incoming(Dispatcher::new(
|
|
||||||
srv.take().unwrap(),
|
self.state = State::Established(Dispatcher::new(
|
||||||
conn,
|
conn,
|
||||||
on_connect_data,
|
srv.take().unwrap(),
|
||||||
config.take().unwrap(),
|
config.take().unwrap(),
|
||||||
*peer_addr,
|
*peer_addr,
|
||||||
|
on_connect_data,
|
||||||
timer,
|
timer,
|
||||||
));
|
));
|
||||||
|
|
||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
trace!("H2 handshake error: {}", err);
|
trace!("H2 handshake error: {}", err);
|
||||||
Poll::Ready(Err(err))
|
Poll::Ready(Err(err))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
State::Established(ref mut disp) => Pin::new(disp).poll(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use http::header::{HeaderName, InvalidHeaderName};
|
||||||
|
|
||||||
/// Sealed trait implemented for types that can be effectively borrowed as a [`HeaderValue`].
|
/// Sealed trait implemented for types that can be effectively borrowed as a [`HeaderValue`].
|
||||||
///
|
///
|
||||||
/// [`HeaderValue`]: crate::http::HeaderValue
|
/// [`HeaderValue`]: super::HeaderValue
|
||||||
pub trait AsHeaderName: Sealed {}
|
pub trait AsHeaderName: Sealed {}
|
||||||
|
|
||||||
pub struct Seal;
|
pub struct Seal;
|
||||||
|
@ -16,6 +16,7 @@ pub trait Sealed {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sealed for HeaderName {
|
impl Sealed for HeaderName {
|
||||||
|
#[inline]
|
||||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
Ok(Cow::Borrowed(self))
|
Ok(Cow::Borrowed(self))
|
||||||
}
|
}
|
||||||
|
@ -23,6 +24,7 @@ impl Sealed for HeaderName {
|
||||||
impl AsHeaderName for HeaderName {}
|
impl AsHeaderName for HeaderName {}
|
||||||
|
|
||||||
impl Sealed for &HeaderName {
|
impl Sealed for &HeaderName {
|
||||||
|
#[inline]
|
||||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
Ok(Cow::Borrowed(*self))
|
Ok(Cow::Borrowed(*self))
|
||||||
}
|
}
|
||||||
|
@ -30,6 +32,7 @@ impl Sealed for &HeaderName {
|
||||||
impl AsHeaderName for &HeaderName {}
|
impl AsHeaderName for &HeaderName {}
|
||||||
|
|
||||||
impl Sealed for &str {
|
impl Sealed for &str {
|
||||||
|
#[inline]
|
||||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
HeaderName::from_str(self).map(Cow::Owned)
|
HeaderName::from_str(self).map(Cow::Owned)
|
||||||
}
|
}
|
||||||
|
@ -37,6 +40,7 @@ impl Sealed for &str {
|
||||||
impl AsHeaderName for &str {}
|
impl AsHeaderName for &str {}
|
||||||
|
|
||||||
impl Sealed for String {
|
impl Sealed for String {
|
||||||
|
#[inline]
|
||||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
HeaderName::from_str(self).map(Cow::Owned)
|
HeaderName::from_str(self).map(Cow::Owned)
|
||||||
}
|
}
|
||||||
|
@ -44,6 +48,7 @@ impl Sealed for String {
|
||||||
impl AsHeaderName for String {}
|
impl AsHeaderName for String {}
|
||||||
|
|
||||||
impl Sealed for &String {
|
impl Sealed for &String {
|
||||||
|
#[inline]
|
||||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||||
HeaderName::from_str(self).map(Cow::Owned)
|
HeaderName::from_str(self).map(Cow::Owned)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
//! [`IntoHeaderPair`] trait and implementations.
|
//! [`TryIntoHeaderPair`] trait and implementations.
|
||||||
|
|
||||||
use std::convert::TryFrom as _;
|
use std::convert::TryFrom as _;
|
||||||
|
|
||||||
use http::{
|
use super::{
|
||||||
header::{HeaderName, InvalidHeaderName, InvalidHeaderValue},
|
Header, HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, TryIntoHeaderValue,
|
||||||
Error as HttpError, HeaderValue,
|
|
||||||
};
|
};
|
||||||
|
use crate::error::HttpError;
|
||||||
|
|
||||||
use super::{Header, IntoHeaderValue};
|
/// An interface for types that can be converted into a [`HeaderName`] + [`HeaderValue`] pair for
|
||||||
|
|
||||||
/// An interface for types that can be converted into a [`HeaderName`]/[`HeaderValue`] pair for
|
|
||||||
/// insertion into a [`HeaderMap`].
|
/// insertion into a [`HeaderMap`].
|
||||||
///
|
///
|
||||||
/// [`HeaderMap`]: crate::http::HeaderMap
|
/// [`HeaderMap`]: super::HeaderMap
|
||||||
pub trait IntoHeaderPair: Sized {
|
pub trait TryIntoHeaderPair: Sized {
|
||||||
type Error: Into<HttpError>;
|
type Error: Into<HttpError>;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>;
|
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -34,14 +32,14 @@ impl From<InvalidHeaderPart> for HttpError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (HeaderName, V)
|
impl<V> TryIntoHeaderPair for (HeaderName, V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
{
|
{
|
||||||
type Error = InvalidHeaderPart;
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
let (name, value) = self;
|
let (name, value) = self;
|
||||||
let value = value
|
let value = value
|
||||||
.try_into_value()
|
.try_into_value()
|
||||||
|
@ -50,14 +48,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (&HeaderName, V)
|
impl<V> TryIntoHeaderPair for (&HeaderName, V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
{
|
{
|
||||||
type Error = InvalidHeaderPart;
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
let (name, value) = self;
|
let (name, value) = self;
|
||||||
let value = value
|
let value = value
|
||||||
.try_into_value()
|
.try_into_value()
|
||||||
|
@ -66,14 +64,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (&[u8], V)
|
impl<V> TryIntoHeaderPair for (&[u8], V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
{
|
{
|
||||||
type Error = InvalidHeaderPart;
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
let (name, value) = self;
|
let (name, value) = self;
|
||||||
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
||||||
let value = value
|
let value = value
|
||||||
|
@ -83,14 +81,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (&str, V)
|
impl<V> TryIntoHeaderPair for (&str, V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
{
|
{
|
||||||
type Error = InvalidHeaderPart;
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
let (name, value) = self;
|
let (name, value) = self;
|
||||||
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
||||||
let value = value
|
let value = value
|
||||||
|
@ -100,23 +98,25 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> IntoHeaderPair for (String, V)
|
impl<V> TryIntoHeaderPair for (String, V)
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
V::Error: Into<InvalidHeaderValue>,
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
{
|
{
|
||||||
type Error = InvalidHeaderPart;
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
#[inline]
|
||||||
|
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
let (name, value) = self;
|
let (name, value) = self;
|
||||||
(name.as_str(), value).try_into_header_pair()
|
(name.as_str(), value).try_into_pair()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Header> IntoHeaderPair for T {
|
impl<T: Header> TryIntoHeaderPair for T {
|
||||||
type Error = <T as IntoHeaderValue>::Error;
|
type Error = <T as TryIntoHeaderValue>::Error;
|
||||||
|
|
||||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
#[inline]
|
||||||
|
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
Ok((T::name(), self.try_into_value()?))
|
Ok((T::name(), self.try_into_value()?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! [`IntoHeaderValue`] trait and implementations.
|
//! [`TryIntoHeaderValue`] trait and implementations.
|
||||||
|
|
||||||
use std::convert::TryFrom as _;
|
use std::convert::TryFrom as _;
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue};
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
|
|
||||||
/// An interface for types that can be converted into a [`HeaderValue`].
|
/// An interface for types that can be converted into a [`HeaderValue`].
|
||||||
pub trait IntoHeaderValue: Sized {
|
pub trait TryIntoHeaderValue: Sized {
|
||||||
/// The type returned in the event of a conversion error.
|
/// The type returned in the event of a conversion error.
|
||||||
type Error: Into<HttpError>;
|
type Error: Into<HttpError>;
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ pub trait IntoHeaderValue: Sized {
|
||||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error>;
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for HeaderValue {
|
impl TryIntoHeaderValue for HeaderValue {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -24,7 +24,7 @@ impl IntoHeaderValue for HeaderValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for &HeaderValue {
|
impl TryIntoHeaderValue for &HeaderValue {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -33,7 +33,7 @@ impl IntoHeaderValue for &HeaderValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for &str {
|
impl TryIntoHeaderValue for &str {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -42,7 +42,7 @@ impl IntoHeaderValue for &str {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for &[u8] {
|
impl TryIntoHeaderValue for &[u8] {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -51,7 +51,7 @@ impl IntoHeaderValue for &[u8] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for Bytes {
|
impl TryIntoHeaderValue for Bytes {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -60,7 +60,7 @@ impl IntoHeaderValue for Bytes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for Vec<u8> {
|
impl TryIntoHeaderValue for Vec<u8> {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -69,7 +69,7 @@ impl IntoHeaderValue for Vec<u8> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for String {
|
impl TryIntoHeaderValue for String {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -78,7 +78,7 @@ impl IntoHeaderValue for String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for usize {
|
impl TryIntoHeaderValue for usize {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -87,7 +87,7 @@ impl IntoHeaderValue for usize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for i64 {
|
impl TryIntoHeaderValue for i64 {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -96,7 +96,7 @@ impl IntoHeaderValue for i64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for u64 {
|
impl TryIntoHeaderValue for u64 {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -105,7 +105,7 @@ impl IntoHeaderValue for u64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for i32 {
|
impl TryIntoHeaderValue for i32 {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -114,7 +114,7 @@ impl IntoHeaderValue for i32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for u32 {
|
impl TryIntoHeaderValue for u32 {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -123,7 +123,7 @@ impl IntoHeaderValue for u32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for Mime {
|
impl TryIntoHeaderValue for Mime {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -123,12 +123,11 @@ impl HeaderMap {
|
||||||
let mut map = HeaderMap::with_capacity(capacity);
|
let mut map = HeaderMap::with_capacity(capacity);
|
||||||
map.append(first_name.clone(), first_value);
|
map.append(first_name.clone(), first_value);
|
||||||
|
|
||||||
let (map, _) =
|
let (map, _) = drain.fold((map, first_name), |(mut map, prev_name), (name, value)| {
|
||||||
drain.fold((map, first_name), |(mut map, prev_name), (name, value)| {
|
let name = name.unwrap_or(prev_name);
|
||||||
let name = name.unwrap_or(prev_name);
|
map.append(name.clone(), value);
|
||||||
map.append(name.clone(), value);
|
(map, name)
|
||||||
(map, name)
|
});
|
||||||
});
|
|
||||||
|
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
@ -307,8 +306,11 @@ impl HeaderMap {
|
||||||
/// assert_eq!(set_cookies_iter.next().unwrap(), "two=2");
|
/// assert_eq!(set_cookies_iter.next().unwrap(), "two=2");
|
||||||
/// assert!(set_cookies_iter.next().is_none());
|
/// assert!(set_cookies_iter.next().is_none());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get_all(&self, key: impl AsHeaderName) -> GetAll<'_> {
|
pub fn get_all(&self, key: impl AsHeaderName) -> std::slice::Iter<'_, HeaderValue> {
|
||||||
GetAll::new(self.get_value(key))
|
match self.get_value(key) {
|
||||||
|
Some(value) => value.iter(),
|
||||||
|
None => (&[]).iter(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: get_all_mut ?
|
// TODO: get_all_mut ?
|
||||||
|
@ -334,7 +336,7 @@ impl HeaderMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a name-value pair into the map.
|
/// Inserts (overrides) a name-value pair in the map.
|
||||||
///
|
///
|
||||||
/// If the map already contained this key, the new value is associated with the key and all
|
/// If the map already contained this key, the new value is associated with the key and all
|
||||||
/// previous values are removed and returned as a `Removed` iterator. The key is not updated;
|
/// previous values are removed and returned as a `Removed` iterator. The key is not updated;
|
||||||
|
@ -373,7 +375,7 @@ impl HeaderMap {
|
||||||
Removed::new(value)
|
Removed::new(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a name-value pair into the map.
|
/// Appends a name-value pair to the map.
|
||||||
///
|
///
|
||||||
/// If the map already contained this key, the new value is added to the list of values
|
/// If the map already contained this key, the new value is added to the list of values
|
||||||
/// currently associated with the key. The key is not updated; this matters for types that can
|
/// currently associated with the key. The key is not updated; this matters for types that can
|
||||||
|
@ -603,52 +605,6 @@ impl<'a> IntoIterator for &'a HeaderMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterator over borrowed values with the same associated name.
|
|
||||||
///
|
|
||||||
/// See [`HeaderMap::get_all`].
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct GetAll<'a> {
|
|
||||||
idx: usize,
|
|
||||||
value: Option<&'a Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> GetAll<'a> {
|
|
||||||
fn new(value: Option<&'a Value>) -> Self {
|
|
||||||
Self { idx: 0, value }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for GetAll<'a> {
|
|
||||||
type Item = &'a HeaderValue;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let val = self.value?;
|
|
||||||
|
|
||||||
match val.get(self.idx) {
|
|
||||||
Some(val) => {
|
|
||||||
self.idx += 1;
|
|
||||||
Some(val)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// current index is none; remove value to fast-path future next calls
|
|
||||||
self.value = None;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
||||||
match self.value {
|
|
||||||
Some(val) => (val.len(), Some(val.len())),
|
|
||||||
None => (0, Some(0)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExactSizeIterator for GetAll<'_> {}
|
|
||||||
|
|
||||||
impl iter::FusedIterator for GetAll<'_> {}
|
|
||||||
|
|
||||||
/// Iterator over removed, owned values with the same associated name.
|
/// Iterator over removed, owned values with the same associated name.
|
||||||
///
|
///
|
||||||
/// Returned from methods that remove or replace items. See [`HeaderMap::insert`]
|
/// Returned from methods that remove or replace items. See [`HeaderMap::insert`]
|
||||||
|
@ -896,7 +852,7 @@ mod tests {
|
||||||
|
|
||||||
assert_impl_all!(HeaderMap: IntoIterator);
|
assert_impl_all!(HeaderMap: IntoIterator);
|
||||||
assert_impl_all!(Keys<'_>: Iterator, ExactSizeIterator, FusedIterator);
|
assert_impl_all!(Keys<'_>: Iterator, ExactSizeIterator, FusedIterator);
|
||||||
assert_impl_all!(GetAll<'_>: Iterator, ExactSizeIterator, FusedIterator);
|
assert_impl_all!(std::slice::Iter<'_, HeaderValue>: Iterator, ExactSizeIterator, FusedIterator);
|
||||||
assert_impl_all!(Removed: Iterator, ExactSizeIterator, FusedIterator);
|
assert_impl_all!(Removed: Iterator, ExactSizeIterator, FusedIterator);
|
||||||
assert_impl_all!(Iter<'_>: Iterator, ExactSizeIterator, FusedIterator);
|
assert_impl_all!(Iter<'_>: Iterator, ExactSizeIterator, FusedIterator);
|
||||||
assert_impl_all!(IntoIter: Iterator, ExactSizeIterator, FusedIterator);
|
assert_impl_all!(IntoIter: Iterator, ExactSizeIterator, FusedIterator);
|
||||||
|
|
|
@ -11,22 +11,20 @@ pub use http::header::{
|
||||||
pub use http::header::{
|
pub use http::header::{
|
||||||
ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES,
|
ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES,
|
||||||
ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS,
|
ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS,
|
||||||
ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN,
|
ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS,
|
||||||
ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_MAX_AGE,
|
ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE,
|
||||||
ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE, ALLOW, ALT_SVC,
|
ALLOW, ALT_SVC, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION,
|
||||||
AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, CONTENT_ENCODING,
|
CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE,
|
||||||
CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE,
|
CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, DATE,
|
||||||
CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE,
|
DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE,
|
||||||
DATE, DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH,
|
IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED, LINK, LOCATION, MAX_FORWARDS,
|
||||||
IF_MODIFIED_SINCE, IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED,
|
ORIGIN, PRAGMA, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, PUBLIC_KEY_PINS,
|
||||||
LINK, LOCATION, MAX_FORWARDS, ORIGIN, PRAGMA, PROXY_AUTHENTICATE,
|
PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER, REFERRER_POLICY, REFRESH, RETRY_AFTER,
|
||||||
PROXY_AUTHORIZATION, PUBLIC_KEY_PINS, PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER,
|
SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL,
|
||||||
REFERRER_POLICY, REFRESH, RETRY_AFTER, SEC_WEBSOCKET_ACCEPT,
|
|
||||||
SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL,
|
|
||||||
SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE, STRICT_TRANSPORT_SECURITY, TE, TRAILER,
|
SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE, STRICT_TRANSPORT_SECURITY, TE, TRAILER,
|
||||||
TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA,
|
TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA, WARNING,
|
||||||
WARNING, WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL,
|
WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL, X_FRAME_OPTIONS,
|
||||||
X_FRAME_OPTIONS, X_XSS_PROTECTION,
|
X_XSS_PROTECTION,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{error::ParseError, HttpMessage};
|
use crate::{error::ParseError, HttpMessage};
|
||||||
|
@ -39,19 +37,19 @@ mod shared;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub use self::as_name::AsHeaderName;
|
pub use self::as_name::AsHeaderName;
|
||||||
pub use self::into_pair::IntoHeaderPair;
|
pub use self::into_pair::TryIntoHeaderPair;
|
||||||
pub use self::into_value::IntoHeaderValue;
|
pub use self::into_value::TryIntoHeaderValue;
|
||||||
pub use self::map::HeaderMap;
|
pub use self::map::HeaderMap;
|
||||||
pub use self::shared::{
|
pub use self::shared::{
|
||||||
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate,
|
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag,
|
||||||
LanguageTag, Quality, QualityItem,
|
Quality, QualityItem,
|
||||||
};
|
};
|
||||||
pub use self::utils::{
|
pub use self::utils::{
|
||||||
fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode,
|
fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An interface for types that already represent a valid header.
|
/// An interface for types that already represent a valid header.
|
||||||
pub trait Header: IntoHeaderValue {
|
pub trait Header: TryIntoHeaderValue {
|
||||||
/// Returns the name of the header field
|
/// Returns the name of the header field
|
||||||
fn name() -> HeaderName;
|
fn name() -> HeaderName;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::ParseError,
|
error::ParseError,
|
||||||
header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, IntoHeaderValue},
|
header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, TryIntoHeaderValue},
|
||||||
HttpMessage,
|
HttpMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,13 +45,13 @@ pub enum ContentEncoding {
|
||||||
impl ContentEncoding {
|
impl ContentEncoding {
|
||||||
/// Is the content compressed?
|
/// Is the content compressed?
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_compression(self) -> bool {
|
pub const fn is_compression(self) -> bool {
|
||||||
matches!(self, ContentEncoding::Identity | ContentEncoding::Auto)
|
matches!(self, ContentEncoding::Identity | ContentEncoding::Auto)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert content encoding to string.
|
/// Convert content encoding to string.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_str(self) -> &'static str {
|
pub const fn as_str(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
ContentEncoding::Br => "br",
|
ContentEncoding::Br => "br",
|
||||||
ContentEncoding::Gzip => "gzip",
|
ContentEncoding::Gzip => "gzip",
|
||||||
|
@ -96,7 +96,7 @@ impl TryFrom<&str> for ContentEncoding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for ContentEncoding {
|
impl TryIntoHeaderValue for ContentEncoding {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into_value(self) -> Result<http::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<http::HeaderValue, Self::Error> {
|
||||||
|
|
|
@ -63,9 +63,7 @@ pub struct ExtendedValue {
|
||||||
/// [RFC 2231 §7]: https://datatracker.ietf.org/doc/html/rfc2231#section-7
|
/// [RFC 2231 §7]: https://datatracker.ietf.org/doc/html/rfc2231#section-7
|
||||||
/// [RFC 2978 §2.3]: https://datatracker.ietf.org/doc/html/rfc2978#section-2.3
|
/// [RFC 2978 §2.3]: https://datatracker.ietf.org/doc/html/rfc2978#section-2.3
|
||||||
/// [RFC 3986 §2.1]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1
|
/// [RFC 3986 §2.1]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1
|
||||||
pub fn parse_extended_value(
|
pub fn parse_extended_value(val: &str) -> Result<ExtendedValue, crate::error::ParseError> {
|
||||||
val: &str,
|
|
||||||
) -> Result<ExtendedValue, crate::error::ParseError> {
|
|
||||||
// Break into three pieces separated by the single-quote character
|
// Break into three pieces separated by the single-quote character
|
||||||
let mut parts = val.splitn(3, '\'');
|
let mut parts = val.splitn(3, '\'');
|
||||||
|
|
||||||
|
@ -100,8 +98,7 @@ pub fn parse_extended_value(
|
||||||
|
|
||||||
impl fmt::Display for ExtendedValue {
|
impl fmt::Display for ExtendedValue {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let encoded_value =
|
let encoded_value = percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
|
||||||
percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
|
|
||||||
if let Some(ref lang) = self.language_tag {
|
if let Some(ref lang) = self.language_tag {
|
||||||
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
|
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
|
||||||
} else {
|
} else {
|
||||||
|
@ -143,8 +140,8 @@ mod tests {
|
||||||
assert!(extended_value.language_tag.is_none());
|
assert!(extended_value.language_tag.is_none());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vec![
|
vec![
|
||||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't',
|
||||||
b't', b'e', b's',
|
b'e', b's',
|
||||||
],
|
],
|
||||||
extended_value.value
|
extended_value.value
|
||||||
);
|
);
|
||||||
|
@ -185,8 +182,8 @@ mod tests {
|
||||||
charset: Charset::Ext("UTF-8".to_string()),
|
charset: Charset::Ext("UTF-8".to_string()),
|
||||||
language_tag: None,
|
language_tag: None,
|
||||||
value: vec![
|
value: vec![
|
||||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't',
|
||||||
b't', b'e', b's',
|
b'e', b's',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -4,7 +4,7 @@ use bytes::BytesMut;
|
||||||
use http::header::{HeaderValue, InvalidHeaderValue};
|
use http::header::{HeaderValue, InvalidHeaderValue};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue,
|
config::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue,
|
||||||
helpers::MutWriter,
|
helpers::MutWriter,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ impl fmt::Display for HttpDate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoHeaderValue for HttpDate {
|
impl TryIntoHeaderValue for HttpDate {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
|
|
@ -87,7 +87,7 @@ impl fmt::Display for Quality {
|
||||||
|
|
||||||
// 0 is already handled so it's not possible to have a trailing 0 in this range
|
// 0 is already handled so it's not possible to have a trailing 0 in this range
|
||||||
// we can just write the integer
|
// we can just write the integer
|
||||||
itoa::fmt(f, x)
|
itoa_fmt(f, x)
|
||||||
} else if x < 100 {
|
} else if x < 100 {
|
||||||
// x in is range 10–99
|
// x in is range 10–99
|
||||||
|
|
||||||
|
@ -95,21 +95,21 @@ impl fmt::Display for Quality {
|
||||||
|
|
||||||
if x % 10 == 0 {
|
if x % 10 == 0 {
|
||||||
// trailing 0, divide by 10 and write
|
// trailing 0, divide by 10 and write
|
||||||
itoa::fmt(f, x / 10)
|
itoa_fmt(f, x / 10)
|
||||||
} else {
|
} else {
|
||||||
itoa::fmt(f, x)
|
itoa_fmt(f, x)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// x is in range 100–999
|
// x is in range 100–999
|
||||||
|
|
||||||
if x % 100 == 0 {
|
if x % 100 == 0 {
|
||||||
// two trailing 0s, divide by 100 and write
|
// two trailing 0s, divide by 100 and write
|
||||||
itoa::fmt(f, x / 100)
|
itoa_fmt(f, x / 100)
|
||||||
} else if x % 10 == 0 {
|
} else if x % 10 == 0 {
|
||||||
// one trailing 0, divide by 10 and write
|
// one trailing 0, divide by 10 and write
|
||||||
itoa::fmt(f, x / 10)
|
itoa_fmt(f, x / 10)
|
||||||
} else {
|
} else {
|
||||||
itoa::fmt(f, x)
|
itoa_fmt(f, x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,6 +117,12 @@ impl fmt::Display for Quality {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write integer to a `fmt::Write`.
|
||||||
|
pub fn itoa_fmt<W: fmt::Write, V: itoa::Integer>(mut wr: W, value: V) -> fmt::Result {
|
||||||
|
let mut buf = itoa::Buffer::new();
|
||||||
|
wr.write_str(buf.format(value))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Display, Error)]
|
#[derive(Debug, Clone, Display, Error)]
|
||||||
#[display(fmt = "quality out of bounds")]
|
#[display(fmt = "quality out of bounds")]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
|
|
@ -120,8 +120,7 @@ impl<T: str::FromStr> str::FromStr for QualityItem<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let q_value = q_val.parse::<f32>().map_err(|_| ParseError::Header)?;
|
let q_value = q_val.parse::<f32>().map_err(|_| ParseError::Header)?;
|
||||||
let q_value =
|
let q_value = Quality::try_from(q_value).map_err(|_| ParseError::Header)?;
|
||||||
Quality::try_from(q_value).map_err(|_| ParseError::Header)?;
|
|
||||||
|
|
||||||
quality = q_value;
|
quality = q_value;
|
||||||
raw_item = val;
|
raw_item = val;
|
||||||
|
|
|
@ -14,11 +14,11 @@
|
||||||
//! [rustls]: https://crates.io/crates/rustls
|
//! [rustls]: https://crates.io/crates/rustls
|
||||||
//! [trust-dns]: https://crates.io/crates/trust-dns
|
//! [trust-dns]: https://crates.io/crates/trust-dns
|
||||||
|
|
||||||
#![deny(rust_2018_idioms, nonstandard_style, clippy::uninit_assumed_init)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
|
#![warn(future_incompatible)]
|
||||||
#![allow(
|
#![allow(
|
||||||
clippy::type_complexity,
|
clippy::type_complexity,
|
||||||
clippy::too_many_arguments,
|
clippy::too_many_arguments,
|
||||||
clippy::new_without_default,
|
|
||||||
clippy::borrow_interior_mutable_const
|
clippy::borrow_interior_mutable_const
|
||||||
)]
|
)]
|
||||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||||
|
@ -27,26 +27,26 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
|
pub use ::http::{uri, uri::Uri};
|
||||||
|
pub use ::http::{Method, StatusCode, Version};
|
||||||
|
|
||||||
pub mod body;
|
pub mod body;
|
||||||
mod builder;
|
mod builder;
|
||||||
mod config;
|
mod config;
|
||||||
|
|
||||||
#[cfg(feature = "__compress")]
|
#[cfg(feature = "__compress")]
|
||||||
pub mod encoding;
|
pub mod encoding;
|
||||||
|
pub mod error;
|
||||||
mod extensions;
|
mod extensions;
|
||||||
|
pub mod h1;
|
||||||
|
pub mod h2;
|
||||||
pub mod header;
|
pub mod header;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod http_message;
|
mod http_message;
|
||||||
mod message;
|
mod message;
|
||||||
mod payload;
|
mod payload;
|
||||||
mod request;
|
mod requests;
|
||||||
mod response;
|
mod responses;
|
||||||
mod response_builder;
|
|
||||||
mod service;
|
mod service;
|
||||||
|
|
||||||
pub mod error;
|
|
||||||
pub mod h1;
|
|
||||||
pub mod h2;
|
|
||||||
pub mod test;
|
pub mod test;
|
||||||
pub mod ws;
|
pub mod ws;
|
||||||
|
|
||||||
|
@ -57,16 +57,13 @@ pub use self::extensions::Extensions;
|
||||||
pub use self::header::ContentEncoding;
|
pub use self::header::ContentEncoding;
|
||||||
pub use self::http_message::HttpMessage;
|
pub use self::http_message::HttpMessage;
|
||||||
pub use self::message::ConnectionType;
|
pub use self::message::ConnectionType;
|
||||||
pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead};
|
pub use self::message::Message;
|
||||||
pub use self::payload::{Payload, PayloadStream};
|
#[allow(deprecated)]
|
||||||
pub use self::request::Request;
|
pub use self::payload::{BoxedPayloadStream, Payload, PayloadStream};
|
||||||
pub use self::response::Response;
|
pub use self::requests::{Request, RequestHead, RequestHeadType};
|
||||||
pub use self::response_builder::ResponseBuilder;
|
pub use self::responses::{Response, ResponseBuilder, ResponseHead};
|
||||||
pub use self::service::HttpService;
|
pub use self::service::HttpService;
|
||||||
|
|
||||||
pub use ::http::{uri, uri::Uri};
|
|
||||||
pub use ::http::{Method, StatusCode, Version};
|
|
||||||
|
|
||||||
/// A major HTTP protocol version.
|
/// A major HTTP protocol version.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
@ -87,24 +84,13 @@ pub(crate) struct OnConnectData(Option<Extensions>);
|
||||||
|
|
||||||
impl OnConnectData {
|
impl OnConnectData {
|
||||||
/// Construct by calling the on-connect callback with the underlying transport I/O.
|
/// Construct by calling the on-connect callback with the underlying transport I/O.
|
||||||
pub(crate) fn from_io<T>(
|
pub(crate) fn from_io<T>(io: &T, on_connect_ext: Option<&ConnectCallback<T>>) -> Self {
|
||||||
io: &T,
|
|
||||||
on_connect_ext: Option<&ConnectCallback<T>>,
|
|
||||||
) -> Self {
|
|
||||||
let ext = on_connect_ext.map(|handler| {
|
let ext = on_connect_ext.map(|handler| {
|
||||||
let mut extensions = Extensions::new();
|
let mut extensions = Extensions::default();
|
||||||
handler(io, &mut extensions);
|
handler(io, &mut extensions);
|
||||||
extensions
|
extensions
|
||||||
});
|
});
|
||||||
|
|
||||||
Self(ext)
|
Self(ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Merge self into given request's extensions.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn merge_into(&mut self, req: &mut Request) {
|
|
||||||
if let Some(ref mut ext) = self.0 {
|
|
||||||
req.head.extensions.get_mut().drain_from(ext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,7 @@
|
||||||
use std::{
|
use std::{cell::RefCell, ops, rc::Rc};
|
||||||
cell::{Ref, RefCell, RefMut},
|
|
||||||
net,
|
|
||||||
rc::Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
header::{self, HeaderMap},
|
|
||||||
Extensions, Method, StatusCode, Uri, Version,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Represents various types of connection
|
/// Represents various types of connection
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
pub enum ConnectionType {
|
pub enum ConnectionType {
|
||||||
|
@ -44,309 +35,6 @@ pub trait Head: Default + 'static {
|
||||||
F: FnOnce(&MessagePool<Self>) -> R;
|
F: FnOnce(&MessagePool<Self>) -> R;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RequestHead {
|
|
||||||
pub method: Method,
|
|
||||||
pub uri: Uri,
|
|
||||||
pub version: Version,
|
|
||||||
pub headers: HeaderMap,
|
|
||||||
pub extensions: RefCell<Extensions>,
|
|
||||||
pub peer_addr: Option<net::SocketAddr>,
|
|
||||||
flags: Flags,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RequestHead {
|
|
||||||
fn default() -> RequestHead {
|
|
||||||
RequestHead {
|
|
||||||
method: Method::default(),
|
|
||||||
uri: Uri::default(),
|
|
||||||
version: Version::HTTP_11,
|
|
||||||
headers: HeaderMap::with_capacity(16),
|
|
||||||
extensions: RefCell::new(Extensions::new()),
|
|
||||||
peer_addr: None,
|
|
||||||
flags: Flags::empty(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Head for RequestHead {
|
|
||||||
fn clear(&mut self) {
|
|
||||||
self.flags = Flags::empty();
|
|
||||||
self.headers.clear();
|
|
||||||
self.extensions.get_mut().clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_pool<F, R>(f: F) -> R
|
|
||||||
where
|
|
||||||
F: FnOnce(&MessagePool<Self>) -> R,
|
|
||||||
{
|
|
||||||
REQUEST_POOL.with(|p| f(p))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RequestHead {
|
|
||||||
/// Message extensions
|
|
||||||
#[inline]
|
|
||||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
|
||||||
self.extensions.borrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mutable reference to a the message's extensions
|
|
||||||
#[inline]
|
|
||||||
pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
|
||||||
self.extensions.borrow_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the message headers.
|
|
||||||
pub fn headers(&self) -> &HeaderMap {
|
|
||||||
&self.headers
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mutable reference to the message headers.
|
|
||||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
|
||||||
&mut self.headers
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is to uppercase headers with Camel-Case.
|
|
||||||
/// Default is `false`
|
|
||||||
#[inline]
|
|
||||||
pub fn camel_case_headers(&self) -> bool {
|
|
||||||
self.flags.contains(Flags::CAMEL_CASE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set `true` to send headers which are formatted as Camel-Case.
|
|
||||||
#[inline]
|
|
||||||
pub fn set_camel_case_headers(&mut self, val: bool) {
|
|
||||||
if val {
|
|
||||||
self.flags.insert(Flags::CAMEL_CASE);
|
|
||||||
} else {
|
|
||||||
self.flags.remove(Flags::CAMEL_CASE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Set connection type of the message
|
|
||||||
pub fn set_connection_type(&mut self, ctype: ConnectionType) {
|
|
||||||
match ctype {
|
|
||||||
ConnectionType::Close => self.flags.insert(Flags::CLOSE),
|
|
||||||
ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE),
|
|
||||||
ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Connection type
|
|
||||||
pub fn connection_type(&self) -> ConnectionType {
|
|
||||||
if self.flags.contains(Flags::CLOSE) {
|
|
||||||
ConnectionType::Close
|
|
||||||
} else if self.flags.contains(Flags::KEEP_ALIVE) {
|
|
||||||
ConnectionType::KeepAlive
|
|
||||||
} else if self.flags.contains(Flags::UPGRADE) {
|
|
||||||
ConnectionType::Upgrade
|
|
||||||
} else if self.version < Version::HTTP_11 {
|
|
||||||
ConnectionType::Close
|
|
||||||
} else {
|
|
||||||
ConnectionType::KeepAlive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Connection upgrade status
|
|
||||||
pub fn upgrade(&self) -> bool {
|
|
||||||
self.headers()
|
|
||||||
.get(header::CONNECTION)
|
|
||||||
.map(|hdr| {
|
|
||||||
if let Ok(s) = hdr.to_str() {
|
|
||||||
s.to_ascii_lowercase().contains("upgrade")
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Get response body chunking state
|
|
||||||
pub fn chunked(&self) -> bool {
|
|
||||||
!self.flags.contains(Flags::NO_CHUNKING)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn no_chunking(&mut self, val: bool) {
|
|
||||||
if val {
|
|
||||||
self.flags.insert(Flags::NO_CHUNKING);
|
|
||||||
} else {
|
|
||||||
self.flags.remove(Flags::NO_CHUNKING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Request contains `EXPECT` header
|
|
||||||
pub fn expect(&self) -> bool {
|
|
||||||
self.flags.contains(Flags::EXPECT)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn set_expect(&mut self) {
|
|
||||||
self.flags.insert(Flags::EXPECT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
|
||||||
pub enum RequestHeadType {
|
|
||||||
Owned(RequestHead),
|
|
||||||
Rc(Rc<RequestHead>, Option<HeaderMap>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RequestHeadType {
|
|
||||||
pub fn extra_headers(&self) -> Option<&HeaderMap> {
|
|
||||||
match self {
|
|
||||||
RequestHeadType::Owned(_) => None,
|
|
||||||
RequestHeadType::Rc(_, headers) => headers.as_ref(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<RequestHead> for RequestHeadType {
|
|
||||||
fn as_ref(&self) -> &RequestHead {
|
|
||||||
match self {
|
|
||||||
RequestHeadType::Owned(head) => head,
|
|
||||||
RequestHeadType::Rc(head, _) => head.as_ref(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RequestHead> for RequestHeadType {
|
|
||||||
fn from(head: RequestHead) -> Self {
|
|
||||||
RequestHeadType::Owned(head)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ResponseHead {
|
|
||||||
pub version: Version,
|
|
||||||
pub status: StatusCode,
|
|
||||||
pub headers: HeaderMap,
|
|
||||||
pub reason: Option<&'static str>,
|
|
||||||
pub(crate) extensions: RefCell<Extensions>,
|
|
||||||
flags: Flags,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResponseHead {
|
|
||||||
/// Create new instance of `ResponseHead` type
|
|
||||||
#[inline]
|
|
||||||
pub fn new(status: StatusCode) -> ResponseHead {
|
|
||||||
ResponseHead {
|
|
||||||
status,
|
|
||||||
version: Version::default(),
|
|
||||||
headers: HeaderMap::with_capacity(12),
|
|
||||||
reason: None,
|
|
||||||
flags: Flags::empty(),
|
|
||||||
extensions: RefCell::new(Extensions::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Message extensions
|
|
||||||
#[inline]
|
|
||||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
|
||||||
self.extensions.borrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mutable reference to a the message's extensions
|
|
||||||
#[inline]
|
|
||||||
pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
|
||||||
self.extensions.borrow_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Read the message headers.
|
|
||||||
pub fn headers(&self) -> &HeaderMap {
|
|
||||||
&self.headers
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Mutable reference to the message headers.
|
|
||||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
|
||||||
&mut self.headers
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Set connection type of the message
|
|
||||||
pub fn set_connection_type(&mut self, ctype: ConnectionType) {
|
|
||||||
match ctype {
|
|
||||||
ConnectionType::Close => self.flags.insert(Flags::CLOSE),
|
|
||||||
ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE),
|
|
||||||
ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn connection_type(&self) -> ConnectionType {
|
|
||||||
if self.flags.contains(Flags::CLOSE) {
|
|
||||||
ConnectionType::Close
|
|
||||||
} else if self.flags.contains(Flags::KEEP_ALIVE) {
|
|
||||||
ConnectionType::KeepAlive
|
|
||||||
} else if self.flags.contains(Flags::UPGRADE) {
|
|
||||||
ConnectionType::Upgrade
|
|
||||||
} else if self.version < Version::HTTP_11 {
|
|
||||||
ConnectionType::Close
|
|
||||||
} else {
|
|
||||||
ConnectionType::KeepAlive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if keep-alive is enabled
|
|
||||||
#[inline]
|
|
||||||
pub fn keep_alive(&self) -> bool {
|
|
||||||
self.connection_type() == ConnectionType::KeepAlive
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check upgrade status of this message
|
|
||||||
#[inline]
|
|
||||||
pub fn upgrade(&self) -> bool {
|
|
||||||
self.connection_type() == ConnectionType::Upgrade
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get custom reason for the response
|
|
||||||
#[inline]
|
|
||||||
pub fn reason(&self) -> &str {
|
|
||||||
self.reason.unwrap_or_else(|| {
|
|
||||||
self.status
|
|
||||||
.canonical_reason()
|
|
||||||
.unwrap_or("<unknown status code>")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn conn_type(&self) -> Option<ConnectionType> {
|
|
||||||
if self.flags.contains(Flags::CLOSE) {
|
|
||||||
Some(ConnectionType::Close)
|
|
||||||
} else if self.flags.contains(Flags::KEEP_ALIVE) {
|
|
||||||
Some(ConnectionType::KeepAlive)
|
|
||||||
} else if self.flags.contains(Flags::UPGRADE) {
|
|
||||||
Some(ConnectionType::Upgrade)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Get response body chunking state
|
|
||||||
pub fn chunked(&self) -> bool {
|
|
||||||
!self.flags.contains(Flags::NO_CHUNKING)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Set no chunking for payload
|
|
||||||
pub fn no_chunking(&mut self, val: bool) {
|
|
||||||
if val {
|
|
||||||
self.flags.insert(Flags::NO_CHUNKING);
|
|
||||||
} else {
|
|
||||||
self.flags.remove(Flags::NO_CHUNKING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Message<T: Head> {
|
pub struct Message<T: Head> {
|
||||||
/// Rc here should not be cloned by anyone.
|
/// Rc here should not be cloned by anyone.
|
||||||
/// It's used to reuse allocation of T and no shared ownership is allowed.
|
/// It's used to reuse allocation of T and no shared ownership is allowed.
|
||||||
|
@ -355,12 +43,13 @@ pub struct Message<T: Head> {
|
||||||
|
|
||||||
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
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
T::with_pool(MessagePool::get_message)
|
T::with_pool(MessagePool::get_message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Head> std::ops::Deref for Message<T> {
|
impl<T: Head> ops::Deref for Message<T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
|
@ -368,7 +57,7 @@ impl<T: Head> std::ops::Deref for Message<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Head> std::ops::DerefMut for Message<T> {
|
impl<T: Head> ops::DerefMut for Message<T> {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
Rc::get_mut(&mut self.head).expect("Multiple copies exist")
|
Rc::get_mut(&mut self.head).expect("Multiple copies exist")
|
||||||
}
|
}
|
||||||
|
@ -380,53 +69,12 @@ impl<T: Head> Drop for Message<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct BoxedResponseHead {
|
|
||||||
head: Option<Box<ResponseHead>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BoxedResponseHead {
|
|
||||||
/// Get new message from the pool of objects
|
|
||||||
pub fn new(status: StatusCode) -> Self {
|
|
||||||
RESPONSE_POOL.with(|p| p.get_message(status))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Deref for BoxedResponseHead {
|
|
||||||
type Target = ResponseHead;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.head.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::DerefMut for BoxedResponseHead {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
self.head.as_mut().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for BoxedResponseHead {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if let Some(head) = self.head.take() {
|
|
||||||
RESPONSE_POOL.with(move |p| p.release(head))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Request's objects pool
|
/// Request's objects pool
|
||||||
pub struct MessagePool<T: Head>(RefCell<Vec<Rc<T>>>);
|
pub struct MessagePool<T: Head>(RefCell<Vec<Rc<T>>>);
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[allow(clippy::vec_box)]
|
|
||||||
/// Request's objects pool
|
|
||||||
pub struct BoxedResponsePool(RefCell<Vec<Box<ResponseHead>>>);
|
|
||||||
|
|
||||||
thread_local!(static REQUEST_POOL: MessagePool<RequestHead> = MessagePool::<RequestHead>::create());
|
|
||||||
thread_local!(static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create());
|
|
||||||
|
|
||||||
impl<T: Head> MessagePool<T> {
|
impl<T: Head> MessagePool<T> {
|
||||||
fn create() -> MessagePool<T> {
|
pub(crate) fn create() -> MessagePool<T> {
|
||||||
MessagePool(RefCell::new(Vec::with_capacity(128)))
|
MessagePool(RefCell::new(Vec::with_capacity(128)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -448,43 +96,11 @@ impl<T: Head> MessagePool<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Release request instance
|
/// Release message instance
|
||||||
fn release(&self, msg: Rc<T>) {
|
fn release(&self, msg: Rc<T>) {
|
||||||
let v = &mut self.0.borrow_mut();
|
let pool = &mut self.0.borrow_mut();
|
||||||
if v.len() < 128 {
|
if pool.len() < 128 {
|
||||||
v.push(msg);
|
pool.push(msg);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BoxedResponsePool {
|
|
||||||
fn create() -> BoxedResponsePool {
|
|
||||||
BoxedResponsePool(RefCell::new(Vec::with_capacity(128)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get message from the pool
|
|
||||||
#[inline]
|
|
||||||
fn get_message(&self, status: StatusCode) -> BoxedResponseHead {
|
|
||||||
if let Some(mut head) = self.0.borrow_mut().pop() {
|
|
||||||
head.reason = None;
|
|
||||||
head.status = status;
|
|
||||||
head.headers.clear();
|
|
||||||
head.flags = Flags::empty();
|
|
||||||
BoxedResponseHead { head: Some(head) }
|
|
||||||
} else {
|
|
||||||
BoxedResponseHead {
|
|
||||||
head: Some(Box::new(ResponseHead::new(status))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Release request instance
|
|
||||||
fn release(&self, mut msg: Box<ResponseHead>) {
|
|
||||||
let v = &mut self.0.borrow_mut();
|
|
||||||
if v.len() < 128 {
|
|
||||||
msg.extensions.get_mut().clear();
|
|
||||||
v.push(msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,70 +1,89 @@
|
||||||
use std::pin::Pin;
|
use std::{
|
||||||
use std::task::{Context, Poll};
|
mem,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use h2::RecvStream;
|
|
||||||
|
|
||||||
use crate::error::PayloadError;
|
use crate::error::PayloadError;
|
||||||
|
|
||||||
/// Type represent boxed payload
|
/// A boxed payload stream.
|
||||||
pub type PayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>;
|
pub type BoxedPayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>;
|
||||||
|
|
||||||
/// Type represent streaming payload
|
#[deprecated(since = "4.0.0", note = "Renamed to `BoxedPayloadStream`.")]
|
||||||
pub enum Payload<S = PayloadStream> {
|
pub type PayloadStream = BoxedPayloadStream;
|
||||||
None,
|
|
||||||
H1(crate::h1::Payload),
|
pin_project_lite::pin_project! {
|
||||||
H2(crate::h2::Payload),
|
/// A streaming payload.
|
||||||
Stream(S),
|
#[project = PayloadProj]
|
||||||
|
pub enum Payload<S = BoxedPayloadStream> {
|
||||||
|
None,
|
||||||
|
H1 { payload: crate::h1::Payload },
|
||||||
|
H2 { payload: crate::h2::Payload },
|
||||||
|
Stream { #[pin] payload: S },
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> From<crate::h1::Payload> for Payload<S> {
|
impl<S> From<crate::h1::Payload> for Payload<S> {
|
||||||
fn from(v: crate::h1::Payload) -> Self {
|
fn from(payload: crate::h1::Payload) -> Self {
|
||||||
Payload::H1(v)
|
Payload::H1 { payload }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> From<crate::h2::Payload> for Payload<S> {
|
impl<S> From<crate::h2::Payload> for Payload<S> {
|
||||||
fn from(v: crate::h2::Payload) -> Self {
|
fn from(payload: crate::h2::Payload) -> Self {
|
||||||
Payload::H2(v)
|
Payload::H2 { payload }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> From<RecvStream> for Payload<S> {
|
impl<S> From<h2::RecvStream> for Payload<S> {
|
||||||
fn from(v: RecvStream) -> Self {
|
fn from(stream: h2::RecvStream) -> Self {
|
||||||
Payload::H2(crate::h2::Payload::new(v))
|
Payload::H2 {
|
||||||
|
payload: crate::h2::Payload::new(stream),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PayloadStream> for Payload {
|
impl From<BoxedPayloadStream> for Payload {
|
||||||
fn from(pl: PayloadStream) -> Self {
|
fn from(payload: BoxedPayloadStream) -> Self {
|
||||||
Payload::Stream(pl)
|
Payload::Stream { payload }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Payload<S> {
|
impl<S> Payload<S> {
|
||||||
/// Takes current payload and replaces it with `None` value
|
/// Takes current payload and replaces it with `None` value
|
||||||
pub fn take(&mut self) -> Payload<S> {
|
pub fn take(&mut self) -> Payload<S> {
|
||||||
std::mem::replace(self, Payload::None)
|
mem::replace(self, Payload::None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Stream for Payload<S>
|
impl<S> Stream for Payload<S>
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
|
S: Stream<Item = Result<Bytes, PayloadError>>,
|
||||||
{
|
{
|
||||||
type Item = Result<Bytes, PayloadError>;
|
type Item = Result<Bytes, PayloadError>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
self: Pin<&mut Self>,
|
match self.project() {
|
||||||
cx: &mut Context<'_>,
|
PayloadProj::None => Poll::Ready(None),
|
||||||
) -> Poll<Option<Self::Item>> {
|
PayloadProj::H1 { payload } => Pin::new(payload).poll_next(cx),
|
||||||
match self.get_mut() {
|
PayloadProj::H2 { payload } => Pin::new(payload).poll_next(cx),
|
||||||
Payload::None => Poll::Ready(None),
|
PayloadProj::Stream { payload } => payload.poll_next(cx),
|
||||||
Payload::H1(ref mut pl) => pl.readany(cx),
|
|
||||||
Payload::H2(ref mut pl) => Pin::new(pl).poll_next(cx),
|
|
||||||
Payload::Stream(ref mut pl) => Pin::new(pl).poll_next(cx),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||||
|
|
||||||
|
use static_assertions::{assert_impl_all, assert_not_impl_any};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
assert_impl_all!(Payload: Unpin);
|
||||||
|
assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
use std::{net, rc::Rc};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
header::{self, HeaderMap},
|
||||||
|
message::{Flags, Head, MessagePool},
|
||||||
|
ConnectionType, Method, Uri, Version,
|
||||||
|
};
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static REQUEST_POOL: MessagePool<RequestHead> = MessagePool::<RequestHead>::create()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RequestHead {
|
||||||
|
pub method: Method,
|
||||||
|
pub uri: Uri,
|
||||||
|
pub version: Version,
|
||||||
|
pub headers: HeaderMap,
|
||||||
|
pub peer_addr: Option<net::SocketAddr>,
|
||||||
|
flags: Flags,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RequestHead {
|
||||||
|
fn default() -> RequestHead {
|
||||||
|
RequestHead {
|
||||||
|
method: Method::default(),
|
||||||
|
uri: Uri::default(),
|
||||||
|
version: Version::HTTP_11,
|
||||||
|
headers: HeaderMap::with_capacity(16),
|
||||||
|
peer_addr: None,
|
||||||
|
flags: Flags::empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Head for RequestHead {
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.flags = Flags::empty();
|
||||||
|
self.headers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_pool<F, R>(f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&MessagePool<Self>) -> R,
|
||||||
|
{
|
||||||
|
REQUEST_POOL.with(|p| f(p))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestHead {
|
||||||
|
/// Read the message headers.
|
||||||
|
pub fn headers(&self) -> &HeaderMap {
|
||||||
|
&self.headers
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutable reference to the message headers.
|
||||||
|
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||||
|
&mut self.headers
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is to uppercase headers with Camel-Case.
|
||||||
|
/// Default is `false`
|
||||||
|
#[inline]
|
||||||
|
pub fn camel_case_headers(&self) -> bool {
|
||||||
|
self.flags.contains(Flags::CAMEL_CASE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set `true` to send headers which are formatted as Camel-Case.
|
||||||
|
#[inline]
|
||||||
|
pub fn set_camel_case_headers(&mut self, val: bool) {
|
||||||
|
if val {
|
||||||
|
self.flags.insert(Flags::CAMEL_CASE);
|
||||||
|
} else {
|
||||||
|
self.flags.remove(Flags::CAMEL_CASE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Set connection type of the message
|
||||||
|
pub fn set_connection_type(&mut self, ctype: ConnectionType) {
|
||||||
|
match ctype {
|
||||||
|
ConnectionType::Close => self.flags.insert(Flags::CLOSE),
|
||||||
|
ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE),
|
||||||
|
ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Connection type
|
||||||
|
pub fn connection_type(&self) -> ConnectionType {
|
||||||
|
if self.flags.contains(Flags::CLOSE) {
|
||||||
|
ConnectionType::Close
|
||||||
|
} else if self.flags.contains(Flags::KEEP_ALIVE) {
|
||||||
|
ConnectionType::KeepAlive
|
||||||
|
} else if self.flags.contains(Flags::UPGRADE) {
|
||||||
|
ConnectionType::Upgrade
|
||||||
|
} else if self.version < Version::HTTP_11 {
|
||||||
|
ConnectionType::Close
|
||||||
|
} else {
|
||||||
|
ConnectionType::KeepAlive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connection upgrade status
|
||||||
|
pub fn upgrade(&self) -> bool {
|
||||||
|
self.headers()
|
||||||
|
.get(header::CONNECTION)
|
||||||
|
.map(|hdr| {
|
||||||
|
if let Ok(s) = hdr.to_str() {
|
||||||
|
s.to_ascii_lowercase().contains("upgrade")
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Get response body chunking state
|
||||||
|
pub fn chunked(&self) -> bool {
|
||||||
|
!self.flags.contains(Flags::NO_CHUNKING)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn no_chunking(&mut self, val: bool) {
|
||||||
|
if val {
|
||||||
|
self.flags.insert(Flags::NO_CHUNKING);
|
||||||
|
} else {
|
||||||
|
self.flags.remove(Flags::NO_CHUNKING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Request contains `EXPECT` header
|
||||||
|
pub fn expect(&self) -> bool {
|
||||||
|
self.flags.contains(Flags::EXPECT)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn set_expect(&mut self) {
|
||||||
|
self.flags.insert(Flags::EXPECT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
pub enum RequestHeadType {
|
||||||
|
Owned(RequestHead),
|
||||||
|
Rc(Rc<RequestHead>, Option<HeaderMap>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestHeadType {
|
||||||
|
pub fn extra_headers(&self) -> Option<&HeaderMap> {
|
||||||
|
match self {
|
||||||
|
RequestHeadType::Owned(_) => None,
|
||||||
|
RequestHeadType::Rc(_, headers) => headers.as_ref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<RequestHead> for RequestHeadType {
|
||||||
|
fn as_ref(&self) -> &RequestHead {
|
||||||
|
match self {
|
||||||
|
RequestHeadType::Owned(head) => head,
|
||||||
|
RequestHeadType::Rc(head, _) => head.as_ref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RequestHead> for RequestHeadType {
|
||||||
|
fn from(head: RequestHead) -> Self {
|
||||||
|
RequestHeadType::Owned(head)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
//! HTTP requests.
|
||||||
|
|
||||||
|
mod head;
|
||||||
|
mod request;
|
||||||
|
|
||||||
|
pub use self::head::{RequestHead, RequestHeadType};
|
||||||
|
pub use self::request::Request;
|
|
@ -1,24 +1,25 @@
|
||||||
//! HTTP requests.
|
//! HTTP requests.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefMut},
|
cell::{Ref, RefCell, RefMut},
|
||||||
fmt, net, str,
|
fmt, mem, net,
|
||||||
|
rc::Rc,
|
||||||
|
str,
|
||||||
};
|
};
|
||||||
|
|
||||||
use http::{header, Method, Uri, Version};
|
use http::{header, Method, Uri, Version};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
extensions::Extensions,
|
header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Message, Payload,
|
||||||
header::HeaderMap,
|
RequestHead,
|
||||||
message::{Message, RequestHead},
|
|
||||||
payload::{Payload, PayloadStream},
|
|
||||||
HttpMessage,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An HTTP request.
|
/// An HTTP request.
|
||||||
pub struct Request<P = PayloadStream> {
|
pub struct Request<P = BoxedPayloadStream> {
|
||||||
pub(crate) payload: Payload<P>,
|
pub(crate) payload: Payload<P>,
|
||||||
pub(crate) head: Message<RequestHead>,
|
pub(crate) head: Message<RequestHead>,
|
||||||
|
pub(crate) conn_data: Option<Rc<Extensions>>,
|
||||||
|
pub(crate) req_data: RefCell<Extensions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> HttpMessage for Request<P> {
|
impl<P> HttpMessage for Request<P> {
|
||||||
|
@ -30,37 +31,42 @@ impl<P> HttpMessage for Request<P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_payload(&mut self) -> Payload<P> {
|
fn take_payload(&mut self) -> Payload<P> {
|
||||||
std::mem::replace(&mut self.payload, Payload::None)
|
mem::replace(&mut self.payload, Payload::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request extensions
|
/// Request extensions
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extensions(&self) -> Ref<'_, Extensions> {
|
fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
self.head.extensions()
|
self.req_data.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutable reference to a the request's extensions
|
/// Mutable reference to a the request's extensions
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
||||||
self.head.extensions_mut()
|
self.req_data.borrow_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Message<RequestHead>> for Request<PayloadStream> {
|
impl From<Message<RequestHead>> for Request<BoxedPayloadStream> {
|
||||||
fn from(head: Message<RequestHead>) -> Self {
|
fn from(head: Message<RequestHead>) -> Self {
|
||||||
Request {
|
Request {
|
||||||
head,
|
head,
|
||||||
payload: Payload::None,
|
payload: Payload::None,
|
||||||
|
req_data: RefCell::new(Extensions::default()),
|
||||||
|
conn_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request<PayloadStream> {
|
impl Request<BoxedPayloadStream> {
|
||||||
/// Create new Request instance
|
/// Create new Request instance
|
||||||
pub fn new() -> Request<PayloadStream> {
|
#[allow(clippy::new_without_default)]
|
||||||
|
pub fn new() -> Request<BoxedPayloadStream> {
|
||||||
Request {
|
Request {
|
||||||
head: Message::new(),
|
head: Message::new(),
|
||||||
payload: Payload::None,
|
payload: Payload::None,
|
||||||
|
req_data: RefCell::new(Extensions::default()),
|
||||||
|
conn_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,16 +77,21 @@ impl<P> Request<P> {
|
||||||
Request {
|
Request {
|
||||||
payload,
|
payload,
|
||||||
head: Message::new(),
|
head: Message::new(),
|
||||||
|
req_data: RefCell::new(Extensions::default()),
|
||||||
|
conn_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create new Request instance
|
/// Create new Request instance
|
||||||
pub fn replace_payload<P1>(self, payload: Payload<P1>) -> (Request<P1>, Payload<P>) {
|
pub fn replace_payload<P1>(self, payload: Payload<P1>) -> (Request<P1>, Payload<P>) {
|
||||||
let pl = self.payload;
|
let pl = self.payload;
|
||||||
|
|
||||||
(
|
(
|
||||||
Request {
|
Request {
|
||||||
payload,
|
payload,
|
||||||
head: self.head,
|
head: self.head,
|
||||||
|
req_data: self.req_data,
|
||||||
|
conn_data: self.conn_data,
|
||||||
},
|
},
|
||||||
pl,
|
pl,
|
||||||
)
|
)
|
||||||
|
@ -93,7 +104,7 @@ impl<P> Request<P> {
|
||||||
|
|
||||||
/// Get request's payload
|
/// Get request's payload
|
||||||
pub fn take_payload(&mut self) -> Payload<P> {
|
pub fn take_payload(&mut self) -> Payload<P> {
|
||||||
std::mem::replace(&mut self.payload, Payload::None)
|
mem::replace(&mut self.payload, Payload::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Split request into request head and payload
|
/// Split request into request head and payload
|
||||||
|
@ -116,7 +127,7 @@ impl<P> Request<P> {
|
||||||
|
|
||||||
/// Mutable reference to the message's headers.
|
/// Mutable reference to the message's headers.
|
||||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||||
&mut self.head_mut().headers
|
&mut self.head.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request's uri.
|
/// Request's uri.
|
||||||
|
@ -128,7 +139,7 @@ impl<P> Request<P> {
|
||||||
/// Mutable reference to the request's uri.
|
/// Mutable reference to the request's uri.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn uri_mut(&mut self) -> &mut Uri {
|
pub fn uri_mut(&mut self) -> &mut Uri {
|
||||||
&mut self.head_mut().uri
|
&mut self.head.uri
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the Request method.
|
/// Read the Request method.
|
||||||
|
@ -170,6 +181,31 @@ impl<P> Request<P> {
|
||||||
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
|
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||||
self.head().peer_addr
|
self.head().peer_addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference a piece of connection data set in an [on-connect] callback.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// let opt_t = req.conn_data::<PeerCertificate>();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [on-connect]: crate::HttpServiceBuilder::on_connect_ext
|
||||||
|
pub fn conn_data<T: 'static>(&self) -> Option<&T> {
|
||||||
|
self.conn_data
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|container| container.get::<T>())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the connection data container if an [on-connect] callback was registered.
|
||||||
|
///
|
||||||
|
/// [on-connect]: crate::HttpServiceBuilder::on_connect_ext
|
||||||
|
pub fn take_conn_data(&mut self) -> Option<Rc<Extensions>> {
|
||||||
|
self.conn_data.take()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the request data container, leaving an empty one in it's place.
|
||||||
|
pub fn take_req_data(&mut self) -> Extensions {
|
||||||
|
mem::take(self.req_data.get_mut())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> fmt::Debug for Request<P> {
|
impl<P> fmt::Debug for Request<P> {
|
|
@ -8,9 +8,9 @@ use std::{
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{EitherBody, MessageBody},
|
body::{EitherBody, MessageBody},
|
||||||
error::{Error, HttpError},
|
error::{Error, HttpError},
|
||||||
header::{self, IntoHeaderPair, IntoHeaderValue},
|
header::{self, TryIntoHeaderPair, TryIntoHeaderValue},
|
||||||
message::{BoxedResponseHead, ConnectionType, ResponseHead},
|
responses::{BoxedResponseHead, ResponseHead},
|
||||||
Extensions, Response, StatusCode,
|
ConnectionType, Extensions, Response, StatusCode,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An HTTP response builder.
|
/// An HTTP response builder.
|
||||||
|
@ -47,7 +47,8 @@ impl ResponseBuilder {
|
||||||
/// Create response builder
|
/// Create response builder
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
// /// use actix_http::{Response, ResponseBuilder, StatusCode};, / ``
|
/// ```
|
||||||
|
/// use actix_http::{Response, ResponseBuilder, StatusCode};
|
||||||
/// let res: Response<_> = ResponseBuilder::default().finish();
|
/// let res: Response<_> = ResponseBuilder::default().finish();
|
||||||
/// assert_eq!(res.status(), StatusCode::OK);
|
/// assert_eq!(res.status(), StatusCode::OK);
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -62,7 +63,8 @@ impl ResponseBuilder {
|
||||||
/// Set HTTP status code of this response.
|
/// Set HTTP status code of this response.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
// /// use actix_http::{ResponseBuilder, StatusCode};, / ``
|
/// ```
|
||||||
|
/// use actix_http::{ResponseBuilder, StatusCode};
|
||||||
/// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish();
|
/// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish();
|
||||||
/// assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
/// assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -88,12 +90,9 @@ impl ResponseBuilder {
|
||||||
/// assert!(res.headers().contains_key("content-type"));
|
/// assert!(res.headers().contains_key("content-type"));
|
||||||
/// assert!(res.headers().contains_key("x-test"));
|
/// assert!(res.headers().contains_key("x-test"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
|
pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
match header.try_into_header_pair() {
|
match header.try_into_pair() {
|
||||||
Ok((key, value)) => {
|
Ok((key, value)) => {
|
||||||
parts.headers.insert(key, value);
|
parts.headers.insert(key, value);
|
||||||
}
|
}
|
||||||
|
@ -119,12 +118,9 @@ impl ResponseBuilder {
|
||||||
/// assert_eq!(res.headers().get_all("content-type").count(), 1);
|
/// assert_eq!(res.headers().get_all("content-type").count(), 1);
|
||||||
/// assert_eq!(res.headers().get_all("x-test").count(), 2);
|
/// assert_eq!(res.headers().get_all("x-test").count(), 2);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn append_header<H>(&mut self, header: H) -> &mut Self
|
pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||||
where
|
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
match header.try_into_header_pair() {
|
match header.try_into_pair() {
|
||||||
Ok((key, value)) => parts.headers.append(key, value),
|
Ok((key, value)) => parts.headers.append(key, value),
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
};
|
};
|
||||||
|
@ -155,7 +151,7 @@ impl ResponseBuilder {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
|
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
|
||||||
where
|
where
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
parts.set_connection_type(ConnectionType::Upgrade);
|
parts.set_connection_type(ConnectionType::Upgrade);
|
||||||
|
@ -193,7 +189,7 @@ impl ResponseBuilder {
|
||||||
#[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
|
||||||
V: IntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
match value.try_into_value() {
|
match value.try_into_value() {
|
|
@ -0,0 +1,208 @@
|
||||||
|
//! Response head type and caching pool.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
cell::{Ref, RefCell, RefMut},
|
||||||
|
ops,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
header::HeaderMap, message::Flags, ConnectionType, Extensions, StatusCode, Version,
|
||||||
|
};
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ResponseHead {
|
||||||
|
pub version: Version,
|
||||||
|
pub status: StatusCode,
|
||||||
|
pub headers: HeaderMap,
|
||||||
|
pub reason: Option<&'static str>,
|
||||||
|
pub(crate) extensions: RefCell<Extensions>,
|
||||||
|
flags: Flags,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseHead {
|
||||||
|
/// Create new instance of `ResponseHead` type
|
||||||
|
#[inline]
|
||||||
|
pub fn new(status: StatusCode) -> ResponseHead {
|
||||||
|
ResponseHead {
|
||||||
|
status,
|
||||||
|
version: Version::HTTP_11,
|
||||||
|
headers: HeaderMap::with_capacity(12),
|
||||||
|
reason: None,
|
||||||
|
flags: Flags::empty(),
|
||||||
|
extensions: RefCell::new(Extensions::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Read the message headers.
|
||||||
|
pub fn headers(&self) -> &HeaderMap {
|
||||||
|
&self.headers
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Mutable reference to the message headers.
|
||||||
|
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||||
|
&mut self.headers
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message extensions
|
||||||
|
#[inline]
|
||||||
|
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
|
self.extensions.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutable reference to a the message's extensions
|
||||||
|
#[inline]
|
||||||
|
pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
||||||
|
self.extensions.borrow_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Set connection type of the message
|
||||||
|
pub fn set_connection_type(&mut self, ctype: ConnectionType) {
|
||||||
|
match ctype {
|
||||||
|
ConnectionType::Close => self.flags.insert(Flags::CLOSE),
|
||||||
|
ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE),
|
||||||
|
ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn connection_type(&self) -> ConnectionType {
|
||||||
|
if self.flags.contains(Flags::CLOSE) {
|
||||||
|
ConnectionType::Close
|
||||||
|
} else if self.flags.contains(Flags::KEEP_ALIVE) {
|
||||||
|
ConnectionType::KeepAlive
|
||||||
|
} else if self.flags.contains(Flags::UPGRADE) {
|
||||||
|
ConnectionType::Upgrade
|
||||||
|
} else if self.version < Version::HTTP_11 {
|
||||||
|
ConnectionType::Close
|
||||||
|
} else {
|
||||||
|
ConnectionType::KeepAlive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if keep-alive is enabled
|
||||||
|
#[inline]
|
||||||
|
pub fn keep_alive(&self) -> bool {
|
||||||
|
self.connection_type() == ConnectionType::KeepAlive
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check upgrade status of this message
|
||||||
|
#[inline]
|
||||||
|
pub fn upgrade(&self) -> bool {
|
||||||
|
self.connection_type() == ConnectionType::Upgrade
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get custom reason for the response
|
||||||
|
#[inline]
|
||||||
|
pub fn reason(&self) -> &str {
|
||||||
|
self.reason.unwrap_or_else(|| {
|
||||||
|
self.status
|
||||||
|
.canonical_reason()
|
||||||
|
.unwrap_or("<unknown status code>")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn conn_type(&self) -> Option<ConnectionType> {
|
||||||
|
if self.flags.contains(Flags::CLOSE) {
|
||||||
|
Some(ConnectionType::Close)
|
||||||
|
} else if self.flags.contains(Flags::KEEP_ALIVE) {
|
||||||
|
Some(ConnectionType::KeepAlive)
|
||||||
|
} else if self.flags.contains(Flags::UPGRADE) {
|
||||||
|
Some(ConnectionType::Upgrade)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Get response body chunking state
|
||||||
|
pub fn chunked(&self) -> bool {
|
||||||
|
!self.flags.contains(Flags::NO_CHUNKING)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Set no chunking for payload
|
||||||
|
pub fn no_chunking(&mut self, val: bool) {
|
||||||
|
if val {
|
||||||
|
self.flags.insert(Flags::NO_CHUNKING);
|
||||||
|
} else {
|
||||||
|
self.flags.remove(Flags::NO_CHUNKING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct BoxedResponseHead {
|
||||||
|
head: Option<Box<ResponseHead>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoxedResponseHead {
|
||||||
|
/// Get new message from the pool of objects
|
||||||
|
pub fn new(status: StatusCode) -> Self {
|
||||||
|
RESPONSE_POOL.with(|p| p.get_message(status))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::Deref for BoxedResponseHead {
|
||||||
|
type Target = ResponseHead;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.head.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::DerefMut for BoxedResponseHead {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
self.head.as_mut().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for BoxedResponseHead {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(head) = self.head.take() {
|
||||||
|
RESPONSE_POOL.with(move |p| p.release(head))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request's objects pool
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct BoxedResponsePool(#[allow(clippy::vec_box)] RefCell<Vec<Box<ResponseHead>>>);
|
||||||
|
|
||||||
|
impl BoxedResponsePool {
|
||||||
|
fn create() -> BoxedResponsePool {
|
||||||
|
BoxedResponsePool(RefCell::new(Vec::with_capacity(128)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get message from the pool
|
||||||
|
#[inline]
|
||||||
|
fn get_message(&self, status: StatusCode) -> BoxedResponseHead {
|
||||||
|
if let Some(mut head) = self.0.borrow_mut().pop() {
|
||||||
|
head.reason = None;
|
||||||
|
head.status = status;
|
||||||
|
head.headers.clear();
|
||||||
|
head.flags = Flags::empty();
|
||||||
|
BoxedResponseHead { head: Some(head) }
|
||||||
|
} else {
|
||||||
|
BoxedResponseHead {
|
||||||
|
head: Some(Box::new(ResponseHead::new(status))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Release request instance
|
||||||
|
#[inline]
|
||||||
|
fn release(&self, mut msg: Box<ResponseHead>) {
|
||||||
|
let pool = &mut self.0.borrow_mut();
|
||||||
|
if pool.len() < 128 {
|
||||||
|
msg.extensions.get_mut().clear();
|
||||||
|
pool.push(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
//! HTTP response.
|
||||||
|
|
||||||
|
mod builder;
|
||||||
|
mod head;
|
||||||
|
#[allow(clippy::module_inception)]
|
||||||
|
mod response;
|
||||||
|
|
||||||
|
pub use self::builder::ResponseBuilder;
|
||||||
|
pub(crate) use self::head::BoxedResponseHead;
|
||||||
|
pub use self::head::ResponseHead;
|
||||||
|
pub use self::response::Response;
|
|
@ -10,10 +10,9 @@ use bytestring::ByteString;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BoxBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
extensions::Extensions,
|
header::{self, HeaderMap, TryIntoHeaderValue},
|
||||||
header::{self, HeaderMap, IntoHeaderValue},
|
responses::BoxedResponseHead,
|
||||||
message::{BoxedResponseHead, ResponseHead},
|
Error, Extensions, ResponseBuilder, ResponseHead, StatusCode,
|
||||||
Error, ResponseBuilder, StatusCode,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An HTTP response.
|
/// An HTTP response.
|
||||||
|
@ -170,7 +169,7 @@ impl<B> Response<B> {
|
||||||
/// Returns split head and body.
|
/// Returns split head and body.
|
||||||
///
|
///
|
||||||
/// # Implementation Notes
|
/// # Implementation Notes
|
||||||
/// Due to internal performance optimisations, the first element of the returned tuple is a
|
/// Due to internal performance optimizations, the first element of the returned tuple is a
|
||||||
/// `Response` as well but only contains the head of the response this was called on.
|
/// `Response` as well but only contains the head of the response this was called on.
|
||||||
pub fn into_parts(self) -> (Response<()>, B) {
|
pub fn into_parts(self) -> (Response<()>, B) {
|
||||||
self.replace_body(())
|
self.replace_body(())
|
||||||
|
@ -194,7 +193,7 @@ impl<B> Response<B> {
|
||||||
where
|
where
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
{
|
{
|
||||||
self.map_body(|_, body| BoxBody::new(body))
|
self.map_body(|_, body| body.boxed())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns body, consuming this response.
|
/// Returns body, consuming this response.
|
||||||
|
@ -231,9 +230,7 @@ impl<B: Default> Default for Response<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: Into<Response<BoxBody>>, E: Into<Error>> From<Result<I, E>>
|
impl<I: Into<Response<BoxBody>>, E: Into<Error>> From<Result<I, E>> for Response<BoxBody> {
|
||||||
for Response<BoxBody>
|
|
||||||
{
|
|
||||||
fn from(res: Result<I, E>) -> Self {
|
fn from(res: Result<I, E>) -> Self {
|
||||||
match res {
|
match res {
|
||||||
Ok(val) => val.into(),
|
Ok(val) => val.into(),
|
|
@ -161,11 +161,7 @@ where
|
||||||
X::Error: Into<Response<BoxBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
|
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<(Request, Framed<TcpStream, h1::Codec>), Config = (), Response = ()>,
|
||||||
(Request, Framed<TcpStream, h1::Codec>),
|
|
||||||
Config = (),
|
|
||||||
Response = (),
|
|
||||||
>,
|
|
||||||
U::Future: 'static,
|
U::Future: 'static,
|
||||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
|
@ -381,9 +377,9 @@ where
|
||||||
|
|
||||||
let upgrade = match upgrade {
|
let upgrade = match upgrade {
|
||||||
Some(upgrade) => {
|
Some(upgrade) => {
|
||||||
let upgrade = upgrade.await.map_err(|e| {
|
let upgrade = upgrade
|
||||||
log::error!("Init http upgrade service error: {:?}", e)
|
.await
|
||||||
})?;
|
.map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?;
|
||||||
Some(upgrade)
|
Some(upgrade)
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
|
@ -497,9 +493,9 @@ where
|
||||||
type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
|
type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
|
||||||
|
|
||||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
self._poll_ready(cx).map_err(|e| {
|
self._poll_ready(cx).map_err(|err| {
|
||||||
log::error!("HTTP service readiness error: {:?}", e);
|
log::error!("HTTP service readiness error: {:?}", err);
|
||||||
DispatchError::Service(e)
|
DispatchError::Service(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,8 +503,7 @@ where
|
||||||
&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 conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||||
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
|
||||||
|
|
||||||
match proto {
|
match proto {
|
||||||
Protocol::Http2 => HttpServiceHandlerResponse {
|
Protocol::Http2 => HttpServiceHandlerResponse {
|
||||||
|
@ -517,7 +512,7 @@ where
|
||||||
h2::handshake_with_timeout(io, &self.cfg),
|
h2::handshake_with_timeout(io, &self.cfg),
|
||||||
self.cfg.clone(),
|
self.cfg.clone(),
|
||||||
self.flow.clone(),
|
self.flow.clone(),
|
||||||
on_connect_data,
|
conn_data,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
@ -527,10 +522,10 @@ where
|
||||||
state: State::H1 {
|
state: State::H1 {
|
||||||
dispatcher: h1::Dispatcher::new(
|
dispatcher: h1::Dispatcher::new(
|
||||||
io,
|
io,
|
||||||
self.cfg.clone(),
|
|
||||||
self.flow.clone(),
|
self.flow.clone(),
|
||||||
on_connect_data,
|
self.cfg.clone(),
|
||||||
peer_addr,
|
peer_addr,
|
||||||
|
conn_data,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -627,17 +622,11 @@ where
|
||||||
StateProj::H2Handshake { handshake: data } => {
|
StateProj::H2Handshake { handshake: data } => {
|
||||||
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
|
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
|
||||||
Ok((conn, timer)) => {
|
Ok((conn, timer)) => {
|
||||||
let (_, config, flow, on_connect_data, peer_addr) =
|
let (_, config, flow, conn_data, peer_addr) = data.take().unwrap();
|
||||||
data.take().unwrap();
|
|
||||||
|
|
||||||
self.as_mut().project().state.set(State::H2 {
|
self.as_mut().project().state.set(State::H2 {
|
||||||
dispatcher: h2::Dispatcher::new(
|
dispatcher: h2::Dispatcher::new(
|
||||||
flow,
|
conn, flow, config, peer_addr, conn_data, timer,
|
||||||
conn,
|
|
||||||
on_connect_data,
|
|
||||||
config,
|
|
||||||
peer_addr,
|
|
||||||
timer,
|
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
|
|
|
@ -14,7 +14,7 @@ use bytes::{Bytes, BytesMut};
|
||||||
use http::{Method, Uri, Version};
|
use http::{Method, Uri, Version};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
header::{HeaderMap, IntoHeaderPair},
|
header::{HeaderMap, TryIntoHeaderPair},
|
||||||
payload::Payload,
|
payload::Payload,
|
||||||
Request,
|
Request,
|
||||||
};
|
};
|
||||||
|
@ -92,11 +92,8 @@ impl TestRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a header, replacing any that were set with an equivalent field name.
|
/// Insert a header, replacing any that were set with an equivalent field name.
|
||||||
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
|
pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||||
where
|
match header.try_into_pair() {
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
match header.try_into_header_pair() {
|
|
||||||
Ok((key, value)) => {
|
Ok((key, value)) => {
|
||||||
parts(&mut self.0).headers.insert(key, value);
|
parts(&mut self.0).headers.insert(key, value);
|
||||||
}
|
}
|
||||||
|
@ -109,11 +106,8 @@ impl TestRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a header, keeping any that were set with an equivalent field name.
|
/// Append a header, keeping any that were set with an equivalent field name.
|
||||||
pub fn append_header<H>(&mut self, header: H) -> &mut Self
|
pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||||
where
|
match header.try_into_pair() {
|
||||||
H: IntoHeaderPair,
|
|
||||||
{
|
|
||||||
match header.try_into_header_pair() {
|
|
||||||
Ok((key, value)) => {
|
Ok((key, value)) => {
|
||||||
parts(&mut self.0).headers.append(key, value);
|
parts(&mut self.0).headers.append(key, value);
|
||||||
}
|
}
|
||||||
|
@ -126,7 +120,7 @@ impl TestRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set request payload.
|
/// Set request payload.
|
||||||
pub fn set_payload<B: Into<Bytes>>(&mut self, data: B) -> &mut Self {
|
pub fn set_payload(&mut self, data: impl Into<Bytes>) -> &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());
|
||||||
parts(&mut self.0).payload = Some(payload.into());
|
parts(&mut self.0).payload = Some(payload.into());
|
||||||
|
@ -270,7 +264,7 @@ impl TestSeqBuffer {
|
||||||
|
|
||||||
/// Create new empty `TestBuffer` instance.
|
/// Create new empty `TestBuffer` instance.
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self::new("")
|
Self::new(BytesMut::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_buf(&self) -> Ref<'_, BytesMut> {
|
pub fn read_buf(&self) -> Ref<'_, BytesMut> {
|
||||||
|
|
|
@ -224,9 +224,7 @@ impl Decoder for Codec {
|
||||||
OpCode::Continue => {
|
OpCode::Continue => {
|
||||||
if self.flags.contains(Flags::CONTINUATION) {
|
if self.flags.contains(Flags::CONTINUATION) {
|
||||||
Ok(Some(Frame::Continuation(Item::Continue(
|
Ok(Some(Frame::Continuation(Item::Continue(
|
||||||
payload
|
payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
|
||||||
.map(|pl| pl.freeze())
|
|
||||||
.unwrap_or_else(Bytes::new),
|
|
||||||
))))
|
))))
|
||||||
} else {
|
} else {
|
||||||
Err(ProtocolError::ContinuationNotStarted)
|
Err(ProtocolError::ContinuationNotStarted)
|
||||||
|
@ -236,9 +234,7 @@ impl Decoder for Codec {
|
||||||
if !self.flags.contains(Flags::CONTINUATION) {
|
if !self.flags.contains(Flags::CONTINUATION) {
|
||||||
self.flags.insert(Flags::CONTINUATION);
|
self.flags.insert(Flags::CONTINUATION);
|
||||||
Ok(Some(Frame::Continuation(Item::FirstBinary(
|
Ok(Some(Frame::Continuation(Item::FirstBinary(
|
||||||
payload
|
payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
|
||||||
.map(|pl| pl.freeze())
|
|
||||||
.unwrap_or_else(Bytes::new),
|
|
||||||
))))
|
))))
|
||||||
} else {
|
} else {
|
||||||
Err(ProtocolError::ContinuationStarted)
|
Err(ProtocolError::ContinuationStarted)
|
||||||
|
@ -248,9 +244,7 @@ impl Decoder for Codec {
|
||||||
if !self.flags.contains(Flags::CONTINUATION) {
|
if !self.flags.contains(Flags::CONTINUATION) {
|
||||||
self.flags.insert(Flags::CONTINUATION);
|
self.flags.insert(Flags::CONTINUATION);
|
||||||
Ok(Some(Frame::Continuation(Item::FirstText(
|
Ok(Some(Frame::Continuation(Item::FirstText(
|
||||||
payload
|
payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
|
||||||
.map(|pl| pl.freeze())
|
|
||||||
.unwrap_or_else(Bytes::new),
|
|
||||||
))))
|
))))
|
||||||
} else {
|
} else {
|
||||||
Err(ProtocolError::ContinuationStarted)
|
Err(ProtocolError::ContinuationStarted)
|
||||||
|
|
|
@ -304,8 +304,7 @@ mod inner {
|
||||||
let item = match this.framed.next_item(cx) {
|
let item = match this.framed.next_item(cx) {
|
||||||
Poll::Ready(Some(Ok(el))) => el,
|
Poll::Ready(Some(Ok(el))) => el,
|
||||||
Poll::Ready(Some(Err(err))) => {
|
Poll::Ready(Some(Err(err))) => {
|
||||||
*this.state =
|
*this.state = State::FramedError(DispatcherError::Decoder(err));
|
||||||
State::FramedError(DispatcherError::Decoder(err));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Poll::Pending => return false,
|
Poll::Pending => return false,
|
||||||
|
@ -348,8 +347,7 @@ mod inner {
|
||||||
match Pin::new(&mut this.rx).poll_next(cx) {
|
match Pin::new(&mut this.rx).poll_next(cx) {
|
||||||
Poll::Ready(Some(Ok(Message::Item(msg)))) => {
|
Poll::Ready(Some(Ok(Message::Item(msg)))) => {
|
||||||
if let Err(err) = this.framed.as_mut().write(msg) {
|
if let Err(err) = this.framed.as_mut().write(msg) {
|
||||||
*this.state =
|
*this.state = State::FramedError(DispatcherError::Encoder(err));
|
||||||
State::FramedError(DispatcherError::Encoder(err));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -371,8 +369,7 @@ mod inner {
|
||||||
Poll::Ready(Ok(_)) => {}
|
Poll::Ready(Ok(_)) => {}
|
||||||
Poll::Ready(Err(err)) => {
|
Poll::Ready(Err(err)) => {
|
||||||
debug!("Error sending data: {:?}", err);
|
debug!("Error sending data: {:?}", err);
|
||||||
*this.state =
|
*this.state = State::FramedError(DispatcherError::Encoder(err));
|
||||||
State::FramedError(DispatcherError::Encoder(err));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -432,9 +429,7 @@ mod inner {
|
||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
State::FramedError(_) => {
|
State::FramedError(_) => Poll::Ready(Err(this.state.take_framed_error())),
|
||||||
Poll::Ready(Err(this.state.take_framed_error()))
|
|
||||||
}
|
|
||||||
State::Stopping => Poll::Ready(Ok(())),
|
State::Stopping => Poll::Ready(Ok(())),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,7 @@ impl Parser {
|
||||||
src: &[u8],
|
src: &[u8],
|
||||||
server: bool,
|
server: bool,
|
||||||
max_size: usize,
|
max_size: usize,
|
||||||
) -> Result<Option<(usize, bool, OpCode, usize, Option<[u8; 4]>)>, ProtocolError>
|
) -> Result<Option<(usize, bool, OpCode, usize, Option<[u8; 4]>)>, ProtocolError> {
|
||||||
{
|
|
||||||
let chunk_len = src.len();
|
let chunk_len = src.len();
|
||||||
|
|
||||||
let mut idx = 2;
|
let mut idx = 2;
|
||||||
|
@ -228,15 +227,11 @@ mod tests {
|
||||||
payload: Bytes,
|
payload: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_none(
|
fn is_none(frm: &Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>) -> bool {
|
||||||
frm: &Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>,
|
|
||||||
) -> bool {
|
|
||||||
matches!(*frm, Ok(None))
|
matches!(*frm, Ok(None))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract(
|
fn extract(frm: Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>) -> F {
|
||||||
frm: Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>,
|
|
||||||
) -> F {
|
|
||||||
match frm {
|
match frm {
|
||||||
Ok(Some((finished, opcode, payload))) => F {
|
Ok(Some((finished, opcode, payload))) => F {
|
||||||
finished,
|
finished,
|
||||||
|
|
|
@ -54,8 +54,8 @@ mod tests {
|
||||||
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
||||||
|
|
||||||
let unmasked = vec![
|
let unmasked = vec![
|
||||||
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
|
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9,
|
||||||
0x74, 0xf9, 0x12, 0x03,
|
0x12, 0x03,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Check masking with proper alignment.
|
// Check masking with proper alignment.
|
||||||
|
@ -85,8 +85,8 @@ mod tests {
|
||||||
fn test_apply_mask() {
|
fn test_apply_mask() {
|
||||||
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
||||||
let unmasked = vec![
|
let unmasked = vec![
|
||||||
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
|
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9,
|
||||||
0x74, 0xf9, 0x12, 0x03,
|
0x12, 0x03,
|
||||||
];
|
];
|
||||||
|
|
||||||
for data_len in 0..=unmasked.len() {
|
for data_len in 0..=unmasked.len() {
|
||||||
|
|
|
@ -9,9 +9,7 @@ use derive_more::{Display, Error, From};
|
||||||
use http::{header, Method, StatusCode};
|
use http::{header, Method, StatusCode};
|
||||||
|
|
||||||
use crate::body::BoxBody;
|
use crate::body::BoxBody;
|
||||||
use crate::{
|
use crate::{header::HeaderValue, RequestHead, Response, ResponseBuilder};
|
||||||
header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod codec;
|
mod codec;
|
||||||
mod dispatcher;
|
mod dispatcher;
|
||||||
|
@ -101,8 +99,9 @@ impl From<HandshakeError> for Response<BoxBody> {
|
||||||
match err {
|
match err {
|
||||||
HandshakeError::GetMethodRequired => {
|
HandshakeError::GetMethodRequired => {
|
||||||
let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED);
|
let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED);
|
||||||
res.headers_mut()
|
#[allow(clippy::declare_interior_mutable_const)]
|
||||||
.insert(header::ALLOW, HeaderValue::from_static("GET"));
|
const HV_GET: HeaderValue = HeaderValue::from_static("GET");
|
||||||
|
res.headers_mut().insert(header::ALLOW, HV_GET);
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{body::BoxBody, HttpMessage, HttpService, Request, Response, StatusCode};
|
||||||
body::BoxBody, HttpMessage, HttpService, Request, Response, StatusCode,
|
|
||||||
};
|
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
use actix_service::ServiceFactoryExt;
|
use actix_service::ServiceFactoryExt;
|
||||||
use actix_utils::future;
|
use actix_utils::future;
|
||||||
|
|
|
@ -8,7 +8,7 @@ use actix_http::{
|
||||||
body::{BodyStream, BoxBody, SizedStream},
|
body::{BodyStream, BoxBody, SizedStream},
|
||||||
error::PayloadError,
|
error::PayloadError,
|
||||||
header::{self, HeaderValue},
|
header::{self, HeaderValue},
|
||||||
Error, HttpMessage, HttpService, Method, Request, Response, StatusCode, Version,
|
Error, HttpService, Method, Request, Response, StatusCode, Version,
|
||||||
};
|
};
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
use actix_service::{fn_service, ServiceFactoryExt};
|
use actix_service::{fn_service, ServiceFactoryExt};
|
||||||
|
@ -101,7 +101,7 @@ async fn test_h2_1() -> io::Result<()> {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_h2_body() -> io::Result<()> {
|
async fn test_h2_body() -> io::Result<()> {
|
||||||
let data = "HELLOWORLD".to_owned().repeat(64 * 1024);
|
let data = "HELLOWORLD".to_owned().repeat(64 * 1024); // 640 KiB
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|mut req: Request<_>| async move {
|
.h2(|mut req: Request<_>| async move {
|
||||||
|
@ -170,10 +170,11 @@ async fn test_h2_headers() {
|
||||||
|
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
let data = data.clone();
|
let data = data.clone();
|
||||||
HttpService::build().h2(move |_| {
|
HttpService::build()
|
||||||
let mut builder = Response::build(StatusCode::OK);
|
.h2(move |_| {
|
||||||
for idx in 0..90 {
|
let mut builder = Response::build(StatusCode::OK);
|
||||||
builder.insert_header(
|
for idx in 0..90 {
|
||||||
|
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 \
|
||||||
|
@ -189,12 +190,13 @@ 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 ",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
ok::<_, Infallible>(builder.body(data.clone()))
|
ok::<_, Infallible>(builder.body(data.clone()))
|
||||||
})
|
})
|
||||||
.openssl(tls_config())
|
.openssl(tls_config())
|
||||||
.map_err(|_| ())
|
.map_err(|_| ())
|
||||||
}).await;
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
let response = srv.sget("/").send().await.unwrap();
|
let response = srv.sget("/").send().await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
@ -315,9 +317,8 @@ async fn test_h2_body_length() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| async {
|
.h2(|_| async {
|
||||||
let body = once(async {
|
let body =
|
||||||
Ok::<_, Infallible>(Bytes::from_static(STR.as_ref()))
|
once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) });
|
||||||
});
|
|
||||||
|
|
||||||
Ok::<_, Infallible>(
|
Ok::<_, Infallible>(
|
||||||
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
|
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
|
||||||
|
@ -430,7 +431,7 @@ async fn test_h2_on_connect() {
|
||||||
data.insert(20isize);
|
data.insert(20isize);
|
||||||
})
|
})
|
||||||
.h2(|req: Request| {
|
.h2(|req: Request| {
|
||||||
assert!(req.extensions().contains::<isize>());
|
assert!(req.conn_data::<isize>().is_some());
|
||||||
ok::<_, Infallible>(Response::ok())
|
ok::<_, Infallible>(Response::ok())
|
||||||
})
|
})
|
||||||
.openssl(tls_config())
|
.openssl(tls_config())
|
||||||
|
|
|
@ -7,6 +7,7 @@ use std::{
|
||||||
io::{self, BufReader, Write},
|
io::{self, BufReader, Write},
|
||||||
net::{SocketAddr, TcpStream as StdTcpStream},
|
net::{SocketAddr, TcpStream as StdTcpStream},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
task::Poll,
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
|
@ -16,25 +17,37 @@ use actix_http::{
|
||||||
Error, HttpService, Method, Request, Response, StatusCode, Version,
|
Error, HttpService, Method, Request, Response, StatusCode, Version,
|
||||||
};
|
};
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
|
use actix_rt::pin;
|
||||||
use actix_service::{fn_factory_with_config, fn_service};
|
use actix_service::{fn_factory_with_config, fn_service};
|
||||||
use actix_tls::connect::rustls::webpki_roots_cert_store;
|
use actix_tls::connect::rustls::webpki_roots_cert_store;
|
||||||
use actix_utils::future::{err, ok};
|
use actix_utils::future::{err, ok, poll_fn};
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use derive_more::{Display, Error};
|
use derive_more::{Display, Error};
|
||||||
use futures_core::Stream;
|
use futures_core::{ready, Stream};
|
||||||
use futures_util::stream::{once, StreamExt as _};
|
use futures_util::stream::once;
|
||||||
use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName};
|
use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName};
|
||||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||||
|
|
||||||
async fn load_body<S>(mut stream: S) -> Result<BytesMut, PayloadError>
|
async fn load_body<S>(stream: S) -> Result<BytesMut, PayloadError>
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
|
S: Stream<Item = Result<Bytes, PayloadError>>,
|
||||||
{
|
{
|
||||||
let mut body = BytesMut::new();
|
let mut buf = BytesMut::new();
|
||||||
while let Some(item) = stream.next().await {
|
|
||||||
body.extend_from_slice(&item?)
|
pin!(stream);
|
||||||
}
|
|
||||||
Ok(body)
|
poll_fn(|cx| loop {
|
||||||
|
let body = stream.as_mut();
|
||||||
|
|
||||||
|
match ready!(body.poll_next(cx)) {
|
||||||
|
Some(Ok(bytes)) => buf.extend_from_slice(&*bytes),
|
||||||
|
None => return Poll::Ready(Ok(())),
|
||||||
|
Some(Err(err)) => return Poll::Ready(Err(err)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tls_config() -> RustlsServerConfig {
|
fn tls_config() -> RustlsServerConfig {
|
||||||
|
@ -238,10 +251,11 @@ async fn test_h2_headers() {
|
||||||
|
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
let data = data.clone();
|
let data = data.clone();
|
||||||
HttpService::build().h2(move |_| {
|
HttpService::build()
|
||||||
let mut config = Response::build(StatusCode::OK);
|
.h2(move |_| {
|
||||||
for idx in 0..90 {
|
let mut config = Response::build(StatusCode::OK);
|
||||||
config.insert_header((
|
for idx in 0..90 {
|
||||||
|
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 \
|
||||||
|
@ -257,11 +271,12 @@ 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 ",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
ok::<_, Infallible>(config.body(data.clone()))
|
ok::<_, Infallible>(config.body(data.clone()))
|
||||||
})
|
})
|
||||||
.rustls(tls_config())
|
.rustls(tls_config())
|
||||||
}).await;
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
let response = srv.sget("/").send().await.unwrap();
|
let response = srv.sget("/").send().await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::{self, BodyStream, BoxBody, SizedStream},
|
body::{self, BodyStream, BoxBody, SizedStream},
|
||||||
header, Error, HttpMessage, HttpService, KeepAlive, Request, Response, StatusCode,
|
header, Error, HttpService, KeepAlive, Request, Response, StatusCode,
|
||||||
};
|
};
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
use actix_rt::time::sleep;
|
use actix_rt::time::sleep;
|
||||||
|
@ -154,9 +154,7 @@ async fn test_chunked_payload() {
|
||||||
})
|
})
|
||||||
.fold(0usize, |acc, chunk| ready(acc + chunk.len()))
|
.fold(0usize, |acc, chunk| ready(acc + chunk.len()))
|
||||||
.map(|req_size| {
|
.map(|req_size| {
|
||||||
Ok::<_, Error>(
|
Ok::<_, Error>(Response::ok().set_body(format!("size={}", req_size)))
|
||||||
Response::ok().set_body(format!("size={}", req_size)),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
.tcp()
|
.tcp()
|
||||||
|
@ -165,8 +163,7 @@ async fn test_chunked_payload() {
|
||||||
|
|
||||||
let returned_size = {
|
let returned_size = {
|
||||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||||
let _ = stream
|
let _ = stream.write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||||
.write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n");
|
|
||||||
|
|
||||||
for chunk_size in chunk_sizes.iter() {
|
for chunk_size in chunk_sizes.iter() {
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
|
@ -293,8 +290,7 @@ async fn test_http1_keepalive_close() {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||||
let _ =
|
let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n");
|
||||||
stream.write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n");
|
|
||||||
let mut data = vec![0; 1024];
|
let mut data = vec![0; 1024];
|
||||||
let _ = stream.read(&mut data);
|
let _ = stream.read(&mut data);
|
||||||
assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
|
assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
|
||||||
|
@ -338,8 +334,8 @@ async fn test_http10_keepalive() {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||||
let _ = stream
|
let _ =
|
||||||
.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n");
|
stream.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n");
|
||||||
let mut data = vec![0; 1024];
|
let mut data = vec![0; 1024];
|
||||||
let _ = stream.read(&mut data);
|
let _ = stream.read(&mut data);
|
||||||
assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n");
|
assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n");
|
||||||
|
@ -436,10 +432,11 @@ async fn test_h1_headers() {
|
||||||
|
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
let data = data.clone();
|
let data = data.clone();
|
||||||
HttpService::build().h1(move |_| {
|
HttpService::build()
|
||||||
let mut builder = Response::build(StatusCode::OK);
|
.h1(move |_| {
|
||||||
for idx in 0..90 {
|
let mut builder = Response::build(StatusCode::OK);
|
||||||
builder.insert_header((
|
for idx in 0..90 {
|
||||||
|
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 \
|
||||||
|
@ -455,10 +452,12 @@ 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 ",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
ok::<_, Infallible>(builder.body(data.clone()))
|
ok::<_, Infallible>(builder.body(data.clone()))
|
||||||
}).tcp()
|
})
|
||||||
}).await;
|
.tcp()
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
let response = srv.get("/").send().await.unwrap();
|
let response = srv.get("/").send().await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
@ -655,9 +654,7 @@ async fn test_h1_body_chunked_implicit() {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| {
|
.h1(|_| {
|
||||||
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
||||||
ok::<_, Infallible>(
|
ok::<_, Infallible>(Response::build(StatusCode::OK).body(BodyStream::new(body)))
|
||||||
Response::build(StatusCode::OK).body(BodyStream::new(body)),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.tcp()
|
.tcp()
|
||||||
})
|
})
|
||||||
|
@ -748,7 +745,7 @@ async fn test_h1_on_connect() {
|
||||||
data.insert(20isize);
|
data.insert(20isize);
|
||||||
})
|
})
|
||||||
.h1(|req: Request| {
|
.h1(|req: Request| {
|
||||||
assert!(req.extensions().contains::<isize>());
|
assert!(req.conn_data::<isize>().is_some());
|
||||||
ok::<_, Infallible>(Response::ok())
|
ok::<_, Infallible>(Response::ok())
|
||||||
})
|
})
|
||||||
.tcp()
|
.tcp()
|
||||||
|
@ -776,10 +773,8 @@ async fn test_not_modified_spec_h1() {
|
||||||
.h1(|req: Request| {
|
.h1(|req: Request| {
|
||||||
let res: Response<BoxBody> = match req.path() {
|
let res: Response<BoxBody> = match req.path() {
|
||||||
// with no content-length
|
// with no content-length
|
||||||
"/none" => {
|
"/none" => Response::with_body(StatusCode::NOT_MODIFIED, body::None::new())
|
||||||
Response::with_body(StatusCode::NOT_MODIFIED, body::None::new())
|
.map_into_boxed_body(),
|
||||||
.map_into_boxed_body()
|
|
||||||
}
|
|
||||||
|
|
||||||
// with no content-length
|
// with no content-length
|
||||||
"/body" => Response::with_body(StatusCode::NOT_MODIFIED, "1234")
|
"/body" => Response::with_body(StatusCode::NOT_MODIFIED, "1234")
|
||||||
|
@ -787,10 +782,8 @@ async fn test_not_modified_spec_h1() {
|
||||||
|
|
||||||
// with manual content-length header and specific None body
|
// with manual content-length header and specific None body
|
||||||
"/cl-none" => {
|
"/cl-none" => {
|
||||||
let mut res = Response::with_body(
|
let mut res =
|
||||||
StatusCode::NOT_MODIFIED,
|
Response::with_body(StatusCode::NOT_MODIFIED, body::None::new());
|
||||||
body::None::new(),
|
|
||||||
);
|
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert(CL.clone(), header::HeaderValue::from_static("24"));
|
.insert(CL.clone(), header::HeaderValue::from_static("24"));
|
||||||
res.map_into_boxed_body()
|
res.map_into_boxed_body()
|
||||||
|
@ -798,8 +791,7 @@ async fn test_not_modified_spec_h1() {
|
||||||
|
|
||||||
// with manual content-length header and ignore-able body
|
// with manual content-length header and ignore-able body
|
||||||
"/cl-body" => {
|
"/cl-body" => {
|
||||||
let mut res =
|
let mut res = Response::with_body(StatusCode::NOT_MODIFIED, "1234");
|
||||||
Response::with_body(StatusCode::NOT_MODIFIED, "1234");
|
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert(CL.clone(), header::HeaderValue::from_static("4"));
|
.insert(CL.clone(), header::HeaderValue::from_static("4"));
|
||||||
res.map_into_boxed_body()
|
res.map_into_boxed_body()
|
||||||
|
|
|
@ -56,8 +56,9 @@ impl From<WsServiceError> for Response<BoxBody> {
|
||||||
WsServiceError::Http(err) => err.into(),
|
WsServiceError::Http(err) => err.into(),
|
||||||
WsServiceError::Ws(err) => err.into(),
|
WsServiceError::Ws(err) => err.into(),
|
||||||
WsServiceError::Io(_err) => unreachable!(),
|
WsServiceError::Io(_err) => unreachable!(),
|
||||||
WsServiceError::Dispatcher => Response::internal_server_error()
|
WsServiceError::Dispatcher => {
|
||||||
.set_body(BoxBody::new(format!("{}", err))),
|
Response::internal_server_error().set_body(BoxBody::new(format!("{}", err)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,9 +98,7 @@ where
|
||||||
async fn service(msg: Frame) -> Result<Message, Error> {
|
async fn service(msg: Frame) -> Result<Message, Error> {
|
||||||
let msg = match msg {
|
let msg = match msg {
|
||||||
Frame::Ping(msg) => Message::Pong(msg),
|
Frame::Ping(msg) => Message::Pong(msg),
|
||||||
Frame::Text(text) => {
|
Frame::Text(text) => Message::Text(String::from_utf8_lossy(&text).into_owned().into()),
|
||||||
Message::Text(String::from_utf8_lossy(&text).into_owned().into())
|
|
||||||
}
|
|
||||||
Frame::Binary(bin) => Message::Binary(bin),
|
Frame::Binary(bin) => Message::Binary(bin),
|
||||||
Frame::Continuation(item) => Message::Continuation(item),
|
Frame::Continuation(item) => Message::Continuation(item),
|
||||||
Frame::Close(reason) => Message::Close(reason),
|
Frame::Close(reason) => Message::Close(reason),
|
||||||
|
|
|
@ -3,116 +3,124 @@
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.0-beta.11 - 2021-12-27
|
||||||
|
* No significant changes since `0.4.0-beta.10`.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.0-beta.10 - 2021-12-11
|
||||||
|
- No significant changes since `0.4.0-beta.9`.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.9 - 2021-12-01
|
## 0.4.0-beta.9 - 2021-12-01
|
||||||
* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463]
|
- Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463]
|
||||||
|
|
||||||
[#2463]: https://github.com/actix/actix-web/pull/2463
|
[#2463]: https://github.com/actix/actix-web/pull/2463
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.8 - 2021-11-22
|
## 0.4.0-beta.8 - 2021-11-22
|
||||||
* Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451]
|
- Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451]
|
||||||
* Added `MultipartError::NoContentDisposition` variant. [#2451]
|
- Added `MultipartError::NoContentDisposition` variant. [#2451]
|
||||||
* Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451]
|
- Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451]
|
||||||
* Added `Field::name` method for getting the field name. [#2451]
|
- Added `Field::name` method for getting the field name. [#2451]
|
||||||
* `MultipartError` now marks variants with inner errors as the source. [#2451]
|
- `MultipartError` now marks variants with inner errors as the source. [#2451]
|
||||||
* `MultipartError` is now marked as non-exhaustive. [#2451]
|
- `MultipartError` is now marked as non-exhaustive. [#2451]
|
||||||
|
|
||||||
[#2451]: https://github.com/actix/actix-web/pull/2451
|
[#2451]: https://github.com/actix/actix-web/pull/2451
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.7 - 2021-10-20
|
## 0.4.0-beta.7 - 2021-10-20
|
||||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.6 - 2021-09-09
|
## 0.4.0-beta.6 - 2021-09-09
|
||||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.5 - 2021-06-17
|
## 0.4.0-beta.5 - 2021-06-17
|
||||||
* No notable changes.
|
- No notable changes.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.4 - 2021-04-02
|
## 0.4.0-beta.4 - 2021-04-02
|
||||||
* No notable changes.
|
- No notable changes.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.3 - 2021-03-09
|
## 0.4.0-beta.3 - 2021-03-09
|
||||||
* No notable changes.
|
- No notable changes.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.2 - 2021-02-10
|
## 0.4.0-beta.2 - 2021-02-10
|
||||||
* No notable changes.
|
- 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]
|
||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
[#1513]: https://github.com/actix/actix-web/pull/1513
|
[#1513]: https://github.com/actix/actix-web/pull/1513
|
||||||
|
|
||||||
|
|
||||||
## 0.3.0 - 2020-09-11
|
## 0.3.0 - 2020-09-11
|
||||||
* No significant changes from `0.3.0-beta.2`.
|
- No significant changes from `0.3.0-beta.2`.
|
||||||
|
|
||||||
|
|
||||||
## 0.3.0-beta.2 - 2020-09-10
|
## 0.3.0-beta.2 - 2020-09-10
|
||||||
* Update `actix-*` dependencies to latest versions.
|
- Update `actix-*` dependencies to latest versions.
|
||||||
|
|
||||||
|
|
||||||
## 0.3.0-beta.1 - 2020-07-15
|
## 0.3.0-beta.1 - 2020-07-15
|
||||||
* Update `actix-web` to 3.0.0-beta.1
|
- Update `actix-web` to 3.0.0-beta.1
|
||||||
|
|
||||||
|
|
||||||
## 0.3.0-alpha.1 - 2020-05-25
|
## 0.3.0-alpha.1 - 2020-05-25
|
||||||
* Update `actix-web` to 3.0.0-alpha.3
|
- Update `actix-web` to 3.0.0-alpha.3
|
||||||
* Bump minimum supported Rust version to 1.40
|
- Bump minimum supported Rust version to 1.40
|
||||||
* Minimize `futures` dependencies
|
- Minimize `futures` dependencies
|
||||||
* Remove the unused `time` dependency
|
- Remove the unused `time` dependency
|
||||||
* Fix missing `std::error::Error` implement for `MultipartError`.
|
- Fix missing `std::error::Error` implement for `MultipartError`.
|
||||||
|
|
||||||
## [0.2.0] - 2019-12-20
|
## [0.2.0] - 2019-12-20
|
||||||
|
|
||||||
* Release
|
- Release
|
||||||
|
|
||||||
## [0.2.0-alpha.4] - 2019-12-xx
|
## [0.2.0-alpha.4] - 2019-12-xx
|
||||||
|
|
||||||
* Multipart handling now handles Pending during read of boundary #1205
|
- Multipart handling now handles Pending during read of boundary #1205
|
||||||
|
|
||||||
## [0.2.0-alpha.2] - 2019-12-03
|
## [0.2.0-alpha.2] - 2019-12-03
|
||||||
|
|
||||||
* Migrate to `std::future`
|
- Migrate to `std::future`
|
||||||
|
|
||||||
## [0.1.4] - 2019-09-12
|
## [0.1.4] - 2019-09-12
|
||||||
|
|
||||||
* Multipart handling now parses requests which do not end in CRLF #1038
|
- Multipart handling now parses requests which do not end in CRLF #1038
|
||||||
|
|
||||||
## [0.1.3] - 2019-08-18
|
## [0.1.3] - 2019-08-18
|
||||||
|
|
||||||
* Fix ring dependency from actix-web default features for #741.
|
- Fix ring dependency from actix-web default features for #741.
|
||||||
|
|
||||||
## [0.1.2] - 2019-06-02
|
## [0.1.2] - 2019-06-02
|
||||||
|
|
||||||
* Fix boundary parsing #876
|
- Fix boundary parsing #876
|
||||||
|
|
||||||
## [0.1.1] - 2019-05-25
|
## [0.1.1] - 2019-05-25
|
||||||
|
|
||||||
* Fix disconnect handling #834
|
- Fix disconnect handling #834
|
||||||
|
|
||||||
## [0.1.0] - 2019-05-18
|
## [0.1.0] - 2019-05-18
|
||||||
|
|
||||||
* Release
|
- Release
|
||||||
|
|
||||||
## [0.1.0-beta.4] - 2019-05-12
|
## [0.1.0-beta.4] - 2019-05-12
|
||||||
|
|
||||||
* Handle cancellation of uploads #736
|
- Handle cancellation of uploads #736
|
||||||
|
|
||||||
* Upgrade to actix-web 1.0.0-beta.4
|
- Upgrade to actix-web 1.0.0-beta.4
|
||||||
|
|
||||||
## [0.1.0-beta.1] - 2019-04-21
|
## [0.1.0-beta.1] - 2019-04-21
|
||||||
|
|
||||||
* Do not support nested multipart
|
- Do not support nested multipart
|
||||||
|
|
||||||
* Split multipart support to separate crate
|
- Split multipart support to separate crate
|
||||||
|
|
||||||
* Optimize multipart handling #634, #769
|
- Optimize multipart handling #634, #769
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-multipart"
|
name = "actix-multipart"
|
||||||
version = "0.4.0-beta.9"
|
version = "0.4.0-beta.11"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Multipart form support for Actix Web"
|
description = "Multipart form support for Actix Web"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
|
@ -14,8 +14,8 @@ name = "actix_multipart"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "4.0.0-beta.11", default-features = false }
|
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
|
actix-web = { version = "4.0.0-beta.17", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
|
@ -28,7 +28,7 @@ twoway = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-http = "3.0.0-beta.14"
|
actix-http = "3.0.0-beta.17"
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
tokio = { version = "1", features = ["sync"] }
|
tokio = { version = "1.8.4", features = ["sync"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
> Multipart form support for Actix Web.
|
> Multipart form support for Actix Web.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-multipart)
|
[](https://crates.io/crates/actix-multipart)
|
||||||
[](https://docs.rs/actix-multipart/0.4.0-beta.9)
|
[](https://docs.rs/actix-multipart/0.4.0-beta.11)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.9)
|
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.11)
|
||||||
[](https://crates.io/crates/actix-multipart)
|
[](https://crates.io/crates/actix-multipart)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! Multipart form support for Actix Web.
|
//! Multipart form support for Actix Web.
|
||||||
|
|
||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
|
#![warn(future_incompatible)]
|
||||||
#![allow(clippy::borrow_interior_mutable_const)]
|
#![allow(clippy::borrow_interior_mutable_const)]
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
|
|
|
@ -1233,7 +1233,7 @@ mod tests {
|
||||||
|
|
||||||
// and should not consume the payload
|
// and should not consume the payload
|
||||||
match payload {
|
match payload {
|
||||||
actix_web::dev::Payload::H1(_) => {} //expected
|
actix_web::dev::Payload::H1 { .. } => {} //expected
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
|
||||||
|
|
||||||
|
## 0.5.0-beta.3 - 2021-12-17
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||||
|
|
||||||
|
|
||||||
## 0.5.0-beta.2 - 2021-09-09
|
## 0.5.0-beta.2 - 2021-09-09
|
||||||
* Introduce `ResourceDef::join`. [#380]
|
- Introduce `ResourceDef::join`. [#380]
|
||||||
* Disallow prefix routes with tail segments. [#379]
|
- Disallow prefix routes with tail segments. [#379]
|
||||||
* Enforce path separators on dynamic prefixes. [#378]
|
- Enforce path separators on dynamic prefixes. [#378]
|
||||||
* Improve malformed path error message. [#384]
|
- Improve malformed path error message. [#384]
|
||||||
* Prefix segments now always end with with a segment delimiter or end-of-input. [#2355]
|
- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355]
|
||||||
* Prefix segments with trailing slashes define a trailing empty segment. [#2355]
|
- Prefix segments with trailing slashes define a trailing empty segment. [#2355]
|
||||||
* Support multi-pattern prefixes and joins. [#2356]
|
- Support multi-pattern prefixes and joins. [#2356]
|
||||||
* `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356]
|
- `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356]
|
||||||
* Support `build_resource_path` on multi-pattern resources. [#2356]
|
- Support `build_resource_path` on multi-pattern resources. [#2356]
|
||||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
[#378]: https://github.com/actix/actix-net/pull/378
|
[#378]: https://github.com/actix/actix-net/pull/378
|
||||||
[#379]: https://github.com/actix/actix-net/pull/379
|
[#379]: https://github.com/actix/actix-net/pull/379
|
||||||
|
@ -25,23 +28,23 @@
|
||||||
|
|
||||||
|
|
||||||
## 0.5.0-beta.1 - 2021-07-20
|
## 0.5.0-beta.1 - 2021-07-20
|
||||||
* Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366]
|
- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366]
|
||||||
* Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373]
|
- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373]
|
||||||
* Fix segment interpolation leaving `Path` in unintended state after matching. [#368]
|
- Fix segment interpolation leaving `Path` in unintended state after matching. [#368]
|
||||||
* Fix `ResourceDef` `PartialEq` implementation. [#373]
|
- Fix `ResourceDef` `PartialEq` implementation. [#373]
|
||||||
* Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372]
|
- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372]
|
||||||
* Implement `IntoPatterns` for `bytestring::ByteString`. [#372]
|
- Implement `IntoPatterns` for `bytestring::ByteString`. [#372]
|
||||||
* Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370]
|
- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370]
|
||||||
* Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371]
|
- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371]
|
||||||
* `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373]
|
- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373]
|
||||||
* Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371]
|
- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371]
|
||||||
* Rename `ResourceDef::{is_prefix_match => find_match}`. [#373]
|
- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373]
|
||||||
* Rename `ResourceDef::{match_path => capture_match_info}`. [#373]
|
- Rename `ResourceDef::{match_path => capture_match_info}`. [#373]
|
||||||
* Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373]
|
- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373]
|
||||||
* Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373]
|
- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373]
|
||||||
* Rename `Router::{*_checked => *_fn}`. [#373]
|
- Rename `Router::{*_checked => *_fn}`. [#373]
|
||||||
* Return type of `ResourceDef::name` is now `Option<&str>`. [#373]
|
- Return type of `ResourceDef::name` is now `Option<&str>`. [#373]
|
||||||
* Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373]
|
- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373]
|
||||||
|
|
||||||
[#368]: https://github.com/actix/actix-net/pull/368
|
[#368]: https://github.com/actix/actix-net/pull/368
|
||||||
[#366]: https://github.com/actix/actix-net/pull/366
|
[#366]: https://github.com/actix/actix-net/pull/366
|
||||||
|
@ -53,10 +56,10 @@
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0 - 2021-06-06
|
## 0.4.0 - 2021-06-06
|
||||||
* When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357]
|
- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357]
|
||||||
* Path tail patterns now match new lines (`\n`) in request URL. [#360]
|
- Path tail patterns now match new lines (`\n`) in request URL. [#360]
|
||||||
* Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359]
|
- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359]
|
||||||
* Methods `Path::{add, add_static}` now take `impl Into<Cow<'static, str>>`. [#345]
|
- Methods `Path::{add, add_static}` now take `impl Into<Cow<'static, str>>`. [#345]
|
||||||
|
|
||||||
[#345]: https://github.com/actix/actix-net/pull/345
|
[#345]: https://github.com/actix/actix-net/pull/345
|
||||||
[#357]: https://github.com/actix/actix-net/pull/357
|
[#357]: https://github.com/actix/actix-net/pull/357
|
||||||
|
@ -65,68 +68,68 @@
|
||||||
|
|
||||||
|
|
||||||
## 0.3.0 - 2019-12-31
|
## 0.3.0 - 2019-12-31
|
||||||
* Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0
|
- Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0
|
||||||
|
|
||||||
|
|
||||||
## 0.2.7 - 2021-02-06
|
## 0.2.7 - 2021-02-06
|
||||||
* Add `Router::recognize_checked` [#247]
|
- Add `Router::recognize_checked` [#247]
|
||||||
|
|
||||||
[#247]: https://github.com/actix/actix-net/pull/247
|
[#247]: https://github.com/actix/actix-net/pull/247
|
||||||
|
|
||||||
|
|
||||||
## 0.2.6 - 2021-01-09
|
## 0.2.6 - 2021-01-09
|
||||||
* Use `bytestring` version range compatible with Bytes v1.0. [#246]
|
- Use `bytestring` version range compatible with Bytes v1.0. [#246]
|
||||||
|
|
||||||
[#246]: https://github.com/actix/actix-net/pull/246
|
[#246]: https://github.com/actix/actix-net/pull/246
|
||||||
|
|
||||||
|
|
||||||
## 0.2.5 - 2020-09-20
|
## 0.2.5 - 2020-09-20
|
||||||
* Fix `from_hex()` method
|
- Fix `from_hex()` method
|
||||||
|
|
||||||
|
|
||||||
## 0.2.4 - 2019-12-31
|
## 0.2.4 - 2019-12-31
|
||||||
* Add `ResourceDef::resource_path_named()` path generation method
|
- Add `ResourceDef::resource_path_named()` path generation method
|
||||||
|
|
||||||
|
|
||||||
## 0.2.3 - 2019-12-25
|
## 0.2.3 - 2019-12-25
|
||||||
* Add impl `IntoPattern` for `&String`
|
- Add impl `IntoPattern` for `&String`
|
||||||
|
|
||||||
|
|
||||||
## 0.2.2 - 2019-12-25
|
## 0.2.2 - 2019-12-25
|
||||||
* Use `IntoPattern` for `RouterBuilder::path()`
|
- Use `IntoPattern` for `RouterBuilder::path()`
|
||||||
|
|
||||||
|
|
||||||
## 0.2.1 - 2019-12-25
|
## 0.2.1 - 2019-12-25
|
||||||
* Add `IntoPattern` trait
|
- Add `IntoPattern` trait
|
||||||
* Add multi-pattern resources
|
- Add multi-pattern resources
|
||||||
|
|
||||||
|
|
||||||
## 0.2.0 - 2019-12-07
|
## 0.2.0 - 2019-12-07
|
||||||
* Update http to 0.2
|
- Update http to 0.2
|
||||||
* Update regex to 1.3
|
- Update regex to 1.3
|
||||||
* Use bytestring instead of string
|
- Use bytestring instead of string
|
||||||
|
|
||||||
|
|
||||||
## 0.1.5 - 2019-05-15
|
## 0.1.5 - 2019-05-15
|
||||||
* Remove debug prints
|
- Remove debug prints
|
||||||
|
|
||||||
|
|
||||||
## 0.1.4 - 2019-05-15
|
## 0.1.4 - 2019-05-15
|
||||||
* Fix checked resource match
|
- Fix checked resource match
|
||||||
|
|
||||||
|
|
||||||
## 0.1.3 - 2019-04-22
|
## 0.1.3 - 2019-04-22
|
||||||
* Added support for `remainder match` (i.e "/path/{tail}*")
|
- Added support for `remainder match` (i.e "/path/{tail}*")
|
||||||
|
|
||||||
|
|
||||||
## 0.1.2 - 2019-04-07
|
## 0.1.2 - 2019-04-07
|
||||||
* Export `Quoter` type
|
- Export `Quoter` type
|
||||||
* Allow to reset `Path` instance
|
- Allow to reset `Path` instance
|
||||||
|
|
||||||
|
|
||||||
## 0.1.1 - 2019-04-03
|
## 0.1.1 - 2019-04-03
|
||||||
* Get dynamic segment by name instead of iterator.
|
- Get dynamic segment by name instead of iterator.
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0 - 2019-03-09
|
## 0.1.0 - 2019-03-09
|
||||||
* Initial release
|
- Initial release
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.5.0-beta.2"
|
version = "0.5.0-beta.3"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||||
|
@ -21,7 +21,7 @@ default = ["http"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytestring = ">=0.1.5, <2"
|
bytestring = ">=0.1.5, <2"
|
||||||
firestorm = "0.4"
|
firestorm = "0.5"
|
||||||
http = { version = "0.2.3", optional = true }
|
http = { version = "0.2.3", optional = true }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
|
@ -29,7 +29,7 @@ serde = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
firestorm = { version = "0.4", features = ["enable_system_time"] }
|
firestorm = { version = "0.5", features = ["enable_system_time"] }
|
||||||
http = "0.2.5"
|
http = "0.2.5"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
|
|
|
@ -1,149 +1,26 @@
|
||||||
//! Resource path matching and router.
|
//! Resource path matching and router.
|
||||||
|
|
||||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
|
#![warn(future_incompatible)]
|
||||||
#![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")]
|
||||||
|
|
||||||
mod de;
|
mod de;
|
||||||
mod path;
|
mod path;
|
||||||
|
mod pattern;
|
||||||
mod resource;
|
mod resource;
|
||||||
|
mod resource_path;
|
||||||
mod router;
|
mod router;
|
||||||
|
|
||||||
pub use self::de::PathDeserializer;
|
|
||||||
pub use self::path::Path;
|
|
||||||
pub use self::resource::ResourceDef;
|
|
||||||
pub use self::router::{ResourceInfo, Router, RouterBuilder};
|
|
||||||
|
|
||||||
// TODO: this trait is necessary, document it
|
|
||||||
// see impl Resource for ServiceRequest
|
|
||||||
pub trait Resource<T: ResourcePath> {
|
|
||||||
fn resource_path(&mut self) -> &mut Path<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ResourcePath {
|
|
||||||
fn path(&self) -> &str;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourcePath for String {
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
self.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ResourcePath for &'a str {
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourcePath for bytestring::ByteString {
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
&*self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// One or many patterns.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub enum Patterns {
|
|
||||||
Single(String),
|
|
||||||
List(Vec<String>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Patterns {
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Patterns::Single(_) => false,
|
|
||||||
Patterns::List(pats) => pats.is_empty(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper trait for type that could be converted to one or more path pattern.
|
|
||||||
pub trait IntoPatterns {
|
|
||||||
fn patterns(&self) -> Patterns;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoPatterns for String {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
Patterns::Single(self.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoPatterns for &'a String {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
Patterns::Single((*self).clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoPatterns for &'a str {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
Patterns::Single((*self).to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoPatterns for bytestring::ByteString {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
Patterns::Single(self.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoPatterns for Patterns {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
self.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<str>> IntoPatterns for Vec<T> {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
let mut patterns = self.iter().map(|v| v.as_ref().to_owned());
|
|
||||||
|
|
||||||
match patterns.size_hint() {
|
|
||||||
(1, _) => Patterns::Single(patterns.next().unwrap()),
|
|
||||||
_ => Patterns::List(patterns.collect()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! array_patterns_single (($tp:ty) => {
|
|
||||||
impl IntoPatterns for [$tp; 1] {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
Patterns::Single(self[0].to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
macro_rules! array_patterns_multiple (($tp:ty, $str_fn:expr, $($num:tt) +) => {
|
|
||||||
// for each array length specified in $num
|
|
||||||
$(
|
|
||||||
impl IntoPatterns for [$tp; $num] {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
Patterns::List(self.iter().map($str_fn).collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
});
|
|
||||||
|
|
||||||
array_patterns_single!(&str);
|
|
||||||
array_patterns_multiple!(&str, |&v| v.to_owned(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
|
|
||||||
|
|
||||||
array_patterns_single!(String);
|
|
||||||
array_patterns_multiple!(String, |v| v.clone(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
|
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
mod url;
|
mod url;
|
||||||
|
|
||||||
|
pub use self::de::PathDeserializer;
|
||||||
|
pub use self::path::Path;
|
||||||
|
pub use self::pattern::{IntoPatterns, Patterns};
|
||||||
|
pub use self::resource::ResourceDef;
|
||||||
|
pub use self::resource_path::{Resource, ResourcePath};
|
||||||
|
pub use self::router::{ResourceInfo, Router, RouterBuilder};
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
pub use self::url::{Quoter, Url};
|
pub use self::url::{Quoter, Url};
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
|
||||||
mod http_impls {
|
|
||||||
use http::Uri;
|
|
||||||
|
|
||||||
use super::ResourcePath;
|
|
||||||
|
|
||||||
impl ResourcePath for Uri {
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
self.path()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
/// One or many patterns.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Patterns {
|
||||||
|
Single(String),
|
||||||
|
List(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Patterns {
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Patterns::Single(_) => false,
|
||||||
|
Patterns::List(pats) => pats.is_empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper trait for type that could be converted to one or more path patterns.
|
||||||
|
pub trait IntoPatterns {
|
||||||
|
fn patterns(&self) -> Patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoPatterns for String {
|
||||||
|
fn patterns(&self) -> Patterns {
|
||||||
|
Patterns::Single(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoPatterns for &String {
|
||||||
|
fn patterns(&self) -> Patterns {
|
||||||
|
(*self).patterns()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoPatterns for str {
|
||||||
|
fn patterns(&self) -> Patterns {
|
||||||
|
Patterns::Single(self.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoPatterns for &str {
|
||||||
|
fn patterns(&self) -> Patterns {
|
||||||
|
(*self).patterns()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoPatterns for bytestring::ByteString {
|
||||||
|
fn patterns(&self) -> Patterns {
|
||||||
|
Patterns::Single(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoPatterns for Patterns {
|
||||||
|
fn patterns(&self) -> Patterns {
|
||||||
|
self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<str>> IntoPatterns for Vec<T> {
|
||||||
|
fn patterns(&self) -> Patterns {
|
||||||
|
let mut patterns = self.iter().map(|v| v.as_ref().to_owned());
|
||||||
|
|
||||||
|
match patterns.size_hint() {
|
||||||
|
(1, _) => Patterns::Single(patterns.next().unwrap()),
|
||||||
|
_ => Patterns::List(patterns.collect()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! array_patterns_single (($tp:ty) => {
|
||||||
|
impl IntoPatterns for [$tp; 1] {
|
||||||
|
fn patterns(&self) -> Patterns {
|
||||||
|
Patterns::Single(self[0].to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
macro_rules! array_patterns_multiple (($tp:ty, $str_fn:expr, $($num:tt) +) => {
|
||||||
|
// for each array length specified in space-separated $num
|
||||||
|
$(
|
||||||
|
impl IntoPatterns for [$tp; $num] {
|
||||||
|
fn patterns(&self) -> Patterns {
|
||||||
|
Patterns::List(self.iter().map($str_fn).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
});
|
||||||
|
|
||||||
|
array_patterns_single!(&str);
|
||||||
|
array_patterns_multiple!(&str, |&v| v.to_owned(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
|
||||||
|
|
||||||
|
array_patterns_single!(String);
|
||||||
|
array_patterns_multiple!(String, |v| v.clone(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue