1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-15 06:26:11 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
2ee953a118 remove either crate conversions 2021-12-14 19:01:14 +00:00
192 changed files with 6853 additions and 8676 deletions

3
.github/FUNDING.yml vendored
View File

@ -1,3 +0,0 @@
# These are supported funding model platforms
github: [robjtede]

View File

@ -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:

View File

@ -1,6 +1,8 @@
name: Benchmark name: Benchmark
on: on:
pull_request:
types: [opened, synchronize, reopened]
push: push:
branches: branches:
- master - master

View File

@ -1,153 +0,0 @@
name: CI (master only)
on:
push:
branches: [master]
jobs:
build_and_test_nightly:
strategy:
fail-fast: false
matrix:
target:
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
- { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc }
version:
- nightly
name: ${{ matrix.target.name }} / ${{ matrix.version }}
runs-on: ${{ matrix.target.os }}
env:
CI: 1
CARGO_INCREMENTAL: 0
VCPKGRS_DYNAMIC: 1
steps:
- uses: actions/checkout@v2
# install OpenSSL on Windows
# TODO: GitHub actions docs state that OpenSSL is
# already installed on these Windows machines somewhere
- name: Set vcpkg root
if: matrix.target.triple == 'x86_64-pc-windows-msvc'
run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Install OpenSSL
if: matrix.target.triple == 'x86_64-pc-windows-msvc'
run: vcpkg install openssl:x64-windows
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
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 minimal
uses: actions-rs/cargo@v1
with: { command: ci-check-min }
- name: check default
uses: actions-rs/cargo@v1
with: { command: ci-check-default }
- name: tests
timeout-minutes: 60
run: |
cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features
cargo test --lib --tests -p=actix-test --all-features
cargo test --lib --tests -p=actix-files
cargo test --lib --tests -p=actix-multipart --all-features
cargo test --lib --tests -p=actix-web-actors --all-features
- name: tests (io-uring)
if: matrix.target.os == 'ubuntu-latest'
timeout-minutes: 60
run: >
sudo bash -c "ulimit -Sl 512
&& ulimit -Hl 512
&& PATH=$PATH:/usr/share/rust/.cargo/bin
&& RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo test --lib --tests -p=actix-files --all-features"
- name: Clear the cargo caches
run: |
cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean
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
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 }

View File

@ -16,8 +16,9 @@ jobs:
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
- { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc } - { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc }
version: version:
- 1.54.0 # MSRV - 1.52.0 # MSRV
- stable - stable
- nightly
name: ${{ matrix.target.name }} / ${{ matrix.version }} name: ${{ matrix.target.name }} / ${{ matrix.version }}
runs-on: ${{ matrix.target.os }} runs-on: ${{ matrix.target.os }}
@ -95,6 +96,68 @@ 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

View File

