1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-04 09:56:22 +02:00

Compare commits

...

34 Commits

Author SHA1 Message Date
c09186a2c0 prepare v4 beta releases (#1881) 2021-01-07 20:02:08 +00:00
d3c476b8c2 use env_logger builders in examples 2021-01-07 02:41:05 +00:00
dc23559f23 address clippy lints 2021-01-07 02:04:26 +00:00
6d710629af fix bug where upgrade future is not reset properly (#1880) 2021-01-07 00:57:34 +00:00
85753130d9 fmt 2021-01-07 00:35:19 +00:00
00ba8d5549 add http3 variant to protocol enum 2021-01-06 18:58:24 +00:00
51e9e1500b add docs to recent additions 2021-01-06 18:52:06 +00:00
a03dbe2dcf replace cloneable service with httpflow abstraction (#1876) 2021-01-06 18:43:52 +00:00
57a3722146 More refactor of app_service (#1879) 2021-01-06 18:11:20 +00:00
57da1d3c0f refactor app_service (#1877) 2021-01-06 11:35:30 +00:00
68117543ea major cleanup of middleware module (#1875)
* major cleanup of middleware module

* update changelog
2021-01-05 09:51:58 +00:00
4f5971d79e add Compat middleware (#1865) 2021-01-05 00:22:57 +00:00
93161df141 clean up body type (#1872) 2021-01-04 23:47:38 +00:00
e567873326 optimize message pool release (#1871) 2021-01-04 13:03:46 +00:00
7d632d0b7b use ByteString as container for websocket text message (#1864) 2021-01-04 11:27:32 +00:00
36aee18c64 fmt 2021-01-04 04:33:15 +00:00
007a145988 use ahash for internal hashmaps 2021-01-04 04:29:07 +00:00
2d4a174420 fmt 2021-01-04 01:01:35 +00:00
21f6c9d7a5 improve code readability 2021-01-04 00:49:02 +00:00
e1683313ec optimize ServiceRequest methods (#1870)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-01-04 00:32:41 +00:00
32de9f8840 Tokio 1.0 (#1813)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-01-03 23:47:04 +00:00
1f202d40e4 optimize write_camel_case in h1 encoder (#1868) 2021-01-03 16:53:01 +00:00
ad608aa64e optimize Resource and Scope service call (#1867) 2021-01-02 19:40:31 +00:00
a1b00b2cd0 change unreleased year 2021-01-02 00:12:18 +00:00
3beb4cf2da replace tinyvec with smallvec (#1866) 2021-01-01 23:18:25 +00:00
522c9a5ea6 update CoC text 2020-12-31 03:24:18 +00:00
102bb8f9ab update dot dep graphs 2020-12-29 00:22:28 +00:00
20b46cdaf9 format factory_tuple macro invocations (#1859) 2020-12-28 21:04:02 +00:00
2a2a20c3e7 bump msrv to 1.46 (#1858) 2020-12-28 00:44:15 +00:00
093d3a6c59 remove deprecated on_connect methods (#1857) 2020-12-27 23:23:30 +00:00
8c9ea43e23 address clippy warnings 2020-12-27 20:54:04 +00:00
f9fcf56d5c reduce branch in actix_http::h1::codec (#1854) 2020-12-27 20:37:53 +00:00
cbda928a33 Rename factory to handler (#1852) 2020-12-26 21:46:19 +00:00
1032f04ded remove unused actix_http::h1::OneRequest (#1853) 2020-12-26 12:46:36 +00:00
129 changed files with 2439 additions and 2763 deletions

View File

@ -13,7 +13,7 @@ jobs:
fail-fast: false
matrix:
version:
- 1.42.0 # MSRV
- 1.46.0 # MSRV
- stable
- nightly
@ -30,6 +30,13 @@ jobs:
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.0.1
- name: check build
uses: actions-rs/cargo@v1
with:
@ -58,12 +65,17 @@ jobs:
args: --package=awc --no-default-features --features=rustls -- --nocapture
- name: Generate coverage file
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
if: matrix.version == 'stable' && github.ref == 'refs/heads/master'
run: |
cargo install cargo-tarpaulin --vers "^0.13"
cargo tarpaulin --out Xml
- name: Upload to Codecov
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
if: matrix.version == 'stable' && github.ref == 'refs/heads/master'
uses: codecov/codecov-action@v1
with:
file: cobertura.xml
- name: Clear the cargo caches
run: |
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo-cache

View File

@ -29,6 +29,13 @@ jobs:
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.0.1
- name: check build
uses: actions-rs/cargo@v1
with:
@ -42,3 +49,8 @@ jobs:
args: --all --all-features --no-fail-fast -- --nocapture
--skip=test_h2_content_length
--skip=test_reading_deflate_encoding_large_random_rustls
- name: Clear the cargo caches
run: |
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo-cache

View File

@ -41,6 +41,13 @@ jobs:
Get-ChildItem C:\vcpkg\installed\x64-windows\bin
Get-ChildItem C:\vcpkg\installed\x64-windows\lib
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.0.1
- name: check build
uses: actions-rs/cargo@v1
with:
@ -62,3 +69,8 @@ jobs:
--skip=test_connection_force_close
--skip=test_connection_server_close
--skip=test_connection_wait_queue_force_close
- name: Clear the cargo caches
run: |
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo-cache

View File

@ -1,14 +1,35 @@
# Changes
## Unreleased - 2020-xx-xx
## Unreleased - 2021-xx-xx
## 4.0.0-beta.1 - 2021-01-07
### Added
* `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and
`Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865]
### Changed
* Bumped `rand` to `0.8`
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
* Bumped `rand` to `0.8`.
* Update `rust-tls` to `0.19`. [#1813]
* Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852]
* The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration
guide for implications. [#1875]
* Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875]
* MSRV is now 1.46.0.
### Fixed
* added the actual parsing error to `test::read_body_json` [#1812]
* Added the underlying parse error to `test::read_body_json`'s panic message. [#1812]
### Removed
* Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now
exposed directly by the `middleware` module.
[#1812]: https://github.com/actix/actix-web/pull/1812
[#1813]: https://github.com/actix/actix-web/pull/1813
[#1852]: https://github.com/actix/actix-web/pull/1852
[#1865]: https://github.com/actix/actix-web/pull/1865
[#1875]: https://github.com/actix/actix-web/pull/1875
## 3.3.2 - 2020-12-01

View File

@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "3.3.2"
version = "4.0.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
readme = "README.md"
@ -47,10 +47,10 @@ compress = ["actix-http/compress", "awc/compress"]
secure-cookies = ["actix-http/secure-cookies"]
# openssl
openssl = ["actix-tls/openssl", "awc/openssl", "open-ssl"]
openssl = ["actix-tls/accept", "actix-tls/openssl", "awc/openssl", "open-ssl"]
# rustls
rustls = ["actix-tls/rustls", "awc/rustls", "rust-tls"]
rustls = ["actix-tls/accept", "actix-tls/rustls", "awc/rustls", "rust-tls"]
[[example]]
name = "basic"
@ -73,28 +73,26 @@ name = "client"
required-features = ["rustls"]
[dependencies]
actix-codec = "0.3.0"
actix-service = "1.0.6"
actix-utils = "2.0.0"
actix-router = "0.2.4"
actix-rt = "1.1.1"
actix-server = "1.0.0"
actix-testing = "1.0.0"
actix-codec = "0.4.0-beta.1"
actix-macros = "0.1.0"
actix-router = "0.2.4"
actix-rt = "2.0.0-beta.1"
actix-server = "2.0.0-beta.2"
actix-service = "2.0.0-beta.2"
actix-utils = "3.0.0-beta.1"
actix-threadpool = "0.3.1"
actix-tls = "2.0.0"
actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true }
actix-web-codegen = "0.4.0"
actix-http = "2.2.0"
awc = { version = "2.0.3", default-features = false }
actix-http = "3.0.0-beta.1"
awc = { version = "3.0.0-beta.1", default-features = false }
bytes = "0.5.3"
ahash = "0.6"
bytes = "1"
derive_more = "0.99.5"
encoding_rs = "0.8"
futures-channel = { version = "0.3.5", default-features = false }
futures-core = { version = "0.3.5", default-features = false }
futures-util = { version = "0.3.5", default-features = false }
fxhash = "0.2.1"
futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false }
log = "0.4"
mime = "0.3"
socket2 = "0.3.16"
@ -106,12 +104,12 @@ serde_urlencoded = "0.7"
time = { version = "0.2.7", default-features = false, features = ["std"] }
url = "2.1"
open-ssl = { package = "openssl", version = "0.10", optional = true }
rust-tls = { package = "rustls", version = "0.18.0", optional = true }
tinyvec = { version = "1", features = ["alloc"] }
rust-tls = { package = "rustls", version = "0.19.0", optional = true }
smallvec = "1.6"
[dev-dependencies]
actix = "0.10.0"
actix-http = { version = "2.1.0", features = ["actors"] }
actix = "0.11.0-beta.1"
actix-http = { version = "3.0.0-beta.1", features = ["actors"] }
rand = "0.8"
env_logger = "0.8"
serde_derive = "1.0"
@ -128,6 +126,7 @@ codegen-units = 1
actix-web = { path = "." }
actix-http = { path = "actix-http" }
actix-http-test = { path = "actix-http-test" }
actix-web-actors = { path = "actix-web-actors" }
actix-web-codegen = { path = "actix-web-codegen" }
actix-multipart = { path = "actix-multipart" }
actix-files = { path = "actix-files" }

View File

@ -1,5 +1,15 @@
## Unreleased
* The default `NormalizePath` behavior now strips trailing slashes by default. This was
previously documented to be the case in v3 but the behavior now matches. The effect is that
routes defined with trailing slashes will become inaccessible when
using `NormalizePath::default()`.
Before: `#[get("/test/")`
After: `#[get("/test")`
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
## 3.0.0

View File

@ -7,7 +7,7 @@
[![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=3.3.2)](https://docs.rs/actix-web/3.3.2)
[![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
![License](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/3.3.2/status.svg)](https://deps.rs/crate/actix-web/3.3.2)
<br />
@ -34,7 +34,7 @@
* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
* Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
* Supports [Actix actor framework](https://github.com/actix/actix)
* Runs on stable Rust 1.42+
* Runs on stable Rust 1.46+
## Documentation
@ -107,5 +107,5 @@ at your option.
## Code of Conduct
Contribution to the actix-web crate is organized under the terms of the Contributor Covenant, the
maintainers of Actix web, promises to intervene to uphold that code of conduct.
Contribution to the actix-web repo is organized under the terms of the Contributor Covenant.
The Actix team promises to intervene to uphold that code of conduct.

View File

@ -1,6 +1,13 @@
# Changes
## Unreleased - 2020-xx-xx
## Unreleased - 2021-xx-xx
## 0.6.0-beta.1 - 2021-01-07
* `HttpRange::parse` now has its own error type.
* Update `bytes` to `1.0`. [#1813]
[#1813]: https://github.com/actix/actix-web/pull/1813
## 0.5.0 - 2020-12-26

View File

@ -1,6 +1,6 @@
[package]
name = "actix-files"
version = "0.5.0"
version = "0.6.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static file serving for Actix Web"
readme = "README.md"
@ -17,10 +17,10 @@ name = "actix_files"
path = "src/lib.rs"
[dependencies]
actix-web = { version = "3.0.0", default-features = false }
actix-service = "1.0.6"
actix-web = { version = "4.0.0-beta.1", default-features = false }
actix-service = "2.0.0-beta.2"
bitflags = "1"
bytes = "0.5.3"
bytes = "1"
futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false }
derive_more = "0.99.5"
@ -31,5 +31,5 @@ percent-encoding = "2.1"
v_htmlescape = "0.12"
[dev-dependencies]
actix-rt = "1.0.0"
actix-web = "3.0.0"
actix-rt = "2.0.0-beta.1"
actix-web = "4.0.0-beta.1"

View File

@ -4,7 +4,7 @@
[![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.5.0)](https://docs.rs/actix-files/0.5.0)
[![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
![License](https://img.shields.io/crates/l/actix-files.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-files/0.5.0/status.svg)](https://deps.rs/crate/actix-files/0.5.0)
@ -16,4 +16,4 @@
- [API Documentation](https://docs.rs/actix-files/)
- [Example Project](https://github.com/actix/examples/tree/master/static_index)
- [Chat on Gitter](https://gitter.im/actix/actix-web)
- Minimum supported Rust version: 1.42 or later
- Minimum supported Rust version: 1.46 or later

View File

@ -1,6 +1,6 @@
use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc};
use actix_service::{boxed, IntoServiceFactory, ServiceFactory};
use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt};
use actix_web::{
dev::{
AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse,
@ -201,10 +201,10 @@ impl Files {
/// Sets default handler which is used when no matched file could be found.
pub fn default_handler<F, U>(mut self, f: F) -> Self
where
F: IntoServiceFactory<U>,
F: IntoServiceFactory<U, ServiceRequest>,
U: ServiceFactory<
ServiceRequest,
Config = (),
Request = ServiceRequest,
Response = ServiceResponse,
Error = Error,
> + 'static,
@ -241,8 +241,7 @@ impl HttpServiceFactory for Files {
}
}
impl ServiceFactory for Files {
type Request = ServiceRequest;
impl ServiceFactory<ServiceRequest> for Files {
type Response = ServiceResponse;
type Error = Error;
type Config = ();

View File

@ -1,3 +1,5 @@
use derive_more::{Display, Error};
/// HTTP Range header representation.
#[derive(Debug, Clone, Copy)]
pub struct HttpRange {
@ -11,17 +13,21 @@ pub struct HttpRange {
const PREFIX: &str = "bytes=";
const PREFIX_LEN: usize = 6;
#[derive(Debug, Clone, Display, Error)]
#[display(fmt = "Parse HTTP Range failed")]
pub struct ParseRangeErr(#[error(not(source))] ());
impl HttpRange {
/// Parses Range HTTP header string as per RFC 2616.
///
/// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`).
/// `size` is full size of response (file).
pub fn parse(header: &str, size: u64) -> Result<Vec<HttpRange>, ()> {
pub fn parse(header: &str, size: u64) -> Result<Vec<HttpRange>, ParseRangeErr> {
if header.is_empty() {
return Ok(Vec::new());
}
if !header.starts_with(PREFIX) {
return Err(());
return Err(ParseRangeErr(()));
}
let size_sig = size as i64;
@ -34,13 +40,14 @@ impl HttpRange {
.map(|ra| {
let mut start_end_iter = ra.split('-');
let start_str = start_end_iter.next().ok_or(())?.trim();
let end_str = start_end_iter.next().ok_or(())?.trim();
let start_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.trim();
let end_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.trim();
if start_str.is_empty() {
// If no start is specified, end specifies the
// range start relative to the end of the file.
let mut length: i64 = end_str.parse().map_err(|_| ())?;
let mut length: i64 =
end_str.parse().map_err(|_| ParseRangeErr(()))?;
if length > size_sig {
length = size_sig;
@ -51,10 +58,10 @@ impl HttpRange {
length: length as u64,
}))
} else {
let start: i64 = start_str.parse().map_err(|_| ())?;
let start: i64 = start_str.parse().map_err(|_| ParseRangeErr(()))?;
if start < 0 {
return Err(());
return Err(ParseRangeErr(()));
}
if start >= size_sig {
no_overlap = true;
@ -65,10 +72,11 @@ impl HttpRange {
// If no end is specified, range extends to end of the file.
size_sig - start
} else {
let mut end: i64 = end_str.parse().map_err(|_| ())?;
let mut end: i64 =
end_str.parse().map_err(|_| ParseRangeErr(()))?;
if start > end {
return Err(());
return Err(ParseRangeErr(()));
}
if end >= size_sig {
@ -89,7 +97,7 @@ impl HttpRange {
let ranges: Vec<HttpRange> = all_ranges.into_iter().filter_map(|x| x).collect();
if no_overlap && ranges.is_empty() {
return Err(());
return Err(ParseRangeErr(()));
}
Ok(ranges)
@ -333,8 +341,7 @@ mod tests {
if expected.is_empty() {
continue;
} else {
assert!(
false,
panic!(
"parse({}, {}) returned error {:?}",
header,
size,
@ -346,28 +353,24 @@ mod tests {
let got = res.unwrap();
if got.len() != expected.len() {
assert!(
false,
panic!(
"len(parseRange({}, {})) = {}, want {}",
header,
size,
got.len(),
expected.len()
);
continue;
}
for i in 0..expected.len() {
if got[i].start != expected[i].start {
assert!(
false,
panic!(
"parseRange({}, {})[{}].start = {}, want {}",
header, size, i, got[i].start, expected[i].start
)
}
if got[i].length != expected[i].length {
assert!(
false,
panic!(
"parseRange({}, {})[{}].length = {}, want {}",
header, size, i, got[i].length, expected[i].length
)

View File

@ -57,8 +57,7 @@ impl fmt::Debug for FilesService {
}
}
impl Service for FilesService {
type Request = ServiceRequest;
impl Service<ServiceRequest> for FilesService {
type Response = ServiceResponse;
type Error = Error;
type Future = FilesServiceFuture;

View File

@ -1,6 +1,12 @@
# Changes
## Unreleased - 2020-xx-xx
## Unreleased - 2021-xx-xx
## 3.0.0-beta.1 - 2021-01-07
* Update `bytes` to `1.0`. [#1813]
[#1813]: https://github.com/actix/actix-web/pull/1813
## 2.1.0 - 2020-11-25

View File

@ -1,6 +1,6 @@
[package]
name = "actix-http-test"
version = "2.1.0"
version = "3.0.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Various helpers for Actix applications to use during testing"
readme = "README.md"
@ -29,19 +29,18 @@ default = []
openssl = ["open-ssl", "awc/openssl"]
[dependencies]
actix-service = "1.0.6"
actix-codec = "0.3.0"
actix-connect = "2.0.0"
actix-utils = "2.0.0"
actix-rt = "1.1.1"
actix-server = "1.0.0"
actix-testing = "1.0.0"
awc = "2.0.0"
actix-service = "2.0.0-beta.2"
actix-codec = "0.4.0-beta.1"
actix-tls = "3.0.0-beta.2"
actix-utils = "3.0.0-beta.1"
actix-rt = "2.0.0-beta.1"
actix-server = "2.0.0-beta.2"
awc = "3.0.0-beta.1"
base64 = "0.13"
bytes = "0.5.3"
futures-core = { version = "0.3.5", default-features = false }
http = "0.2.0"
bytes = "1"
futures-core = { version = "0.3.7", default-features = false }
http = "0.2.2"
log = "0.4"
socket2 = "0.3"
serde = "1.0"
@ -52,5 +51,5 @@ time = { version = "0.2.7", default-features = false, features = ["std"] }
open-ssl = { version = "0.10", package = "openssl", optional = true }
[dev-dependencies]
actix-web = "3.0.0"
actix-http = "2.0.0"
actix-web = "4.0.0-beta.1"
actix-http = "3.0.0-beta.1"

View File

@ -12,4 +12,4 @@
- [API Documentation](https://docs.rs/actix-http-test)
- [Chat on Gitter](https://gitter.im/actix/actix-web)
- Minimum Supported Rust Version (MSRV): 1.42.0
- Minimum Supported Rust Version (MSRV): 1.46.0

View File

@ -16,8 +16,6 @@ use futures_core::stream::Stream;
use http::Method;
use socket2::{Domain, Protocol, Socket, Type};
pub use actix_testing::*;
/// Start test server
///
/// `TestServer` is very simple test server that simplify process of writing
@ -65,13 +63,16 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
let sys = System::new("actix-test-server");
let local_addr = tcp.local_addr().unwrap();
Server::build()
let srv = Server::build()
.listen("test", tcp, factory)?
.workers(1)
.disable_signals()
.start();
.disable_signals();
sys.block_on(async {
srv.start();
tx.send((System::current(), local_addr)).unwrap();
});
sys.run()
});
@ -105,7 +106,7 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
Client::builder().connector(connector).finish()
};
actix_connect::start_default_resolver().await.unwrap();
actix_tls::connect::start_default_resolver().await.unwrap();
TestServer {
addr,

View File

@ -1,8 +1,32 @@
# Changes
## Unreleased - 2020-xx-xx
## Unreleased - 2021-xx-xx
## 3.0.0-beta.1 - 2021-01-07
### Added
* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`.
### Changed
* Bumped `rand` to `0.8`
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
* Bumped `rand` to `0.8`.
* Update `bytes` to `1.0`. [#1813]
* Update `h2` to `0.3`. [#1813]
* The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864]
### Removed
* Deprecated `on_connect` methods have been removed. Prefer the new
`on_connect_ext` technique. [#1857]
* Remove `ResponseError` impl for `actix::actors::resolver::ResolverError`
due to deprecate of resolver actor. [#1813]
* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`.
due to the removal of this type from `tokio-openssl` crate. openssl handshake
error would return as `ConnectError::SslError`. [#1813]
[#1813]: https://github.com/actix/actix-web/pull/1813
[#1857]: https://github.com/actix/actix-web/pull/1857
[#1864]: https://github.com/actix/actix-web/pull/1864
## 2.2.0 - 2020-11-25
### Added

View File

@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "2.2.0"
version = "3.0.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "HTTP primitives for the Actix ecosystem"
readme = "README.md"
@ -25,10 +25,10 @@ path = "src/lib.rs"
default = []
# openssl
openssl = ["actix-tls/openssl", "actix-connect/openssl"]
openssl = ["actix-tls/openssl"]
# rustls support
rustls = ["actix-tls/rustls", "actix-connect/rustls"]
rustls = ["actix-tls/rustls"]
# enable compressison support
compress = ["flate2", "brotli2"]
@ -40,29 +40,29 @@ secure-cookies = ["cookie/secure"]
actors = ["actix"]
[dependencies]
actix-service = "1.0.6"
actix-codec = "0.3.0"
actix-connect = "2.0.0"
actix-utils = "2.0.0"
actix-rt = "1.0.0"
actix-service = "2.0.0-beta.2"
actix-codec = "0.4.0-beta.1"
actix-utils = "3.0.0-beta.1"
actix-rt = "2.0.0-beta.1"
actix-threadpool = "0.3.1"
actix-tls = { version = "2.0.0", optional = true }
actix = { version = "0.10.0", optional = true }
actix-tls = "3.0.0-beta.2"
actix = { version = "0.11.0-beta.1", optional = true }
base64 = "0.13"
bitflags = "1.2"
bytes = "0.5.3"
bytes = "1"
bytestring = "1"
cookie = { version = "0.14.1", features = ["percent-encode"] }
copyless = "0.1.4"
derive_more = "0.99.2"
derive_more = "0.99.5"
either = "1.5.3"
encoding_rs = "0.8"
futures-channel = { version = "0.3.5", default-features = false }
futures-core = { version = "0.3.5", default-features = false }
futures-util = { version = "0.3.5", default-features = false }
futures-channel = { 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, features = ["sink"] }
fxhash = "0.2.1"
h2 = "0.2.1"
http = "0.2.0"
h2 = "0.3.0"
http = "0.2.2"
httparse = "1.3"
indexmap = "1.3"
itoa = "0.4"
@ -86,18 +86,17 @@ brotli2 = { version="0.3.2", optional = true }
flate2 = { version = "1.0.13", optional = true }
[dev-dependencies]
actix-server = "1.0.1"
actix-connect = { version = "2.0.0", features = ["openssl"] }
actix-http-test = { version = "2.0.0", features = ["openssl"] }
actix-tls = { version = "2.0.0", features = ["openssl"] }
actix-server = "2.0.0-beta.2"
actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] }
actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] }
criterion = "0.3"
env_logger = "0.7"
serde_derive = "1.0"
open-ssl = { version="0.10", package = "openssl" }
rust-tls = { version="0.18", package = "rustls" }
rust-tls = { version="0.19", package = "rustls" }
[[bench]]
name = "content-length"
name = "write-camel-case"
harness = false
[[bench]]

View File

@ -12,7 +12,7 @@
- [API Documentation](https://docs.rs/actix-http)
- [Chat on Gitter](https://gitter.im/actix/actix-web)
- Minimum Supported Rust Version (MSRV): 1.42.0
- Minimum Supported Rust Version (MSRV): 1.46.0
## Example

View File

@ -1,291 +0,0 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use bytes::BytesMut;
// benchmark sending all requests at the same time
fn bench_write_content_length(c: &mut Criterion) {
let mut group = c.benchmark_group("write_content_length");
let sizes = [
0, 1, 11, 83, 101, 653, 1001, 6323, 10001, 56329, 100001, 123456, 98724245,
4294967202,
];
for i in sizes.iter() {
group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_original::write_content_length(i, &mut b)
})
});
group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_new::write_content_length(i, &mut b)
})
});
group.bench_with_input(BenchmarkId::new("itoa", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_itoa::write_content_length(i, &mut b)
})
});
}
group.finish();
}
criterion_group!(benches, bench_write_content_length);
criterion_main!(benches);
mod _itoa {
use bytes::{BufMut, BytesMut};
pub fn write_content_length(n: usize, bytes: &mut BytesMut) {
if n == 0 {
bytes.put_slice(b"\r\ncontent-length: 0\r\n");
return;
}
let mut buf = itoa::Buffer::new();
bytes.put_slice(b"\r\ncontent-length: ");
bytes.put_slice(buf.format(n).as_bytes());
bytes.put_slice(b"\r\n");
}
}
mod _new {
use bytes::{BufMut, BytesMut};
const DIGITS_START: u8 = b'0';
/// NOTE: bytes object has to contain enough space
pub fn write_content_length(n: usize, bytes: &mut BytesMut) {
if n == 0 {
bytes.put_slice(b"\r\ncontent-length: 0\r\n");
return;
}
bytes.put_slice(b"\r\ncontent-length: ");
if n < 10 {
bytes.put_u8(DIGITS_START + (n as u8));
} else if n < 100 {
let n = n as u8;
let d10 = n / 10;
let d1 = n % 10;
bytes.put_u8(DIGITS_START + d10);
bytes.put_u8(DIGITS_START + d1);
} else if n < 1000 {
let n = n as u16;
let d100 = (n / 100) as u8;
let d10 = ((n / 10) % 10) as u8;
let d1 = (n % 10) as u8;
bytes.put_u8(DIGITS_START + d100);
bytes.put_u8(DIGITS_START + d10);
bytes.put_u8(DIGITS_START + d1);
} else if n < 10_000 {
let n = n as u16;
let d1000 = (n / 1000) as u8;
let d100 = ((n / 100) % 10) as u8;
let d10 = ((n / 10) % 10) as u8;
let d1 = (n % 10) as u8;
bytes.put_u8(DIGITS_START + d1000);
bytes.put_u8(DIGITS_START + d100);
bytes.put_u8(DIGITS_START + d10);
bytes.put_u8(DIGITS_START + d1);
} else if n < 100_000 {
let n = n as u32;
let d10000 = (n / 10000) as u8;
let d1000 = ((n / 1000) % 10) as u8;
let d100 = ((n / 100) % 10) as u8;
let d10 = ((n / 10) % 10) as u8;
let d1 = (n % 10) as u8;
bytes.put_u8(DIGITS_START + d10000);
bytes.put_u8(DIGITS_START + d1000);
bytes.put_u8(DIGITS_START + d100);
bytes.put_u8(DIGITS_START + d10);
bytes.put_u8(DIGITS_START + d1);
} else if n < 1_000_000 {
let n = n as u32;
let d100000 = (n / 100000) as u8;
let d10000 = ((n / 10000) % 10) as u8;
let d1000 = ((n / 1000) % 10) as u8;
let d100 = ((n / 100) % 10) as u8;
let d10 = ((n / 10) % 10) as u8;
let d1 = (n % 10) as u8;
bytes.put_u8(DIGITS_START + d100000);
bytes.put_u8(DIGITS_START + d10000);
bytes.put_u8(DIGITS_START + d1000);
bytes.put_u8(DIGITS_START + d100);
bytes.put_u8(DIGITS_START + d10);
bytes.put_u8(DIGITS_START + d1);
} else {
write_usize(n, bytes);
}
bytes.put_slice(b"\r\n");
}
fn write_usize(n: usize, bytes: &mut BytesMut) {
let mut n = n;
// 20 chars is max length of a usize (2^64)
// digits will be added to the buffer from lsd to msd
let mut buf = BytesMut::with_capacity(20);
while n > 9 {
// "pop" the least-significant digit
let lsd = (n % 10) as u8;
// remove the lsd from n
n = n / 10;
buf.put_u8(DIGITS_START + lsd);
}
// put msd to result buffer
bytes.put_u8(DIGITS_START + (n as u8));
// put, in reverse (msd to lsd), remaining digits to buffer
for i in (0..buf.len()).rev() {
bytes.put_u8(buf[i]);
}
}
}
mod _original {
use std::{mem, ptr, slice};
use bytes::{BufMut, BytesMut};
const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899";
/// NOTE: bytes object has to contain enough space
pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
if n < 10 {
let mut buf: [u8; 21] = [
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l',
b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'\r', b'\n',
];
buf[18] = (n as u8) + b'0';
bytes.put_slice(&buf);
} else if n < 100 {
let mut buf: [u8; 22] = [
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l',
b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'\r', b'\n',
];
let d1 = n << 1;
unsafe {
ptr::copy_nonoverlapping(
DEC_DIGITS_LUT.as_ptr().add(d1),
buf.as_mut_ptr().offset(18),
2,
);
}
bytes.put_slice(&buf);
} else if n < 1000 {
let mut buf: [u8; 23] = [
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l',
b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'0', b'\r',
b'\n',
];
// decode 2 more chars, if > 2 chars
let d1 = (n % 100) << 1;
n /= 100;
unsafe {
ptr::copy_nonoverlapping(
DEC_DIGITS_LUT.as_ptr().add(d1),
buf.as_mut_ptr().offset(19),
2,
)
};
// decode last 1
buf[18] = (n as u8) + b'0';
bytes.put_slice(&buf);
} else {
bytes.put_slice(b"\r\ncontent-length: ");
convert_usize(n, bytes);
}
}
pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
let mut curr: isize = 39;
let mut buf: [u8; 41] = unsafe { mem::MaybeUninit::uninit().assume_init() };
buf[39] = b'\r';
buf[40] = b'\n';
let buf_ptr = buf.as_mut_ptr();
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
// eagerly decode 4 characters at a time
while n >= 10_000 {
let rem = (n % 10_000) as isize;
n /= 10_000;
let d1 = (rem / 100) << 1;
let d2 = (rem % 100) << 1;
curr -= 4;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
ptr::copy_nonoverlapping(
lut_ptr.offset(d2),
buf_ptr.offset(curr + 2),
2,
);
}
}
// if we reach here numbers are <= 9999, so at most 4 chars long
let mut n = n as isize; // possibly reduce 64bit math
// decode 2 more chars, if > 2 chars
if n >= 100 {
let d1 = (n % 100) << 1;
n /= 100;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
}
}
// decode last 1 or 2 chars
if n < 10 {
curr -= 1;
unsafe {
*buf_ptr.offset(curr) = (n as u8) + b'0';
}
} else {
let d1 = n << 1;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
}
}
unsafe {
bytes.extend_from_slice(slice::from_raw_parts(
buf_ptr.offset(curr),
41 - curr as usize,
));
}
}
}

View File

@ -176,7 +176,7 @@ mod _original {
buf[5] = b'0';
buf[7] = b'9';
}
_ => (),
_ => {}
}
let mut curr: isize = 12;

View File

@ -0,0 +1,89 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
fn bench_write_camel_case(c: &mut Criterion) {
let mut group = c.benchmark_group("write_camel_case");
let names = ["connection", "Transfer-Encoding", "transfer-encoding"];
for &i in &names {
let bts = i.as_bytes();
group.bench_with_input(BenchmarkId::new("Original", i), bts, |b, bts| {
b.iter(|| {
let mut buf = black_box([0; 24]);
_original::write_camel_case(black_box(bts), &mut buf)
});
});
group.bench_with_input(BenchmarkId::new("New", i), bts, |b, bts| {
b.iter(|| {
let mut buf = black_box([0; 24]);
_new::write_camel_case(black_box(bts), &mut buf)
});
});
}
group.finish();
}
criterion_group!(benches, bench_write_camel_case);
criterion_main!(benches);
mod _new {
pub fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
// first copy entire (potentially wrong) slice to output
buffer[..value.len()].copy_from_slice(value);
let mut iter = value.iter();
// first character should be uppercase
if let Some(c @ b'a'..=b'z') = iter.next() {
buffer[0] = c & 0b1101_1111;
}
// track 1 ahead of the current position since that's the location being assigned to
let mut index = 2;
// remaining characters after hyphens should also be uppercase
while let Some(&c) = iter.next() {
if c == b'-' {
// advance iter by one and uppercase if needed
if let Some(c @ b'a'..=b'z') = iter.next() {
buffer[index] = c & 0b1101_1111;
}
}
index += 1;
}
}
}
mod _original {
pub fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
let mut index = 0;
let key = value;
let mut key_iter = key.iter();
if let Some(c) = key_iter.next() {
if *c >= b'a' && *c <= b'z' {
buffer[index] = *c ^ b' ';
index += 1;
}
} else {
return;
}
while let Some(c) = key_iter.next() {
buffer[index] = *c;
index += 1;
if *c == b'-' {
if let Some(c) = key_iter.next() {
if *c >= b'a' && *c <= b'z' {
buffer[index] = *c ^ b' ';
index += 1;
}
}
}
}
}
}

View File

@ -1,11 +1,9 @@
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{fmt, mem};
use bytes::{Bytes, BytesMut};
use futures_core::Stream;
use futures_util::ready;
use futures_core::{ready, Stream};
use pin_project::pin_project;
use crate::error::Error;
@ -68,7 +66,7 @@ impl<T: MessageBody + Unpin> MessageBody for Box<T> {
#[pin_project(project = ResponseBodyProj)]
pub enum ResponseBody<B> {
Body(#[pin] B),
Other(#[pin] Body),
Other(Body),
}
impl ResponseBody<Body> {
@ -110,7 +108,7 @@ impl<B: MessageBody> MessageBody for ResponseBody<B> {
) -> Poll<Option<Result<Bytes, Error>>> {
match self.project() {
ResponseBodyProj::Body(body) => body.poll_next(cx),
ResponseBodyProj::Other(body) => body.poll_next(cx),
ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx),
}
}
}
@ -124,12 +122,11 @@ impl<B: MessageBody> Stream for ResponseBody<B> {
) -> Poll<Option<Self::Item>> {
match self.project() {
ResponseBodyProj::Body(body) => body.poll_next(cx),
ResponseBodyProj::Other(body) => body.poll_next(cx),
ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx),
}
}
}
#[pin_project(project = BodyProj)]
/// Represents various types of http message body.
pub enum Body {
/// Empty response. `Content-Length` header is not set.
@ -168,10 +165,10 @@ impl MessageBody for Body {
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Error>>> {
match self.project() {
BodyProj::None => Poll::Ready(None),
BodyProj::Empty => Poll::Ready(None),
BodyProj::Bytes(ref mut bin) => {
match self.get_mut() {
Body::None => Poll::Ready(None),
Body::Empty => Poll::Ready(None),
Body::Bytes(ref mut bin) => {
let len = bin.len();
if len == 0 {
Poll::Ready(None)
@ -179,7 +176,7 @@ impl MessageBody for Body {
Poll::Ready(Some(Ok(mem::take(bin))))
}
}
BodyProj::Message(ref mut body) => Pin::new(body.as_mut()).poll_next(cx),
Body::Message(body) => Pin::new(&mut **body).poll_next(cx),
}
}
}
@ -266,12 +263,12 @@ where
}
}
impl<S, E> From<BodyStream<S, E>> for Body
impl<S, E> From<BodyStream<S>> for Body
where
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
E: Into<Error> + 'static,
{
fn from(s: BodyStream<S, E>) -> Body {
fn from(s: BodyStream<S>) -> Body {
Body::from_message(s)
}
}
@ -367,27 +364,21 @@ impl MessageBody for String {
/// Type represent streaming body.
/// Response does not contain `content-length` header and appropriate transfer encoding is used.
#[pin_project]
pub struct BodyStream<S: Unpin, E> {
#[pin]
pub struct BodyStream<S: Unpin> {
stream: S,
_t: PhantomData<E>,
}
impl<S, E> BodyStream<S, E>
impl<S, E> BodyStream<S>
where
S: Stream<Item = Result<Bytes, E>> + Unpin,
E: Into<Error>,
{
pub fn new(stream: S) -> Self {
BodyStream {
stream,
_t: PhantomData,
}
BodyStream { stream }
}
}
impl<S, E> MessageBody for BodyStream<S, E>
impl<S, E> MessageBody for BodyStream<S>
where
S: Stream<Item = Result<Bytes, E>> + Unpin,
E: Into<Error>,
@ -402,13 +393,12 @@ where
/// ended on a zero-length chunk, but rather proceed until the underlying
/// [`Stream`] ends.
fn poll_next(
self: Pin<&mut Self>,
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Error>>> {
let mut stream = self.project().stream;
loop {
let stream = stream.as_mut();
return Poll::Ready(match ready!(stream.poll_next(cx)) {
let stream = &mut self.as_mut().stream;
return Poll::Ready(match ready!(Pin::new(stream).poll_next(cx)) {
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
opt => opt.map(|res| res.map_err(Into::into)),
});
@ -418,10 +408,8 @@ where
/// Type represent streaming body. This body implementation should be used
/// if total size of stream is known. Data get sent as is without using transfer encoding.
#[pin_project]
pub struct SizedStream<S: Unpin> {
size: u64,
#[pin]
stream: S,
}
@ -448,13 +436,12 @@ where
/// ended on a zero-length chunk, but rather proceed until the underlying
/// [`Stream`] ends.
fn poll_next(
self: Pin<&mut Self>,
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Error>>> {
let mut stream: Pin<&mut S> = self.project().stream;
loop {
let stream = stream.as_mut();
return Poll::Ready(match ready!(stream.poll_next(cx)) {
let stream = &mut self.as_mut().stream;
return Poll::Ready(match ready!(Pin::new(stream).poll_next(cx)) {
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
val => val,
});

View File

@ -10,7 +10,6 @@ use crate::config::{KeepAlive, ServiceConfig};
use crate::error::Error;
use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler};
use crate::h2::H2Service;
use crate::helpers::{Data, DataFactory};
use crate::request::Request;
use crate::response::Response;
use crate::service::HttpService;
@ -20,7 +19,7 @@ use crate::{ConnectCallback, Extensions};
///
/// This type can be used to construct an instance of [`HttpService`] through a
/// builder-like pattern.
pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
keep_alive: KeepAlive,
client_timeout: u64,
client_disconnect: u64,
@ -28,18 +27,16 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
local_addr: Option<net::SocketAddr>,
expect: X,
upgrade: Option<U>,
// DEPRECATED: in favor of on_connect_ext
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_t: PhantomData<(T, S)>,
_phantom: PhantomData<S>,
}
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler<T>>
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
{
/// Create instance of `ServiceConfigBuilder`
pub fn new() -> Self {
@ -51,27 +48,26 @@ where
local_addr: None,
expect: ExpectHandler,
upgrade: None,
on_connect: None,
on_connect_ext: None,
_t: PhantomData,
_phantom: PhantomData,
}
}
}
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
<S::Service as Service>::Future: 'static,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
<S::Service as Service<Request>>::Future: 'static,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static,
U: ServiceFactory<Config = (), Request = (Request, Framed<T, Codec>), Response = ()>,
<X::Service as Service<Request>>::Future: 'static,
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
<U::Service as Service<(Request, Framed<T, Codec>)>>::Future: 'static,
{
/// Set server keep-alive setting.
///
@ -127,11 +123,11 @@ where
/// request will be forwarded to main service.
pub fn expect<F, X1>(self, expect: F) -> HttpServiceBuilder<T, S, X1, U>
where
F: IntoServiceFactory<X1>,
X1: ServiceFactory<Config = (), Request = Request, Response = Request>,
F: IntoServiceFactory<X1, Request>,
X1: ServiceFactory<Request, Config = (), Response = Request>,
X1::Error: Into<Error>,
X1::InitError: fmt::Debug,
<X1::Service as Service>::Future: 'static,
<X1::Service as Service<Request>>::Future: 'static,
{
HttpServiceBuilder {
keep_alive: self.keep_alive,
@ -141,9 +137,8 @@ where
local_addr: self.local_addr,
expect: expect.into_factory(),
upgrade: self.upgrade,
on_connect: self.on_connect,
on_connect_ext: self.on_connect_ext,
_t: PhantomData,
_phantom: PhantomData,
}
}
@ -153,15 +148,11 @@ where
/// and this service get called with original request and framed object.
pub fn upgrade<F, U1>(self, upgrade: F) -> HttpServiceBuilder<T, S, X, U1>
where
F: IntoServiceFactory<U1>,
U1: ServiceFactory<
Config = (),
Request = (Request, Framed<T, Codec>),
Response = (),
>,
F: IntoServiceFactory<U1, (Request, Framed<T, Codec>)>,
U1: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
U1::Error: fmt::Display,
U1::InitError: fmt::Debug,
<U1::Service as Service>::Future: 'static,
<U1::Service as Service<(Request, Framed<T, Codec>)>>::Future: 'static,
{
HttpServiceBuilder {
keep_alive: self.keep_alive,
@ -171,26 +162,11 @@ where
local_addr: self.local_addr,
expect: self.expect,
upgrade: Some(upgrade.into_factory()),
on_connect: self.on_connect,
on_connect_ext: self.on_connect_ext,
_t: PhantomData,
_phantom: PhantomData,
}
}
/// Set on-connect callback.
///
/// Called once per connection. Return value of the call is stored in request extensions.
///
/// *SOFT DEPRECATED*: Prefer the `on_connect_ext` style callback.
pub fn on_connect<F, I>(mut self, f: F) -> Self
where
F: Fn(&T) -> I + 'static,
I: Clone + 'static,
{
self.on_connect = Some(Rc::new(move |io| Box::new(Data(f(io)))));
self
}
/// Sets the callback to be run on connection establishment.
///
/// Has mutable access to a data container that will be merged into request extensions.
@ -208,7 +184,7 @@ where
pub fn h1<F, B>(self, service: F) -> H1Service<T, S, B, X, U>
where
B: MessageBody,
F: IntoServiceFactory<S>,
F: IntoServiceFactory<S, Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
@ -224,7 +200,6 @@ where
H1Service::with_config(cfg, service.into_factory())
.expect(self.expect)
.upgrade(self.upgrade)
.on_connect(self.on_connect)
.on_connect_ext(self.on_connect_ext)
}
@ -232,11 +207,11 @@ where
pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
where
B: MessageBody + 'static,
F: IntoServiceFactory<S>,
F: IntoServiceFactory<S, Request>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
{
let cfg = ServiceConfig::new(
self.keep_alive,
@ -247,7 +222,6 @@ where
);
H2Service::with_config(cfg, service.into_factory())
.on_connect(self.on_connect)
.on_connect_ext(self.on_connect_ext)
}
@ -255,11 +229,11 @@ where
pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U>
where
B: MessageBody + 'static,
F: IntoServiceFactory<S>,
F: IntoServiceFactory<S, Request>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
{
let cfg = ServiceConfig::new(
self.keep_alive,
@ -272,7 +246,6 @@ where
HttpService::with_config(cfg, service.into_factory())
.expect(self.expect)
.upgrade(self.upgrade)
.on_connect(self.on_connect)
.on_connect_ext(self.on_connect_ext)
}
}

View File

@ -1,10 +1,10 @@
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{fmt, io, mem, time};
use std::{fmt, io, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use bytes::{Buf, Bytes};
use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf};
use bytes::Bytes;
use futures_util::future::{err, Either, FutureExt, LocalBoxFuture, Ready};
use h2::client::SendRequest;
use pin_project::pin_project;
@ -223,23 +223,13 @@ where
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
match self.project() {
EitherIoProj::A(val) => val.poll_read(cx, buf),
EitherIoProj::B(val) => val.poll_read(cx, buf),
}
}
unsafe fn prepare_uninitialized_buffer(
&self,
buf: &mut [mem::MaybeUninit<u8>],
) -> bool {
match self {
EitherIo::A(ref val) => val.prepare_uninitialized_buffer(buf),
EitherIo::B(ref val) => val.prepare_uninitialized_buffer(buf),
}
}
}
impl<A, B> AsyncWrite for EitherIo<A, B>
@ -274,18 +264,4 @@ where
EitherIoProj::B(val) => val.poll_shutdown(cx),
}
}
fn poll_write_buf<U: Buf>(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut U,
) -> Poll<Result<usize, io::Error>>
where
Self: Sized,
{
match self.project() {
EitherIoProj::A(val) => val.poll_write_buf(cx, buf),
EitherIoProj::B(val) => val.poll_write_buf(cx, buf),
}
}
}

View File

@ -3,11 +3,11 @@ use std::marker::PhantomData;
use std::time::Duration;
use actix_codec::{AsyncRead, AsyncWrite};
use actix_connect::{
use actix_rt::net::TcpStream;
use actix_service::{apply_fn, Service, ServiceExt};
use actix_tls::connect::{
default_connector, Connect as TcpConnect, Connection as TcpConnection,
};
use actix_rt::net::TcpStream;
use actix_service::{apply_fn, Service};
use actix_utils::timeout::{TimeoutError, TimeoutService};
use http::Uri;
@ -18,10 +18,10 @@ use super::pool::{ConnectionPool, Protocol};
use super::Connect;
#[cfg(feature = "openssl")]
use actix_connect::ssl::openssl::SslConnector as OpensslConnector;
use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector;
#[cfg(feature = "rustls")]
use actix_connect::ssl::rustls::ClientConfig;
use actix_tls::connect::ssl::rustls::ClientConfig;
#[cfg(feature = "rustls")]
use std::sync::Arc;
@ -52,7 +52,7 @@ pub struct Connector<T, U> {
config: ConnectorConfig,
#[allow(dead_code)]
ssl: SslConnector,
_t: PhantomData<U>,
_phantom: PhantomData<U>,
}
trait Io: AsyncRead + AsyncWrite + Unpin {}
@ -62,9 +62,9 @@ impl Connector<(), ()> {
#[allow(clippy::new_ret_no_self, clippy::let_unit_value)]
pub fn new() -> Connector<
impl Service<
Request = TcpConnect<Uri>,
TcpConnect<Uri>,
Response = TcpConnection<Uri, TcpStream>,
Error = actix_connect::ConnectError,
Error = actix_tls::connect::ConnectError,
> + Clone,
TcpStream,
> {
@ -72,14 +72,14 @@ impl Connector<(), ()> {
ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
connector: default_connector(),
config: ConnectorConfig::default(),
_t: PhantomData,
_phantom: PhantomData,
}
}
// Build Ssl connector with openssl, based on supplied alpn protocols
#[cfg(feature = "openssl")]
fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector {
use actix_connect::ssl::openssl::SslMethod;
use actix_tls::connect::ssl::openssl::SslMethod;
use bytes::{BufMut, BytesMut};
let mut alpn = BytesMut::with_capacity(20);
@ -102,7 +102,7 @@ impl Connector<(), ()> {
config.set_protocols(&protocols);
config
.root_store
.add_server_trust_anchors(&actix_tls::rustls::TLS_SERVER_ROOTS);
.add_server_trust_anchors(&actix_tls::accept::rustls::TLS_SERVER_ROOTS);
SslConnector::Rustls(Arc::new(config))
}
@ -117,16 +117,16 @@ impl<T, U> Connector<T, U> {
where
U1: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
T1: Service<
Request = TcpConnect<Uri>,
TcpConnect<Uri>,
Response = TcpConnection<Uri, U1>,
Error = actix_connect::ConnectError,
Error = actix_tls::connect::ConnectError,
> + Clone,
{
Connector {
connector,
config: self.config,
ssl: self.ssl,
_t: PhantomData,
_phantom: PhantomData,
}
}
}
@ -135,9 +135,9 @@ impl<T, U> Connector<T, U>
where
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
T: Service<
Request = TcpConnect<Uri>,
TcpConnect<Uri>,
Response = TcpConnection<Uri, U>,
Error = actix_connect::ConnectError,
Error = actix_tls::connect::ConnectError,
> + Clone
+ 'static,
{
@ -241,8 +241,8 @@ where
/// its combinator chain.
pub fn finish(
self,
) -> impl Service<Request = Connect, Response = impl Connection, Error = ConnectError>
+ Clone {
) -> impl Service<Connect, Response = impl Connection, Error = ConnectError> + Clone
{
#[cfg(not(any(feature = "openssl", feature = "rustls")))]
{
let connector = TimeoutService::new(
@ -268,11 +268,11 @@ where
#[cfg(any(feature = "openssl", feature = "rustls"))]
{
const H2: &[u8] = b"h2";
#[cfg(feature = "openssl")]
use actix_connect::ssl::openssl::OpensslConnector;
#[cfg(feature = "rustls")]
use actix_connect::ssl::rustls::{RustlsConnector, Session};
use actix_service::{boxed::service, pipeline};
#[cfg(feature = "openssl")]
use actix_tls::connect::ssl::openssl::OpensslConnector;
#[cfg(feature = "rustls")]
use actix_tls::connect::ssl::rustls::{RustlsConnector, Session};
let ssl_service = TimeoutService::new(
self.config.timeout,
@ -363,8 +363,7 @@ mod connect_impl {
pub(crate) struct InnerConnector<T, Io>
where
Io: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ 'static,
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
{
pub(crate) tcp_pool: ConnectionPool<T, Io>,
}
@ -372,8 +371,7 @@ mod connect_impl {
impl<T, Io> Clone for InnerConnector<T, Io>
where
Io: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ 'static,
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
{
fn clone(&self) -> Self {
InnerConnector {
@ -382,17 +380,15 @@ mod connect_impl {
}
}
impl<T, Io> Service for InnerConnector<T, Io>
impl<T, Io> Service<Connect> for InnerConnector<T, Io>
where
Io: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ 'static,
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
{
type Request = Connect;
type Response = IoConnection<Io>;
type Error = ConnectError;
type Future = Either<
<ConnectionPool<T, Io> as Service>::Future,
<ConnectionPool<T, Io> as Service<Connect>>::Future,
Ready<Result<IoConnection<Io>, ConnectError>>,
>;
@ -428,8 +424,8 @@ mod connect_impl {
where
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>,
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>,
T1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError>,
T2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError>,
{
pub(crate) tcp_pool: ConnectionPool<T1, Io1>,
pub(crate) ssl_pool: ConnectionPool<T2, Io2>,
@ -439,10 +435,8 @@ mod connect_impl {
where
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
+ 'static,
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
+ 'static,
T1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
T2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
{
fn clone(&self) -> Self {
InnerConnector {
@ -452,16 +446,13 @@ mod connect_impl {
}
}
impl<T1, T2, Io1, Io2> Service for InnerConnector<T1, T2, Io1, Io2>
impl<T1, T2, Io1, Io2> Service<Connect> for InnerConnector<T1, T2, Io1, Io2>
where
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
+ 'static,
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
+ 'static,
T1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
T2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
{
type Request = Connect;
type Response = EitherConnection<Io1, Io2>;
type Error = ConnectError;
type Future = Either<
@ -477,11 +468,11 @@ mod connect_impl {
match req.uri.scheme_str() {
Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB {
fut: self.ssl_pool.call(req),
_t: PhantomData,
_phantom: PhantomData,
}),
_ => Either::Left(InnerConnectorResponseA {
fut: self.tcp_pool.call(req),
_t: PhantomData,
_phantom: PhantomData,
}),
}
}
@ -491,18 +482,16 @@ mod connect_impl {
pub(crate) struct InnerConnectorResponseA<T, Io1, Io2>
where
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
+ 'static,
T: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
{
#[pin]
fut: <ConnectionPool<T, Io1> as Service>::Future,
_t: PhantomData<Io2>,
fut: <ConnectionPool<T, Io1> as Service<Connect>>::Future,
_phantom: PhantomData<Io2>,
}
impl<T, Io1, Io2> Future for InnerConnectorResponseA<T, Io1, Io2>
where
T: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
+ 'static,
T: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
{
@ -520,18 +509,16 @@ mod connect_impl {
pub(crate) struct InnerConnectorResponseB<T, Io1, Io2>
where
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
+ 'static,
T: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
{
#[pin]
fut: <ConnectionPool<T, Io2> as Service>::Future,
_t: PhantomData<Io1>,
fut: <ConnectionPool<T, Io2> as Service<Connect>>::Future,
_phantom: PhantomData<Io1>,
}
impl<T, Io1, Io2> Future for InnerConnectorResponseB<T, Io1, Io2>
where
T: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
+ 'static,
T: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
{

View File

@ -1,10 +1,10 @@
use std::io;
use actix_connect::resolver::ResolveError;
use actix_tls::connect::resolver::ResolveError;
use derive_more::{Display, From};
#[cfg(feature = "openssl")]
use actix_connect::ssl::openssl::{HandshakeError, SslError};
use actix_tls::accept::openssl::SslError;
use crate::error::{Error, ParseError, ResponseError};
use crate::http::{Error as HttpError, StatusCode};
@ -21,11 +21,6 @@ pub enum ConnectError {
#[display(fmt = "{}", _0)]
SslError(SslError),
/// SSL Handshake error
#[cfg(feature = "openssl")]
#[display(fmt = "{}", _0)]
SslHandshakeError(String),
/// Failed to resolve the hostname
#[display(fmt = "Failed resolving hostname: {}", _0)]
Resolver(ResolveError),
@ -57,25 +52,18 @@ pub enum ConnectError {
impl std::error::Error for ConnectError {}
impl From<actix_connect::ConnectError> for ConnectError {
fn from(err: actix_connect::ConnectError) -> ConnectError {
impl From<actix_tls::connect::ConnectError> for ConnectError {
fn from(err: actix_tls::connect::ConnectError) -> ConnectError {
match err {
actix_connect::ConnectError::Resolver(e) => ConnectError::Resolver(e),
actix_connect::ConnectError::NoRecords => ConnectError::NoRecords,
actix_connect::ConnectError::InvalidInput => panic!(),
actix_connect::ConnectError::Unresolved => ConnectError::Unresolved,
actix_connect::ConnectError::Io(e) => ConnectError::Io(e),
actix_tls::connect::ConnectError::Resolver(e) => ConnectError::Resolver(e),
actix_tls::connect::ConnectError::NoRecords => ConnectError::NoRecords,
actix_tls::connect::ConnectError::InvalidInput => panic!(),
actix_tls::connect::ConnectError::Unresolved => ConnectError::Unresolved,
actix_tls::connect::ConnectError::Io(e) => ConnectError::Io(e),
}
}
}
#[cfg(feature = "openssl")]
impl<T: std::fmt::Debug> From<HandshakeError<T>> for ConnectError {
fn from(err: HandshakeError<T>) -> ConnectError {
ConnectError::SslHandshakeError(format!("{:?}", err))
}
}
#[derive(Debug, Display, From)]
pub enum InvalidUrl {
#[display(fmt = "Missing url scheme")]

View File

@ -1,10 +1,10 @@
use std::io::Write;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{io, mem, time};
use std::{io, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use bytes::buf::BufMutExt;
use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf};
use bytes::buf::BufMut;
use bytes::{Bytes, BytesMut};
use futures_core::Stream;
use futures_util::future::poll_fn;
@ -72,7 +72,7 @@ where
// send request body
match body.size() {
BodySize::None | BodySize::Empty | BodySize::Sized(0) => (),
BodySize::None | BodySize::Empty | BodySize::Sized(0) => {}
_ => send_body(body, Pin::new(&mut framed_inner)).await?,
};
@ -204,18 +204,11 @@ where
}
impl<T: AsyncRead + AsyncWrite + Unpin + 'static> AsyncRead for H1Connection<T> {
unsafe fn prepare_uninitialized_buffer(
&self,
buf: &mut [mem::MaybeUninit<u8>],
) -> bool {
self.io.as_ref().unwrap().prepare_uninitialized_buffer(buf)
}
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Pin::new(&mut self.io.as_mut().unwrap()).poll_read(cx, buf)
}
}

View File

@ -89,7 +89,7 @@ where
CONNECTION | TRANSFER_ENCODING => continue, // http2 specific
CONTENT_LENGTH if skip_len => continue,
// DATE => has_date = true,
_ => (),
_ => {}
}
req.headers_mut().append(key, value.clone());
}

View File

@ -6,8 +6,8 @@ use std::rc::Rc;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::time::{delay_for, Delay};
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
use actix_rt::time::{sleep, Sleep};
use actix_service::Service;
use actix_utils::task::LocalWaker;
use bytes::Bytes;
@ -50,8 +50,7 @@ pub(crate) struct ConnectionPool<T, Io: 'static>(Rc<RefCell<T>>, Rc<RefCell<Inne
impl<T, Io> ConnectionPool<T, Io>
where
Io: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ 'static,
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
{
pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self {
let connector_rc = Rc::new(RefCell::new(connector));
@ -90,13 +89,11 @@ impl<T, Io> Drop for ConnectionPool<T, Io> {
}
}
impl<T, Io> Service for ConnectionPool<T, Io>
impl<T, Io> Service<Connect> for ConnectionPool<T, Io>
where
Io: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ 'static,
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
{
type Request = Connect;
type Response = IoConnection<Io>;
type Error = ConnectError;
type Future = LocalBoxFuture<'static, Result<IoConnection<Io>, ConnectError>>;
@ -334,10 +331,11 @@ where
} else {
let mut io = conn.io;
let mut buf = [0; 2];
let mut read_buf = ReadBuf::new(&mut buf);
if let ConnectionType::H1(ref mut s) = io {
match Pin::new(s).poll_read(cx, &mut buf) {
Poll::Pending => (),
Poll::Ready(Ok(n)) if n > 0 => {
match Pin::new(s).poll_read(cx, &mut read_buf) {
Poll::Pending => {}
Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => {
if let Some(timeout) = self.config.disconnect_timeout {
if let ConnectionType::H1(io) = io {
actix_rt::spawn(CloseConnection::new(
@ -387,9 +385,11 @@ where
}
}
#[pin_project::pin_project]
struct CloseConnection<T> {
io: T,
timeout: Delay,
#[pin]
timeout: Sleep,
}
impl<T> CloseConnection<T>
@ -399,7 +399,7 @@ where
fn new(io: T, timeout: Duration) -> Self {
CloseConnection {
io,
timeout: delay_for(timeout),
timeout: sleep(timeout),
}
}
}
@ -411,11 +411,11 @@ where
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
let this = self.get_mut();
let this = self.project();
match Pin::new(&mut this.timeout).poll(cx) {
match this.timeout.poll(cx) {
Poll::Ready(_) => Poll::Ready(()),
Poll::Pending => match Pin::new(&mut this.io).poll_shutdown(cx) {
Poll::Pending => match Pin::new(this.io).poll_shutdown(cx) {
Poll::Ready(_) => Poll::Ready(()),
Poll::Pending => Poll::Pending,
},
@ -435,7 +435,7 @@ where
impl<T, Io> Future for ConnectorPoolSupport<T, Io>
where
Io: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>,
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError>,
T::Future: 'static,
{
type Output = ();

View File

View File

@ -1,40 +0,0 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_service::Service;
/// Service that allows to turn non-clone service to a service with `Clone` impl
///
/// # Panics
/// CloneableService might panic with some creative use of thread local storage.
/// See https://github.com/actix/actix-web/issues/1295 for example
#[doc(hidden)]
pub(crate) struct CloneableService<T: Service>(Rc<RefCell<T>>);
impl<T: Service> CloneableService<T> {
pub(crate) fn new(service: T) -> Self {
Self(Rc::new(RefCell::new(service)))
}
}
impl<T: Service> Clone for CloneableService<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: Service> Service for CloneableService<T> {
type Request = T::Request;
type Response = T::Response;
type Error = T::Error;
type Future = T::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.borrow_mut().poll_ready(cx)
}
fn call(&mut self, req: T::Request) -> Self::Future {
self.0.borrow_mut().call(req)
}
}

View File

@ -4,7 +4,7 @@ use std::rc::Rc;
use std::time::Duration;
use std::{fmt, net};
use actix_rt::time::{delay_for, delay_until, Delay, Instant};
use actix_rt::time::{sleep, sleep_until, Instant, Sleep};
use bytes::BytesMut;
use futures_util::{future, FutureExt};
use time::OffsetDateTime;
@ -121,10 +121,10 @@ impl ServiceConfig {
#[inline]
/// Client timeout for first request.
pub fn client_timer(&self) -> Option<Delay> {
pub fn client_timer(&self) -> Option<Sleep> {
let delay_time = self.0.client_timeout;
if delay_time != 0 {
Some(delay_until(
Some(sleep_until(
self.0.timer.now() + Duration::from_millis(delay_time),
))
} else {
@ -154,9 +154,9 @@ impl ServiceConfig {
#[inline]
/// Return keep-alive timer delay is configured.
pub fn keep_alive_timer(&self) -> Option<Delay> {
pub fn keep_alive_timer(&self) -> Option<Sleep> {
if let Some(ka) = self.0.keep_alive {
Some(delay_until(self.0.timer.now() + ka))
Some(sleep_until(self.0.timer.now() + ka))
} else {
None
}
@ -266,7 +266,7 @@ impl DateService {
// periodic date update
let s = self.clone();
actix_rt::spawn(delay_for(Duration::from_millis(500)).then(move |_| {
actix_rt::spawn(sleep(Duration::from_millis(500)).then(move |_| {
s.0.reset();
future::ready(())
}));

View File

@ -178,11 +178,7 @@ impl ResponseError for FormError {}
#[cfg(feature = "openssl")]
/// `InternalServerError` for `openssl::ssl::Error`
impl ResponseError for actix_connect::ssl::openssl::SslError {}
#[cfg(feature = "openssl")]
/// `InternalServerError` for `openssl::ssl::HandshakeError`
impl<T: std::fmt::Debug> ResponseError for actix_tls::openssl::HandshakeError<T> {}
impl ResponseError for actix_tls::accept::openssl::SslError {}
/// Return `BAD_REQUEST` for `de::value::Error`
impl ResponseError for DeError {
@ -956,11 +952,6 @@ where
/// This is supported on feature=`actors` only
impl ResponseError for actix::MailboxError {}
#[cfg(feature = "actors")]
/// `InternalServerError` for `actix::ResolverError`
/// This is supported on feature=`actors` only
impl ResponseError for actix::actors::resolver::ResolverError {}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -111,8 +111,8 @@ impl Decoder for Codec {
type Error = ParseError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if self.payload.is_some() {
Ok(match self.payload.as_mut().unwrap().decode(src)? {
if let Some(ref mut payload) = self.payload {
Ok(match payload.decode(src)? {
Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))),
Some(PayloadItem::Eof) => {
self.payload.take();

View File

@ -137,7 +137,7 @@ pub(crate) trait MessageType: Sized {
expect = true;
}
}
_ => (),
_ => {}
}
headers.append(name, value);
@ -685,7 +685,7 @@ mod tests {
match MessageDecoder::<Request>::default().decode($e) {
Err(err) => match err {
ParseError::Io(_) => unreachable!("Parse error expected"),
_ => (),
_ => {}
},
_ => unreachable!("Error expected"),
}

View File

@ -1,32 +1,30 @@
use std::{
cell::RefCell,
collections::VecDeque,
fmt,
future::Future,
io, mem, net,
pin::Pin,
rc::Rc,
task::{Context, Poll},
};
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts};
use actix_rt::time::{delay_until, Delay, Instant};
use actix_rt::time::{sleep_until, Instant, Sleep};
use actix_service::Service;
use bitflags::bitflags;
use bytes::{Buf, BytesMut};
use log::{error, trace};
use pin_project::pin_project;
use crate::cloneable::CloneableService;
use crate::body::{Body, BodySize, MessageBody, ResponseBody};
use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error};
use crate::error::{ParseError, PayloadError};
use crate::helpers::DataFactory;
use crate::httpmessage::HttpMessage;
use crate::request::Request;
use crate::response::Response;
use crate::{
body::{Body, BodySize, MessageBody, ResponseBody},
Extensions,
};
use crate::service::HttpFlow;
use crate::OnConnectData;
use super::codec::Codec;
use super::payload::{Payload, PayloadSender, PayloadStatus};
@ -52,12 +50,12 @@ bitflags! {
/// Dispatcher for HTTP/1.1 protocol
pub struct Dispatcher<T, S, B, X, U>
where
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
#[pin]
@ -70,34 +68,31 @@ where
#[pin_project(project = DispatcherStateProj)]
enum DispatcherState<T, S, B, X, U>
where
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
Normal(#[pin] InnerDispatcher<T, S, B, X, U>),
Upgrade(Pin<Box<U::Future>>),
Upgrade(#[pin] U::Future),
}
#[pin_project(project = InnerDispatcherProj)]
struct InnerDispatcher<T, S, B, X, U>
where
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
service: CloneableService<S>,
expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>,
on_connect: Option<Box<dyn DataFactory>>,
on_connect_data: Extensions,
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
on_connect_data: OnConnectData,
flags: Flags,
peer_addr: Option<net::SocketAddr>,
error: Option<DispatchError>,
@ -108,7 +103,8 @@ where
messages: VecDeque<DispatcherMessage>,
ka_expire: Instant,
ka_timer: Option<Delay>,
#[pin]
ka_timer: Option<Sleep>,
io: Option<T>,
read_buf: BytesMut,
@ -125,8 +121,8 @@ enum DispatcherMessage {
#[pin_project(project = StateProj)]
enum State<S, B, X>
where
S: Service<Request = Request>,
X: Service<Request = Request, Response = Request>,
S: Service<Request>,
X: Service<Request, Response = Request>,
B: MessageBody,
{
None,
@ -137,8 +133,8 @@ where
impl<S, B, X> State<S, B, X>
where
S: Service<Request = Request>,
X: Service<Request = Request, Response = Request>,
S: Service<Request>,
X: Service<Request, Response = Request>,
B: MessageBody,
{
fn is_empty(&self) -> bool {
@ -168,24 +164,21 @@ impl PartialEq for PollResponse {
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
/// Create HTTP/1 dispatcher.
pub(crate) fn new(
stream: T,
config: ServiceConfig,
service: CloneableService<S>,
expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>,
on_connect: Option<Box<dyn DataFactory>>,
on_connect_data: Extensions,
services: Rc<RefCell<HttpFlow<S, X, U>>>,
on_connect_data: OnConnectData,
peer_addr: Option<net::SocketAddr>,
) -> Self {
Dispatcher::with_timeout(
@ -194,10 +187,7 @@ where
config,
BytesMut::with_capacity(HW_BUFFER_SIZE),
None,
service,
expect,
upgrade,
on_connect,
services,
on_connect_data,
peer_addr,
)
@ -209,12 +199,9 @@ where
codec: Codec,
config: ServiceConfig,
read_buf: BytesMut,
timeout: Option<Delay>,
service: CloneableService<S>,
expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>,
on_connect: Option<Box<dyn DataFactory>>,
on_connect_data: Extensions,
timeout: Option<Sleep>,
services: Rc<RefCell<HttpFlow<S, X, U>>>,
on_connect_data: OnConnectData,
peer_addr: Option<net::SocketAddr>,
) -> Self {
let keepalive = config.keep_alive_enabled();
@ -243,10 +230,7 @@ where
io: Some(io),
codec,
read_buf,
service,
expect,
upgrade,
on_connect,
flow: services,
on_connect_data,
flags,
peer_addr,
@ -263,13 +247,13 @@ where
impl<T, S, B, X, U> InnerDispatcher<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
fn can_read(&self, cx: &mut Context<'_>) -> bool {
@ -400,7 +384,8 @@ where
Poll::Ready(Ok(req)) => {
self.as_mut().send_continue();
this = self.as_mut().project();
this.state.set(State::ServiceCall(this.service.call(req)));
let fut = this.flow.borrow_mut().service.call(req);
this.state.set(State::ServiceCall(fut));
continue;
}
Poll::Ready(Err(e)) => {
@ -488,12 +473,14 @@ where
// Handle `EXPECT: 100-Continue` header
if req.head().expect() {
// set dispatcher state so the future is pinned.
let task = self.as_mut().project().expect.call(req);
self.as_mut().project().state.set(State::ExpectCall(task));
let mut this = self.as_mut().project();
let task = this.flow.borrow_mut().expect.call(req);
this.state.set(State::ExpectCall(task));
} else {
// the same as above.
let task = self.as_mut().project().service.call(req);
self.as_mut().project().state.set(State::ServiceCall(task));
let mut this = self.as_mut().project();
let task = this.flow.borrow_mut().service.call(req);
this.state.set(State::ServiceCall(task));
};
// eagerly poll the future for once(or twice if expect is resolved immediately).
@ -504,8 +491,9 @@ where
// expect is resolved. continue loop and poll the service call branch.
Poll::Ready(Ok(req)) => {
self.as_mut().send_continue();
let task = self.as_mut().project().service.call(req);
self.as_mut().project().state.set(State::ServiceCall(task));
let mut this = self.as_mut().project();
let task = this.flow.borrow_mut().service.call(req);
this.state.set(State::ServiceCall(task));
continue;
}
// future is pending. return Ok(()) to notify that a new state is
@ -572,16 +560,12 @@ where
let pl = this.codec.message_type();
req.head_mut().peer_addr = *this.peer_addr;
// DEPRECATED
// set on_connect data
if let Some(ref on_connect) = this.on_connect {
on_connect.set(&mut req.extensions_mut());
}
// merge on_connect_ext data into request extensions
req.extensions_mut().drain_from(this.on_connect_data);
this.on_connect_data.merge_into(&mut req);
if pl == MessageType::Stream && this.upgrade.is_some() {
if pl == MessageType::Stream
&& this.flow.borrow().upgrade.is_some()
{
this.messages.push_back(DispatcherMessage::Upgrade(req));
break;
}
@ -672,7 +656,7 @@ where
// shutdown timeout
if this.flags.contains(Flags::SHUTDOWN) {
if let Some(interval) = this.codec.config().client_disconnect_timer() {
*this.ka_timer = Some(delay_until(interval));
this.ka_timer.set(Some(sleep_until(interval)));
} else {
this.flags.insert(Flags::READ_DISCONNECT);
if let Some(mut payload) = this.payload.take() {
@ -685,12 +669,14 @@ where
}
}
match Pin::new(&mut this.ka_timer.as_mut().unwrap()).poll(cx) {
match this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx) {
Poll::Ready(()) => {
// if we get timeout during shutdown, drop connection
if this.flags.contains(Flags::SHUTDOWN) {
return Err(DispatchError::DisconnectTimeout);
} else if this.ka_timer.as_mut().unwrap().deadline() >= *this.ka_expire {
} else if this.ka_timer.as_mut().as_pin_mut().unwrap().deadline()
>= *this.ka_expire
{
// check for any outstanding tasks
if this.state.is_empty() && this.write_buf.is_empty() {
if this.flags.contains(Flags::STARTED) {
@ -701,9 +687,15 @@ where
if let Some(deadline) =
this.codec.config().client_disconnect_timer()
{
if let Some(mut timer) = this.ka_timer.as_mut() {
if let Some(timer) = this.ka_timer.as_mut().as_pin_mut()
{
timer.reset(deadline);
let _ = Pin::new(&mut timer).poll(cx);
let _ = this
.ka_timer
.as_mut()
.as_pin_mut()
.unwrap()
.poll(cx);
}
} else {
// no shutdown timeout, drop socket
@ -728,17 +720,18 @@ where
} else if let Some(deadline) =
this.codec.config().keep_alive_expire()
{
if let Some(mut timer) = this.ka_timer.as_mut() {
if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() {
timer.reset(deadline);
let _ = Pin::new(&mut timer).poll(cx);
let _ =
this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx);
}
}
} else if let Some(mut timer) = this.ka_timer.as_mut() {
} else if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() {
timer.reset(*this.ka_expire);
let _ = Pin::new(&mut timer).poll(cx);
let _ = this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx);
}
}
Poll::Pending => (),
Poll::Pending => {}
}
Ok(())
@ -748,13 +741,13 @@ where
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
type Output = Result<(), DispatchError>;
@ -836,12 +829,17 @@ where
);
parts.write_buf = mem::take(inner_p.write_buf);
let framed = Framed::from_parts(parts);
let upgrade =
inner_p.upgrade.take().unwrap().call((req, framed));
let upgrade = inner_p
.flow
.borrow_mut()
.upgrade
.take()
.unwrap()
.call((req, framed));
self.as_mut()
.project()
.inner
.set(DispatcherState::Upgrade(Box::pin(upgrade)));
.set(DispatcherState::Upgrade(upgrade));
return self.poll(cx);
}
@ -892,7 +890,7 @@ where
}
}
}
DispatcherStateProj::Upgrade(fut) => fut.as_mut().poll(cx).map_err(|e| {
DispatcherStateProj::Upgrade(fut) => fut.poll(cx).map_err(|e| {
error!("Upgrade handler error: {}", e);
DispatchError::Upgrade
}),
@ -963,12 +961,12 @@ fn read<T>(
where
T: AsyncRead + Unpin,
{
Pin::new(io).poll_read_buf(cx, buf)
actix_codec::poll_read_buf(Pin::new(io), cx, buf)
}
#[cfg(test)]
mod tests {
use std::{marker::PhantomData, str};
use std::str;
use actix_service::fn_service;
use futures_util::future::{lazy, ready};
@ -997,21 +995,19 @@ mod tests {
}
}
fn ok_service() -> impl Service<Request = Request, Response = Response, Error = Error>
{
fn ok_service() -> impl Service<Request, Response = Response, Error = Error> {
fn_service(|_req: Request| ready(Ok::<_, Error>(Response::Ok().finish())))
}
fn echo_path_service(
) -> impl Service<Request = Request, Response = Response, Error = Error> {
fn echo_path_service() -> impl Service<Request, Response = Response, Error = Error> {
fn_service(|req: Request| {
let path = req.path().as_bytes();
ready(Ok::<_, Error>(Response::Ok().body(Body::from_slice(path))))
})
}
fn echo_payload_service(
) -> impl Service<Request = Request, Response = Response, Error = Error> {
fn echo_payload_service() -> impl Service<Request, Response = Response, Error = Error>
{
fn_service(|mut req: Request| {
Box::pin(async move {
use futures_util::stream::StreamExt as _;
@ -1019,7 +1015,7 @@ mod tests {
let mut pl = req.take_payload();
let mut body = BytesMut::new();
while let Some(chunk) = pl.next().await {
body.extend_from_slice(chunk.unwrap().bytes())
body.extend_from_slice(chunk.unwrap().chunk())
}
Ok::<_, Error>(Response::Ok().body(body))
@ -1032,14 +1028,13 @@ mod tests {
lazy(|cx| {
let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n");
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<TestBuffer>>::new(
let services = HttpFlow::new(ok_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf,
ServiceConfig::default(),
CloneableService::new(ok_service()),
CloneableService::new(ExpectHandler),
None,
None,
Extensions::new(),
services,
OnConnectData::default(),
None,
);
@ -1073,14 +1068,13 @@ mod tests {
let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<TestBuffer>>::new(
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf,
cfg,
CloneableService::new(echo_path_service()),
CloneableService::new(ExpectHandler),
None,
None,
Extensions::new(),
services,
OnConnectData::default(),
None,
);
@ -1128,14 +1122,13 @@ mod tests {
let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<TestBuffer>>::new(
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf,
cfg,
CloneableService::new(echo_path_service()),
CloneableService::new(ExpectHandler),
None,
None,
Extensions::new(),
services,
OnConnectData::default(),
None,
);
@ -1178,14 +1171,14 @@ mod tests {
lazy(|cx| {
let mut buf = TestSeqBuffer::empty();
let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new(
let services = HttpFlow::new(echo_payload_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
cfg,
CloneableService::new(echo_payload_service()),
CloneableService::new(ExpectHandler),
None,
None,
Extensions::new(),
services,
OnConnectData::default(),
None,
);
@ -1250,14 +1243,14 @@ mod tests {
lazy(|cx| {
let mut buf = TestSeqBuffer::empty();
let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new(
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
cfg,
CloneableService::new(echo_path_service()),
CloneableService::new(ExpectHandler),
None,
None,
Extensions::new(),
services,
OnConnectData::default(),
None,
);
@ -1310,14 +1303,15 @@ mod tests {
lazy(|cx| {
let mut buf = TestSeqBuffer::empty();
let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new(
let services =
HttpFlow::new(ok_service(), ExpectHandler, Some(UpgradeHandler));
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
cfg,
CloneableService::new(ok_service()),
CloneableService::new(ExpectHandler),
Some(CloneableService::new(UpgradeHandler(PhantomData))),
None,
Extensions::new(),
services,
OnConnectData::default(),
None,
);

View File

@ -21,7 +21,7 @@ const AVERAGE_HEADER_SIZE: usize = 30;
pub(crate) struct MessageEncoder<T: MessageType> {
pub length: BodySize,
pub te: TransferEncoding,
_t: PhantomData<T>,
_phantom: PhantomData<T>,
}
impl<T: MessageType> Default for MessageEncoder<T> {
@ -29,7 +29,7 @@ impl<T: MessageType> Default for MessageEncoder<T> {
MessageEncoder {
length: BodySize::None,
te: TransferEncoding::empty(),
_t: PhantomData,
_phantom: PhantomData,
}
}
}
@ -118,7 +118,7 @@ pub(crate) trait MessageType: Sized {
dst.put_slice(b"connection: close\r\n")
}
}
_ => (),
_ => {}
}
// merging headers from head and extra headers. HeaderMap::new() does not allocate.
@ -135,7 +135,7 @@ pub(crate) trait MessageType: Sized {
let mut has_date = false;
let mut buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
let mut buf = dst.chunk_mut().as_mut_ptr() as *mut u8;
let mut remaining = dst.capacity() - dst.len();
// tracks bytes written since last buffer resize
@ -148,7 +148,7 @@ pub(crate) trait MessageType: Sized {
CONNECTION => continue,
TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue,
DATE => has_date = true,
_ => (),
_ => {}
}
let k = key.as_str().as_bytes();
@ -177,7 +177,7 @@ pub(crate) trait MessageType: Sized {
// re-assign buf raw pointer since it's possible that the buffer was
// reallocated and/or resized
buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
buf = dst.chunk_mut().as_mut_ptr() as *mut u8;
}
// SAFETY: on each write, it is enough to ensure that the advancement of the
@ -224,7 +224,7 @@ pub(crate) trait MessageType: Sized {
// re-assign buf raw pointer since it's possible that the buffer was
// reallocated and/or resized
buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
buf = dst.chunk_mut().as_mut_ptr() as *mut u8;
}
// SAFETY: on each write, it is enough to ensure that the advancement of
@ -532,30 +532,29 @@ unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) {
}
fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
let mut index = 0;
let key = value;
let mut key_iter = key.iter();
// first copy entire (potentially wrong) slice to output
buffer[..value.len()].copy_from_slice(value);
if let Some(c) = key_iter.next() {
if *c >= b'a' && *c <= b'z' {
buffer[index] = *c ^ b' ';
index += 1;
}
} else {
return;
let mut iter = value.iter();
// first character should be uppercase
if let Some(c @ b'a'..=b'z') = iter.next() {
buffer[0] = c & 0b1101_1111;
}
// track 1 ahead of the current position since that's the location being assigned to
let mut index = 2;
// remaining characters after hyphens should also be uppercase
while let Some(&c) = iter.next() {
if c == b'-' {
// advance iter by one and uppercase if needed
if let Some(c @ b'a'..=b'z') = iter.next() {
buffer[index] = c & 0b1101_1111;
}
}
while let Some(c) = key_iter.next() {
buffer[index] = *c;
index += 1;
if *c == b'-' {
if let Some(c) = key_iter.next() {
if *c >= b'a' && *c <= b'z' {
buffer[index] = *c ^ b' ';
index += 1;
}
}
}
}
}
@ -604,6 +603,8 @@ mod tests {
);
let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
eprintln!("{}", &data);
assert!(data.contains("Content-Length: 0\r\n"));
assert!(data.contains("Connection: close\r\n"));
assert!(data.contains("Content-Type: plain/text\r\n"));

View File

@ -8,11 +8,10 @@ use crate::request::Request;
pub struct ExpectHandler;
impl ServiceFactory for ExpectHandler {
type Config = ();
type Request = Request;
impl ServiceFactory<Request> for ExpectHandler {
type Response = Request;
type Error = Error;
type Config = ();
type Service = ExpectHandler;
type InitError = Error;
type Future = Ready<Result<Self::Service, Self::InitError>>;
@ -22,8 +21,7 @@ impl ServiceFactory for ExpectHandler {
}
}
impl Service for ExpectHandler {
type Request = Request;
impl Service<Request> for ExpectHandler {
type Response = Request;
type Error = Error;
type Future = Ready<Result<Self::Response, Self::Error>>;

View File

@ -17,7 +17,7 @@ pub use self::codec::Codec;
pub use self::dispatcher::Dispatcher;
pub use self::expect::ExpectHandler;
pub use self::payload::Payload;
pub use self::service::{H1Service, H1ServiceHandler, OneRequest};
pub use self::service::{H1Service, H1ServiceHandler};
pub use self::upgrade::UpgradeHandler;
pub use self::utils::SendResponse;

View File

@ -1,3 +1,4 @@
use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
@ -9,42 +10,40 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_rt::net::TcpStream;
use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
use futures_core::ready;
use futures_util::future::{ok, Ready};
use futures_util::future::ready;
use crate::body::MessageBody;
use crate::cloneable::CloneableService;
use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error, ParseError};
use crate::helpers::DataFactory;
use crate::error::{DispatchError, Error};
use crate::request::Request;
use crate::response::Response;
use crate::{ConnectCallback, Extensions};
use crate::service::HttpFlow;
use crate::{ConnectCallback, OnConnectData};
use super::codec::Codec;
use super::dispatcher::Dispatcher;
use super::{ExpectHandler, Message, UpgradeHandler};
use super::{ExpectHandler, UpgradeHandler};
/// `ServiceFactory` implementation for HTTP1 transport
pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler<T>> {
pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> {
srv: S,
cfg: ServiceConfig,
expect: X,
upgrade: Option<U>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_t: PhantomData<(T, B)>,
_phantom: PhantomData<B>,
}
impl<T, S, B> H1Service<T, S, B>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
{
/// Create new `HttpService` instance with config.
pub(crate) fn with_config<F: IntoServiceFactory<S>>(
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
cfg: ServiceConfig,
service: F,
) -> Self {
@ -53,28 +52,23 @@ where
srv: service.into_factory(),
expect: ExpectHandler,
upgrade: None,
on_connect: None,
on_connect_ext: None,
_t: PhantomData,
_phantom: PhantomData,
}
}
}
impl<S, B, X, U> H1Service<TcpStream, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<TcpStream, Codec>),
Response = (),
>,
U: ServiceFactory<(Request, Framed<TcpStream, Codec>), Config = (), Response = ()>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
{
@ -82,15 +76,15 @@ where
pub fn tcp(
self,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Request = TcpStream,
Response = (),
Error = DispatchError,
InitError = (),
> {
pipeline_factory(|io: TcpStream| {
let peer_addr = io.peer_addr().ok();
ok((io, peer_addr))
ready(Ok((io, peer_addr)))
})
.and_then(self)
}
@ -100,22 +94,23 @@ where
mod openssl {
use super::*;
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
use actix_tls::{openssl::HandshakeError, TlsError};
use actix_service::ServiceFactoryExt;
use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream};
use actix_tls::accept::TlsError;
impl<S, B, X, U> H1Service<SslStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<
(Request, Framed<SslStream<TcpStream>, Codec>),
Config = (),
Request = (Request, Framed<SslStream<TcpStream>, Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
@ -126,10 +121,10 @@ mod openssl {
self,
acceptor: SslAcceptor,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Request = TcpStream,
Response = (),
Error = TlsError<HandshakeError<TcpStream>, DispatchError>,
Error = TlsError<SslError, DispatchError>,
InitError = (),
> {
pipeline_factory(
@ -139,7 +134,7 @@ mod openssl {
)
.and_then(|io: SslStream<TcpStream>| {
let peer_addr = io.get_ref().peer_addr().ok();
ok((io, peer_addr))
ready(Ok((io, peer_addr)))
})
.and_then(self.map_err(TlsError::Service))
}
@ -149,23 +144,24 @@ mod openssl {
#[cfg(feature = "rustls")]
mod rustls {
use super::*;
use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream};
use actix_tls::TlsError;
use actix_service::ServiceFactoryExt;
use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream};
use actix_tls::accept::TlsError;
use std::{fmt, io};
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<
(Request, Framed<TlsStream<TcpStream>, Codec>),
Config = (),
Request = (Request, Framed<TlsStream<TcpStream>, Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
@ -176,8 +172,8 @@ mod rustls {
self,
config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Request = TcpStream,
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = (),
@ -189,7 +185,7 @@ mod rustls {
)
.and_then(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
ok((io, peer_addr))
ready(Ok((io, peer_addr)))
})
.and_then(self.map_err(TlsError::Service))
}
@ -198,7 +194,7 @@ mod rustls {
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
S::InitError: fmt::Debug,
@ -206,7 +202,7 @@ where
{
pub fn expect<X1>(self, expect: X1) -> H1Service<T, S, B, X1, U>
where
X1: ServiceFactory<Request = Request, Response = Request>,
X1: ServiceFactory<Request, Response = Request>,
X1::Error: Into<Error>,
X1::InitError: fmt::Debug,
{
@ -215,15 +211,14 @@ where
cfg: self.cfg,
srv: self.srv,
upgrade: self.upgrade,
on_connect: self.on_connect,
on_connect_ext: self.on_connect_ext,
_t: PhantomData,
_phantom: PhantomData,
}
}
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> H1Service<T, S, B, X, U1>
where
U1: ServiceFactory<Request = (Request, Framed<T, Codec>), Response = ()>,
U1: ServiceFactory<(Request, Framed<T, Codec>), Response = ()>,
U1::Error: fmt::Display,
U1::InitError: fmt::Debug,
{
@ -232,21 +227,11 @@ where
cfg: self.cfg,
srv: self.srv,
expect: self.expect,
on_connect: self.on_connect,
on_connect_ext: self.on_connect_ext,
_t: PhantomData,
_phantom: PhantomData,
}
}
/// Set on connect callback.
pub(crate) fn on_connect(
mut self,
f: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
) -> Self {
self.on_connect = f;
self
}
/// Set on connect callback.
pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
self.on_connect_ext = f;
@ -254,27 +239,27 @@ where
}
}
impl<T, S, B, X, U> ServiceFactory for H1Service<T, S, B, X, U>
impl<T, S, B, X, U> ServiceFactory<(T, Option<net::SocketAddr>)>
for H1Service<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
S::InitError: fmt::Debug,
B: MessageBody,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<Config = (), Request = (Request, Framed<T, Codec>), Response = ()>,
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
{
type Config = ();
type Request = (T, Option<net::SocketAddr>);
type Response = ();
type Error = DispatchError;
type InitError = ();
type Config = ();
type Service = H1ServiceHandler<T, S::Service, B, X::Service, U::Service>;
type InitError = ();
type Future = H1ServiceResponse<T, S, B, X, U>;
fn new_service(&self, _: ()) -> Self::Future {
@ -284,10 +269,9 @@ where
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())),
expect: None,
upgrade: None,
on_connect: self.on_connect.clone(),
on_connect_ext: self.on_connect_ext.clone(),
cfg: Some(self.cfg.clone()),
_t: PhantomData,
_phantom: PhantomData,
}
}
}
@ -296,13 +280,13 @@ where
#[pin_project::pin_project]
pub struct H1ServiceResponse<T, S, B, X, U>
where
S: ServiceFactory<Request = Request>,
S: ServiceFactory<Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
X: ServiceFactory<Request = Request, Response = Request>,
X: ServiceFactory<Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<Request = (Request, Framed<T, Codec>), Response = ()>,
U: ServiceFactory<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
{
@ -314,24 +298,23 @@ where
fut_upg: Option<U::Future>,
expect: Option<X::Service>,
upgrade: Option<U::Service>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
cfg: Option<ServiceConfig>,
_t: PhantomData<(T, B)>,
_phantom: PhantomData<B>,
}
impl<T, S, B, X, U> Future for H1ServiceResponse<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Request = Request>,
S: ServiceFactory<Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
S::InitError: fmt::Debug,
B: MessageBody,
X: ServiceFactory<Request = Request, Response = Request>,
X: ServiceFactory<Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<Request = (Request, Framed<T, Codec>), Response = ()>,
U: ServiceFactory<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
{
@ -355,7 +338,7 @@ where
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
this = self.as_mut().project();
*this.upgrade = Some(upgrade);
this.fut_ex.set(None);
this.fut_upg.set(None);
}
let result = ready!(this
@ -371,7 +354,6 @@ where
service,
this.expect.take().unwrap(),
this.upgrade.take(),
this.on_connect.clone(),
this.on_connect_ext.clone(),
)
}))
@ -379,66 +361,65 @@ where
}
/// `Service` implementation for HTTP/1 transport
pub struct H1ServiceHandler<T, S: Service, B, X: Service, U: Service> {
srv: CloneableService<S>,
expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
pub struct H1ServiceHandler<T, S, B, X, U>
where
S: Service<Request>,
X: Service<Request>,
U: Service<(Request, Framed<T, Codec>)>,
{
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
cfg: ServiceConfig,
_t: PhantomData<(T, B)>,
_phantom: PhantomData<B>,
}
impl<T, S, B, X, U> H1ServiceHandler<T, S, B, X, U>
where
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
fn new(
cfg: ServiceConfig,
srv: S,
service: S,
expect: X,
upgrade: Option<U>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
) -> H1ServiceHandler<T, S, B, X, U> {
H1ServiceHandler {
srv: CloneableService::new(srv),
expect: CloneableService::new(expect),
upgrade: upgrade.map(CloneableService::new),
flow: HttpFlow::new(service, expect, upgrade),
cfg,
on_connect,
on_connect_ext,
_t: PhantomData,
_phantom: PhantomData,
}
}
}
impl<T, S, B, X, U> Service for H1ServiceHandler<T, S, B, X, U>
impl<T, S, B, X, U> Service<(T, Option<net::SocketAddr>)>
for H1ServiceHandler<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display + Into<Error>,
{
type Request = (T, Option<net::SocketAddr>);
type Response = ();
type Error = DispatchError;
type Future = Dispatcher<T, S, B, X, U>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let ready = self
let mut flow = self.flow.borrow_mut();
let ready = flow
.expect
.poll_ready(cx)
.map_err(|e| {
@ -448,8 +429,8 @@ where
})?
.is_ready();
let ready = self
.srv
let ready = flow
.service
.poll_ready(cx)
.map_err(|e| {
let e = e.into();
@ -459,7 +440,7 @@ where
.is_ready()
&& ready;
let ready = if let Some(ref mut upg) = self.upgrade {
let ready = if let Some(ref mut upg) = flow.upgrade {
upg.poll_ready(cx)
.map_err(|e| {
let e = e.into();
@ -479,124 +460,16 @@ where
}
}
fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
let mut connect_extensions = Extensions::new();
if let Some(ref handler) = self.on_connect_ext {
// run on_connect_ext callback, populating connect extensions
handler(&io, &mut connect_extensions);
}
fn call(&mut self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
let on_connect_data =
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
Dispatcher::new(
io,
self.cfg.clone(),
self.srv.clone(),
self.expect.clone(),
self.upgrade.clone(),
deprecated_on_connect,
connect_extensions,
self.flow.clone(),
on_connect_data,
addr,
)
}
}
/// `ServiceFactory` implementation for `OneRequestService` service
#[derive(Default)]
pub struct OneRequest<T> {
config: ServiceConfig,
_t: PhantomData<T>,
}
impl<T> OneRequest<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
/// Create new `H1SimpleService` instance.
pub fn new() -> Self {
OneRequest {
config: ServiceConfig::default(),
_t: PhantomData,
}
}
}
impl<T> ServiceFactory for OneRequest<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
type Config = ();
type Request = T;
type Response = (Request, Framed<T, Codec>);
type Error = ParseError;
type InitError = ();
type Service = OneRequestService<T>;
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future {
ok(OneRequestService {
_t: PhantomData,
config: self.config.clone(),
})
}
}
/// `Service` implementation for HTTP1 transport. Reads one request and returns
/// request and framed object.
pub struct OneRequestService<T> {
_t: PhantomData<T>,
config: ServiceConfig,
}
impl<T> Service for OneRequestService<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
type Request = T;
type Response = (Request, Framed<T, Codec>);
type Error = ParseError;
type Future = OneRequestServiceResponse<T>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Self::Request) -> Self::Future {
OneRequestServiceResponse {
framed: Some(Framed::new(req, Codec::new(self.config.clone()))),
}
}
}
#[doc(hidden)]
#[pin_project::pin_project]
pub struct OneRequestServiceResponse<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
#[pin]
framed: Option<Framed<T, Codec>>,
}
impl<T> Future for OneRequestServiceResponse<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
type Output = Result<(Request, Framed<T, Codec>), ParseError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.as_mut().project();
match ready!(this.framed.as_pin_mut().unwrap().next_item(cx)) {
Some(Ok(req)) => match req {
Message::Item(req) => {
let mut this = self.as_mut().project();
Poll::Ready(Ok((req, this.framed.take().unwrap())))
}
Message::Chunk(_) => unreachable!("Something is wrong"),
},
Some(Err(err)) => Poll::Ready(Err(err)),
None => Poll::Ready(Err(ParseError::Incomplete)),
}
}
}

View File

@ -1,4 +1,3 @@
use std::marker::PhantomData;
use std::task::{Context, Poll};
use actix_codec::Framed;
@ -9,14 +8,13 @@ use crate::error::Error;
use crate::h1::Codec;
use crate::request::Request;
pub struct UpgradeHandler<T>(pub(crate) PhantomData<T>);
pub struct UpgradeHandler;
impl<T> ServiceFactory for UpgradeHandler<T> {
type Config = ();
type Request = (Request, Framed<T, Codec>);
impl<T> ServiceFactory<(Request, Framed<T, Codec>)> for UpgradeHandler {
type Response = ();
type Error = Error;
type Service = UpgradeHandler<T>;
type Config = ();
type Service = UpgradeHandler;
type InitError = Error;
type Future = Ready<Result<Self::Service, Self::InitError>>;
@ -25,8 +23,7 @@ impl<T> ServiceFactory for UpgradeHandler<T> {
}
}
impl<T> Service for UpgradeHandler<T> {
type Request = (Request, Framed<T, Codec>);
impl<T> Service<(Request, Framed<T, Codec>)> for UpgradeHandler {
type Response = ();
type Error = Error;
type Future = Ready<Result<Self::Response, Self::Error>>;
@ -35,7 +32,7 @@ impl<T> Service for UpgradeHandler<T> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: Self::Request) -> Self::Future {
fn call(&mut self, _: (Request, Framed<T, Codec>)) -> Self::Future {
ready(Ok(()))
}
}

View File

@ -1,66 +1,66 @@
use std::convert::TryFrom;
use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData;
use std::net;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use std::{cmp, convert::TryFrom};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::time::{Delay, Instant};
use actix_rt::time::{Instant, Sleep};
use actix_service::Service;
use bytes::{Bytes, BytesMut};
use futures_core::ready;
use h2::server::{Connection, SendResponse};
use h2::SendStream;
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
use log::{error, trace};
use crate::body::{BodySize, MessageBody, ResponseBody};
use crate::cloneable::CloneableService;
use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error};
use crate::helpers::DataFactory;
use crate::httpmessage::HttpMessage;
use crate::message::ResponseHead;
use crate::payload::Payload;
use crate::request::Request;
use crate::response::Response;
use crate::Extensions;
use crate::service::HttpFlow;
use crate::OnConnectData;
const CHUNK_SIZE: usize = 16_384;
/// Dispatcher for HTTP/2 protocol
/// Dispatcher for HTTP/2 protocol.
#[pin_project::pin_project]
pub struct Dispatcher<T, S: Service<Request = Request>, B: MessageBody>
pub struct Dispatcher<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
B: MessageBody,
{
service: CloneableService<S>,
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
connection: Connection<T, Bytes>,
on_connect: Option<Box<dyn DataFactory>>,
on_connect_data: Extensions,
on_connect_data: OnConnectData,
config: ServiceConfig,
peer_addr: Option<net::SocketAddr>,
ka_expire: Instant,
ka_timer: Option<Delay>,
_t: PhantomData<B>,
ka_timer: Option<Sleep>,
_phantom: PhantomData<B>,
}
impl<T, S, B> Dispatcher<T, S, B>
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error>,
// S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody,
{
pub(crate) fn new(
service: CloneableService<S>,
services: Rc<RefCell<HttpFlow<S, X, U>>>,
connection: Connection<T, Bytes>,
on_connect: Option<Box<dyn DataFactory>>,
on_connect_data: Extensions,
on_connect_data: OnConnectData,
config: ServiceConfig,
timeout: Option<Delay>,
timeout: Option<Sleep>,
peer_addr: Option<net::SocketAddr>,
) -> Self {
// let keepalive = config.keep_alive_enabled();
@ -80,23 +80,22 @@ where
};
Dispatcher {
service,
flow: services,
config,
peer_addr,
connection,
on_connect,
on_connect_data,
ka_expire,
ka_timer,
_t: PhantomData,
_phantom: PhantomData,
}
}
}
impl<T, S, B> Future for Dispatcher<T, S, B>
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
@ -109,10 +108,12 @@ where
let this = self.get_mut();
loop {
match Pin::new(&mut this.connection).poll_accept(cx) {
Poll::Ready(None) => return Poll::Ready(Ok(())),
Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())),
Poll::Ready(Some(Ok((req, res)))) => {
match ready!(Pin::new(&mut this.connection).poll_accept(cx)) {
None => return Poll::Ready(Ok(())),
Some(Err(err)) => return Poll::Ready(Err(err.into())),
Some(Ok((req, res))) => {
// update keep-alive expire
if this.ka_timer.is_some() {
if let Some(expire) = this.config.keep_alive_expire() {
@ -121,11 +122,9 @@ where
}
let (parts, body) = req.into_parts();
let mut req = Request::with_payload(Payload::<
crate::payload::PayloadStream,
>::H2(
crate::h2::Payload::new(body)
));
let pl = crate::h2::Payload::new(body);
let pl = Payload::<crate::payload::PayloadStream>::H2(pl);
let mut req = Request::with_payload(pl);
let head = &mut req.head_mut();
head.uri = parts.uri;
@ -134,31 +133,21 @@ where
head.headers = parts.headers.into();
head.peer_addr = this.peer_addr;
// DEPRECATED
// set on_connect data
if let Some(ref on_connect) = this.on_connect {
on_connect.set(&mut req.extensions_mut());
}
// merge on_connect_ext data into request extensions
req.extensions_mut().drain_from(&mut this.on_connect_data);
this.on_connect_data.merge_into(&mut req);
actix_rt::spawn(ServiceResponse::<
S::Future,
S::Response,
S::Error,
B,
> {
let svc = ServiceResponse::<S::Future, S::Response, S::Error, B> {
state: ServiceResponseState::ServiceCall(
this.service.call(req),
this.flow.borrow_mut().service.call(req),
Some(res),
),
config: this.config.clone(),
buffer: None,
_t: PhantomData,
});
_phantom: PhantomData,
};
actix_rt::spawn(svc);
}
Poll::Pending => return Poll::Pending,
}
}
}
@ -170,7 +159,7 @@ struct ServiceResponse<F, I, E, B> {
state: ServiceResponseState<F, B>,
config: ServiceConfig,
buffer: Option<Bytes>,
_t: PhantomData<(I, E)>,
_phantom: PhantomData<(I, E)>,
}
#[pin_project::pin_project(project = ServiceResponseStateProj)]
@ -207,8 +196,9 @@ where
skip_len = true;
*size = BodySize::Stream;
}
_ => (),
_ => {}
}
let _ = match size {
BodySize::None | BodySize::Stream => None,
BodySize::Empty => res
@ -223,11 +213,13 @@ where
// copy headers
for (key, value) in head.headers.iter() {
match *key {
CONNECTION | TRANSFER_ENCODING => continue, // http2 specific
// omit HTTP/1 only headers
CONNECTION | TRANSFER_ENCODING => continue,
CONTENT_LENGTH if skip_len => continue,
DATE => has_date = true,
_ => (),
_ => {}
}
res.headers_mut().append(key, value.clone());
}
@ -259,18 +251,20 @@ where
let mut this = self.as_mut().project();
match this.state.project() {
ServiceResponseStateProj::ServiceCall(call, send) => match call.poll(cx) {
Poll::Ready(Ok(res)) => {
ServiceResponseStateProj::ServiceCall(call, send) => {
match ready!(call.poll(cx)) {
Ok(res) => {
let (res, body) = res.into().replace_body(());
let mut send = send.take().unwrap();
let mut size = body.size();
let h2_res = self.as_mut().prepare_response(res.head(), &mut size);
let h2_res =
self.as_mut().prepare_response(res.head(), &mut size);
this = self.as_mut().project();
let stream = match send.send_response(h2_res, size.is_eof()) {
Err(e) => {
trace!("Error sending h2 response: {:?}", e);
trace!("Error sending HTTP/2 response: {:?}", e);
return Poll::Ready(());
}
Ok(stream) => stream,
@ -284,19 +278,20 @@ where
self.poll(cx)
}
}
Poll::Pending => Poll::Pending,
Poll::Ready(Err(e)) => {
Err(e) => {
let res: Response = e.into().into();
let (res, body) = res.replace_body(());
let mut send = send.take().unwrap();
let mut size = body.size();
let h2_res = self.as_mut().prepare_response(res.head(), &mut size);
let h2_res =
self.as_mut().prepare_response(res.head(), &mut size);
this = self.as_mut().project();
let stream = match send.send_response(h2_res, size.is_eof()) {
Err(e) => {
trace!("Error sending h2 response: {:?}", e);
trace!("Error sending HTTP/2 response: {:?}", e);
return Poll::Ready(());
}
Ok(stream) => stream,
@ -312,56 +307,61 @@ where
self.poll(cx)
}
}
},
}
}
ServiceResponseStateProj::SendPayload(ref mut stream, ref mut body) => {
loop {
loop {
if let Some(ref mut buffer) = this.buffer {
match stream.poll_capacity(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(None) => return Poll::Ready(()),
Poll::Ready(Some(Ok(cap))) => {
match this.buffer {
Some(ref mut buffer) => {
match ready!(stream.poll_capacity(cx)) {
None => return Poll::Ready(()),
Some(Ok(cap)) => {
let len = buffer.len();
let bytes = buffer.split_to(std::cmp::min(cap, len));
let bytes = buffer.split_to(cmp::min(cap, len));
if let Err(e) = stream.send_data(bytes, false) {
warn!("{:?}", e);
return Poll::Ready(());
} else if !buffer.is_empty() {
let cap =
std::cmp::min(buffer.len(), CHUNK_SIZE);
let cap = cmp::min(buffer.len(), CHUNK_SIZE);
stream.reserve_capacity(cap);
} else {
this.buffer.take();
}
}
Poll::Ready(Some(Err(e))) => {
Some(Err(e)) => {
warn!("{:?}", e);
return Poll::Ready(());
}
}
} else {
match body.as_mut().poll_next(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(None) => {
}
None => match ready!(body.as_mut().poll_next(cx)) {
None => {
if let Err(e) = stream.send_data(Bytes::new(), true)
{
warn!("{:?}", e);
}
return Poll::Ready(());
}
Poll::Ready(Some(Ok(chunk))) => {
stream.reserve_capacity(std::cmp::min(
Some(Ok(chunk)) => {
stream.reserve_capacity(cmp::min(
chunk.len(),
CHUNK_SIZE,
));
*this.buffer = Some(chunk);
}
Poll::Ready(Some(Err(e))) => {
Some(Err(e)) => {
error!("Response payload stream error: {:?}", e);
return Poll::Ready(());
}
}
},
}
}
}

View File

@ -1,9 +1,12 @@
//! HTTP/2 implementation
use std::pin::Pin;
use std::task::{Context, Poll};
//! HTTP/2 implementation.
use std::{
pin::Pin,
task::{Context, Poll},
};
use bytes::Bytes;
use futures_core::Stream;
use futures_core::{ready, Stream};
use h2::RecvStream;
mod dispatcher;
@ -13,14 +16,14 @@ pub use self::dispatcher::Dispatcher;
pub use self::service::H2Service;
use crate::error::PayloadError;
/// H2 receive stream
/// HTTP/2 peer stream.
pub struct Payload {
pl: RecvStream,
stream: RecvStream,
}
impl Payload {
pub(crate) fn new(pl: RecvStream) -> Self {
Self { pl }
pub(crate) fn new(stream: RecvStream) -> Self {
Self { stream }
}
}
@ -33,18 +36,17 @@ impl Stream for Payload {
) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
match Pin::new(&mut this.pl).poll_data(cx) {
Poll::Ready(Some(Ok(chunk))) => {
match ready!(Pin::new(&mut this.stream).poll_data(cx)) {
Some(Ok(chunk)) => {
let len = chunk.len();
if let Err(err) = this.pl.flow_control().release_capacity(len) {
Poll::Ready(Some(Err(err.into())))
} else {
Poll::Ready(Some(Ok(chunk)))
match this.stream.flow_control().release_capacity(len) {
Ok(()) => Poll::Ready(Some(Ok(chunk))),
Err(err) => Poll::Ready(Some(Err(err.into()))),
}
}
Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err.into()))),
Poll::Pending => Poll::Pending,
Poll::Ready(None) => Poll::Ready(None),
Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
None => Poll::Ready(None),
}
}
}

View File

@ -1,3 +1,4 @@
use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
@ -17,57 +18,44 @@ use h2::server::{self, Handshake};
use log::error;
use crate::body::MessageBody;
use crate::cloneable::CloneableService;
use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error};
use crate::helpers::DataFactory;
use crate::request::Request;
use crate::response::Response;
use crate::{ConnectCallback, Extensions};
use crate::service::HttpFlow;
use crate::{ConnectCallback, OnConnectData};
use super::dispatcher::Dispatcher;
/// `ServiceFactory` implementation for HTTP2 transport
/// `ServiceFactory` implementation for HTTP/2 transport
pub struct H2Service<T, S, B> {
srv: S,
cfg: ServiceConfig,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_t: PhantomData<(T, B)>,
_phantom: PhantomData<(T, B)>,
}
impl<T, S, B> H2Service<T, S, B>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
/// Create new `HttpService` instance with config.
pub(crate) fn with_config<F: IntoServiceFactory<S>>(
/// Create new `H2Service` instance with config.
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
cfg: ServiceConfig,
service: F,
) -> Self {
H2Service {
cfg,
on_connect: None,
on_connect_ext: None,
srv: service.into_factory(),
_t: PhantomData,
_phantom: PhantomData,
}
}
/// Set on connect callback.
pub(crate) fn on_connect(
mut self,
f: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
) -> Self {
self.on_connect = f;
self
}
/// Set on connect callback.
pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
self.on_connect_ext = f;
@ -77,18 +65,18 @@ where
impl<S, B> H2Service<TcpStream, S, B>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
/// Create simple tcp based service
/// Create plain TCP based service
pub fn tcp(
self,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Request = TcpStream,
Response = (),
Error = DispatchError,
InitError = S::InitError,
@ -105,29 +93,29 @@ where
#[cfg(feature = "openssl")]
mod openssl {
use actix_service::{fn_factory, fn_service};
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
use actix_tls::{openssl::HandshakeError, TlsError};
use actix_service::{fn_factory, fn_service, ServiceFactoryExt};
use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream};
use actix_tls::accept::TlsError;
use super::*;
impl<S, B> H2Service<SslStream<TcpStream>, S, B>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
/// Create ssl based service
/// Create OpenSSL based service
pub fn openssl(
self,
acceptor: SslAcceptor,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Request = TcpStream,
Response = (),
Error = TlsError<HandshakeError<TcpStream>, DispatchError>,
Error = TlsError<SslError, DispatchError>,
InitError = S::InitError,
> {
pipeline_factory(
@ -149,25 +137,26 @@ mod openssl {
#[cfg(feature = "rustls")]
mod rustls {
use super::*;
use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream};
use actix_tls::TlsError;
use actix_service::ServiceFactoryExt;
use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream};
use actix_tls::accept::TlsError;
use std::io;
impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
/// Create openssl based service
/// Create Rustls based service
pub fn rustls(
self,
mut config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Request = TcpStream,
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = S::InitError,
@ -191,52 +180,52 @@ mod rustls {
}
}
impl<T, S, B> ServiceFactory for H2Service<T, S, B>
impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
type Config = ();
type Request = (T, Option<net::SocketAddr>);
type Response = ();
type Error = DispatchError;
type InitError = S::InitError;
type Config = ();
type Service = H2ServiceHandler<T, S::Service, B>;
type InitError = S::InitError;
type Future = H2ServiceResponse<T, S, B>;
fn new_service(&self, _: ()) -> Self::Future {
H2ServiceResponse {
fut: self.srv.new_service(()),
cfg: Some(self.cfg.clone()),
on_connect: self.on_connect.clone(),
on_connect_ext: self.on_connect_ext.clone(),
_t: PhantomData,
_phantom: PhantomData,
}
}
}
#[doc(hidden)]
#[pin_project::pin_project]
pub struct H2ServiceResponse<T, S: ServiceFactory, B> {
pub struct H2ServiceResponse<T, S, B>
where
S: ServiceFactory<Request>,
{
#[pin]
fut: S::Future,
cfg: Option<ServiceConfig>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_t: PhantomData<(T, B)>,
_phantom: PhantomData<B>,
}
impl<T, S, B> Future for H2ServiceResponse<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
type Output = Result<H2ServiceHandler<T, S::Service, B>, S::InitError>;
@ -244,30 +233,31 @@ where
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.as_mut().project();
Poll::Ready(ready!(this.fut.poll(cx)).map(|service| {
this.fut.poll(cx).map_ok(|service| {
let this = self.as_mut().project();
H2ServiceHandler::new(
this.cfg.take().unwrap(),
this.on_connect.clone(),
this.on_connect_ext.clone(),
service,
)
}))
})
}
}
/// `Service` implementation for http/2 transport
pub struct H2ServiceHandler<T, S: Service, B> {
srv: CloneableService<S>,
pub struct H2ServiceHandler<T, S, B>
where
S: Service<Request>,
{
flow: Rc<RefCell<HttpFlow<S, (), ()>>>,
cfg: ServiceConfig,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_t: PhantomData<(T, B)>,
_phantom: PhantomData<B>,
}
impl<T, S, B> H2ServiceHandler<T, S, B>
where
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
@ -275,76 +265,66 @@ where
{
fn new(
cfg: ServiceConfig,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
srv: S,
service: S,
) -> H2ServiceHandler<T, S, B> {
H2ServiceHandler {
flow: HttpFlow::new(service, (), None),
cfg,
on_connect,
on_connect_ext,
srv: CloneableService::new(srv),
_t: PhantomData,
_phantom: PhantomData,
}
}
}
impl<T, S, B> Service for H2ServiceHandler<T, S, B>
impl<T, S, B> Service<(T, Option<net::SocketAddr>)> for H2ServiceHandler<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
{
type Request = (T, Option<net::SocketAddr>);
type Response = ();
type Error = DispatchError;
type Future = H2ServiceHandlerResponse<T, S, B>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.srv.poll_ready(cx).map_err(|e| {
self.flow.borrow_mut().service.poll_ready(cx).map_err(|e| {
let e = e.into();
error!("Service readiness error: {:?}", e);
DispatchError::Service(e)
})
}
fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
let mut connect_extensions = Extensions::new();
if let Some(ref handler) = self.on_connect_ext {
// run on_connect_ext callback, populating connect extensions
handler(&io, &mut connect_extensions);
}
fn call(&mut self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
let on_connect_data =
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
H2ServiceHandlerResponse {
state: State::Handshake(
Some(self.srv.clone()),
Some(self.flow.clone()),
Some(self.cfg.clone()),
addr,
deprecated_on_connect,
Some(connect_extensions),
on_connect_data,
server::handshake(io),
),
}
}
}
enum State<T, S: Service<Request = Request>, B: MessageBody>
enum State<T, S: Service<Request>, B: MessageBody>
where
T: AsyncRead + AsyncWrite + Unpin,
S::Future: 'static,
{
Incoming(Dispatcher<T, S, B>),
Incoming(Dispatcher<T, S, B, (), ()>),
Handshake(
Option<CloneableService<S>>,
Option<Rc<RefCell<HttpFlow<S, (), ()>>>>,
Option<ServiceConfig>,
Option<net::SocketAddr>,
Option<Box<dyn DataFactory>>,
Option<Extensions>,
OnConnectData,
Handshake<T, Bytes>,
),
}
@ -352,7 +332,7 @@ where
pub struct H2ServiceHandlerResponse<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
@ -364,7 +344,7 @@ where
impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
@ -379,27 +359,25 @@ where
ref mut srv,
ref mut config,
ref peer_addr,
ref mut on_connect,
ref mut on_connect_data,
ref mut handshake,
) => match Pin::new(handshake).poll(cx) {
Poll::Ready(Ok(conn)) => {
) => match ready!(Pin::new(handshake).poll(cx)) {
Ok(conn) => {
let on_connect_data = std::mem::take(on_connect_data);
self.state = State::Incoming(Dispatcher::new(
srv.take().unwrap(),
conn,
on_connect.take(),
on_connect_data.take().unwrap(),
on_connect_data,
config.take().unwrap(),
None,
*peer_addr,
));
self.poll(cx)
}
Poll::Ready(Err(err)) => {
Err(err) => {
trace!("H2 handshake error: {}", err);
Poll::Ready(Err(err.into()))
}
Poll::Pending => Poll::Pending,
},
}
}

View File

@ -318,9 +318,8 @@ impl ContentDisposition {
return Err(crate::error::ParseError::Header);
}
left = new_left;
if param_name.ends_with('*') {
if let Some(param_name) = param_name.strip_suffix('*') {
// extended parameters
let param_name = &param_name[..param_name.len() - 1]; // trim asterisk
let (ext_value, new_left) = split_once_and_trim(left, ';');
left = new_left;
let ext_value = header::parse_extended_value(ext_value)?;

View File

@ -3,7 +3,8 @@ use std::io::Write;
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};
use bytes::{buf::BufMutExt, BytesMut};
use bytes::buf::BufMut;
use bytes::BytesMut;
use http::header::{HeaderValue, InvalidHeaderValue};
use time::{offset, OffsetDateTime, PrimitiveDateTime};

View File

@ -3,8 +3,6 @@ use std::io;
use bytes::{BufMut, BytesMut};
use http::Version;
use crate::extensions::Extensions;
const DIGITS_START: u8 = b'0';
pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) {
@ -56,18 +54,6 @@ impl<'a> io::Write for Writer<'a> {
}
}
pub(crate) trait DataFactory {
fn set(&self, ext: &mut Extensions);
}
pub(crate) struct Data<T>(pub(crate) T);
impl<T: Clone + 'static> DataFactory for Data<T> {
fn set(&self, ext: &mut Extensions) {
ext.insert(self.0.clone())
}
}
#[cfg(test)]
mod tests {
use std::str::from_utf8;

View File

@ -7,7 +7,6 @@
clippy::new_without_default,
clippy::borrow_interior_mutable_const
)]
#![allow(clippy::manual_strip)] // Allow this to keep MSRV(1.42).
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
@ -20,7 +19,6 @@ mod macros;
pub mod body;
mod builder;
pub mod client;
mod cloneable;
mod config;
#[cfg(feature = "compress")]
pub mod encoding;
@ -74,11 +72,49 @@ pub mod http {
pub use crate::message::ConnectionType;
}
/// Http protocol
/// A major HTTP protocol version.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Protocol {
Http1,
Http2,
Http3,
}
type ConnectCallback<IO> = dyn Fn(&IO, &mut Extensions);
/// Container for data that extract with ConnectCallback.
///
/// # Implementation Details
/// Uses Option to reduce necessary allocations when merging with request extensions.
pub(crate) struct OnConnectData(Option<Extensions>);
impl Default for OnConnectData {
fn default() -> Self {
Self(None)
}
}
impl OnConnectData {
/// Construct by calling the on-connect callback with the underlying transport I/O.
pub(crate) fn from_io<T>(
io: &T,
on_connect_ext: Option<&ConnectCallback<T>>,
) -> Self {
let ext = on_connect_ext.map(|handler| {
let mut extensions = Extensions::new();
handler(io, &mut extensions);
extensions
});
Self(ext)
}
/// Merge self into given request's extensions.
#[inline]
pub(crate) fn merge_into(&mut self, req: &mut Request) {
if let Some(ref mut ext) = self.0 {
req.head.extensions.get_mut().drain_from(ext);
}
}
}

View File

@ -67,7 +67,7 @@ impl Head for RequestHead {
fn clear(&mut self) {
self.flags = Flags::empty();
self.headers.clear();
self.extensions.borrow_mut().clear();
self.extensions.get_mut().clear();
}
fn pool() -> &'static MessagePool<Self> {
@ -440,9 +440,11 @@ impl<T: Head> MessagePool<T> {
#[inline]
fn get_message(&'static self) -> Message<T> {
if let Some(mut msg) = self.0.borrow_mut().pop() {
if let Some(r) = Rc::get_mut(&mut msg) {
r.clear();
}
// Message is put in pool only when it's the last copy.
// which means it's guaranteed to be unique when popped out.
Rc::get_mut(&mut msg)
.expect("Multiple copies exist")
.clear();
Message { head: msg }
} else {
Message {

View File

@ -23,6 +23,10 @@ impl<P> HttpMessage for Request<P> {
&self.head().headers
}
fn take_payload(&mut self) -> Payload<P> {
std::mem::replace(&mut self.payload, Payload::None)
}
/// Request extensions
#[inline]
fn extensions(&self) -> Ref<'_, Extensions> {
@ -34,10 +38,6 @@ impl<P> HttpMessage for Request<P> {
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.head.extensions_mut()
}
fn take_payload(&mut self) -> Payload<P> {
std::mem::replace(&mut self.payload, Payload::None)
}
}
impl From<Message<RequestHead>> for Request<PayloadStream> {

View File

@ -1,3 +1,4 @@
use std::cell::RefCell;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
@ -8,39 +9,34 @@ use actix_rt::net::TcpStream;
use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
use bytes::Bytes;
use futures_core::{ready, Future};
use futures_util::future::ok;
use h2::server::{self, Handshake};
use pin_project::pin_project;
use crate::body::MessageBody;
use crate::builder::HttpServiceBuilder;
use crate::cloneable::CloneableService;
use crate::config::{KeepAlive, ServiceConfig};
use crate::error::{DispatchError, Error};
use crate::helpers::DataFactory;
use crate::request::Request;
use crate::response::Response;
use crate::{h1, h2::Dispatcher, ConnectCallback, Extensions, Protocol};
use crate::{h1, h2::Dispatcher, ConnectCallback, OnConnectData, Protocol};
/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol.
pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler<T>> {
pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler> {
srv: S,
cfg: ServiceConfig,
expect: X,
upgrade: Option<U>,
// DEPRECATED: in favor of on_connect_ext
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_t: PhantomData<(T, B)>,
_phantom: PhantomData<B>,
}
impl<T, S, B> HttpService<T, S, B>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
/// Create builder for `HttpService` instance.
@ -51,15 +47,15 @@ where
impl<T, S, B> HttpService<T, S, B>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
/// Create new `HttpService` instance.
pub fn new<F: IntoServiceFactory<S>>(service: F) -> Self {
pub fn new<F: IntoServiceFactory<S, Request>>(service: F) -> Self {
let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0, false, None);
HttpService {
@ -67,14 +63,13 @@ where
srv: service.into_factory(),
expect: h1::ExpectHandler,
upgrade: None,
on_connect: None,
on_connect_ext: None,
_t: PhantomData,
_phantom: PhantomData,
}
}
/// Create new `HttpService` instance with config.
pub(crate) fn with_config<F: IntoServiceFactory<S>>(
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
cfg: ServiceConfig,
service: F,
) -> Self {
@ -83,20 +78,19 @@ where
srv: service.into_factory(),
expect: h1::ExpectHandler,
upgrade: None,
on_connect: None,
on_connect_ext: None,
_t: PhantomData,
_phantom: PhantomData,
}
}
}
impl<T, S, B, X, U> HttpService<T, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody,
{
/// Provide service for `EXPECT: 100-Continue` support.
@ -106,19 +100,18 @@ where
/// request will be forwarded to main service.
pub fn expect<X1>(self, expect: X1) -> HttpService<T, S, B, X1, U>
where
X1: ServiceFactory<Config = (), Request = Request, Response = Request>,
X1: ServiceFactory<Request, Config = (), Response = Request>,
X1::Error: Into<Error>,
X1::InitError: fmt::Debug,
<X1::Service as Service>::Future: 'static,
<X1::Service as Service<Request>>::Future: 'static,
{
HttpService {
expect,
cfg: self.cfg,
srv: self.srv,
upgrade: self.upgrade,
on_connect: self.on_connect,
on_connect_ext: self.on_connect_ext,
_t: PhantomData,
_phantom: PhantomData,
}
}
@ -128,35 +121,21 @@ where
/// and this service get called with original request and framed object.
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> HttpService<T, S, B, X, U1>
where
U1: ServiceFactory<
Config = (),
Request = (Request, Framed<T, h1::Codec>),
Response = (),
>,
U1: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
U1::Error: fmt::Display,
U1::InitError: fmt::Debug,
<U1::Service as Service>::Future: 'static,
<U1::Service as Service<(Request, Framed<T, h1::Codec>)>>::Future: 'static,
{
HttpService {
upgrade,
cfg: self.cfg,
srv: self.srv,
expect: self.expect,
on_connect: self.on_connect,
on_connect_ext: self.on_connect_ext,
_t: PhantomData,
_phantom: PhantomData,
}
}
/// Set on connect callback.
pub(crate) fn on_connect(
mut self,
f: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
) -> Self {
self.on_connect = f;
self
}
/// Set connect callback with mutable access to request data container.
pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
self.on_connect_ext = f;
@ -166,38 +145,38 @@ where
impl<S, B, X, U> HttpService<TcpStream, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static,
<X::Service as Service<Request>>::Future: 'static,
U: ServiceFactory<
(Request, Framed<TcpStream, h1::Codec>),
Config = (),
Request = (Request, Framed<TcpStream, h1::Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
<U::Service as Service<(Request, Framed<TcpStream, h1::Codec>)>>::Future: 'static,
{
/// Create simple tcp stream service
pub fn tcp(
self,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Request = TcpStream,
Response = (),
Error = DispatchError,
InitError = (),
> {
pipeline_factory(|io: TcpStream| {
pipeline_factory(|io: TcpStream| async {
let peer_addr = io.peer_addr().ok();
ok((io, Protocol::Http1, peer_addr))
Ok((io, Protocol::Http1, peer_addr))
})
.and_then(self)
}
@ -206,39 +185,40 @@ where
#[cfg(feature = "openssl")]
mod openssl {
use super::*;
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
use actix_tls::{openssl::HandshakeError, TlsError};
use actix_service::ServiceFactoryExt;
use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream};
use actix_tls::accept::TlsError;
impl<S, B, X, U> HttpService<SslStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static,
<X::Service as Service<Request>>::Future: 'static,
U: ServiceFactory<
(Request, Framed<SslStream<TcpStream>, h1::Codec>),
Config = (),
Request = (Request, Framed<SslStream<TcpStream>, h1::Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
<U::Service as Service<(Request, Framed<SslStream<TcpStream>, h1::Codec>)>>::Future: 'static,
{
/// Create openssl based service
pub fn openssl(
self,
acceptor: SslAcceptor,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Request = TcpStream,
Response = (),
Error = TlsError<HandshakeError<TcpStream>, DispatchError>,
Error = TlsError<SslError, DispatchError>,
InitError = (),
> {
pipeline_factory(
@ -246,7 +226,7 @@ mod openssl {
.map_err(TlsError::Tls)
.map_init_err(|_| panic!()),
)
.and_then(|io: SslStream<TcpStream>| {
.and_then(|io: SslStream<TcpStream>| async {
let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() {
if protos.windows(2).any(|window| window == b"h2") {
Protocol::Http2
@ -257,7 +237,7 @@ mod openssl {
Protocol::Http1
};
let peer_addr = io.get_ref().peer_addr().ok();
ok((io, proto, peer_addr))
Ok((io, proto, peer_addr))
})
.and_then(self.map_err(TlsError::Service))
}
@ -266,39 +246,42 @@ mod openssl {
#[cfg(feature = "rustls")]
mod rustls {
use super::*;
use actix_tls::rustls::{Acceptor, ServerConfig, Session, TlsStream};
use actix_tls::TlsError;
use std::io;
use actix_tls::accept::rustls::{Acceptor, ServerConfig, Session, TlsStream};
use actix_tls::accept::TlsError;
use super::*;
use actix_service::ServiceFactoryExt;
impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static,
<X::Service as Service<Request>>::Future: 'static,
U: ServiceFactory<
(Request, Framed<TlsStream<TcpStream>, h1::Codec>),
Config = (),
Request = (Request, Framed<TlsStream<TcpStream>, h1::Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
<U::Service as Service<(Request, Framed<TlsStream<TcpStream>, h1::Codec>)>>::Future: 'static,
{
/// Create openssl based service
pub fn rustls(
self,
mut config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Request = TcpStream,
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = (),
@ -311,7 +294,7 @@ mod rustls {
.map_err(TlsError::Tls)
.map_init_err(|_| panic!()),
)
.and_then(|io: TlsStream<TcpStream>| {
.and_then(|io: TlsStream<TcpStream>| async {
let proto = if let Some(protos) = io.get_ref().1.get_alpn_protocol() {
if protos.windows(2).any(|window| window == b"h2") {
Protocol::Http2
@ -322,41 +305,37 @@ mod rustls {
Protocol::Http1
};
let peer_addr = io.get_ref().0.peer_addr().ok();
ok((io, proto, peer_addr))
Ok((io, proto, peer_addr))
})
.and_then(self.map_err(TlsError::Service))
}
}
}
impl<T, S, B, X, U> ServiceFactory for HttpService<T, S, B, X, U>
impl<T, S, B, X, U> ServiceFactory<(T, Protocol, Option<net::SocketAddr>)>
for HttpService<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<T, h1::Codec>),
Response = (),
>,
<X::Service as Service<Request>>::Future: 'static,
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
<U::Service as Service<(Request, Framed<T, h1::Codec>)>>::Future: 'static,
{
type Config = ();
type Request = (T, Protocol, Option<net::SocketAddr>);
type Response = ();
type Error = DispatchError;
type InitError = ();
type Config = ();
type Service = HttpServiceHandler<T, S::Service, B, X::Service, U::Service>;
type InitError = ();
type Future = HttpServiceResponse<T, S, B, X, U>;
fn new_service(&self, _: ()) -> Self::Future {
@ -366,23 +345,21 @@ where
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())),
expect: None,
upgrade: None,
on_connect: self.on_connect.clone(),
on_connect_ext: self.on_connect_ext.clone(),
cfg: self.cfg.clone(),
_t: PhantomData,
_phantom: PhantomData,
}
}
}
#[doc(hidden)]
#[pin_project]
pub struct HttpServiceResponse<
T,
S: ServiceFactory,
B,
X: ServiceFactory,
U: ServiceFactory,
> {
pub struct HttpServiceResponse<T, S, B, X, U>
where
S: ServiceFactory<Request>,
X: ServiceFactory<Request>,
U: ServiceFactory<(Request, Framed<T, h1::Codec>)>,
{
#[pin]
fut: S::Future,
#[pin]
@ -391,29 +368,28 @@ pub struct HttpServiceResponse<
fut_upg: Option<U::Future>,
expect: Option<X::Service>,
upgrade: Option<U::Service>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
cfg: ServiceConfig,
_t: PhantomData<(T, B)>,
_phantom: PhantomData<B>,
}
impl<T, S, B, X, U> Future for HttpServiceResponse<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Request = Request>,
S: ServiceFactory<Request>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Request = Request, Response = Request>,
X: ServiceFactory<Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static,
U: ServiceFactory<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
<X::Service as Service<Request>>::Future: 'static,
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
<U::Service as Service<(Request, Framed<T, h1::Codec>)>>::Future: 'static,
{
type Output =
Result<HttpServiceHandler<T, S::Service, B, X::Service, U::Service>, ()>;
@ -436,7 +412,7 @@ where
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
this = self.as_mut().project();
*this.upgrade = Some(upgrade);
this.fut_ex.set(None);
this.fut_upg.set(None);
}
let result = ready!(this
@ -451,7 +427,6 @@ where
service,
this.expect.take().unwrap(),
this.upgrade.take(),
this.on_connect.clone(),
this.on_connect_ext.clone(),
)
}))
@ -459,68 +434,84 @@ where
}
/// `Service` implementation for http transport
pub struct HttpServiceHandler<T, S: Service, B, X: Service, U: Service> {
srv: CloneableService<S>,
expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>,
pub struct HttpServiceHandler<T, S, B, X, U>
where
S: Service<Request>,
X: Service<Request>,
U: Service<(Request, Framed<T, h1::Codec>)>,
{
flow: Rc<RefCell<HttpFlow<S, X, U>>>,
cfg: ServiceConfig,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_t: PhantomData<(T, B, X)>,
_phantom: PhantomData<B>,
}
/// A collection of services that describe an HTTP request flow.
pub(super) struct HttpFlow<S, X, U> {
pub(super) service: S,
pub(super) expect: X,
pub(super) upgrade: Option<U>,
}
impl<S, X, U> HttpFlow<S, X, U> {
pub(super) fn new(service: S, expect: X, upgrade: Option<U>) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self {
service,
expect,
upgrade,
}))
}
}
impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
where
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{
fn new(
cfg: ServiceConfig,
srv: S,
service: S,
expect: X,
upgrade: Option<U>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
) -> HttpServiceHandler<T, S, B, X, U> {
HttpServiceHandler {
cfg,
on_connect,
on_connect_ext,
srv: CloneableService::new(srv),
expect: CloneableService::new(expect),
upgrade: upgrade.map(CloneableService::new),
_t: PhantomData,
flow: HttpFlow::new(service, expect, upgrade),
_phantom: PhantomData,
}
}
}
impl<T, S, B, X, U> Service for HttpServiceHandler<T, S, B, X, U>
impl<T, S, B, X, U> Service<(T, Protocol, Option<net::SocketAddr>)>
for HttpServiceHandler<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display + Into<Error>,
{
type Request = (T, Protocol, Option<net::SocketAddr>);
type Response = ();
type Error = DispatchError;
type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let ready = self
let mut flow = self.flow.borrow_mut();
let ready = flow
.expect
.poll_ready(cx)
.map_err(|e| {
@ -530,8 +521,8 @@ where
})?
.is_ready();
let ready = self
.srv
let ready = flow
.service
.poll_ready(cx)
.map_err(|e| {
let e = e.into();
@ -541,7 +532,7 @@ where
.is_ready()
&& ready;
let ready = if let Some(ref mut upg) = self.upgrade {
let ready = if let Some(ref mut upg) = flow.upgrade {
upg.poll_ready(cx)
.map_err(|e| {
let e = e.into();
@ -561,22 +552,20 @@ where
}
}
fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future {
let mut connect_extensions = Extensions::new();
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
if let Some(ref handler) = self.on_connect_ext {
handler(&io, &mut connect_extensions);
}
fn call(
&mut self,
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
) -> Self::Future {
let on_connect_data =
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
match proto {
Protocol::Http2 => HttpServiceHandlerResponse {
state: State::H2Handshake(Some((
server::handshake(io),
self.cfg.clone(),
self.srv.clone(),
deprecated_on_connect,
connect_extensions,
self.flow.clone(),
on_connect_data,
peer_addr,
))),
},
@ -585,14 +574,13 @@ where
state: State::H1(h1::Dispatcher::new(
io,
self.cfg.clone(),
self.srv.clone(),
self.expect.clone(),
self.upgrade.clone(),
deprecated_on_connect,
connect_extensions,
self.flow.clone(),
on_connect_data,
peer_addr,
)),
},
proto => unimplemented!("Unsupported HTTP version: {:?}.", proto),
}
}
}
@ -600,25 +588,24 @@ where
#[pin_project(project = StateProj)]
enum State<T, S, B, X, U>
where
S: Service<Request = Request>,
S: Service<Request>,
S::Future: 'static,
S::Error: Into<Error>,
T: AsyncRead + AsyncWrite + Unpin,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{
H1(#[pin] h1::Dispatcher<T, S, B, X, U>),
H2(#[pin] Dispatcher<T, S, B>),
H2(#[pin] Dispatcher<T, S, B, X, U>),
H2Handshake(
Option<(
Handshake<T, Bytes>,
ServiceConfig,
CloneableService<S>,
Option<Box<dyn DataFactory>>,
Extensions,
Rc<RefCell<HttpFlow<S, X, U>>>,
OnConnectData,
Option<net::SocketAddr>,
)>,
),
@ -628,14 +615,14 @@ where
pub struct HttpServiceHandlerResponse<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{
#[pin]
@ -645,61 +632,30 @@ where
impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{
type Output = Result<(), DispatchError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.project().state.poll(cx)
}
}
impl<T, S, B, X, U> State<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{
fn poll(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), DispatchError>> {
match self.as_mut().project() {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.as_mut().project().state.project() {
StateProj::H1(disp) => disp.poll(cx),
StateProj::H2(disp) => disp.poll(cx),
StateProj::H2Handshake(ref mut data) => {
let conn = if let Some(ref mut item) = data {
match Pin::new(&mut item.0).poll(cx) {
Poll::Ready(Ok(conn)) => conn,
Poll::Ready(Err(err)) => {
trace!("H2 handshake error: {}", err);
return Poll::Ready(Err(err.into()));
}
Poll::Pending => return Poll::Pending,
}
} else {
panic!()
};
let (_, cfg, srv, on_connect, on_connect_data, peer_addr) =
StateProj::H2Handshake(data) => {
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
Ok(conn) => {
let (_, cfg, srv, on_connect_data, peer_addr) =
data.take().unwrap();
self.set(State::H2(Dispatcher::new(
self.as_mut().project().state.set(State::H2(Dispatcher::new(
srv,
conn,
on_connect,
on_connect_data,
cfg,
None,
@ -707,6 +663,12 @@ where
)));
self.poll(cx)
}
Err(err) => {
trace!("H2 handshake error: {}", err);
Poll::Ready(Err(err.into()))
}
}
}
}
}
}

View File

@ -10,7 +10,7 @@ use std::{
task::{Context, Poll},
};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
use bytes::{Bytes, BytesMut};
use http::header::{self, HeaderName, HeaderValue};
use http::{Error as HttpError, Method, Uri, Version};
@ -251,9 +251,11 @@ impl AsyncRead for TestBuffer {
fn poll_read(
self: Pin<&mut Self>,
_: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
Poll::Ready(self.get_mut().read(buf))
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let dst = buf.initialize_unfilled();
let res = self.get_mut().read(dst).map(|n| buf.advance(n));
Poll::Ready(res)
}
}
@ -356,11 +358,15 @@ impl AsyncRead for TestSeqBuffer {
fn poll_read(
self: Pin<&mut Self>,
_: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
let r = self.get_mut().read(buf);
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let dst = buf.initialize_unfilled();
let r = self.get_mut().read(dst);
match r {
Ok(n) => Poll::Ready(Ok(n)),
Ok(n) => {
buf.advance(n);
Poll::Ready(Ok(()))
}
Err(err) if err.kind() == io::ErrorKind::WouldBlock => Poll::Pending,
Err(err) => Poll::Ready(Err(err)),
}

View File

@ -1,47 +1,60 @@
use actix_codec::{Decoder, Encoder};
use bitflags::bitflags;
use bytes::{Bytes, BytesMut};
use bytestring::ByteString;
use super::frame::Parser;
use super::proto::{CloseReason, OpCode};
use super::ProtocolError;
/// `WebSocket` Message
/// A WebSocket message.
#[derive(Debug, PartialEq)]
pub enum Message {
/// Text message
Text(String),
/// Binary message
/// Text message.
Text(ByteString),
/// Binary message.
Binary(Bytes),
/// Continuation
/// Continuation.
Continuation(Item),
/// Ping message
/// Ping message.
Ping(Bytes),
/// Pong message
/// Pong message.
Pong(Bytes),
/// Close message with optional reason
/// Close message with optional reason.
Close(Option<CloseReason>),
/// No-op. Useful for actix-net services
/// No-op. Useful for low-level services.
Nop,
}
/// `WebSocket` frame
/// A WebSocket frame.
#[derive(Debug, PartialEq)]
pub enum Frame {
/// Text frame, codec does not verify utf8 encoding
/// Text frame. Note that the codec does not validate UTF-8 encoding.
Text(Bytes),
/// Binary frame
/// Binary frame.
Binary(Bytes),
/// Continuation
/// Continuation.
Continuation(Item),
/// Ping message
/// Ping message.
Ping(Bytes),
/// Pong message
/// Pong message.
Pong(Bytes),
/// Close message with optional reason
/// Close message with optional reason.
Close(Option<CloseReason>),
}
/// `WebSocket` continuation item
/// A `WebSocket` continuation item.
#[derive(Debug, PartialEq)]
pub enum Item {
FirstText(Bytes),
@ -51,13 +64,13 @@ pub enum Item {
}
#[derive(Debug, Copy, Clone)]
/// WebSockets protocol codec
/// WebSocket protocol codec.
pub struct Codec {
flags: Flags,
max_size: usize,
}
bitflags::bitflags! {
bitflags! {
struct Flags: u8 {
const SERVER = 0b0000_0001;
const CONTINUATION = 0b0000_0010;
@ -66,7 +79,7 @@ bitflags::bitflags! {
}
impl Codec {
/// Create new websocket frames decoder
/// Create new websocket frames decoder.
pub fn new() -> Codec {
Codec {
max_size: 65_536,
@ -74,9 +87,9 @@ impl Codec {
}
}
/// Set max frame size
/// Set max frame size.
///
/// By default max size is set to 64kb
/// By default max size is set to 64kb.
pub fn max_size(mut self, size: usize) -> Self {
self.max_size = size;
self
@ -184,7 +197,7 @@ impl Encoder<Message> for Codec {
}
}
},
Message::Nop => (),
Message::Nop => {}
}
Ok(())
}

View File

@ -11,7 +11,7 @@ use super::{Codec, Frame, Message};
#[pin_project::pin_project]
pub struct Dispatcher<S, T>
where
S: Service<Request = Frame, Response = Message> + 'static,
S: Service<Frame, Response = Message> + 'static,
T: AsyncRead + AsyncWrite,
{
#[pin]
@ -21,17 +21,17 @@ where
impl<S, T> Dispatcher<S, T>
where
T: AsyncRead + AsyncWrite,
S: Service<Request = Frame, Response = Message>,
S: Service<Frame, Response = Message>,
S::Future: 'static,
S::Error: 'static,
{
pub fn new<F: IntoService<S>>(io: T, service: F) -> Self {
pub fn new<F: IntoService<S, Frame>>(io: T, service: F) -> Self {
Dispatcher {
inner: InnerDispatcher::new(Framed::new(io, Codec::new()), service),
}
}
pub fn with<F: IntoService<S>>(framed: Framed<T, Codec>, service: F) -> Self {
pub fn with<F: IntoService<S, Frame>>(framed: Framed<T, Codec>, service: F) -> Self {
Dispatcher {
inner: InnerDispatcher::new(framed, service),
}
@ -41,7 +41,7 @@ where
impl<S, T> Future for Dispatcher<S, T>
where
T: AsyncRead + AsyncWrite,
S: Service<Request = Frame, Response = Message>,
S: Service<Frame, Response = Message>,
S::Future: 'static,
S::Error: 'static,
{

View File

@ -125,7 +125,7 @@ impl Parser {
debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame.");
return Ok(Some((true, OpCode::Close, None)));
}
_ => (),
_ => {}
}
// unmask

View File

@ -1,11 +1,11 @@
//! WebSocket protocol support.
//!
//! To setup a `WebSocket`, first do web socket handshake then on success
//! convert `Payload` into a `WsStream` stream and then use `WsWriter` to
//! communicate with the peer.
//! To setup a WebSocket, first do web socket handshake then on success convert `Payload` into a
//! `WsStream` stream and then use `WsWriter` to communicate with the peer.
use std::io;
use derive_more::{Display, From};
use derive_more::{Display, Error, From};
use http::{header, Method, StatusCode};
use crate::error::ResponseError;
@ -23,86 +23,103 @@ pub use self::dispatcher::Dispatcher;
pub use self::frame::Parser;
pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode};
/// Websocket protocol errors
#[derive(Debug, Display, From)]
/// WebSocket protocol errors.
#[derive(Debug, Display, From, Error)]
pub enum ProtocolError {
/// Received an unmasked frame from client
#[display(fmt = "Received an unmasked frame from client")]
/// Received an unmasked frame from client.
#[display(fmt = "Received an unmasked frame from client.")]
UnmaskedFrame,
/// Received a masked frame from server
#[display(fmt = "Received a masked frame from server")]
/// Received a masked frame from server.
#[display(fmt = "Received a masked frame from server.")]
MaskedFrame,
/// Encountered invalid opcode
#[display(fmt = "Invalid opcode: {}", _0)]
InvalidOpcode(u8),
/// Encountered invalid opcode.
#[display(fmt = "Invalid opcode: {}.", _0)]
InvalidOpcode(#[error(not(source))] u8),
/// Invalid control frame length
#[display(fmt = "Invalid control frame length: {}", _0)]
InvalidLength(usize),
/// Bad web socket op code
#[display(fmt = "Bad web socket op code")]
#[display(fmt = "Invalid control frame length: {}.", _0)]
InvalidLength(#[error(not(source))] usize),
/// Bad opcode.
#[display(fmt = "Bad opcode.")]
BadOpCode,
/// A payload reached size limit.
#[display(fmt = "A payload reached size limit.")]
Overflow,
/// Continuation is not started
/// Continuation is not started.
#[display(fmt = "Continuation is not started.")]
ContinuationNotStarted,
/// Received new continuation but it is already started
#[display(fmt = "Received new continuation but it is already started")]
/// Received new continuation but it is already started.
#[display(fmt = "Received new continuation but it is already started.")]
ContinuationStarted,
/// Unknown continuation fragment
#[display(fmt = "Unknown continuation fragment.")]
ContinuationFragment(OpCode),
/// Io error
#[display(fmt = "io error: {}", _0)]
/// Unknown continuation fragment.
#[display(fmt = "Unknown continuation fragment: {}.", _0)]
ContinuationFragment(#[error(not(source))] OpCode),
/// I/O error.
#[display(fmt = "I/O error: {}", _0)]
Io(io::Error),
}
impl std::error::Error for ProtocolError {}
impl ResponseError for ProtocolError {}
/// Websocket handshake errors
/// WebSocket handshake errors
#[derive(PartialEq, Debug, Display)]
pub enum HandshakeError {
/// Only get method is allowed
#[display(fmt = "Method not allowed")]
/// Only get method is allowed.
#[display(fmt = "Method not allowed.")]
GetMethodRequired,
/// Upgrade header if not set to websocket
#[display(fmt = "Websocket upgrade is expected")]
/// Upgrade header if not set to websocket.
#[display(fmt = "WebSocket upgrade is expected.")]
NoWebsocketUpgrade,
/// Connection header is not set to upgrade
#[display(fmt = "Connection upgrade is expected")]
/// Connection header is not set to upgrade.
#[display(fmt = "Connection upgrade is expected.")]
NoConnectionUpgrade,
/// Websocket version header is not set
#[display(fmt = "Websocket version header is required")]
/// WebSocket version header is not set.
#[display(fmt = "WebSocket version header is required.")]
NoVersionHeader,
/// Unsupported websocket version
#[display(fmt = "Unsupported version")]
/// Unsupported websocket version.
#[display(fmt = "Unsupported version.")]
UnsupportedVersion,
/// Websocket key is not set or wrong
#[display(fmt = "Unknown websocket key")]
/// WebSocket key is not set or wrong.
#[display(fmt = "Unknown websocket key.")]
BadWebsocketKey,
}
impl ResponseError for HandshakeError {
fn error_response(&self) -> Response {
match *self {
match self {
HandshakeError::GetMethodRequired => Response::MethodNotAllowed()
.header(header::ALLOW, "GET")
.finish(),
HandshakeError::NoWebsocketUpgrade => Response::BadRequest()
.reason("No WebSocket UPGRADE header found")
.finish(),
HandshakeError::NoConnectionUpgrade => Response::BadRequest()
.reason("No CONNECTION upgrade")
.finish(),
HandshakeError::NoVersionHeader => Response::BadRequest()
.reason("Websocket version header is required")
.finish(),
HandshakeError::UnsupportedVersion => Response::BadRequest()
.reason("Unsupported version")
.finish(),
HandshakeError::BadWebsocketKey => {
Response::BadRequest().reason("Handshake error").finish()
}

View File

@ -1,28 +1,34 @@
use std::convert::{From, Into};
use std::fmt;
use self::OpCode::*;
/// Operation codes as part of rfc6455.
/// Operation codes as part of RFC6455.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum OpCode {
/// Indicates a continuation frame of a fragmented message.
Continue,
/// Indicates a text data frame.
Text,
/// Indicates a binary data frame.
Binary,
/// Indicates a close control frame.
Close,
/// Indicates a ping control frame.
Ping,
/// Indicates a pong control frame.
Pong,
/// Indicates an invalid opcode was received.
Bad,
}
impl fmt::Display for OpCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::OpCode::*;
match *self {
Continue => write!(f, "CONTINUE"),
Text => write!(f, "TEXT"),
@ -35,9 +41,10 @@ impl fmt::Display for OpCode {
}
}
impl Into<u8> for OpCode {
fn into(self) -> u8 {
match self {
impl From<OpCode> for u8 {
fn from(op: OpCode) -> u8 {
use self::OpCode::*;
match op {
Continue => 0,
Text => 1,
Binary => 2,
@ -54,6 +61,7 @@ impl Into<u8> for OpCode {
impl From<u8> for OpCode {
fn from(byte: u8) -> OpCode {
use self::OpCode::*;
match byte {
0 => Continue,
1 => Text,
@ -66,7 +74,6 @@ impl From<u8> for OpCode {
}
}
use self::CloseCode::*;
/// Status code used to indicate why an endpoint is closing the `WebSocket`
/// connection.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
@ -132,9 +139,10 @@ pub enum CloseCode {
Other(u16),
}
impl Into<u16> for CloseCode {
fn into(self) -> u16 {
match self {
impl From<CloseCode> for u16 {
fn from(code: CloseCode) -> u16 {
use self::CloseCode::*;
match code {
Normal => 1000,
Away => 1001,
Protocol => 1002,
@ -155,6 +163,7 @@ impl Into<u16> for CloseCode {
impl From<u16> for CloseCode {
fn from(code: u16) -> CloseCode {
use self::CloseCode::*;
match code {
1000 => Normal,
1001 => Away,
@ -179,6 +188,7 @@ impl From<u16> for CloseCode {
pub struct CloseReason {
/// Exit code
pub code: CloseCode,
/// Optional description of the exit code
pub description: Option<String>,
}
@ -222,7 +232,7 @@ mod test {
macro_rules! opcode_into {
($from:expr => $opcode:pat) => {
match OpCode::from($from) {
e @ $opcode => (),
e @ $opcode => {}
e => unreachable!("{:?}", e),
}
};
@ -232,7 +242,7 @@ mod test {
($from:expr => $opcode:pat) => {
let res: u8 = $from.into();
match res {
e @ $opcode => (),
e @ $opcode => {}
e => unreachable!("{:?}", e),
}
};

View File

@ -1,9 +1,8 @@
use actix_service::ServiceFactory;
use bytes::Bytes;
use futures_util::future::{self, ok};
use actix_http::{http, HttpService, Request, Response};
use actix_http_test::test_server;
use actix_service::ServiceFactoryExt;
use bytes::Bytes;
use futures_util::future::{self, ok};
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \

View File

@ -1,19 +1,17 @@
#![cfg(feature = "openssl")]
use std::io;
use actix_http_test::test_server;
use actix_service::{fn_service, ServiceFactory};
use bytes::{Bytes, BytesMut};
use futures_util::future::{err, ok, ready};
use futures_util::stream::{once, Stream, StreamExt};
use open_ssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod};
use actix_http::error::{ErrorBadRequest, PayloadError};
use actix_http::http::header::{self, HeaderName, HeaderValue};
use actix_http::http::{Method, StatusCode, Version};
use actix_http::httpmessage::HttpMessage;
use actix_http::{body, Error, HttpService, Request, Response};
use actix_http_test::test_server;
use actix_service::{fn_service, ServiceFactoryExt};
use bytes::{Bytes, BytesMut};
use futures_util::future::{err, ok, ready};
use futures_util::stream::{once, Stream, StreamExt};
use open_ssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod};
async fn load_body<S>(stream: S) -> Result<BytesMut, PayloadError>
where
@ -410,10 +408,8 @@ async fn test_h2_service_error() {
async fn test_h2_on_connect() {
let srv = test_server(move || {
HttpService::build()
.on_connect(|_| 10usize)
.on_connect_ext(|_, data| data.insert(20isize))
.h2(|req: Request| {
assert!(req.extensions().contains::<usize>());
assert!(req.extensions().contains::<isize>());
ok::<_, ()>(Response::Ok().finish())
})

View File

@ -3,7 +3,7 @@ use std::time::Duration;
use std::{net, thread};
use actix_http_test::test_server;
use actix_rt::time::delay_for;
use actix_rt::time::sleep;
use actix_service::fn_service;
use bytes::Bytes;
use futures_util::future::{self, err, ok, ready, FutureExt};
@ -88,7 +88,7 @@ async fn test_expect_continue_h1() {
let srv = test_server(|| {
HttpService::build()
.expect(fn_service(|req: Request| {
delay_for(Duration::from_millis(20)).then(move |_| {
sleep(Duration::from_millis(20)).then(move |_| {
if req.head().uri.query() == Some("yes=") {
ok(req)
} else {
@ -662,10 +662,8 @@ async fn test_h1_service_error() {
async fn test_h1_on_connect() {
let srv = test_server(|| {
HttpService::build()
.on_connect(|_| 10usize)
.on_connect_ext(|_, data| data.insert(20isize))
.h1(|req: Request| {
assert!(req.extensions().contains::<usize>());
assert!(req.extensions().contains::<isize>());
future::ok::<_, ()>(Response::Ok().finish())
})

View File

@ -36,11 +36,10 @@ impl<T> Clone for WsService<T> {
}
}
impl<T> Service for WsService<T>
impl<T> Service<(Request, Framed<T, h1::Codec>)> for WsService<T>
where
T: AsyncRead + AsyncWrite + Unpin + 'static,
{
type Request = (Request, Framed<T, h1::Codec>);
type Response = ();
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<(), Error>>>>;
@ -50,7 +49,10 @@ where
Poll::Ready(Ok(()))
}
fn call(&mut self, (req, mut framed): Self::Request) -> Self::Future {
fn call(
&mut self,
(req, mut framed): (Request, Framed<T, h1::Codec>),
) -> Self::Future {
let fut = async move {
let res = ws::handshake(req.head()).unwrap().message_body(());
@ -72,7 +74,7 @@ async fn service(msg: ws::Frame) -> Result<ws::Message, Error> {
let msg = match msg {
ws::Frame::Ping(msg) => ws::Message::Pong(msg),
ws::Frame::Text(text) => {
ws::Message::Text(String::from_utf8_lossy(&text).to_string())
ws::Message::Text(String::from_utf8_lossy(&text).into_owned().into())
}
ws::Frame::Binary(bin) => ws::Message::Binary(bin),
ws::Frame::Continuation(item) => ws::Message::Continuation(item),
@ -99,10 +101,7 @@ async fn test_simple() {
// client service
let mut framed = srv.ws().await.unwrap();
framed
.send(ws::Message::Text("text".to_string()))
.await
.unwrap();
framed.send(ws::Message::Text("text".into())).await.unwrap();
let (item, mut framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),

View File

@ -1,14 +1,21 @@
# Changes
## Unreleased - 2020-xx-xx
* Fix multipart consuming payload before header checks #1513
## Unreleased - 2021-xx-xx
## 3.0.0 - 2020-09-11
* No significant changes from `3.0.0-beta.2`.
## 0.4.0-beta.1 - 2021-01-07
* Fix multipart consuming payload before header checks. [#1513]
* Update `bytes` to `1.0`. [#1813]
[#1813]: https://github.com/actix/actix-web/pull/1813
[#1513]: https://github.com/actix/actix-web/pull/1513
## 3.0.0-beta.2 - 2020-09-10
## 0.3.0 - 2020-09-11
* No significant changes from `0.3.0-beta.2`.
## 0.3.0-beta.2 - 2020-09-10
* Update `actix-*` dependencies to latest versions.

View File

@ -1,6 +1,6 @@
[package]
name = "actix-multipart"
version = "0.3.0"
version = "0.4.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Multipart support for actix web framework."
readme = "README.md"
@ -16,17 +16,17 @@ name = "actix_multipart"
path = "src/lib.rs"
[dependencies]
actix-web = { version = "3.0.0", default-features = false }
actix-service = "1.0.6"
actix-utils = "2.0.0"
bytes = "0.5.3"
derive_more = "0.99.2"
actix-web = { version = "4.0.0-beta.1", default-features = false }
actix-utils = "3.0.0-beta.1"
bytes = "1"
derive_more = "0.99.5"
httparse = "1.3"
futures-util = { version = "0.3.5", default-features = false }
futures-util = { version = "0.3.7", default-features = false }
log = "0.4"
mime = "0.3"
twoway = "0.2"
[dev-dependencies]
actix-rt = "1.0.0"
actix-http = "2.0.0"
actix-rt = "2.0.0-beta.1"
actix-http = "3.0.0-beta.1"

View File

@ -326,7 +326,7 @@ impl InnerMultipart {
}
}
}
_ => (),
_ => {}
}
// read field headers for next field
@ -835,7 +835,7 @@ mod tests {
async fn test_boundary() {
let headers = HeaderMap::new();
match Multipart::boundary(&headers) {
Err(MultipartError::NoContentType) => (),
Err(MultipartError::NoContentType) => {}
_ => unreachable!("should not happen"),
}
@ -846,7 +846,7 @@ mod tests {
);
match Multipart::boundary(&headers) {
Err(MultipartError::ParseContentType) => (),
Err(MultipartError::ParseContentType) => {}
_ => unreachable!("should not happen"),
}
@ -856,7 +856,7 @@ mod tests {
header::HeaderValue::from_static("multipart/mixed"),
);
match Multipart::boundary(&headers) {
Err(MultipartError::Boundary) => (),
Err(MultipartError::Boundary) => {}
_ => unreachable!("should not happen"),
}
@ -956,17 +956,17 @@ mod tests {
let mut multipart = Multipart::new(&headers, payload);
match multipart.next().await.unwrap() {
Ok(_) => (),
Ok(_) => {}
_ => unreachable!(),
}
match multipart.next().await.unwrap() {
Ok(_) => (),
Ok(_) => {}
_ => unreachable!(),
}
match multipart.next().await {
None => (),
None => {}
_ => unreachable!(),
}
}
@ -993,7 +993,7 @@ mod tests {
_ => unreachable!(),
}
match field.next().await {
None => (),
None => {}
_ => unreachable!(),
}
}
@ -1010,7 +1010,7 @@ mod tests {
_ => unreachable!(),
}
match field.next().await {
None => (),
None => {}
_ => unreachable!(),
}
}
@ -1018,7 +1018,7 @@ mod tests {
}
match multipart.next().await {
None => (),
None => {}
_ => unreachable!(),
}
}
@ -1066,7 +1066,7 @@ mod tests {
}
match multipart.next().await {
None => (),
None => {}
_ => unreachable!(),
}
}

View File

@ -1,7 +1,16 @@
# Changes
## Unreleased - 2020-xx-xx
* Upgrade `pin-project` to `1.0`.
## Unreleased - 2021-xx-xx
## 4.0.0-beta.1 - 2021-01-07
* Update `pin-project` to `1.0`.
* Update `bytes` to `1.0`. [#1813]
* `WebsocketContext::text` now takes an `Into<bytestring::ByteString>`. [#1864]
[#1813]: https://github.com/actix/actix-web/pull/1813
[#1864]: https://github.com/actix/actix-web/pull/1864
## 3.0.0 - 2020-09-11
* No significant changes from `3.0.0-beta.2`.

View File

@ -1,6 +1,6 @@
[package]
name = "actix-web-actors"
version = "3.0.0"
version = "4.0.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for actix web framework."
readme = "README.md"
@ -16,16 +16,18 @@ name = "actix_web_actors"
path = "src/lib.rs"
[dependencies]
actix = "0.10.0"
actix-web = { version = "3.0.0", default-features = false }
actix-http = "2.0.0"
actix-codec = "0.3.0"
bytes = "0.5.2"
futures-channel = { version = "0.3.5", default-features = false }
futures-core = { version = "0.3.5", default-features = false }
actix = "0.11.0-beta.1"
actix-codec = "0.4.0-beta.1"
actix-http = "3.0.0-beta.1"
actix-web = { version = "4.0.0-beta.1", default-features = false }
bytes = "1"
bytestring = "1"
futures-core = { version = "0.3.7", default-features = false }
pin-project = "1.0.0"
tokio = { version = "1", features = ["sync"] }
[dev-dependencies]
actix-rt = "1.1.1"
actix-rt = "2.0.0-beta.1"
env_logger = "0.7"
futures-util = { version = "0.3.5", default-features = false }
futures-util = { version = "0.3.7", default-features = false }

View File

@ -12,8 +12,8 @@ use actix::{
};
use actix_web::error::Error;
use bytes::Bytes;
use futures_channel::oneshot::Sender;
use futures_core::Stream;
use tokio::sync::oneshot::Sender;
/// Execution context for http actors
pub struct HttpContext<A>

View File

@ -1,9 +1,10 @@
//! Websocket integration
use std::collections::VecDeque;
//! Websocket integration.
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{collections::VecDeque, convert::TryFrom};
use actix::dev::{
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler,
@ -24,10 +25,11 @@ use actix_web::error::{Error, PayloadError};
use actix_web::http::{header, Method, StatusCode};
use actix_web::{HttpRequest, HttpResponse};
use bytes::{Bytes, BytesMut};
use futures_channel::oneshot::Sender;
use bytestring::ByteString;
use futures_core::Stream;
use tokio::sync::oneshot::Sender;
/// Do websocket handshake and start ws actor.
/// Perform WebSocket handshake and start actor.
pub fn start<A, T>(actor: A, req: &HttpRequest, stream: T) -> Result<HttpResponse, Error>
where
A: Actor<Context = WebsocketContext<A>>
@ -38,7 +40,7 @@ where
Ok(res.streaming(WebsocketContext::create(actor, stream)))
}
/// Do websocket handshake and start ws actor.
/// Perform WebSocket handshake and start actor.
///
/// `req` is an HTTP Request that should be requesting a websocket protocol
/// change. `stream` should be a `Bytes` stream (such as
@ -338,13 +340,13 @@ where
/// Send text frame
#[inline]
pub fn text<T: Into<String>>(&mut self, text: T) {
pub fn text(&mut self, text: impl Into<ByteString>) {
self.write_raw(Message::Text(text.into()));
}
/// Send binary frame
#[inline]
pub fn binary<B: Into<Bytes>>(&mut self, data: B) {
pub fn binary(&mut self, data: impl Into<Bytes>) {
self.write_raw(Message::Binary(data.into()));
}
@ -528,16 +530,14 @@ where
}
Some(frm) => {
let msg = match frm {
Frame::Text(data) => Message::Text(
std::str::from_utf8(&data)
.map_err(|e| {
Frame::Text(data) => {
Message::Text(ByteString::try_from(data).map_err(|e| {
ProtocolError::Io(io::Error::new(
io::ErrorKind::Other,
format!("{}", e),
))
})?
.to_string(),
),
})?)
}
Frame::Binary(data) => Message::Binary(data),
Frame::Ping(s) => Message::Ping(s),
Frame::Pong(s) => Message::Pong(s),

View File

@ -21,7 +21,7 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Ws {
ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Close(reason) => ctx.close(reason),
_ => (),
_ => {}
}
}
}
@ -38,10 +38,7 @@ async fn test_simple() {
// client service
let mut framed = srv.ws().await.unwrap();
framed
.send(ws::Message::Text("text".to_string()))
.await
.unwrap();
framed.send(ws::Message::Text("text".into())).await.unwrap();
let item = framed.next().await.unwrap().unwrap();
assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text")));

View File

@ -1,6 +1,6 @@
# Changes
## Unreleased - 2020-xx-xx
## Unreleased - 2021-xx-xx
## 0.4.0 - 2020-09-20

View File

@ -19,8 +19,8 @@ syn = { version = "1", features = ["full", "parsing"] }
proc-macro2 = "1"
[dev-dependencies]
actix-rt = "1.1.1"
actix-web = "3.0.0"
futures-util = { version = "0.3.5", default-features = false }
actix-rt = "2.0.0-beta.1"
actix-web = "4.0.0-beta.1"
futures-util = { version = "0.3.7", default-features = false }
trybuild = "1"
rustversion = "1"

View File

@ -4,7 +4,7 @@
[![crates.io](https://meritbadge.herokuapp.com/actix-web-codegen)](https://crates.io/crates/actix-web-codegen)
[![Documentation](https://docs.rs/actix-web-codegen/badge.svg)](https://docs.rs/actix-web-codegen/0.4.0/actix_web_codegen/)
[![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)
[![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@ -14,7 +14,7 @@
- [API Documentation](https://docs.rs/actix-web-codegen)
- [Chat on Gitter](https://gitter.im/actix/actix-web)
- Cargo package: [actix-web-codegen](https://crates.io/crates/actix-web-codegen)
- Minimum supported Rust version: 1.42 or later.
- Minimum supported Rust version: 1.46 or later.
## Compile Testing
Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this.

View File

@ -88,17 +88,16 @@ async fn route_test() -> impl Responder {
pub struct ChangeStatusCode;
impl<S, B> Transform<S> for ChangeStatusCode
impl<S, B> Transform<S, ServiceRequest> for ChangeStatusCode
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = ChangeStatusCodeMiddleware<S>;
type InitError = ();
type Future = future::Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
@ -110,13 +109,12 @@ pub struct ChangeStatusCodeMiddleware<S> {
service: S,
}
impl<S, B> Service for ChangeStatusCodeMiddleware<S>
impl<S, B> Service<ServiceRequest> for ChangeStatusCodeMiddleware<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
#[allow(clippy::type_complexity)]

View File

@ -6,31 +6,15 @@ fn compile_macros() {
t.compile_fail("tests/trybuild/simple-fail.rs");
t.pass("tests/trybuild/route-ok.rs");
test_route_duplicate_unexpected_method(&t);
test_route_missing_method(&t)
}
#[rustversion::stable(1.42)]
fn test_route_missing_method(t: &trybuild::TestCases) {
t.compile_fail("tests/trybuild/route-missing-method-fail-msrv.rs");
}
#[rustversion::not(stable(1.42))]
#[rustversion::not(nightly)]
fn test_route_missing_method(t: &trybuild::TestCases) {
t.compile_fail("tests/trybuild/route-missing-method-fail.rs");
}
#[rustversion::nightly]
fn test_route_missing_method(_t: &trybuild::TestCases) {}
// FIXME: Re-test them on nightly once rust-lang/rust#77993 is fixed.
#[rustversion::not(nightly)]
fn test_route_duplicate_unexpected_method(t: &trybuild::TestCases) {
t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs");
t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs");
}
#[rustversion::nightly]
fn test_route_duplicate_unexpected_method(_t: &trybuild::TestCases) {}
// #[rustversion::not(nightly)]
// fn skip_on_nightly(t: &trybuild::TestCases) {
//
// }
// #[rustversion::nightly]
// fn skip_on_nightly(_t: &trybuild::TestCases) {}

View File

@ -1 +0,0 @@
route-missing-method-fail.rs

View File

@ -1,11 +0,0 @@
error: The #[route(..)] macro requires at least one `method` attribute
--> $DIR/route-missing-method-fail-msrv.rs:3:1
|
3 | #[route("/")]
| ^^^^^^^^^^^^^
error[E0425]: cannot find value `index` in this scope
--> $DIR/route-missing-method-fail-msrv.rs:12:49
|
12 | let srv = test::start(|| App::new().service(index));
| ^^^^^ not found in this scope

View File

@ -1,8 +1,15 @@
# Changes
## Unreleased - 2020-xx-xx
## Unreleased - 2021-xx-xx
## 3.0.0-beta.1 - 2021-01-07
### Changed
* Bumped `rand` to `0.8`
* Update `rand` to `0.8`
* Update `bytes` to `1.0`. [#1813]
* Update `rust-tls` to `0.19`. [#1813]
[#1813]: https://github.com/actix/actix-web/pull/1813
## 2.0.3 - 2020-11-29

View File

@ -1,6 +1,6 @@
[package]
name = "awc"
version = "2.0.3"
version = "3.0.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Async HTTP and WebSocket client library built on the Actix ecosystem"
readme = "README.md"
@ -37,16 +37,16 @@ rustls = ["rust-tls", "actix-http/rustls"]
compress = ["actix-http/compress"]
[dependencies]
actix-codec = "0.3.0"
actix-service = "1.0.6"
actix-http = "2.2.0"
actix-rt = "1.0.0"
actix-codec = "0.4.0-beta.1"
actix-service = "2.0.0-beta.2"
actix-http = "3.0.0-beta.1"
actix-rt = "2.0.0-beta.1"
base64 = "0.13"
bytes = "0.5.3"
bytes = "1"
cfg-if = "1.0"
derive_more = "0.99.2"
futures-core = { version = "0.3.5", default-features = false }
derive_more = "0.99.5"
futures-core = { version = "0.3.7", default-features = false }
log =" 0.4"
mime = "0.3"
percent-encoding = "2.1"
@ -55,18 +55,20 @@ serde = "1.0"
serde_json = "1.0"
serde_urlencoded = "0.7"
open-ssl = { version = "0.10", package = "openssl", optional = true }
rust-tls = { version = "0.18.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
rust-tls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
[dev-dependencies]
actix-connect = { version = "2.0.0", features = ["openssl"] }
actix-web = { version = "3.0.0", features = ["openssl"] }
actix-http = { version = "2.0.0", features = ["openssl"] }
actix-http-test = { version = "2.0.0", features = ["openssl"] }
actix-utils = "2.0.0"
actix-server = "1.0.0"
actix-tls = { version = "2.0.0", features = ["openssl", "rustls"] }
# TODO: actix is temporary added as dev dep for actix-macro reason.
# Can be removed when it does not impact tests.
actix = "0.11.0-beta.1"
actix-web = { version = "4.0.0-beta.1", features = ["openssl"] }
actix-http = { version = "3.0.0-beta.1", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] }
actix-utils = "3.0.0-beta.1"
actix-server = "2.0.0-beta.2"
actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] }
brotli2 = "0.3.2"
flate2 = "1.0.13"
futures-util = { version = "0.3.5", default-features = false }
futures-util = { version = "0.3.7", default-features = false }
env_logger = "0.7"
webpki = "0.21"

View File

@ -13,7 +13,7 @@
- [API Documentation](https://docs.rs/awc)
- [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https)
- [Chat on Gitter](https://gitter.im/actix/actix-web)
- Minimum Supported Rust Version (MSRV): 1.42.0
- Minimum Supported Rust Version (MSRV): 1.46.0
## Example
```rust

View File

@ -51,7 +51,7 @@ impl ClientBuilder {
/// Use custom connector service.
pub fn connector<T>(mut self, connector: T) -> Self
where
T: Service<Request = HttpConnect, Error = ConnectError> + 'static,
T: Service<HttpConnect, Error = ConnectError> + 'static,
T::Response: Connection,
<T::Response as Connection>::Future: 'static,
T::Future: 'static,

View File

@ -2,9 +2,9 @@ use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use std::{fmt, io, mem, net};
use std::{fmt, io, net};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf};
use actix_http::body::Body;
use actix_http::client::{
Connect as ClientConnect, ConnectError, Connection, SendRequestError,
@ -70,7 +70,7 @@ pub(crate) trait Connect {
impl<T> Connect for ConnectorWrapper<T>
where
T: Service<Request = ClientConnect, Error = ConnectError>,
T: Service<ClientConnect, Error = ConnectError>,
T::Response: Connection,
<T::Response as Connection>::Io: 'static,
<T::Response as Connection>::Future: 'static,
@ -221,18 +221,11 @@ impl fmt::Debug for BoxedSocket {
}
impl AsyncRead for BoxedSocket {
unsafe fn prepare_uninitialized_buffer(
&self,
buf: &mut [mem::MaybeUninit<u8>],
) -> bool {
self.0.as_read().prepare_uninitialized_buffer(buf)
}
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Pin::new(self.get_mut().0.as_read_mut()).poll_read(cx, buf)
}
}

View File

@ -76,7 +76,7 @@
//! .await?;
//!
//! connection
//! .send(awc::ws::Message::Text("Echo".to_string()))
//! .send(awc::ws::Message::Text("Echo".into()))
//! .await?;
//! let response = connection.next().await.unwrap()?;
//! # assert_eq!(response, awc::ws::Frame::Text("Echo".as_bytes().into()));

View File

@ -523,7 +523,7 @@ impl ClientRequest {
return Err(InvalidUrl::MissingScheme.into());
} else if let Some(scheme) = uri.scheme() {
match scheme.as_str() {
"http" | "ws" | "https" | "wss" => (),
"http" | "ws" | "https" | "wss" => {}
_ => return Err(InvalidUrl::UnknownScheme.into()),
}
} else {

View File

@ -234,7 +234,7 @@ pub struct JsonBody<S, U> {
length: Option<usize>,
err: Option<JsonPayloadError>,
fut: Option<ReadBody<S>>,
_t: PhantomData<U>,
_phantom: PhantomData<U>,
}
impl<S, U> JsonBody<S, U>
@ -255,7 +255,7 @@ where
length: None,
fut: None,
err: Some(JsonPayloadError::ContentType),
_t: PhantomData,
_phantom: PhantomData,
};
}
@ -272,7 +272,7 @@ where
length: len,
err: None,
fut: Some(ReadBody::new(req.take_payload(), 65536)),
_t: PhantomData,
_phantom: PhantomData,
}
}
@ -370,14 +370,14 @@ mod tests {
async fn test_body() {
let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish();
match req.body().await.err().unwrap() {
PayloadError::UnknownLength => (),
PayloadError::UnknownLength => {}
_ => unreachable!("error"),
}
let mut req =
TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish();
match req.body().await.err().unwrap() {
PayloadError::Overflow => (),
PayloadError::Overflow => {}
_ => unreachable!("error"),
}
@ -390,7 +390,7 @@ mod tests {
.set_payload(Bytes::from_static(b"11111111111111"))
.finish();
match req.body().limit(5).await.err().unwrap() {
PayloadError::Overflow => (),
PayloadError::Overflow => {}
_ => unreachable!("error"),
}
}

View File

@ -5,7 +5,7 @@ use std::rc::Rc;
use std::task::{Context, Poll};
use std::time::Duration;
use actix_rt::time::{delay_for, Delay};
use actix_rt::time::{sleep, Sleep};
use bytes::Bytes;
use derive_more::From;
use futures_core::Stream;
@ -33,18 +33,18 @@ pub(crate) enum PrepForSendingError {
Http(HttpError),
}
impl Into<FreezeRequestError> for PrepForSendingError {
fn into(self) -> FreezeRequestError {
match self {
impl From<PrepForSendingError> for FreezeRequestError {
fn from(err: PrepForSendingError) -> FreezeRequestError {
match err {
PrepForSendingError::Url(e) => FreezeRequestError::Url(e),
PrepForSendingError::Http(e) => FreezeRequestError::Http(e),
}
}
}
impl Into<SendRequestError> for PrepForSendingError {
fn into(self) -> SendRequestError {
match self {
impl From<PrepForSendingError> for SendRequestError {
fn from(err: PrepForSendingError) -> SendRequestError {
match err {
PrepForSendingError::Url(e) => SendRequestError::Url(e),
PrepForSendingError::Http(e) => SendRequestError::Http(e),
}
@ -56,7 +56,8 @@ impl Into<SendRequestError> for PrepForSendingError {
pub enum SendClientRequest {
Fut(
Pin<Box<dyn Future<Output = Result<ClientResponse, SendRequestError>>>>,
Option<Delay>,
// FIXME: use a pinned Sleep instead of box.
Option<Pin<Box<Sleep>>>,
bool,
),
Err(Option<SendRequestError>),
@ -68,7 +69,7 @@ impl SendClientRequest {
response_decompress: bool,
timeout: Option<Duration>,
) -> SendClientRequest {
let delay = timeout.map(delay_for);
let delay = timeout.map(|d| Box::pin(sleep(d)));
SendClientRequest::Fut(send, delay, response_decompress)
}
}
@ -85,7 +86,7 @@ impl Future for SendClientRequest {
SendClientRequest::Fut(send, delay, response_decompress) => {
if delay.is_some() {
match Pin::new(delay.as_mut().unwrap()).poll(cx) {
Poll::Pending => (),
Poll::Pending => {}
_ => return Poll::Ready(Err(SendRequestError::Timeout)),
}
}
@ -126,7 +127,7 @@ impl Future for SendClientRequest {
SendClientRequest::Fut(send, delay, _) => {
if delay.is_some() {
match Pin::new(delay.as_mut().unwrap()).poll(cx) {
Poll::Pending => (),
Poll::Pending => {}
_ => return Poll::Ready(Err(SendRequestError::Timeout)),
}
}

View File

@ -17,7 +17,7 @@
//! .unwrap();
//!
//! connection
//! .send(ws::Message::Text("Echo".to_string()))
//! .send(ws::Message::Text("Echo".into()))
//! .await
//! .unwrap();
//! let response = connection.next().await.unwrap().unwrap();
@ -259,7 +259,7 @@ impl WebsocketsRequest {
return Err(InvalidUrl::MissingScheme.into());
} else if let Some(scheme) = uri.scheme() {
match scheme.as_str() {
"http" | "ws" | "https" | "wss" => (),
"http" | "ws" | "https" | "wss" => {}
_ => return Err(InvalidUrl::UnknownScheme.into()),
}
} else {

View File

@ -108,14 +108,14 @@ async fn test_form() {
async fn test_timeout() {
let srv = test::start(|| {
App::new().service(web::resource("/").route(web::to(|| async {
actix_rt::time::delay_for(Duration::from_millis(200)).await;
actix_rt::time::sleep(Duration::from_millis(200)).await;
Ok::<_, Error>(HttpResponse::Ok().body(STR))
})))
});
let connector = awc::Connector::new()
.connector(actix_connect::new_connector(
actix_connect::start_default_resolver().await.unwrap(),
.connector(actix_tls::connect::new_connector(
actix_tls::connect::start_default_resolver().await.unwrap(),
))
.timeout(Duration::from_secs(15))
.finish();
@ -127,7 +127,7 @@ async fn test_timeout() {
let request = client.get(srv.url("/")).send();
match request.await {
Err(SendRequestError::Timeout) => (),
Err(SendRequestError::Timeout) => {}
_ => panic!(),
}
}
@ -136,7 +136,7 @@ async fn test_timeout() {
async fn test_timeout_override() {
let srv = test::start(|| {
App::new().service(web::resource("/").route(web::to(|| async {
actix_rt::time::delay_for(Duration::from_millis(200)).await;
actix_rt::time::sleep(Duration::from_millis(200)).await;
Ok::<_, Error>(HttpResponse::Ok().body(STR))
})))
});
@ -149,7 +149,7 @@ async fn test_timeout_override() {
.timeout(Duration::from_millis(50))
.send();
match request.await {
Err(SendRequestError::Timeout) => (),
Err(SendRequestError::Timeout) => {}
_ => panic!(),
}
}

View File

@ -1,7 +1,7 @@
#![cfg(feature = "openssl")]
use actix_http::HttpService;
use actix_http_test::test_server;
use actix_service::{map_config, ServiceFactory};
use actix_service::{map_config, ServiceFactoryExt};
use actix_web::http::Version;
use actix_web::{dev::AppConfig, web, App, HttpResponse};
use open_ssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode};

View File

@ -4,7 +4,7 @@ use std::sync::Arc;
use actix_http::HttpService;
use actix_http_test::test_server;
use actix_service::{map_config, pipeline_factory, ServiceFactory};
use actix_service::{map_config, pipeline_factory, ServiceFactoryExt};
use actix_web::http::Version;
use actix_web::{dev::AppConfig, web, App, HttpResponse};
use futures_util::future::ok;

View File

@ -4,7 +4,7 @@ use std::sync::Arc;
use actix_http::HttpService;
use actix_http_test::test_server;
use actix_service::{map_config, pipeline_factory, ServiceFactory};
use actix_service::{map_config, pipeline_factory, ServiceFactoryExt};
use actix_web::http::Version;
use actix_web::{dev::AppConfig, web, App, HttpResponse};
use futures_util::future::ok;

View File

@ -11,7 +11,7 @@ async fn ws_service(req: ws::Frame) -> Result<ws::Message, io::Error> {
match req {
ws::Frame::Ping(msg) => Ok(ws::Message::Pong(msg)),
ws::Frame::Text(text) => Ok(ws::Message::Text(
String::from_utf8(Vec::from(text.as_ref())).unwrap(),
String::from_utf8(Vec::from(text.as_ref())).unwrap().into(),
)),
ws::Frame::Binary(bin) => Ok(ws::Message::Binary(bin)),
ws::Frame::Close(reason) => Ok(ws::Message::Close(reason)),
@ -43,10 +43,7 @@ async fn test_simple() {
// client service
let mut framed = srv.ws().await.unwrap();
framed
.send(ws::Message::Text("text".to_string()))
.await
.unwrap();
framed.send(ws::Message::Text("text".into())).await.unwrap();
let item = framed.next().await.unwrap().unwrap();
assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text")));

View File

@ -29,18 +29,22 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
fn bench_async_burst(c: &mut Criterion) {
// We are using System here, since Runtime requires preinitialized tokio
// Maybe add to actix_rt docs
let mut rt = actix_rt::System::new("test");
let rt = actix_rt::System::new("test");
let srv = test::start(|| {
App::new()
.service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
let srv = rt.block_on(async {
test::start(|| {
App::new().service(
web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))),
)
})
});
let url = srv.url("/");
c.bench_function("get_body_async_burst", move |b| {
b.iter_custom(|iters| {
let client = Client::new().get(url.clone()).freeze().unwrap();
let client =
rt.block_on(async { Client::new().get(url.clone()).freeze().unwrap() });
let start = std::time::Instant::now();
// benchmark body

View File

@ -23,10 +23,9 @@ use actix_web::test::{init_service, ok_service, TestRequest};
/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us]
pub fn bench_async_service<S>(c: &mut Criterion, srv: S, name: &str)
where
S: Service<Request = ServiceRequest, Response = ServiceResponse, Error = Error>
+ 'static,
S: Service<ServiceRequest, Response = ServiceResponse, Error = Error> + 'static,
{
let mut rt = actix_rt::System::new("test");
let rt = actix_rt::System::new("test");
let srv = Rc::new(RefCell::new(srv));
let req = TestRequest::default().to_srv_request();
@ -41,14 +40,15 @@ where
b.iter_custom(|iters| {
let srv = srv.clone();
// exclude request generation, it appears it takes significant time vs call (3us vs 1us)
let reqs: Vec<_> = (0..iters)
let futs = (0..iters)
.map(|_| TestRequest::default().to_srv_request())
.collect();
.map(|req| srv.borrow_mut().call(req));
let start = std::time::Instant::now();
// benchmark body
rt.block_on(async move {
for req in reqs {
srv.borrow_mut().call(req).await.unwrap();
for fut in futs {
fut.await.unwrap();
}
});
let elapsed = start.elapsed();
@ -67,7 +67,7 @@ async fn index(req: ServiceRequest) -> Result<ServiceResponse, Error> {
// Sample results on MacBook Pro '14
// time: [2.0724 us 2.1345 us 2.2074 us]
fn async_web_service(c: &mut Criterion) {
let mut rt = actix_rt::System::new("test");
let rt = actix_rt::System::new("test");
let srv = Rc::new(RefCell::new(rt.block_on(init_service(
App::new().service(web::service("/").finish(index)),
))));
@ -83,13 +83,14 @@ fn async_web_service(c: &mut Criterion) {
c.bench_function("async_web_service_direct", move |b| {
b.iter_custom(|iters| {
let srv = srv.clone();
let reqs = (0..iters).map(|_| TestRequest::get().uri("/").to_request());
let futs = (0..iters)
.map(|_| TestRequest::get().uri("/").to_request())
.map(|req| srv.borrow_mut().call(req));
let start = std::time::Instant::now();
// benchmark body
rt.block_on(async move {
for req in reqs {
srv.borrow_mut().call(req).await.unwrap();
for fut in futs {
fut.await.unwrap();
}
});
let elapsed = start.elapsed();

View File

@ -2,12 +2,10 @@ digraph {
subgraph cluster_net {
label="actix/actix-net";
"actix-codec"
"actix-connect"
"actix-macros"
"actix-rt"
"actix-server"
"actix-service"
"actix-testing"
"actix-threadpool"
"actix-tls"
"actix-tracing"
@ -17,9 +15,7 @@ digraph {
"actix-utils" -> { "actix-service" "actix-rt" "actix-codec" }
"actix-tracing" -> { "actix-service" }
"actix-tls" -> { "actix-service" "actix-codec" "actix-utils" }
"actix-testing" -> { "actix-rt" "actix-macros" "actix-server" "actix-service" }
"actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" }
"actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" }
"actix-rt" -> { "actix-macros" "actix-threadpool" }
"actix-connect" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" }
}

View File

@ -2,31 +2,29 @@ digraph {
subgraph cluster_web {
label="actix/actix-web"
"awc"
"web"
"files"
"http"
"multipart"
"web-actors"
"codegen"
"http-test"
"actix-web"
"actix-files"
"actix-http"
"actix-multipart"
"actix-web-actors"
"actix-web-codegen"
"actix-http-test"
}
"web" -> { "codec" "service" "utils" "router" "rt" "server" "testing" "macros" "threadpool" "tls" "codegen" "http" "awc" }
"awc" -> { "codec" "service" "http" "rt" }
"web-actors" -> { "actix" "web" "http" "codec" }
"multipart" -> { "web" "service" "utils" }
"http" -> { "service" "codec" "connect" "utils" "rt" "threadpool" }
"http" -> { "actix" "tls" }[color=blue] // optional
"files" -> { "web" }
"http-test" -> { "service" "codec" "connect" "utils" "rt" "server" "testing" "awc" }
"actix-web" -> { "actix-codec" "actix-service" "actix-utils" "actix-router" "actix-rt" "actix-server" "macros" "threadpool" "actix-tls" "actix-web-codegen" "actix-http" "awc" }
"awc" -> { "actix-codec" "actix-service" "actix-http" "actix-rt" }
"actix-web-actors" -> { "actix" "actix-web" "actix-http" "actix-codec" }
"actix-multipart" -> { "actix-web" "actix-service" "actix-utils" }
"actix-http" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "threadpool" }
"actix-http" -> { "actix" "actix-tls" }[color=blue] // optional
"actix-files" -> { "actix-web" }
"actix-http-test" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "actix-server" "awc" }
// net
"utils" -> { "service" "rt" "codec" }
"tracing" -> { "service" }
"tls" -> { "service" "codec" "utils" }
"testing" -> { "rt" "macros" "server" "service" }
"server" -> { "service" "rt" "codec" "utils" }
"rt" -> { "macros" "threadpool" }
"connect" -> { "service" "codec" "utils" "rt" }
"actix-utils" -> { "actix-service" "actix-rt" "actix-codec" }
"actix-tracing" -> { "actix-service" }
"actix-tls" -> { "actix-service" "actix-codec" "actix-utils" }
"actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" }
"actix-rt" -> { "macros" "threadpool" }
}

View File

@ -18,8 +18,7 @@ async fn no_params() -> &'static str {
#[actix_web::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
env_logger::init();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
HttpServer::new(|| {
App::new()

View File

@ -4,7 +4,7 @@
//! For an example of extracting a client TLS certificate, see:
//! <https://github.com/actix/examples/tree/HEAD/rustls-client-cert>
use std::{any::Any, env, io, net::SocketAddr};
use std::{any::Any, io, net::SocketAddr};
use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer};
@ -36,11 +36,7 @@ fn get_conn_info(connection: &dyn Any, data: &mut Extensions) {
#[actix_web::main]
async fn main() -> io::Result<()> {
if env::var("RUST_LOG").is_err() {
env::set_var("RUST_LOG", "info");
}
env_logger::init();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
HttpServer::new(|| App::new().default_service(web::to(route_whoami)))
.on_connect(get_conn_info)

View File

@ -22,8 +22,7 @@ async fn no_params() -> &'static str {
#[cfg(unix)]
#[actix_web::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
env_logger::init();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
HttpServer::new(|| {
App::new()

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