Merge branch 'master' into feat/awc_response_timeout

This commit is contained in:
fakeshadow 2021-02-12 21:22:58 -08:00 committed by GitHub
commit 9684c9f4e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
131 changed files with 2473 additions and 2184 deletions

View File

@ -1,24 +1,27 @@
name: CI (Linux)
name: CI
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
branches: [master]
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
target:
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
version:
- 1.46.0 # MSRV
- stable
- nightly
name: ${{ matrix.version }} - x86_64-unknown-linux-gnu
runs-on: ubuntu-latest
name: ${{ matrix.target.name }} / ${{ matrix.version }}
runs-on: ${{ matrix.target.os }}
steps:
- uses: actions/checkout@v2
@ -26,7 +29,7 @@ jobs:
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
profile: minimal
override: true
@ -37,18 +40,25 @@ jobs:
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.0.1
- name: check build
- name: check minimal
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
args: --workspace --no-default-features --tests
- name: check full
uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture
args: -v --workspace --all-features --no-fail-fast -- --nocapture
--skip=test_h2_content_length
--skip=test_reading_deflate_encoding_large_random_rustls
- name: tests (actix-http)
uses: actions-rs/cargo@v1
@ -65,12 +75,18 @@ jobs:
args: --package=awc --no-default-features --features=rustls -- --nocapture
- name: Generate coverage file
if: matrix.version == 'stable' && github.ref == 'refs/heads/master'
if: >
matrix.target.os == 'ubuntu-latest'
&& 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'
if: >
matrix.target.os == 'ubuntu-latest'
&& matrix.version == 'stable'
&& github.ref == 'refs/heads/master'
uses: codecov/codecov-action@v1
with:
file: cobertura.xml

View File

@ -1,56 +0,0 @@
name: CI (macOS)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
name: ${{ matrix.version }} - x86_64-apple-darwin
runs-on: macOS-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-apple-darwin
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:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
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