@ -14,7 +14,6 @@ jobs:
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: stable
profile: minimal
components: rustfmt components: rustfmt
- name: Check with rustfmt - name: Check with rustfmt
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
@ -31,18 +30,10 @@ jobs:
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: stable
profile: minimal
components: clippy components: clippy
override: true override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
- name: Check with Clippy - name: Check with Clippy
uses: actions-rs/clippy-check@v1 uses: actions-rs/clippy-check@v1
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace --tests --examples --all-features args: --workspace --all-features --tests

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "4.0.0-beta.20" version = "4.0.0-beta.14"
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"]
@ -28,15 +28,15 @@ path = "src/lib.rs"
resolver = "2" resolver = "2"
members = [ members = [
".", ".",
"actix-files", "awc",
"actix-http-test",
"actix-http", "actix-http",
"actix-files",
"actix-multipart", "actix-multipart",
"actix-router",
"actix-test",
"actix-web-actors", "actix-web-actors",
"actix-web-codegen", "actix-web-codegen",
"awc", "actix-http-test",
"actix-test",
"actix-router",
] ]
[features] [features]
@ -71,29 +71,30 @@ experimental-io-uring = ["actix-server/io-uring"]
[dependencies] [dependencies]
actix-codec = "0.4.1" actix-codec = "0.4.1"
actix-macros = "0.2.3" actix-macros = "0.2.3"
actix-rt = "2.6" actix-rt = "2.3"
actix-server = "2.0.0-rc.4" actix-server = "2.0.0-rc.1"
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", default-features = false, optional = true } actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true }
actix-http = "3.0.0-beta.18" actix-http = "3.0.0-beta.15"
actix-router = "0.5.0-rc.1" actix-router = "0.5.0-beta.2"
actix-web-codegen = "0.5.0-rc.1" 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.16", features = ["percent-encode"], optional = true } cookie = { version = "0.15", features = ["percent-encode"], optional = true }
derive_more = "0.99.5" derive_more = "0.99.5"
encoding_rs = "0.8" encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false } futures-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 = "1" itoa = "0.4"
language-tags = "0.3" language-tags = "0.3"
once_cell = "1.5" once_cell = "1.5"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
paste = "1"
pin-project-lite = "0.2.7" pin-project-lite = "0.2.7"
regex = "1.4" regex = "1.4"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
@ -105,12 +106,10 @@ time = { version = "0.3", default-features = false, features = ["formatting"] }
url = "2.1" url = "2.1"
[dev-dependencies] [dev-dependencies]
actix-files = "0.6.0-beta.14" actix-test = { version = "0.1.0-beta.8", features = ["openssl", "rustls"] }
actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } awc = { version = "3.0.0-beta.13", features = ["openssl"] }
awc = { version = "3.0.0-beta.18", features = ["openssl"] }
brotli2 = "0.3.2" brotli2 = "0.3.2"
const-str = "0.3"
criterion = { version = "0.3", features = ["html_reports"] } criterion = { version = "0.3", features = ["html_reports"] }
env_logger = "0.9" env_logger = "0.9"
flate2 = "1.0.13" flate2 = "1.0.13"
@ -118,7 +117,6 @@ futures-util = { version = "0.3.7", default-features = false, features = ["std"]
rand = "0.8" rand = "0.8"
rcgen = "0.8" rcgen = "0.8"
rustls-pemfile = "0.2" rustls-pemfile = "0.2"
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" }
zstd = "0.9" zstd = "0.9"
@ -166,7 +164,7 @@ name = "uds"
required-features = ["compress-gzip"] required-features = ["compress-gzip"]
[[example]] [[example]]
name = "on-connect" name = "on_connect"
required-features = [] required-features = []
[[bench]] [[bench]]

View File

@ -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`

View File

@ -6,13 +6,13 @@
<p> <p>
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.20)](https://docs.rs/actix-web/4.0.0-beta.20) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.14)](https://docs.rs/actix-web/4.0.0-beta.14)
![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.20/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.20) [![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.14/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.14)
<br /> <br />
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)
![downloads](https://img.shields.io/crates/d/actix-web.svg) ![downloads](https://img.shields.io/crates/d/actix-web.svg)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
@ -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.54+ * 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.

View File

@ -3,65 +3,43 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 0.6.0-beta.14 - 2022-01-14
- The `prefer_utf8` option introduced in `0.4.0` is now true by default. [#2583]
[#2583]: https://github.com/actix/actix-web/pull/2583
## 0.6.0-beta.13 - 2022-01-04
- The `Files` service now rejects requests with URL paths that include `%2F` (decoded: `/`). [#2398]
- The `Files` service now correctly decodes `%25` in the URL path to `%` for the file path. [#2398]
- Minimum supported Rust version (MSRV) is now 1.54.
[#2398]: https://github.com/actix/actix-web/pull/2398
## 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 ## 0.6.0-beta.10 - 2021-12-11
- No significant changes since `0.6.0-beta.9`. * 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
@ -70,130 +48,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

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-files" name = "actix-files"
version = "0.6.0-beta.14" version = "0.6.0-beta.10"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>", "fakeshadow <24548779@qq.com>",
@ -22,10 +22,10 @@ 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-http = "3.0.0-beta.18" actix-http = "3.0.0-beta.15"
actix-service = "2" actix-service = "2"
actix-utils = "3" actix-utils = "3"
actix-web = { version = "4.0.0-beta.20", default-features = false } actix-web = { version = "4.0.0-beta.14", default-features = false }
askama_escape = "0.10" askama_escape = "0.10"
bitflags = "1" bitflags = "1"
@ -39,10 +39,9 @@ mime_guess = "2.0.1"
percent-encoding = "2.1" percent-encoding = "2.1"
pin-project-lite = "0.2.7" pin-project-lite = "0.2.7"
tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } tokio-uring = { version = "0.1", optional = true }
[dev-dependencies] [dev-dependencies]
actix-rt = "2.2" actix-rt = "2.2"
actix-test = "0.1.0-beta.11" actix-test = "0.1.0-beta.8"
actix-web = "4.0.0-beta.20" actix-web = "4.0.0-beta.14"
tempfile = "3.2"

View File

@ -3,11 +3,11 @@
> Static file serving for Actix Web > Static file serving for Actix Web
[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.14)](https://docs.rs/actix-files/0.6.0-beta.14) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.10)](https://docs.rs/actix-files/0.6.0-beta.10)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![License](https://img.shields.io/crates/l/actix-files.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.14/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.14) [![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.10/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.10)
[![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
@ -15,4 +15,4 @@
- [API Documentation](https://docs.rs/actix-files/) - [API Documentation](https://docs.rs/actix-files/)
- [Example Project](https://github.com/actix/examples/tree/master/basics/static_index) - [Example Project](https://github.com/actix/examples/tree/master/basics/static_index)
- Minimum Supported Rust Version (MSRV): 1.54 - Minimum Supported Rust Version (MSRV): 1.52

View File

@ -10,9 +10,6 @@ use actix_web::{error::Error, web::Bytes};
use futures_core::{ready, Stream}; use futures_core::{ready, Stream};
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
#[cfg(feature = "experimental-io-uring")]
use bytes::BytesMut;
use super::named::File; use super::named::File;
pin_project! { pin_project! {
@ -217,3 +214,64 @@ where
} }
} }
} }
#[cfg(feature = "experimental-io-uring")]
use bytes_mut::BytesMut;
// TODO: remove new type and use bytes::BytesMut directly
#[doc(hidden)]
#[cfg(feature = "experimental-io-uring")]
mod bytes_mut {
use std::ops::{Deref, DerefMut};
use tokio_uring::buf::{IoBuf, IoBufMut};
#[derive(Debug)]
pub struct BytesMut(bytes::BytesMut);
impl BytesMut {
pub(super) fn new() -> Self {
Self(bytes::BytesMut::new())
}
}
impl Deref for BytesMut {
type Target = bytes::BytesMut;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for BytesMut {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
unsafe impl IoBuf for BytesMut {
fn stable_ptr(&self) -> *const u8 {
self.0.as_ptr()
}
fn bytes_init(&self) -> usize {
self.0.len()
}
fn bytes_total(&self) -> usize {
self.0.capacity()
}
}
unsafe impl IoBufMut for BytesMut {
fn stable_mut_ptr(&mut self) -> *mut u8 {
self.0.as_mut_ptr()
}
unsafe fn set_init(&mut self, init_len: usize) {
if self.len() < init_len {
self.0.set_len(init_len);
}
}
}
}

View File

@ -40,23 +40,14 @@ impl Directory {
pub(crate) type DirectoryRenderer = pub(crate) type DirectoryRenderer =
dyn Fn(&Directory, &HttpRequest) -> Result<ServiceResponse, io::Error>; dyn Fn(&Directory, &HttpRequest) -> Result<ServiceResponse, io::Error>;
/// Returns percent encoded file URL path. // show file url as relative to static path
macro_rules! encode_file_url { macro_rules! encode_file_url {
($path:ident) => { ($path:ident) => {
utf8_percent_encode(&$path, CONTROLS) utf8_percent_encode(&$path, CONTROLS)
}; };
} }
/// Returns HTML entity encoded formatter. // " -- &quot; & -- &amp; ' -- &#x27; < -- &lt; > -- &gt; / -- &#x2f;
///
/// ```plain
/// " => &quot;
/// & => &amp;
/// ' => &#x27;
/// < => &lt;
/// > => &gt;
/// / => &#x2f;
/// ```
macro_rules! encode_file_name { macro_rules! encode_file_name {
($entry:ident) => { ($entry:ident) => {
escape_html_entity(&$entry.file_name().to_string_lossy(), Html) escape_html_entity(&$entry.file_name().to_string_lossy(), Html)

View File

@ -23,23 +23,16 @@ impl ResponseError for FilesError {
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
#[derive(Display, Debug, PartialEq)] #[derive(Display, Debug, PartialEq)]
#[non_exhaustive]
pub enum UriSegmentError { pub enum UriSegmentError {
/// The segment started with the wrapped invalid character. /// The segment started with the wrapped invalid character.
#[display(fmt = "The segment started with the wrapped invalid character")] #[display(fmt = "The segment started with the wrapped invalid character")]
BadStart(char), BadStart(char),
/// The segment contained the wrapped invalid character. /// The segment contained the wrapped invalid character.
#[display(fmt = "The segment contained the wrapped invalid character")] #[display(fmt = "The segment contained the wrapped invalid character")]
BadChar(char), BadChar(char),
/// The segment ended with the wrapped invalid character. /// The segment ended with the wrapped invalid character.
#[display(fmt = "The segment ended with the wrapped invalid character")] #[display(fmt = "The segment ended with the wrapped invalid character")]
BadEnd(char), BadEnd(char),
/// The path is not a valid UTF-8 string after doing percent decoding.
#[display(fmt = "The path is not a valid UTF-8 string after percent-decoding")]
NotValidUtf8,
} }
/// Return `BadRequest` for `UriSegmentError` /// Return `BadRequest` for `UriSegmentError`

View File

@ -28,7 +28,6 @@ use crate::{
/// ///
/// `Files` service must be registered with `App::service()` method. /// `Files` service must be registered with `App::service()` method.
/// ///
/// # Examples
/// ``` /// ```
/// use actix_web::App; /// use actix_web::App;
/// use actix_files::Files; /// use actix_files::Files;

View File

@ -67,8 +67,8 @@ mod tests {
time::{Duration, SystemTime}, time::{Duration, SystemTime},
}; };
use actix_service::ServiceFactory;
use actix_web::{ use actix_web::{
dev::ServiceFactory,
guard, guard,
http::{ http::{
header::{self, ContentDisposition, DispositionParam, DispositionType}, header::{self, ContentDisposition, DispositionParam, DispositionType},
@ -303,7 +303,7 @@ mod tests {
let resp = file.respond_to(&req).await.unwrap(); let resp = file.respond_to(&req).await.unwrap();
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"application/javascript; charset=utf-8" "application/javascript"
); );
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
@ -597,8 +597,7 @@ mod tests {
.to_request(); .to_request();
let res = test::call_service(&srv, request).await; let res = test::call_service(&srv, request).await;
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert!(res.headers().contains_key(header::CONTENT_ENCODING)); assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
assert!(!test::read_body(res).await.is_empty());
} }
#[actix_rt::test] #[actix_rt::test]
@ -803,38 +802,6 @@ mod tests {
let req = TestRequest::get().uri("/test/%43argo.toml").to_request(); let req = TestRequest::get().uri("/test/%43argo.toml").to_request();
let res = test::call_service(&srv, req).await; let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
// `%2F` == `/`
let req = TestRequest::get().uri("/test%2Ftest.binary").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let req = TestRequest::get().uri("/test/Cargo.toml%00").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn test_percent_encoding_2() {
let tmpdir = tempfile::tempdir().unwrap();
let filename = match cfg!(unix) {
true => "ض:?#[]{}<>()@!$&'`|*+,;= %20.test",
false => "ض#[]{}()@!$&'`+,;= %20.test",
};
let filename_encoded = filename
.as_bytes()
.iter()
.map(|c| format!("%{:02X}", c))
.collect::<String>();
std::fs::File::create(tmpdir.path().join(filename)).unwrap();
let srv = test::init_service(App::new().service(Files::new("", tmpdir.path()))).await;
let req = TestRequest::get()
.uri(&format!("/{}", filename_encoded))
.to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
} }
#[actix_rt::test] #[actix_rt::test]

View File

@ -1,20 +1,22 @@
use std::{ use std::{
fmt,
fs::Metadata, fs::Metadata,
io, io,
path::{Path, PathBuf}, path::{Path, PathBuf},
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
use actix_service::{Service, ServiceFactory};
use actix_web::{ use actix_web::{
body::{self, BoxBody, SizedStream}, body::{self, BoxBody, SizedStream},
dev::{ dev::{
self, AppService, HttpServiceFactory, ResourceDef, Service, ServiceFactory, AppService, BodyEncoding, HttpServiceFactory, ResourceDef, ServiceRequest,
ServiceRequest, ServiceResponse, ServiceResponse,
}, },
http::{ http::{
header::{ header::{
self, Charset, ContentDisposition, ContentEncoding, DispositionParam, self, Charset, ContentDisposition, ContentEncoding, DispositionParam,
DispositionType, ExtendedValue, HeaderValue, DispositionType, ExtendedValue,
}, },
StatusCode, StatusCode,
}, },
@ -38,7 +40,7 @@ bitflags! {
impl Default for Flags { impl Default for Flags {
fn default() -> Self { fn default() -> Self {
Flags::from_bits_truncate(0b0000_1111) Flags::from_bits_truncate(0b0000_0111)
} }
} }
@ -66,12 +68,12 @@ impl Default for Flags {
/// NamedFile::open_async("./static/index.html").await /// NamedFile::open_async("./static/index.html").await
/// } /// }
/// ``` /// ```
#[derive(Debug, Deref, DerefMut)] #[derive(Deref, DerefMut)]
pub struct NamedFile { pub struct NamedFile {
path: PathBuf,
#[deref] #[deref]
#[deref_mut] #[deref_mut]
file: File, file: File,
path: PathBuf,
modified: Option<SystemTime>, modified: Option<SystemTime>,
pub(crate) md: Metadata, pub(crate) md: Metadata,
pub(crate) flags: Flags, pub(crate) flags: Flags,
@ -81,6 +83,32 @@ pub struct NamedFile {
pub(crate) encoding: Option<ContentEncoding>, pub(crate) encoding: Option<ContentEncoding>,
} }
impl fmt::Debug for NamedFile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NamedFile")
.field("path", &self.path)
.field(
"file",
#[cfg(feature = "experimental-io-uring")]
{
&"tokio_uring::File"
},
#[cfg(not(feature = "experimental-io-uring"))]
{
&self.file
},
)
.field("modified", &self.modified)
.field("md", &self.md)
.field("flags", &self.flags)
.field("status_code", &self.status_code)
.field("content_type", &self.content_type)
.field("content_disposition", &self.content_disposition)
.field("encoding", &self.encoding)
.finish()
}
}
#[cfg(not(feature = "experimental-io-uring"))] #[cfg(not(feature = "experimental-io-uring"))]
pub(crate) use std::fs::File; pub(crate) use std::fs::File;
#[cfg(feature = "experimental-io-uring")] #[cfg(feature = "experimental-io-uring")]
@ -196,6 +224,7 @@ impl NamedFile {
}) })
} }
#[cfg(not(feature = "experimental-io-uring"))]
/// Attempts to open a file in read-only mode. /// Attempts to open a file in read-only mode.
/// ///
/// # Examples /// # Examples
@ -203,7 +232,6 @@ impl NamedFile {
/// use actix_files::NamedFile; /// use actix_files::NamedFile;
/// let file = NamedFile::open("foo.txt"); /// let file = NamedFile::open("foo.txt");
/// ``` /// ```
#[cfg(not(feature = "experimental-io-uring"))]
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> { pub fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> {
let file = File::open(&path)?; let file = File::open(&path)?;
Self::from_file(file, path) Self::from_file(file, path)
@ -267,21 +295,23 @@ impl NamedFile {
self self
} }
/// Set the MIME Content-Type for serving this file. By default the Content-Type is inferred /// Set the MIME Content-Type for serving this file. By default
/// from the filename extension. /// the Content-Type is inferred from the filename extension.
#[inline] #[inline]
pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self { pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self {
self.content_type = mime_type; self.content_type = mime_type;
self self
} }
/// Set the Content-Disposition for serving this file. This allows changing the /// Set the Content-Disposition for serving this file. This allows
/// `inline/attachment` disposition as well as the filename sent to the peer. /// changing the inline/attachment disposition as well as the filename
/// sent to the peer.
/// ///
/// By default the disposition is `inline` for `text/*`, `image/*`, `video/*` and /// By default the disposition is `inline` for `text/*`, `image/*`, `video/*` and
/// `application/{javascript, json, wasm}` mime types, and `attachment` otherwise, and the /// `application/{javascript, json, wasm}` mime types, and `attachment` otherwise,
/// filename is taken from the path provided in the `open` method after converting it to UTF-8 /// and the filename is taken from the path provided in the `open` method
/// (using `to_string_lossy`). /// after converting it to UTF-8 using.
/// [`std::ffi::OsStr::to_string_lossy`]
#[inline] #[inline]
pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self {
self.content_disposition = cd; self.content_disposition = cd;
@ -307,7 +337,7 @@ impl NamedFile {
self self
} }
/// Specifies whether to return `ETag` header in response. /// Specifies whether to use ETag or not.
/// ///
/// Default is true. /// Default is true.
#[inline] #[inline]
@ -316,7 +346,7 @@ impl NamedFile {
self self
} }
/// Specifies whether to return `Last-Modified` header in response. /// Specifies whether to use Last-Modified or not.
/// ///
/// Default is true. /// Default is true.
#[inline] #[inline]
@ -334,7 +364,7 @@ impl NamedFile {
self self
} }
/// Creates an `ETag` in a format is similar to Apache's. /// 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> {
self.modified.as_ref().map(|mtime| { self.modified.as_ref().map(|mtime| {
let ino = { let ino = {
@ -356,7 +386,7 @@ impl NamedFile {
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.expect("modification time must be after epoch"); .expect("modification time must be after epoch");
header::EntityTag::new_strong(format!( header::EntityTag::strong(format!(
"{:x}:{:x}:{:x}:{:x}", "{:x}:{:x}:{:x}:{:x}",
ino, ino,
self.md.len(), self.md.len(),
@ -375,13 +405,12 @@ impl NamedFile {
if self.status_code != StatusCode::OK { if self.status_code != StatusCode::OK {
let mut res = HttpResponse::build(self.status_code); let mut res = HttpResponse::build(self.status_code);
let ct = if self.flags.contains(Flags::PREFER_UTF8) { if self.flags.contains(Flags::PREFER_UTF8) {
equiv_utf8_text(self.content_type.clone()) let ct = equiv_utf8_text(self.content_type.clone());
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
} else { } else {
self.content_type res.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
}; }
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
if self.flags.contains(Flags::CONTENT_DISPOSITION) { if self.flags.contains(Flags::CONTENT_DISPOSITION) {
res.insert_header(( res.insert_header((
@ -391,7 +420,7 @@ impl NamedFile {
} }
if let Some(current_encoding) = self.encoding { if let Some(current_encoding) = self.encoding {
res.insert_header((header::CONTENT_ENCODING, current_encoding.as_str())); res.encoding(current_encoding);
} }
let reader = chunked::new_chunked_read(self.md.len(), 0, self.file); let reader = chunked::new_chunked_read(self.md.len(), 0, self.file);
@ -449,13 +478,12 @@ impl NamedFile {
let mut res = HttpResponse::build(self.status_code); let mut res = HttpResponse::build(self.status_code);
let ct = if self.flags.contains(Flags::PREFER_UTF8) { if self.flags.contains(Flags::PREFER_UTF8) {
equiv_utf8_text(self.content_type.clone()) let ct = equiv_utf8_text(self.content_type.clone());
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
} else { } else {
self.content_type res.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
}; }
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
if self.flags.contains(Flags::CONTENT_DISPOSITION) { if self.flags.contains(Flags::CONTENT_DISPOSITION) {
res.insert_header(( res.insert_header((
@ -464,8 +492,9 @@ impl NamedFile {
)); ));
} }
// default compressing
if let Some(current_encoding) = self.encoding { if let Some(current_encoding) = self.encoding {
res.insert_header((header::CONTENT_ENCODING, current_encoding.as_str())); res.encoding(current_encoding);
} }
if let Some(lm) = last_modified { if let Some(lm) = last_modified {
@ -488,12 +517,7 @@ impl NamedFile {
length = ranges[0].length; length = ranges[0].length;
offset = ranges[0].start; offset = ranges[0].start;
// don't allow compression middleware to modify partial content res.encoding(ContentEncoding::Identity);
res.insert_header((
header::CONTENT_ENCODING,
HeaderValue::from_static("identity"),
));
res.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()),
@ -602,7 +626,7 @@ impl Service<ServiceRequest> for NamedFileService {
type Error = Error; type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
dev::always_ready!(); actix_service::always_ready!();
fn call(&self, req: ServiceRequest) -> Self::Future { fn call(&self, req: ServiceRequest) -> Self::Future {
let (req, _) = req.into_parts(); let (req, _) = req.into_parts();

View File

@ -1,5 +1,5 @@
use std::{ use std::{
path::{Component, Path, PathBuf}, path::{Path, PathBuf},
str::FromStr, str::FromStr,
}; };
@ -26,23 +26,8 @@ impl PathBufWrap {
pub fn parse_path(path: &str, hidden_files: bool) -> Result<Self, UriSegmentError> { pub fn parse_path(path: &str, hidden_files: bool) -> Result<Self, UriSegmentError> {
let mut buf = PathBuf::new(); let mut buf = PathBuf::new();
// equivalent to `path.split('/').count()`
let mut segment_count = path.matches('/').count() + 1;
// we can decode the whole path here (instead of per-segment decoding)
// because we will reject `%2F` in paths using `segement_count`.
let path = percent_encoding::percent_decode_str(path)
.decode_utf8()
.map_err(|_| UriSegmentError::NotValidUtf8)?;
// disallow decoding `%2F` into `/`
if segment_count != path.matches('/').count() + 1 {
return Err(UriSegmentError::BadChar('/'));
}
for segment in path.split('/') { for segment in path.split('/') {
if segment == ".." { if segment == ".." {
segment_count -= 1;
buf.pop(); buf.pop();
} else if !hidden_files && segment.starts_with('.') { } else if !hidden_files && segment.starts_with('.') {
return Err(UriSegmentError::BadStart('.')); return Err(UriSegmentError::BadStart('.'));
@ -55,7 +40,6 @@ impl PathBufWrap {
} else if segment.ends_with('<') { } else if segment.ends_with('<') {
return Err(UriSegmentError::BadEnd('<')); return Err(UriSegmentError::BadEnd('<'));
} else if segment.is_empty() { } else if segment.is_empty() {
segment_count -= 1;
continue; continue;
} else if cfg!(windows) && segment.contains('\\') { } else if cfg!(windows) && segment.contains('\\') {
return Err(UriSegmentError::BadChar('\\')); return Err(UriSegmentError::BadChar('\\'));
@ -64,12 +48,6 @@ impl PathBufWrap {
} }
} }
// make sure we agree with stdlib parser
for (i, component) in buf.components().enumerate() {
assert!(matches!(component, Component::Normal(_)));
assert!(i < segment_count);
}
Ok(PathBufWrap(buf)) Ok(PathBufWrap(buf))
} }
} }

View File

@ -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::{
body::BoxBody, dev::{ServiceRequest, ServiceResponse},
dev::{self, Service, ServiceRequest, ServiceResponse},
error::Error, error::Error,
guard::Guard, guard::Guard,
http::{header, Method}, http::{header, Method},
@ -94,16 +94,16 @@ impl fmt::Debug for FilesService {
} }
impl Service<ServiceRequest> for FilesService { impl Service<ServiceRequest> for FilesService {
type Response = ServiceResponse<BoxBody>; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
dev::always_ready!(); actix_service::always_ready!();
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.guard_ctx()) (**guard).check(req.head())
} else { } else {
// default behavior // default behavior
matches!(*req.method(), Method::HEAD | Method::GET) matches!(*req.method(), Method::HEAD | Method::GET)
@ -114,7 +114,7 @@ impl Service<ServiceRequest> for FilesService {
Box::pin(async move { Box::pin(async move {
if !is_method_valid { if !is_method_valid {
return Ok(req.into_response( return Ok(req.into_response(
HttpResponse::MethodNotAllowed() actix_web::HttpResponse::MethodNotAllowed()
.insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8)) .insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8))
.body("Request did not meet this resource's requirements."), .body("Request did not meet this resource's requirements."),
)); ));
@ -123,7 +123,7 @@ impl Service<ServiceRequest> for FilesService {
let real_path = let real_path =
match PathBufWrap::parse_path(req.match_info().path(), this.hidden_files) { match PathBufWrap::parse_path(req.match_info().path(), this.hidden_files) {
Ok(item) => item, Ok(item) => item,
Err(err) => return Ok(req.error_response(err)), Err(e) => return Ok(req.error_response(e)),
}; };
if let Some(filter) = &this.path_filter { if let Some(filter) = &this.path_filter {
@ -131,7 +131,9 @@ impl Service<ServiceRequest> for FilesService {
if let Some(ref default) = this.default { if let Some(ref default) = this.default {
return default.call(req).await; return default.call(req).await;
} else { } else {
return Ok(req.into_response(HttpResponse::NotFound().finish())); return Ok(
req.into_response(actix_web::HttpResponse::NotFound().finish())
);
} }
} }
} }

View File

@ -19,12 +19,12 @@ async fn test_utf8_file_contents() {
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert_eq!( assert_eq!(
res.headers().get(header::CONTENT_TYPE), res.headers().get(header::CONTENT_TYPE),
Some(&HeaderValue::from_static("text/plain; charset=utf-8")), Some(&HeaderValue::from_static("text/plain")),
); );
// disable UTF-8 attribute // prefer UTF-8 encoding
let srv = let srv =
test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(false))) test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(true)))
.await; .await;
let req = TestRequest::with_uri("/utf8.txt").to_request(); let req = TestRequest::with_uri("/utf8.txt").to_request();
@ -33,6 +33,6 @@ async fn test_utf8_file_contents() {
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert_eq!( assert_eq!(
res.headers().get(header::CONTENT_TYPE), res.headers().get(header::CONTENT_TYPE),
Some(&HeaderValue::from_static("text/plain")), Some(&HeaderValue::from_static("text/plain; charset=utf-8")),
); );
} }

View File

@ -3,136 +3,126 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 3.0.0-beta.11 - 2022-01-04
- Minimum supported Rust version (MSRV) is now 1.54.
## 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 ## 3.0.0-beta.9 - 2021-12-11
- No significant changes since `3.0.0-beta.8`. * 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

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-http-test" name = "actix-http-test"
version = "3.0.0-beta.11" version = "3.0.0-beta.9"
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" actix-tls = "3.0.0-rc.1"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-rt = "2.2" actix-rt = "2.2"
actix-server = "2.0.0-rc.2" actix-server = "2.0.0-rc.1"
awc = { version = "3.0.0-beta.18", default-features = false } awc = { version = "3.0.0-beta.13", 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.8.4", features = ["sync"] } tokio = { version = "1.2", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
actix-web = { version = "4.0.0-beta.20", default-features = false, features = ["cookies"] } actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["cookies"] }
actix-http = "3.0.0-beta.18" actix-http = "3.0.0-beta.15"

View File

@ -3,15 +3,15 @@
> Various helpers for Actix applications to use during testing. > Various helpers for Actix applications to use during testing.
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.11)](https://docs.rs/actix-http-test/3.0.0-beta.11) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.9)](https://docs.rs/actix-http-test/3.0.0-beta.9)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
<br> <br>
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.11/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.11) [![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.9)
[![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http-test) - [API Documentation](https://docs.rs/actix-http-test)
- Minimum Supported Rust Version (MSRV): 1.54 - Minimum Supported Rust Version (MSRV): 1.52

View File

@ -12,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, ServerServiceFactory}; use actix_server::{Server, ServiceFactory};
use awc::{ use awc::{
error::PayloadError, http::header::HeaderMap, ws, Client, ClientRequest, ClientResponse, error::PayloadError, http::header::HeaderMap, ws, Client, ClientRequest, ClientResponse,
Connector, Connector,
@ -51,13 +51,13 @@ use tokio::sync::mpsc;
/// assert!(response.status().is_success()); /// assert!(response.status().is_success());
/// } /// }
/// ``` /// ```
pub async fn test_server<F: ServerServiceFactory<TcpStream>>(factory: F) -> TestServer { pub async fn test_server<F: ServiceFactory<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: ServerServiceFactory<TcpStream>>( pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
tcp: net::TcpListener, tcp: net::TcpListener,
factory: F, factory: F,
) -> TestServer { ) -> TestServer {
@ -107,7 +107,7 @@ pub async fn test_server_with_addr<F: ServerServiceFactory<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))
.openssl(builder.build()) .ssl(builder.build())
}; };
#[cfg(not(feature = "openssl"))] #[cfg(not(feature = "openssl"))]

View File

@ -1,106 +1,52 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 3.0.0-beta.18 - 2022-01-04
### Added
- `impl Eq` for `header::ContentEncoding`. [#2501]
- `impl Copy` for `QualityItem` where `T: Copy`. [#2501]
- `Quality::ZERO` equivalent to `q=0`. [#2501]
- `QualityItem::zero` that uses `Quality::ZERO`. [#2501]
- `ContentEncoding::to_header_value()`. [#2501]
### Changed ### Changed
- `Quality::MIN` is now the smallest non-zero value. [#2501] * Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510]
- `QualityItem::min` semantics changed with `QualityItem::MIN`. [#2501] * Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510]
- Rename `ContentEncoding::{Br => Brotli}`. [#2501] * Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510]
- Minimum supported Rust version (MSRV) is now 1.54.
- Rename `header::EntityTag::{weak => new_weak, strong => new_strong}`. [#2565]
### Fixed
- `ContentEncoding::Identity` can now be parsed from a string. [#2501]
- A `Vary` header is now correctly sent along with compressed content. [#2501]
### Removed
- `ContentEncoding::Auto` variant. [#2501]
- `ContentEncoding::is_compression()`. [#2501]
[#2501]: https://github.com/actix/actix-web/pull/2501
[#2565]: https://github.com/actix/actix-web/pull/2565
## 3.0.0-beta.17 - 2021-12-27
### Changes
- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527]
- `Payload` inner fields are now named. [#2545]
- `impl Stream` for `Payload` no longer requires the `Stream` variant be `Unpin`. [#2545]
- `impl Future` for `h1::SendResponse` no longer requires the body type be `Unpin`. [#2545]
- `impl Stream` for `encoding::Decoder` no longer requires the stream type be `Unpin`. [#2545]
- Rename `PayloadStream` to `BoxedPayloadStream`. [#2545]
### Removed
- `h1::Payload::readany`. [#2545]
[#2527]: https://github.com/actix/actix-web/pull/2527
[#2545]: https://github.com/actix/actix-web/pull/2545
## 3.0.0-beta.16 - 2021-12-17
### 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 [#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 ## 3.0.0-beta.15 - 2021-12-11
### Added ### Added
- Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] * 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] * HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
- `Response::map_into_boxed_body`. [#2468] * `Response::map_into_boxed_body`. [#2468]
- `body::EitherBody` enum. [#2468] * `body::EitherBody` enum. [#2468]
- `body::None` struct. [#2468] * `body::None` struct. [#2468]
- Impl `MessageBody` for `bytestring::ByteString`. [#2468] * Impl `MessageBody` for `bytestring::ByteString`. [#2468]
- `impl Clone for ws::HandshakeError`. [#2468] * `impl Clone for ws::HandshakeError`. [#2468]
- `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] * `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920]
- `impl Default ` for `ws::Codec`. [#1920] * `impl Default ` for `ws::Codec`. [#1920]
- `header::QualityItem::{max, min}`. [#2486] * `header::QualityItem::{max, min}`. [#2486]
- `header::Quality::{MAX, MIN}`. [#2486] * `header::Quality::{MAX, MIN}`. [#2486]
- `impl Display` for `header::Quality`. [#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] * 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_conn_data()`. [#2491]
- `Request::take_req_data()`. [#2487] * `Request::take_req_data()`. [#2487]
- `impl Clone` for `RequestHead`. [#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 methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimisations on body types that are done in exactly one poll/chunk. [#2497]
- New `boxed` method on `MessageBody` trait for wrapping body type. [#2520]
### Changed ### Changed
- Rename `body::BoxBody::{from_body => new}`. [#2468] * Rename `body::BoxBody::{from_body => new}`. [#2468]
- Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#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] * 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] * Error types using in service builders now require `Into<Response<BoxBody>>`. [#2468]
- `From` implementations on error types now return a `Response<BoxBody>`. [#2468] * `From` implementations on error types now return a `Response<BoxBody>`. [#2468]
- `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468] * `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468]
- `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468] * `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468]
### Removed ### Removed
- `ResponseBuilder::streaming`. [#2468] * `ResponseBuilder::streaming`. [#2468]
- `impl Future` for `ResponseBuilder`. [#2468] * `impl Future` for `ResponseBuilder`. [#2468]
- Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] * Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468]
- Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] * Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468]
- `impl Copy` for `ws::Codec`. [#1920] * `impl Copy` for `ws::Codec`. [#1920]
- `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486] * `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486]
- `impl TryFrom<u16>` for `header::Quality`. [#2486] * `impl TryFrom<u16>` for `header::Quality`. [#2486]
- `http` module. Most everything it contained is exported at the crate root. [#2488] * `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
@ -110,15 +56,14 @@
[#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 [#2491]: https://github.com/actix/actix-web/pull/2491
[#2497]: https://github.com/actix/actix-web/pull/2497 [#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
@ -127,24 +72,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
@ -153,11 +98,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
@ -165,21 +110,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
@ -189,15 +134,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
@ -205,37 +150,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
@ -250,27 +195,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
@ -282,16 +227,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
@ -301,13 +246,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
@ -316,48 +261,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
- `TryIntoHeaderPair` 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 TryIntoHeaderValue` to support using typed * `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed
`mime` types. [#1894] `mime` types. [#1894]
- Renamed `TryIntoHeaderValue::{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
@ -372,24 +317,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]
@ -401,20 +346,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
@ -425,12 +370,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
@ -439,28 +384,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
@ -468,10 +413,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
@ -480,15 +425,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
@ -496,13 +441,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
@ -511,12 +456,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
@ -524,61 +469,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
@ -586,169 +531,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

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "3.0.0-beta.18" version = "3.0.0-beta.15"
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 = { version = "2.2", default-features = false } actix-rt = "2.2"
ahash = "0.7" ahash = "0.7"
base64 = "0.13" base64 = "0.13"
@ -55,23 +55,25 @@ 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.9"
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 = "1" itoa = "0.4"
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.10" sha-1 = "0.9"
smallvec = "1.6.1" smallvec = "1.6.1"
# tls # tls
actix-tls = { version = "3.0.0", default-features = false, optional = true } actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true }
# compression # compression
brotli2 = { version="0.3.2", optional = true } brotli2 = { version="0.3.2", optional = true }
@ -79,15 +81,14 @@ flate2 = { version = "1.0.13", optional = true }
zstd = { version = "0.9", optional = true } zstd = { version = "0.9", optional = true }
[dev-dependencies] [dev-dependencies]
actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] }
actix-server = "2.0.0-rc.2" actix-server = "2.0.0-rc.1"
actix-tls = { version = "3.0.0", features = ["openssl"] } actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] }
actix-web = "4.0.0-beta.20" actix-web = "4.0.0-beta.14"
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"
@ -96,7 +97,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.8.4", features = ["net", "rt", "macros"] } tokio = { version = "1.2", features = ["net", "rt", "macros"] }
[[example]] [[example]]
name = "ws" name = "ws"

View File

@ -3,18 +3,18 @@
> HTTP primitives for the Actix ecosystem. > HTTP primitives for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.18)](https://docs.rs/actix-http/3.0.0-beta.18) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.15)](https://docs.rs/actix-http/3.0.0-beta.15)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.18/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.18) [![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.15/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.15)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http) - [API Documentation](https://docs.rs/actix-http)
- Minimum Supported Rust Version (MSRV): 1.54 - Minimum Supported Rust Version (MSRV): 1.52
## Example ## Example
@ -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.

View File

@ -42,37 +42,32 @@ mod _new {
if x < 10 { if x < 10 {
f.write_str("00")?; f.write_str("00")?;
// 0 is handled so it's not possible to have a trailing 0, we can just return // 0 is handled so it's not possible to have a trailing 0, we can just return
itoa_fmt(f, x) itoa::fmt(f, x)
} else if x < 100 { } else if x < 100 {
f.write_str("0")?; f.write_str("0")?;
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 101999 // x is in range 101999
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)
} }
} }
} }
} }
} }
} }
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))
}
} }
mod _naive { mod _naive {

View File

@ -8,97 +8,76 @@ use std::{
use bytes::Bytes; use bytes::Bytes;
use super::{BodySize, MessageBody, MessageBodyMapErr}; use super::{BodySize, MessageBody, MessageBodyMapErr};
use crate::body; use crate::Error;
/// A boxed message body with boxed errors. /// A boxed message body with boxed errors.
#[derive(Debug)] pub struct BoxBody(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>);
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 {
/// Same as `MessageBody::boxed`. /// Boxes a `MessageBody` and any errors it generates.
///
/// 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,
{ {
match body.size() { let body = MessageBodyMapErr::new(body, Into::into);
BodySize::None => Self(BoxBodyInner::None(body::None)), Self(Box::pin(body))
_ => 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.
#[inline] pub fn as_pin_mut(&mut self) -> Pin<&mut (dyn MessageBody<Error = Box<dyn StdError>>)> {
pub fn as_pin_mut(&mut self) -> Pin<&mut Self> { self.0.as_mut()
Pin::new(self) }
}
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 = Box<dyn StdError>; type Error = Error;
#[inline]
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
match &self.0 { self.0.size()
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>>> {
match &mut self.0 { self.0
BoxBodyInner::None(body) => { .as_mut()
Pin::new(body).poll_next(cx).map_err(|err| match err {}) .poll_next(cx)
} .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 is_complete_body(&self) -> bool {
fn try_into_bytes(self) -> Result<Bytes, Self> { self.0.is_complete_body()
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 take_complete_body(&mut self) -> Bytes {
fn boxed(self) -> BoxBody { debug_assert!(
self self.is_complete_body(),
"boxed type does not allow taking complete body; caller should make sure to \
call `is_complete_body` first",
);
// we do not have DerefMut access to call take_complete_body directly but since
// is_complete_body is true we should expect the entire bytes chunk in one poll_next
let waker = futures_util::task::noop_waker();
let mut cx = Context::from_waker(&waker);
match self.as_pin_mut().poll_next(&mut cx) {
Poll::Ready(Some(Ok(data))) => data,
_ => {
panic!(
"boxed type indicated it allows taking complete body but failed to \
return Bytes when polled",
);
}
}
} }
} }

View File

@ -74,22 +74,18 @@ where
} }
#[inline] #[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> { fn is_complete_body(&self) -> bool {
match self { match self {
EitherBody::Left { body } => body EitherBody::Left { body } => body.is_complete_body(),
.try_into_bytes() EitherBody::Right { body } => body.is_complete_body(),
.map_err(|body| EitherBody::Left { body }),
EitherBody::Right { body } => body
.try_into_bytes()
.map_err(|body| EitherBody::Right { body }),
} }
} }
#[inline] #[inline]
fn boxed(self) -> BoxBody { fn take_complete_body(&mut self) -> Bytes {
match self { match self {
EitherBody::Left { body } => body.boxed(), EitherBody::Left { body } => body.take_complete_body(),
EitherBody::Right { body } => body.boxed(), EitherBody::Right { body } => body.take_complete_body(),
} }
} }
} }

View File

@ -12,20 +12,16 @@ 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, BoxBody}; use super::BodySize;
/// 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 {
/// The type of error that will be returned if streaming body fails. // TODO: consider this bound to only fmt::Display since the error type is not really used
/// // 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.
@ -35,32 +31,51 @@ pub trait MessageBody {
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. /// Returns true if entire body bytes chunk is obtainable in one call to `poll_next`.
/// ///
/// Implement this method if the entire body can be trivially extracted. This is useful for /// This method's implementation should agree with [`take_complete_body`] and should always be
/// optimizations where `poll_next` calls can be avoided. /// checked before taking the body.
/// ///
/// Body types with [`BodySize::None`] are allowed to return empty `Bytes`. Although, if calling /// The default implementation returns `false.
/// this method, it is recommended to check `size` first and return early.
/// ///
/// # Errors /// [`take_complete_body`]: MessageBody::take_complete_body
/// The default implementation will error and return the original type back to the caller for fn is_complete_body(&self) -> bool {
/// further use. false
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self>
where
Self: Sized,
{
Err(self)
} }
/// Converts this body into `BoxBody`. /// Returns the complete chunk of body bytes.
#[inline] ///
fn boxed(self) -> BoxBody /// Implementors of this method should note the following:
where /// - It is acceptable to skip the omit checks of [`is_complete_body`]. The responsibility of
Self: Sized + 'static, /// performing this check is delegated to the caller.
{ /// - If the result of [`is_complete_body`] is conditional, that condition should be given
BoxBody::new(self) /// equivalent attention here.
/// - A second call call to [`take_complete_body`] should return an empty `Bytes` or panic.
/// - A call to [`poll_next`] after calling [`take_complete_body`] should return `None` unless
/// the chunk is guaranteed to be empty.
///
/// The default implementation panics unconditionally, indicating a control flow bug in the
/// calling code.
///
/// # Panics
/// With a correct implementation, panics if called without first checking [`is_complete_body`].
///
/// [`is_complete_body`]: MessageBody::is_complete_body
/// [`take_complete_body`]: MessageBody::take_complete_body
/// [`poll_next`]: MessageBody::poll_next
fn take_complete_body(&mut self) -> Bytes {
assert!(
self.is_complete_body(),
"type ({}) allows taking complete body but did not provide an implementation \
of `take_complete_body`",
std::any::type_name::<Self>()
);
unimplemented!(
"type ({}) does not allow taking complete body; caller should make sure to \
check `is_complete_body` first",
std::any::type_name::<Self>()
);
} }
} }
@ -80,6 +95,14 @@ mod foreign_impls {
) -> Poll<Option<Result<Bytes, Self::Error>>> { ) -> Poll<Option<Result<Bytes, Self::Error>>> {
match *self {} match *self {}
} }
fn is_complete_body(&self) -> bool {
true
}
fn take_complete_body(&mut self) -> Bytes {
match *self {}
}
} }
impl MessageBody for () { impl MessageBody for () {
@ -99,14 +122,19 @@ mod foreign_impls {
} }
#[inline] #[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> { fn is_complete_body(&self) -> bool {
Ok(Bytes::new()) true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
Bytes::new()
} }
} }
impl<B> MessageBody for Box<B> impl<B> MessageBody for Box<B>
where where
B: MessageBody + Unpin + ?Sized, B: MessageBody + Unpin,
{ {
type Error = B::Error; type Error = B::Error;
@ -122,11 +150,21 @@ mod foreign_impls {
) -> Poll<Option<Result<Bytes, Self::Error>>> { ) -> Poll<Option<Result<Bytes, Self::Error>>> {
Pin::new(self.get_mut().as_mut()).poll_next(cx) Pin::new(self.get_mut().as_mut()).poll_next(cx)
} }
#[inline]
fn is_complete_body(&self) -> bool {
self.as_ref().is_complete_body()
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
self.as_mut().take_complete_body()
}
} }
impl<B> MessageBody for Pin<Box<B>> impl<B> MessageBody for Pin<Box<B>>
where where
B: MessageBody + ?Sized, B: MessageBody,
{ {
type Error = B::Error; type Error = B::Error;
@ -137,10 +175,42 @@ mod foreign_impls {
#[inline] #[inline]
fn poll_next( fn poll_next(
self: Pin<&mut Self>, mut self: Pin<&mut Self>,
cx: &mut Context<'_>, cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> { ) -> Poll<Option<Result<Bytes, Self::Error>>> {
self.get_mut().as_mut().poll_next(cx) self.as_mut().poll_next(cx)
}
#[inline]
fn is_complete_body(&self) -> bool {
self.as_ref().is_complete_body()
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
debug_assert!(
self.is_complete_body(),
"inner type \"{}\" does not allow taking complete body; caller should make sure to \
call `is_complete_body` first",
std::any::type_name::<B>(),
);
// we do not have DerefMut access to call take_complete_body directly but since
// is_complete_body is true we should expect the entire bytes chunk in one poll_next
let waker = futures_util::task::noop_waker();
let mut cx = Context::from_waker(&waker);
match self.as_mut().poll_next(&mut cx) {
Poll::Ready(Some(Ok(data))) => data,
_ => {
panic!(
"inner type \"{}\" indicated it allows taking complete body but failed to \
return Bytes when polled",
std::any::type_name::<B>()
);
}
}
} }
} }
@ -152,21 +222,25 @@ mod foreign_impls {
BodySize::Sized(self.len() as u64) BodySize::Sized(self.len() as u64)
} }
#[inline]
fn poll_next( fn poll_next(
self: Pin<&mut Self>, mut self: Pin<&mut Self>,
_cx: &mut Context<'_>, _cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> { ) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() { if self.is_empty() {
Poll::Ready(None) Poll::Ready(None)
} else { } else {
Poll::Ready(Some(Ok(Bytes::from_static(mem::take(self.get_mut()))))) Poll::Ready(Some(Ok(self.take_complete_body())))
} }
} }
#[inline] #[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> { fn is_complete_body(&self) -> bool {
Ok(Bytes::from_static(self)) true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
Bytes::from_static(mem::take(self))
} }
} }
@ -178,21 +252,25 @@ mod foreign_impls {
BodySize::Sized(self.len() as u64) BodySize::Sized(self.len() as u64)
} }
#[inline]
fn poll_next( fn poll_next(
self: Pin<&mut Self>, mut self: Pin<&mut Self>,
_cx: &mut Context<'_>, _cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> { ) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() { if self.is_empty() {
Poll::Ready(None) Poll::Ready(None)
} else { } else {
Poll::Ready(Some(Ok(mem::take(self.get_mut())))) Poll::Ready(Some(Ok(self.take_complete_body())))
} }
} }
#[inline] #[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> { fn is_complete_body(&self) -> bool {
Ok(self) true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
mem::take(self)
} }
} }
@ -204,21 +282,25 @@ mod foreign_impls {
BodySize::Sized(self.len() as u64) BodySize::Sized(self.len() as u64)
} }
#[inline]
fn poll_next( fn poll_next(
self: Pin<&mut Self>, mut self: Pin<&mut Self>,
_cx: &mut Context<'_>, _cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> { ) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() { if self.is_empty() {
Poll::Ready(None) Poll::Ready(None)
} else { } else {
Poll::Ready(Some(Ok(mem::take(self.get_mut()).freeze()))) Poll::Ready(Some(Ok(self.take_complete_body())))
} }
} }
#[inline] #[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> { fn is_complete_body(&self) -> bool {
Ok(self.freeze()) true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
mem::take(self).freeze()
} }
} }
@ -230,21 +312,25 @@ mod foreign_impls {
BodySize::Sized(self.len() as u64) BodySize::Sized(self.len() as u64)
} }
#[inline]
fn poll_next( fn poll_next(
self: Pin<&mut Self>, mut self: Pin<&mut Self>,
_cx: &mut Context<'_>, _cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> { ) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() { if self.is_empty() {
Poll::Ready(None) Poll::Ready(None)
} else { } else {
Poll::Ready(Some(Ok(mem::take(self.get_mut()).into()))) Poll::Ready(Some(Ok(self.take_complete_body())))
} }
} }
#[inline] #[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> { fn is_complete_body(&self) -> bool {
Ok(Bytes::from(self)) true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
Bytes::from(mem::take(self))
} }
} }
@ -256,7 +342,6 @@ mod foreign_impls {
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<'_>,
@ -271,8 +356,13 @@ mod foreign_impls {
} }
#[inline] #[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> { fn is_complete_body(&self) -> bool {
Ok(Bytes::from_static(self.as_bytes())) true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
Bytes::from_static(mem::take(self).as_bytes())
} }
} }
@ -284,7 +374,6 @@ mod foreign_impls {
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<'_>,
@ -298,8 +387,13 @@ mod foreign_impls {
} }
#[inline] #[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> { fn is_complete_body(&self) -> bool {
Ok(Bytes::from(self)) true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
Bytes::from(mem::take(self))
} }
} }
@ -311,7 +405,6 @@ mod foreign_impls {
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<'_>,
@ -321,8 +414,13 @@ mod foreign_impls {
} }
#[inline] #[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> { fn is_complete_body(&self) -> bool {
Ok(self.into_bytes()) true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
mem::take(self).into_bytes()
} }
} }
} }
@ -377,12 +475,6 @@ 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)]
@ -392,7 +484,6 @@ 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) => {
@ -494,45 +585,49 @@ mod tests {
assert_poll_next!(pl, Bytes::from("test")); assert_poll_next!(pl, Bytes::from("test"));
} }
#[actix_rt::test] #[test]
async fn complete_body_combinators() { fn take_string() {
let body = Bytes::from_static(b"test"); let mut data = "test".repeat(2);
let body = BoxBody::new(body); let data_bytes = Bytes::from(data.clone());
let body = EitherBody::<_, ()>::left(body); assert!(data.is_complete_body());
let body = EitherBody::<(), _>::right(body); assert_eq!(data.take_complete_body(), data_bytes);
// 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")); let mut big_data = "test".repeat(64 * 1024);
let data_bytes = Bytes::from(big_data.clone());
assert!(big_data.is_complete_body());
assert_eq!(big_data.take_complete_body(), data_bytes);
} }
#[actix_rt::test] #[test]
async fn complete_body_combinators_poll() { fn take_boxed_equivalence() {
let body = Bytes::from_static(b"test"); let mut data = Bytes::from_static(b"test");
let body = BoxBody::new(body); assert!(data.is_complete_body());
let body = EitherBody::<_, ()>::left(body); assert_eq!(data.take_complete_body(), b"test".as_ref());
let body = EitherBody::<(), _>::right(body);
let mut body = body;
assert_eq!(body.size(), BodySize::Sized(4)); let mut data = Box::new(Bytes::from_static(b"test"));
assert_poll_next!(Pin::new(&mut body), Bytes::from("test")); assert!(data.is_complete_body());
assert_poll_next_none!(Pin::new(&mut body)); assert_eq!(data.take_complete_body(), b"test".as_ref());
let mut data = Box::pin(Bytes::from_static(b"test"));
assert!(data.is_complete_body());
assert_eq!(data.take_complete_body(), b"test".as_ref());
} }
#[actix_rt::test] #[test]
async fn none_body_combinators() { fn take_policy() {
fn none_body() -> BoxBody { let mut data = Bytes::from_static(b"test");
let body = body::None; // first call returns chunk
let body = BoxBody::new(body); assert_eq!(data.take_complete_body(), b"test".as_ref());
let body = EitherBody::<_, ()>::left(body); // second call returns empty
let body = EitherBody::<(), _>::right(body); assert_eq!(data.take_complete_body(), b"".as_ref());
body.boxed()
}
assert_eq!(none_body().size(), BodySize::None); let waker = futures_util::task::noop_waker();
assert_eq!(none_body().try_into_bytes().unwrap(), Bytes::new()); let mut cx = Context::from_waker(&waker);
assert_poll_next_none!(Pin::new(&mut none_body())); let mut data = Bytes::from_static(b"test");
// take returns whole chunk
assert_eq!(data.take_complete_body(), b"test".as_ref());
// subsequent poll_next returns None
assert_eq!(Pin::new(&mut data).poll_next(&mut cx), Poll::Ready(None));
} }
// down-casting used to be done with a method on MessageBody trait // down-casting used to be done with a method on MessageBody trait

View File

@ -42,7 +42,12 @@ impl MessageBody for None {
} }
#[inline] #[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> { fn is_complete_body(&self) -> bool {
Ok(Bytes::new()) true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
Bytes::new()
} }
} }

View File

@ -36,7 +36,6 @@ 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),

View File

@ -28,14 +28,11 @@ use crate::{
const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049; const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049;
pin_project_lite::pin_project! { pub struct Decoder<S> {
pub struct Decoder<S> { decoder: Option<ContentDecoder>,
decoder: Option<ContentDecoder>, stream: S,
#[pin] eof: bool,
stream: S, fut: Option<JoinHandle<Result<(Option<Bytes>, ContentDecoder), io::Error>>>,
eof: bool,
fut: Option<JoinHandle<Result<(Option<Bytes>, ContentDecoder), io::Error>>>,
}
} }
impl<S> Decoder<S> impl<S> Decoder<S>
@ -47,9 +44,9 @@ 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::Brotli => Some(ContentDecoder::Brotli(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()),
@ -92,44 +89,42 @@ where
impl<S> Stream for Decoder<S> impl<S> Stream for Decoder<S>
where where
S: Stream<Item = Result<Bytes, PayloadError>>, S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
{ {
type Item = Result<Bytes, PayloadError>; type Item = Result<Bytes, PayloadError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut this = self.project();
loop { loop {
if let Some(ref mut fut) = this.fut { if let Some(ref mut fut) = self.fut {
let (chunk, decoder) = let (chunk, decoder) =
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
*this.decoder = Some(decoder); self.decoder = Some(decoder);
this.fut.take(); self.fut.take();
if let Some(chunk) = chunk { if let Some(chunk) = chunk {
return Poll::Ready(Some(Ok(chunk))); return Poll::Ready(Some(Ok(chunk)));
} }
} }
if *this.eof { if self.eof {
return Poll::Ready(None); return Poll::Ready(None);
} }
match ready!(this.stream.as_mut().poll_next(cx)) { match ready!(Pin::new(&mut self.stream).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) = this.decoder.take() { if let Some(mut decoder) = self.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)?;
*this.decoder = Some(decoder); self.decoder = Some(decoder);
if let Some(chunk) = chunk { if let Some(chunk) = chunk {
return Poll::Ready(Some(Ok(chunk))); return Poll::Ready(Some(Ok(chunk)));
} }
} else { } else {
*this.fut = Some(spawn_blocking(move || { self.fut = Some(spawn_blocking(move || {
let chunk = decoder.feed_data(chunk)?; let chunk = decoder.feed_data(chunk)?;
Ok((chunk, decoder)) Ok((chunk, decoder))
})); }));
@ -142,9 +137,9 @@ where
} }
None => { None => {
*this.eof = true; self.eof = true;
return if let Some(mut decoder) = this.decoder.take() { return if let Some(mut decoder) = self.decoder.take() {
match decoder.feed_eof() { match decoder.feed_eof() {
Ok(Some(res)) => Poll::Ready(Some(Ok(res))), Ok(Some(res)) => Poll::Ready(Some(Ok(res))),
Ok(None) => Poll::Ready(None), Ok(None) => Poll::Ready(None),
@ -165,7 +160,7 @@ enum ContentDecoder {
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
Gzip(Box<GzDecoder<Writer>>), Gzip(Box<GzDecoder<Writer>>),
#[cfg(feature = "compress-brotli")] #[cfg(feature = "compress-brotli")]
Brotli(Box<BrotliDecoder<Writer>>), Br(Box<BrotliDecoder<Writer>>),
// We need explicit 'static lifetime here because ZstdDecoder need lifetime // We need explicit 'static lifetime here because ZstdDecoder need lifetime
// argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static` // argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static`
#[cfg(feature = "compress-zstd")] #[cfg(feature = "compress-zstd")]
@ -176,7 +171,7 @@ impl ContentDecoder {
fn feed_eof(&mut self) -> io::Result<Option<Bytes>> { fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
match self { match self {
#[cfg(feature = "compress-brotli")] #[cfg(feature = "compress-brotli")]
ContentDecoder::Brotli(ref mut decoder) => match decoder.flush() { ContentDecoder::Br(ref mut decoder) => match decoder.flush() {
Ok(()) => { Ok(()) => {
let b = decoder.get_mut().take(); let b = decoder.get_mut().take();
@ -234,7 +229,7 @@ impl ContentDecoder {
fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> { fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
match self { match self {
#[cfg(feature = "compress-brotli")] #[cfg(feature = "compress-brotli")]
ContentDecoder::Brotli(ref mut decoder) => match decoder.write_all(&data) { ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => { Ok(_) => {
decoder.flush()?; decoder.flush()?;
let b = decoder.get_mut().take(); let b = decoder.get_mut().take();

View File

@ -25,7 +25,7 @@ use zstd::stream::write::Encoder as ZstdEncoder;
use super::Writer; use super::Writer;
use crate::{ use crate::{
body::{self, BodySize, MessageBody}, body::{BodySize, MessageBody},
error::BlockingError, error::BlockingError,
header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING}, header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING},
ResponseHead, StatusCode, ResponseHead, StatusCode,
@ -46,34 +46,35 @@ 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(encoding: ContentEncoding, head: &mut ResponseHead, body: B) -> Self { pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, mut body: B) -> Self {
let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|| head.status == StatusCode::SWITCHING_PROTOCOLS
|| head.status == StatusCode::NO_CONTENT
|| encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto);
// no need to compress an empty body // no need to compress an empty body
if matches!(body.size(), BodySize::None) { if matches!(body.size(), BodySize::None) {
return Self::none(); return Self::none();
} }
let should_encode = !(head.headers().contains_key(&CONTENT_ENCODING) let body = if body.is_complete_body() {
|| head.status == StatusCode::SWITCHING_PROTOCOLS let body = body.take_complete_body();
|| head.status == StatusCode::NO_CONTENT EncoderBody::Full { body }
|| encoding == ContentEncoding::Identity); } else {
EncoderBody::Stream { body }
let body = match body.try_into_bytes() {
Ok(body) => EncoderBody::Full { body },
Err(body) => EncoderBody::Stream { body },
}; };
if should_encode { if can_encode {
// wrap body only if encoder is feature-enabled // Modify response body only if encoder is set
if let Some(enc) = ContentEncoder::select(encoding) { if let Some(enc) = ContentEncoder::encoder(encoding) {
update_head(encoding, head); update_head(encoding, head);
return Encoder { return Encoder {
@ -97,7 +98,7 @@ impl<B: MessageBody> Encoder<B> {
pin_project! { pin_project! {
#[project = EncoderBodyProj] #[project = EncoderBodyProj]
enum EncoderBody<B> { enum EncoderBody<B> {
None { body: body::None }, None,
Full { body: Bytes }, Full { body: Bytes },
Stream { #[pin] body: B }, Stream { #[pin] body: B },
} }
@ -109,10 +110,9 @@ where
{ {
type Error = EncoderError; type Error = EncoderError;
#[inline]
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
match self { match self {
EncoderBody::None { body } => body.size(), EncoderBody::None => BodySize::None,
EncoderBody::Full { body } => body.size(), EncoderBody::Full { body } => body.size(),
EncoderBody::Stream { body } => body.size(), EncoderBody::Stream { body } => body.size(),
} }
@ -123,9 +123,7 @@ 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 { body } => { EncoderBodyProj::None => Poll::Ready(None),
Pin::new(body).poll_next(cx).map_err(|err| match err {})
}
EncoderBodyProj::Full { body } => { EncoderBodyProj::Full { body } => {
Pin::new(body).poll_next(cx).map_err(|err| match err {}) Pin::new(body).poll_next(cx).map_err(|err| match err {})
} }
@ -135,15 +133,21 @@ where
} }
} }
#[inline] fn is_complete_body(&self) -> bool {
fn try_into_bytes(self) -> Result<Bytes, Self>
where
Self: Sized,
{
match self { match self {
EncoderBody::None { body } => Ok(body.try_into_bytes().unwrap()), EncoderBody::None => true,
EncoderBody::Full { body } => Ok(body.try_into_bytes().unwrap()), EncoderBody::Full { .. } => true,
_ => Err(self), EncoderBody::Stream { .. } => false,
}
}
fn take_complete_body(&mut self) -> Bytes {
match self {
EncoderBody::None => Bytes::new(),
EncoderBody::Full { body } => body.take_complete_body(),
EncoderBody::Stream { .. } => {
panic!("EncoderBody::Stream variant cannot be taken")
}
} }
} }
} }
@ -154,7 +158,6 @@ where
{ {
type Error = EncoderError; type Error = EncoderError;
#[inline]
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
if self.encoder.is_some() { if self.encoder.is_some() {
BodySize::Stream BodySize::Stream
@ -168,7 +171,6 @@ where
cx: &mut Context<'_>, cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> { ) -> Poll<Option<Result<Bytes, Self::Error>>> {
let mut this = self.project(); let mut this = self.project();
loop { loop {
if *this.eof { if *this.eof {
return Poll::Ready(None); return Poll::Ready(None);
@ -232,30 +234,28 @@ where
} }
} }
#[inline] fn is_complete_body(&self) -> bool {
fn try_into_bytes(mut self) -> Result<Bytes, Self>
where
Self: Sized,
{
if self.encoder.is_some() { if self.encoder.is_some() {
Err(self) false
} else { } else {
match self.body.try_into_bytes() { self.body.is_complete_body()
Ok(body) => Ok(body), }
Err(body) => { }
self.body = body;
Err(self) fn take_complete_body(&mut self) -> Bytes {
} if self.encoder.is_some() {
} panic!("compressed body stream cannot be taken")
} else {
self.body.take_complete_body()
} }
} }
} }
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
head.headers_mut() head.headers_mut().insert(
.insert(header::CONTENT_ENCODING, encoding.to_header_value()); header::CONTENT_ENCODING,
head.headers_mut() HeaderValue::from_static(encoding.as_str()),
.insert(header::VARY, HeaderValue::from_static("accept-encoding")); );
head.no_chunking(false); head.no_chunking(false);
} }
@ -268,7 +268,7 @@ enum ContentEncoder {
Gzip(GzEncoder<Writer>), Gzip(GzEncoder<Writer>),
#[cfg(feature = "compress-brotli")] #[cfg(feature = "compress-brotli")]
Brotli(BrotliEncoder<Writer>), Br(BrotliEncoder<Writer>),
// Wwe need explicit 'static lifetime here because ZstdEncoder needs a lifetime argument and we // Wwe need explicit 'static lifetime here because ZstdEncoder needs a lifetime argument and we
// use `spawn_blocking` in `Encoder::poll_next` that requires `FnOnce() -> R + Send + 'static`. // use `spawn_blocking` in `Encoder::poll_next` that requires `FnOnce() -> R + Send + 'static`.
@ -277,7 +277,7 @@ enum ContentEncoder {
} }
impl ContentEncoder { impl ContentEncoder {
fn select(encoding: ContentEncoding) -> Option<Self> { fn encoder(encoding: ContentEncoding) -> Option<Self> {
match encoding { match encoding {
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new( ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new(
@ -292,8 +292,8 @@ impl ContentEncoder {
))), ))),
#[cfg(feature = "compress-brotli")] #[cfg(feature = "compress-brotli")]
ContentEncoding::Brotli => { ContentEncoding::Br => {
Some(ContentEncoder::Brotli(BrotliEncoder::new(Writer::new(), 3))) Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3)))
} }
#[cfg(feature = "compress-zstd")] #[cfg(feature = "compress-zstd")]
@ -310,7 +310,7 @@ impl ContentEncoder {
pub(crate) fn take(&mut self) -> Bytes { pub(crate) fn take(&mut self) -> Bytes {
match *self { match *self {
#[cfg(feature = "compress-brotli")] #[cfg(feature = "compress-brotli")]
ContentEncoder::Brotli(ref mut encoder) => encoder.get_mut().take(), ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
@ -326,7 +326,7 @@ impl ContentEncoder {
fn finish(self) -> Result<Bytes, io::Error> { fn finish(self) -> Result<Bytes, io::Error> {
match self { match self {
#[cfg(feature = "compress-brotli")] #[cfg(feature = "compress-brotli")]
ContentEncoder::Brotli(encoder) => match encoder.finish() { ContentEncoder::Br(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()), Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err), Err(err) => Err(err),
}, },
@ -354,7 +354,7 @@ impl ContentEncoder {
fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
match *self { match *self {
#[cfg(feature = "compress-brotli")] #[cfg(feature = "compress-brotli")]
ContentEncoder::Brotli(ref mut encoder) => match encoder.write_all(data) { ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(err) => { Err(err) => {
trace!("Error decoding br encoding: {}", err); trace!("Error decoding br encoding: {}", err);

View File

@ -250,7 +250,6 @@ impl From<ParseError> for Response<BoxBody> {
/// A set of errors that can occur running blocking tasks in thread pool. /// A set of errors that can occur running blocking tasks in thread pool.
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "Blocking thread pool is gone")] #[display(fmt = "Blocking thread pool is gone")]
// TODO: non-exhaustive
pub struct BlockingError; pub struct BlockingError;
/// A set of errors that can occur during payload parsing. /// A set of errors that can occur during payload parsing.
@ -333,28 +332,31 @@ 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, From)] #[derive(Debug, Display, Error, 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(Response<BoxBody>), Service(#[error(not(source))] Response<BoxBody>),
/// Body streaming error. /// Body error
#[display(fmt = "Body error: {}", _0)] // FIXME: display and error type
Body(Box<dyn StdError>), #[display(fmt = "Body Error")]
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),
/// Request parse error. /// Http request parse error.
#[display(fmt = "Request parse error: {}", _0)] #[display(fmt = "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),
@ -366,23 +368,21 @@ pub enum DispatchError {
#[display(fmt = "Connection shutdown timeout")] #[display(fmt = "Connection shutdown timeout")]
DisconnectTimeout, DisconnectTimeout,
/// Internal error. /// Payload is not consumed
#[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,
}
impl StdError for DispatchError { /// Unknown error
fn source(&self) -> Option<&(dyn StdError + 'static)> { #[display(fmt = "Unknown error")]
match self { Unknown,
// 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.

View File

@ -5,15 +5,13 @@ use bitflags::bitflags;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use http::{Method, Version}; use http::{Method, Version};
use super::{ use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
decoder::{self, PayloadDecoder, PayloadItem, PayloadType}, use super::{decoder, encoder, reserve_readbuf};
encoder, reserve_readbuf, Message, MessageType, use super::{Message, MessageType};
}; use crate::body::BodySize;
use crate::{ use crate::config::ServiceConfig;
body::BodySize, use crate::error::{ParseError, PayloadError};
error::{ParseError, PayloadError}, use crate::message::{ConnectionType, RequestHeadType, ResponseHead};
ConnectionType, RequestHeadType, ResponseHead, ServiceConfig,
};
bitflags! { bitflags! {
struct Flags: u8 { struct Flags: u8 {

View File

@ -5,13 +5,15 @@ use bitflags::bitflags;
use bytes::BytesMut; use bytes::BytesMut;
use http::{Method, Version}; use http::{Method, Version};
use super::{ use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
decoder::{self, PayloadDecoder, PayloadItem, PayloadType}, use super::{decoder, encoder};
encoder, Message, MessageType, use super::{Message, MessageType};
}; use crate::body::BodySize;
use crate::{ use crate::config::ServiceConfig;
body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig, use crate::error::ParseError;
}; use crate::message::ConnectionType;
use crate::request::Request;
use crate::response::Response;
bitflags! { bitflags! {
struct Flags: u8 { struct Flags: u8 {
@ -197,7 +199,7 @@ mod tests {
use http::Method; use http::Method;
use super::*; use super::*;
use crate::HttpMessage as _; use crate::HttpMessage;
#[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() {

View File

@ -2,14 +2,17 @@ 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::{ use http::header::{HeaderName, HeaderValue};
header::{self, HeaderName, HeaderValue}, use http::{header, Method, StatusCode, Uri, Version};
Method, StatusCode, Uri, Version,
};
use log::{debug, error, trace}; use log::{debug, error, trace};
use super::chunked::ChunkedState; use super::chunked::ChunkedState;
use crate::{error::ParseError, header::HeaderMap, ConnectionType, Request, ResponseHead}; use crate::{
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;
@ -47,7 +50,7 @@ pub(crate) enum PayloadLength {
} }
pub(crate) trait MessageType: Sized { pub(crate) trait MessageType: Sized {
fn set_connection_type(&mut self, conn_type: Option<ConnectionType>); fn set_connection_type(&mut self, ctype: Option<ConnectionType>);
fn set_expect(&mut self); fn set_expect(&mut self);
@ -190,8 +193,8 @@ pub(crate) trait MessageType: Sized {
} }
impl MessageType for Request { impl MessageType for Request {
fn set_connection_type(&mut self, conn_type: Option<ConnectionType>) { fn set_connection_type(&mut self, ctype: Option<ConnectionType>) {
if let Some(ctype) = conn_type { if let Some(ctype) = ctype {
self.head_mut().set_connection_type(ctype); self.head_mut().set_connection_type(ctype);
} }
} }
@ -275,8 +278,8 @@ impl MessageType for Request {
} }
impl MessageType for ResponseHead { impl MessageType for ResponseHead {
fn set_connection_type(&mut self, conn_type: Option<ConnectionType>) { fn set_connection_type(&mut self, ctype: Option<ConnectionType>) {
if let Some(ctype) = conn_type { if let Some(ctype) = ctype {
ResponseHead::set_connection_type(self, ctype); ResponseHead::set_connection_type(self, ctype);
} }
} }

View File

@ -15,14 +15,14 @@ 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_lite::pin_project; use pin_project::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,
Error, Extensions, OnConnectData, Request, Response, StatusCode, Extensions, OnConnectData, Request, Response, StatusCode,
}; };
use super::{ use super::{
@ -46,111 +46,79 @@ bitflags! {
} }
} }
// there's 2 versions of Dispatcher state because of: #[pin_project]
// https://github.com/taiki-e/pin-project-lite/issues/3 /// Dispatcher for HTTP/1.1 protocol
// pub struct Dispatcher<T, S, B, X, U>
// tl;dr: pin-project-lite doesn't play well with other attribute macros where
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
#[cfg(not(test))] B: MessageBody,
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>>,
B: MessageBody, X: Service<Request, Response = Request>,
X::Error: Into<Response<BoxBody>>,
X: Service<Request, Response = Request>, U: Service<(Request, Framed<T, Codec>), Response = ()>,
X::Error: Into<Response<BoxBody>>, U::Error: fmt::Display,
{
#[pin]
inner: DispatcherState<T, S, B, X, U>,
U: Service<(Request, Framed<T, Codec>), Response = ()>, #[cfg(test)]
U::Error: fmt::Display, poll_count: u64,
{
#[pin]
inner: DispatcherState<T, S, B, X, U>,
}
} }
#[cfg(test)] #[pin_project(project = DispatcherStateProj)]
pin_project! { enum DispatcherState<T, S, B, X, U>
/// Dispatcher for HTTP/1.1 protocol where
pub struct Dispatcher<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,
{ {
#[pin] Normal(#[pin] InnerDispatcher<T, S, B, X, U>),
inner: DispatcherState<T, S, B, X, U>, Upgrade(#[pin] U::Future),
// used in tests
poll_count: u64,
}
} }
pin_project! { #[pin_project(project = InnerDispatcherProj)]
#[project = DispatcherStateProj] struct InnerDispatcher<T, S, B, X, U>
enum DispatcherState<T, S, B, X, U> where
where S: Service<Request>,
S: Service<Request>, S::Error: Into<Response<BoxBody>>,
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] inner: InnerDispatcher<T, S, B, X, U> }, flow: Rc<HttpFlow<S, X, U>>,
Upgrade { #[pin] fut: U::Future }, flags: Flags,
} peer_addr: Option<net::SocketAddr>,
} conn_data: Option<Rc<Extensions>>,
error: Option<DispatchError>,
pin_project! { #[pin]
#[project = InnerDispatcherProj] state: State<S, B, X>,
struct InnerDispatcher<T, S, B, X, U> payload: Option<PayloadSender>,
where messages: VecDeque<DispatcherMessage>,
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
B: MessageBody, ka_expire: Instant,
#[pin]
ka_timer: Option<Sleep>,
X: Service<Request, Response = Request>, io: Option<T>,
X::Error: Into<Response<BoxBody>>, read_buf: BytesMut,
write_buf: BytesMut,
U: Service<(Request, Framed<T, Codec>), Response = ()>, codec: Codec,
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 {
@ -159,21 +127,19 @@ enum DispatcherMessage {
Error(Response<()>), Error(Response<()>),
} }
pin_project! { #[pin_project(project = StateProj)]
#[project = StateProj] enum State<S, B, X>
enum State<S, B, X> where
where S: Service<Request>,
S: Service<Request>, X: Service<Request, Response = Request>,
X: Service<Request, Response = Request>,
B: MessageBody, B: MessageBody,
{ {
None, None,
ExpectCall { #[pin] fut: X::Future }, ExpectCall(#[pin] X::Future),
ServiceCall { #[pin] fut: S::Future }, ServiceCall(#[pin] S::Future),
SendPayload { #[pin] body: B }, SendPayload(#[pin] B),
SendErrorPayload { #[pin] body: BoxBody }, SendErrorPayload(#[pin] BoxBody),
}
} }
impl<S, B, X> State<S, B, X> impl<S, B, X> State<S, B, X>
@ -232,27 +198,25 @@ where
}; };
Dispatcher { Dispatcher {
inner: DispatcherState::Normal { inner: DispatcherState::Normal(InnerDispatcher {
inner: InnerDispatcher { flow,
flow, flags,
flags, peer_addr,
peer_addr, conn_data: conn_data.0.map(Rc::new),
conn_data: conn_data.0.map(Rc::new), error: None,
error: None,
state: State::None, state: State::None,
payload: None, payload: None,
messages: VecDeque::new(), messages: VecDeque::new(),
ka_expire, ka_expire,
ka_timer, ka_timer,
io: Some(io), io: Some(io),
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
codec: Codec::new(config), codec: Codec::new(config),
}, }),
},
#[cfg(test)] #[cfg(test)]
poll_count: 0, poll_count: 0,
@ -352,7 +316,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(())
@ -366,7 +330,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(())
@ -392,12 +356,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 fut = this.flow.expect.call(req); let task = this.flow.expect.call(req);
this.state.set(State::ExpectCall { fut }); this.state.set(State::ExpectCall(task));
} else { } else {
// the same as expect call. // the same as expect call.
let fut = this.flow.service.call(req); let task = this.flow.service.call(req);
this.state.set(State::ServiceCall { fut }); this.state.set(State::ServiceCall(task));
}; };
} }
@ -417,7 +381,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(());
@ -443,11 +407,11 @@ where
} }
}, },
StateProj::SendPayload { mut body } => { StateProj::SendPayload(mut stream) => {
// 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 body.as_mut().poll_next(cx) { match stream.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => { Poll::Ready(Some(Ok(item))) => {
this.codec this.codec
.encode(Message::Chunk(Some(item)), this.write_buf)?; .encode(Message::Chunk(Some(item)), this.write_buf)?;
@ -473,13 +437,13 @@ where
return Ok(PollResponse::DrainWriteBuf); return Ok(PollResponse::DrainWriteBuf);
} }
StateProj::SendErrorPayload { mut body } => { StateProj::SendErrorPayload(mut stream) => {
// 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 body.as_mut().poll_next(cx) { match stream.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => { Poll::Ready(Some(Ok(item))) => {
this.codec this.codec
.encode(Message::Chunk(Some(item)), this.write_buf)?; .encode(Message::Chunk(Some(item)), this.write_buf)?;
@ -494,9 +458,7 @@ where
} }
Poll::Ready(Some(Err(err))) => { Poll::Ready(Some(Err(err))) => {
return Err(DispatchError::Body( return Err(DispatchError::Service(err.into()))
Error::new_body().with_cause(err).into(),
))
} }
Poll::Pending => return Ok(PollResponse::DoNothing), Poll::Pending => return Ok(PollResponse::DoNothing),
@ -507,14 +469,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
@ -540,25 +502,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 fut = this.flow.expect.call(req); let task = this.flow.expect.call(req);
this.state.set(State::ExpectCall { fut }); this.state.set(State::ExpectCall(task));
} else { } else {
// the same as above. // the same as above.
let fut = this.flow.service.call(req); let task = this.flow.service.call(req);
this.state.set(State::ServiceCall { fut }); this.state.set(State::ServiceCall(task));
}; };
// 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 fut = this.flow.service.call(req); let task = this.flow.service.call(req);
this.state.set(State::ServiceCall { fut }); this.state.set(State::ServiceCall(task));
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
@ -574,7 +536,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
@ -646,11 +608,10 @@ 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 (sender, payload) = Payload::create(false); let (ps, pl) = 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(sender); *this.payload = Some(ps);
} }
// Request has no payload. // Request has no payload.
@ -940,7 +901,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) {
@ -980,7 +941,7 @@ where
self.as_mut() self.as_mut()
.project() .project()
.inner .inner
.set(DispatcherState::Upgrade { fut: upgrade }); .set(DispatcherState::Upgrade(upgrade));
return self.poll(cx); return self.poll(cx);
} }
}; };
@ -1032,8 +993,8 @@ where
} }
} }
} }
DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| { DispatcherStateProj::Upgrade(fut) => fut.poll(cx).map_err(|e| {
error!("Upgrade handler error: {}", err); error!("Upgrade handler error: {}", e);
DispatchError::Upgrade DispatchError::Upgrade
}), }),
} }
@ -1127,7 +1088,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],
@ -1162,7 +1123,7 @@ mod tests {
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"),
@ -1172,7 +1133,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);
@ -1216,7 +1177,7 @@ mod tests {
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"),
@ -1226,7 +1187,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);
@ -1276,13 +1237,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!(
@ -1297,7 +1258,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);
@ -1348,12 +1309,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);
@ -1425,7 +1386,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);

View File

@ -1,19 +1,19 @@
use std::{ use std::io::Write;
cmp, use std::marker::PhantomData;
io::{self, Write as _}, use std::ptr::copy_nonoverlapping;
marker::PhantomData, use std::slice::from_raw_parts_mut;
ptr::copy_nonoverlapping, use std::{cmp, io};
slice::from_raw_parts_mut,
};
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use crate::{ use crate::{
body::BodySize, body::BodySize,
header::{ config::ServiceConfig,
map::Value, HeaderMap, HeaderName, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, header::{map::Value, HeaderMap, HeaderName},
}, header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
helpers, ConnectionType, RequestHeadType, Response, ServiceConfig, StatusCode, Version, helpers,
message::{ConnectionType, RequestHeadType},
Response, StatusCode, Version,
}; };
const AVERAGE_HEADER_SIZE: usize = 30; const AVERAGE_HEADER_SIZE: usize = 30;

View File

@ -1,7 +1,8 @@
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, Request}; use crate::error::Error;
use crate::request::Request;
pub struct ExpectHandler; pub struct ExpectHandler;

View File

@ -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; use crate::request::Request;
impl Message<Request> { impl Message<Request> {
pub fn message(self) -> Request { pub fn message(self) -> Request {

View File

@ -1,12 +1,9 @@
//! Payload stream //! Payload stream
use std::cell::RefCell;
use std::{ use std::collections::VecDeque;
cell::RefCell, use std::pin::Pin;
collections::VecDeque, use std::rc::{Rc, Weak};
pin::Pin, use std::task::{Context, Poll, Waker};
rc::{Rc, Weak},
task::{Context, Poll, Waker},
};
use bytes::Bytes; use bytes::Bytes;
use futures_core::Stream; use futures_core::Stream;
@ -25,32 +22,39 @@ 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 `poll_next`. Payload does /// Payload stores chunks in a vector. First chunk can be received with
/// not notify current task when new data is available. /// `.readany()` method. Payload stream is not thread safe. Payload does not
/// notify current task when new data is available.
/// ///
/// Payload can be used as `Response` body stream. /// Payload stream 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 {
/// Creates a payload stream. /// Create payload stream.
/// ///
/// This method construct two objects responsible for bytes stream generation: /// This method construct two objects responsible for bytes stream
/// - `PayloadSender` - *Sender* side of the stream /// generation.
/// - `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::new(Rc::downgrade(&shared)), PayloadSender {
inner: Rc::downgrade(&shared),
},
Payload { inner: shared }, Payload { inner: shared },
) )
} }
/// Creates an empty payload. /// Create empty payload
pub(crate) fn empty() -> Payload { #[doc(hidden)]
pub fn empty() -> Payload {
Payload { Payload {
inner: Rc::new(RefCell::new(Inner::new(true))), inner: Rc::new(RefCell::new(Inner::new(true))),
} }
@ -73,6 +77,14 @@ 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 {
@ -82,7 +94,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>>> {
Pin::new(&mut *self.inner.borrow_mut()).poll_next(cx) self.inner.borrow_mut().readany(cx)
} }
} }
@ -92,10 +104,6 @@ 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() {
@ -219,10 +227,7 @@ impl Inner {
self.len self.len
} }
fn poll_next( fn readany(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, PayloadError>>> {
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> {
if let Some(data) = self.items.pop_front() { if let Some(data) = self.items.pop_front() {
self.len -= data.len(); self.len -= data.len();
self.need_read = self.len < MAX_BUFFER_SIZE; self.need_read = self.len < MAX_BUFFER_SIZE;
@ -252,18 +257,8 @@ impl Inner {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::panic::{RefUnwindSafe, UnwindSafe};
use actix_utils::future::poll_fn;
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*; use super::*;
use actix_utils::future::poll_fn;
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() {
@ -275,10 +270,7 @@ mod tests {
assert_eq!( assert_eq!(
Bytes::from("data"), Bytes::from("data"),
poll_fn(|cx| Pin::new(&mut payload).poll_next(cx)) poll_fn(|cx| payload.readany(cx)).await.unwrap().unwrap()
.await
.unwrap()
.unwrap()
); );
} }
} }

View File

@ -356,9 +356,9 @@ 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(|err| { self._poll_ready(cx).map_err(|e| {
log::error!("HTTP/1 service readiness error: {:?}", err); log::error!("HTTP/1 service readiness error: {:?}", e);
DispatchError::Service(err) DispatchError::Service(e)
}) })
} }

View File

@ -2,7 +2,9 @@ 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::{h1::Codec, Error, Request}; use crate::error::Error;
use crate::h1::Codec;
use crate::request::Request;
pub struct UpgradeHandler; pub struct UpgradeHandler;

View File

@ -9,8 +9,9 @@ use pin_project_lite::pin_project;
use crate::{ use crate::{
body::{BodySize, MessageBody}, body::{BodySize, MessageBody},
error::Error,
h1::{Codec, Message}, h1::{Codec, Message},
Error, Response, response::Response,
}; };
pin_project! { pin_project! {
@ -45,7 +46,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, B: MessageBody + Unpin,
B::Error: Into<Error>, B::Error: Into<Error>,
{ {
type Output = Result<Framed<T, Codec>, Error>; type Output = Result<Framed<T, Codec>, Error>;
@ -81,7 +82,7 @@ where
// 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 {
this.body.set(None); let _ = this.body.take();
} }
let framed = this.framed.as_mut().as_pin_mut().unwrap(); let framed = this.framed.as_mut().as_pin_mut().unwrap();
framed framed

View File

@ -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 payload = crate::h2::Payload::new(body); let pl = crate::h2::Payload::new(body);
let pl = Payload::H2 { payload }; let pl = Payload::H2(pl);
let mut req = Request::with_payload(pl); let mut req = Request::with_payload(pl);
let head = req.head_mut(); let head = req.head_mut();
@ -288,11 +288,9 @@ fn prepare_response(
let _ = match size { let _ = match size {
BodySize::None | BodySize::Stream => None, BodySize::None | BodySize::Stream => None,
BodySize::Sized(0) => { BodySize::Sized(0) => res
#[allow(clippy::declare_interior_mutable_const)] .headers_mut()
const HV_ZERO: HeaderValue = HeaderValue::from_static("0"); .insert(CONTENT_LENGTH, 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();

View File

@ -98,14 +98,3 @@ 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);
}

View File

@ -6,7 +6,7 @@ use ahash::AHashMap;
use http::header::{HeaderName, HeaderValue}; use http::header::{HeaderName, HeaderValue};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use super::AsHeaderName; use crate::header::AsHeaderName;
/// A multi-map of HTTP headers. /// A multi-map of HTTP headers.
/// ///
@ -306,11 +306,8 @@ 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) -> std::slice::Iter<'_, HeaderValue> { pub fn get_all(&self, key: impl AsHeaderName) -> GetAll<'_> {
match self.get_value(key) { GetAll::new(self.get_value(key))
Some(value) => value.iter(),
None => (&[]).iter(),
}
} }
// TODO: get_all_mut ? // TODO: get_all_mut ?
@ -605,13 +602,52 @@ impl<'a> IntoIterator for &'a HeaderMap {
} }
} }
/// Convert `http::HeaderMap` to our `HeaderMap`. /// Iterator over borrowed values with the same associated name.
impl From<http::HeaderMap> for HeaderMap { ///
fn from(mut map: http::HeaderMap) -> HeaderMap { /// See [`HeaderMap::get_all`].
HeaderMap::from_drain(map.drain()) #[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`]
@ -859,7 +895,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!(std::slice::Iter<'_, HeaderValue>: Iterator, ExactSizeIterator, FusedIterator); assert_impl_all!(GetAll<'_>: 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);

View File

@ -50,13 +50,20 @@ pub use self::utils::{
/// An interface for types that already represent a valid header. /// An interface for types that already represent a valid header.
pub trait Header: TryIntoHeaderValue { pub trait Header: TryIntoHeaderValue {
/// Returns the name of the header field. /// Returns the name of the header field
fn name() -> HeaderName; fn name() -> HeaderName;
/// Parse the header from a HTTP message. /// Parse a header
fn parse<M: HttpMessage>(msg: &M) -> Result<Self, ParseError>; fn parse<M: HttpMessage>(msg: &M) -> Result<Self, ParseError>;
} }
/// Convert `http::HeaderMap` to our `HeaderMap`.
impl From<http::HeaderMap> for HeaderMap {
fn from(mut map: http::HeaderMap) -> HeaderMap {
HeaderMap::from_drain(map.drain())
}
}
/// This encode set is used for HTTP header values and is defined at /// This encode set is used for HTTP header values and is defined at
/// <https://datatracker.ietf.org/doc/html/rfc5987#section-3.2>. /// <https://datatracker.ietf.org/doc/html/rfc5987#section-3.2>.
pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS

View File

@ -20,16 +20,14 @@ pub struct ContentEncodingParseError;
/// See [IANA HTTP Content Coding Registry]. /// See [IANA HTTP Content Coding Registry].
/// ///
/// [IANA HTTP Content Coding Registry]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml /// [IANA HTTP Content Coding Registry]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive] #[non_exhaustive]
pub enum ContentEncoding { pub enum ContentEncoding {
/// Indicates the no-op identity encoding. /// Automatically select encoding based on encoding negotiation.
/// Auto,
/// I.e., no compression or modification.
Identity,
/// A format using the Brotli algorithm. /// A format using the Brotli algorithm.
Brotli, Br,
/// A format using the zlib structure with deflate algorithm. /// A format using the zlib structure with deflate algorithm.
Deflate, Deflate,
@ -39,36 +37,32 @@ pub enum ContentEncoding {
/// Zstd algorithm. /// Zstd algorithm.
Zstd, Zstd,
/// Indicates the identity function (i.e. no compression, nor modification).
Identity,
} }
impl ContentEncoding { impl ContentEncoding {
/// Is the content compressed?
#[inline]
pub fn is_compression(self) -> bool {
matches!(self, ContentEncoding::Identity | ContentEncoding::Auto)
}
/// Convert content encoding to string. /// Convert content encoding to string.
#[inline] #[inline]
pub const fn as_str(self) -> &'static str { pub fn as_str(self) -> &'static str {
match self { match self {
ContentEncoding::Brotli => "br", ContentEncoding::Br => "br",
ContentEncoding::Gzip => "gzip", ContentEncoding::Gzip => "gzip",
ContentEncoding::Deflate => "deflate", ContentEncoding::Deflate => "deflate",
ContentEncoding::Zstd => "zstd", ContentEncoding::Zstd => "zstd",
ContentEncoding::Identity => "identity", ContentEncoding::Identity | ContentEncoding::Auto => "identity",
}
}
/// Convert content encoding to header value.
#[inline]
pub const fn to_header_value(self) -> HeaderValue {
match self {
ContentEncoding::Brotli => HeaderValue::from_static("br"),
ContentEncoding::Gzip => HeaderValue::from_static("gzip"),
ContentEncoding::Deflate => HeaderValue::from_static("deflate"),
ContentEncoding::Zstd => HeaderValue::from_static("zstd"),
ContentEncoding::Identity => HeaderValue::from_static("identity"),
} }
} }
} }
impl Default for ContentEncoding { impl Default for ContentEncoding {
#[inline]
fn default() -> Self { fn default() -> Self {
Self::Identity Self::Identity
} }
@ -77,18 +71,16 @@ impl Default for ContentEncoding {
impl FromStr for ContentEncoding { impl FromStr for ContentEncoding {
type Err = ContentEncodingParseError; type Err = ContentEncodingParseError;
fn from_str(enc: &str) -> Result<Self, Self::Err> { fn from_str(val: &str) -> Result<Self, Self::Err> {
let enc = enc.trim(); let val = val.trim();
if enc.eq_ignore_ascii_case("br") { if val.eq_ignore_ascii_case("br") {
Ok(ContentEncoding::Brotli) Ok(ContentEncoding::Br)
} else if enc.eq_ignore_ascii_case("gzip") { } else if val.eq_ignore_ascii_case("gzip") {
Ok(ContentEncoding::Gzip) Ok(ContentEncoding::Gzip)
} else if enc.eq_ignore_ascii_case("deflate") { } else if val.eq_ignore_ascii_case("deflate") {
Ok(ContentEncoding::Deflate) Ok(ContentEncoding::Deflate)
} else if enc.eq_ignore_ascii_case("identity") { } else if val.eq_ignore_ascii_case("zstd") {
Ok(ContentEncoding::Identity)
} else if enc.eq_ignore_ascii_case("zstd") {
Ok(ContentEncoding::Zstd) Ok(ContentEncoding::Zstd)
} else { } else {
Err(ContentEncodingParseError) Err(ContentEncodingParseError)

View File

@ -27,8 +27,7 @@ const MAX_QUALITY_FLOAT: f32 = 1.0;
/// ///
/// assert_eq!(q(0.42).to_string(), "0.42"); /// assert_eq!(q(0.42).to_string(), "0.42");
/// assert_eq!(q(1.0).to_string(), "1"); /// assert_eq!(q(1.0).to_string(), "1");
/// assert_eq!(Quality::MIN.to_string(), "0.001"); /// assert_eq!(Quality::MIN.to_string(), "0");
/// assert_eq!(Quality::ZERO.to_string(), "0");
/// ``` /// ```
/// ///
/// [RFC 7231 §5.3.1]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1 /// [RFC 7231 §5.3.1]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1
@ -39,11 +38,8 @@ impl Quality {
/// The maximum quality value, equivalent to `q=1.0`. /// The maximum quality value, equivalent to `q=1.0`.
pub const MAX: Quality = Quality(MAX_QUALITY_INT); pub const MAX: Quality = Quality(MAX_QUALITY_INT);
/// The minimum, non-zero quality value, equivalent to `q=0.001`. /// The minimum quality value, equivalent to `q=0.0`.
pub const MIN: Quality = Quality(1); pub const MIN: Quality = Quality(0);
/// The zero quality value, equivalent to `q=0.0`.
pub const ZERO: Quality = Quality(0);
/// Converts a float in the range 0.01.0 to a `Quality`. /// Converts a float in the range 0.01.0 to a `Quality`.
/// ///
@ -55,7 +51,7 @@ impl Quality {
// Check that `value` is within range should be done before calling this method. // Check that `value` is within range should be done before calling this method.
// Just in case, this debug_assert should catch if we were forgetful. // Just in case, this debug_assert should catch if we were forgetful.
debug_assert!( debug_assert!(
(0.0..=MAX_QUALITY_FLOAT).contains(&value), (0.0f32..=1.0f32).contains(&value),
"q value must be between 0.0 and 1.0" "q value must be between 0.0 and 1.0"
); );
@ -91,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 1099 // x in is range 1099
@ -99,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 100999 // x is in range 100999
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)
} }
} }
} }
@ -121,12 +117,6 @@ 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]
@ -158,13 +148,10 @@ impl TryFrom<f32> for Quality {
/// let q1 = q(1.0); /// let q1 = q(1.0);
/// assert_eq!(q1, Quality::MAX); /// assert_eq!(q1, Quality::MAX);
/// ///
/// let q2 = q(0.001); /// let q2 = q(0.0);
/// assert_eq!(q2, Quality::MIN); /// assert_eq!(q2, Quality::MIN);
/// ///
/// let q3 = q(0.0); /// let q3 = q(0.42);
/// assert_eq!(q3, Quality::ZERO);
///
/// let q4 = q(0.42);
/// ``` /// ```
/// ///
/// An out-of-range `f32` quality will panic. /// An out-of-range `f32` quality will panic.
@ -192,10 +179,6 @@ mod tests {
#[test] #[test]
fn display_output() { fn display_output() {
assert_eq!(Quality::ZERO.to_string(), "0");
assert_eq!(Quality::MIN.to_string(), "0.001");
assert_eq!(Quality::MAX.to_string(), "1");
assert_eq!(q(0.0).to_string(), "0"); assert_eq!(q(0.0).to_string(), "0");
assert_eq!(q(1.0).to_string(), "1"); assert_eq!(q(1.0).to_string(), "1");
assert_eq!(q(0.001).to_string(), "0.001"); assert_eq!(q(0.001).to_string(), "0.001");

View File

@ -31,7 +31,7 @@ use super::Quality;
/// let q_item_fallback: QualityItem<String> = "abc;q=0.1".parse().unwrap(); /// let q_item_fallback: QualityItem<String> = "abc;q=0.1".parse().unwrap();
/// assert!(q_item > q_item_fallback); /// assert!(q_item > q_item_fallback);
/// ``` /// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct QualityItem<T> { pub struct QualityItem<T> {
/// The wrapped contents of the field. /// The wrapped contents of the field.
pub item: T, pub item: T,
@ -53,15 +53,10 @@ impl<T> QualityItem<T> {
Self::new(item, Quality::MAX) Self::new(item, Quality::MAX)
} }
/// Constructs a new `QualityItem` from an item, using the minimum, non-zero q-value. /// Constructs a new `QualityItem` from an item, using the minimum q-value.
pub fn min(item: T) -> Self { pub fn min(item: T) -> Self {
Self::new(item, Quality::MIN) Self::new(item, Quality::MIN)
} }
/// Constructs a new `QualityItem` from an item, using zero q-value of zero.
pub fn zero(item: T) -> Self {
Self::new(item, Quality::ZERO)
}
} }
impl<T: PartialEq> PartialOrd for QualityItem<T> { impl<T: PartialEq> PartialOrd for QualityItem<T> {
@ -78,10 +73,7 @@ impl<T: fmt::Display> fmt::Display for QualityItem<T> {
// q-factor value is implied for max value // q-factor value is implied for max value
Quality::MAX => Ok(()), Quality::MAX => Ok(()),
// fast path for zero Quality::MIN => f.write_str("; q=0"),
Quality::ZERO => f.write_str("; q=0"),
// quality formatting is already using itoa
q => write!(f, "; q={}", q), q => write!(f, "; q={}", q),
} }
} }

View File

@ -19,6 +19,7 @@
#![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 +28,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 requests; mod request;
mod responses; mod response;
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,13 +58,16 @@ 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; pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead};
#[allow(deprecated)] pub use self::payload::{Payload, PayloadStream};
pub use self::payload::{BoxedPayloadStream, Payload, PayloadStream}; pub use self::request::Request;
pub use self::requests::{Request, RequestHead, RequestHeadType}; pub use self::response::Response;
pub use self::responses::{Response, ResponseBuilder, ResponseHead}; pub use self::response_builder::ResponseBuilder;
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]

View File

@ -1,7 +1,16 @@
use std::{cell::RefCell, ops, rc::Rc}; use std::{
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 {
@ -35,6 +44,294 @@ pub trait Head: Default + 'static {
F: FnOnce(&MessagePool<Self>) -> R; F: FnOnce(&MessagePool<Self>) -> R;
} }
#[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)
}
}
#[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.
@ -43,13 +340,12 @@ 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> ops::Deref for Message<T> { impl<T: Head> std::ops::Deref for Message<T> {
type Target = T; type Target = T;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@ -57,7 +353,7 @@ impl<T: Head> ops::Deref for Message<T> {
} }
} }
impl<T: Head> ops::DerefMut for Message<T> { impl<T: Head> std::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")
} }
@ -69,12 +365,53 @@ 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> {
pub(crate) fn create() -> MessagePool<T> { fn create() -> MessagePool<T> {
MessagePool(RefCell::new(Vec::with_capacity(128))) MessagePool(RefCell::new(Vec::with_capacity(128)))
} }
@ -96,11 +433,43 @@ impl<T: Head> MessagePool<T> {
} }
#[inline] #[inline]
/// Release message instance /// Release request instance
fn release(&self, msg: Rc<T>) { fn release(&self, msg: Rc<T>) {
let pool = &mut self.0.borrow_mut(); let v = &mut self.0.borrow_mut();
if pool.len() < 128 { if v.len() < 128 {
pool.push(msg); v.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);
} }
} }
} }

View File

@ -1,89 +1,67 @@
use std::{ use std::pin::Pin;
mem, use std::task::{Context, Poll};
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;
/// A boxed payload stream. /// A boxed payload.
pub type BoxedPayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>; pub type PayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>;
#[deprecated(since = "4.0.0", note = "Renamed to `BoxedPayloadStream`.")] /// A streaming payload.
pub type PayloadStream = BoxedPayloadStream; pub enum Payload<S = PayloadStream> {
None,
pin_project_lite::pin_project! { H1(crate::h1::Payload),
/// A streaming payload. H2(crate::h2::Payload),
#[project = PayloadProj] Stream(S),
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(payload: crate::h1::Payload) -> Self { fn from(v: crate::h1::Payload) -> Self {
Payload::H1 { payload } Payload::H1(v)
} }
} }
impl<S> From<crate::h2::Payload> for Payload<S> { impl<S> From<crate::h2::Payload> for Payload<S> {
fn from(payload: crate::h2::Payload) -> Self { fn from(v: crate::h2::Payload) -> Self {
Payload::H2 { payload } Payload::H2(v)
} }
} }
impl<S> From<h2::RecvStream> for Payload<S> { impl<S> From<RecvStream> for Payload<S> {
fn from(stream: h2::RecvStream) -> Self { fn from(v: RecvStream) -> Self {
Payload::H2 { Payload::H2(crate::h2::Payload::new(v))
payload: crate::h2::Payload::new(stream),
}
} }
} }
impl From<BoxedPayloadStream> for Payload { impl From<PayloadStream> for Payload {
fn from(payload: BoxedPayloadStream) -> Self { fn from(pl: PayloadStream) -> Self {
Payload::Stream { payload } Payload::Stream(pl)
} }
} }
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> {
mem::replace(self, Payload::None) std::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>>, S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
{ {
type Item = Result<Bytes, PayloadError>; type Item = Result<Bytes, PayloadError>;
#[inline] #[inline]
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match self.project() { match self.get_mut() {
PayloadProj::None => Poll::Ready(None), Payload::None => Poll::Ready(None),
PayloadProj::H1 { payload } => Pin::new(payload).poll_next(cx), Payload::H1(ref mut pl) => pl.readany(cx),
PayloadProj::H2 { payload } => Pin::new(payload).poll_next(cx), Payload::H2(ref mut pl) => Pin::new(pl).poll_next(cx),
PayloadProj::Stream { payload } => payload.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);
}

View File

@ -10,12 +10,15 @@ use std::{
use http::{header, Method, Uri, Version}; use http::{header, Method, Uri, Version};
use crate::{ use crate::{
header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Message, Payload, extensions::Extensions,
RequestHead, header::HeaderMap,
message::{Message, RequestHead},
payload::{Payload, PayloadStream},
HttpMessage,
}; };
/// An HTTP request. /// An HTTP request.
pub struct Request<P = BoxedPayloadStream> { pub struct Request<P = PayloadStream> {
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) conn_data: Option<Rc<Extensions>>,
@ -47,7 +50,7 @@ impl<P> HttpMessage for Request<P> {
} }
} }
impl From<Message<RequestHead>> for Request<BoxedPayloadStream> { impl From<Message<RequestHead>> for Request<PayloadStream> {
fn from(head: Message<RequestHead>) -> Self { fn from(head: Message<RequestHead>) -> Self {
Request { Request {
head, head,
@ -58,10 +61,9 @@ impl From<Message<RequestHead>> for Request<BoxedPayloadStream> {
} }
} }
impl Request<BoxedPayloadStream> { impl Request<PayloadStream> {
/// Create new Request instance /// Create new Request instance
#[allow(clippy::new_without_default)] pub fn new() -> Request<PayloadStream> {
pub fn new() -> Request<BoxedPayloadStream> {
Request { Request {
head: Message::new(), head: Message::new(),
payload: Payload::None, payload: Payload::None,
@ -204,7 +206,7 @@ impl<P> Request<P> {
/// Returns the request data container, leaving an empty one in it's place. /// Returns the request data container, leaving an empty one in it's place.
pub fn take_req_data(&mut self) -> Extensions { pub fn take_req_data(&mut self) -> Extensions {
mem::take(self.req_data.get_mut()) mem::take(&mut self.req_data.get_mut())
} }
} }

View File

@ -1,174 +0,0 @@
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)
}
}

View File

@ -1,7 +0,0 @@
//! HTTP requests.
mod head;
mod request;
pub use self::head::{RequestHead, RequestHeadType};
pub use self::request::Request;

View File

@ -10,9 +10,10 @@ use bytestring::ByteString;
use crate::{ use crate::{
body::{BoxBody, MessageBody}, body::{BoxBody, MessageBody},
extensions::Extensions,
header::{self, HeaderMap, TryIntoHeaderValue}, header::{self, HeaderMap, TryIntoHeaderValue},
responses::BoxedResponseHead, message::{BoxedResponseHead, ResponseHead},
Error, Extensions, ResponseBuilder, ResponseHead, StatusCode, Error, ResponseBuilder, StatusCode,
}; };
/// An HTTP response. /// An HTTP response.
@ -169,7 +170,7 @@ impl<B> Response<B> {
/// Returns split head and body. /// Returns split head and body.
/// ///
/// # Implementation Notes /// # Implementation Notes
/// Due to internal performance optimizations, the first element of the returned tuple is a /// Due to internal performance optimisations, 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(())
@ -193,7 +194,7 @@ impl<B> Response<B> {
where where
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
self.map_body(|_, body| body.boxed()) self.map_body(|_, body| BoxBody::new(body))
} }
/// Returns body, consuming this response. /// Returns body, consuming this response.

View File

@ -9,8 +9,8 @@ use crate::{
body::{EitherBody, MessageBody}, body::{EitherBody, MessageBody},
error::{Error, HttpError}, error::{Error, HttpError},
header::{self, TryIntoHeaderPair, TryIntoHeaderValue}, header::{self, TryIntoHeaderPair, TryIntoHeaderValue},
responses::{BoxedResponseHead, ResponseHead}, message::{BoxedResponseHead, ConnectionType, ResponseHead},
ConnectionType, Extensions, Response, StatusCode, Extensions, Response, StatusCode,
}; };
/// An HTTP response builder. /// An HTTP response builder.

View File

@ -1,208 +0,0 @@
//! 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);
}
}
}

View File

@ -1,11 +0,0 @@
//! 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;

View File

@ -493,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(|err| { self._poll_ready(cx).map_err(|e| {
log::error!("HTTP service readiness error: {:?}", err); log::error!("HTTP service readiness error: {:?}", e);
DispatchError::Service(err) DispatchError::Service(e)
}) })
} }

View File

@ -120,7 +120,7 @@ impl TestRequest {
} }
/// Set request payload. /// Set request payload.
pub fn set_payload(&mut self, data: impl Into<Bytes>) -> &mut Self { pub fn set_payload<B: Into<Bytes>>(&mut self, data: B) -> &mut Self {
let mut payload = crate::h1::Payload::empty(); let mut payload = crate::h1::Payload::empty();
payload.unread_data(data.into()); payload.unread_data(data.into());
parts(&mut self.0).payload = Some(payload.into()); parts(&mut self.0).payload = Some(payload.into());
@ -264,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(BytesMut::new()) Self::new("")
} }
pub fn read_buf(&self) -> Ref<'_, BytesMut> { pub fn read_buf(&self) -> Ref<'_, BytesMut> {

View File

@ -9,7 +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::{header::HeaderValue, RequestHead, Response, ResponseBuilder}; use crate::{header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder};
mod codec; mod codec;
mod dispatcher; mod dispatcher;
@ -99,9 +99,8 @@ 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);
#[allow(clippy::declare_interior_mutable_const)] res.headers_mut()
const HV_GET: HeaderValue = HeaderValue::from_static("GET"); .insert(header::ALLOW, HeaderValue::from_static("GET"));
res.headers_mut().insert(header::ALLOW, HV_GET);
res res
} }

View File

@ -7,7 +7,6 @@ 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::{
@ -17,37 +16,25 @@ 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, poll_fn}; use actix_utils::future::{err, ok};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use derive_more::{Display, Error}; use derive_more::{Display, Error};
use futures_core::{ready, Stream}; use futures_core::Stream;
use futures_util::stream::once; use futures_util::stream::{once, StreamExt as _};
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>(stream: S) -> Result<BytesMut, PayloadError> async fn load_body<S>(mut stream: S) -> Result<BytesMut, PayloadError>
where where
S: Stream<Item = Result<Bytes, PayloadError>>, S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
{ {
let mut buf = BytesMut::new(); let mut body = BytesMut::new();
while let Some(item) = stream.next().await {
pin!(stream); body.extend_from_slice(&item?)
}
poll_fn(|cx| loop { Ok(body)
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 {

View File

@ -3,128 +3,120 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 0.4.0-beta.12 - 2022-01-04
- Minimum supported Rust version (MSRV) is now 1.54.
## 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 ## 0.4.0-beta.10 - 2021-12-11
- No significant changes since `0.4.0-beta.9`. * 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

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-multipart" name = "actix-multipart"
version = "0.4.0-beta.12" version = "0.4.0-beta.10"
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"]
@ -15,7 +15,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.20", default-features = false } actix-web = { version = "4.0.0-beta.14", 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.18" actix-http = "3.0.0-beta.15"
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.8.4", features = ["sync"] } tokio = { version = "1", features = ["sync"] }
tokio-stream = "0.1" tokio-stream = "0.1"

View File

@ -3,15 +3,15 @@
> Multipart form support for Actix Web. > Multipart form support for Actix Web.
[![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart)
[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.12)](https://docs.rs/actix-multipart/0.4.0-beta.12) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.10)](https://docs.rs/actix-multipart/0.4.0-beta.10)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.12/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.12) [![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.10/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.10)
[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart) - [API Documentation](https://docs.rs/actix-multipart)
- Minimum Supported Rust Version (MSRV): 1.54 - Minimum Supported Rust Version (MSRV): 1.52

View File

@ -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!(),
} }
} }

View File

@ -1,37 +1,20 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* Minimum supported Rust version (MSRV) is now 1.52.
## 0.5.0-rc.1 - 2022-01-14
- `Resource` trait now have an associated type, `Path`, instead of the generic parameter. [#2568]
- `Resource` is now implemented for `&mut Path<_>` and `RefMut<Path<_>>`. [#2568]
[#2568]: https://github.com/actix/actix-web/pull/2568
## 0.5.0-beta.4 - 2022-01-04
- `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#2566]
- Minimum supported Rust version (MSRV) is now 1.54.
[#2566]: https://github.com/actix/actix-net/pull/2566
## 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
@ -42,23 +25,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
@ -70,10 +53,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
@ -82,68 +65,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

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-router" name = "actix-router"
version = "0.5.0-rc.1" version = "0.5.0-beta.2"
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.5" firestorm = "0.4"
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.5", features = ["enable_system_time"] } firestorm = { version = "0.4", features = ["enable_system_time"] }
http = "0.2.5" http = "0.2.5"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }

View File

@ -1,14 +1,8 @@
use std::borrow::Cow;
use serde::de::{self, Deserializer, Error as DeError, Visitor}; use serde::de::{self, Deserializer, Error as DeError, Visitor};
use serde::forward_to_deserialize_any; use serde::forward_to_deserialize_any;
use crate::path::{Path, PathIter}; use crate::path::{Path, PathIter};
use crate::{Quoter, ResourcePath}; use crate::ResourcePath;
thread_local! {
static FULL_QUOTER: Quoter = Quoter::new(b"+/%", b"");
}
macro_rules! unsupported_type { macro_rules! unsupported_type {
($trait_fn:ident, $name:expr) => { ($trait_fn:ident, $name:expr) => {
@ -16,13 +10,16 @@ macro_rules! unsupported_type {
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
Err(de::Error::custom(concat!("unsupported type: ", $name))) Err(de::value::Error::custom(concat!(
"unsupported type: ",
$name
)))
} }
}; };
} }
macro_rules! parse_single_value { macro_rules! parse_single_value {
($trait_fn:ident) => { ($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error> fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
@ -36,35 +33,18 @@ macro_rules! parse_single_value {
.as_str(), .as_str(),
)) ))
} else { } else {
Value { let v = self.path[0].parse().map_err(|_| {
value: &self.path[0], de::value::Error::custom(format!(
} "can not parse {:?} to a {}",
.$trait_fn(visitor) &self.path[0], $tp
))
})?;
visitor.$visit_fn(v)
} }
} }
}; };
} }
macro_rules! parse_value {
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
let decoded = FULL_QUOTER
.with(|q| q.requote(self.value.as_bytes()))
.map(Cow::Owned)
.unwrap_or(Cow::Borrowed(self.value));
let v = decoded.parse().map_err(|_| {
de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp))
})?;
visitor.$visit_fn(v)
}
};
}
pub struct PathDeserializer<'de, T: ResourcePath> { pub struct PathDeserializer<'de, T: ResourcePath> {
path: &'de Path<T>, path: &'de Path<T>,
} }
@ -192,6 +172,23 @@ impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T>
} }
} }
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.path.segment_count() != 1 {
Err(de::value::Error::custom(
format!(
"wrong number of parameters: {} expected 1",
self.path.segment_count()
)
.as_str(),
))
} else {
visitor.visit_str(&self.path[0])
}
}
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error> fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
@ -202,26 +199,25 @@ impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T>
} }
unsupported_type!(deserialize_any, "'any'"); unsupported_type!(deserialize_any, "'any'");
unsupported_type!(deserialize_bytes, "bytes");
unsupported_type!(deserialize_option, "Option<T>"); unsupported_type!(deserialize_option, "Option<T>");
unsupported_type!(deserialize_identifier, "identifier"); unsupported_type!(deserialize_identifier, "identifier");
unsupported_type!(deserialize_ignored_any, "ignored_any"); unsupported_type!(deserialize_ignored_any, "ignored_any");
parse_single_value!(deserialize_bool); parse_single_value!(deserialize_bool, visit_bool, "bool");
parse_single_value!(deserialize_i8); parse_single_value!(deserialize_i8, visit_i8, "i8");
parse_single_value!(deserialize_i16); parse_single_value!(deserialize_i16, visit_i16, "i16");
parse_single_value!(deserialize_i32); parse_single_value!(deserialize_i32, visit_i32, "i32");
parse_single_value!(deserialize_i64); parse_single_value!(deserialize_i64, visit_i64, "i64");
parse_single_value!(deserialize_u8); parse_single_value!(deserialize_u8, visit_u8, "u8");
parse_single_value!(deserialize_u16); parse_single_value!(deserialize_u16, visit_u16, "u16");
parse_single_value!(deserialize_u32); parse_single_value!(deserialize_u32, visit_u32, "u32");
parse_single_value!(deserialize_u64); parse_single_value!(deserialize_u64, visit_u64, "u64");
parse_single_value!(deserialize_f32); parse_single_value!(deserialize_f32, visit_f32, "f32");
parse_single_value!(deserialize_f64); parse_single_value!(deserialize_f64, visit_f64, "f64");
parse_single_value!(deserialize_str); parse_single_value!(deserialize_string, visit_string, "String");
parse_single_value!(deserialize_string); parse_single_value!(deserialize_byte_buf, visit_string, "String");
parse_single_value!(deserialize_bytes); parse_single_value!(deserialize_char, visit_char, "char");
parse_single_value!(deserialize_byte_buf);
parse_single_value!(deserialize_char);
} }
struct ParamsDeserializer<'de, T: ResourcePath> { struct ParamsDeserializer<'de, T: ResourcePath> {
@ -283,6 +279,20 @@ impl<'de> Deserializer<'de> for Key<'de> {
} }
} }
macro_rules! parse_value {
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
let v = self.value.parse().map_err(|_| {
de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp))
})?;
visitor.$visit_fn(v)
}
};
}
struct Value<'de> { struct Value<'de> {
value: &'de str, value: &'de str,
} }
@ -301,6 +311,8 @@ impl<'de> Deserializer<'de> for Value<'de> {
parse_value!(deserialize_u64, visit_u64, "u64"); parse_value!(deserialize_u64, visit_u64, "u64");
parse_value!(deserialize_f32, visit_f32, "f32"); parse_value!(deserialize_f32, visit_f32, "f32");
parse_value!(deserialize_f64, visit_f64, "f64"); parse_value!(deserialize_f64, visit_f64, "f64");
parse_value!(deserialize_string, visit_string, "String");
parse_value!(deserialize_byte_buf, visit_string, "String");
parse_value!(deserialize_char, visit_char, "char"); parse_value!(deserialize_char, visit_char, "char");
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error> fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
@ -328,38 +340,18 @@ impl<'de> Deserializer<'de> for Value<'de> {
visitor.visit_unit() visitor.visit_unit()
} }
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) {
Some(s) => visitor.visit_string(s),
None => visitor.visit_borrowed_str(self.value),
}
}
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error> fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
match FULL_QUOTER.with(|q| q.requote(self.value.as_bytes())) { visitor.visit_borrowed_bytes(self.value.as_bytes())
Some(s) => visitor.visit_byte_buf(s.into()),
None => visitor.visit_borrowed_bytes(self.value.as_bytes()),
}
} }
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error> fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
self.deserialize_bytes(visitor) visitor.visit_borrowed_str(self.value)
}
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_str(visitor)
} }
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error> fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
@ -505,7 +497,6 @@ mod tests {
use super::*; use super::*;
use crate::path::Path; use crate::path::Path;
use crate::router::Router; use crate::router::Router;
use crate::ResourceDef;
#[derive(Deserialize)] #[derive(Deserialize)]
struct MyStruct { struct MyStruct {
@ -666,79 +657,6 @@ mod tests {
assert!(format!("{:?}", s).contains("can not parse")); assert!(format!("{:?}", s).contains("can not parse"));
} }
#[test]
fn deserialize_path_decode_string() {
let rdef = ResourceDef::new("/{key}");
let mut path = Path::new("/%25");
rdef.capture_match_info(&mut path);
let de = PathDeserializer::new(&path);
let segment: String = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(segment, "%");
let mut path = Path::new("/%2F");
rdef.capture_match_info(&mut path);
let de = PathDeserializer::new(&path);
let segment: String = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(segment, "/")
}
#[test]
fn deserialize_path_decode_seq() {
let rdef = ResourceDef::new("/{key}/{value}");
let mut path = Path::new("/%30%25/%30%2F");
rdef.capture_match_info(&mut path);
let de = PathDeserializer::new(&path);
let segment: (String, String) = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(segment.0, "0%");
assert_eq!(segment.1, "0/");
}
#[test]
fn deserialize_path_decode_map() {
#[derive(Deserialize)]
struct Vals {
key: String,
value: String,
}
let rdef = ResourceDef::new("/{key}/{value}");
let mut path = Path::new("/%25/%2F");
rdef.capture_match_info(&mut path);
let de = PathDeserializer::new(&path);
let vals: Vals = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(vals.key, "%");
assert_eq!(vals.value, "/");
}
#[test]
fn deserialize_borrowed() {
#[derive(Debug, Deserialize)]
struct Params<'a> {
val: &'a str,
}
let rdef = ResourceDef::new("/{val}");
let mut path = Path::new("/X");
rdef.capture_match_info(&mut path);
let de = PathDeserializer::new(&path);
let params: Params<'_> = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(params.val, "X");
let de = PathDeserializer::new(&path);
let params: &str = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(params, "X");
let mut path = Path::new("/%2F");
rdef.capture_match_info(&mut path);
let de = PathDeserializer::new(&path);
assert!(<Params<'_> as serde::Deserialize>::deserialize(de).is_err());
let de = PathDeserializer::new(&path);
assert!(<&str as serde::Deserialize>::deserialize(de).is_err());
}
// #[test] // #[test]
// fn test_extract_path_decode() { // fn test_extract_path_decode() {
// let mut router = Router::<()>::default(); // let mut router = Router::<()>::default();

View File

@ -8,7 +8,6 @@
mod de; mod de;
mod path; mod path;
mod pattern; mod pattern;
mod quoter;
mod resource; mod resource;
mod resource_path; mod resource_path;
mod router; mod router;
@ -19,10 +18,9 @@ mod url;
pub use self::de::PathDeserializer; pub use self::de::PathDeserializer;
pub use self::path::Path; pub use self::path::Path;
pub use self::pattern::{IntoPatterns, Patterns}; pub use self::pattern::{IntoPatterns, Patterns};
pub use self::quoter::Quoter;
pub use self::resource::ResourceDef; pub use self::resource::ResourceDef;
pub use self::resource_path::{Resource, ResourcePath}; pub use self::resource_path::{Resource, ResourcePath};
pub use self::router::{ResourceInfo, Router, RouterBuilder}; pub use self::router::{ResourceInfo, Router, RouterBuilder};
#[cfg(feature = "http")] #[cfg(feature = "http")]
pub use self::url::Url; pub use self::url::{Quoter, Url};

View File

@ -1,5 +1,5 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::ops::{DerefMut, Index}; use std::ops::Index;
use firestorm::profile_method; use firestorm::profile_method;
use serde::de; use serde::de;
@ -213,38 +213,8 @@ impl<T: ResourcePath> Index<usize> for Path<T> {
} }
} }
impl<T: ResourcePath> Resource for Path<T> { impl<T: ResourcePath> Resource<T> for Path<T> {
type Path = T; fn resource_path(&mut self) -> &mut Self {
fn resource_path(&mut self) -> &mut Path<Self::Path> {
self self
} }
} }
impl<T, P> Resource for T
where
T: DerefMut<Target = Path<P>>,
P: ResourcePath,
{
type Path = P;
fn resource_path(&mut self) -> &mut Path<Self::Path> {
&mut *self
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use super::*;
#[test]
fn deref_impls() {
let mut foo = Path::new("/foo");
let _ = (&mut foo).resource_path();
let foo = RefCell::new(foo);
let _ = foo.borrow_mut().resource_path();
}
}

View File

@ -1,219 +0,0 @@
#[allow(dead_code)]
const GEN_DELIMS: &[u8] = b":/?#[]@";
#[allow(dead_code)]
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
#[allow(dead_code)]
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
#[allow(dead_code)]
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
#[allow(dead_code)]
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
1234567890
-._~";
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
1234567890
-._~
!$'()*,";
const QS: &[u8] = b"+&=;b";
/// A quoter
pub struct Quoter {
/// Simple bit-map of safe values in the 0-127 ASCII range.
safe_table: [u8; 16],
/// Simple bit-map of protected values in the 0-127 ASCII range.
protected_table: [u8; 16],
}
impl Quoter {
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
let mut quoter = Quoter {
safe_table: [0; 16],
protected_table: [0; 16],
};
// prepare safe table
for ch in 0..128 {
if ALLOWED.contains(&ch) {
set_bit(&mut quoter.safe_table, ch);
}
if QS.contains(&ch) {
set_bit(&mut quoter.safe_table, ch);
}
}
for &ch in safe {
set_bit(&mut quoter.safe_table, ch)
}
// prepare protected table
for &ch in protected {
set_bit(&mut quoter.safe_table, ch);
set_bit(&mut quoter.protected_table, ch);
}
quoter
}
/// Re-quotes... ?
///
/// Returns `None` when no modification to the original string was required.
pub fn requote(&self, val: &[u8]) -> Option<String> {
let mut has_pct = 0;
let mut pct = [b'%', 0, 0];
let mut idx = 0;
let mut cloned: Option<Vec<u8>> = None;
let len = val.len();
while idx < len {
let ch = val[idx];
if has_pct != 0 {
pct[has_pct] = val[idx];
has_pct += 1;
if has_pct == 3 {
has_pct = 0;
let buf = cloned.as_mut().unwrap();
if let Some(ch) = hex_pair_to_char(pct[1], pct[2]) {
if ch < 128 {
if bit_at(&self.protected_table, ch) {
buf.extend_from_slice(&pct);
idx += 1;
continue;
}
if bit_at(&self.safe_table, ch) {
buf.push(ch);
idx += 1;
continue;
}
}
buf.push(ch);
} else {
buf.extend_from_slice(&pct[..]);
}
}
} else if ch == b'%' {
has_pct = 1;
if cloned.is_none() {
let mut c = Vec::with_capacity(len);
c.extend_from_slice(&val[..idx]);
cloned = Some(c);
}
} else if let Some(ref mut cloned) = cloned {
cloned.push(ch)
}
idx += 1;
}
cloned.map(|data| String::from_utf8_lossy(&data).into_owned())
}
}
/// Converts an ASCII character in the hex-encoded set (`0-9`, `A-F`, `a-f`) to its integer
/// representation from `0x0``0xF`.
///
/// - `0x30 ('0') => 0x0`
/// - `0x39 ('9') => 0x9`
/// - `0x41 ('a') => 0xA`
/// - `0x61 ('A') => 0xA`
/// - `0x46 ('f') => 0xF`
/// - `0x66 ('F') => 0xF`
fn from_ascii_hex(v: u8) -> Option<u8> {
match v {
b'0'..=b'9' => Some(v - 0x30), // ord('0') == 0x30
b'A'..=b'F' => Some(v - 0x41 + 10), // ord('A') == 0x41
b'a'..=b'f' => Some(v - 0x61 + 10), // ord('a') == 0x61
_ => None,
}
}
/// Decode a ASCII hex-encoded pair to an integer.
///
/// Returns `None` if either portion of the decoded pair does not evaluate to a valid hex value.
///
/// - `0x33 ('3'), 0x30 ('0') => 0x30 ('0')`
/// - `0x34 ('4'), 0x31 ('1') => 0x41 ('A')`
/// - `0x36 ('6'), 0x31 ('1') => 0x61 ('a')`
fn hex_pair_to_char(d1: u8, d2: u8) -> Option<u8> {
let (d_high, d_low) = (from_ascii_hex(d1)?, from_ascii_hex(d2)?);
// left shift high nibble by 4 bits
Some(d_high << 4 | d_low)
}
/// Sets bit in given bit-map to 1=true.
///
/// # Panics
/// Panics if `ch` index is out of bounds.
fn set_bit(array: &mut [u8], ch: u8) {
array[(ch >> 3) as usize] |= 0b1 << (ch & 0b111)
}
/// Returns true if bit to true in given bit-map.
///
/// # Panics
/// Panics if `ch` index is out of bounds.
fn bit_at(array: &[u8], ch: u8) -> bool {
array[(ch >> 3) as usize] & (0b1 << (ch & 0b111)) != 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hex_encoding() {
let hex = b"0123456789abcdefABCDEF";
for i in 0..256 {
let c = i as u8;
if hex.contains(&c) {
assert!(from_ascii_hex(c).is_some())
} else {
assert!(from_ascii_hex(c).is_none())
}
}
let expected = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15,
];
for i in 0..hex.len() {
assert_eq!(from_ascii_hex(hex[i]).unwrap(), expected[i]);
}
}
#[test]
fn custom_quoter() {
let q = Quoter::new(b"", b"+");
assert_eq!(q.requote(b"/a%25c").unwrap(), "/a%c");
assert_eq!(q.requote(b"/a%2Bc").unwrap(), "/a%2Bc");
let q = Quoter::new(b"%+", b"/");
assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), "/a%b+c");
assert_eq!(q.requote(b"/a%2fb").unwrap(), "/a%2fb");
assert_eq!(q.requote(b"/a%2Fb").unwrap(), "/a%2Fb");
assert_eq!(q.requote(b"/a%0Ab").unwrap(), "/a\nb");
}
#[test]
fn quoter_no_modification() {
let q = Quoter::new(b"", b"");
assert_eq!(q.requote(b"/abc/../efg"), None);
}
}

View File

@ -29,25 +29,26 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// ///
/// ///
/// # Pattern Format and Matching Behavior /// # Pattern Format and Matching Behavior
///
/// Resource pattern is defined as a string of zero or more _segments_ where each segment is /// Resource pattern is defined as a string of zero or more _segments_ where each segment is
/// preceded by a slash `/`. /// preceded by a slash `/`.
/// ///
/// This means that pattern string __must__ either be empty or begin with a slash (`/`). This also /// This means that pattern string __must__ either be empty or begin with a slash (`/`).
/// implies that a trailing slash in pattern defines an empty segment. For example, the pattern /// This also implies that a trailing slash in pattern defines an empty segment.
/// `"/user/"` has two segments: `["user", ""]` /// For example, the pattern `"/user/"` has two segments: `["user", ""]`
/// ///
/// A key point to understand is that `ResourceDef` matches segments, not strings. Segments are /// A key point to underhand is that `ResourceDef` matches segments, not strings.
/// matched individually. For example, the pattern `/user/` is not considered a prefix for the path /// It matches segments individually.
/// `/user/123/456`, because the second segment doesn't match: `["user", ""]` /// For example, the pattern `/user/` is not considered a prefix for the path `/user/123/456`,
/// vs `["user", "123", "456"]`. /// because the second segment doesn't match: `["user", ""]` vs `["user", "123", "456"]`.
/// ///
/// This definition is consistent with the definition of absolute URL path in /// This definition is consistent with the definition of absolute URL path in
/// [RFC 3986 §3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3) /// [RFC 3986 (section 3.3)](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3)
/// ///
/// ///
/// # Static Resources /// # Static Resources
/// A static resource is the most basic type of definition. Pass a pattern to [new][Self::new]. /// A static resource is the most basic type of definition. Pass a pattern to
/// Conforming paths must match the pattern exactly. /// [new][Self::new]. Conforming paths must match the pattern exactly.
/// ///
/// ## Examples /// ## Examples
/// ``` /// ```
@ -62,6 +63,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/search")); /// assert!(!resource.is_match("/search"));
/// ``` /// ```
/// ///
///
/// # Dynamic Segments /// # Dynamic Segments
/// Also known as "path parameters". Resources can define sections of a pattern that be extracted /// Also known as "path parameters". Resources can define sections of a pattern that be extracted
/// from a conforming path, if it conforms to (one of) the resource pattern(s). /// from a conforming path, if it conforms to (one of) the resource pattern(s).
@ -100,15 +102,15 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert_eq!(path.get("id").unwrap(), "123"); /// assert_eq!(path.get("id").unwrap(), "123");
/// ``` /// ```
/// ///
///
/// # Prefix Resources /// # Prefix Resources
/// A prefix resource is defined as pattern that can match just the start of a path, up to a /// A prefix resource is defined as pattern that can match just the start of a path, up to a
/// segment boundary. /// segment boundary.
/// ///
/// Prefix patterns with a trailing slash may have an unexpected, though correct, behavior. /// Prefix patterns with a trailing slash may have an unexpected, though correct, behavior.
/// They define and therefore require an empty segment in order to match. It is easier to understand /// They define and therefore require an empty segment in order to match. Examples are given below.
/// this behavior after reading the [matching behavior section]. Examples are given below.
/// ///
/// The empty pattern (`""`), as a prefix, matches any path. /// Empty pattern matches any path as a prefix.
/// ///
/// Prefix resources can contain dynamic segments. /// Prefix resources can contain dynamic segments.
/// ///
@ -128,6 +130,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/user/123")); /// assert!(!resource.is_match("/user/123"));
/// ``` /// ```
/// ///
///
/// # Custom Regex Segments /// # Custom Regex Segments
/// Dynamic segments can be customised to only match a specific regular expression. It can be /// Dynamic segments can be customised to only match a specific regular expression. It can be
/// helpful to do this if resource definitions would otherwise conflict and cause one to /// helpful to do this if resource definitions would otherwise conflict and cause one to
@ -155,6 +158,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/user/abc")); /// assert!(!resource.is_match("/user/abc"));
/// ``` /// ```
/// ///
///
/// # Tail Segments /// # Tail Segments
/// As a shortcut to defining a custom regex for matching _all_ remaining characters (not just those /// As a shortcut to defining a custom regex for matching _all_ remaining characters (not just those
/// up until a `/` character), there is a special pattern to match (and capture) the remaining /// up until a `/` character), there is a special pattern to match (and capture) the remaining
@ -175,6 +179,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert_eq!(path.get("tail").unwrap(), "main/LICENSE"); /// assert_eq!(path.get("tail").unwrap(), "main/LICENSE");
/// ``` /// ```
/// ///
///
/// # Multi-Pattern Resources /// # Multi-Pattern Resources
/// For resources that can map to multiple distinct paths, it may be suitable to use /// For resources that can map to multiple distinct paths, it may be suitable to use
/// multi-pattern resources by passing an array/vec to [`new`][Self::new]. They will be combined /// multi-pattern resources by passing an array/vec to [`new`][Self::new]. They will be combined
@ -193,6 +198,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(resource.is_match("/index")); /// assert!(resource.is_match("/index"));
/// ``` /// ```
/// ///
///
/// # Trailing Slashes /// # Trailing Slashes
/// It should be noted that this library takes no steps to normalize intra-path or trailing slashes. /// It should be noted that this library takes no steps to normalize intra-path or trailing slashes.
/// As such, all resource definitions implicitly expect a pre-processing step to normalize paths if /// As such, all resource definitions implicitly expect a pre-processing step to normalize paths if
@ -206,8 +212,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!ResourceDef::new("/root/").is_match("/root")); /// assert!(!ResourceDef::new("/root/").is_match("/root"));
/// assert!(!ResourceDef::prefix("/root/").is_match("/root")); /// assert!(!ResourceDef::prefix("/root/").is_match("/root"));
/// ``` /// ```
///
/// [matching behavior section]: #pattern-format-and-matching-behavior
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ResourceDef { pub struct ResourceDef {
id: u16, id: u16,
@ -275,7 +279,7 @@ impl ResourceDef {
/// ``` /// ```
pub fn new<T: IntoPatterns>(paths: T) -> Self { pub fn new<T: IntoPatterns>(paths: T) -> Self {
profile_method!(new); profile_method!(new);
Self::construct(paths, false) Self::new2(paths, false)
} }
/// Constructs a new resource definition using a pattern that performs prefix matching. /// Constructs a new resource definition using a pattern that performs prefix matching.
@ -288,7 +292,7 @@ impl ResourceDef {
/// resource definition with a tail segment; use [`new`][Self::new] in this case. /// resource definition with a tail segment; use [`new`][Self::new] in this case.
/// ///
/// # Panics /// # Panics
/// Panics if path pattern is malformed. /// Panics if path regex pattern is malformed.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
@ -303,14 +307,14 @@ impl ResourceDef {
/// ``` /// ```
pub fn prefix<T: IntoPatterns>(paths: T) -> Self { pub fn prefix<T: IntoPatterns>(paths: T) -> Self {
profile_method!(prefix); profile_method!(prefix);
ResourceDef::construct(paths, true) ResourceDef::new2(paths, true)
} }
/// Constructs a new resource definition using a string pattern that performs prefix matching, /// Constructs a new resource definition using a string pattern that performs prefix matching,
/// ensuring a leading `/` if pattern is not empty. /// inserting a `/` to beginning of the pattern if absent and pattern is not empty.
/// ///
/// # Panics /// # Panics
/// Panics if path pattern is malformed. /// Panics if path regex pattern is malformed.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
@ -511,8 +515,8 @@ impl ResourceDef {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
match patterns.len() { match patterns.len() {
1 => ResourceDef::construct(&patterns[0], other.is_prefix()), 1 => ResourceDef::new2(&patterns[0], other.is_prefix()),
_ => ResourceDef::construct(patterns, other.is_prefix()), _ => ResourceDef::new2(patterns, other.is_prefix()),
} }
} }
@ -678,14 +682,15 @@ impl ResourceDef {
/// assert!(!try_match(&resource, &mut path)); /// assert!(!try_match(&resource, &mut path));
/// assert_eq!(path.unprocessed(), "/user/admin/stars"); /// assert_eq!(path.unprocessed(), "/user/admin/stars");
/// ``` /// ```
pub fn capture_match_info_fn<R, F, U>( pub fn capture_match_info_fn<R, T, F, U>(
&self, &self,
resource: &mut R, resource: &mut R,
check_fn: F, check_fn: F,
user_data: U, user_data: U,
) -> bool ) -> bool
where where
R: Resource, R: Resource<T>,
T: ResourcePath,
F: FnOnce(&R, U) -> bool, F: FnOnce(&R, U) -> bool,
{ {
profile_method!(capture_match_info_fn); profile_method!(capture_match_info_fn);
@ -876,8 +881,8 @@ impl ResourceDef {
} }
} }
fn construct<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self { fn new2<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
profile_method!(construct); profile_method!(new2);
let patterns = paths.patterns(); let patterns = paths.patterns();
let (pat_type, segments) = match &patterns { let (pat_type, segments) = match &patterns {
@ -1809,7 +1814,7 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn prefix_plus_tail_match_disallowed() { fn prefix_plus_tail_match_is_allowed() {
ResourceDef::prefix("/user/{id}*"); ResourceDef::prefix("/user/{id}*");
} }
} }

View File

@ -2,11 +2,8 @@ use crate::Path;
// TODO: this trait is necessary, document it // TODO: this trait is necessary, document it
// see impl Resource for ServiceRequest // see impl Resource for ServiceRequest
pub trait Resource { pub trait Resource<T: ResourcePath> {
/// Type of resource's path returned in `resource_path`. fn resource_path(&mut self) -> &mut Path<T>;
type Path: ResourcePath;
fn resource_path(&mut self) -> &mut Path<Self::Path>;
} }
pub trait ResourcePath { pub trait ResourcePath {

View File

@ -1,6 +1,6 @@
use firestorm::profile_method; use firestorm::profile_method;
use crate::{IntoPatterns, Resource, ResourceDef}; use crate::{IntoPatterns, Resource, ResourceDef, ResourcePath};
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct ResourceId(pub u16); pub struct ResourceId(pub u16);
@ -26,9 +26,10 @@ impl<T, U> Router<T, U> {
} }
} }
pub fn recognize<R>(&self, resource: &mut R) -> Option<(&T, ResourceId)> pub fn recognize<R, P>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
where where
R: Resource, R: Resource<P>,
P: ResourcePath,
{ {
profile_method!(recognize); profile_method!(recognize);
@ -41,9 +42,10 @@ impl<T, U> Router<T, U> {
None None
} }
pub fn recognize_mut<R>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)> pub fn recognize_mut<R, P>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)>
where where
R: Resource, R: Resource<P>,
P: ResourcePath,
{ {
profile_method!(recognize_mut); profile_method!(recognize_mut);
@ -56,10 +58,11 @@ impl<T, U> Router<T, U> {
None None
} }
pub fn recognize_fn<R, F>(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)> pub fn recognize_fn<R, P, F>(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)>
where where
F: Fn(&R, &Option<U>) -> bool, F: Fn(&R, &Option<U>) -> bool,
R: Resource, R: Resource<P>,
P: ResourcePath,
{ {
profile_method!(recognize_checked); profile_method!(recognize_checked);
@ -72,14 +75,15 @@ impl<T, U> Router<T, U> {
None None
} }
pub fn recognize_mut_fn<R, F>( pub fn recognize_mut_fn<R, P, F>(
&mut self, &mut self,
resource: &mut R, resource: &mut R,
check: F, check: F,
) -> Option<(&mut T, ResourceId)> ) -> Option<(&mut T, ResourceId)>
where where
F: Fn(&R, &Option<U>) -> bool, F: Fn(&R, &Option<U>) -> bool,
R: Resource, R: Resource<P>,
P: ResourcePath,
{ {
profile_method!(recognize_mut_checked); profile_method!(recognize_mut_checked);

View File

@ -1,44 +1,68 @@
use crate::ResourcePath; use crate::ResourcePath;
use crate::Quoter; #[allow(dead_code)]
const GEN_DELIMS: &[u8] = b":/?#[]@";
#[allow(dead_code)]
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
#[allow(dead_code)]
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
#[allow(dead_code)]
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
#[allow(dead_code)]
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
1234567890
-._~";
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
1234567890
-._~
!$'()*,";
const QS: &[u8] = b"+&=;b";
#[inline]
fn bit_at(array: &[u8], ch: u8) -> bool {
array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0
}
#[inline]
fn set_bit(array: &mut [u8], ch: u8) {
array[(ch >> 3) as usize] |= 1 << (ch & 7)
}
thread_local! { thread_local! {
static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+"); static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+");
} }
#[derive(Debug, Clone, Default)] #[derive(Default, Clone, Debug)]
pub struct Url { pub struct Url {
uri: http::Uri, uri: http::Uri,
path: Option<String>, path: Option<String>,
} }
impl Url { impl Url {
#[inline]
pub fn new(uri: http::Uri) -> Url { pub fn new(uri: http::Uri) -> Url {
let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes())); let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
Url { uri, path } Url { uri, path }
} }
#[inline] pub fn with_quoter(uri: http::Uri, quoter: &Quoter) -> Url {
pub fn new_with_quoter(uri: http::Uri, quoter: &Quoter) -> Url {
Url { Url {
path: quoter.requote(uri.path().as_bytes()), path: quoter.requote(uri.path().as_bytes()),
uri, uri,
} }
} }
/// Returns URI.
#[inline]
pub fn uri(&self) -> &http::Uri { pub fn uri(&self) -> &http::Uri {
&self.uri &self.uri
} }
/// Returns path.
#[inline]
pub fn path(&self) -> &str { pub fn path(&self) -> &str {
match self.path { if let Some(ref s) = self.path {
Some(ref path) => path, s
_ => self.uri.path(), } else {
self.uri.path()
} }
} }
@ -62,6 +86,112 @@ impl ResourcePath for Url {
} }
} }
pub struct Quoter {
safe_table: [u8; 16],
protected_table: [u8; 16],
}
impl Quoter {
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
let mut q = Quoter {
safe_table: [0; 16],
protected_table: [0; 16],
};
// prepare safe table
for i in 0..128 {
if ALLOWED.contains(&i) {
set_bit(&mut q.safe_table, i);
}
if QS.contains(&i) {
set_bit(&mut q.safe_table, i);
}
}
for ch in safe {
set_bit(&mut q.safe_table, *ch)
}
// prepare protected table
for ch in protected {
set_bit(&mut q.safe_table, *ch);
set_bit(&mut q.protected_table, *ch);
}
q
}
pub fn requote(&self, val: &[u8]) -> Option<String> {
let mut has_pct = 0;
let mut pct = [b'%', 0, 0];
let mut idx = 0;
let mut cloned: Option<Vec<u8>> = None;
let len = val.len();
while idx < len {
let ch = val[idx];
if has_pct != 0 {
pct[has_pct] = val[idx];
has_pct += 1;
if has_pct == 3 {
has_pct = 0;
let buf = cloned.as_mut().unwrap();
if let Some(ch) = restore_ch(pct[1], pct[2]) {
if ch < 128 {
if bit_at(&self.protected_table, ch) {
buf.extend_from_slice(&pct);
idx += 1;
continue;
}
if bit_at(&self.safe_table, ch) {
buf.push(ch);
idx += 1;
continue;
}
}
buf.push(ch);
} else {
buf.extend_from_slice(&pct[..]);
}
}
} else if ch == b'%' {
has_pct = 1;
if cloned.is_none() {
let mut c = Vec::with_capacity(len);
c.extend_from_slice(&val[..idx]);
cloned = Some(c);
}
} else if let Some(ref mut cloned) = cloned {
cloned.push(ch)
}
idx += 1;
}
cloned.map(|data| String::from_utf8_lossy(&data).into_owned())
}
}
#[inline]
fn from_hex(v: u8) -> Option<u8> {
if (b'0'..=b'9').contains(&v) {
Some(v - 0x30) // ord('0') == 0x30
} else if (b'A'..=b'F').contains(&v) {
Some(v - 0x41 + 10) // ord('A') == 0x41
} else if (b'a'..=b'f').contains(&v) {
Some(v - 0x61 + 10) // ord('a') == 0x61
} else {
None
}
}
#[inline]
fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
from_hex(d1).and_then(|d1| from_hex(d2).map(move |d2| d1 << 4 | d2))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use http::Uri; use http::Uri;
@ -85,21 +215,11 @@ mod tests {
} }
#[test] #[test]
fn parse_url() { fn test_parse_url() {
let re = "/user/{id}/test"; let re = "/user/{id}/test";
let path = match_url(re, "/user/2345/test"); let path = match_url(re, "/user/2345/test");
assert_eq!(path.get("id").unwrap(), "2345"); assert_eq!(path.get("id").unwrap(), "2345");
}
#[test]
fn protected_chars() {
let re = "/user/{id}/test";
let encoded = percent_encode(PROTECTED);
let path = match_url(re, format!("/user/{}/test", encoded));
// characters in captured segment remain unencoded
assert_eq!(path.get("id").unwrap(), &encoded);
// "%25" should never be decoded into '%' to guarantee the output is a valid // "%25" should never be decoded into '%' to guarantee the output is a valid
// percent-encoded format // percent-encoded format
@ -111,17 +231,24 @@ mod tests {
} }
#[test] #[test]
fn non_protected_ascii() { fn test_protected_chars() {
let non_protected_ascii = ('\u{0}'..='\u{7F}') let encoded = percent_encode(PROTECTED);
.filter(|&c| c.is_ascii() && !PROTECTED.contains(&(c as u8)))
.collect::<String>();
let encoded = percent_encode(non_protected_ascii.as_bytes());
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded)); let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
assert_eq!(path.get("id").unwrap(), &non_protected_ascii); assert_eq!(path.get("id").unwrap(), &encoded);
} }
#[test] #[test]
fn valid_utf8_multibyte() { fn test_non_protecteed_ascii() {
let nonprotected_ascii = ('\u{0}'..='\u{7F}')
.filter(|&c| c.is_ascii() && !PROTECTED.contains(&(c as u8)))
.collect::<String>();
let encoded = percent_encode(nonprotected_ascii.as_bytes());
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
assert_eq!(path.get("id").unwrap(), &nonprotected_ascii);
}
#[test]
fn test_valid_utf8_multibyte() {
let test = ('\u{FF00}'..='\u{FFFF}').collect::<String>(); let test = ('\u{FF00}'..='\u{FFFF}').collect::<String>();
let encoded = percent_encode(test.as_bytes()); let encoded = percent_encode(test.as_bytes());
let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded)); let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded));
@ -129,7 +256,7 @@ mod tests {
} }
#[test] #[test]
fn invalid_utf8() { fn test_invalid_utf8() {
let invalid_utf8 = percent_encode((0x80..=0xff).collect::<Vec<_>>().as_slice()); let invalid_utf8 = percent_encode((0x80..=0xff).collect::<Vec<_>>().as_slice());
let uri = Uri::try_from(format!("/{}", invalid_utf8)).unwrap(); let uri = Uri::try_from(format!("/{}", invalid_utf8)).unwrap();
let path = Path::new(Url::new(uri)); let path = Path::new(Url::new(uri));
@ -137,4 +264,25 @@ mod tests {
// We should always get a valid utf8 string // We should always get a valid utf8 string
assert!(String::from_utf8(path.path().as_bytes().to_owned()).is_ok()); assert!(String::from_utf8(path.path().as_bytes().to_owned()).is_ok());
} }
#[test]
fn test_from_hex() {
let hex = b"0123456789abcdefABCDEF";
for i in 0..256 {
let c = i as u8;
if hex.contains(&c) {
assert!(from_hex(c).is_some())
} else {
assert!(from_hex(c).is_none())
}
}
let expected = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15,
];
for i in 0..hex.len() {
assert_eq!(from_hex(hex[i]).unwrap(), expected[i]);
}
}
} }

View File

@ -3,55 +3,40 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 0.1.0-beta.11 - 2022-01-04
- Minimum supported Rust version (MSRV) is now 1.54.
## 0.1.0-beta.10 - 2021-12-27
- No significant changes since `0.1.0-beta.9`.
## 0.1.0-beta.9 - 2021-12-17
- Re-export `actix_http::body::to_bytes`. [#2518]
- Update `actix_web::test` re-exports. [#2518]
[#2518]: https://github.com/actix/actix-web/pull/2518
## 0.1.0-beta.8 - 2021-12-11 ## 0.1.0-beta.8 - 2021-12-11
- No significant changes since `0.1.0-beta.7`. * No significant changes since `0.1.0-beta.7`.
## 0.1.0-beta.7 - 2021-11-22 ## 0.1.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
## 0.1.0-beta.6 - 2021-11-15 ## 0.1.0-beta.6 - 2021-11-15
- No significant changes from `0.1.0-beta.5`. * No significant changes from `0.1.0-beta.5`.
## 0.1.0-beta.5 - 2021-10-20 ## 0.1.0-beta.5 - 2021-10-20
- 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
## 0.1.0-beta.4 - 2021-09-09 ## 0.1.0-beta.4 - 2021-09-09
- Minimum supported Rust version (MSRV) is now 1.51. * Minimum supported Rust version (MSRV) is now 1.51.
## 0.1.0-beta.3 - 2021-06-20 ## 0.1.0-beta.3 - 2021-06-20
- No significant changes from `0.1.0-beta.2`. * No significant changes from `0.1.0-beta.2`.
## 0.1.0-beta.2 - 2021-04-17 ## 0.1.0-beta.2 - 2021-04-17
- No significant changes from `0.1.0-beta.1`. * No significant changes from `0.1.0-beta.1`.
## 0.1.0-beta.1 - 2021-04-02 ## 0.1.0-beta.1 - 2021-04-02
- Move integration testing structs from `actix-web`. [#2112] * Move integration testing structs from `actix-web`. [#2112]
[#2112]: https://github.com/actix/actix-web/pull/2112 [#2112]: https://github.com/actix/actix-web/pull/2112

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-test" name = "actix-test"
version = "0.1.0-beta.11" version = "0.1.0-beta.8"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>", "Rob Ede <robjtede@icloud.com>",
@ -29,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
[dependencies] [dependencies]
actix-codec = "0.4.1" actix-codec = "0.4.1"
actix-http = "3.0.0-beta.18" actix-http = "3.0.0-beta.15"
actix-http-test = "3.0.0-beta.11" actix-http-test = "3.0.0-beta.9"
actix-rt = "2.1" actix-rt = "2.1"
actix-service = "2.0.0" actix-service = "2.0.0"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.20", default-features = false, features = ["cookies"] } actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["cookies"] }
awc = { version = "3.0.0-beta.18", default-features = false, features = ["cookies"] } awc = { version = "3.0.0-beta.13", default-features = false, features = ["cookies"] }
futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
futures-util = { version = "0.3.7", default-features = false, features = [] } futures-util = { version = "0.3.7", default-features = false, features = [] }
@ -45,4 +45,4 @@ serde_json = "1"
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
tls-rustls = { package = "rustls", version = "0.20.0", optional = true } tls-rustls = { package = "rustls", version = "0.20.0", optional = true }
tokio = { version = "1.8.4", features = ["sync"] } tokio = { version = "1.2", features = ["sync"] }

View File

@ -37,14 +37,9 @@ extern crate tls_rustls as rustls;
use std::{fmt, net, thread, time::Duration}; use std::{fmt, net, thread, time::Duration};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
pub use actix_http::{body::to_bytes, test::TestBuffer}; pub use actix_http::test::TestBuffer;
use actix_http::{header::HeaderMap, ws, HttpService, Method, Request, Response}; use actix_http::{header::HeaderMap, ws, HttpService, Method, Request, Response};
pub use actix_http_test::unused_addr;
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _}; use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
pub use actix_web::test::{
call_and_read_body, call_and_read_body_json, call_service, init_service, ok_service,
read_body, read_body_json, simple_service, TestRequest,
};
use actix_web::{ use actix_web::{
body::MessageBody, body::MessageBody,
dev::{AppConfig, Server, ServerHandle, Service}, dev::{AppConfig, Server, ServerHandle, Service},
@ -53,6 +48,12 @@ use actix_web::{
}; };
use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector}; use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
use futures_core::Stream; use futures_core::Stream;
pub use actix_http_test::unused_addr;
pub use actix_web::test::{
call_service, default_service, init_service, load_stream, ok_service, read_body,
read_body_json, read_response, read_response_json, TestRequest,
};
use tokio::sync::mpsc; use tokio::sync::mpsc;
/// Start default [`TestServer`]. /// Start default [`TestServer`].
@ -340,7 +341,7 @@ where
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))
.openssl(builder.build()) .ssl(builder.build())
} }
#[cfg(not(feature = "openssl"))] #[cfg(not(feature = "openssl"))]
{ {

View File

@ -3,114 +3,106 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 4.0.0-beta.10 - 2022-01-04
- Minimum supported Rust version (MSRV) is now 1.54.
## 4.0.0-beta.9 - 2021-12-27
- No significant changes since `4.0.0-beta.8`.
## 4.0.0-beta.8 - 2021-12-11 ## 4.0.0-beta.8 - 2021-12-11
- Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] * Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920]
- Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] * Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920]
- Minimum supported Rust version (MSRV) is now 1.52. * Minimum supported Rust version (MSRV) is now 1.52.
[#1920]: https://github.com/actix/actix-web/pull/1920 [#1920]: https://github.com/actix/actix-web/pull/1920
## 4.0.0-beta.7 - 2021-09-09 ## 4.0.0-beta.7 - 2021-09-09
- Minimum supported Rust version (MSRV) is now 1.51. * Minimum supported Rust version (MSRV) is now 1.51.
## 4.0.0-beta.6 - 2021-06-26 ## 4.0.0-beta.6 - 2021-06-26
- Update `actix` to `0.12`. [#2277] * Update `actix` to `0.12`. [#2277]
[#2277]: https://github.com/actix/actix-web/pull/2277 [#2277]: https://github.com/actix/actix-web/pull/2277
## 4.0.0-beta.5 - 2021-06-17 ## 4.0.0-beta.5 - 2021-06-17
- No notable changes. * No notable changes.
## 4.0.0-beta.4 - 2021-04-02 ## 4.0.0-beta.4 - 2021-04-02
- No notable changes. * No notable changes.
## 4.0.0-beta.3 - 2021-03-09 ## 4.0.0-beta.3 - 2021-03-09
- No notable changes. * No notable changes.
## 4.0.0-beta.2 - 2021-02-10 ## 4.0.0-beta.2 - 2021-02-10
- No notable changes. * No notable changes.
## 4.0.0-beta.1 - 2021-01-07 ## 4.0.0-beta.1 - 2021-01-07
- Update `pin-project` to `1.0`. * Update `pin-project` to `1.0`.
- Update `bytes` to `1.0`. [#1813] * Update `bytes` to `1.0`. [#1813]
- `WebsocketContext::text` now takes an `Into<bytestring::ByteString>`. [#1864] * `WebsocketContext::text` now takes an `Into<bytestring::ByteString>`. [#1864]
[#1813]: https://github.com/actix/actix-web/pull/1813 [#1813]: https://github.com/actix/actix-web/pull/1813
[#1864]: https://github.com/actix/actix-web/pull/1864 [#1864]: https://github.com/actix/actix-web/pull/1864
## 3.0.0 - 2020-09-11 ## 3.0.0 - 2020-09-11
- No significant changes from `3.0.0-beta.2`. * No significant changes from `3.0.0-beta.2`.
## 3.0.0-beta.2 - 2020-09-10 ## 3.0.0-beta.2 - 2020-09-10
- Update `actix-*` dependencies to latest versions. * Update `actix-*` dependencies to latest versions.
## [3.0.0-beta.1] - 2020-xx-xx ## [3.0.0-beta.1] - 2020-xx-xx
- Update `actix-web` & `actix-http` dependencies to beta.1 * Update `actix-web` & `actix-http` dependencies to beta.1
- Bump minimum supported Rust version to 1.40 * Bump minimum supported Rust version to 1.40
## [3.0.0-alpha.1] - 2020-05-08 ## [3.0.0-alpha.1] - 2020-05-08
- Update the actix-web dependency to 3.0.0-alpha.1 * Update the actix-web dependency to 3.0.0-alpha.1
- Update the actix dependency to 0.10.0-alpha.2 * Update the actix dependency to 0.10.0-alpha.2
- Update the actix-http dependency to 2.0.0-alpha.3 * Update the actix-http dependency to 2.0.0-alpha.3
## [2.0.0] - 2019-12-20 ## [2.0.0] - 2019-12-20
- Release * Release
## [2.0.0-alpha.1] - 2019-12-15 ## [2.0.0-alpha.1] - 2019-12-15
- Migrate to actix-web 2.0.0 * Migrate to actix-web 2.0.0
## [1.0.4] - 2019-12-07 ## [1.0.4] - 2019-12-07
- Allow comma-separated websocket subprotocols without spaces (#1172) * Allow comma-separated websocket subprotocols without spaces (#1172)
## [1.0.3] - 2019-11-14 ## [1.0.3] - 2019-11-14
- Update actix-web and actix-http dependencies * Update actix-web and actix-http dependencies
## [1.0.2] - 2019-07-20 ## [1.0.2] - 2019-07-20
- Add `ws::start_with_addr()`, returning the address of the created actor, along * Add `ws::start_with_addr()`, returning the address of the created actor, along
with the `HttpResponse`. with the `HttpResponse`.
- Add support for specifying protocols on websocket handshake #835 * Add support for specifying protocols on websocket handshake #835
## [1.0.1] - 2019-06-28 ## [1.0.1] - 2019-06-28
- Allow to use custom ws codec with `WebsocketContext` #925 * Allow to use custom ws codec with `WebsocketContext` #925
## [1.0.0] - 2019-05-29 ## [1.0.0] - 2019-05-29
- Update actix-http and actix-web * Update actix-http and actix-web
## [0.1.0-alpha.3] - 2019-04-02 ## [0.1.0-alpha.3] - 2019-04-02
- Update actix-http and actix-web * Update actix-http and actix-web
## [0.1.0-alpha.2] - 2019-03-29 ## [0.1.0-alpha.2] - 2019-03-29
- Update actix-http and actix-web * Update actix-http and actix-web
## [0.1.0-alpha.1] - 2019-03-28 ## [0.1.0-alpha.1] - 2019-03-28
- Initial impl * Initial impl

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-actors" name = "actix-web-actors"
version = "4.0.0-beta.10" version = "4.0.0-beta.8"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for Actix Web" description = "Actix actors support for Actix Web"
keywords = ["actix", "http", "web", "framework", "async"] keywords = ["actix", "http", "web", "framework", "async"]
@ -16,19 +16,19 @@ path = "src/lib.rs"
[dependencies] [dependencies]
actix = { version = "0.12.0", default-features = false } actix = { version = "0.12.0", default-features = false }
actix-codec = "0.4.1" actix-codec = "0.4.1"
actix-http = "3.0.0-beta.18" actix-http = "3.0.0-beta.15"
actix-web = { version = "4.0.0-beta.20", default-features = false } actix-web = { version = "4.0.0-beta.14", default-features = false }
bytes = "1" bytes = "1"
bytestring = "1" bytestring = "1"
futures-core = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.7", default-features = false }
pin-project-lite = "0.2" pin-project-lite = "0.2"
tokio = { version = "1.8.4", features = ["sync"] } tokio = { version = "1", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
actix-rt = "2.2" actix-rt = "2.2"
actix-test = "0.1.0-beta.11" actix-test = "0.1.0-beta.8"
awc = { version = "3.0.0-beta.18", default-features = false } awc = { version = "3.0.0-beta.13", default-features = false }
env_logger = "0.9" env_logger = "0.9"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }

View File

@ -3,15 +3,15 @@
> Actix actors support for Actix Web. > Actix actors support for Actix Web.
[![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.10)](https://docs.rs/actix-web-actors/4.0.0-beta.10) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.8)](https://docs.rs/actix-web-actors/4.0.0-beta.8)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![License](https://img.shields.io/crates/l/actix-web-actors.svg) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.10/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.10) [![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.8)
[![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-web-actors) - [API Documentation](https://docs.rs/actix-web-actors)
- Minimum Supported Rust Version (MSRV): 1.54 - Minimum Supported Rust Version (MSRV): 1.52

View File

@ -3,106 +3,102 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 0.5.0-rc.1 - 2022-01-04
- Minimum supported Rust version (MSRV) is now 1.54.
## 0.5.0-beta.6 - 2021-12-11 ## 0.5.0-beta.6 - 2021-12-11
- No significant changes since `0.5.0-beta.5`. * No significant changes since `0.5.0-beta.5`.
## 0.5.0-beta.5 - 2021-10-20 ## 0.5.0-beta.5 - 2021-10-20
- Improve error recovery potential when macro input is invalid. [#2410] * Improve error recovery potential when macro input is invalid. [#2410]
- Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] * Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409]
- Minimum supported Rust version (MSRV) is now 1.52. * Minimum supported Rust version (MSRV) is now 1.52.
[#2410]: https://github.com/actix/actix-web/pull/2410 [#2410]: https://github.com/actix/actix-web/pull/2410
[#2409]: https://github.com/actix/actix-web/pull/2409 [#2409]: https://github.com/actix/actix-web/pull/2409
## 0.5.0-beta.4 - 2021-09-09 ## 0.5.0-beta.4 - 2021-09-09
- In routing macros, paths are now validated at compile time. [#2350] * In routing macros, paths are now validated at compile time. [#2350]
- Minimum supported Rust version (MSRV) is now 1.51. * Minimum supported Rust version (MSRV) is now 1.51.
[#2350]: https://github.com/actix/actix-web/pull/2350 [#2350]: https://github.com/actix/actix-web/pull/2350
## 0.5.0-beta.3 - 2021-06-17 ## 0.5.0-beta.3 - 2021-06-17
- No notable changes. * No notable changes.
## 0.5.0-beta.2 - 2021-03-09 ## 0.5.0-beta.2 - 2021-03-09
- Preserve doc comments when using route macros. [#2022] * Preserve doc comments when using route macros. [#2022]
- Add `name` attribute to `route` macro. [#1934] * Add `name` attribute to `route` macro. [#1934]
[#2022]: https://github.com/actix/actix-web/pull/2022 [#2022]: https://github.com/actix/actix-web/pull/2022
[#1934]: https://github.com/actix/actix-web/pull/1934 [#1934]: https://github.com/actix/actix-web/pull/1934
## 0.5.0-beta.1 - 2021-02-10 ## 0.5.0-beta.1 - 2021-02-10
- Use new call signature for `System::new`. * Use new call signature for `System::new`.
## 0.4.0 - 2020-09-20 ## 0.4.0 - 2020-09-20
- Added compile success and failure testing. [#1677] * Added compile success and failure testing. [#1677]
- Add `route` macro for supporting multiple HTTP methods guards. [#1674] * Add `route` macro for supporting multiple HTTP methods guards. [#1674]
[#1677]: https://github.com/actix/actix-web/pull/1677 [#1677]: https://github.com/actix/actix-web/pull/1677
[#1674]: https://github.com/actix/actix-web/pull/1674 [#1674]: https://github.com/actix/actix-web/pull/1674
## 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-14 ## 0.3.0-beta.1 - 2020-07-14
- Add main entry-point macro that uses re-exported runtime. [#1559] * Add main entry-point macro that uses re-exported runtime. [#1559]
[#1559]: https://github.com/actix/actix-web/pull/1559 [#1559]: https://github.com/actix/actix-web/pull/1559
## 0.2.2 - 2020-05-23 ## 0.2.2 - 2020-05-23
- Add resource middleware on actix-web-codegen [#1467] * Add resource middleware on actix-web-codegen [#1467]
[#1467]: https://github.com/actix/actix-web/pull/1467 [#1467]: https://github.com/actix/actix-web/pull/1467
## 0.2.1 - 2020-02-25 ## 0.2.1 - 2020-02-25
- Add `#[allow(missing_docs)]` attribute to generated structs [#1368] * Add `#[allow(missing_docs)]` attribute to generated structs [#1368]
- Allow the handler function to be named as `config` [#1290] * Allow the handler function to be named as `config` [#1290]
[#1368]: https://github.com/actix/actix-web/issues/1368 [#1368]: https://github.com/actix/actix-web/issues/1368
[#1290]: https://github.com/actix/actix-web/issues/1290 [#1290]: https://github.com/actix/actix-web/issues/1290
## 0.2.0 - 2019-12-13 ## 0.2.0 - 2019-12-13
- Generate code for actix-web 2.0 * Generate code for actix-web 2.0
## 0.1.3 - 2019-10-14 ## 0.1.3 - 2019-10-14
- Bump up `syn` & `quote` to 1.0 * Bump up `syn` & `quote` to 1.0
- Provide better error message * Provide better error message
## 0.1.2 - 2019-06-04 ## 0.1.2 - 2019-06-04
- Add macros for head, options, trace, connect and patch http methods * Add macros for head, options, trace, connect and patch http methods
## 0.1.1 - 2019-06-01 ## 0.1.1 - 2019-06-01
- Add syn "extra-traits" feature * Add syn "extra-traits" feature
## 0.1.0 - 2019-05-18 ## 0.1.0 - 2019-05-18
- Release * Release
## 0.1.0-beta.1 - 2019-04-20 ## 0.1.0-beta.1 - 2019-04-20
- Gen code for actix-web 1.0.0-beta.1 * Gen code for actix-web 1.0.0-beta.1
## 0.1.0-alpha.6 - 2019-04-14 ## 0.1.0-alpha.6 - 2019-04-14
- Gen code for actix-web 1.0.0-alpha.6 * Gen code for actix-web 1.0.0-alpha.6
## 0.1.0-alpha.1 - 2019-03-28 ## 0.1.0-alpha.1 - 2019-03-28
- Initial impl * Initial impl

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-codegen" name = "actix-web-codegen"
version = "0.5.0-rc.1" version = "0.5.0-beta.6"
description = "Routing and runtime macros for Actix Web" description = "Routing and runtime macros for Actix Web"
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
@ -15,17 +15,17 @@ edition = "2018"
proc-macro = true proc-macro = true
[dependencies] [dependencies]
actix-router = "0.5.0-beta.4"
proc-macro2 = "1"
quote = "1" quote = "1"
syn = { version = "1", features = ["full", "parsing"] } syn = { version = "1", features = ["full", "parsing"] }
proc-macro2 = "1"
actix-router = "0.5.0-beta.2"
[dev-dependencies] [dev-dependencies]
actix-macros = "0.2.3" actix-macros = "0.2.3"
actix-rt = "2.2" actix-rt = "2.2"
actix-test = "0.1.0-beta.11" actix-test = "0.1.0-beta.8"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-web = "4.0.0-beta.20" actix-web = "4.0.0-beta.14"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
trybuild = "1" trybuild = "1"

View File

@ -3,18 +3,18 @@
> Routing and runtime macros for Actix Web. > Routing and runtime macros for Actix Web.
[![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen)
[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-rc.1)](https://docs.rs/actix-web-codegen/0.5.0-rc.1) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.6)](https://docs.rs/actix-web-codegen/0.5.0-beta.6)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) [![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![License](https://img.shields.io/crates/l/actix-web-codegen.svg) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-rc.1/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-rc.1) [![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.6/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.6)
[![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-web-codegen) - [API Documentation](https://docs.rs/actix-web-codegen)
- Minimum Supported Rust Version (MSRV): 1.54 - Minimum Supported Rust Version (MSRV): 1.52
## Compile Testing ## Compile Testing

View File

@ -1,4 +1,4 @@
#[rustversion::stable(1.54)] // MSRV #[rustversion::stable(1.52)] // MSRV
#[test] #[test]
fn compile_macros() { fn compile_macros() {
let t = trybuild::TestCases::new(); let t = trybuild::TestCases::new();

View File

@ -1,13 +1,13 @@
error: The #[route(..)] macro requires at least one `method` attribute error: The #[route(..)] macro requires at least one `method` attribute
--> tests/trybuild/route-missing-method-fail.rs:3:1 --> $DIR/route-missing-method-fail.rs:3:1
| |
3 | #[route("/")] 3 | #[route("/")]
| ^^^^^^^^^^^^^ | ^^^^^^^^^^^^^
| |
= note: this error originates in the attribute macro `route` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied
--> tests/trybuild/route-missing-method-fail.rs:12:55 --> $DIR/route-missing-method-fail.rs:12:55
| |
12 | let srv = actix_test::start(|| App::new().service(index)); 12 | let srv = actix_test::start(|| App::new().service(index));
| ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}`

View File

@ -1,107 +1,68 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510]
## 3.0.0-beta.18 - 2022-01-04
- Minimum supported Rust version (MSRV) is now 1.54.
## 3.0.0-beta.17 - 2021-12-29
### Changed
- Update `cookie` dependency (re-exported) to `0.16`. [#2555]
### Security
- `cookie` upgrade addresses [`RUSTSEC-2020-0071`].
[#2555]: https://github.com/actix/actix-web/pull/2555
[`RUSTSEC-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html
## 3.0.0-beta.16 - 2021-12-29
- `*::send_json` and `*::send_form` methods now receive `impl Serialize`. [#2553]
- `FrozenClientRequest::extra_header` now uses receives an `impl TryIntoHeaderPair`. [#2553]
- Remove unnecessary `Unpin` bounds on `*::send_stream`. [#2553]
[#2553]: https://github.com/actix/actix-web/pull/2553
## 3.0.0-beta.15 - 2021-12-27
- Rename `Connector::{ssl => openssl}`. [#2503]
- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503]
- `ClientRequest::send_body` now takes an `impl MessageBody`. [#2546]
- Rename `MessageBody => ResponseBody` to avoid conflicts with `MessageBody` trait. [#2546]
- `impl Future` for `ResponseBody` no longer requires the body type be `Unpin`. [#2546]
- `impl Future` for `JsonBody` no longer requires the body type be `Unpin`. [#2546]
- `impl Stream` for `ClientResponse` no longer requires the body type be `Unpin`. [#2546]
[#2503]: https://github.com/actix/actix-web/pull/2503
[#2546]: https://github.com/actix/actix-web/pull/2546
## 3.0.0-beta.14 - 2021-12-17
- Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510]
[#2510]: https://github.com/actix/actix-web/pull/2510 [#2510]: https://github.com/actix/actix-web/pull/2510
## 3.0.0-beta.13 - 2021-12-11 ## 3.0.0-beta.13 - 2021-12-11
- No significant changes since `3.0.0-beta.12`. * No significant changes since `3.0.0-beta.12`.
## 3.0.0-beta.12 - 2021-11-30 ## 3.0.0-beta.12 - 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.11 - 2021-11-22 ## 3.0.0-beta.11 - 2021-11-22
- No significant changes from `3.0.0-beta.10`. * No significant changes from `3.0.0-beta.10`.
## 3.0.0-beta.10 - 2021-11-15 ## 3.0.0-beta.10 - 2021-11-15
- No significant changes from `3.0.0-beta.9`. * No significant changes from `3.0.0-beta.9`.
## 3.0.0-beta.9 - 2021-10-20 ## 3.0.0-beta.9 - 2021-10-20
- Updated rustls to v0.20. [#2414] * Updated rustls to v0.20. [#2414]
[#2414]: https://github.com/actix/actix-web/pull/2414 [#2414]: https://github.com/actix/actix-web/pull/2414
## 3.0.0-beta.8 - 2021-09-09 ## 3.0.0-beta.8 - 2021-09-09
### Changed ### Changed
- Send headers within the redirect requests. [#2310] * Send headers within the redirect requests. [#2310]
[#2310]: https://github.com/actix/actix-web/pull/2310 [#2310]: https://github.com/actix/actix-web/pull/2310
## 3.0.0-beta.7 - 2021-06-26 ## 3.0.0-beta.7 - 2021-06-26
### Changed ### Changed
- Change compression algorithm features flags. [#2250] * Change compression algorithm features flags. [#2250]
[#2250]: https://github.com/actix/actix-web/pull/2250 [#2250]: https://github.com/actix/actix-web/pull/2250
## 3.0.0-beta.6 - 2021-06-17 ## 3.0.0-beta.6 - 2021-06-17
- No significant changes since 3.0.0-beta.5. * No significant changes since 3.0.0-beta.5.
## 3.0.0-beta.5 - 2021-04-17 ## 3.0.0-beta.5 - 2021-04-17
### Removed ### Removed
- Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] * Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148]
[#2148]: https://github.com/actix/actix-web/pull/2148 [#2148]: https://github.com/actix/actix-web/pull/2148
## 3.0.0-beta.4 - 2021-04-02 ## 3.0.0-beta.4 - 2021-04-02
### Added ### Added
- Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114] * Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114]
### Changed ### Changed
- `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] * `ConnectorService` type is renamed to `BoxConnectorService`. [#2081]
- Fix http/https encoding when enabling `compress` feature. [#2116] * Fix http/https encoding when enabling `compress` feature. [#2116]
- Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header * Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header
methods now take `TryIntoHeaderPair` tuples. [#2094] methods now take `TryIntoHeaderPair` tuples. [#2094]
[#2081]: https://github.com/actix/actix-web/pull/2081 [#2081]: https://github.com/actix/actix-web/pull/2081
@ -112,16 +73,16 @@
## 3.0.0-beta.3 - 2021-03-08 ## 3.0.0-beta.3 - 2021-03-08
### Added ### Added
- `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] * `ClientResponse::timeout` for set the timeout of collecting response body. [#1931]
- `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] * `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024]
### Changed ### Changed
- Feature `cookies` is now optional and enabled by default. [#1981] * Feature `cookies` is now optional and enabled by default. [#1981]
- `ClientBuilder::connector` method would take `actix_http::client::Connector<T, U>` type. [#2008] * `ClientBuilder::connector` method would take `actix_http::client::Connector<T, U>` type. [#2008]
- Basic auth password now takes blank passwords as an empty string instead of Option. [#2050] * Basic auth password now takes blank passwords as an empty string instead of Option. [#2050]
### Removed ### Removed
- `ClientBuilder::default` function [#2008] * `ClientBuilder::default` function [#2008]
[#1931]: https://github.com/actix/actix-web/pull/1931 [#1931]: https://github.com/actix/actix-web/pull/1931
[#1981]: https://github.com/actix/actix-web/pull/1981 [#1981]: https://github.com/actix/actix-web/pull/1981
@ -132,18 +93,18 @@
## 3.0.0-beta.2 - 2021-02-10 ## 3.0.0-beta.2 - 2021-02-10
### Added ### Added
- `ClientRequest::insert_header` method which allows using typed headers. [#1869] * `ClientRequest::insert_header` method which allows using typed headers. [#1869]
- `ClientRequest::append_header` method which allows using typed headers. [#1869] * `ClientRequest::append_header` method which allows using typed headers. [#1869]
- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] * `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
### Changed ### Changed
- Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] * Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905]
### Removed ### Removed
- `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869] * `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869]
- `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869] * `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869]
- `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869] * `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869]
- `ClientRequest::header`; use `ClientRequest::append_header`. [#1869] * `ClientRequest::header`; use `ClientRequest::append_header`. [#1869]
[#1869]: https://github.com/actix/actix-web/pull/1869 [#1869]: https://github.com/actix/actix-web/pull/1869
[#1905]: https://github.com/actix/actix-web/pull/1905 [#1905]: https://github.com/actix/actix-web/pull/1905
@ -152,32 +113,32 @@
## 3.0.0-beta.1 - 2021-01-07 ## 3.0.0-beta.1 - 2021-01-07
### Changed ### Changed
- Update `rand` to `0.8` * Update `rand` to `0.8`
- Update `bytes` to `1.0`. [#1813] * Update `bytes` to `1.0`. [#1813]
- Update `rust-tls` to `0.19`. [#1813] * Update `rust-tls` to `0.19`. [#1813]
[#1813]: https://github.com/actix/actix-web/pull/1813 [#1813]: https://github.com/actix/actix-web/pull/1813
## 2.0.3 - 2020-11-29 ## 2.0.3 - 2020-11-29
### Fixed ### Fixed
- Ensure `actix-http` dependency uses same `serde_urlencoded`. * Ensure `actix-http` dependency uses same `serde_urlencoded`.
## 2.0.2 - 2020-11-25 ## 2.0.2 - 2020-11-25
### 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
## 2.0.1 - 2020-10-30 ## 2.0.1 - 2020-10-30
### Changed ### Changed
- Upgrade `base64` to `0.13`. [#1744] * Upgrade `base64` to `0.13`. [#1744]
- Deprecate `ClientRequest::{if_some, if_true}`. [#1760] * Deprecate `ClientRequest::{if_some, if_true}`. [#1760]
### Fixed ### Fixed
- Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature * Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature
is enabled [#1737] is enabled [#1737]
[#1737]: https://github.com/actix/actix-web/pull/1737 [#1737]: https://github.com/actix/actix-web/pull/1737
@ -187,209 +148,209 @@
## 2.0.0 - 2020-09-11 ## 2.0.0 - 2020-09-11
### Changed ### Changed
- `Client::build` was renamed to `Client::builder`. * `Client::build` was renamed to `Client::builder`.
## 2.0.0-beta.4 - 2020-09-09 ## 2.0.0-beta.4 - 2020-09-09
### Changed ### Changed
- Update actix-codec & actix-tls dependencies. * Update actix-codec & actix-tls dependencies.
## 2.0.0-beta.3 - 2020-08-17 ## 2.0.0-beta.3 - 2020-08-17
### Changed ### Changed
- Update `rustls` to 0.18 * Update `rustls` to 0.18
## 2.0.0-beta.2 - 2020-07-21 ## 2.0.0-beta.2 - 2020-07-21
### Changed ### Changed
- Update `actix-http` dependency to 2.0.0-beta.2 * Update `actix-http` dependency to 2.0.0-beta.2
## [2.0.0-beta.1] - 2020-07-14 ## [2.0.0-beta.1] - 2020-07-14
### Changed ### Changed
- Update `actix-http` dependency to 2.0.0-beta.1 * Update `actix-http` dependency to 2.0.0-beta.1
## [2.0.0-alpha.2] - 2020-05-21 ## [2.0.0-alpha.2] - 2020-05-21
### Changed ### Changed
- Implement `std::error::Error` for our custom errors [#1422] * Implement `std::error::Error` for our custom errors [#1422]
- Bump minimum supported Rust version to 1.40 * Bump minimum supported Rust version to 1.40
- Update `base64` dependency to 0.12 * Update `base64` dependency to 0.12
[#1422]: https://github.com/actix/actix-web/pull/1422 [#1422]: https://github.com/actix/actix-web/pull/1422
## [2.0.0-alpha.1] - 2020-03-11 ## [2.0.0-alpha.1] - 2020-03-11
- Update `actix-http` dependency to 2.0.0-alpha.2 * Update `actix-http` dependency to 2.0.0-alpha.2
- Update `rustls` dependency to 0.17 * Update `rustls` dependency to 0.17
- ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration * ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration
- ClientBuilder allowing to set max_http_version to limit HTTP version to be used * ClientBuilder allowing to set max_http_version to limit HTTP version to be used
## [1.0.1] - 2019-12-15 ## [1.0.1] - 2019-12-15
- Fix compilation with default features off * Fix compilation with default features off
## [1.0.0] - 2019-12-13 ## [1.0.0] - 2019-12-13
- Release * Release
## [1.0.0-alpha.3] ## [1.0.0-alpha.3]
- Migrate to `std::future` * Migrate to `std::future`
## [0.2.8] - 2019-11-06 ## [0.2.8] - 2019-11-06
- Add support for setting query from Serialize type for client request. * Add support for setting query from Serialize type for client request.
## [0.2.7] - 2019-09-25 ## [0.2.7] - 2019-09-25
### Added ### Added
- Remaining getter methods for `ClientRequest`'s private `head` field #1101 * Remaining getter methods for `ClientRequest`'s private `head` field #1101
## [0.2.6] - 2019-09-12 ## [0.2.6] - 2019-09-12
### Added ### Added
- Export frozen request related types. * Export frozen request related types.
## [0.2.5] - 2019-09-11 ## [0.2.5] - 2019-09-11
### Added ### Added
- Add `FrozenClientRequest` to support retries for sending HTTP requests * Add `FrozenClientRequest` to support retries for sending HTTP requests
### Changed ### Changed
- Ensure that the `Host` header is set when initiating a WebSocket client connection. * Ensure that the `Host` header is set when initiating a WebSocket client connection.
## [0.2.4] - 2019-08-13 ## [0.2.4] - 2019-08-13
### Changed ### Changed
- 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"
## [0.2.3] - 2019-08-01 ## [0.2.3] - 2019-08-01
### Added ### Added
- Add `rustls` support * Add `rustls` support
## [0.2.2] - 2019-07-01 ## [0.2.2] - 2019-07-01
### Changed ### Changed
- Always append a colon after username in basic auth * Always append a colon after username in basic auth
- Upgrade `rand` dependency version to 0.7 * Upgrade `rand` dependency version to 0.7
## [0.2.1] - 2019-06-05 ## [0.2.1] - 2019-06-05
### Added ### Added
- Add license files * Add license files
## [0.2.0] - 2019-05-12 ## [0.2.0] - 2019-05-12
### Added ### Added
- Allow to send headers in `Camel-Case` form. * Allow to send headers in `Camel-Case` form.
### Changed ### Changed
- Upgrade actix-http dependency. * Upgrade actix-http dependency.
## [0.1.1] - 2019-04-19 ## [0.1.1] - 2019-04-19
### Added ### Added
- Allow to specify server address for http and ws requests. * Allow to specify server address for http and ws requests.
### Changed ### Changed
- `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref * `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref
## [0.1.0] - 2019-04-16 ## [0.1.0] - 2019-04-16
- No changes * No changes
## [0.1.0-alpha.6] - 2019-04-14 ## [0.1.0-alpha.6] - 2019-04-14
### Changed ### Changed
- Do not set default headers for websocket request * Do not set default headers for websocket request
## [0.1.0-alpha.5] - 2019-04-12 ## [0.1.0-alpha.5] - 2019-04-12
### Changed ### Changed
- Do not set any default headers * Do not set any default headers
### Added ### Added
- Add Debug impl for BoxedSocket * Add Debug impl for BoxedSocket
## [0.1.0-alpha.4] - 2019-04-08 ## [0.1.0-alpha.4] - 2019-04-08
### Changed ### Changed
- Update actix-http dependency * Update actix-http dependency
## [0.1.0-alpha.3] - 2019-04-02 ## [0.1.0-alpha.3] - 2019-04-02
### Added ### Added
- Export `MessageBody` type * Export `MessageBody` type
- `ClientResponse::json()` - Loads and parse `application/json` encoded body * `ClientResponse::json()` - Loads and parse `application/json` encoded body
### Changed ### Changed
- `ClientRequest::json()` accepts reference instead of object. * `ClientRequest::json()` accepts reference instead of object.
- `ClientResponse::body()` does not consume response object. * `ClientResponse::body()` does not consume response object.
- Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` * Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()`
## [0.1.0-alpha.2] - 2019-03-29 ## [0.1.0-alpha.2] - 2019-03-29
### Added ### Added
- Per request and session wide request timeout. * Per request and session wide request timeout.
- Session wide headers. * Session wide headers.
- Session wide basic and bearer auth. * Session wide basic and bearer auth.
- Re-export `actix_http::client::Connector`. * Re-export `actix_http::client::Connector`.
### Changed ### Changed
- Allow to override request's uri * Allow to override request's uri
- Export `ws` sub-module with websockets related types * Export `ws` sub-module with websockets related types
## [0.1.0-alpha.1] - 2019-03-28 ## [0.1.0-alpha.1] - 2019-03-28
- Initial impl * Initial impl

View File

@ -1,6 +1,6 @@
[package] [package]
name = "awc" name = "awc"
version = "3.0.0-beta.18" version = "3.0.0-beta.13"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>", "fakeshadow <24548779@qq.com>",
@ -60,9 +60,9 @@ dangerous-h2c = []
[dependencies] [dependencies]
actix-codec = "0.4.1" actix-codec = "0.4.1"
actix-service = "2.0.0" actix-service = "2.0.0"
actix-http = "3.0.0-beta.18" actix-http = "3.0.0-beta.15"
actix-rt = { version = "2.1", default-features = false } actix-rt = { version = "2.1", default-features = false }
actix-tls = { version = "3.0.0", features = ["connect", "uri"] } actix-tls = { version = "3.0.0-rc.1", features = ["connect", "uri"] }
actix-utils = "3.0.0" actix-utils = "3.0.0"
ahash = "0.7" ahash = "0.7"
@ -70,11 +70,11 @@ base64 = "0.13"
bytes = "1" bytes = "1"
cfg-if = "1" cfg-if = "1"
derive_more = "0.99.5" 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 }
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } futures-util = { version = "0.3.7", default-features = false }
h2 = "0.3.9" h2 = "0.3.9"
http = "0.2.5" http = "0.2.5"
itoa = "1" itoa = "0.4"
log =" 0.4" log =" 0.4"
mime = "0.3" mime = "0.3"
percent-encoding = "2.1" percent-encoding = "2.1"
@ -83,9 +83,9 @@ rand = "0.8"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
tokio = { version = "1.8.4", features = ["sync"] } tokio = { version = "1", features = ["sync"] }
cookie = { version = "0.16", features = ["percent-encode"], optional = true } cookie = { version = "0.15", features = ["percent-encode"], optional = true }
tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] } tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] }
@ -93,23 +93,21 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features
trust-dns-resolver = { version = "0.20.0", optional = true } trust-dns-resolver = { version = "0.20.0", optional = true }
[dev-dependencies] [dev-dependencies]
actix-http = { version = "3.0.0-beta.18", features = ["openssl"] } actix-http = { version = "3.0.0-beta.15", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] }
actix-server = "2.0.0-rc.2" actix-server = "2.0.0-rc.1"
actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.8", features = ["openssl", "rustls"] }
actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] }
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.20", features = ["openssl"] } actix-web = { version = "4.0.0-beta.14", features = ["openssl"] }
brotli2 = "0.3.2" brotli2 = "0.3.2"
const-str = "0.3"
env_logger = "0.9" env_logger = "0.9"
flate2 = "1.0.13" flate2 = "1.0.13"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
static_assertions = "1.1" static_assertions = "1.1"
rcgen = "0.8" rcgen = "0.8"
rustls-pemfile = "0.2" rustls-pemfile = "0.2"
zstd = "0.9"
[[example]] [[example]]
name = "client" name = "client"

View File

@ -3,16 +3,16 @@
> Async HTTP and WebSocket client library. > Async HTTP and WebSocket client library.
[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc)
[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.18)](https://docs.rs/awc/3.0.0-beta.18) [![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.13)](https://docs.rs/awc/3.0.0-beta.13)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc)
[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.18/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.18) [![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.13/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.13)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/awc) - [API Documentation](https://docs.rs/awc)
- [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https) - [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https)
- Minimum Supported Rust Version (MSRV): 1.54 - Minimum Supported Rust Version (MSRV): 1.52
## Example ## Example

View File

@ -1,13 +1,17 @@
use std::{ use std::{
borrow::Cow,
fmt, mem, fmt, mem,
pin::Pin, pin::Pin,
task::{Context, Poll}, task::{Context, Poll},
}; };
use bytes::Bytes; use bytes::{Bytes, BytesMut};
use futures_core::Stream;
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use actix_http::body::{BodySize, BoxBody, MessageBody}; use actix_http::body::{BodySize, BodyStream, BoxBody, MessageBody, SizedStream};
use crate::BoxError;
pin_project! { pin_project! {
/// Represents various types of HTTP message body. /// Represents various types of HTTP message body.
@ -41,7 +45,9 @@ impl AnyBody {
where where
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
Self::Body { body: body.boxed() } Self::Body {
body: BoxBody::new(body),
}
} }
/// Constructs new `AnyBody` instance from a slice of bytes by copying it. /// Constructs new `AnyBody` instance from a slice of bytes by copying it.
@ -73,27 +79,10 @@ impl<B> AnyBody<B>
where where
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
/// Converts a [`MessageBody`] type into the best possible representation.
///
/// Checks size for `None` and tries to convert to `Bytes`. Otherwise, uses the `Body` variant.
pub fn from_message_body(body: B) -> Self
where
B: MessageBody,
{
if matches!(body.size(), BodySize::None) {
return Self::None;
}
match body.try_into_bytes() {
Ok(body) => Self::Bytes { body },
Err(body) => Self::new(body),
}
}
pub fn into_boxed(self) -> AnyBody { pub fn into_boxed(self) -> AnyBody {
match self { match self {
Self::None => AnyBody::None, Self::None => AnyBody::None,
Self::Bytes { body } => AnyBody::Bytes { body }, Self::Bytes { body: bytes } => AnyBody::Bytes { body: bytes },
Self::Body { body } => AnyBody::new_boxed(body), Self::Body { body } => AnyBody::new_boxed(body),
} }
} }
@ -156,6 +145,91 @@ impl<S: fmt::Debug> fmt::Debug for AnyBody<S> {
} }
} }
impl<B> From<&'static str> for AnyBody<B> {
fn from(string: &'static str) -> Self {
Self::Bytes {
body: Bytes::from_static(string.as_ref()),
}
}
}
impl<B> From<&'static [u8]> for AnyBody<B> {
fn from(bytes: &'static [u8]) -> Self {
Self::Bytes {
body: Bytes::from_static(bytes),
}
}
}
impl<B> From<Vec<u8>> for AnyBody<B> {
fn from(vec: Vec<u8>) -> Self {
Self::Bytes {
body: Bytes::from(vec),
}
}
}
impl<B> From<String> for AnyBody<B> {
fn from(string: String) -> Self {
Self::Bytes {
body: Bytes::from(string),
}
}
}
impl<B> From<&'_ String> for AnyBody<B> {
fn from(string: &String) -> Self {
Self::Bytes {
body: Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string)),
}
}
}
impl<B> From<Cow<'_, str>> for AnyBody<B> {
fn from(string: Cow<'_, str>) -> Self {
match string {
Cow::Owned(s) => Self::from(s),
Cow::Borrowed(s) => Self::Bytes {
body: Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s)),
},
}
}
}
impl<B> From<Bytes> for AnyBody<B> {
fn from(bytes: Bytes) -> Self {
Self::Bytes { body: bytes }
}
}
impl<B> From<BytesMut> for AnyBody<B> {
fn from(bytes: BytesMut) -> Self {
Self::Bytes {
body: bytes.freeze(),
}
}
}
impl<S, E> From<SizedStream<S>> for AnyBody
where
S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<BoxError> + 'static,
{
fn from(stream: SizedStream<S>) -> Self {
AnyBody::new_boxed(stream)
}
}
impl<S, E> From<BodyStream<S>> for AnyBody
where
S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<BoxError> + 'static,
{
fn from(stream: BodyStream<S>) -> Self {
AnyBody::new_boxed(stream)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::marker::PhantomPinned; use std::marker::PhantomPinned;

Some files were not shown because too many files have changed in this diff Show More