@ -1,76 +0,0 @@
name: CI (Windows)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
env:
VCPKGRS_DYNAMIC: 1
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
name: ${{ matrix.version }} - x86_64-pc-windows-msvc
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-pc-windows-msvc
profile: minimal
override: true
- name: Install OpenSSL
run: |
vcpkg integrate install
vcpkg install openssl:x64-windows
Copy-Item C:\vcpkg\installed\x64-windows\bin\libcrypto-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libcrypto.dll
Copy-Item C:\vcpkg\installed\x64-windows\bin\libssl-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libssl.dll
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:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture
--skip=test_h2_content_length
--skip=test_reading_deflate_encoding_large_random_rustls
--skip=test_params
--skip=test_simple
--skip=test_expect_continue
--skip=test_http10_keepalive
--skip=test_slow_request
--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,26 +1,33 @@
# Changes
## Unreleased - 2021-xx-xx
## 4.0.0-beta.3 - 2021-02-10
* Update `actix-web-codegen` to `0.5.0-beta.1`.
## 4.0.0-beta.2 - 2021-02-10
### Added
* The method `Either<web::Json<T>, web::Form<T>>::into_inner()` which returns the inner type for
whichever variant was created. Also works for `Either<web::Form<T>, web::Json<T>>`. [#1894]
* Add `services!` macro for helping register multiple services to `App`. [#1933]
* Enable registering vector of same type of `HttpServiceFactory` to `App` [#1933]
* Enable registering a vec of services of the same type to `App` [#1933]
### Changed
* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly.
Making it more simple and performant. [#1891]
* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail.
`ServiceRequest::from_request` would not fail and no payload would be generated [#1893]
Making it simpler and more performant. [#1891]
* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893]
* `ServiceRequest::from_request` can no longer fail. [#1893]
* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894]
* `test::{call_service, read_response, read_response_json, send_request}` take `&Service`
* `test::{call_service, read_response, read_response_json, send_request}` take `&Service`
in argument [#1905]
* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` would give `&Service` in closure
argument [#1905]
* `web::block` accept any closure that has an output bound to `Send` and `'static`. [#1957]
* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` provide `&Service` in closure
argument. [#1905]
* `web::block` no longer requires the output is a Result. [#1957]
### Fixed
* Multiple calls `App::data` with the same type now keeps the latest call's data. [#1906]
* Multiple calls to `App::data` with the same type now keeps the latest call's data. [#1906]
### Removed
* Public field of `web::Path` has been made private. [#1894]

View File

@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "4.0.0-beta.1"
version = "4.0.0-beta.3"
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 = ["tls_openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"]
openssl = ["tls-openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"]
# rustls
rustls = ["tls_rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"]
rustls = ["tls-rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"]
[[example]]
name = "basic"
@ -82,9 +82,9 @@ actix-service = "2.0.0-beta.4"
actix-utils = "3.0.0-beta.2"
actix-tls = { version = "3.0.0-beta.3", default-features = false, optional = true }
actix-web-codegen = "0.4.0"
actix-http = "3.0.0-beta.1"
awc = { version = "3.0.0-beta.1", default-features = false }
actix-web-codegen = "0.5.0-beta.1"
actix-http = "3.0.0-beta.3"
awc = { version = "3.0.0-beta.2", default-features = false }
ahash = "0.7"
bytes = "1"
@ -103,13 +103,18 @@ serde_json = "1.0"
serde_urlencoded = "0.7"
time = { version = "0.2.23", default-features = false, features = ["std"] }
url = "2.1"
tls_openssl = { package = "openssl", version = "0.10.9", optional = true }
tls_rustls = { package = "rustls", version = "0.19.0", optional = true }
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
tls-rustls = { package = "rustls", version = "0.19.0", optional = true }
smallvec = "1.6"
[target.'cfg(windows)'.dependencies.tls-openssl]
version = "0.10.9"
package = "openssl"
features = ["vendored"]
optional = true
[dev-dependencies]
actix = { version = "0.11.0-beta.2", default-features = false }
actix-http = { version = "3.0.0-beta.1", features = ["actors"] }
rand = "0.8"
env_logger = "0.8"
serde_derive = "1.0"
@ -117,6 +122,9 @@ brotli2 = "0.3.2"
flate2 = "1.0.13"
criterion = "0.3"
[profile.dev]
debug = false
[profile.release]
lto = true
opt-level = 3

View File

@ -1,15 +1,15 @@
<div align="center">
<h1>Actix web</h1>
<h1>Actix Web</h1>
<p>
<strong>Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust</strong>
</p>
<p>
[![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)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.2)](https://docs.rs/actix-web/4.0.0-beta.2)
[![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)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.2/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.2)
<br />
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)
@ -32,7 +32,6 @@
* SSL support using OpenSSL or Rustls
* 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.46+
## Documentation

View File

@ -1,12 +1,16 @@
# Changes
## Unreleased - 2021-xx-xx
## 0.6.0-beta.2 - 2021-02-10
* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
* Replace `v_htmlescape` with `askama_escape`. [#1953]
[#1887]: https://github.com/actix/actix-web/pull/1887
[#1953]: https://github.com/actix/actix-web/pull/1953
## 0.6.0-beta.1 - 2021-01-07
* `HttpRange::parse` now has its own error type.
* Update `bytes` to `1.0`. [#1813]

View File

@ -1,6 +1,6 @@
[package]
name = "actix-files"
version = "0.6.0-beta.1"
version = "0.6.0-beta.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static file serving for Actix Web"
readme = "README.md"
@ -17,7 +17,7 @@ name = "actix_files"
path = "src/lib.rs"
[dependencies]
actix-web = { version = "4.0.0-beta.1", default-features = false }
actix-web = { version = "4.0.0-beta.3", default-features = false }
actix-service = "2.0.0-beta.4"
askama_escape = "0.10"
@ -33,4 +33,4 @@ percent-encoding = "2.1"
[dev-dependencies]
actix-rt = "2"
actix-web = "4.0.0-beta.1"
actix-web = "4.0.0-beta.3"

View File

@ -49,10 +49,7 @@ impl fmt::Debug for ChunkedReadFile {
impl Stream for ChunkedReadFile {
type Item = Result<Bytes, Error>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.as_mut().get_mut();
match this.state {
ChunkedReadFileState::File(ref mut file) => {
@ -68,16 +65,13 @@ impl Stream for ChunkedReadFile {
.expect("ChunkedReadFile polled after completion");
let fut = spawn_blocking(move || {
let max_bytes =
cmp::min(size.saturating_sub(counter), 65_536) as usize;
let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize;
let mut buf = Vec::with_capacity(max_bytes);
file.seek(io::SeekFrom::Start(offset))?;
let n_bytes = file
.by_ref()
.take(max_bytes as u64)
.read_to_end(&mut buf)?;
let n_bytes =
file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?;
if n_bytes == 0 {
return Err(io::ErrorKind::UnexpectedEof.into());

View File

@ -66,9 +66,7 @@ pub(crate) fn directory_listing(
if dir.is_visible(&entry) {
let entry = entry.unwrap();
let p = match entry.path().strip_prefix(&dir.path) {
Ok(p) if cfg!(windows) => {
base.join(p).to_string_lossy().replace("\\", "/")
}
Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace("\\", "/"),
Ok(p) => base.join(p).to_string_lossy().into_owned(),
Err(_) => continue,
};

View File

@ -2,9 +2,7 @@ use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc};
use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt};
use actix_web::{
dev::{
AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse,
},
dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse},
error::Error,
guard::Guard,
http::header::DispositionType,
@ -13,8 +11,8 @@ use actix_web::{
use futures_util::future::{ok, FutureExt, LocalBoxFuture};
use crate::{
directory_listing, named, Directory, DirectoryRenderer, FilesService,
HttpNewService, MimeOverride,
directory_listing, named, Directory, DirectoryRenderer, FilesService, HttpNewService,
MimeOverride,
};
/// Static files handling service.
@ -129,8 +127,8 @@ impl Files {
/// Set custom directory renderer
pub fn files_listing_renderer<F>(mut self, f: F) -> Self
where
for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) -> Result<ServiceResponse, io::Error>
+ 'static,
for<'r, 's> F:
Fn(&'r Directory, &'s HttpRequest) -> Result<ServiceResponse, io::Error> + 'static,
{
self.renderer = Rc::new(f);
self

View File

@ -98,8 +98,7 @@ mod tests {
#[actix_rt::test]
async fn test_if_modified_since_without_if_none_match() {
let file = NamedFile::open("Cargo.toml").unwrap();
let since =
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
let req = TestRequest::default()
.insert_header((header::IF_MODIFIED_SINCE, since))
@ -123,8 +122,7 @@ mod tests {
#[actix_rt::test]
async fn test_if_modified_since_with_if_none_match() {
let file = NamedFile::open("Cargo.toml").unwrap();
let since =
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
let req = TestRequest::default()
.insert_header((header::IF_NONE_MATCH, "miss_etag"))
@ -212,8 +210,7 @@ mod tests {
#[actix_rt::test]
async fn test_named_file_non_ascii_file_name() {
let mut file =
NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml")
.unwrap();
NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml").unwrap();
{
file.file();
let _f: &File = &file;
@ -605,10 +602,9 @@ mod tests {
#[actix_rt::test]
async fn test_static_files() {
let srv = test::init_service(
App::new().service(Files::new("/", ".").show_files_listing()),
)
.await;
let srv =
test::init_service(App::new().service(Files::new("/", ".").show_files_listing()))
.await;
let req = TestRequest::with_uri("/missing").to_request();
let resp = test::call_service(&srv, req).await;
@ -620,10 +616,9 @@ mod tests {
let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let srv = test::init_service(
App::new().service(Files::new("/", ".").show_files_listing()),
)
.await;
let srv =
test::init_service(App::new().service(Files::new("/", ".").show_files_listing()))
.await;
let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&srv, req).await;
assert_eq!(

View File

@ -11,8 +11,7 @@ use actix_web::{
dev::{BodyEncoding, SizedStream},
http::{
header::{
self, Charset, ContentDisposition, DispositionParam, DispositionType,
ExtendedValue,
self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue,
},
ContentEncoding, StatusCode,
},
@ -395,18 +394,10 @@ impl NamedFile {
resp.encoding(ContentEncoding::Identity);
resp.insert_header((
header::CONTENT_RANGE,
format!(
"bytes {}-{}/{}",
offset,
offset + length - 1,
self.md.len()
),
format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()),
));
} else {
resp.insert_header((
header::CONTENT_RANGE,
format!("bytes */{}", length),
));
resp.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length)));
return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
};
} else {

View File

@ -46,8 +46,7 @@ impl HttpRange {
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(|_| ParseRangeErr(()))?;
let mut length: i64 = end_str.parse().map_err(|_| ParseRangeErr(()))?;
if length > size_sig {
length = size_sig;
@ -72,8 +71,7 @@ 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(|_| ParseRangeErr(()))?;
let mut end: i64 = end_str.parse().map_err(|_| ParseRangeErr(()))?;
if start > end {
return Err(ParseRangeErr(()));

View File

@ -11,8 +11,8 @@ use actix_web::{
use futures_util::future::{ok, Either, LocalBoxFuture, Ready};
use crate::{
named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride,
NamedFile, PathBufWrap,
named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, NamedFile,
PathBufWrap,
};
/// Assembled file serving service.
@ -138,8 +138,7 @@ impl Service<ServiceRequest> for FilesService {
match NamedFile::open(path) {
Ok(mut named_file) => {
if let Some(ref mime_override) = self.mime_override {
let new_disposition =
mime_override(&named_file.content_type.type_());
let new_disposition = mime_override(&named_file.content_type.type_());
named_file.content_disposition.disposition = new_disposition;
}
named_file.flags = self.file_flags;

View File

@ -23,10 +23,9 @@ async fn test_utf8_file_contents() {
);
// prefer UTF-8 encoding
let srv = test::init_service(
App::new().service(Files::new("/", "./tests").prefer_utf8(true)),
)
.await;
let srv =
test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(true)))
.await;
let req = TestRequest::with_uri("/utf8.txt").to_request();
let res = test::call_service(&srv, req).await;

View File

@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx
## 3.0.0-beta.2 - 2021-02-10
* No notable changes.
## 3.0.0-beta.1 - 2021-01-07
* Update `bytes` to `1.0`. [#1813]

View File

@ -1,6 +1,6 @@
[package]
name = "actix-http-test"
version = "3.0.0-beta.1"
version = "3.0.0-beta.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Various helpers for Actix applications to use during testing"
readme = "README.md"
@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.3"
actix-utils = "3.0.0-beta.2"
actix-rt = "2"
actix-server = "2.0.0-beta.3"
awc = "3.0.0-beta.1"
awc = "3.0.0-beta.2"
base64 = "0.13"
bytes = "1"
@ -50,6 +50,12 @@ serde_urlencoded = "0.7"
time = { version = "0.2.23", default-features = false, features = ["std"] }
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
[target.'cfg(windows)'.dependencies.tls-openssl]
version = "0.10.9"
package = "openssl"
features = ["vendored"]
optional = true
[dev-dependencies]
actix-web = "4.0.0-beta.1"
actix-http = "3.0.0-beta.1"
actix-web = "4.0.0-beta.3"
actix-http = "3.0.0-beta.3"

View File

@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=2.1.0)](https://docs.rs/actix-http-test/2.1.0)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http-test)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
[![Dependency Status](https://deps.rs/crate/actix-http-test/2.1.0/status.svg)](https://deps.rs/crate/actix-http-test/2.1.0)
[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

View File

@ -120,8 +120,7 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
/// Get first available unused address
pub fn unused_addr() -> net::SocketAddr {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
let socket =
Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
socket.bind(&addr.into()).unwrap();
socket.set_reuse_address(true).unwrap();
let tcp = socket.into_tcp_listener();
@ -150,7 +149,7 @@ impl TestServer {
}
}
/// Construct test https server url
/// Construct test HTTPS server URL.
pub fn surl(&self, uri: &str) -> String {
if uri.starts_with('/') {
format!("https://localhost:{}{}", self.addr.port(), uri)
@ -164,7 +163,7 @@ impl TestServer {
self.client.get(self.url(path.as_ref()).as_str())
}
/// Create https `GET` request
/// Create HTTPS `GET` request
pub fn sget<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.get(self.surl(path.as_ref()).as_str())
}
@ -174,7 +173,7 @@ impl TestServer {
self.client.post(self.url(path.as_ref()).as_str())
}
/// Create https `POST` request
/// Create HTTPS `POST` request
pub fn spost<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.post(self.surl(path.as_ref()).as_str())
}
@ -184,7 +183,7 @@ impl TestServer {
self.client.head(self.url(path.as_ref()).as_str())
}
/// Create https `HEAD` request
/// Create HTTPS `HEAD` request
pub fn shead<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.head(self.surl(path.as_ref()).as_str())
}
@ -194,7 +193,7 @@ impl TestServer {
self.client.put(self.url(path.as_ref()).as_str())
}
/// Create https `PUT` request
/// Create HTTPS `PUT` request
pub fn sput<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.put(self.surl(path.as_ref()).as_str())
}
@ -204,7 +203,7 @@ impl TestServer {
self.client.patch(self.url(path.as_ref()).as_str())
}
/// Create https `PATCH` request
/// Create HTTPS `PATCH` request
pub fn spatch<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.patch(self.surl(path.as_ref()).as_str())
}
@ -214,7 +213,7 @@ impl TestServer {
self.client.delete(self.url(path.as_ref()).as_str())
}
/// Create https `DELETE` request
/// Create HTTPS `DELETE` request
pub fn sdelete<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.delete(self.surl(path.as_ref()).as_str())
}
@ -224,12 +223,12 @@ impl TestServer {
self.client.options(self.url(path.as_ref()).as_str())
}
/// Create https `OPTIONS` request
/// Create HTTPS `OPTIONS` request
pub fn soptions<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.options(self.surl(path.as_ref()).as_str())
}
/// Connect to test http server
/// Connect to test HTTP server
pub fn request<S: AsRef<str>>(&self, method: Method, path: S) -> ClientRequest {
self.client.request(method, path.as_ref())
}
@ -244,26 +243,24 @@ impl TestServer {
response.body().limit(10_485_760).await
}
/// Connect to websocket server at a given path
/// Connect to WebSocket server at a given path.
pub async fn ws_at(
&mut self,
path: &str,
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError>
{
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError> {
let url = self.url(path);
let connect = self.client.ws(url).connect();
connect.await.map(|(_, framed)| framed)
}
/// Connect to a websocket server
/// Connect to a WebSocket server.
pub async fn ws(
&mut self,
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError>
{
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError> {
self.ws_at("/").await
}
/// Stop http server
/// Stop HTTP server
fn stop(&mut self) {
self.system.stop();
}

View File

@ -1,12 +1,23 @@
# Changes
## Unreleased - 2021-xx-xx
## 3.0.0-beta.3 - 2021-02-10
* No notable changes.
## 3.0.0-beta.2 - 2021-02-10
### Added
* `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869]
* `ResponseBuilder::append_header` method which allows using typed headers. [#1869]
* `TestRequest::insert_header` method which allows using typed headers. [#1869]
* `ContentEncoding` implements all necessary header traits. [#1912]
* `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964]
* `HeaderMap::drain` as an efficient draining iterator. [#1964]
* Implement `IntoIterator` for owned `HeaderMap`. [#1964]
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
### Changed
* `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed
@ -14,11 +25,15 @@
* Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
`TryInto` trait. [#1894]
* `Extensions::insert` returns Option of replaced item. [#1904]
* Remove `HttpResponseBuilder::json2()` and make `HttpResponseBuilder::json()` take a value by
reference. [#1903]
* `client::error::ConnectError` Resolver variant contains `Box<dyn std::error::Error>` type [#1905]
* Remove `HttpResponseBuilder::json2()`. [#1903]
* Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903]
* `client::error::ConnectError` Resolver variant contains `Box<dyn std::error::Error>` type. [#1905]
* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905]
* Simplify `BlockingError` type to a struct. It's only triggered with blocking thread pool is dead. [#1957]
* Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool
is dead. [#1957]
* `HeaderMap::len` now returns number of values instead of number of keys. [#1964]
* `HeaderMap::insert` now returns iterator of removed values. [#1964]
* `HeaderMap::remove` now returns iterator of removed values. [#1964]
### Removed
* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869]
@ -26,6 +41,11 @@
* `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869]
* `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869]
* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869]
* `actors` optional feature. [#1969]
* `ResponseError` impl for `actix::MailboxError`. [#1969]
### Documentation
* Vastly improve docs and add examples for `HeaderMap`. [#1964]
[#1869]: https://github.com/actix/actix-web/pull/1869
[#1894]: https://github.com/actix/actix-web/pull/1894
@ -34,6 +54,8 @@
[#1905]: https://github.com/actix/actix-web/pull/1905
[#1912]: https://github.com/actix/actix-web/pull/1912
[#1957]: https://github.com/actix/actix-web/pull/1957
[#1964]: https://github.com/actix/actix-web/pull/1964
[#1969]: https://github.com/actix/actix-web/pull/1969
## 3.0.0-beta.1 - 2021-01-07
@ -109,15 +131,14 @@
* Update actix-connect and actix-tls dependencies.
## [2.0.0-beta.3] - 2020-08-14
## 2.0.0-beta.3 - 2020-08-14
### Fixed
* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626]
[#1626]: https://github.com/actix/actix-web/pull/1626
## [2.0.0-beta.2] - 2020-07-21
## 2.0.0-beta.2 - 2020-07-21
### Fixed
* Potential UB in h1 decoder using uninitialized memory. [#1614]
@ -128,10 +149,8 @@
[#1615]: https://github.com/actix/actix-web/pull/1615
## [2.0.0-beta.1] - 2020-07-11
## 2.0.0-beta.1 - 2020-07-11
### Changed
* Migrate cookie handling to `cookie` crate. [#1558]
* Update `sha-1` to 0.9. [#1586]
* Fix leak in client pool. [#1580]
@ -141,33 +160,30 @@
[#1586]: https://github.com/actix/actix-web/pull/1586
[#1580]: https://github.com/actix/actix-web/pull/1580
## [2.0.0-alpha.4] - 2020-05-21
## 2.0.0-alpha.4 - 2020-05-21
### Changed
* Bump minimum supported Rust version to 1.40
* content_length function is removed, and you can set Content-Length by calling no_chunking function [#1439]
* content_length function is removed, and you can set Content-Length by calling
no_chunking function [#1439]
* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
`u64` instead of a `usize`.
* Update `base64` dependency to 0.12
### Fixed
* Support parsing of `SameSite=None` [#1503]
[#1439]: https://github.com/actix/actix-web/pull/1439
[#1503]: https://github.com/actix/actix-web/pull/1503
## [2.0.0-alpha.3] - 2020-05-08
## 2.0.0-alpha.3 - 2020-05-08
### Fixed
* Correct spelling of ConnectError::Unresolved [#1487]
* Fix a mistake in the encoding of websocket continuation messages wherein
Item::FirstText and Item::FirstBinary are each encoded as the other.
### Changed
* Implement `std::error::Error` for our custom errors [#1422]
* Remove `failure` support for `ResponseError` since that crate
will be deprecated in the near future.
@ -175,338 +191,247 @@
[#1422]: https://github.com/actix/actix-web/pull/1422
[#1487]: https://github.com/actix/actix-web/pull/1487
## [2.0.0-alpha.2] - 2020-03-07
## 2.0.0-alpha.2 - 2020-03-07
### Changed
* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395]
* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB respectively
to improve download speed for awc when downloading large objects. [#1394]
* client::Connector accepts initial_window_size and initial_connection_window_size HTTP2 configuration. [#1394]
* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB
respectively to improve download speed for awc when downloading large objects. [#1394]
* client::Connector accepts initial_window_size and initial_connection_window_size
HTTP2 configuration. [#1394]
* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394]
[#1394]: https://github.com/actix/actix-web/pull/1394
[#1395]: https://github.com/actix/actix-web/pull/1395
## [2.0.0-alpha.1] - 2020-02-27
## 2.0.0-alpha.1 - 2020-02-27
### Changed
* Update the `time` dependency to 0.2.7.
* Moved actors messages support from actix crate, enabled with feature `actors`.
* Breaking change: trait MessageBody requires Unpin and accepting Pin<&mut Self> instead of &mut self in the poll_next().
* Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of
`&mut self` in the poll_next().
* MessageBody is not implemented for &'static [u8] anymore.
### Fixed
* Allow `SameSite=None` cookies to be sent in a response.
## [1.0.1] - 2019-12-20
## 1.0.1 - 2019-12-20
### Fixed
* Poll upgrade service's readiness from HTTP service handlers
* Replace brotli with brotli2 #1224
## [1.0.0] - 2019-12-13
## 1.0.0 - 2019-12-13
### Added
* Add websockets continuation frame support
### Changed
* Replace `flate2-xxx` features with `compress`
## [1.0.0-alpha.5] - 2019-12-09
## 1.0.0-alpha.5 - 2019-12-09
### Fixed
* Check `Upgrade` service readiness before calling it
* Fix buffer remaining capacity calcualtion
* Fix buffer remaining capacity calculation
### Changed
* Websockets: Ping and Pong should have binary data #1049
## [1.0.0-alpha.4] - 2019-12-08
## 1.0.0-alpha.4 - 2019-12-08
### Added
* Add impl ResponseBuilder for Error
### Changed
* Use rust based brotli compression library
## [1.0.0-alpha.3] - 2019-12-07
## 1.0.0-alpha.3 - 2019-12-07
### Changed
* Migrate to tokio 0.2
* Migrate to `std::future`
## [0.2.11] - 2019-11-06
## 0.2.11 - 2019-11-06
### Added
* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()
* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151)
* Add an additional `filename*` param in the `Content-Disposition` header of
`actix_files::NamedFile` to be more compatible. (#1151)
* Allow to use `std::convert::Infallible` as `actix_http::error::Error`
### Fixed
* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain;
charset=utf-8` header [#1118]
* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header #1118
[#1878]: https://github.com/actix/actix-web/pull/1878
## [0.2.10] - 2019-09-11
## 0.2.10 - 2019-09-11
### Added
* Add support for sending HTTP requests with `Rc<RequestHead>` in addition to sending HTTP requests with `RequestHead`
* Add support for sending HTTP requests with `Rc<RequestHead>` in addition to sending HTTP requests
with `RequestHead`
### Fixed
* h2 will use error response #1080
* on_connect result isn't added to request extensions for http2 requests #1009
## [0.2.9] - 2019-08-13
## 0.2.9 - 2019-08-13
### Changed
* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation
* Update percent-encoding to 2.1
* Update serde_urlencoded to 0.6.1
### Fixed
* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031)
## [0.2.8] - 2019-08-01
## 0.2.8 - 2019-08-01
### Added
* Add `rustls` support
* Add `Clone` impl for `HeaderMap`
### Fixed
* awc client panic #1016
* Invalid response with compression middleware enabled, but compression-related features disabled #997
* Invalid response with compression middleware enabled, but compression-related features
disabled #997
## [0.2.7] - 2019-07-18
## 0.2.7 - 2019-07-18
### Added
* Add support for downcasting response errors #986
## [0.2.6] - 2019-07-17
## 0.2.6 - 2019-07-17
### Changed
* Replace `ClonableService` with local copy
* Upgrade `rand` dependency version to 0.7
## [0.2.5] - 2019-06-28
## 0.2.5 - 2019-06-28
### Added
* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946
### Changed
* Use `encoding_rs` crate instead of unmaintained `encoding` crate
* Add `Copy` and `Clone` impls for `ws::Codec`
## [0.2.4] - 2019-06-16
## 0.2.4 - 2019-06-16
### Fixed
* Do not compress NoContent (204) responses #918
## [0.2.3] - 2019-06-02
## 0.2.3 - 2019-06-02
### Added
* Debug impl for ResponseBuilder
* From SizedStream and BodyStream for Body
### Changed
* SizedStream uses u64
## [0.2.2] - 2019-05-29
## 0.2.2 - 2019-05-29
### Fixed
* Parse incoming stream before closing stream on disconnect #868
## [0.2.1] - 2019-05-25
## 0.2.1 - 2019-05-25
### Fixed
* Handle socket read disconnect
## [0.2.0] - 2019-05-12
## 0.2.0 - 2019-05-12
### Changed
* Update actix-service to 0.4
* Expect and upgrade services accept `ServerConfig` config.
### Deleted
* `OneRequest` service
## [0.1.5] - 2019-05-04
## 0.1.5 - 2019-05-04
### Fixed
* Clean up response extensions in response pool #817
## [0.1.4] - 2019-04-24
## 0.1.4 - 2019-04-24
### Added
* Allow to render h1 request headers in `Camel-Case`
### Fixed
* Read until eof for http/1.0 responses #771
## [0.1.3] - 2019-04-23
## 0.1.3 - 2019-04-23
### Fixed
* Fix http client pool management
* Fix http client wait queue management #794
## [0.1.2] - 2019-04-23
## 0.1.2 - 2019-04-23
### Fixed
* Fix BorrowMutError panic in client connector #793
## [0.1.1] - 2019-04-19
## 0.1.1 - 2019-04-19
### Changed
* Cookie::max_age() accepts value in seconds
* Cookie::max_age_time() accepts value in time::Duration
* Allow to specify server address for client connector
## [0.1.0] - 2019-04-16
## 0.1.0 - 2019-04-16
### Added
* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr`
### Changed
* `actix_http::encoding` always available
* use trust-dns-resolver 0.11.0
## [0.1.0-alpha.5] - 2019-04-12
## 0.1.0-alpha.5 - 2019-04-12
### Added
* Allow to use custom service for upgrade requests
* Added `h1::SendResponse` future.
### Changed
* MessageBody::length() renamed to MessageBody::size() for consistency
* ws handshake verification functions take RequestHead instead of Request
## [0.1.0-alpha.4] - 2019-04-08
## 0.1.0-alpha.4 - 2019-04-08
### Added
* Allow to use custom `Expect` handler
* Add minimal `std::error::Error` impl for `Error`
### Changed
* Export IntoHeaderValue
* Render error and return as response body
* Use thread pool for response body comression
* Use thread pool for response body compression
### Deleted
* Removed PayloadBuffer
## [0.1.0-alpha.3] - 2019-04-02
## 0.1.0-alpha.3 - 2019-04-02
### Added
* Warn when an unsealed private cookie isn't valid UTF-8
### Fixed
* Rust 1.31.0 compatibility
* Preallocate read buffer for h1 codec
* Detect socket disconnection during protocol selection
## [0.1.0-alpha.2] - 2019-03-29
## 0.1.0-alpha.2 - 2019-03-29
### Added
* Added ws::Message::Nop, no-op websockets message
### Changed
* Do not use thread pool for decomression if chunk size is smaller than 2048.
* Do not use thread pool for decompression if chunk size is smaller than 2048.
## [0.1.0-alpha.1] - 2019-03-28
## 0.1.0-alpha.1 - 2019-03-28
* Initial impl

View File

@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "3.0.0-beta.1"
version = "3.0.0-beta.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "HTTP primitives for the Actix ecosystem"
readme = "README.md"
@ -15,7 +15,7 @@ license = "MIT OR Apache-2.0"
edition = "2018"
[package.metadata.docs.rs]
features = ["openssl", "rustls", "compress", "secure-cookies", "actors"]
features = ["openssl", "rustls", "compress", "secure-cookies"]
[lib]
name = "actix_http"
@ -36,8 +36,8 @@ compress = ["flate2", "brotli2"]
# support for secure cookies
secure-cookies = ["cookie/secure"]
# support for actix Actor messages
actors = ["actix"]
# trust-dns as client dns resolver
trust-dns = ["trust-dns-resolver"]
[dependencies]
actix-service = "2.0.0-beta.4"
@ -45,7 +45,6 @@ actix-codec = "0.4.0-beta.1"
actix-utils = "3.0.0-beta.2"
actix-rt = "2"
actix-tls = "3.0.0-beta.2"
actix = { version = "0.11.0-beta.2", default-features = false, optional = true }
base64 = "0.13"
bitflags = "1.2"
@ -53,7 +52,6 @@ bytes = "1"
bytestring = "1"
cookie = { version = "0.14.1", features = ["percent-encode"] }
derive_more = "0.99.5"
either = "1.5.3"
encoding_rs = "0.8"
futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] }
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
@ -84,9 +82,11 @@ time = { version = "0.2.23", default-features = false, features = ["std"] }
brotli2 = { version="0.3.2", optional = true }
flate2 = { version = "1.0.13", optional = true }
trust-dns-resolver = { version = "0.20.0", optional = true }
[dev-dependencies]
actix-server = "2.0.0-beta.3"
actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] }
actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] }
criterion = "0.3"
env_logger = "0.8"
@ -94,6 +94,11 @@ serde_derive = "1.0"
tls-openssl = { version = "0.10", package = "openssl" }
tls-rustls = { version = "0.19", package = "rustls" }
[target.'cfg(windows)'.dev-dependencies.tls-openssl]
version = "0.10.9"
package = "openssl"
features = ["vendored"]
[[bench]]
name = "write-camel-case"
harness = false

View File

@ -3,10 +3,13 @@
> HTTP primitives for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=2.2.0)](https://docs.rs/actix-http/2.2.0)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http)
[![Dependency Status](https://deps.rs/crate/actix-http/2.2.0/status.svg)](https://deps.rs/crate/actix-http/2.2.0)
[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.3)](https://docs.rs/actix-http/3.0.0-beta.3)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.3/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.3)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![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)
## Documentation & Resources

View File

@ -1,6 +1,10 @@
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{fmt, mem};
//! Traits and structures to aid consuming and writing HTTP payloads.
use std::{
fmt, mem,
pin::Pin,
task::{Context, Poll},
};
use bytes::{Bytes, BytesMut};
use futures_core::{ready, Stream};
@ -8,8 +12,8 @@ use pin_project::pin_project;
use crate::error::Error;
/// Body size hint.
#[derive(Debug, PartialEq, Copy, Clone)]
/// Body size hint
pub enum BodySize {
None,
Empty,
@ -23,7 +27,7 @@ impl BodySize {
}
}
/// Type that provides this trait can be streamed to a peer.
/// Type that implement this trait can be streamed to a peer.
pub trait MessageBody {
fn size(&self) -> BodySize;
@ -80,7 +84,7 @@ impl ResponseBody<Body> {
impl<B> ResponseBody<B> {
pub fn take_body(&mut self) -> ResponseBody<B> {
std::mem::replace(self, ResponseBody::Other(Body::None))
mem::replace(self, ResponseBody::Other(Body::None))
}
}
@ -127,7 +131,7 @@ impl<B: MessageBody> Stream for ResponseBody<B> {
}
}
/// Represents various types of http message body.
/// Represents various types of HTTP message body.
pub enum Body {
/// Empty response. `Content-Length` header is not set.
None,

View File

@ -6,7 +6,7 @@ use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream;
use actix_service::{apply_fn, Service, ServiceExt};
use actix_tls::connect::{
default_connector, Connect as TcpConnect, Connection as TcpConnection,
new_connector, Connect as TcpConnect, Connection as TcpConnection, Resolver,
};
use actix_utils::timeout::{TimeoutError, TimeoutService};
use http::Uri;
@ -19,7 +19,6 @@ use super::Connect;
#[cfg(feature = "openssl")]
use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector;
#[cfg(feature = "rustls")]
use actix_tls::connect::ssl::rustls::ClientConfig;
#[cfg(feature = "rustls")]
@ -35,7 +34,8 @@ enum SslConnector {
#[cfg(not(any(feature = "openssl", feature = "rustls")))]
type SslConnector = ();
/// Manages http client network connectivity
/// Manages HTTP client network connectivity.
///
/// The `Connector` type uses a builder-like combinator pattern for service
/// construction that finishes by calling the `.finish()` method.
///
@ -70,7 +70,7 @@ impl Connector<(), ()> {
> {
Connector {
ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
connector: default_connector(),
connector: new_connector(resolver::resolver()),
config: ConnectorConfig::default(),
_phantom: PhantomData,
}
@ -161,8 +161,9 @@ where
self
}
/// Maximum supported http major version
/// Supported versions http/1.1, http/2
/// Maximum supported HTTP major version.
///
/// Supported versions are HTTP/1.1 and HTTP/2.
pub fn max_http_version(mut self, val: http::Version) -> Self {
let versions = match val {
http::Version::HTTP_11 => vec![b"http/1.1".to_vec()],
@ -532,3 +533,82 @@ mod connect_impl {
}
}
}
#[cfg(not(feature = "trust-dns"))]
mod resolver {
use super::*;
pub(super) fn resolver() -> Resolver {
Resolver::Default
}
}
#[cfg(feature = "trust-dns")]
mod resolver {
use std::{cell::RefCell, net::SocketAddr};
use actix_tls::connect::Resolve;
use futures_core::future::LocalBoxFuture;
use trust_dns_resolver::{
config::{ResolverConfig, ResolverOpts},
system_conf::read_system_conf,
TokioAsyncResolver,
};
use super::*;
pub(super) fn resolver() -> Resolver {
// new type for impl Resolve trait for TokioAsyncResolver.
struct TrustDnsResolver(TokioAsyncResolver);
impl Resolve for TrustDnsResolver {
fn lookup<'a>(
&'a self,
host: &'a str,
port: u16,
) -> LocalBoxFuture<'a, Result<Vec<SocketAddr>, Box<dyn std::error::Error>>>
{
Box::pin(async move {
let res = self
.0
.lookup_ip(host)
.await?
.iter()
.map(|ip| SocketAddr::new(ip, port))
.collect();
Ok(res)
})
}
}
// dns struct is cached in thread local.
// so new client constructor can reuse the existing dns resolver.
thread_local! {
static TRUST_DNS_RESOLVER: RefCell<Option<Resolver>> = RefCell::new(None);
}
// get from thread local or construct a new trust-dns resolver.
TRUST_DNS_RESOLVER.with(|local| {
let resolver = local.borrow().as_ref().map(Clone::clone);
match resolver {
Some(resolver) => resolver,
None => {
let (cfg, opts) = match read_system_conf() {
Ok((cfg, opts)) => (cfg, opts),
Err(e) => {
log::error!("TRust-DNS can not load system config: {}", e);
(ResolverConfig::default(), ResolverOpts::default())
}
};
let resolver = TokioAsyncResolver::tokio(cfg, opts).unwrap();
// box trust dns resolver and put it in thread local.
let resolver = Resolver::new_custom(TrustDnsResolver(resolver));
*local.borrow_mut() = Some(resolver.clone());
resolver
}
}
})
}
}

View File

@ -65,13 +65,16 @@ impl From<actix_tls::connect::ConnectError> for ConnectError {
#[derive(Debug, Display, From)]
pub enum InvalidUrl {
#[display(fmt = "Missing url scheme")]
#[display(fmt = "Missing URL scheme")]
MissingScheme,
#[display(fmt = "Unknown url scheme")]
#[display(fmt = "Unknown URL scheme")]
UnknownScheme,
#[display(fmt = "Missing host name")]
MissingHost,
#[display(fmt = "Url parse error: {}", _0)]
#[display(fmt = "URL parse error: {}", _0)]
HttpError(http::Error),
}
@ -83,25 +86,33 @@ pub enum SendRequestError {
/// Invalid URL
#[display(fmt = "Invalid URL: {}", _0)]
Url(InvalidUrl),
/// Failed to connect to host
#[display(fmt = "Failed to connect to host: {}", _0)]
Connect(ConnectError),
/// Error sending request
Send(io::Error),
/// Error parsing response
Response(ParseError),
/// Http error
#[display(fmt = "{}", _0)]
Http(HttpError),
/// Http2 error
#[display(fmt = "{}", _0)]
H2(h2::Error),
/// Response took too long
#[display(fmt = "Timeout while waiting for response")]
Timeout,
/// Tunnels are not supported for http2 connection
/// Tunnels are not supported for HTTP/2 connection
#[display(fmt = "Tunnels are not supported for http2 connection")]
TunnelNotSupported,
/// Error sending request body
Body(Error),
}
@ -127,7 +138,8 @@ pub enum FreezeRequestError {
/// Invalid URL
#[display(fmt = "Invalid URL: {}", _0)]
Url(InvalidUrl),
/// Http error
/// HTTP error
#[display(fmt = "{}", _0)]
Http(HttpError),
}

View File

@ -48,11 +48,11 @@ where
match wrt.get_mut().split().freeze().try_into_value() {
Ok(value) => match head {
RequestHeadType::Owned(ref mut head) => {
head.headers.insert(HOST, value)
head.headers.insert(HOST, value);
}
RequestHeadType::Rc(_, ref mut extra_headers) => {
let headers = extra_headers.get_or_insert(HeaderMap::new());
headers.insert(HOST, value)
headers.insert(HOST, value);
}
},
Err(e) => log::error!("Can not set HOST header {}", e),

View File

@ -1,4 +1,5 @@
//! Http client api
//! HTTP client.
use http::Uri;
mod config;

View File

@ -4,9 +4,11 @@ use std::rc::Rc;
use std::time::Duration;
use std::{fmt, net};
use actix_rt::time::{sleep, sleep_until, Instant, Sleep};
use actix_rt::{
task::JoinHandle,
time::{interval, sleep_until, Instant, Sleep},
};
use bytes::BytesMut;
use futures_util::{future, FutureExt};
use time::OffsetDateTime;
/// "Sun, 06 Nov 1994 08:49:37 GMT".len()
@ -49,7 +51,7 @@ struct Inner {
ka_enabled: bool,
secure: bool,
local_addr: Option<std::net::SocketAddr>,
timer: DateService,
date_service: DateService,
}
impl Clone for ServiceConfig {
@ -91,41 +93,41 @@ impl ServiceConfig {
client_disconnect,
secure,
local_addr,
timer: DateService::new(),
date_service: DateService::new(),
}))
}
/// Returns true if connection is secure (HTTPS)
#[inline]
/// Returns true if connection is secure(https)
pub fn secure(&self) -> bool {
self.0.secure
}
#[inline]
/// Returns the local address that this server is bound to.
#[inline]
pub fn local_addr(&self) -> Option<net::SocketAddr> {
self.0.local_addr
}
#[inline]
/// Keep alive duration if configured.
#[inline]
pub fn keep_alive(&self) -> Option<Duration> {
self.0.keep_alive
}
#[inline]
/// Return state of connection keep-alive functionality
#[inline]
pub fn keep_alive_enabled(&self) -> bool {
self.0.ka_enabled
}
#[inline]
/// Client timeout for first request.
#[inline]
pub fn client_timer(&self) -> Option<Sleep> {
let delay_time = self.0.client_timeout;
if delay_time != 0 {
Some(sleep_until(
self.0.timer.now() + Duration::from_millis(delay_time),
self.0.date_service.now() + Duration::from_millis(delay_time),
))
} else {
None
@ -136,7 +138,7 @@ impl ServiceConfig {
pub fn client_timer_expire(&self) -> Option<Instant> {
let delay = self.0.client_timeout;
if delay != 0 {
Some(self.0.timer.now() + Duration::from_millis(delay))
Some(self.0.date_service.now() + Duration::from_millis(delay))
} else {
None
}
@ -146,7 +148,7 @@ impl ServiceConfig {
pub fn client_disconnect_timer(&self) -> Option<Instant> {
let delay = self.0.client_disconnect;
if delay != 0 {
Some(self.0.timer.now() + Duration::from_millis(delay))
Some(self.0.date_service.now() + Duration::from_millis(delay))
} else {
None
}
@ -156,7 +158,7 @@ impl ServiceConfig {
/// Return keep-alive timer delay is configured.
pub fn keep_alive_timer(&self) -> Option<Sleep> {
if let Some(ka) = self.0.keep_alive {
Some(sleep_until(self.0.timer.now() + ka))
Some(sleep_until(self.0.date_service.now() + ka))
} else {
None
}
@ -165,7 +167,7 @@ impl ServiceConfig {
/// Keep-alive expire time
pub fn keep_alive_expire(&self) -> Option<Instant> {
if let Some(ka) = self.0.keep_alive {
Some(self.0.timer.now() + ka)
Some(self.0.date_service.now() + ka)
} else {
None
}
@ -173,7 +175,7 @@ impl ServiceConfig {
#[inline]
pub(crate) fn now(&self) -> Instant {
self.0.timer.now()
self.0.date_service.now()
}
#[doc(hidden)]
@ -181,7 +183,7 @@ impl ServiceConfig {
let mut buf: [u8; 39] = [0; 39];
buf[..6].copy_from_slice(b"date: ");
self.0
.timer
.date_service
.set_date(|date| buf[6..35].copy_from_slice(&date.bytes));
buf[35..].copy_from_slice(b"\r\n\r\n");
dst.extend_from_slice(&buf);
@ -189,7 +191,7 @@ impl ServiceConfig {
pub(crate) fn set_date_header(&self, dst: &mut BytesMut) {
self.0
.timer
.date_service
.set_date(|date| dst.extend_from_slice(&date.bytes));
}
}
@ -230,57 +232,103 @@ impl fmt::Write for Date {
}
}
#[derive(Clone)]
struct DateService(Rc<DateServiceInner>);
struct DateServiceInner {
current: Cell<Option<(Date, Instant)>>,
/// Service for update Date and Instant periodically at 500 millis interval.
struct DateService {
current: Rc<Cell<(Date, Instant)>>,
handle: JoinHandle<()>,
}
impl DateServiceInner {
fn new() -> Self {
DateServiceInner {
current: Cell::new(None),
}
}
fn reset(&self) {
self.current.take();
}
fn update(&self) {
let now = Instant::now();
let date = Date::new();
self.current.set(Some((date, now)));
impl Drop for DateService {
fn drop(&mut self) {
// stop the timer update async task on drop.
self.handle.abort();
}
}
impl DateService {
fn new() -> Self {
DateService(Rc::new(DateServiceInner::new()))
}
// shared date and timer for DateService and update async task.
let current = Rc::new(Cell::new((Date::new(), Instant::now())));
let current_clone = Rc::clone(&current);
// spawn an async task sleep for 500 milli and update current date/timer in a loop.
// handle is used to stop the task on DateService drop.
let handle = actix_rt::spawn(async move {
#[cfg(test)]
let _notify = notify_on_drop::NotifyOnDrop::new();
fn check_date(&self) {
if self.0.current.get().is_none() {
self.0.update();
let mut interval = interval(Duration::from_millis(500));
loop {
let now = interval.tick().await;
let date = Date::new();
current_clone.set((date, now));
}
});
// periodic date update
let s = self.clone();
actix_rt::spawn(sleep(Duration::from_millis(500)).then(move |_| {
s.0.reset();
future::ready(())
}));
}
DateService { current, handle }
}
fn now(&self) -> Instant {
self.check_date();
self.0.current.get().unwrap().1
self.current.get().1
}
fn set_date<F: FnMut(&Date)>(&self, mut f: F) {
self.check_date();
f(&self.0.current.get().unwrap().0);
f(&self.current.get().0);
}
}
// TODO: move to a util module for testing all spawn handle drop style tasks.
#[cfg(test)]
/// Test Module for checking the drop state of certain async tasks that are spawned
/// with `actix_rt::spawn`
///
/// The target task must explicitly generate `NotifyOnDrop` when spawn the task
mod notify_on_drop {
use std::cell::RefCell;
thread_local! {
static NOTIFY_DROPPED: RefCell<Option<bool>> = RefCell::new(None);
}
/// Check if the spawned task is dropped.
///
/// # Panic:
///
/// When there was no `NotifyOnDrop` instance on current thread
pub(crate) fn is_dropped() -> bool {
NOTIFY_DROPPED.with(|bool| {
bool.borrow()
.expect("No NotifyOnDrop existed on current thread")
})
}
pub(crate) struct NotifyOnDrop;
impl NotifyOnDrop {
/// # Panic:
///
/// When construct multiple instances on any given thread.
pub(crate) fn new() -> Self {
NOTIFY_DROPPED.with(|bool| {
let mut bool = bool.borrow_mut();
if bool.is_some() {
panic!("NotifyOnDrop existed on current thread");
} else {
*bool = Some(false);
}
});
NotifyOnDrop
}
}
impl Drop for NotifyOnDrop {
fn drop(&mut self) {
NOTIFY_DROPPED.with(|bool| {
if let Some(b) = bool.borrow_mut().as_mut() {
*b = true;
}
});
}
}
}
@ -288,14 +336,53 @@ impl DateService {
mod tests {
use super::*;
// Test modifying the date from within the closure
// passed to `set_date`
#[test]
fn test_evil_date() {
let service = DateService::new();
// Make sure that `check_date` doesn't try to spawn a task
service.0.update();
service.set_date(|_| service.0.reset());
use actix_rt::task::yield_now;
#[actix_rt::test]
async fn test_date_service_update() {
let settings = ServiceConfig::new(KeepAlive::Os, 0, 0, false, None);
yield_now().await;
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.set_date(&mut buf1);
let now1 = settings.now();
sleep_until(Instant::now() + Duration::from_secs(2)).await;
yield_now().await;
let now2 = settings.now();
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.set_date(&mut buf2);
assert_ne!(now1, now2);
assert_ne!(buf1, buf2);
drop(settings);
assert!(notify_on_drop::is_dropped());
}
#[actix_rt::test]
async fn test_date_service_drop() {
let service = Rc::new(DateService::new());
// yield so date service have a chance to register the spawned timer update task.
yield_now().await;
let clone1 = service.clone();
let clone2 = service.clone();
let clone3 = service.clone();
drop(clone1);
assert_eq!(false, notify_on_drop::is_dropped());
drop(clone2);
assert_eq!(false, notify_on_drop::is_dropped());
drop(clone3);
assert_eq!(false, notify_on_drop::is_dropped());
drop(service);
assert!(notify_on_drop::is_dropped());
}
#[test]

View File

@ -1,7 +1,11 @@
use std::future::Future;
use std::io::{self, Write};
use std::pin::Pin;
use std::task::{Context, Poll};
//! Stream decoders.
use std::{
future::Future,
io::{self, Write as _},
pin::Pin,
task::{Context, Poll},
};
use actix_rt::task::{spawn_blocking, JoinHandle};
use brotli2::write::BrotliDecoder;
@ -9,11 +13,13 @@ use bytes::Bytes;
use flate2::write::{GzDecoder, ZlibDecoder};
use futures_core::{ready, Stream};
use super::Writer;
use crate::error::{BlockingError, PayloadError};
use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING};
use crate::{
encoding::Writer,
error::{BlockingError, PayloadError},
http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING},
};
const INPLACE: usize = 2049;
const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049;
pub struct Decoder<S> {
decoder: Option<ContentDecoder>,
@ -41,6 +47,7 @@ where
))),
_ => None,
};
Decoder {
decoder,
stream,
@ -53,15 +60,11 @@ where
#[inline]
pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder<S> {
// check content-encoding
let encoding = if let Some(enc) = headers.get(&CONTENT_ENCODING) {
if let Ok(enc) = enc.to_str() {
ContentEncoding::from(enc)
} else {
ContentEncoding::Identity
}
} else {
ContentEncoding::Identity
};
let encoding = headers
.get(&CONTENT_ENCODING)
.and_then(|val| val.to_str().ok())
.map(ContentEncoding::from)
.unwrap_or(ContentEncoding::Identity);
Self::new(stream, encoding)
}
@ -81,8 +84,10 @@ where
if let Some(ref mut fut) = self.fut {
let (chunk, decoder) =
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
self.decoder = Some(decoder);
self.fut.take();
if let Some(chunk) = chunk {
return Poll::Ready(Some(Ok(chunk)));
}
@ -92,13 +97,15 @@ where
return Poll::Ready(None);
}
match Pin::new(&mut self.stream).poll_next(cx) {
Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))),
Poll::Ready(Some(Ok(chunk))) => {
match ready!(Pin::new(&mut self.stream).poll_next(cx)) {
Some(Err(err)) => return Poll::Ready(Some(Err(err))),
Some(Ok(chunk)) => {
if let Some(mut decoder) = self.decoder.take() {
if chunk.len() < INPLACE {
if chunk.len() < MAX_CHUNK_SIZE_DECODE_IN_PLACE {
let chunk = decoder.feed_data(chunk)?;
self.decoder = Some(decoder);
if let Some(chunk) = chunk {
return Poll::Ready(Some(Ok(chunk)));
}
@ -108,13 +115,16 @@ where
Ok((chunk, decoder))
}));
}
continue;
} else {
return Poll::Ready(Some(Ok(chunk)));
}
}
Poll::Ready(None) => {
None => {
self.eof = true;
return if let Some(mut decoder) = self.decoder.take() {
match decoder.feed_eof() {
Ok(Some(res)) => Poll::Ready(Some(Ok(res))),
@ -125,10 +135,8 @@ where
Poll::Ready(None)
};
}
Poll::Pending => break,
}
}
Poll::Pending
}
}
@ -144,6 +152,7 @@ impl ContentDecoder {
ContentDecoder::Br(ref mut decoder) => match decoder.flush() {
Ok(()) => {
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
@ -152,9 +161,11 @@ impl ContentDecoder {
}
Err(e) => Err(e),
},
ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() {
Ok(_) => {
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
@ -163,6 +174,7 @@ impl ContentDecoder {
}
Err(e) => Err(e),
},
ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() {
Ok(_) => {
let b = decoder.get_mut().take();
@ -183,6 +195,7 @@ impl ContentDecoder {
Ok(_) => {
decoder.flush()?;
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
@ -191,10 +204,12 @@ impl ContentDecoder {
}
Err(e) => Err(e),
},
ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => {
decoder.flush()?;
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
@ -203,9 +218,11 @@ impl ContentDecoder {
}
Err(e) => Err(e),
},
ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => {
decoder.flush()?;
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))

View File

@ -1,8 +1,11 @@
//! Stream encoder
use std::future::Future;
use std::io::{self, Write};
use std::pin::Pin;
use std::task::{Context, Poll};
//! Stream encoders.
use std::{
future::Future,
io::{self, Write as _},
pin::Pin,
task::{Context, Poll},
};
use actix_rt::task::{spawn_blocking, JoinHandle};
use brotli2::write::BrotliEncoder;
@ -11,15 +14,19 @@ use flate2::write::{GzEncoder, ZlibEncoder};
use futures_core::ready;
use pin_project::pin_project;
use crate::body::{Body, BodySize, MessageBody, ResponseBody};
use crate::http::header::{ContentEncoding, CONTENT_ENCODING};
use crate::http::{HeaderValue, StatusCode};
use crate::{Error, ResponseHead};
use crate::{
body::{Body, BodySize, MessageBody, ResponseBody},
http::{
header::{ContentEncoding, CONTENT_ENCODING},
HeaderValue, StatusCode,
},
Error, ResponseHead,
};
use super::Writer;
use crate::error::BlockingError;
const INPLACE: usize = 1024;
const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024;
#[pin_project]
pub struct Encoder<B> {
@ -138,23 +145,28 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
if let Some(ref mut fut) = this.fut {
let mut encoder =
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
let chunk = encoder.take();
*this.encoder = Some(encoder);
this.fut.take();
if !chunk.is_empty() {
return Poll::Ready(Some(Ok(chunk)));
}
}
let result = this.body.as_mut().poll_next(cx);
let result = ready!(this.body.as_mut().poll_next(cx));
match result {
Poll::Ready(Some(Ok(chunk))) => {
Some(Err(err)) => return Poll::Ready(Some(Err(err))),
Some(Ok(chunk)) => {
if let Some(mut encoder) = this.encoder.take() {
if chunk.len() < INPLACE {
if chunk.len() < MAX_CHUNK_SIZE_ENCODE_IN_PLACE {
encoder.write(&chunk)?;
let chunk = encoder.take();
*this.encoder = Some(encoder);
if !chunk.is_empty() {
return Poll::Ready(Some(Ok(chunk)));
}
@ -168,7 +180,8 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
return Poll::Ready(Some(Ok(chunk)));
}
}
Poll::Ready(None) => {
None => {
if let Some(encoder) = this.encoder.take() {
let chunk = encoder.finish()?;
if chunk.is_empty() {
@ -181,7 +194,6 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
return Poll::Ready(None);
}
}
val => return val,
}
}
}

View File

@ -1,4 +1,5 @@
//! Content-Encoding support
//! Content-Encoding support.
use std::io;
use bytes::{Bytes, BytesMut};

View File

@ -38,7 +38,7 @@ pub type Result<T, E = Error> = result::Result<T, E>;
/// converting errors with `into()`.
///
/// Whenever it is created from an external object a response error is created
/// for it that can be used to create an http response from it this means that
/// for it that can be used to create an HTTP response from it this means that
/// if you have access to an actix `Error` you can always get a
/// `ResponseError` reference from it.
pub struct Error {
@ -404,7 +404,7 @@ impl ResponseError for crate::cookie::ParseError {
}
#[derive(Debug, Display, From)]
/// A set of errors that can occur during dispatching http requests
/// A set of errors that can occur during dispatching HTTP requests
pub enum DispatchError {
/// Service error
Service(Error),
@ -968,12 +968,6 @@ where
InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into()
}
#[cfg(feature = "actors")]
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix::MailboxError`].
///
/// This is only supported when the feature `actors` is enabled.
impl ResponseError for actix::MailboxError {}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -199,10 +199,10 @@ mod tests {
use http::Method;
use super::*;
use crate::httpmessage::HttpMessage;
use crate::HttpMessage;
#[test]
fn test_http_request_chunked_payload_and_next_message() {
#[actix_rt::test]
async fn test_http_request_chunked_payload_and_next_message() {
let mut codec = Codec::default();
let mut buf = BytesMut::from(

View File

@ -224,7 +224,7 @@ impl MessageType for Request {
let decoder = match length {
PayloadLength::Payload(pl) => pl,
PayloadLength::UpgradeWebSocket => {
// upgrade(websocket)
// upgrade (WebSocket)
PayloadType::Stream(PayloadDecoder::eof())
}
PayloadLength::None => {
@ -652,7 +652,7 @@ mod tests {
use super::*;
use crate::error::ParseError;
use crate::http::header::{HeaderName, SET_COOKIE};
use crate::httpmessage::HttpMessage;
use crate::HttpMessage;
impl PayloadType {
fn unwrap(self) -> PayloadDecoder {
@ -830,8 +830,8 @@ mod tests {
.get_all(SET_COOKIE)
.map(|v| v.to_str().unwrap().to_owned())
.collect();
assert_eq!(val[1], "c1=cookie1");
assert_eq!(val[0], "c2=cookie2");
assert_eq!(val[0], "c1=cookie1");
assert_eq!(val[1], "c2=cookie2");
}
#[test]

View File

@ -37,15 +37,14 @@ bitflags! {
pub struct Flags: u8 {
const STARTED = 0b0000_0001;
const KEEPALIVE = 0b0000_0010;
const POLLED = 0b0000_0100;
const SHUTDOWN = 0b0000_1000;
const READ_DISCONNECT = 0b0001_0000;
const WRITE_DISCONNECT = 0b0010_0000;
const UPGRADE = 0b0100_0000;
const SHUTDOWN = 0b0000_0100;
const READ_DISCONNECT = 0b0000_1000;
const WRITE_DISCONNECT = 0b0001_0000;
const UPGRADE = 0b0010_0000;
}
}
#[pin_project::pin_project]
#[pin_project]
/// Dispatcher for HTTP/1.1 protocol
pub struct Dispatcher<T, S, B, X, U>
where
@ -139,27 +138,14 @@ where
fn is_empty(&self) -> bool {
matches!(self, State::None)
}
fn is_call(&self) -> bool {
matches!(self, State::ServiceCall(_))
}
}
enum PollResponse {
Upgrade(Request),
DoNothing,
DrainWriteBuf,
}
impl PartialEq for PollResponse {
fn eq(&self, other: &PollResponse) -> bool {
match self {
PollResponse::DrainWriteBuf => matches!(other, PollResponse::DrainWriteBuf),
PollResponse::DoNothing => matches!(other, PollResponse::DoNothing),
_ => false,
}
}
}
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
@ -174,62 +160,35 @@ where
{
/// Create HTTP/1 dispatcher.
pub(crate) fn new(
stream: T,
config: ServiceConfig,
services: Rc<HttpFlow<S, X, U>>,
on_connect_data: OnConnectData,
peer_addr: Option<net::SocketAddr>,
) -> Self {
Dispatcher::with_timeout(
stream,
Codec::new(config.clone()),
config,
BytesMut::with_capacity(HW_BUFFER_SIZE),
None,
services,
on_connect_data,
peer_addr,
)
}
/// Create http/1 dispatcher with slow request timeout.
pub(crate) fn with_timeout(
io: T,
codec: Codec,
config: ServiceConfig,
read_buf: BytesMut,
timeout: Option<Sleep>,
services: Rc<HttpFlow<S, X, U>>,
flow: Rc<HttpFlow<S, X, U>>,
on_connect_data: OnConnectData,
peer_addr: Option<net::SocketAddr>,
) -> Self {
let keepalive = config.keep_alive_enabled();
let flags = if keepalive {
let flags = if config.keep_alive_enabled() {
Flags::KEEPALIVE
} else {
Flags::empty()
};
// keep-alive timer
let (ka_expire, ka_timer) = if let Some(delay) = timeout {
(delay.deadline(), Some(delay))
} else if let Some(delay) = config.keep_alive_timer() {
(delay.deadline(), Some(delay))
} else {
(config.now(), None)
let (ka_expire, ka_timer) = match config.keep_alive_timer() {
Some(delay) => (delay.deadline(), Some(delay)),
None => (config.now(), None),
};
Dispatcher {
inner: DispatcherState::Normal(InnerDispatcher {
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
payload: None,
state: State::None,
error: None,
messages: VecDeque::new(),
io: Some(io),
codec,
read_buf,
flow: services,
codec: Codec::new(config),
flow,
on_connect_data,
flags,
peer_addr,
@ -286,15 +245,12 @@ where
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Result<bool, DispatchError> {
let len = self.write_buf.len();
if len == 0 {
return Ok(false);
}
let InnerDispatcherProj { io, write_buf, .. } = self.project();
let mut io = Pin::new(io.as_mut().unwrap());
let len = write_buf.len();
let mut written = 0;
while written < len {
match io.as_mut().poll_write(cx, &write_buf[written..]) {
Poll::Ready(Ok(0)) => {
@ -312,11 +268,13 @@ where
}
}
// SAFETY: setting length to 0 is safe
// skips one length check vs truncate
unsafe { write_buf.set_len(0) }
// everything has written to io. clear buffer.
write_buf.clear();
Ok(false)
// flush the io and check if get blocked.
let blocked = io.poll_flush(cx)?.is_pending();
Ok(blocked)
}
fn send_response(
@ -324,9 +282,10 @@ where
message: Response<()>,
body: ResponseBody<B>,
) -> Result<(), DispatchError> {
let size = body.size();
let mut this = self.project();
this.codec
.encode(Message::Item((message, body.size())), &mut this.write_buf)
.encode(Message::Item((message, size)), &mut this.write_buf)
.map_err(|err| {
if let Some(mut payload) = this.payload.take() {
payload.set_error(PayloadError::Incomplete(None));
@ -335,7 +294,7 @@ where
})?;
this.flags.set(Flags::KEEPALIVE, this.codec.keepalive());
match body.size() {
match size {
BodySize::None | BodySize::Empty => this.state.set(State::None),
_ => this.state.set(State::SendPayload(body)),
};
@ -352,109 +311,121 @@ where
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Result<PollResponse, DispatchError> {
loop {
'res: loop {
let mut this = self.as_mut().project();
// state is not changed on Poll::Pending.
// other variant and conditions always trigger a state change(or an error).
let state_change = match this.state.project() {
match this.state.as_mut().project() {
// no future is in InnerDispatcher state. pop next message.
StateProj::None => match this.messages.pop_front() {
// handle request message.
Some(DispatcherMessage::Item(req)) => {
self.as_mut().handle_request(req, cx)?;
true
// Handle `EXPECT: 100-Continue` header
if req.head().expect() {
// set InnerDispatcher state and continue loop to poll it.
let task = this.flow.expect.call(req);
this.state.set(State::ExpectCall(task));
} else {
// the same as expect call.
let task = this.flow.service.call(req);
this.state.set(State::ServiceCall(task));
};
}
// handle error message.
Some(DispatcherMessage::Error(res)) => {
// send_response would update InnerDispatcher state to SendPayload or
// None(If response body is empty).
// continue loop to poll it.
self.as_mut()
.send_response(res, ResponseBody::Other(Body::Empty))?;
true
}
// return with upgrade request and poll it exclusively.
Some(DispatcherMessage::Upgrade(req)) => {
return Ok(PollResponse::Upgrade(req));
}
None => false,
},
StateProj::ExpectCall(fut) => match fut.poll(cx) {
Poll::Ready(Ok(req)) => {
self.as_mut().send_continue();
this = self.as_mut().project();
let fut = this.flow.service.call(req);
this.state.set(State::ServiceCall(fut));
continue;
}
Poll::Ready(Err(e)) => {
let res: Response = e.into().into();
let (res, body) = res.replace_body(());
self.as_mut().send_response(res, body.into_body())?;
true
}
Poll::Pending => false,
// all messages are dealt with.
None => return Ok(PollResponse::DoNothing),
},
StateProj::ServiceCall(fut) => match fut.poll(cx) {
// service call resolved. send response.
Poll::Ready(Ok(res)) => {
let (res, body) = res.into().replace_body(());
self.as_mut().send_response(res, body)?;
continue;
}
Poll::Ready(Err(e)) => {
let res: Response = e.into().into();
// send service call error as response
Poll::Ready(Err(err)) => {
let res: Response = err.into().into();
let (res, body) = res.replace_body(());
self.as_mut().send_response(res, body.into_body())?;
true
}
Poll::Pending => false,
},
StateProj::SendPayload(mut stream) => {
loop {
if this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
match stream.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => {
this.codec.encode(
Message::Chunk(Some(item)),
&mut this.write_buf,
)?;
continue;
}
Poll::Ready(None) => {
this.codec.encode(
Message::Chunk(None),
&mut this.write_buf,
)?;
this = self.as_mut().project();
this.state.set(State::None);
}
Poll::Ready(Some(Err(_))) => {
return Err(DispatchError::Unknown)
}
Poll::Pending => return Ok(PollResponse::DoNothing),
}
} else {
return Ok(PollResponse::DrainWriteBuf);
// service call pending and could be waiting for more chunk messages.
// (pipeline message limit and/or payload can_read limit)
Poll::Pending => {
// no new message is decoded and no new payload is feed.
// nothing to do except waiting for new incoming data from client.
if !self.as_mut().poll_request(cx)? {
return Ok(PollResponse::DoNothing);
}
break;
// otherwise keep loop.
}
continue;
}
};
},
// state is changed and continue when the state is not Empty
if state_change {
if !self.state.is_empty() {
continue;
}
} else {
// if read-backpressure is enabled and we consumed some data.
// we may read more data and retry
if self.state.is_call() {
if self.as_mut().poll_request(cx)? {
continue;
StateProj::SendPayload(mut stream) => {
// keep populate writer buffer until buffer size limit hit,
// get blocked or finished.
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
match stream.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => {
this.codec.encode(
Message::Chunk(Some(item)),
&mut this.write_buf,
)?;
}
Poll::Ready(None) => {
this.codec
.encode(Message::Chunk(None), &mut this.write_buf)?;
// payload stream finished.
// set state to None and handle next message
this.state.set(State::None);
continue 'res;
}
Poll::Ready(Some(Err(err))) => {
return Err(DispatchError::Service(err))
}
Poll::Pending => return Ok(PollResponse::DoNothing),
}
}
} else if !self.messages.is_empty() {
continue;
// buffer is beyond max size.
// return and try to write the whole buffer to io stream.
return Ok(PollResponse::DrainWriteBuf);
}
StateProj::ExpectCall(fut) => match fut.poll(cx) {
// expect resolved. write continue to buffer and set InnerDispatcher state
// to service call.
Poll::Ready(Ok(req)) => {
this.write_buf
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
let fut = this.flow.service.call(req);
this.state.set(State::ServiceCall(fut));
}
// send expect error as response
Poll::Ready(Err(err)) => {
let res: Response = err.into().into();
let (res, body) = res.replace_body(());
self.as_mut().send_response(res, body.into_body())?;
}
// expect must be solved before progress can be made.
Poll::Pending => return Ok(PollResponse::DoNothing),
},
}
break;
}
Ok(PollResponse::DoNothing)
}
fn handle_request(
@ -494,9 +465,9 @@ where
// future is error. send response and return a result. On success
// to notify the dispatcher a new state is set and the outer loop
// should be continue.
Poll::Ready(Err(e)) => {
let e = e.into();
let res: Response = e.into();
Poll::Ready(Err(err)) => {
let err = err.into();
let res: Response = err.into();
let (res, body) = res.replace_body(());
return self.send_response(res, body.into_body());
}
@ -514,9 +485,9 @@ where
}
// see the comment on ExpectCall state branch's Pending.
Poll::Pending => Ok(()),
// see the comment on ExpectCall state branch's Ready(Err(e)).
Poll::Ready(Err(e)) => {
let res: Response = e.into().into();
// see the comment on ExpectCall state branch's Ready(Err(err)).
Poll::Ready(Err(err)) => {
let res: Response = err.into().into();
let (res, body) = res.replace_body(());
self.send_response(res, body.into_body())
}
@ -608,25 +579,25 @@ where
// decode is partial and buffer is not full yet.
// break and wait for more read.
Ok(None) => break,
Err(ParseError::Io(e)) => {
Err(ParseError::Io(err)) => {
self.as_mut().client_disconnected();
this = self.as_mut().project();
*this.error = Some(DispatchError::Io(e));
*this.error = Some(DispatchError::Io(err));
break;
}
Err(ParseError::TooLarge) => {
if let Some(mut payload) = this.payload.take() {
payload.set_error(PayloadError::Overflow);
}
// Requests overflow buffer size should be responded with 413
// Requests overflow buffer size should be responded with 431
this.messages.push_back(DispatcherMessage::Error(
Response::PayloadTooLarge().finish().drop_body(),
Response::RequestHeaderFieldsTooLarge().finish().drop_body(),
));
this.flags.insert(Flags::READ_DISCONNECT);
*this.error = Some(ParseError::TooLarge.into());
break;
}
Err(e) => {
Err(err) => {
if let Some(mut payload) = this.payload.take() {
payload.set_error(PayloadError::EncodingCorrupted);
}
@ -636,7 +607,7 @@ where
Response::BadRequest().finish().drop_body(),
));
this.flags.insert(Flags::READ_DISCONNECT);
*this.error = Some(e.into());
*this.error = Some(err.into());
break;
}
}
@ -774,7 +745,12 @@ where
// at this point it's not known io is still scheduled to
// be waked up. so force wake up dispatcher just in case.
// TODO: figure out the overhead.
cx.waker().wake_by_ref();
if this.payload.is_none() {
// When dispatcher has a payload. The responsibility of
// wake up stream would be shift to PayloadSender.
// Therefore no self wake up is needed.
cx.waker().wake_by_ref();
}
return Ok(false);
}
@ -839,9 +815,8 @@ where
if inner.flags.contains(Flags::WRITE_DISCONNECT) {
Poll::Ready(Ok(()))
} else {
// flush buffer
inner.as_mut().poll_flush(cx)?;
if !inner.write_buf.is_empty() {
// flush buffer and wait on block.
if inner.as_mut().poll_flush(cx)? {
Poll::Pending
} else {
Pin::new(inner.project().io.as_mut().unwrap())

View File

@ -144,104 +144,54 @@ pub(crate) trait MessageType: Sized {
let k = key.as_str().as_bytes();
let k_len = k.len();
match value {
Value::One(ref val) => {
let v = val.as_ref();
let v_len = v.len();
// TODO: drain?
for val in value.iter() {
let v = val.as_ref();
let v_len = v.len();
// key length + value length + colon + space + \r\n
let len = k_len + v_len + 4;
// key length + value length + colon + space + \r\n
let len = k_len + v_len + 4;
if len > remaining {
// not enough room in buffer for this header; reserve more space
// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
unsafe {
dst.advance_mut(pos);
}
pos = 0;
dst.reserve(len * 2);
remaining = dst.capacity() - dst.len();
// re-assign buf raw pointer since it's possible that the buffer was
// reallocated and/or resized
buf = dst.chunk_mut().as_mut_ptr();
}
// SAFETY: on each write, it is enough to ensure that the advancement of the
// cursor matches the number of bytes written
if len > remaining {
// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
unsafe {
// use upper Camel-Case
if camel_case {
write_camel_case(k, from_raw_parts_mut(buf, k_len))
} else {
write_data(k, buf, k_len)
}
buf = buf.add(k_len);
write_data(b": ", buf, 2);
buf = buf.add(2);
write_data(v, buf, v_len);
buf = buf.add(v_len);
write_data(b"\r\n", buf, 2);
buf = buf.add(2);
dst.advance_mut(pos);
}
pos += len;
remaining -= len;
pos = 0;
dst.reserve(len * 2);
remaining = dst.capacity() - dst.len();
// re-assign buf raw pointer since it's possible that the buffer was
// reallocated and/or resized
buf = dst.chunk_mut().as_mut_ptr();
}
Value::Multi(ref vec) => {
for val in vec {
let v = val.as_ref();
let v_len = v.len();
let len = k_len + v_len + 4;
if len > remaining {
// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
unsafe {
dst.advance_mut(pos);
}
pos = 0;
dst.reserve(len * 2);
remaining = dst.capacity() - dst.len();
// re-assign buf raw pointer since it's possible that the buffer was
// reallocated and/or resized
buf = dst.chunk_mut().as_mut_ptr();
}
// SAFETY: on each write, it is enough to ensure that the advancement of
// the cursor matches the number of bytes written
unsafe {
if camel_case {
write_camel_case(k, from_raw_parts_mut(buf, k_len));
} else {
write_data(k, buf, k_len);
}
buf = buf.add(k_len);
write_data(b": ", buf, 2);
buf = buf.add(2);
write_data(v, buf, v_len);
buf = buf.add(v_len);
write_data(b"\r\n", buf, 2);
buf = buf.add(2);
};
pos += len;
remaining -= len;
// SAFETY: on each write, it is enough to ensure that the advancement of
// the cursor matches the number of bytes written
unsafe {
if camel_case {
// use Camel-Case headers
write_camel_case(k, from_raw_parts_mut(buf, k_len));
} else {
write_data(k, buf, k_len);
}
}
buf = buf.add(k_len);
write_data(b": ", buf, 2);
buf = buf.add(2);
write_data(v, buf, v_len);
buf = buf.add(v_len);
write_data(b"\r\n", buf, 2);
buf = buf.add(2);
};
pos += len;
remaining -= len;
}
});
@ -579,8 +529,8 @@ mod tests {
);
}
#[test]
fn test_camel_case() {
#[actix_rt::test]
async fn test_camel_case() {
let mut bytes = BytesMut::with_capacity(2048);
let mut head = RequestHead::default();
head.set_camel_case_headers(true);
@ -643,8 +593,8 @@ mod tests {
assert!(data.contains("date: date\r\n"));
}
#[test]
fn test_extra_headers() {
#[actix_rt::test]
async fn test_extra_headers() {
let mut bytes = BytesMut::with_capacity(2048);
let mut head = RequestHead::default();
@ -677,8 +627,8 @@ mod tests {
assert!(data.contains("date: date\r\n"));
}
#[test]
fn test_no_content_length() {
#[actix_rt::test]
async fn test_no_content_length() {
let mut bytes = BytesMut::with_capacity(2048);
let mut res: Response<()> =

View File

@ -1,4 +1,4 @@
//! HTTP/1 implementation
//! HTTP/1 protocol implementation.
use bytes::{Bytes, BytesMut};
mod client;

View File

@ -1,4 +1,4 @@
//! HTTP/2 implementation.
//! HTTP/2 protocol.
use std::{
pin::Pin,

View File

@ -243,7 +243,7 @@ where
}
}
/// `Service` implementation for http/2 transport
/// `Service` implementation for HTTP/2 transport
pub struct H2ServiceHandler<T, S, B>
where
S: Service<Request>,

View File

@ -0,0 +1,48 @@
//! Helper trait for types that can be effectively borrowed as a [HeaderValue].
//!
//! [HeaderValue]: crate::http::HeaderValue
use std::{borrow::Cow, str::FromStr};
use http::header::{HeaderName, InvalidHeaderName};
pub trait AsHeaderName: Sealed {}
pub trait Sealed {
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName>;
}
impl Sealed for HeaderName {
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
Ok(Cow::Borrowed(self))
}
}
impl AsHeaderName for HeaderName {}
impl Sealed for &HeaderName {
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
Ok(Cow::Borrowed(*self))
}
}
impl AsHeaderName for &HeaderName {}
impl Sealed for &str {
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
HeaderName::from_str(self).map(Cow::Owned)
}
}
impl AsHeaderName for &str {}
impl Sealed for String {
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
HeaderName::from_str(self).map(Cow::Owned)
}
}
impl AsHeaderName for String {}
impl Sealed for &String {
fn try_as_name(&self) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
HeaderName::from_str(self).map(Cow::Owned)
}
}
impl AsHeaderName for &String {}

View File

@ -5,7 +5,7 @@ use crate::header::{
self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate,
IntoHeaderValue, InvalidHeaderValue, Writer,
};
use crate::httpmessage::HttpMessage;
use crate::HttpMessage;
/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2)
///

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing/conversion and other
//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing and conversion, and other
//! header utility methods.
use std::fmt;
@ -9,8 +9,9 @@ use percent_encoding::{AsciiSet, CONTROLS};
pub use http::header::*;
use crate::error::ParseError;
use crate::httpmessage::HttpMessage;
use crate::HttpMessage;
mod as_name;
mod into_pair;
mod into_value;
mod utils;
@ -23,6 +24,7 @@ pub use self::common::*;
#[doc(hidden)]
pub use self::shared::*;
pub use self::as_name::AsHeaderName;
pub use self::into_pair::IntoHeaderPair;
pub use self::into_value::IntoHeaderValue;
#[doc(hidden)]
@ -39,16 +41,14 @@ pub trait Header: IntoHeaderValue {
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
}
#[doc(hidden)]
#[derive(Debug, Default)]
pub(crate) struct Writer {
buf: BytesMut,
}
impl Writer {
fn new() -> Writer {
Writer {
buf: BytesMut::new(),
}
Writer::default()
}
fn take(&mut self) -> Bytes {
@ -71,12 +71,8 @@ impl fmt::Write for Writer {
/// Convert `http::HeaderMap` to our `HeaderMap`.
impl From<http::HeaderMap> for HeaderMap {
fn from(map: http::HeaderMap) -> HeaderMap {
let mut new_map = HeaderMap::with_capacity(map.capacity());
for (h, v) in map.iter() {
new_map.append(h.clone(), v.clone());
}
new_map
fn from(mut map: http::HeaderMap) -> HeaderMap {
HeaderMap::from_drain(map.drain())
}
}

View File

@ -67,6 +67,14 @@ impl Response {
static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY);
static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS);
static_resp!(
RequestHeaderFieldsTooLarge,
StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE
);
static_resp!(
UnavailableForLegalReasons,
StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS
);
static_resp!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR);
static_resp!(NotImplemented, StatusCode::NOT_IMPLEMENTED);

View File

@ -1,6 +1,6 @@
//! HTTP primitives for the Actix ecosystem.
#![deny(rust_2018_idioms)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![allow(
clippy::type_complexity,
clippy::too_many_arguments,
@ -25,8 +25,8 @@ pub mod encoding;
mod extensions;
mod header;
mod helpers;
mod httpcodes;
pub mod httpmessage;
mod http_codes;
mod http_message;
mod message;
mod payload;
mod request;
@ -45,7 +45,7 @@ pub use self::builder::HttpServiceBuilder;
pub use self::config::{KeepAlive, ServiceConfig};
pub use self::error::{Error, ResponseError, Result};
pub use self::extensions::Extensions;
pub use self::httpmessage::HttpMessage;
pub use self::http_message::HttpMessage;
pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead};
pub use self::payload::{Payload, PayloadStream};
pub use self::request::Request;

View File

@ -13,8 +13,10 @@ use crate::http::{header, Method, StatusCode, Uri, Version};
pub enum ConnectionType {
/// Close connection after response
Close,
/// Keep connection alive after response
KeepAlive,
/// Connection is upgraded to different type
Upgrade,
}

View File

@ -9,9 +9,9 @@ use http::{header, Method, Uri, Version};
use crate::extensions::Extensions;
use crate::header::HeaderMap;
use crate::httpmessage::HttpMessage;
use crate::message::{Message, RequestHead};
use crate::payload::{Payload, PayloadStream};
use crate::HttpMessage;
/// Request
pub struct Request<P = PayloadStream> {
@ -107,7 +107,7 @@ impl<P> Request<P> {
#[inline]
#[doc(hidden)]
/// Mutable reference to a http message part of the request
/// Mutable reference to a HTTP message part of the request
pub fn head_mut(&mut self) -> &mut RequestHead {
&mut *self.head
}
@ -158,10 +158,12 @@ impl<P> Request<P> {
self.head().method == Method::CONNECT
}
/// Peer socket address
/// Peer socket address.
///
/// Peer address is actual socket address, if proxy is used in front of
/// actix http server, then peer address would be address of this proxy.
/// Peer address is the directly connected peer's socket address. If a proxy is used in front of
/// the Actix Web server, then it would be address of this proxy.
///
/// Will only return None when called in unit tests.
#[inline]
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
self.head().peer_addr
@ -177,13 +179,17 @@ impl<P> fmt::Debug for Request<P> {
self.method(),
self.path()
)?;
if let Some(q) = self.uri().query().as_ref() {
writeln!(f, " query: ?{:?}", q)?;
}
writeln!(f, " headers:")?;
for (key, val) in self.headers() {
for (key, val) in self.headers().iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}

View File

@ -32,13 +32,13 @@ pub struct Response<B = Body> {
}
impl Response<Body> {
/// Create http response builder with specific status.
/// Create HTTP response builder with specific status.
#[inline]
pub fn build(status: StatusCode) -> ResponseBuilder {
ResponseBuilder::new(status)
}
/// Create http response builder
/// Create HTTP response builder
#[inline]
pub fn build_from<T: Into<ResponseBuilder>>(source: T) -> ResponseBuilder {
source.into()
@ -97,7 +97,7 @@ impl<B> Response<B> {
}
#[inline]
/// Mutable reference to a http message part of the response
/// Mutable reference to a HTTP message part of the response
pub fn head_mut(&mut self) -> &mut ResponseHead {
&mut *self.head
}
@ -363,7 +363,9 @@ impl ResponseBuilder {
{
if let Some(parts) = parts(&mut self.head, &self.err) {
match header.try_into_header_pair() {
Ok((key, value)) => parts.headers.insert(key, value),
Ok((key, value)) => {
parts.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
};
}
@ -752,9 +754,11 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
let mut msg = BoxedResponseHead::new(head.status);
msg.version = head.version;
msg.reason = head.reason;
for (k, v) in &head.headers {
for (k, v) in head.headers.iter() {
msg.headers.append(k.clone(), v.clone());
}
msg.no_chunking(!head.chunked());
ResponseBuilder {
@ -863,6 +867,7 @@ mod tests {
use super::*;
use crate::body::Body;
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE};
use crate::HttpMessage;
#[test]
fn test_debug() {
@ -876,8 +881,6 @@ mod tests {
#[test]
fn test_response_cookies() {
use crate::httpmessage::HttpMessage;
let req = crate::test::TestRequest::default()
.append_header((COOKIE, "cookie1=value1"))
.append_header((COOKIE, "cookie2=value2"))
@ -893,16 +896,20 @@ mod tests {
.max_age(time::Duration::days(1))
.finish(),
)
.del_cookie(&cookies[1])
.del_cookie(&cookies[0])
.finish();
let mut val: Vec<_> = resp
let mut val = resp
.headers()
.get_all(SET_COOKIE)
.map(|v| v.to_str().unwrap().to_owned())
.collect();
.collect::<Vec<_>>();
val.sort();
// the .del_cookie call
assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
// the .cookie call
assert_eq!(
val[1],
"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"
@ -927,9 +934,9 @@ mod tests {
let mut iter = r.cookies();
let v = iter.next().unwrap();
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
let v = iter.next().unwrap();
assert_eq!((v.name(), v.value()), ("original", "val100"));
let v = iter.next().unwrap();
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
}
#[test]

View File

@ -432,7 +432,7 @@ where
}
}
/// `Service` implementation for http transport
/// `Service` implementation for HTTP transport
pub struct HttpServiceHandler<T, S, B, X, U>
where
S: Service<Request>,

View File

@ -8,35 +8,65 @@ pub fn parse_http_date(time: &str) -> Option<PrimitiveDateTime> {
}
/// Attempt to parse a `time` string as a RFC 1123 formatted date time string.
///
/// Eg: `Fri, 12 Feb 2021 00:14:29 GMT`
fn try_parse_rfc_1123(time: &str) -> Option<PrimitiveDateTime> {
time::parse(time, "%a, %d %b %Y %H:%M:%S").ok()
}
/// Attempt to parse a `time` string as a RFC 850 formatted date time string.
///
/// Eg: `Wednesday, 11-Jan-21 13:37:41 UTC`
fn try_parse_rfc_850(time: &str) -> Option<PrimitiveDateTime> {
match PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S") {
Ok(dt) => {
// If the `time` string contains a two-digit year, then as per RFC 2616 § 19.3,
// we consider the year as part of this century if it's within the next 50 years,
// otherwise we consider as part of the previous century.
let now = OffsetDateTime::now_utc();
let century_start_year = (now.year() / 100) * 100;
let mut expanded_year = century_start_year + dt.year();
let dt = PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S").ok()?;
if expanded_year > now.year() + 50 {
expanded_year -= 100;
}
// If the `time` string contains a two-digit year, then as per RFC 2616 § 19.3,
// we consider the year as part of this century if it's within the next 50 years,
// otherwise we consider as part of the previous century.
match Date::try_from_ymd(expanded_year, dt.month(), dt.day()) {
Ok(date) => Some(PrimitiveDateTime::new(date, dt.time())),
Err(_) => None,
}
}
Err(_) => None,
let now = OffsetDateTime::now_utc();
let century_start_year = (now.year() / 100) * 100;
let mut expanded_year = century_start_year + dt.year();
if expanded_year > now.year() + 50 {
expanded_year -= 100;
}
let date = Date::try_from_ymd(expanded_year, dt.month(), dt.day()).ok()?;
Some(PrimitiveDateTime::new(date, dt.time()))
}
/// Attempt to parse a `time` string using ANSI C's `asctime` format.
///
/// Eg: `Wed Feb 13 15:46:11 2013`
fn try_parse_asctime(time: &str) -> Option<PrimitiveDateTime> {
time::parse(time, "%a %b %_d %H:%M:%S %Y").ok()
}
#[cfg(test)]
mod tests {
use time::{date, time};
use super::*;
#[test]
fn test_rfc_850_year_shift() {
let date = try_parse_rfc_850("Friday, 19-Nov-82 16:14:55 EST").unwrap();
assert_eq!(date, date!(1982 - 11 - 19).with_time(time!(16:14:55)));
let date = try_parse_rfc_850("Wednesday, 11-Jan-62 13:37:41 EST").unwrap();
assert_eq!(date, date!(2062 - 01 - 11).with_time(time!(13:37:41)));
let date = try_parse_rfc_850("Wednesday, 11-Jan-21 13:37:41 EST").unwrap();
assert_eq!(date, date!(2021 - 01 - 11).with_time(time!(13:37:41)));
let date = try_parse_rfc_850("Wednesday, 11-Jan-23 13:37:41 EST").unwrap();
assert_eq!(date, date!(2023 - 01 - 11).with_time(time!(13:37:41)));
let date = try_parse_rfc_850("Wednesday, 11-Jan-99 13:37:41 EST").unwrap();
assert_eq!(date, date!(1999 - 01 - 11).with_time(time!(13:37:41)));
let date = try_parse_rfc_850("Wednesday, 11-Jan-00 13:37:41 EST").unwrap();
assert_eq!(date, date!(2000 - 01 - 11).with_time(time!(13:37:41)));
}
}

View File

@ -54,7 +54,7 @@ pub enum Frame {
Close(Option<CloseReason>),
}
/// A `WebSocket` continuation item.
/// A WebSocket continuation item.
#[derive(Debug, PartialEq)]
pub enum Item {
FirstText(Bytes),
@ -79,7 +79,7 @@ bitflags! {
}
impl Codec {
/// Create new websocket frames decoder.
/// Create new WebSocket frames decoder.
pub fn new() -> Codec {
Codec {
max_size: 65_536,

View File

@ -7,7 +7,7 @@ use crate::ws::mask::apply_mask;
use crate::ws::proto::{CloseCode, CloseReason, OpCode};
use crate::ws::ProtocolError;
/// A struct representing a `WebSocket` frame.
/// A struct representing a WebSocket frame.
#[derive(Debug)]
pub struct Parser;
@ -16,7 +16,8 @@ impl Parser {
src: &[u8],
server: bool,
max_size: usize,
) -> Result<Option<(usize, bool, OpCode, usize, Option<u32>)>, ProtocolError> {
) -> Result<Option<(usize, bool, OpCode, usize, Option<[u8; 4]>)>, ProtocolError>
{
let chunk_len = src.len();
let mut idx = 2;
@ -77,9 +78,10 @@ impl Parser {
return Ok(None);
}
let mask =
u32::from_le_bytes(TryFrom::try_from(&src[idx..idx + 4]).unwrap());
let mask = TryFrom::try_from(&src[idx..idx + 4]).unwrap();
idx += 4;
Some(mask)
} else {
None
@ -187,8 +189,8 @@ impl Parser {
};
if mask {
let mask = rand::random::<u32>();
dst.put_u32_le(mask);
let mask = rand::random::<[u8; 4]>();
dst.put_slice(mask.as_ref());
dst.put_slice(payload.as_ref());
let pos = dst.len() - payload_len;
apply_mask(&mut dst[pos..], mask);

View File

@ -1,136 +1,57 @@
//! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs)
#![allow(clippy::cast_ptr_alignment)]
use std::ptr::copy_nonoverlapping;
use std::slice;
/// Holds a slice guaranteed to be shorter than 8 bytes.
struct ShortSlice<'a> {
inner: &'a mut [u8],
}
impl<'a> ShortSlice<'a> {
/// # Safety
/// Given slice must be shorter than 8 bytes.
unsafe fn new(slice: &'a mut [u8]) -> Self {
// Sanity check for debug builds
debug_assert!(slice.len() < 8);
ShortSlice { inner: slice }
}
fn len(&self) -> usize {
self.inner.len()
}
}
/// Faster version of `apply_mask()` which operates on 8-byte blocks.
/// Mask/unmask a frame.
#[inline]
#[allow(clippy::cast_lossless)]
pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
// Extend the mask to 64 bits
let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64);
// Split the buffer into three segments
let (head, mid, tail) = align_buf(buf);
pub fn apply_mask(buf: &mut [u8], mask: [u8; 4]) {
apply_mask_fast32(buf, mask)
}
// Initial unaligned segment
let head_len = head.len();
if head_len > 0 {
xor_short(head, mask_u64);
/// A safe unoptimized mask application.
#[inline]
fn apply_mask_fallback(buf: &mut [u8], mask: [u8; 4]) {
for (i, byte) in buf.iter_mut().enumerate() {
*byte ^= mask[i & 3];
}
}
/// Faster version of `apply_mask()` which operates on 4-byte blocks.
#[inline]
pub fn apply_mask_fast32(buf: &mut [u8], mask: [u8; 4]) {
let mask_u32 = u32::from_ne_bytes(mask);
// SAFETY:
//
// buf is a valid slice borrowed mutably from bytes::BytesMut.
//
// un aligned prefix and suffix would be mask/unmask per byte.
// proper aligned middle slice goes into fast path and operates on 4-byte blocks.
let (mut prefix, words, mut suffix) = unsafe { buf.align_to_mut::<u32>() };
apply_mask_fallback(&mut prefix, mask);
let head = prefix.len() & 3;
let mask_u32 = if head > 0 {
if cfg!(target_endian = "big") {
mask_u64 = mask_u64.rotate_left(8 * head_len as u32);
mask_u32.rotate_left(8 * head as u32)
} else {
mask_u64 = mask_u64.rotate_right(8 * head_len as u32);
}
}
// Aligned segment
for v in mid {
*v ^= mask_u64;
}
// Final unaligned segment
if tail.len() > 0 {
xor_short(tail, mask_u64);
}
}
// TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so
// inefficient, it could be done better. The compiler does not understand that
// a `ShortSlice` must be smaller than a u64.
#[inline]
#[allow(clippy::needless_pass_by_value)]
fn xor_short(buf: ShortSlice<'_>, mask: u64) {
// SAFETY: we know that a `ShortSlice` fits in a u64
unsafe {
let (ptr, len) = (buf.inner.as_mut_ptr(), buf.len());
let mut b: u64 = 0;
#[allow(trivial_casts)]
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
b ^= mask;
#[allow(trivial_casts)]
copy_nonoverlapping(&b as *const _ as *const u8, ptr, len);
}
}
/// # Safety
/// Caller must ensure the buffer has the correct size and alignment.
#[inline]
unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] {
// Assert correct size and alignment in debug builds
debug_assert!(buf.len().trailing_zeros() >= 3);
debug_assert!((buf.as_ptr() as usize).trailing_zeros() >= 3);
slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3)
}
/// Splits a slice into three parts:
/// - an unaligned short head
/// - an aligned `u64` slice mid section
/// - an unaligned short tail
#[inline]
fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) {
let start_ptr = buf.as_ptr() as usize;
let end_ptr = start_ptr + buf.len();
// Round *up* to next aligned boundary for start
let start_aligned = (start_ptr + 7) & !0x7;
// Round *down* to last aligned boundary for end
let end_aligned = end_ptr & !0x7;
if end_aligned >= start_aligned {
// We have our three segments (head, mid, tail)
let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr);
let (head, mid) = tmp.split_at_mut(start_aligned - start_ptr);
// SAFETY: we know the middle section is correctly aligned, and the outer
// sections are smaller than 8 bytes
unsafe {
(
ShortSlice::new(head),
cast_slice(mid),
ShortSlice::new(tail),
)
mask_u32.rotate_right(8 * head as u32)
}
} else {
// We didn't cross even one aligned boundary!
// SAFETY: The outer sections are smaller than 8 bytes
unsafe { (ShortSlice::new(buf), &mut [], ShortSlice::new(&mut [])) }
mask_u32
};
for word in words.iter_mut() {
*word ^= mask_u32;
}
apply_mask_fallback(&mut suffix, mask_u32.to_ne_bytes());
}
#[cfg(test)]
mod tests {
use super::apply_mask;
/// A safe unoptimized mask application.
fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) {
for (i, byte) in buf.iter_mut().enumerate() {
*byte ^= mask[i & 3];
}
}
use super::*;
// legacy test from old apply mask test. kept for now for back compat test.
// TODO: remove it and favor the other test.
#[test]
fn test_apply_mask() {
fn test_apply_mask_legacy() {
let mask = [0x6d, 0xb6, 0xb2, 0x80];
let mask_u32 = u32::from_le_bytes(mask);
let unmasked = vec![
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
@ -140,10 +61,10 @@ mod tests {
// Check masking with proper alignment.
{
let mut masked = unmasked.clone();
apply_mask_fallback(&mut masked, &mask);
apply_mask_fallback(&mut masked, mask);
let mut masked_fast = unmasked.clone();
apply_mask(&mut masked_fast, mask_u32);
apply_mask(&mut masked_fast, mask);
assert_eq!(masked, masked_fast);
}
@ -151,12 +72,38 @@ mod tests {
// Check masking without alignment.
{
let mut masked = unmasked.clone();
apply_mask_fallback(&mut masked[1..], &mask);
apply_mask_fallback(&mut masked[1..], mask);
let mut masked_fast = unmasked;
apply_mask(&mut masked_fast[1..], mask_u32);
apply_mask(&mut masked_fast[1..], mask);
assert_eq!(masked, masked_fast);
}
}
#[test]
fn test_apply_mask() {
let mask = [0x6d, 0xb6, 0xb2, 0x80];
let unmasked = vec![
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
0x74, 0xf9, 0x12, 0x03,
];
for data_len in 0..=unmasked.len() {
let unmasked = &unmasked[0..data_len];
// Check masking with different alignment.
for off in 0..=3 {
if unmasked.len() < off {
continue;
}
let mut masked = unmasked.to_vec();
apply_mask_fallback(&mut masked[off..], mask);
let mut masked_fast = unmasked.to_vec();
apply_mask_fast32(&mut masked_fast[off..], mask);
assert_eq!(masked, masked_fast);
}
}
}
}

View File

@ -1,6 +1,6 @@
//! WebSocket protocol support.
//! WebSocket protocol.
//!
//! To setup a WebSocket, first do web socket handshake then on success convert `Payload` into a
//! To setup a WebSocket, first perform the WebSocket handshake then on success convert `Payload` into a
//! `WsStream` stream and then use `WsWriter` to communicate with the peer.
use std::io;
@ -76,7 +76,7 @@ pub enum HandshakeError {
#[display(fmt = "Method not allowed.")]
GetMethodRequired,
/// Upgrade header if not set to websocket.
/// Upgrade header if not set to WebSocket.
#[display(fmt = "WebSocket upgrade is expected.")]
NoWebsocketUpgrade,
@ -88,7 +88,7 @@ pub enum HandshakeError {
#[display(fmt = "WebSocket version header is required.")]
NoVersionHeader,
/// Unsupported websocket version.
/// Unsupported WebSocket version.
#[display(fmt = "Unsupported version.")]
UnsupportedVersion,
@ -127,20 +127,20 @@ impl ResponseError for HandshakeError {
}
}
/// Verify `WebSocket` handshake request and create handshake response.
/// Verify WebSocket handshake request and create handshake response.
pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
verify_handshake(req)?;
Ok(handshake_response(req))
}
/// Verify `WebSocket` handshake request.
/// Verify WebSocket handshake request.
pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
// WebSocket accepts only GET
if req.method != Method::GET {
return Err(HandshakeError::GetMethodRequired);
}
// Check for "UPGRADE" to websocket header
// Check for "UPGRADE" to WebSocket header
let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) {
if let Ok(s) = hdr.to_str() {
s.to_ascii_lowercase().contains("websocket")
@ -181,7 +181,7 @@ pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
Ok(())
}
/// Create websocket handshake response
/// Create WebSocket handshake response.
///
/// This function returns handshake `Response`, ready to send to peer.
pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {

View File

@ -74,8 +74,7 @@ impl From<u8> for OpCode {
}
}
/// Status code used to indicate why an endpoint is closing the `WebSocket`
/// connection.
/// Status code used to indicate why an endpoint is closing the WebSocket connection.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum CloseCode {
/// Indicates a normal closure, meaning that the purpose for

View File

@ -7,7 +7,7 @@ use std::io;
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::HttpMessage;
use actix_http::{body, Error, HttpService, Request, Response};
use actix_http_test::test_server;
use actix_service::{fn_service, ServiceFactoryExt};

View File

@ -10,7 +10,7 @@ use futures_util::future::{self, err, ok, ready, FutureExt};
use futures_util::stream::{once, StreamExt};
use regex::Regex;
use actix_http::httpmessage::HttpMessage;
use actix_http::HttpMessage;
use actix_http::{
body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response,
};

View File

@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx
## 0.4.0-beta.2 - 2021-02-10
* No notable changes.
## 0.4.0-beta.1 - 2021-01-07
* Fix multipart consuming payload before header checks. [#1513]
* Update `bytes` to `1.0`. [#1813]

View File

@ -1,8 +1,8 @@
[package]
name = "actix-multipart"
version = "0.4.0-beta.1"
version = "0.4.0-beta.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Multipart support for actix web framework."
description = "Multipart form support for Actix Web"
readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs"
@ -16,7 +16,7 @@ name = "actix_multipart"
path = "src/lib.rs"
[dependencies]
actix-web = { version = "4.0.0-beta.1", default-features = false }
actix-web = { version = "4.0.0-beta.3", default-features = false }
actix-utils = "3.0.0-beta.2"
bytes = "1"
@ -29,4 +29,4 @@ twoway = "0.2"
[dev-dependencies]
actix-rt = "2"
actix-http = "3.0.0-beta.1"
actix-http = "3.0.0-beta.3"

View File

@ -1,8 +1,18 @@
# Multipart support for actix web framework [![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) [![crates.io](https://meritbadge.herokuapp.com/actix-multipart)](https://crates.io/crates/actix-multipart) [![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)
# actix-multipart
## Documentation & community resources
> Multipart form support for Actix Web.
* [API Documentation](https://docs.rs/actix-multipart/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-multipart](https://crates.io/crates/actix-multipart)
* Minimum supported Rust version: 1.40 or later
[![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart)
[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.2)](https://docs.rs/actix-multipart/0.4.0-beta.2)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.2/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.2)
[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart)
[![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)
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart)
- [Chat on Gitter](https://gitter.im/actix/actix-web)
- Minimum Supported Rust Version (MSRV): 1.46.0

View File

@ -1,4 +1,4 @@
//! Multipart form support for Actix web.
//! Multipart form support for Actix Web.
#![deny(rust_2018_idioms)]
#![allow(clippy::borrow_interior_mutable_const)]

View File

@ -13,9 +13,7 @@ use futures_util::stream::{LocalBoxStream, Stream, StreamExt};
use actix_utils::task::LocalWaker;
use actix_web::error::{ParseError, PayloadError};
use actix_web::http::header::{
self, ContentDisposition, HeaderMap, HeaderName, HeaderValue,
};
use actix_web::http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue};
use crate::error::MultipartError;
@ -120,10 +118,7 @@ impl Multipart {
impl Stream for Multipart {
type Item = Result<Field, MultipartError>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if let Some(err) = self.error.take() {
Poll::Ready(Some(Err(err)))
} else if self.safety.current() {
@ -142,9 +137,7 @@ impl Stream for Multipart {
}
impl InnerMultipart {
fn read_headers(
payload: &mut PayloadBuffer,
) -> Result<Option<HeaderMap>, MultipartError> {
fn read_headers(payload: &mut PayloadBuffer) -> Result<Option<HeaderMap>, MultipartError> {
match payload.read_until(b"\r\n\r\n")? {
None => {
if payload.eof {
@ -226,8 +219,7 @@ impl InnerMultipart {
if chunk.len() < boundary.len() {
continue;
}
if &chunk[..2] == b"--"
&& &chunk[2..chunk.len() - 2] == boundary.as_bytes()
if &chunk[..2] == b"--" && &chunk[2..chunk.len() - 2] == boundary.as_bytes()
{
break;
} else {
@ -273,9 +265,7 @@ impl InnerMultipart {
match field.borrow_mut().poll(safety) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Some(Ok(_))) => continue,
Poll::Ready(Some(Err(e))) => {
return Poll::Ready(Some(Err(e)))
}
Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))),
Poll::Ready(None) => true,
}
}
@ -311,10 +301,7 @@ impl InnerMultipart {
}
// read boundary
InnerState::Boundary => {
match InnerMultipart::read_boundary(
&mut *payload,
&self.boundary,
)? {
match InnerMultipart::read_boundary(&mut *payload, &self.boundary)? {
None => return Poll::Pending,
Some(eof) => {
if eof {
@ -418,8 +405,7 @@ impl Field {
pub fn content_disposition(&self) -> Option<ContentDisposition> {
// RFC 7578: 'Each part MUST contain a Content-Disposition header field
// where the disposition type is "form-data".'
if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION)
{
if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION) {
ContentDisposition::from_raw(content_disposition).ok()
} else {
None
@ -430,15 +416,10 @@ impl Field {
impl Stream for Field {
type Item = Result<Bytes, MultipartError>;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if self.safety.current() {
let mut inner = self.inner.borrow_mut();
if let Some(mut payload) =
inner.payload.as_ref().unwrap().get_mut(&self.safety)
{
if let Some(mut payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety) {
payload.poll_stream(cx)?;
}
inner.poll(&self.safety)
@ -607,8 +588,7 @@ impl InnerField {
return Poll::Ready(None);
}
let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s)
{
let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) {
if !self.eof {
let res = if let Some(ref mut len) = self.length {
InnerField::read_len(&mut *payload, len)
@ -628,7 +608,9 @@ impl InnerField {
Ok(None) => Poll::Pending,
Ok(Some(line)) => {
if line.as_ref() != b"\r\n" {
log::warn!("multipart field did not read all the data or it is malformed");
log::warn!(
"multipart field did not read all the data or it is malformed"
);
}
Poll::Ready(None)
}
@ -804,9 +786,7 @@ impl PayloadBuffer {
/// Read bytes until new line delimiter or eof
pub fn readline_or_eof(&mut self) -> Result<Option<Bytes>, MultipartError> {
match self.readline() {
Err(MultipartError::Incomplete) if self.eof => {
Ok(Some(self.buf.split().freeze()))
}
Err(MultipartError::Incomplete) if self.eof => Ok(Some(self.buf.split().freeze())),
line => line,
}
}
@ -902,10 +882,7 @@ mod tests {
impl Stream for SlowStream {
type Item = Result<Bytes, PayloadError>;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
if !this.ready {
this.ready = true;

View File

@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx
## 4.0.0-beta.2 - 2021-02-10
* No notable changes.
## 4.0.0-beta.1 - 2021-01-07
* Update `pin-project` to `1.0`.
* Update `bytes` to `1.0`. [#1813]

View File

@ -1,8 +1,8 @@
[package]
name = "actix-web-actors"
version = "4.0.0-beta.1"
version = "4.0.0-beta.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for actix web framework."
description = "Actix actors support for Actix Web"
readme = "README.md"
keywords = ["actix", "http", "web", "framework", "async"]
homepage = "https://actix.rs"
@ -18,8 +18,8 @@ path = "src/lib.rs"
[dependencies]
actix = { version = "0.11.0-beta.2", default-features = false }
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 }
actix-http = "3.0.0-beta.3"
actix-web = { version = "4.0.0-beta.3", default-features = false }
bytes = "1"
bytestring = "1"

View File

@ -1,8 +1,18 @@
Actix actors support for actix web framework [![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) [![crates.io](https://meritbadge.herokuapp.com/actix-web-actors)](https://crates.io/crates/actix-web-actors) [![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)
# actix-web-actors
## Documentation & community resources
> Actix actors support for Actix Web.
* [API Documentation](https://docs.rs/actix-web-actors/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web-actors](https://crates.io/crates/actix-web-actors)
* Minimum supported Rust version: 1.40 or later
[![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=0.5.0)](https://docs.rs/actix-web-actors/0.5.0)
[![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-actors.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-web-actors/0.5.0/status.svg)](https://deps.rs/crate/actix-web-actors/0.5.0)
[![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors)
[![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)
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-web-actors)
- [Chat on Gitter](https://gitter.im/actix/actix-web)
- Minimum supported Rust version: 1.46 or later

View File

@ -3,9 +3,7 @@ use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix::dev::{
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope,
};
use actix::dev::{AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope};
use actix::fut::ActorFuture;
use actix::{
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
@ -15,7 +13,7 @@ use bytes::Bytes;
use futures_core::Stream;
use tokio::sync::oneshot::Sender;
/// Execution context for http actors
/// Execution context for HTTP actors
pub struct HttpContext<A>
where
A: Actor<Context = HttpContext<A>>,
@ -165,10 +163,7 @@ where
{
type Item = Result<Bytes, Error>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if self.fut.alive() {
let _ = Pin::new(&mut self.fut).poll(cx);
}
@ -233,10 +228,11 @@ mod tests {
#[actix_rt::test]
async fn test_default_resource() {
let srv = init_service(App::new().service(web::resource("/test").to(|| {
HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 }))
})))
.await;
let srv =
init_service(App::new().service(web::resource("/test").to(|| {
HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 }))
})))
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&srv, req).await;

View File

@ -1,4 +1,4 @@
//! Actix actors integration for Actix web framework
//! Actix actors support for Actix Web.
#![deny(rust_2018_idioms)]
#![allow(clippy::borrow_interior_mutable_const)]

View File

@ -7,13 +7,12 @@ use std::task::{Context, Poll};
use std::{collections::VecDeque, convert::TryFrom};
use actix::dev::{
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler,
ToEnvelope,
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, ToEnvelope,
};
use actix::fut::ActorFuture;
use actix::{
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler,
Message as ActixMessage, SpawnHandle,
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message as ActixMessage,
SpawnHandle,
};
use actix_codec::{Decoder, Encoder};
use actix_http::ws::{hash_key, Codec};
@ -32,8 +31,7 @@ use tokio::sync::oneshot::Sender;
/// 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>>
+ StreamHandler<Result<Message, ProtocolError>>,
A: Actor<Context = WebsocketContext<A>> + StreamHandler<Result<Message, ProtocolError>>,
T: Stream<Item = Result<Bytes, PayloadError>> + 'static,
{
let mut res = handshake(req)?;
@ -50,15 +48,14 @@ where
///
/// If successful, returns a pair where the first item is an address for the
/// created actor and the second item is the response that should be returned
/// from the websocket request.
/// from the WebSocket request.
pub fn start_with_addr<A, T>(
actor: A,
req: &HttpRequest,
stream: T,
) -> Result<(Addr<A>, HttpResponse), Error>
where
A: Actor<Context = WebsocketContext<A>>
+ StreamHandler<Result<Message, ProtocolError>>,
A: Actor<Context = WebsocketContext<A>> + StreamHandler<Result<Message, ProtocolError>>,
T: Stream<Item = Result<Bytes, PayloadError>> + 'static,
{
let mut res = handshake(req)?;
@ -66,7 +63,7 @@ where
Ok((addr, res.streaming(out_stream)))
}
/// Do websocket handshake and start ws actor.
/// Do WebSocket handshake and start ws actor.
///
/// `protocols` is a sequence of known protocols.
pub fn start_with_protocols<A, T>(
@ -76,15 +73,14 @@ pub fn start_with_protocols<A, T>(
stream: T,
) -> Result<HttpResponse, Error>
where
A: Actor<Context = WebsocketContext<A>>
+ StreamHandler<Result<Message, ProtocolError>>,
A: Actor<Context = WebsocketContext<A>> + StreamHandler<Result<Message, ProtocolError>>,
T: Stream<Item = Result<Bytes, PayloadError>> + 'static,
{
let mut res = handshake_with_protocols(req, protocols)?;
Ok(res.streaming(WebsocketContext::create(actor, stream)))
}
/// Prepare `WebSocket` handshake response.
/// Prepare WebSocket handshake response.
///
/// This function returns handshake `HttpResponse`, ready to send to peer.
/// It does not perform any IO.
@ -92,7 +88,7 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeErro
handshake_with_protocols(req, &[])
}
/// Prepare `WebSocket` handshake response.
/// Prepare WebSocket handshake response.
///
/// This function returns handshake `HttpResponse`, ready to send to peer.
/// It does not perform any IO.
@ -109,7 +105,7 @@ pub fn handshake_with_protocols(
return Err(HandshakeError::GetMethodRequired);
}
// Check for "UPGRADE" to websocket header
// check for "UPGRADE" to WebSocket header
let has_hdr = if let Some(hdr) = req.headers().get(&header::UPGRADE) {
if let Ok(s) = hdr.to_str() {
s.to_ascii_lowercase().contains("websocket")
@ -301,10 +297,7 @@ where
}
/// Create a new Websocket context
pub fn with_factory<S, F>(
stream: S,
f: F,
) -> impl Stream<Item = Result<Bytes, Error>>
pub fn with_factory<S, F>(stream: S, f: F) -> impl Stream<Item = Result<Bytes, Error>>
where
F: FnOnce(&mut Self) -> A + 'static,
A: StreamHandler<Result<Message, ProtocolError>>,
@ -423,10 +416,7 @@ where
{
type Item = Result<Bytes, Error>;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
if this.fut.alive() {
@ -493,10 +483,7 @@ where
{
type Item = Result<Message, ProtocolError>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut this = self.as_mut().project();
if !*this.closed {
@ -512,9 +499,10 @@ where
}
Poll::Pending => break,
Poll::Ready(Some(Err(e))) => {
return Poll::Ready(Some(Err(ProtocolError::Io(
io::Error::new(io::ErrorKind::Other, format!("{}", e)),
))));
return Poll::Ready(Some(Err(ProtocolError::Io(io::Error::new(
io::ErrorKind::Other,
format!("{}", e),
)))));
}
}
}

View File

@ -11,11 +11,7 @@ impl Actor for Ws {
}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Ws {
fn handle(
&mut self,
msg: Result<ws::Message, ws::ProtocolError>,
ctx: &mut Self::Context,
) {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
match msg.unwrap() {
ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(text),
@ -30,9 +26,7 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Ws {
async fn test_simple() {
let mut srv = test::start(|| {
App::new().service(web::resource("/").to(
|req: HttpRequest, stream: web::Payload| async move {
ws::start(Ws, &req, stream)
},
|req: HttpRequest, stream: web::Payload| async move { ws::start(Ws, &req, stream) },
))
});

View File

@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx
## 0.5.0-beta.1 - 2021-02-10
* Use new call signature for `System::new`.
## 0.4.0 - 2020-09-20
* Added compile success and failure testing. [#1677]
* Add `route` macro for supporting multiple HTTP methods guards. [#1674]

View File

@ -1,7 +1,7 @@
[package]
name = "actix-web-codegen"
version = "0.4.0"
description = "Actix web proc macros"
version = "0.5.0-beta.1"
description = "Routing and runtime macros for Actix Web"
readme = "README.md"
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web"
@ -20,7 +20,7 @@ proc-macro2 = "1"
[dev-dependencies]
actix-rt = "2"
actix-web = "4.0.0-beta.1"
actix-web = "4.0.0-beta.3"
futures-util = { version = "0.3.7", default-features = false }
trybuild = "1"
rustversion = "1"

View File

@ -1,22 +1,24 @@
# actix-web-codegen
> Helper and convenience macros for Actix Web
> Routing and runtime macros for Actix Web.
[![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/)
[![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen)
[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.1)](https://docs.rs/actix-web-codegen/0.5.0-beta.1)
[![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)
![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.1/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.1)
[![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen)
[![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)
## Documentation & Resources
- [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.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.
[`trybuild`]: https://github.com/dtolnay/trybuild

View File

@ -1,6 +1,6 @@
//! Macros for reducing boilerplate code in Actix Web applications.
//! Routing and runtime macros for Actix Web.
//!
//! ## Actix Web Re-exports
//! # Actix Web Re-exports
//! Actix Web re-exports a version of this crate in it's entirety so you usually don't have to
//! specify a dependency on this crate explicitly. Sometimes, however, updates are made to this
//! crate before the actix-web dependency is updated. Therefore, code examples here will show
@ -10,7 +10,7 @@
//! # Runtime Setup
//! Used for setting up the actix async runtime. See [macro@main] macro docs.
//!
//! ```rust
//! ```
//! #[actix_web_codegen::main] // or `#[actix_web::main]` in Actix Web apps
//! async fn main() {
//! async { println!("Hello world"); }.await
@ -23,7 +23,7 @@
//!
//! See docs for: [GET], [POST], [PATCH], [PUT], [DELETE], [HEAD], [CONNECT], [OPTIONS], [TRACE]
//!
//! ```rust
//! ```
//! # use actix_web::HttpResponse;
//! # use actix_web_codegen::get;
//! #[get("/test")]
@ -36,7 +36,7 @@
//! Similar to the single method handler macro but takes one or more arguments for the HTTP methods
//! it should respond to. See [macro@route] macro docs.
//!
//! ```rust
//! ```
//! # use actix_web::HttpResponse;
//! # use actix_web_codegen::route;
//! #[route("/test", method="GET", method="HEAD")]
@ -159,7 +159,7 @@ method_macro! {
/// # Actix Web Re-export
/// This macro can be applied with `#[actix_web::main]` when used in Actix Web applications.
///
/// # Usage
/// # Examples
/// ```rust
/// #[actix_web_codegen::main]
/// async fn main() {

View File

@ -4,9 +4,7 @@ use std::task::{Context, Poll};
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::http::header::{HeaderName, HeaderValue};
use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder};
use actix_web_codegen::{
connect, delete, get, head, options, patch, post, put, route, trace,
};
use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace};
use futures_util::future::{self, LocalBoxFuture};
// Make sure that we can name function as 'config'

View File

@ -1,9 +1,13 @@
# Changes
## Unreleased - 2021-xx-xx
## 3.0.0-beta.2 - 2021-02-10
### Added
* `ClientRequest::insert_header` method which allows using typed headers. [#1869]
* `ClientRequest::append_header` method which allows using typed headers. [#1869]
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
### Changed
* Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905]
@ -16,6 +20,7 @@
[#1869]: https://github.com/actix/actix-web/pull/1869
[#1905]: https://github.com/actix/actix-web/pull/1905
[#1969]: https://github.com/actix/actix-web/pull/1969
## 3.0.0-beta.1 - 2021-01-07

View File

@ -1,6 +1,6 @@
[package]
name = "awc"
version = "3.0.0-beta.1"
version = "3.0.0-beta.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Async HTTP and WebSocket client library built on the Actix ecosystem"
readme = "README.md"
@ -36,10 +36,13 @@ rustls = ["tls-rustls", "actix-http/rustls"]
# content-encoding support
compress = ["actix-http/compress"]
# trust-dns as dns resolver
trust-dns = ["actix-http/trust-dns"]
[dependencies]
actix-codec = "0.4.0-beta.1"
actix-service = "2.0.0-beta.4"
actix-http = "3.0.0-beta.1"
actix-http = "3.0.0-beta.3"
actix-rt = "2"
base64 = "0.13"
@ -57,10 +60,16 @@ serde_urlencoded = "0.7"
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
[target.'cfg(windows)'.dependencies.tls-openssl]
version = "0.10.9"
package = "openssl"
features = ["vendored"]
optional = true
[dev-dependencies]
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-web = { version = "4.0.0-beta.3", features = ["openssl"] }
actix-http = { version = "3.0.0-beta.3", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] }
actix-utils = "3.0.0-beta.1"
actix-server = "2.0.0-beta.3"
actix-tls = { version = "3.0.0-beta.3", features = ["openssl", "rustls"] }

View File

@ -3,9 +3,9 @@
> Async HTTP and WebSocket client library.
[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc)
[![Documentation](https://docs.rs/awc/badge.svg?version=2.0.3)](https://docs.rs/awc/2.0.3)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/awc)
[![Dependency Status](https://deps.rs/crate/awc/2.0.3/status.svg)](https://deps.rs/crate/awc/2.0.3)
[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.2)](https://docs.rs/awc/3.0.0-beta.2)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc)
[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.2/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.2)
[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Documentation & Resources

View File

@ -82,8 +82,9 @@ impl ClientBuilder {
self
}
/// Maximum supported http major version
/// Supported versions http/1.1, http/2
/// Maximum supported HTTP major version.
///
/// Supported versions are HTTP/1.1 and HTTP/2.
pub fn max_http_version(mut self, val: http::Version) -> Self {
self.max_http_version = Some(val);
self

View File

@ -82,8 +82,7 @@ where
let connection = fut.await?;
// send request
let (head, framed) =
connection.open_tunnel(RequestHeadType::from(head)).await?;
let (head, framed) = connection.open_tunnel(RequestHeadType::from(head)).await?;
let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io))));
Ok((head, framed))
@ -142,10 +141,7 @@ impl AsyncWrite for BoxedSocket {
Pin::new(self.get_mut().0.as_write()).poll_flush(cx)
}
fn poll_shutdown(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>> {
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(self.get_mut().0.as_write()).poll_shutdown(cx)
}
}

View File

@ -1,7 +1,5 @@
//! Http client errors
pub use actix_http::client::{
ConnectError, FreezeRequestError, InvalidUrl, SendRequestError,
};
pub use actix_http::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
pub use actix_http::error::PayloadError;
pub use actix_http::http::Error as HttpError;
pub use actix_http::ws::HandshakeError as WsHandshakeError;

View File

@ -145,7 +145,9 @@ impl FrozenSendBuilder {
{
match HeaderName::try_from(key) {
Ok(key) => match value.try_into_value() {
Ok(value) => self.extra_headers.insert(key, value),
Ok(value) => {
self.extra_headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
},
Err(e) => self.err = Some(e.into()),

View File

@ -134,7 +134,7 @@ use self::connect::{Connect, ConnectorWrapper};
///
/// let res = client.get("http://www.rust-lang.org") // <- Create request builder
/// .insert_header(("User-Agent", "Actix-web"))
/// .send() // <- Send http request
/// .send() // <- Send HTTP request
/// .await; // <- send request and wait for response
///
/// println!("Response: {:?}", res);

View File

@ -11,8 +11,7 @@ use actix_http::body::Body;
use actix_http::cookie::{Cookie, CookieJar};
use actix_http::http::header::{self, IntoHeaderPair};
use actix_http::http::{
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri,
Version,
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version,
};
use actix_http::{Error, RequestHead};
@ -42,10 +41,10 @@ cfg_if::cfg_if! {
/// let response = awc::Client::new()
/// .get("http://www.rust-lang.org") // <- Create request builder
/// .insert_header(("User-Agent", "Actix-web"))
/// .send() // <- Send http request
/// .send() // <- Send HTTP request
/// .await;
///
/// response.and_then(|response| { // <- server http response
/// response.and_then(|response| { // <- server HTTP response
/// println!("Response: {:?}", response);
/// Ok(())
/// });
@ -159,7 +158,9 @@ impl ClientRequest {
H: IntoHeaderPair,
{
match header.try_into_header_pair() {
Ok((key, value)) => self.head.headers.insert(key, value),
Ok((key, value)) => {
self.head.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
};
@ -217,7 +218,7 @@ impl ClientRequest {
}
/// Force close connection instead of returning it back to connections pool.
/// This setting affect only http/1 connections.
/// This setting affect only HTTP/1 connections.
#[inline]
pub fn force_close(mut self) -> Self {
self.head.set_connection_type(ConnectionType::Close);
@ -232,7 +233,9 @@ impl ClientRequest {
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
{
match HeaderValue::try_from(value) {
Ok(value) => self.head.headers.insert(header::CONTENT_TYPE, value),
Ok(value) => {
self.head.headers.insert(header::CONTENT_TYPE, value);
}
Err(e) => self.err = Some(e.into()),
}
self
@ -516,15 +519,11 @@ impl ClientRequest {
.unwrap_or(true);
if https {
slf =
slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING))
slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING))
} else {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
{
slf = slf.insert_header_if_none((
header::ACCEPT_ENCODING,
"gzip, deflate",
))
slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, "gzip, deflate"))
}
};
}

View File

@ -85,8 +85,7 @@ impl<S> HttpMessage for ClientResponse<S> {
if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new();
for hdr in self.headers().get_all(&SET_COOKIE) {
let s = std::str::from_utf8(hdr.as_bytes())
.map_err(CookieParseError::from)?;
let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
cookies.push(Cookie::parse_encoded(s)?.into_owned());
}
self.extensions_mut().insert(Cookies(cookies));
@ -185,7 +184,7 @@ impl<S> ClientResponse<S>
where
S: Stream<Item = Result<Bytes, PayloadError>>,
{
/// Loads http response's body.
/// Loads HTTP response's body.
pub fn body(&mut self) -> MessageBody<S> {
MessageBody::new(self)
}
@ -230,7 +229,7 @@ impl<S> fmt::Debug for ClientResponse<S> {
}
}
/// Future that resolves to a complete http message body.
/// Future that resolves to a complete HTTP message body.
pub struct MessageBody<S> {
length: Option<usize>,
err: Option<PayloadError>,
@ -393,9 +392,7 @@ where
if let Some(len) = self.length.take() {
if len > self.fut.as_ref().unwrap().limit {
return Poll::Ready(Err(JsonPayloadError::Payload(
PayloadError::Overflow,
)));
return Poll::Ready(Err(JsonPayloadError::Payload(PayloadError::Overflow)));
}
}
@ -465,8 +462,7 @@ mod tests {
_ => unreachable!("error"),
}
let mut req =
TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish();
let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish();
match req.body().await.err().unwrap() {
PayloadError::Overflow => {}
_ => unreachable!("error"),

View File

@ -78,8 +78,7 @@ impl SendClientRequest {
#[cfg(feature = "compress")]
impl Future for SendClientRequest {
type Output =
Result<ClientResponse<Decoder<Payload<PayloadStream>>>, SendRequestError>;
type Output = Result<ClientResponse<Decoder<Payload<PayloadStream>>>, SendRequestError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
@ -95,15 +94,9 @@ impl Future for SendClientRequest {
let res = ready!(send.as_mut().poll(cx)).map(|res| {
res._timeout(delay.take()).map_body(|head, payload| {
if *response_decompress {
Payload::Stream(Decoder::from_headers(
payload,
&head.headers,
))
Payload::Stream(Decoder::from_headers(payload, &head.headers))
} else {
Payload::Stream(Decoder::new(
payload,
ContentEncoding::Identity,
))
Payload::Stream(Decoder::new(payload, ContentEncoding::Identity))
}
})
});
@ -187,11 +180,11 @@ impl RequestSender {
B: Into<Body>,
{
let fut = match self {
RequestSender::Owned(head) => config.connector.send_request(
RequestHeadType::Owned(head),
body.into(),
addr,
),
RequestSender::Owned(head) => {
config
.connector
.send_request(RequestHeadType::Owned(head), body.into(), addr)
}
RequestSender::Rc(head, extra_headers) => config.connector.send_request(
RequestHeadType::Rc(head, extra_headers),
body.into(),
@ -215,8 +208,7 @@ impl RequestSender {
Err(e) => return Error::from(e).into(),
};
if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json")
{
if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") {
return e.into();
}
@ -243,10 +235,9 @@ impl RequestSender {
};
// set content-type
if let Err(e) = self.set_header_if_none(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
) {
if let Err(e) =
self.set_header_if_none(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
{
return e.into();
}
@ -290,11 +281,7 @@ impl RequestSender {
self.send_body(addr, response_decompress, timeout, config, Body::Empty)
}
fn set_header_if_none<V>(
&mut self,
key: HeaderName,
value: V,
) -> Result<(), HttpError>
fn set_header_if_none<V>(&mut self, key: HeaderName, value: V) -> Result<(), HttpError>
where
V: IntoHeaderValue,
{
@ -302,7 +289,9 @@ impl RequestSender {
RequestSender::Owned(head) => {
if !head.headers.contains_key(&key) {
match value.try_into_value() {
Ok(value) => head.headers.insert(key, value),
Ok(value) => {
head.headers.insert(key, value);
}
Err(e) => return Err(e.into()),
}
}

View File

@ -40,16 +40,12 @@ pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message};
use crate::connect::BoxedSocket;
use crate::error::{InvalidUrl, SendRequestError, WsClientError};
use crate::http::header::{
self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION,
};
use crate::http::{
ConnectionType, Error as HttpError, Method, StatusCode, Uri, Version,
};
use crate::http::header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION};
use crate::http::{ConnectionType, Error as HttpError, Method, StatusCode, Uri, Version};
use crate::response::ClientResponse;
use crate::ClientConfig;
/// `WebSocket` connection
/// WebSocket connection.
pub struct WebsocketsRequest {
pub(crate) head: RequestHead,
err: Option<HttpError>,
@ -63,7 +59,7 @@ pub struct WebsocketsRequest {
}
impl WebsocketsRequest {
/// Create new websocket connection
/// Create new WebSocket connection
pub(crate) fn new<U>(uri: U, config: Rc<ClientConfig>) -> Self
where
Uri: TryFrom<U>,
@ -106,7 +102,7 @@ impl WebsocketsRequest {
self
}
/// Set supported websocket protocols
/// Set supported WebSocket protocols
pub fn protocols<U, V>(mut self, protos: U) -> Self
where
U: IntoIterator<Item = V>,
@ -243,7 +239,7 @@ impl WebsocketsRequest {
self.header(AUTHORIZATION, format!("Bearer {}", token))
}
/// Complete request construction and connect to a websockets server.
/// Complete request construction and connect to a WebSocket server.
pub async fn connect(
mut self,
) -> Result<(ClientResponse, Framed<BoxedSocket, Codec>), WsClientError> {
@ -342,7 +338,7 @@ impl WebsocketsRequest {
return Err(WsClientError::InvalidResponseStatus(head.status));
}
// Check for "UPGRADE" to websocket header
// check for "UPGRADE" to WebSocket header
let has_hdr = if let Some(hdr) = head.headers.get(&header::UPGRADE) {
if let Ok(s) = hdr.to_str() {
s.to_ascii_lowercase().contains("websocket")

View File

@ -51,8 +51,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
#[actix_rt::test]
async fn test_simple() {
let srv = test::start(|| {
App::new()
.service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
});
let request = srv.get("/").insert_header(("x-test", "111")).send();
@ -685,9 +684,8 @@ async fn test_client_streaming_explicit() {
}))
});
let body = stream::once(async {
Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes()))
});
let body =
stream::once(async { Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) });
let req = srv.post("/").send_stream(Box::pin(body));
let mut res = req.await.unwrap();

View File

@ -64,8 +64,7 @@ async fn test_connection_reuse_h2() {
.and_then(
HttpService::build()
.h2(map_config(
App::new()
.service(web::resource("/").route(web::to(HttpResponse::Ok))),
App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))),
|_| AppConfig::default(),
))
.rustls(tls_config())

View File

@ -48,8 +48,7 @@ async fn test_connection_reuse_h2() {
.and_then(
HttpService::build()
.h2(map_config(
App::new()
.service(web::resource("/").route(web::to(HttpResponse::Ok))),
App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))),
|_| AppConfig::default(),
))
.openssl(ssl_acceptor())

View File

@ -31,7 +31,7 @@ async fn test_simple() {
.send(h1::Message::Item((res.drop_body(), BodySize::None)))
.await?;
// start websocket service
// start WebSocket service
let framed = framed.replace_codec(ws::Codec::new());
ws::Dispatcher::with(framed, ws_service).await
}

View File

@ -33,9 +33,8 @@ fn bench_async_burst(c: &mut Criterion) {
let srv = rt.block_on(async {
test::start(|| {
App::new().service(
web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))),
)
App::new()
.service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
})
});
@ -43,24 +42,25 @@ fn bench_async_burst(c: &mut Criterion) {
c.bench_function("get_body_async_burst", move |b| {
b.iter_custom(|iters| {
let client =
rt.block_on(async { Client::new().get(url.clone()).freeze().unwrap() });
rt.block_on(async {
let client = Client::new().get(url.clone()).freeze().unwrap();
let start = std::time::Instant::now();
// benchmark body
let start = std::time::Instant::now();
// benchmark body
let resps = rt.block_on(async move {
let burst = (0..iters).map(|_| client.send());
join_all(burst).await
});
let elapsed = start.elapsed();
let resps = join_all(burst).await;
// if there are failed requests that might be an issue
let failed = resps.iter().filter(|r| r.is_err()).count();
if failed > 0 {
eprintln!("failed {} requests (might be bench timeout)", failed);
};
let elapsed = start.elapsed();
elapsed
// if there are failed requests that might be an issue
let failed = resps.iter().filter(|r| r.is_err()).count();
if failed > 0 {
eprintln!("failed {} requests (might be bench timeout)", failed);
};
elapsed
})
})
});
}

View File

@ -1,6 +1,7 @@
digraph {
subgraph cluster_web {
label="actix/actix-web"
"awc"
"actix-web"
"actix-files"
@ -16,7 +17,7 @@ digraph {
"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-http" -> { "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" }
@ -27,4 +28,8 @@ digraph {
"actix-tls" -> { "actix-service" "actix-codec" "actix-utils" }
"actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" }
"actix-rt" -> { "macros" "threadpool" }
// actix
"actix" -> { "actix-rt" }
}

View File

@ -15,7 +15,6 @@ digraph {
"awc" -> { "actix-http" }
"actix-web-actors" -> { "actix" "actix-web" "actix-http" }
"actix-multipart" -> { "actix-web" }
"actix-http" -> { "actix" }[color=blue] // optional
"actix-files" -> { "actix-web" }
"actix-http-test" -> { "awc" }
}

View File

@ -29,12 +29,8 @@ async fn main() -> std::io::Result<()> {
.service(no_params)
.service(
web::resource("/resource2/index.html")
.wrap(
middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"),
)
.default_service(
web::route().to(|| HttpResponse::MethodNotAllowed()),
)
.wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"))
.default_service(web::route().to(|| HttpResponse::MethodNotAllowed()))
.route(web::get().to(index_async)),
)
.service(web::resource("/test1.html").to(|| async { "Test\r\n" }))

View File

@ -33,12 +33,8 @@ async fn main() -> std::io::Result<()> {
.service(no_params)
.service(
web::resource("/resource2/index.html")
.wrap(
middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"),
)
.default_service(
web::route().to(|| HttpResponse::MethodNotAllowed()),
)
.wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"))
.default_service(web::route().to(|| HttpResponse::MethodNotAllowed()))
.route(web::get().to(index_async)),
)
.service(web::resource("/test1.html").to(|| async { "Test\r\n" }))

View File

@ -1,2 +1,2 @@
max_width = 89
max_width = 96
reorder_imports = true

View File

@ -8,8 +8,7 @@ use actix_http::body::{Body, MessageBody};
use actix_http::{Extensions, Request};
use actix_service::boxed::{self, BoxServiceFactory};
use actix_service::{
apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt,
Transform,
apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform,
};
use futures_util::future::FutureExt;
@ -72,7 +71,7 @@ where
/// Set application data. Application data could be accessed
/// by using `Data<T>` extractor where `T` is data type.
///
/// **Note**: http server accepts an application factory rather than
/// **Note**: HTTP server accepts an application factory rather than
/// an application instance. Http server constructs an application
/// instance for each thread, thus application data must be constructed
/// multiple times. If you want to share data between different
@ -207,11 +206,11 @@ where
)
}
/// Register http service.
/// Register HTTP service.
///
/// Http service is any type that implements `HttpServiceFactory` trait.
///
/// Actix web provides several services implementations:
/// Actix Web provides several services implementations:
///
/// * *Resource* is an entry in resource table which corresponds to requested URL.
/// * *Scope* is a set of resources with common root path.
@ -473,17 +472,13 @@ mod tests {
use crate::http::{header, HeaderValue, Method, StatusCode};
use crate::middleware::DefaultHeaders;
use crate::service::ServiceRequest;
use crate::test::{
call_service, init_service, read_body, try_init_service, TestRequest,
};
use crate::test::{call_service, init_service, read_body, try_init_service, TestRequest};
use crate::{web, HttpRequest, HttpResponse};
#[actix_rt::test]
async fn test_default_resource() {
let srv = init_service(
App::new().service(web::resource("/test").to(HttpResponse::Ok)),
)
.await;
let srv =
init_service(App::new().service(web::resource("/test").to(HttpResponse::Ok))).await;
let req = TestRequest::with_uri("/test").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
@ -525,20 +520,22 @@ mod tests {
#[actix_rt::test]
async fn test_data_factory() {
let srv =
init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
))
.await;
let srv = init_service(
App::new()
.data_factory(|| ok::<_, ()>(10usize))
.service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok())),
)
.await;
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let srv =
init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
))
.await;
let srv = init_service(
App::new()
.data_factory(|| ok::<_, ()>(10u32))
.service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok())),
)
.await;
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
@ -546,23 +543,24 @@ mod tests {
#[actix_rt::test]
async fn test_data_factory_errors() {
let srv =
try_init_service(App::new().data_factory(|| err::<u32, _>(())).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
))
.await;
let srv = try_init_service(
App::new()
.data_factory(|| err::<u32, _>(()))
.service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok())),
)
.await;
assert!(srv.is_err());
}
#[actix_rt::test]
async fn test_extension() {
let srv = init_service(App::new().app_data(10usize).service(
web::resource("/").to(|req: HttpRequest| {
let srv = init_service(App::new().app_data(10usize).service(web::resource("/").to(
|req: HttpRequest| {
assert_eq!(*req.app_data::<usize>().unwrap(), 10);
HttpResponse::Ok()
}),
))
},
)))
.await;
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
@ -617,10 +615,8 @@ mod tests {
let fut = srv.call(req);
async move {
let mut res = fut.await?;
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
);
res.headers_mut()
.insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
Ok(res)
}
})
@ -645,10 +641,8 @@ mod tests {
let fut = srv.call(req);
async {
let mut res = fut.await?;
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
);
res.headers_mut()
.insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
Ok(res)
}
}),
@ -671,9 +665,8 @@ mod tests {
.route(
"/test",
web::get().to(|req: HttpRequest| {
HttpResponse::Ok().body(
req.url_for("youtube", &["12345"]).unwrap().to_string(),
)
HttpResponse::Ok()
.body(req.url_for("youtube", &["12345"]).unwrap().to_string())
}),
),
)

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