Merge branch 'master' into master

This commit is contained in:
Muhammad-Jibril 2021-04-05 13:06:40 +08:00 committed by GitHub
commit f9aab6890e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
146 changed files with 2312 additions and 1646 deletions

3
.cargo/config.toml Normal file
View File

@ -0,0 +1,3 @@
[alias]
chk = "hack check --workspace --all-features --tests --examples"
lint = "hack --clean-per-run clippy --workspace --tests --examples"

View File

@ -23,6 +23,9 @@ jobs:
name: ${{ matrix.target.name }} / ${{ matrix.version }} name: ${{ matrix.target.name }} / ${{ matrix.version }}
runs-on: ${{ matrix.target.os }} runs-on: ${{ matrix.target.os }}
env:
VCPKGRS_DYNAMIC: 1
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -65,7 +68,13 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: hack command: hack
args: --clean-per-run check --workspace --no-default-features --tests args: check --workspace --no-default-features
- name: check minimal + tests
uses: actions-rs/cargo@v1
with:
command: hack
args: check --workspace --no-default-features --tests --examples
- name: check full - name: check full
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1

View File

@ -1,19 +1,31 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 4.0.0-beta.5 - 2021-04-02
### Added
* `Header` extractor for extracting common HTTP headers in handlers. [#2094]
* Added `TestServer::client_headers` method. [#2097]
### Fixed ### Fixed
* Double ampersand in Logger format is escaped correctly. [#2067] * Double ampersand in Logger format is escaped correctly. [#2067]
### Changed ### Changed
* `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed instead of skipping. * `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed
(Only the first error is kept when multiple error occur) [#2093] instead of skipping. (Only the first error is kept when multiple error occur) [#2093]
### Removed ### Removed
* The `client` mod was removed. Clients should now use `awc` directly. * The `client` mod was removed. Clients should now use `awc` directly.
[871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7) [871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7)
* Integration testing was moved to new `actix-test` crate. Namely these items from the `test`
module: `TestServer`, `TestServerConfig`, `start`, `start_with`, and `unused_addr`. [#2112]
[#2067]: https://github.com/actix/actix-web/pull/2067 [#2067]: https://github.com/actix/actix-web/pull/2067
[#2093]: https://github.com/actix/actix-web/pull/2093 [#2093]: https://github.com/actix/actix-web/pull/2093
[#2094]: https://github.com/actix/actix-web/pull/2094
[#2097]: https://github.com/actix/actix-web/pull/2097
[#2112]: https://github.com/actix/actix-web/pull/2112
## 4.0.0-beta.4 - 2021-03-09 ## 4.0.0-beta.4 - 2021-03-09

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "4.0.0-beta.4" version = "4.0.0-beta.5"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
readme = "README.md" readme = "README.md"
@ -36,25 +36,26 @@ members = [
"actix-web-actors", "actix-web-actors",
"actix-web-codegen", "actix-web-codegen",
"actix-http-test", "actix-http-test",
"actix-test",
] ]
[features] [features]
default = ["compress", "cookies"] default = ["compress", "cookies"]
# content-encoding support # content-encoding support
compress = ["actix-http/compress", "awc/compress"] compress = ["actix-http/compress"]
# support for cookies # support for cookies
cookies = ["actix-http/cookies", "awc/cookies"] cookies = ["actix-http/cookies"]
# secure cookies feature # secure cookies feature
secure-cookies = ["actix-http/secure-cookies"] secure-cookies = ["actix-http/secure-cookies"]
# openssl # openssl
openssl = ["tls-openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"] openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"]
# rustls # rustls
rustls = ["tls-rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"] rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"]
[[example]] [[example]]
name = "basic" name = "basic"
@ -72,23 +73,18 @@ required-features = ["compress", "cookies"]
name = "on_connect" name = "on_connect"
required-features = [] required-features = []
[[example]]
name = "client"
required-features = ["rustls"]
[dependencies] [dependencies]
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-macros = "0.2.0" actix-macros = "0.2.0"
actix-router = "0.2.7" actix-router = "0.2.7"
actix-rt = "2.1" actix-rt = "2.2"
actix-server = "2.0.0-beta.3" actix-server = "2.0.0-beta.3"
actix-service = "2.0.0-beta.4" actix-service = "2.0.0-beta.4"
actix-utils = "3.0.0-beta.2" actix-utils = "3.0.0-beta.4"
actix-tls = { version = "3.0.0-beta.4", default-features = false, optional = true } actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true }
actix-web-codegen = "0.5.0-beta.2" actix-web-codegen = "0.5.0-beta.2"
actix-http = "3.0.0-beta.4" actix-http = "3.0.0-beta.5"
awc = { version = "3.0.0-beta.3", default-features = false }
ahash = "0.7" ahash = "0.7"
bytes = "1" bytes = "1"
@ -97,6 +93,8 @@ either = "1.5.3"
encoding_rs = "0.8" encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
language-tags = "0.2"
once_cell = "1.5"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
pin-project = "1.0.0" pin-project = "1.0.0"
@ -107,11 +105,12 @@ serde_urlencoded = "0.7"
smallvec = "1.6" smallvec = "1.6"
socket2 = "0.4.0" socket2 = "0.4.0"
time = { version = "0.2.23", default-features = false, features = ["std"] } time = { version = "0.2.23", default-features = false, features = ["std"] }
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
tls-rustls = { package = "rustls", version = "0.19.0", optional = true }
url = "2.1" url = "2.1"
[dev-dependencies] [dev-dependencies]
actix-test = { version = "0.1.0-beta.1", features = ["openssl", "rustls"] }
awc = { version = "3.0.0-beta.4", features = ["openssl"] }
brotli2 = "0.3.2" brotli2 = "0.3.2"
criterion = "0.3" criterion = "0.3"
env_logger = "0.8" env_logger = "0.8"
@ -119,6 +118,8 @@ flate2 = "1.0.13"
rand = "0.8" rand = "0.8"
rcgen = "0.8" rcgen = "0.8"
serde_derive = "1.0" serde_derive = "1.0"
tls-openssl = { package = "openssl", version = "0.10.9" }
tls-rustls = { package = "rustls", version = "0.19.0" }
[profile.release] [profile.release]
lto = true lto = true
@ -126,13 +127,14 @@ opt-level = 3
codegen-units = 1 codegen-units = 1
[patch.crates-io] [patch.crates-io]
actix-web = { path = "." } actix-files = { path = "actix-files" }
actix-http = { path = "actix-http" } actix-http = { path = "actix-http" }
actix-http-test = { path = "actix-http-test" } actix-http-test = { path = "actix-http-test" }
actix-multipart = { path = "actix-multipart" }
actix-test = { path = "actix-test" }
actix-web = { path = "." }
actix-web-actors = { path = "actix-web-actors" } actix-web-actors = { path = "actix-web-actors" }
actix-web-codegen = { path = "actix-web-codegen" } actix-web-codegen = { path = "actix-web-codegen" }
actix-multipart = { path = "actix-multipart" }
actix-files = { path = "actix-files" }
awc = { path = "awc" } awc = { path = "awc" }
[[bench]] [[bench]]

View File

@ -6,10 +6,10 @@
<p> <p>
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.4)](https://docs.rs/actix-web/4.0.0-beta.4) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.5)](https://docs.rs/actix-web/4.0.0-beta.5)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![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-web.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.4) [![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.5/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.5)
<br /> <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) [![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)

View File

@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 0.6.0-beta.4 - 2021-04-02
* No notable changes.
## 0.6.0-beta.3 - 2021-03-09 ## 0.6.0-beta.3 - 2021-03-09
* No notable changes. * No notable changes.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-files" name = "actix-files"
version = "0.6.0-beta.3" version = "0.6.0-beta.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static file serving for Actix Web" description = "Static file serving for Actix Web"
readme = "README.md" readme = "README.md"
@ -17,14 +17,14 @@ name = "actix_files"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = { version = "4.0.0-beta.4", default-features = false } actix-web = { version = "4.0.0-beta.5", default-features = false }
actix-service = "2.0.0-beta.4" actix-service = "2.0.0-beta.4"
actix-utils = "3.0.0-beta.4"
askama_escape = "0.10" askama_escape = "0.10"
bitflags = "1" bitflags = "1"
bytes = "1" bytes = "1"
futures-core = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
futures-util = { version = "0.3.7", default-features = false }
http-range = "0.1.4" http-range = "0.1.4"
derive_more = "0.99.5" derive_more = "0.99.5"
log = "0.4" log = "0.4"
@ -33,5 +33,6 @@ mime_guess = "2.0.1"
percent-encoding = "2.1" percent-encoding = "2.1"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.1" actix-rt = "2.2"
actix-web = "4.0.0-beta.4" actix-web = "4.0.0-beta.5"
actix-test = "0.1.0-beta.1"

View File

@ -3,11 +3,11 @@
> Static file serving for Actix Web > Static file serving for Actix Web
[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.3)](https://docs.rs/actix-files/0.6.0-beta.3) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.4)](https://docs.rs/actix-files/0.6.0-beta.4)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
![License](https://img.shields.io/crates/l/actix-files.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.3/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.3) [![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.4/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.4)
[![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files)
[![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) [![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)

View File

@ -1,6 +1,7 @@
use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc}; use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc};
use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt}; use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt};
use actix_utils::future::ok;
use actix_web::{ use actix_web::{
dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse}, dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse},
error::Error, error::Error,
@ -8,7 +9,7 @@ use actix_web::{
http::header::DispositionType, http::header::DispositionType,
HttpRequest, HttpRequest,
}; };
use futures_util::future::{ok, FutureExt, LocalBoxFuture}; use futures_core::future::LocalBoxFuture;
use crate::{ use crate::{
directory_listing, named, Directory, DirectoryRenderer, FilesService, HttpNewService, directory_listing, named, Directory, DirectoryRenderer, FilesService, HttpNewService,
@ -263,18 +264,18 @@ impl ServiceFactory<ServiceRequest> for Files {
}; };
if let Some(ref default) = *self.default.borrow() { if let Some(ref default) = *self.default.borrow() {
default let fut = default.new_service(());
.new_service(()) Box::pin(async {
.map(move |result| match result { match fut.await {
Ok(default) => { Ok(default) => {
srv.default = Some(default); srv.default = Some(default);
Ok(srv) Ok(srv)
} }
Err(_) => Err(()), Err(_) => Err(()),
}
}) })
.boxed_local()
} else { } else {
ok(srv).boxed_local() Box::pin(ok(srv))
} }
} }
} }

View File

@ -65,6 +65,7 @@ mod tests {
}; };
use actix_service::ServiceFactory; use actix_service::ServiceFactory;
use actix_utils::future::ok;
use actix_web::{ use actix_web::{
guard, guard,
http::{ http::{
@ -76,7 +77,6 @@ mod tests {
web::{self, Bytes}, web::{self, Bytes},
App, HttpResponse, Responder, App, HttpResponse, Responder,
}; };
use futures_util::future::ok;
use super::*; use super::*;
@ -413,7 +413,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_named_file_content_range_headers() { async fn test_named_file_content_range_headers() {
let srv = test::start(|| App::new().service(Files::new("/", "."))); let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
// Valid range header // Valid range header
let response = srv let response = srv
@ -438,7 +438,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_named_file_content_length_headers() { async fn test_named_file_content_length_headers() {
let srv = test::start(|| App::new().service(Files::new("/", "."))); let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
// Valid range header // Valid range header
let response = srv let response = srv
@ -477,7 +477,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_head_content_length_headers() { async fn test_head_content_length_headers() {
let srv = test::start(|| App::new().service(Files::new("/", "."))); let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
let response = srv.head("/tests/test.binary").send().await.unwrap(); let response = srv.head("/tests/test.binary").send().await.unwrap();

View File

@ -3,8 +3,8 @@ use std::{
str::FromStr, str::FromStr,
}; };
use actix_utils::future::{ready, Ready};
use actix_web::{dev::Payload, FromRequest, HttpRequest}; use actix_web::{dev::Payload, FromRequest, HttpRequest};
use futures_util::future::{ready, Ready};
use crate::error::UriSegmentError; use crate::error::UriSegmentError;

View File

@ -1,6 +1,7 @@
use std::{fmt, io, path::PathBuf, rc::Rc}; use std::{fmt, io, path::PathBuf, rc::Rc};
use actix_service::Service; use actix_service::Service;
use actix_utils::future::ok;
use actix_web::{ use actix_web::{
dev::{ServiceRequest, ServiceResponse}, dev::{ServiceRequest, ServiceResponse},
error::Error, error::Error,
@ -8,7 +9,7 @@ use actix_web::{
http::{header, Method}, http::{header, Method},
HttpResponse, HttpResponse,
}; };
use futures_util::future::{ok, Either, LocalBoxFuture, Ready}; use futures_core::future::LocalBoxFuture;
use crate::{ use crate::{
named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, NamedFile, named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, NamedFile,
@ -29,19 +30,18 @@ pub struct FilesService {
pub(crate) hidden_files: bool, pub(crate) hidden_files: bool,
} }
type FilesServiceFuture = Either<
Ready<Result<ServiceResponse, Error>>,
LocalBoxFuture<'static, Result<ServiceResponse, Error>>,
>;
impl FilesService { impl FilesService {
fn handle_err(&self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture { fn handle_err(
log::debug!("Failed to handle {}: {}", req.path(), e); &self,
err: io::Error,
req: ServiceRequest,
) -> LocalBoxFuture<'static, Result<ServiceResponse, Error>> {
log::debug!("error handling {}: {}", req.path(), err);
if let Some(ref default) = self.default { if let Some(ref default) = self.default {
Either::Right(default.call(req)) Box::pin(default.call(req))
} else { } else {
Either::Left(ok(req.error_response(e))) Box::pin(ok(req.error_response(err)))
} }
} }
} }
@ -55,7 +55,7 @@ impl fmt::Debug for FilesService {
impl Service<ServiceRequest> for FilesService { impl Service<ServiceRequest> for FilesService {
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Future = FilesServiceFuture; type Future = LocalBoxFuture<'static, Result<ServiceResponse, Error>>;
actix_service::always_ready!(); actix_service::always_ready!();
@ -69,7 +69,7 @@ impl Service<ServiceRequest> for FilesService {
}; };
if !is_method_valid { if !is_method_valid {
return Either::Left(ok(req.into_response( return Box::pin(ok(req.into_response(
actix_web::HttpResponse::MethodNotAllowed() actix_web::HttpResponse::MethodNotAllowed()
.insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8)) .insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8))
.body("Request did not meet this resource's requirements."), .body("Request did not meet this resource's requirements."),
@ -79,13 +79,13 @@ impl Service<ServiceRequest> for FilesService {
let real_path = let real_path =
match PathBufWrap::parse_path(req.match_info().path(), self.hidden_files) { match PathBufWrap::parse_path(req.match_info().path(), self.hidden_files) {
Ok(item) => item, Ok(item) => item,
Err(e) => return Either::Left(ok(req.error_response(e))), Err(e) => return Box::pin(ok(req.error_response(e))),
}; };
// full file path // full file path
let path = match self.directory.join(&real_path).canonicalize() { let path = match self.directory.join(&real_path).canonicalize() {
Ok(path) => path, Ok(path) => path,
Err(e) => return self.handle_err(e, req), Err(err) => return Box::pin(self.handle_err(err, req)),
}; };
if path.is_dir() { if path.is_dir() {
@ -93,7 +93,7 @@ impl Service<ServiceRequest> for FilesService {
if self.redirect_to_slash && !req.path().ends_with('/') { if self.redirect_to_slash && !req.path().ends_with('/') {
let redirect_to = format!("{}/", req.path()); let redirect_to = format!("{}/", req.path());
return Either::Left(ok(req.into_response( return Box::pin(ok(req.into_response(
HttpResponse::Found() HttpResponse::Found()
.insert_header((header::LOCATION, redirect_to)) .insert_header((header::LOCATION, redirect_to))
.body("") .body("")
@ -114,9 +114,9 @@ impl Service<ServiceRequest> for FilesService {
let (req, _) = req.into_parts(); let (req, _) = req.into_parts();
let res = named_file.into_response(&req); let res = named_file.into_response(&req);
Either::Left(ok(ServiceResponse::new(req, res))) Box::pin(ok(ServiceResponse::new(req, res)))
} }
Err(e) => self.handle_err(e, req), Err(err) => self.handle_err(err, req),
} }
} else if self.show_index { } else if self.show_index {
let dir = Directory::new(self.directory.clone(), path); let dir = Directory::new(self.directory.clone(), path);
@ -124,12 +124,12 @@ impl Service<ServiceRequest> for FilesService {
let (req, _) = req.into_parts(); let (req, _) = req.into_parts();
let x = (self.renderer)(&dir, &req); let x = (self.renderer)(&dir, &req);
match x { Box::pin(match x {
Ok(resp) => Either::Left(ok(resp)), Ok(resp) => ok(resp),
Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))), Err(err) => ok(ServiceResponse::from_err(err, req)),
} })
} else { } else {
Either::Left(ok(ServiceResponse::from_err( Box::pin(ok(ServiceResponse::from_err(
FilesError::IsDirectory, FilesError::IsDirectory,
req.into_parts().0, req.into_parts().0,
))) )))
@ -145,9 +145,9 @@ impl Service<ServiceRequest> for FilesService {
let (req, _) = req.into_parts(); let (req, _) = req.into_parts();
let res = named_file.into_response(&req); let res = named_file.into_response(&req);
Either::Left(ok(ServiceResponse::new(req, res))) Box::pin(ok(ServiceResponse::new(req, res)))
} }
Err(e) => self.handle_err(e, req), Err(err) => self.handle_err(err, req),
} }
} }
} }

View File

@ -3,6 +3,12 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 3.0.0-beta.4 - 2021-04-02
* Added `TestServer::client_headers` method. [#2097]
[#2097]: https://github.com/actix/actix-web/pull/2097
## 3.0.0-beta.3 - 2021-03-09 ## 3.0.0-beta.3 - 2021-03-09
* No notable changes. * No notable changes.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-http-test" name = "actix-http-test"
version = "3.0.0-beta.3" version = "3.0.0-beta.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Various helpers for Actix applications to use during testing" description = "Various helpers for Actix applications to use during testing"
readme = "README.md" readme = "README.md"
@ -31,11 +31,11 @@ openssl = ["tls-openssl", "awc/openssl"]
[dependencies] [dependencies]
actix-service = "2.0.0-beta.4" actix-service = "2.0.0-beta.4"
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-tls = "3.0.0-beta.4" actix-tls = "3.0.0-beta.5"
actix-utils = "3.0.0-beta.2" actix-utils = "3.0.0-beta.4"
actix-rt = "2.1" actix-rt = "2.2"
actix-server = "2.0.0-beta.3" actix-server = "2.0.0-beta.3"
awc = { version = "3.0.0-beta.3", default-features = false } awc = { version = "3.0.0-beta.4", default-features = false }
base64 = "0.13" base64 = "0.13"
bytes = "1" bytes = "1"
@ -51,5 +51,5 @@ time = { version = "0.2.23", default-features = false, features = ["std"] }
tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
[dev-dependencies] [dev-dependencies]
actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] } actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] }
actix-http = "3.0.0-beta.4" actix-http = "3.0.0-beta.5"

View File

@ -3,9 +3,9 @@
> Various helpers for Actix applications to use during testing. > Various helpers for Actix applications to use during testing.
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.3)](https://docs.rs/actix-http-test/3.0.0-beta.3) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.4)](https://docs.rs/actix-http-test/3.0.0-beta.4)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.3/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.3) [![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.4)
[![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) [![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 ## Documentation & Resources

View File

@ -13,7 +13,9 @@ use std::{net, thread, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_rt::{net::TcpStream, System}; use actix_rt::{net::TcpStream, System};
use actix_server::{Server, ServiceFactory}; use actix_server::{Server, ServiceFactory};
use awc::{error::PayloadError, ws, Client, ClientRequest, ClientResponse, Connector}; use awc::{
error::PayloadError, http::HeaderMap, ws, Client, ClientRequest, ClientResponse, Connector,
};
use bytes::Bytes; use bytes::Bytes;
use futures_core::stream::Stream; use futures_core::stream::Stream;
use http::Method; use http::Method;
@ -115,16 +117,6 @@ 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();
socket.bind(&addr.into()).unwrap();
socket.set_reuse_address(true).unwrap();
let tcp = net::TcpListener::from(socket);
tcp.local_addr().unwrap()
}
/// Test server controller /// Test server controller
pub struct TestServer { pub struct TestServer {
addr: net::SocketAddr, addr: net::SocketAddr,
@ -258,6 +250,14 @@ impl TestServer {
self.ws_at("/").await self.ws_at("/").await
} }
/// Get default HeaderMap of Client.
///
/// Returns Some(&mut HeaderMap) when Client object is unique
/// (No other clone of client exists at the same time).
pub fn client_headers(&mut self) -> Option<&mut HeaderMap> {
self.client.headers()
}
/// Stop HTTP server /// Stop HTTP server
fn stop(&mut self) { fn stop(&mut self) {
self.system.stop(); self.system.stop();
@ -269,3 +269,13 @@ impl Drop for TestServer {
self.stop() self.stop()
} }
} }
/// Get a localhost socket address with random, unused port.
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();
socket.bind(&addr.into()).unwrap();
socket.set_reuse_address(true).unwrap();
let tcp = net::TcpListener::from(socket);
tcp.local_addr().unwrap()
}

View File

@ -1,16 +1,25 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 3.0.0-beta.5 - 2021-04-02
### Added ### Added
* `client::Connector::handshake_timeout` method for customize tls connection handshake timeout. [#2081] * `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081]
* `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] * `client::ConnectorService` as `client::Connector::finish` method's return type [#2081]
* `client::ConnectionIo` trait alias [#2081] * `client::ConnectionIo` trait alias [#2081]
### Chaged ### Changed
* `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063] * `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063]
### Removed
* Common typed HTTP headers were moved to actix-web. [2094]
* `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127]
[#2063]: https://github.com/actix/actix-web/pull/2063 [#2063]: https://github.com/actix/actix-web/pull/2063
[#2081]: https://github.com/actix/actix-web/pull/2081 [#2081]: https://github.com/actix/actix-web/pull/2081
[#2094]: https://github.com/actix/actix-web/pull/2094
[#2127]: https://github.com/actix/actix-web/pull/2127
## 3.0.0-beta.4 - 2021-03-08 ## 3.0.0-beta.4 - 2021-03-08

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "3.0.0-beta.4" version = "3.0.0-beta.5"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "HTTP primitives for the Actix ecosystem" description = "HTTP primitives for the Actix ecosystem"
readme = "README.md" readme = "README.md"
@ -46,16 +46,15 @@ trust-dns = ["trust-dns-resolver"]
[dependencies] [dependencies]
actix-service = "2.0.0-beta.4" actix-service = "2.0.0-beta.4"
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-utils = "3.0.0-beta.2" actix-utils = "3.0.0-beta.4"
actix-rt = "2.1" actix-rt = "2.2"
actix-tls = "3.0.0-beta.4" actix-tls = { version = "3.0.0-beta.5", features = ["accept", "connect"] }
ahash = "0.7" ahash = "0.7"
base64 = "0.13" base64 = "0.13"
bitflags = "1.2" bitflags = "1.2"
bytes = "1" bytes = "1"
bytestring = "1" bytestring = "1"
cfg-if = "1"
cookie = { version = "0.14.1", features = ["percent-encode"], optional = true } cookie = { version = "0.14.1", features = ["percent-encode"], optional = true }
derive_more = "0.99.5" derive_more = "0.99.5"
encoding_rs = "0.8" encoding_rs = "0.8"
@ -66,11 +65,13 @@ http = "0.2.2"
httparse = "1.3" httparse = "1.3"
itoa = "0.4" itoa = "0.4"
language-tags = "0.2" language-tags = "0.2"
local-channel = "0.1"
once_cell = "1.5" once_cell = "1.5"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
percent-encoding = "2.1" percent-encoding = "2.1"
pin-project = "1.0.0" pin-project = "1.0.0"
pin-project-lite = "0.2"
rand = "0.8" rand = "0.8"
regex = "1.3" regex = "1.3"
serde = "1.0" serde = "1.0"
@ -89,8 +90,8 @@ trust-dns-resolver = { version = "0.20.0", optional = true }
[dev-dependencies] [dev-dependencies]
actix-server = "2.0.0-beta.3" actix-server = "2.0.0-beta.3"
actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] }
actix-tls = { version = "3.0.0-beta.4", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] }
criterion = "0.3" criterion = "0.3"
env_logger = "0.8" env_logger = "0.8"
rcgen = "0.8" rcgen = "0.8"

View File

@ -3,11 +3,11 @@
> HTTP primitives for the Actix ecosystem. > HTTP primitives for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.4)](https://docs.rs/actix-http/3.0.0-beta.4) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.5)](https://docs.rs/actix-http/3.0.0-beta.5)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![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) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.4) [![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.5/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.5)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![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) [![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)

View File

@ -2,7 +2,7 @@ use std::{env, io};
use actix_http::{HttpService, Response}; use actix_http::{HttpService, Response};
use actix_server::Server; use actix_server::Server;
use futures_util::future; use actix_utils::future;
use http::header::HeaderValue; use http::header::HeaderValue;
use log::info; use log::info;

View File

@ -20,8 +20,9 @@ mod tests {
use std::pin::Pin; use std::pin::Pin;
use actix_rt::pin; use actix_rt::pin;
use actix_utils::future::poll_fn;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures_util::{future::poll_fn, stream}; use futures_util::stream;
use super::*; use super::*;

View File

@ -63,11 +63,9 @@ where
X: ServiceFactory<Request, Config = (), Response = Request>, X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
<X::Service as Service<Request>>::Future: 'static,
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>, U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
<U::Service as Service<(Request, Framed<T, Codec>)>>::Future: 'static,
{ {
/// Set server keep-alive setting. /// Set server keep-alive setting.
/// ///
@ -127,7 +125,6 @@ where
X1: ServiceFactory<Request, Config = (), Response = Request>, X1: ServiceFactory<Request, Config = (), Response = Request>,
X1::Error: Into<Error>, X1::Error: Into<Error>,
X1::InitError: fmt::Debug, X1::InitError: fmt::Debug,
<X1::Service as Service<Request>>::Future: 'static,
{ {
HttpServiceBuilder { HttpServiceBuilder {
keep_alive: self.keep_alive, keep_alive: self.keep_alive,
@ -152,7 +149,6 @@ where
U1: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>, U1: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
U1::Error: fmt::Display, U1::Error: fmt::Display,
U1::InitError: fmt::Debug, U1::InitError: fmt::Debug,
<U1::Service as Service<(Request, Framed<T, Codec>)>>::Future: 'static,
{ {
HttpServiceBuilder { HttpServiceBuilder {
keep_alive: self.keep_alive, keep_alive: self.keep_alive,
@ -211,7 +207,6 @@ where
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
{ {
let cfg = ServiceConfig::new( let cfg = ServiceConfig::new(
self.keep_alive, self.keep_alive,
@ -233,7 +228,6 @@ where
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
{ {
let cfg = ServiceConfig::new( let cfg = ServiceConfig::new(
self.keep_alive, self.keep_alive,

View File

@ -174,8 +174,14 @@ impl H2ConnectionInner {
/// Cancel spawned connection task on drop. /// Cancel spawned connection task on drop.
impl Drop for H2ConnectionInner { impl Drop for H2ConnectionInner {
fn drop(&mut self) { fn drop(&mut self) {
if self
.sender
.send_request(http::Request::new(()), true)
.is_err()
{
self.handle.abort(); self.handle.abort();
} }
}
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -398,9 +404,18 @@ where
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::net; use std::{
future::Future,
net,
pin::Pin,
task::{Context, Poll},
time::{Duration, Instant},
};
use actix_rt::net::TcpStream; use actix_rt::{
net::TcpStream,
time::{interval, Interval},
};
use super::*; use super::*;
@ -424,9 +439,36 @@ mod test {
drop(conn); drop(conn);
match sender.ready().await { struct DropCheck {
Ok(_) => panic!("connection should be gone and can not be ready"), sender: h2::client::SendRequest<Bytes>,
Err(e) => assert!(e.is_io()), interval: Interval,
}; start_from: Instant,
}
impl Future for DropCheck {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
match futures_core::ready!(this.sender.poll_ready(cx)) {
Ok(()) => {
if this.start_from.elapsed() > Duration::from_secs(10) {
panic!("connection should be gone and can not be ready");
} else {
let _ = this.interval.poll_tick(cx);
Poll::Pending
}
}
Err(_) => Poll::Ready(()),
}
}
}
DropCheck {
sender,
interval: interval(Duration::from_millis(100)),
start_from: Instant::now(),
}
.await;
} }
} }

View File

@ -9,7 +9,7 @@ use std::{
}; };
use actix_rt::{ use actix_rt::{
net::TcpStream, net::{ActixStream, TcpStream},
time::{sleep, Sleep}, time::{sleep, Sleep},
}; };
use actix_service::Service; use actix_service::Service;
@ -119,7 +119,7 @@ impl<S> Connector<S> {
/// Use custom connector. /// Use custom connector.
pub fn connector<S1, Io1>(self, connector: S1) -> Connector<S1> pub fn connector<S1, Io1>(self, connector: S1) -> Connector<S1>
where where
Io1: ConnectionIo + fmt::Debug, Io1: ActixStream + fmt::Debug + 'static,
S1: Service< S1: Service<
TcpConnect<Uri>, TcpConnect<Uri>,
Response = TcpConnection<Uri, Io1>, Response = TcpConnection<Uri, Io1>,
@ -136,7 +136,14 @@ impl<S> Connector<S> {
impl<S, Io> Connector<S> impl<S, Io> Connector<S>
where where
Io: ConnectionIo + fmt::Debug, // Note:
// Input Io type is bound to ActixStream trait but internally in client module they
// are bound to ConnectionIo trait alias. And latter is the trait exposed to public
// in the form of Box<dyn ConnectionIo> type.
//
// This remap is to hide ActixStream's trait methods. They are not meant to be called
// from user code.
Io: ActixStream + fmt::Debug + 'static,
S: Service< S: Service<
TcpConnect<Uri>, TcpConnect<Uri>,
Response = TcpConnection<Uri, Io>, Response = TcpConnection<Uri, Io>,
@ -407,16 +414,14 @@ struct TlsConnectorService<S, St> {
timeout: Duration, timeout: Duration,
} }
impl<S, St, Io, Res> Service<Connect> for TlsConnectorService<S, St> impl<S, St, Io> Service<Connect> for TlsConnectorService<S, St>
where where
S: Service<Connect, Response = TcpConnection<Uri, Io>, Error = ConnectError> S: Service<Connect, Response = TcpConnection<Uri, Io>, Error = ConnectError>
+ Clone + Clone
+ 'static, + 'static,
St: Service<TcpConnection<Uri, Io>, Response = Res, Error = std::io::Error> St: Service<TcpConnection<Uri, Io>, Error = std::io::Error> + Clone + 'static,
+ Clone
+ 'static,
Io: ConnectionIo, Io: ConnectionIo,
Res: IntoConnectionIo, St::Response: IntoConnectionIo,
{ {
type Response = (Box<dyn ConnectionIo>, Protocol); type Response = (Box<dyn ConnectionIo>, Protocol);
type Error = ConnectError; type Error = ConnectError;
@ -471,10 +476,10 @@ where
Error = std::io::Error, Error = std::io::Error,
Future = Fut2, Future = Fut2,
>, >,
S::Response: IntoConnectionIo,
Fut1: Future<Output = Result<TcpConnection<Uri, Io>, ConnectError>>, Fut1: Future<Output = Result<TcpConnection<Uri, Io>, ConnectError>>,
Fut2: Future<Output = Result<S::Response, S::Error>>, Fut2: Future<Output = Result<S::Response, S::Error>>,
Io: ConnectionIo, Io: ConnectionIo,
Res: IntoConnectionIo,
{ {
type Output = Result<(Box<dyn ConnectionIo>, Protocol), ConnectError>; type Output = Result<(Box<dyn ConnectionIo>, Protocol), ConnectError>;

View File

@ -5,10 +5,11 @@ use std::{
}; };
use actix_codec::Framed; use actix_codec::Framed;
use actix_utils::future::poll_fn;
use bytes::buf::BufMut; use bytes::buf::BufMut;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures_core::Stream; use futures_core::{ready, Stream};
use futures_util::{future::poll_fn, SinkExt as _}; use futures_util::SinkExt as _;
use crate::error::PayloadError; use crate::error::PayloadError;
use crate::h1; use crate::h1;
@ -17,7 +18,7 @@ use crate::http::{
StatusCode, StatusCode,
}; };
use crate::message::{RequestHeadType, ResponseHead}; use crate::message::{RequestHeadType, ResponseHead};
use crate::payload::{Payload, PayloadStream}; use crate::payload::Payload;
use super::connection::{ConnectionIo, H1Connection}; use super::connection::{ConnectionIo, H1Connection};
use super::error::{ConnectError, SendRequestError}; use super::error::{ConnectError, SendRequestError};
@ -122,10 +123,7 @@ where
Ok((head, Payload::None)) Ok((head, Payload::None))
} }
_ => { _ => Ok((head, Payload::Stream(Box::pin(PlStream::new(framed))))),
let pl: PayloadStream = Box::pin(PlStream::new(framed));
Ok((head, pl.into()))
}
} }
} }
@ -194,21 +192,16 @@ where
} }
#[pin_project::pin_project] #[pin_project::pin_project]
pub(crate) struct PlStream<Io: ConnectionIo> pub(crate) struct PlStream<Io: ConnectionIo> {
where
Io: ConnectionIo,
{
#[pin] #[pin]
framed: Option<Framed<H1Connection<Io>, h1::ClientPayloadCodec>>, framed: Framed<H1Connection<Io>, h1::ClientPayloadCodec>,
} }
impl<Io: ConnectionIo> PlStream<Io> { impl<Io: ConnectionIo> PlStream<Io> {
fn new(framed: Framed<H1Connection<Io>, h1::ClientCodec>) -> Self { fn new(framed: Framed<H1Connection<Io>, h1::ClientCodec>) -> Self {
let framed = framed.into_map_codec(|codec| codec.into_payload_codec()); let framed = framed.into_map_codec(|codec| codec.into_payload_codec());
PlStream { PlStream { framed }
framed: Some(framed),
}
} }
} }
@ -219,20 +212,16 @@ impl<Io: ConnectionIo> Stream for PlStream<Io> {
self: Pin<&mut Self>, self: Pin<&mut Self>,
cx: &mut Context<'_>, cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> { ) -> Poll<Option<Self::Item>> {
let mut framed = self.project().framed.as_pin_mut().unwrap(); let mut this = self.project();
match framed.as_mut().next_item(cx)? { match ready!(this.framed.as_mut().next_item(cx)?) {
Poll::Pending => Poll::Pending, Some(Some(chunk)) => Poll::Ready(Some(Ok(chunk))),
Poll::Ready(Some(chunk)) => { Some(None) => {
if let Some(chunk) = chunk { let keep_alive = this.framed.codec_ref().keepalive();
Poll::Ready(Some(Ok(chunk))) this.framed.io_mut().on_release(keep_alive);
} else {
let keep_alive = framed.codec_ref().keepalive();
framed.io_mut().on_release(keep_alive);
Poll::Ready(None) Poll::Ready(None)
} }
} None => Poll::Ready(None),
Poll::Ready(None) => Poll::Ready(None),
} }
} }
} }

View File

@ -1,7 +1,7 @@
use std::future::Future; use std::future::Future;
use actix_utils::future::poll_fn;
use bytes::Bytes; use bytes::Bytes;
use futures_util::future::poll_fn;
use h2::{ use h2::{
client::{Builder, Connection, SendRequest}, client::{Builder, Connection, SendRequest},
SendStream, SendStream,

View File

@ -6,9 +6,6 @@ use std::str::Utf8Error;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use std::{fmt, io, result}; use std::{fmt, io, result};
use actix_codec::{Decoder, Encoder};
use actix_utils::dispatcher::DispatcherError as FramedDispatcherError;
use actix_utils::timeout::TimeoutError;
use bytes::BytesMut; use bytes::BytesMut;
use derive_more::{Display, From}; use derive_more::{Display, From};
use http::uri::InvalidUri; use http::uri::InvalidUri;
@ -148,19 +145,6 @@ impl From<ResponseBuilder> for Error {
} }
} }
/// Inspects the underlying enum and returns an appropriate status code.
///
/// If the variant is [`TimeoutError::Service`], the error code of the service is returned.
/// Otherwise, [`StatusCode::GATEWAY_TIMEOUT`] is returned.
impl<E: ResponseError> ResponseError for TimeoutError<E> {
fn status_code(&self) -> StatusCode {
match self {
TimeoutError::Service(e) => e.status_code(),
TimeoutError::Timeout => StatusCode::GATEWAY_TIMEOUT,
}
}
}
#[derive(Debug, Display)] #[derive(Debug, Display)]
#[display(fmt = "UnknownError")] #[display(fmt = "UnknownError")]
struct UnitError; struct UnitError;
@ -469,14 +453,6 @@ impl ResponseError for ContentTypeError {
} }
} }
impl<E, U: Encoder<I> + Decoder, I> ResponseError for FramedDispatcherError<E, U, I>
where
E: fmt::Debug + fmt::Display,
<U as Encoder<I>>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
}
/// Helper type that can wrap any error and generate custom response. /// Helper type that can wrap any error and generate custom response.
/// ///
/// In following example any `io::Error` will be converted into "BAD REQUEST" /// In following example any `io::Error` will be converted into "BAD REQUEST"

View File

@ -951,7 +951,8 @@ mod tests {
use std::str; use std::str;
use actix_service::fn_service; use actix_service::fn_service;
use futures_util::future::{lazy, ready, Ready}; use actix_utils::future::{ready, Ready};
use futures_util::future::lazy;
use super::*; use super::*;
use crate::{ use crate::{

View File

@ -1,5 +1,5 @@
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use futures_util::future::{ready, Ready}; use actix_utils::future::{ready, Ready};
use crate::error::Error; use crate::error::Error;
use crate::request::Request; use crate::request::Request;

View File

@ -263,7 +263,7 @@ impl Inner {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use futures_util::future::poll_fn; use actix_utils::future::poll_fn;
#[actix_rt::test] #[actix_rt::test]
async fn test_unread_data() { async fn test_unread_data() {

View File

@ -1,6 +1,4 @@
use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::{fmt, net}; use std::{fmt, net};
@ -8,15 +6,15 @@ use std::{fmt, net};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
use futures_core::ready; use actix_utils::future::ready;
use futures_util::future::ready; use futures_core::future::LocalBoxFuture;
use crate::body::MessageBody; use crate::body::MessageBody;
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error}; use crate::error::{DispatchError, Error};
use crate::request::Request; use crate::request::Request;
use crate::response::Response; use crate::response::Response;
use crate::service::HttpFlow; use crate::service::HttpServiceHandler;
use crate::{ConnectCallback, OnConnectData}; use crate::{ConnectCallback, OnConnectData};
use super::codec::Codec; use super::codec::Codec;
@ -60,14 +58,17 @@ where
impl<S, B, X, U> H1Service<TcpStream, S, B, X, U> impl<S, B, X, U> H1Service<TcpStream, S, B, X, U>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error>, S::Error: Into<Error>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
B: MessageBody, B: MessageBody,
X: ServiceFactory<Request, Config = (), Response = Request>, X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<TcpStream, Codec>), Config = (), Response = ()>, U: ServiceFactory<(Request, Framed<TcpStream, Codec>), Config = (), Response = ()>,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>, U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
@ -94,17 +95,21 @@ mod openssl {
use super::*; use super::*;
use actix_service::ServiceFactoryExt; use actix_service::ServiceFactoryExt;
use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream}; use actix_tls::accept::{
use actix_tls::accept::TlsError; openssl::{Acceptor, SslAcceptor, SslError, TlsStream},
TlsError,
};
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U> impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error>, S::Error: Into<Error>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
B: MessageBody, B: MessageBody,
X: ServiceFactory<Request, Config = (), Response = Request>, X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: ServiceFactory< U: ServiceFactory<
@ -112,6 +117,7 @@ mod openssl {
Config = (), Config = (),
Response = (), Response = (),
>, >,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>, U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
@ -143,19 +149,25 @@ mod openssl {
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
mod rustls { mod rustls {
use super::*; use super::*;
use std::io;
use actix_service::ServiceFactoryExt; use actix_service::ServiceFactoryExt;
use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream}; use actix_tls::accept::{
use actix_tls::accept::TlsError; rustls::{Acceptor, ServerConfig, TlsStream},
use std::{fmt, io}; TlsError,
};
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U> impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error>, S::Error: Into<Error>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
B: MessageBody, B: MessageBody,
X: ServiceFactory<Request, Config = (), Response = Request>, X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: ServiceFactory< U: ServiceFactory<
@ -163,6 +175,7 @@ mod rustls {
Config = (), Config = (),
Response = (), Response = (),
>, >,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>, U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
@ -241,16 +254,19 @@ where
impl<T, S, B, X, U> ServiceFactory<(T, Option<net::SocketAddr>)> impl<T, S, B, X, U> ServiceFactory<(T, Option<net::SocketAddr>)>
for H1Service<T, S, B, X, U> for H1Service<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin + 'static,
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
B: MessageBody, B: MessageBody,
X: ServiceFactory<Request, Config = (), Response = Request>, X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>, U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>, U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
@ -259,148 +275,50 @@ where
type Config = (); type Config = ();
type Service = H1ServiceHandler<T, S::Service, B, X::Service, U::Service>; type Service = H1ServiceHandler<T, S::Service, B, X::Service, U::Service>;
type InitError = (); type InitError = ();
type Future = H1ServiceResponse<T, S, B, X, U>; type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
H1ServiceResponse { let service = self.srv.new_service(());
fut: self.srv.new_service(()), let expect = self.expect.new_service(());
fut_ex: Some(self.expect.new_service(())), let upgrade = self.upgrade.as_ref().map(|s| s.new_service(()));
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), let on_connect_ext = self.on_connect_ext.clone();
expect: None, let cfg = self.cfg.clone();
upgrade: None,
on_connect_ext: self.on_connect_ext.clone(), Box::pin(async move {
cfg: Some(self.cfg.clone()), let expect = expect
_phantom: PhantomData, .await
.map_err(|e| log::error!("Init http expect service error: {:?}", e))?;
let upgrade = match upgrade {
Some(upgrade) => {
let upgrade = upgrade.await.map_err(|e| {
log::error!("Init http upgrade service error: {:?}", e)
})?;
Some(upgrade)
} }
} None => None,
} };
#[doc(hidden)] let service = service
#[pin_project::pin_project] .await
pub struct H1ServiceResponse<T, S, B, X, U> .map_err(|e| log::error!("Init http service error: {:?}", e))?;
where
S: ServiceFactory<Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
X: ServiceFactory<Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
{
#[pin]
fut: S::Future,
#[pin]
fut_ex: Option<X::Future>,
#[pin]
fut_upg: Option<U::Future>,
expect: Option<X::Service>,
upgrade: Option<U::Service>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
cfg: Option<ServiceConfig>,
_phantom: PhantomData<B>,
}
impl<T, S, B, X, U> Future for H1ServiceResponse<T, S, B, X, U> Ok(H1ServiceHandler::new(
where cfg,
T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
S::InitError: fmt::Debug,
B: MessageBody,
X: ServiceFactory<Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
{
type Output = Result<H1ServiceHandler<T, S::Service, B, X::Service, U::Service>, ()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
if let Some(fut) = this.fut_ex.as_pin_mut() {
let expect = ready!(fut
.poll(cx)
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
this = self.as_mut().project();
*this.expect = Some(expect);
this.fut_ex.set(None);
}
if let Some(fut) = this.fut_upg.as_pin_mut() {
let upgrade = ready!(fut
.poll(cx)
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
this = self.as_mut().project();
*this.upgrade = Some(upgrade);
this.fut_upg.set(None);
}
let result = ready!(this
.fut
.poll(cx)
.map_err(|e| log::error!("Init http service error: {:?}", e)));
Poll::Ready(result.map(|service| {
let this = self.as_mut().project();
H1ServiceHandler::new(
this.cfg.take().unwrap(),
service, service,
this.expect.take().unwrap(), expect,
this.upgrade.take(), upgrade,
this.on_connect_ext.clone(), on_connect_ext,
) ))
})) })
} }
} }
/// `Service` implementation for HTTP/1 transport /// `Service` implementation for HTTP/1 transport
pub struct H1ServiceHandler<T, S, B, X, U> pub type H1ServiceHandler<T, S, B, X, U> = HttpServiceHandler<T, S, B, X, U>;
where
S: Service<Request>,
X: Service<Request>,
U: Service<(Request, Framed<T, Codec>)>,
{
flow: Rc<HttpFlow<S, X, U>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
cfg: ServiceConfig,
_phantom: PhantomData<B>,
}
impl<T, S, B, X, U> H1ServiceHandler<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
fn new(
cfg: ServiceConfig,
service: S,
expect: X,
upgrade: Option<U>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
) -> H1ServiceHandler<T, S, B, X, U> {
H1ServiceHandler {
flow: HttpFlow::new(service, expect, upgrade),
cfg,
on_connect_ext,
_phantom: PhantomData,
}
}
}
impl<T, S, B, X, U> Service<(T, Option<net::SocketAddr>)> impl<T, S, B, X, U> Service<(T, Option<net::SocketAddr>)>
for H1ServiceHandler<T, S, B, X, U> for HttpServiceHandler<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>, S: Service<Request>,
@ -417,47 +335,10 @@ where
type Future = Dispatcher<T, S, B, X, U>; type Future = Dispatcher<T, S, B, X, U>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let ready = self self._poll_ready(cx).map_err(|e| {
.flow log::error!("HTTP/1 service readiness error: {:?}", e);
.expect
.poll_ready(cx)
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e) DispatchError::Service(e)
})? })
.is_ready();
let ready = self
.flow
.service
.poll_ready(cx)
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e)
})?
.is_ready()
&& ready;
let ready = if let Some(ref upg) = self.flow.upgrade {
upg.poll_ready(cx)
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e)
})?
.is_ready()
&& ready
} else {
ready
};
if ready {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
} }
fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future { fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {

View File

@ -10,9 +10,9 @@ use actix_service::{
fn_factory, fn_service, pipeline_factory, IntoServiceFactory, Service, fn_factory, fn_service, pipeline_factory, IntoServiceFactory, Service,
ServiceFactory, ServiceFactory,
}; };
use actix_utils::future::ready;
use bytes::Bytes; use bytes::Bytes;
use futures_core::ready; use futures_core::{future::LocalBoxFuture, ready};
use futures_util::future::ok;
use h2::server::{handshake, Handshake}; use h2::server::{handshake, Handshake};
use log::error; use log::error;
@ -65,6 +65,7 @@ where
impl<S, B> H2Service<TcpStream, S, B> impl<S, B> H2Service<TcpStream, S, B>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
@ -80,11 +81,11 @@ where
Error = DispatchError, Error = DispatchError,
InitError = S::InitError, InitError = S::InitError,
> { > {
pipeline_factory(fn_factory(|| async { pipeline_factory(fn_factory(|| {
Ok::<_, S::InitError>(fn_service(|io: TcpStream| { ready(Ok::<_, S::InitError>(fn_service(|io: TcpStream| {
let peer_addr = io.peer_addr().ok(); let peer_addr = io.peer_addr().ok();
ok::<_, DispatchError>((io, peer_addr)) ready(Ok::<_, DispatchError>((io, peer_addr)))
})) })))
})) }))
.and_then(self) .and_then(self)
} }
@ -101,6 +102,7 @@ mod openssl {
impl<S, B> H2Service<TlsStream<TcpStream>, S, B> impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
@ -123,10 +125,12 @@ mod openssl {
.map_init_err(|_| panic!()), .map_init_err(|_| panic!()),
) )
.and_then(fn_factory(|| { .and_then(fn_factory(|| {
ok::<_, S::InitError>(fn_service(|io: TlsStream<TcpStream>| { ready(Ok::<_, S::InitError>(fn_service(
|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().peer_addr().ok(); let peer_addr = io.get_ref().peer_addr().ok();
ok((io, peer_addr)) ready(Ok((io, peer_addr)))
})) },
)))
})) }))
.and_then(self.map_err(TlsError::Service)) .and_then(self.map_err(TlsError::Service))
} }
@ -144,6 +148,7 @@ mod rustls {
impl<S, B> H2Service<TlsStream<TcpStream>, S, B> impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
@ -169,10 +174,12 @@ mod rustls {
.map_init_err(|_| panic!()), .map_init_err(|_| panic!()),
) )
.and_then(fn_factory(|| { .and_then(fn_factory(|| {
ok::<_, S::InitError>(fn_service(|io: TlsStream<TcpStream>| { ready(Ok::<_, S::InitError>(fn_service(
|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok(); let peer_addr = io.get_ref().0.peer_addr().ok();
ok((io, peer_addr)) ready(Ok((io, peer_addr)))
})) },
)))
})) }))
.and_then(self.map_err(TlsError::Service)) .and_then(self.map_err(TlsError::Service))
} }
@ -181,8 +188,9 @@ mod rustls {
impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B> impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B>
where where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin + 'static,
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
@ -193,52 +201,16 @@ where
type Config = (); type Config = ();
type Service = H2ServiceHandler<T, S::Service, B>; type Service = H2ServiceHandler<T, S::Service, B>;
type InitError = S::InitError; type InitError = S::InitError;
type Future = H2ServiceResponse<T, S, B>; type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
H2ServiceResponse { let service = self.srv.new_service(());
fut: self.srv.new_service(()), let cfg = self.cfg.clone();
cfg: Some(self.cfg.clone()), let on_connect_ext = self.on_connect_ext.clone();
on_connect_ext: self.on_connect_ext.clone(),
_phantom: PhantomData,
}
}
}
#[doc(hidden)] Box::pin(async move {
#[pin_project::pin_project] let service = service.await?;
pub struct H2ServiceResponse<T, S, B> Ok(H2ServiceHandler::new(cfg, on_connect_ext, service))
where
S: ServiceFactory<Request>,
{
#[pin]
fut: S::Future,
cfg: Option<ServiceConfig>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_phantom: PhantomData<B>,
}
impl<T, S, B> Future for H2ServiceResponse<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
type Output = Result<H2ServiceHandler<T, S::Service, B>, S::InitError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.as_mut().project();
this.fut.poll(cx).map_ok(|service| {
let this = self.as_mut().project();
H2ServiceHandler::new(
this.cfg.take().unwrap(),
this.on_connect_ext.clone(),
service,
)
}) })
} }
} }

View File

@ -1,9 +1,6 @@
//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing and conversion, and other //! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing and conversion, and other
//! header utility methods. //! header utility methods.
use std::fmt;
use bytes::{Bytes, BytesMut};
use percent_encoding::{AsciiSet, CONTROLS}; use percent_encoding::{AsciiSet, CONTROLS};
pub use http::header::*; pub use http::header::*;
@ -16,11 +13,9 @@ mod into_pair;
mod into_value; mod into_value;
mod utils; mod utils;
mod common;
pub(crate) mod map; pub(crate) mod map;
mod shared; mod shared;
pub use self::common::*;
#[doc(hidden)] #[doc(hidden)]
pub use self::shared::*; pub use self::shared::*;
@ -41,34 +36,6 @@ pub trait Header: IntoHeaderValue {
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>; fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
} }
#[derive(Debug, Default)]
pub(crate) struct Writer {
buf: BytesMut,
}
impl Writer {
fn new() -> Writer {
Writer::default()
}
fn take(&mut self) -> Bytes {
self.buf.split().freeze()
}
}
impl fmt::Write for Writer {
#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.buf.extend_from_slice(s.as_bytes());
Ok(())
}
#[inline]
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
fmt::write(self, args)
}
}
/// Convert `http::HeaderMap` to our `HeaderMap`. /// Convert `http::HeaderMap` to our `HeaderMap`.
impl From<http::HeaderMap> for HeaderMap { impl From<http::HeaderMap> for HeaderMap {
fn from(mut map: http::HeaderMap) -> HeaderMap { fn from(mut map: http::HeaderMap) -> HeaderMap {

View File

@ -1,18 +1,20 @@
use std::fmt::{self, Display}; use std::{
use std::io::Write; fmt,
use std::str::FromStr; io::Write,
use std::time::{SystemTime, UNIX_EPOCH}; str::FromStr,
time::{SystemTime, UNIX_EPOCH},
};
use bytes::buf::BufMut; use bytes::buf::BufMut;
use bytes::BytesMut; use bytes::BytesMut;
use http::header::{HeaderValue, InvalidHeaderValue}; use http::header::{HeaderValue, InvalidHeaderValue};
use time::{offset, OffsetDateTime, PrimitiveDateTime}; use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
use crate::error::ParseError; use crate::error::ParseError;
use crate::header::IntoHeaderValue; use crate::header::IntoHeaderValue;
use crate::time_parser; use crate::time_parser;
/// A timestamp with HTTP formatting and parsing /// A timestamp with HTTP formatting and parsing.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct HttpDate(OffsetDateTime); pub struct HttpDate(OffsetDateTime);
@ -27,18 +29,12 @@ impl FromStr for HttpDate {
} }
} }
impl Display for HttpDate { impl fmt::Display for HttpDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0.format("%a, %d %b %Y %H:%M:%S GMT"), f) fmt::Display::fmt(&self.0.format("%a, %d %b %Y %H:%M:%S GMT"), f)
} }
} }
impl From<OffsetDateTime> for HttpDate {
fn from(dt: OffsetDateTime) -> HttpDate {
HttpDate(dt)
}
}
impl From<SystemTime> for HttpDate { impl From<SystemTime> for HttpDate {
fn from(sys: SystemTime) -> HttpDate { fn from(sys: SystemTime) -> HttpDate {
HttpDate(PrimitiveDateTime::from(sys).assume_utc()) HttpDate(PrimitiveDateTime::from(sys).assume_utc())
@ -54,7 +50,7 @@ impl IntoHeaderValue for HttpDate {
wrt, wrt,
"{}", "{}",
self.0 self.0
.to_offset(offset!(UTC)) .to_offset(UtcOffset::UTC)
.format("%a, %d %b %Y %H:%M:%S GMT") .format("%a, %d %b %Y %H:%M:%S GMT")
) )
.unwrap(); .unwrap();

View File

@ -1,15 +1,13 @@
//! Originally taken from `hyper::header::shared`. //! Originally taken from `hyper::header::shared`.
mod charset; mod charset;
mod encoding; mod content_encoding;
mod entity;
mod extended; mod extended;
mod httpdate; mod httpdate;
mod quality_item; mod quality_item;
pub use self::charset::Charset; pub use self::charset::Charset;
pub use self::encoding::Encoding; pub use self::content_encoding::ContentEncoding;
pub use self::entity::EntityTag;
pub use self::extended::{parse_extended_value, ExtendedValue}; pub use self::extended::{parse_extended_value, ExtendedValue};
pub use self::httpdate::HttpDate; pub use self::httpdate::HttpDate;
pub use self::quality_item::{q, qitem, Quality, QualityItem}; pub use self::quality_item::{q, qitem, Quality, QualityItem};

View File

@ -193,21 +193,69 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::encoding::*;
use super::*; use super::*;
// copy of encoding from actix-web headers
#[derive(Clone, PartialEq, Debug)]
pub enum Encoding {
Chunked,
Brotli,
Gzip,
Deflate,
Compress,
Identity,
Trailers,
EncodingExt(String),
}
impl fmt::Display for Encoding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Encoding::*;
f.write_str(match *self {
Chunked => "chunked",
Brotli => "br",
Gzip => "gzip",
Deflate => "deflate",
Compress => "compress",
Identity => "identity",
Trailers => "trailers",
EncodingExt(ref s) => s.as_ref(),
})
}
}
impl str::FromStr for Encoding {
type Err = crate::error::ParseError;
fn from_str(s: &str) -> Result<Encoding, crate::error::ParseError> {
use Encoding::*;
match s {
"chunked" => Ok(Chunked),
"br" => Ok(Brotli),
"deflate" => Ok(Deflate),
"gzip" => Ok(Gzip),
"compress" => Ok(Compress),
"identity" => Ok(Identity),
"trailers" => Ok(Trailers),
_ => Ok(EncodingExt(s.to_owned())),
}
}
}
#[test] #[test]
fn test_quality_item_fmt_q_1() { fn test_quality_item_fmt_q_1() {
use Encoding::*;
let x = qitem(Chunked); let x = qitem(Chunked);
assert_eq!(format!("{}", x), "chunked"); assert_eq!(format!("{}", x), "chunked");
} }
#[test] #[test]
fn test_quality_item_fmt_q_0001() { fn test_quality_item_fmt_q_0001() {
use Encoding::*;
let x = QualityItem::new(Chunked, Quality(1)); let x = QualityItem::new(Chunked, Quality(1));
assert_eq!(format!("{}", x), "chunked; q=0.001"); assert_eq!(format!("{}", x), "chunked; q=0.001");
} }
#[test] #[test]
fn test_quality_item_fmt_q_05() { fn test_quality_item_fmt_q_05() {
use Encoding::*;
// Custom value // Custom value
let x = QualityItem { let x = QualityItem {
item: EncodingExt("identity".to_owned()), item: EncodingExt("identity".to_owned()),
@ -218,6 +266,7 @@ mod tests {
#[test] #[test]
fn test_quality_item_fmt_q_0() { fn test_quality_item_fmt_q_0() {
use Encoding::*;
// Custom value // Custom value
let x = QualityItem { let x = QualityItem {
item: EncodingExt("identity".to_owned()), item: EncodingExt("identity".to_owned()),
@ -228,6 +277,7 @@ mod tests {
#[test] #[test]
fn test_quality_item_from_str1() { fn test_quality_item_from_str1() {
use Encoding::*;
let x: Result<QualityItem<Encoding>, _> = "chunked".parse(); let x: Result<QualityItem<Encoding>, _> = "chunked".parse();
assert_eq!( assert_eq!(
x.unwrap(), x.unwrap(),
@ -237,8 +287,10 @@ mod tests {
} }
); );
} }
#[test] #[test]
fn test_quality_item_from_str2() { fn test_quality_item_from_str2() {
use Encoding::*;
let x: Result<QualityItem<Encoding>, _> = "chunked; q=1".parse(); let x: Result<QualityItem<Encoding>, _> = "chunked; q=1".parse();
assert_eq!( assert_eq!(
x.unwrap(), x.unwrap(),
@ -248,8 +300,10 @@ mod tests {
} }
); );
} }
#[test] #[test]
fn test_quality_item_from_str3() { fn test_quality_item_from_str3() {
use Encoding::*;
let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.5".parse(); let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.5".parse();
assert_eq!( assert_eq!(
x.unwrap(), x.unwrap(),
@ -259,8 +313,10 @@ mod tests {
} }
); );
} }
#[test] #[test]
fn test_quality_item_from_str4() { fn test_quality_item_from_str4() {
use Encoding::*;
let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.273".parse(); let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.273".parse();
assert_eq!( assert_eq!(
x.unwrap(), x.unwrap(),
@ -270,16 +326,19 @@ mod tests {
} }
); );
} }
#[test] #[test]
fn test_quality_item_from_str5() { fn test_quality_item_from_str5() {
let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.2739999".parse(); let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.2739999".parse();
assert!(x.is_err()); assert!(x.is_err());
} }
#[test] #[test]
fn test_quality_item_from_str6() { fn test_quality_item_from_str6() {
let x: Result<QualityItem<Encoding>, _> = "gzip; q=2".parse(); let x: Result<QualityItem<Encoding>, _> = "gzip; q=2".parse();
assert!(x.is_err()); assert!(x.is_err());
} }
#[test] #[test]
fn test_quality_item_ordering() { fn test_quality_item_ordering() {
let x: QualityItem<Encoding> = "gzip; q=0.5".parse().ok().unwrap(); let x: QualityItem<Encoding> = "gzip; q=0.5".parse().ok().unwrap();

View File

@ -1,7 +1,6 @@
use std::{fmt, str::FromStr}; use std::{fmt, str::FromStr};
use http::HeaderValue; use super::HeaderValue;
use crate::{error::ParseError, header::HTTP_VALUE}; use crate::{error::ParseError, header::HTTP_VALUE};
/// Reads a comma-delimited raw header into a Vec. /// Reads a comma-delimited raw header into a Vec.

View File

@ -359,10 +359,10 @@ impl ResponseBuilder {
/// ///
/// ``` /// ```
/// # use actix_http::Response; /// # use actix_http::Response;
/// use actix_http::http::header::ContentType; /// use actix_http::http::header;
/// ///
/// Response::Ok() /// Response::Ok()
/// .insert_header(ContentType(mime::APPLICATION_JSON)) /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON))
/// .insert_header(("X-TEST", "value")) /// .insert_header(("X-TEST", "value"))
/// .finish(); /// .finish();
/// ``` /// ```
@ -386,10 +386,10 @@ impl ResponseBuilder {
/// ///
/// ``` /// ```
/// # use actix_http::Response; /// # use actix_http::Response;
/// use actix_http::http::header::ContentType; /// use actix_http::http::header;
/// ///
/// Response::Ok() /// Response::Ok()
/// .append_header(ContentType(mime::APPLICATION_JSON)) /// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON))
/// .append_header(("X-TEST", "value1")) /// .append_header(("X-TEST", "value1"))
/// .append_header(("X-TEST", "value2")) /// .append_header(("X-TEST", "value2"))
/// .finish(); /// .finish();
@ -682,7 +682,7 @@ impl ResponseBuilder {
}; };
if !contains { if !contains {
self.insert_header(header::ContentType(mime::APPLICATION_JSON)); self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
} }
self.body(Body::from(body)) self.body(Body::from(body))
@ -1133,7 +1133,7 @@ mod tests {
#[test] #[test]
fn response_builder_header_insert_typed() { fn response_builder_header_insert_typed() {
let mut res = Response::Ok(); let mut res = Response::Ok();
res.insert_header(header::ContentType(mime::APPLICATION_OCTET_STREAM)); res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
let res = res.finish(); let res = res.finish();
assert_eq!( assert_eq!(
@ -1158,8 +1158,8 @@ mod tests {
#[test] #[test]
fn response_builder_header_append_typed() { fn response_builder_header_append_typed() {
let mut res = Response::Ok(); let mut res = Response::Ok();
res.append_header(header::ContentType(mime::APPLICATION_OCTET_STREAM)); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
res.append_header(header::ContentType(mime::APPLICATION_JSON)); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
let res = res.finish(); let res = res.finish();
let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();

View File

@ -12,7 +12,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
use bytes::Bytes; use bytes::Bytes;
use futures_core::ready; use futures_core::{future::LocalBoxFuture, ready};
use h2::server::{handshake, Handshake}; use h2::server::{handshake, Handshake};
use pin_project::pin_project; use pin_project::pin_project;
@ -107,7 +107,6 @@ where
X1: ServiceFactory<Request, Config = (), Response = Request>, X1: ServiceFactory<Request, Config = (), Response = Request>,
X1::Error: Into<Error>, X1::Error: Into<Error>,
X1::InitError: fmt::Debug, X1::InitError: fmt::Debug,
<X1::Service as Service<Request>>::Future: 'static,
{ {
HttpService { HttpService {
expect, expect,
@ -128,7 +127,6 @@ where
U1: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>, U1: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
U1::Error: fmt::Display, U1::Error: fmt::Display,
U1::InitError: fmt::Debug, U1::InitError: fmt::Debug,
<U1::Service as Service<(Request, Framed<T, h1::Codec>)>>::Future: 'static,
{ {
HttpService { HttpService {
upgrade, upgrade,
@ -150,23 +148,27 @@ where
impl<S, B, X, U> HttpService<TcpStream, S, B, X, U> impl<S, B, X, U> HttpService<TcpStream, S, B, X, U>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
X: ServiceFactory<Request, Config = (), Response = Request>, X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
<X::Service as Service<Request>>::Future: 'static,
U: ServiceFactory< U: ServiceFactory<
(Request, Framed<TcpStream, h1::Codec>), (Request, Framed<TcpStream, h1::Codec>),
Config = (), Config = (),
Response = (), Response = (),
>, >,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>, U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
<U::Service as Service<(Request, Framed<TcpStream, h1::Codec>)>>::Future: 'static,
{ {
/// Create simple tcp stream service /// Create simple tcp stream service
pub fn tcp( pub fn tcp(
@ -188,31 +190,36 @@ where
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
mod openssl { mod openssl {
use super::*;
use actix_service::ServiceFactoryExt; use actix_service::ServiceFactoryExt;
use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream}; use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream};
use actix_tls::accept::TlsError; use actix_tls::accept::TlsError;
use super::*;
impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U> impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
X: ServiceFactory<Request, Config = (), Response = Request>, X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
<X::Service as Service<Request>>::Future: 'static,
U: ServiceFactory< U: ServiceFactory<
(Request, Framed<TlsStream<TcpStream>, h1::Codec>), (Request, Framed<TlsStream<TcpStream>, h1::Codec>),
Config = (), Config = (),
Response = (), Response = (),
>, >,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>, U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
<U::Service as Service<(Request, Framed<TlsStream<TcpStream>, h1::Codec>)>>::Future: 'static,
{ {
/// Create openssl based service /// Create openssl based service
pub fn openssl( pub fn openssl(
@ -261,25 +268,29 @@ mod rustls {
impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U> impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U>
where where
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
X: ServiceFactory<Request, Config = (), Response = Request>, X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
<X::Service as Service<Request>>::Future: 'static,
U: ServiceFactory< U: ServiceFactory<
(Request, Framed<TlsStream<TcpStream>, h1::Codec>), (Request, Framed<TlsStream<TcpStream>, h1::Codec>),
Config = (), Config = (),
Response = (), Response = (),
>, >,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>, U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
<U::Service as Service<(Request, Framed<TlsStream<TcpStream>, h1::Codec>)>>::Future: 'static,
{ {
/// Create openssl based service /// Create rustls based service
pub fn rustls( pub fn rustls(
self, self,
mut config: ServerConfig, mut config: ServerConfig,
@ -319,137 +330,121 @@ mod rustls {
impl<T, S, B, X, U> ServiceFactory<(T, Protocol, Option<net::SocketAddr>)> impl<T, S, B, X, U> ServiceFactory<(T, Protocol, Option<net::SocketAddr>)>
for HttpService<T, S, B, X, U> for HttpService<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin + 'static,
S: ServiceFactory<Request, Config = ()>, S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static, <S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
X: ServiceFactory<Request, Config = (), Response = Request>, X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
<X::Service as Service<Request>>::Future: 'static,
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>, U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
U::Future: 'static,
U::Error: fmt::Display + Into<Error>, U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
<U::Service as Service<(Request, Framed<T, h1::Codec>)>>::Future: 'static,
{ {
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type Config = (); type Config = ();
type Service = HttpServiceHandler<T, S::Service, B, X::Service, U::Service>; type Service = HttpServiceHandler<T, S::Service, B, X::Service, U::Service>;
type InitError = (); type InitError = ();
type Future = HttpServiceResponse<T, S, B, X, U>; type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
HttpServiceResponse { let service = self.srv.new_service(());
fut: self.srv.new_service(()), let expect = self.expect.new_service(());
fut_ex: Some(self.expect.new_service(())), let upgrade = self.upgrade.as_ref().map(|s| s.new_service(()));
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), let on_connect_ext = self.on_connect_ext.clone();
expect: None, let cfg = self.cfg.clone();
upgrade: None,
on_connect_ext: self.on_connect_ext.clone(), Box::pin(async move {
cfg: self.cfg.clone(), let expect = expect
_phantom: PhantomData, .await
.map_err(|e| log::error!("Init http expect service error: {:?}", e))?;
let upgrade = match upgrade {
Some(upgrade) => {
let upgrade = upgrade.await.map_err(|e| {
log::error!("Init http upgrade service error: {:?}", e)
})?;
Some(upgrade)
} }
} None => None,
} };
#[doc(hidden)] let service = service
#[pin_project] .await
pub struct HttpServiceResponse<T, S, B, X, U> .map_err(|e| log::error!("Init http service error: {:?}", e))?;
where
S: ServiceFactory<Request>,
X: ServiceFactory<Request>,
U: ServiceFactory<(Request, Framed<T, h1::Codec>)>,
{
#[pin]
fut: S::Future,
#[pin]
fut_ex: Option<X::Future>,
#[pin]
fut_upg: Option<U::Future>,
expect: Option<X::Service>,
upgrade: Option<U::Service>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
cfg: ServiceConfig,
_phantom: PhantomData<B>,
}
impl<T, S, B, X, U> Future for HttpServiceResponse<T, S, B, X, U> Ok(HttpServiceHandler::new(
where cfg,
T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Request>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
<X::Service as Service<Request>>::Future: 'static,
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
<U::Service as Service<(Request, Framed<T, h1::Codec>)>>::Future: 'static,
{
type Output =
Result<HttpServiceHandler<T, S::Service, B, X::Service, U::Service>, ()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
if let Some(fut) = this.fut_ex.as_pin_mut() {
let expect = ready!(fut
.poll(cx)
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
this = self.as_mut().project();
*this.expect = Some(expect);
this.fut_ex.set(None);
}
if let Some(fut) = this.fut_upg.as_pin_mut() {
let upgrade = ready!(fut
.poll(cx)
.map_err(|e| log::error!("Init http service error: {:?}", e)))?;
this = self.as_mut().project();
*this.upgrade = Some(upgrade);
this.fut_upg.set(None);
}
let result = ready!(this
.fut
.poll(cx)
.map_err(|e| log::error!("Init http service error: {:?}", e)));
Poll::Ready(result.map(|service| {
let this = self.as_mut().project();
HttpServiceHandler::new(
this.cfg.clone(),
service, service,
this.expect.take().unwrap(), expect,
this.upgrade.take(), upgrade,
this.on_connect_ext.clone(), on_connect_ext,
) ))
})) })
} }
} }
/// `Service` implementation for HTTP transport /// `Service` implementation for HTTP/1 and HTTP/2 transport
pub struct HttpServiceHandler<T, S, B, X, U> pub struct HttpServiceHandler<T, S, B, X, U>
where where
S: Service<Request>, S: Service<Request>,
X: Service<Request>, X: Service<Request>,
U: Service<(Request, Framed<T, h1::Codec>)>, U: Service<(Request, Framed<T, h1::Codec>)>,
{ {
flow: Rc<HttpFlow<S, X, U>>, pub(super) flow: Rc<HttpFlow<S, X, U>>,
cfg: ServiceConfig, pub(super) cfg: ServiceConfig,
on_connect_ext: Option<Rc<ConnectCallback<T>>>, pub(super) on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_phantom: PhantomData<B>, _phantom: PhantomData<B>,
} }
impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Error>,
X: Service<Request>,
X::Error: Into<Error>,
U: Service<(Request, Framed<T, h1::Codec>)>,
U::Error: Into<Error>,
{
pub(super) fn new(
cfg: ServiceConfig,
service: S,
expect: X,
upgrade: Option<U>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
) -> HttpServiceHandler<T, S, B, X, U> {
HttpServiceHandler {
cfg,
on_connect_ext,
flow: HttpFlow::new(service, expect, upgrade),
_phantom: PhantomData,
}
}
pub(super) fn _poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?;
ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?;
if let Some(ref upg) = self.flow.upgrade {
ready!(upg.poll_ready(cx).map_err(Into::into))?;
};
Poll::Ready(Ok(()))
}
}
/// A collection of services that describe an HTTP request flow. /// A collection of services that describe an HTTP request flow.
pub(super) struct HttpFlow<S, X, U> { pub(super) struct HttpFlow<S, X, U> {
pub(super) service: S, pub(super) service: S,
@ -467,34 +462,6 @@ impl<S, X, U> HttpFlow<S, X, U> {
} }
} }
impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Error> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
X: Service<Request, Response = Request>,
X::Error: Into<Error>,
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{
fn new(
cfg: ServiceConfig,
service: S,
expect: X,
upgrade: Option<U>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
) -> HttpServiceHandler<T, S, B, X, U> {
HttpServiceHandler {
cfg,
on_connect_ext,
flow: HttpFlow::new(service, expect, upgrade),
_phantom: PhantomData,
}
}
}
impl<T, S, B, X, U> Service<(T, Protocol, Option<net::SocketAddr>)> impl<T, S, B, X, U> Service<(T, Protocol, Option<net::SocketAddr>)>
for HttpServiceHandler<T, S, B, X, U> for HttpServiceHandler<T, S, B, X, U>
where where
@ -514,47 +481,10 @@ where
type Future = HttpServiceHandlerResponse<T, S, B, X, U>; type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let ready = self self._poll_ready(cx).map_err(|e| {
.flow log::error!("HTTP service readiness error: {:?}", e);
.expect
.poll_ready(cx)
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e) DispatchError::Service(e)
})? })
.is_ready();
let ready = self
.flow
.service
.poll_ready(cx)
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e)
})?
.is_ready()
&& ready;
let ready = if let Some(ref upg) = self.flow.upgrade {
upg.poll_ready(cx)
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e)
})?
.is_ready()
&& ready
} else {
ready
};
if ready {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
} }
fn call( fn call(

View File

@ -4,7 +4,6 @@ use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_service::{IntoService, Service}; use actix_service::{IntoService, Service};
use actix_utils::dispatcher::{Dispatcher as InnerDispatcher, DispatcherError};
use super::{Codec, Frame, Message}; use super::{Codec, Frame, Message};
@ -15,7 +14,7 @@ where
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite,
{ {
#[pin] #[pin]
inner: InnerDispatcher<S, T, Codec, Message>, inner: inner::Dispatcher<S, T, Codec, Message>,
} }
impl<S, T> Dispatcher<S, T> impl<S, T> Dispatcher<S, T>
@ -27,13 +26,13 @@ where
{ {
pub fn new<F: IntoService<S, Frame>>(io: T, service: F) -> Self { pub fn new<F: IntoService<S, Frame>>(io: T, service: F) -> Self {
Dispatcher { Dispatcher {
inner: InnerDispatcher::new(Framed::new(io, Codec::new()), service), inner: inner::Dispatcher::new(Framed::new(io, Codec::new()), service),
} }
} }
pub fn with<F: IntoService<S, Frame>>(framed: Framed<T, Codec>, service: F) -> Self { pub fn with<F: IntoService<S, Frame>>(framed: Framed<T, Codec>, service: F) -> Self {
Dispatcher { Dispatcher {
inner: InnerDispatcher::new(framed, service), inner: inner::Dispatcher::new(framed, service),
} }
} }
} }
@ -45,9 +44,393 @@ where
S::Future: 'static, S::Future: 'static,
S::Error: 'static, S::Error: 'static,
{ {
type Output = Result<(), DispatcherError<S::Error, Codec, Message>>; type Output = Result<(), inner::DispatcherError<S::Error, Codec, Message>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.project().inner.poll(cx) self.project().inner.poll(cx)
} }
} }
/// Framed dispatcher service and related utilities.
mod inner {
// allow dead code since this mod was ripped from actix-utils
#![allow(dead_code)]
use core::{
fmt,
future::Future,
mem,
pin::Pin,
task::{Context, Poll},
};
use actix_service::{IntoService, Service};
use futures_core::stream::Stream;
use local_channel::mpsc;
use log::debug;
use pin_project_lite::pin_project;
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
use crate::ResponseError;
/// Framed transport errors
pub enum DispatcherError<E, U, I>
where
U: Encoder<I> + Decoder,
{
/// Inner service error.
Service(E),
/// Frame encoding error.
Encoder(<U as Encoder<I>>::Error),
/// Frame decoding error.
Decoder(<U as Decoder>::Error),
}
impl<E, U, I> From<E> for DispatcherError<E, U, I>
where
U: Encoder<I> + Decoder,
{
fn from(err: E) -> Self {
DispatcherError::Service(err)
}
}
impl<E, U, I> fmt::Debug for DispatcherError<E, U, I>
where
E: fmt::Debug,
U: Encoder<I> + Decoder,
<U as Encoder<I>>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
DispatcherError::Service(ref e) => {
write!(fmt, "DispatcherError::Service({:?})", e)
}
DispatcherError::Encoder(ref e) => {
write!(fmt, "DispatcherError::Encoder({:?})", e)
}
DispatcherError::Decoder(ref e) => {
write!(fmt, "DispatcherError::Decoder({:?})", e)
}
}
}
}
impl<E, U, I> fmt::Display for DispatcherError<E, U, I>
where
E: fmt::Display,
U: Encoder<I> + Decoder,
<U as Encoder<I>>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
DispatcherError::Service(ref e) => write!(fmt, "{}", e),
DispatcherError::Encoder(ref e) => write!(fmt, "{:?}", e),
DispatcherError::Decoder(ref e) => write!(fmt, "{:?}", e),
}
}
}
impl<E, U, I> ResponseError for DispatcherError<E, U, I>
where
E: fmt::Debug + fmt::Display,
U: Encoder<I> + Decoder,
<U as Encoder<I>>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
}
/// Message type wrapper for signalling end of message stream.
pub enum Message<T> {
/// Message item.
Item(T),
/// Signal from service to flush all messages and stop processing.
Close,
}
pin_project! {
/// A future that reads frames from a [`Framed`] object and passes them to a [`Service`].
pub struct Dispatcher<S, T, U, I>
where
S: Service<<U as Decoder>::Item, Response = I>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead,
T: AsyncWrite,
U: Encoder<I>,
U: Decoder,
I: 'static,
<U as Encoder<I>>::Error: fmt::Debug,
{
service: S,
state: State<S, U, I>,
#[pin]
framed: Framed<T, U>,
rx: mpsc::Receiver<Result<Message<I>, S::Error>>,
tx: mpsc::Sender<Result<Message<I>, S::Error>>,
}
}
enum State<S, U, I>
where
S: Service<<U as Decoder>::Item>,
U: Encoder<I> + Decoder,
{
Processing,
Error(DispatcherError<S::Error, U, I>),
FramedError(DispatcherError<S::Error, U, I>),
FlushAndStop,
Stopping,
}
impl<S, U, I> State<S, U, I>
where
S: Service<<U as Decoder>::Item>,
U: Encoder<I> + Decoder,
{
fn take_error(&mut self) -> DispatcherError<S::Error, U, I> {
match mem::replace(self, State::Processing) {
State::Error(err) => err,
_ => panic!(),
}
}
fn take_framed_error(&mut self) -> DispatcherError<S::Error, U, I> {
match mem::replace(self, State::Processing) {
State::FramedError(err) => err,
_ => panic!(),
}
}
}
impl<S, T, U, I> Dispatcher<S, T, U, I>
where
S: Service<<U as Decoder>::Item, Response = I>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Decoder + Encoder<I>,
I: 'static,
<U as Decoder>::Error: fmt::Debug,
<U as Encoder<I>>::Error: fmt::Debug,
{
/// Create new `Dispatcher`.
pub fn new<F>(framed: Framed<T, U>, service: F) -> Self
where
F: IntoService<S, <U as Decoder>::Item>,
{
let (tx, rx) = mpsc::channel();
Dispatcher {
framed,
rx,
tx,
service: service.into_service(),
state: State::Processing,
}
}
/// Construct new `Dispatcher` instance with customer `mpsc::Receiver`
pub fn with_rx<F>(
framed: Framed<T, U>,
service: F,
rx: mpsc::Receiver<Result<Message<I>, S::Error>>,
) -> Self
where
F: IntoService<S, <U as Decoder>::Item>,
{
let tx = rx.sender();
Dispatcher {
framed,
rx,
tx,
service: service.into_service(),
state: State::Processing,
}
}
/// Get sender handle.
pub fn tx(&self) -> mpsc::Sender<Result<Message<I>, S::Error>> {
self.tx.clone()
}
/// Get reference to a service wrapped by `Dispatcher` instance.
pub fn service(&self) -> &S {
&self.service
}
/// Get mutable reference to a service wrapped by `Dispatcher` instance.
pub fn service_mut(&mut self) -> &mut S {
&mut self.service
}
/// Get reference to a framed instance wrapped by `Dispatcher` instance.
pub fn framed(&self) -> &Framed<T, U> {
&self.framed
}
/// Get mutable reference to a framed instance wrapped by `Dispatcher` instance.
pub fn framed_mut(&mut self) -> &mut Framed<T, U> {
&mut self.framed
}
/// Read from framed object.
fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool
where
S: Service<<U as Decoder>::Item, Response = I>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Decoder + Encoder<I>,
I: 'static,
<U as Encoder<I>>::Error: fmt::Debug,
{
loop {
let this = self.as_mut().project();
match this.service.poll_ready(cx) {
Poll::Ready(Ok(_)) => {
let item = match this.framed.next_item(cx) {
Poll::Ready(Some(Ok(el))) => el,
Poll::Ready(Some(Err(err))) => {
*this.state =
State::FramedError(DispatcherError::Decoder(err));
return true;
}
Poll::Pending => return false,
Poll::Ready(None) => {
*this.state = State::Stopping;
return true;
}
};
let tx = this.tx.clone();
let fut = this.service.call(item);
actix_rt::spawn(async move {
let item = fut.await;
let _ = tx.send(item.map(Message::Item));
});
}
Poll::Pending => return false,
Poll::Ready(Err(err)) => {
*this.state = State::Error(DispatcherError::Service(err));
return true;
}
}
}
}
/// Write to framed object.
fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool
where
S: Service<<U as Decoder>::Item, Response = I>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Decoder + Encoder<I>,
I: 'static,
<U as Encoder<I>>::Error: fmt::Debug,
{
loop {
let mut this = self.as_mut().project();
while !this.framed.is_write_buf_full() {
match Pin::new(&mut this.rx).poll_next(cx) {
Poll::Ready(Some(Ok(Message::Item(msg)))) => {
if let Err(err) = this.framed.as_mut().write(msg) {
*this.state =
State::FramedError(DispatcherError::Encoder(err));
return true;
}
}
Poll::Ready(Some(Ok(Message::Close))) => {
*this.state = State::FlushAndStop;
return true;
}
Poll::Ready(Some(Err(err))) => {
*this.state = State::Error(DispatcherError::Service(err));
return true;
}
Poll::Ready(None) | Poll::Pending => break,
}
}
if !this.framed.is_write_buf_empty() {
match this.framed.flush(cx) {
Poll::Pending => break,
Poll::Ready(Ok(_)) => {}
Poll::Ready(Err(err)) => {
debug!("Error sending data: {:?}", err);
*this.state =
State::FramedError(DispatcherError::Encoder(err));
return true;
}
}
} else {
break;
}
}
false
}
}
impl<S, T, U, I> Future for Dispatcher<S, T, U, I>
where
S: Service<<U as Decoder>::Item, Response = I>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Decoder + Encoder<I>,
I: 'static,
<U as Encoder<I>>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
type Output = Result<(), DispatcherError<S::Error, U, I>>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
loop {
let this = self.as_mut().project();
return match this.state {
State::Processing => {
if self.as_mut().poll_read(cx) || self.as_mut().poll_write(cx) {
continue;
} else {
Poll::Pending
}
}
State::Error(_) => {
// flush write buffer
if !this.framed.is_write_buf_empty()
&& this.framed.flush(cx).is_pending()
{
return Poll::Pending;
}
Poll::Ready(Err(this.state.take_error()))
}
State::FlushAndStop => {
if !this.framed.is_write_buf_empty() {
this.framed.flush(cx).map(|res| {
if let Err(err) = res {
debug!("Error sending data: {:?}", err);
}
Ok(())
})
} else {
Poll::Ready(Ok(()))
}
}
State::FramedError(_) => {
Poll::Ready(Err(this.state.take_framed_error()))
}
State::Stopping => Poll::Ready(Ok(())),
};
}
}
}
}

View File

@ -3,11 +3,9 @@ use actix_http::{
}; };
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::ServiceFactoryExt; use actix_service::ServiceFactoryExt;
use actix_utils::future;
use bytes::Bytes; use bytes::Bytes;
use futures_util::{ use futures_util::StreamExt as _;
future::{self, ok},
StreamExt as _,
};
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \
@ -63,7 +61,7 @@ async fn test_h1_v2() {
async fn test_connection_close() { async fn test_connection_close() {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
.tcp() .tcp()
.map(|_| ()) .map(|_| ())
}) })
@ -79,9 +77,9 @@ async fn test_with_query_parameter() {
HttpService::build() HttpService::build()
.finish(|req: Request| { .finish(|req: Request| {
if req.uri().query().unwrap().contains("qp=") { if req.uri().query().unwrap().contains("qp=") {
ok::<_, ()>(Response::Ok().finish()) future::ok::<_, ()>(Response::Ok().finish())
} else { } else {
ok::<_, ()>(Response::BadRequest().finish()) future::ok::<_, ()>(Response::BadRequest().finish())
} }
}) })
.tcp() .tcp()

View File

@ -11,12 +11,10 @@ use actix_http::HttpMessage;
use actix_http::{body, Error, HttpService, Request, Response}; use actix_http::{body, Error, HttpService, Request, Response};
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::{fn_service, ServiceFactoryExt}; use actix_service::{fn_service, ServiceFactoryExt};
use actix_utils::future::{err, ok, ready};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures_core::Stream; use futures_core::Stream;
use futures_util::{ use futures_util::stream::{once, StreamExt as _};
future::{err, ok, ready},
stream::{once, StreamExt as _},
};
use openssl::{ use openssl::{
pkey::PKey, pkey::PKey,
ssl::{SslAcceptor, SslMethod}, ssl::{SslAcceptor, SslMethod},

View File

@ -8,10 +8,10 @@ use actix_http::http::{Method, StatusCode, Version};
use actix_http::{body, error, Error, HttpService, Request, Response}; use actix_http::{body, error, Error, HttpService, Request, Response};
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::{fn_factory_with_config, fn_service}; use actix_service::{fn_factory_with_config, fn_service};
use actix_utils::future::{err, ok};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures_core::Stream; use futures_core::Stream;
use futures_util::future::{self, err, ok};
use futures_util::stream::{once, StreamExt as _}; use futures_util::stream::{once, StreamExt as _};
use rustls::{ use rustls::{
internal::pemfile::{certs, pkcs8_private_keys}, internal::pemfile::{certs, pkcs8_private_keys},
@ -51,7 +51,7 @@ fn tls_config() -> RustlsServerConfig {
async fn test_h1() -> io::Result<()> { async fn test_h1() -> io::Result<()> {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.h1(|_| future::ok::<_, Error>(Response::Ok().finish())) .h1(|_| ok::<_, Error>(Response::Ok().finish()))
.rustls(tls_config()) .rustls(tls_config())
}) })
.await; .await;
@ -65,7 +65,7 @@ async fn test_h1() -> io::Result<()> {
async fn test_h2() -> io::Result<()> { async fn test_h2() -> io::Result<()> {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| future::ok::<_, Error>(Response::Ok().finish())) .h2(|_| ok::<_, Error>(Response::Ok().finish()))
.rustls(tls_config()) .rustls(tls_config())
}) })
.await; .await;
@ -82,7 +82,7 @@ async fn test_h1_1() -> io::Result<()> {
.h1(|req: Request| { .h1(|req: Request| {
assert!(req.peer_addr().is_some()); assert!(req.peer_addr().is_some());
assert_eq!(req.version(), Version::HTTP_11); assert_eq!(req.version(), Version::HTTP_11);
future::ok::<_, Error>(Response::Ok().finish()) ok::<_, Error>(Response::Ok().finish())
}) })
.rustls(tls_config()) .rustls(tls_config())
}) })
@ -100,7 +100,7 @@ async fn test_h2_1() -> io::Result<()> {
.finish(|req: Request| { .finish(|req: Request| {
assert!(req.peer_addr().is_some()); assert!(req.peer_addr().is_some());
assert_eq!(req.version(), Version::HTTP_2); assert_eq!(req.version(), Version::HTTP_2);
future::ok::<_, Error>(Response::Ok().finish()) ok::<_, Error>(Response::Ok().finish())
}) })
.rustls(tls_config()) .rustls(tls_config())
}) })
@ -144,7 +144,7 @@ async fn test_h2_content_length() {
StatusCode::OK, StatusCode::OK,
StatusCode::NOT_FOUND, StatusCode::NOT_FOUND,
]; ];
future::ok::<_, ()>(Response::new(statuses[indx])) ok::<_, ()>(Response::new(statuses[indx]))
}) })
.rustls(tls_config()) .rustls(tls_config())
}) })
@ -213,7 +213,7 @@ async fn test_h2_headers() {
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
)); ));
} }
future::ok::<_, ()>(config.body(data.clone())) ok::<_, ()>(config.body(data.clone()))
}) })
.rustls(tls_config()) .rustls(tls_config())
}).await; }).await;
@ -252,7 +252,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
async fn test_h2_body2() { async fn test_h2_body2() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .h2(|_| ok::<_, ()>(Response::Ok().body(STR)))
.rustls(tls_config()) .rustls(tls_config())
}) })
.await; .await;

View File

@ -5,9 +5,10 @@ use std::{net, thread};
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_rt::time::sleep; use actix_rt::time::sleep;
use actix_service::fn_service; use actix_service::fn_service;
use actix_utils::future::{err, ok, ready};
use bytes::Bytes; use bytes::Bytes;
use futures_util::future::{self, err, ok, ready, FutureExt};
use futures_util::stream::{once, StreamExt as _}; use futures_util::stream::{once, StreamExt as _};
use futures_util::FutureExt as _;
use regex::Regex; use regex::Regex;
use actix_http::HttpMessage; use actix_http::HttpMessage;
@ -24,7 +25,7 @@ async fn test_h1() {
.client_disconnect(1000) .client_disconnect(1000)
.h1(|req: Request| { .h1(|req: Request| {
assert!(req.peer_addr().is_some()); assert!(req.peer_addr().is_some());
future::ok::<_, ()>(Response::Ok().finish()) ok::<_, ()>(Response::Ok().finish())
}) })
.tcp() .tcp()
}) })
@ -44,7 +45,7 @@ async fn test_h1_2() {
.finish(|req: Request| { .finish(|req: Request| {
assert!(req.peer_addr().is_some()); assert!(req.peer_addr().is_some());
assert_eq!(req.version(), http::Version::HTTP_11); assert_eq!(req.version(), http::Version::HTTP_11);
future::ok::<_, ()>(Response::Ok().finish()) ok::<_, ()>(Response::Ok().finish())
}) })
.tcp() .tcp()
}) })
@ -65,7 +66,7 @@ async fn test_expect_continue() {
err(error::ErrorPreconditionFailed("error")) err(error::ErrorPreconditionFailed("error"))
} }
})) }))
.finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .finish(|_| ok::<_, ()>(Response::Ok().finish()))
.tcp() .tcp()
}) })
.await; .await;
@ -96,7 +97,7 @@ async fn test_expect_continue_h1() {
} }
}) })
})) }))
.h1(fn_service(|_| future::ok::<_, ()>(Response::Ok().finish()))) .h1(fn_service(|_| ok::<_, ()>(Response::Ok().finish())))
.tcp() .tcp()
}) })
.await; .await;
@ -175,7 +176,7 @@ async fn test_slow_request() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.client_timeout(100) .client_timeout(100)
.finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .finish(|_| ok::<_, ()>(Response::Ok().finish()))
.tcp() .tcp()
}) })
.await; .await;
@ -191,7 +192,7 @@ async fn test_slow_request() {
async fn test_http1_malformed_request() { async fn test_http1_malformed_request() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| future::ok::<_, ()>(Response::Ok().finish())) .h1(|_| ok::<_, ()>(Response::Ok().finish()))
.tcp() .tcp()
}) })
.await; .await;
@ -207,7 +208,7 @@ async fn test_http1_malformed_request() {
async fn test_http1_keepalive() { async fn test_http1_keepalive() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| future::ok::<_, ()>(Response::Ok().finish())) .h1(|_| ok::<_, ()>(Response::Ok().finish()))
.tcp() .tcp()
}) })
.await; .await;
@ -229,7 +230,7 @@ async fn test_http1_keepalive_timeout() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.keep_alive(1) .keep_alive(1)
.h1(|_| future::ok::<_, ()>(Response::Ok().finish())) .h1(|_| ok::<_, ()>(Response::Ok().finish()))
.tcp() .tcp()
}) })
.await; .await;
@ -250,7 +251,7 @@ async fn test_http1_keepalive_timeout() {
async fn test_http1_keepalive_close() { async fn test_http1_keepalive_close() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| future::ok::<_, ()>(Response::Ok().finish())) .h1(|_| ok::<_, ()>(Response::Ok().finish()))
.tcp() .tcp()
}) })
.await; .await;
@ -271,7 +272,7 @@ async fn test_http1_keepalive_close() {
async fn test_http10_keepalive_default_close() { async fn test_http10_keepalive_default_close() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| future::ok::<_, ()>(Response::Ok().finish())) .h1(|_| ok::<_, ()>(Response::Ok().finish()))
.tcp() .tcp()
}) })
.await; .await;
@ -291,7 +292,7 @@ async fn test_http10_keepalive_default_close() {
async fn test_http10_keepalive() { async fn test_http10_keepalive() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| future::ok::<_, ()>(Response::Ok().finish())) .h1(|_| ok::<_, ()>(Response::Ok().finish()))
.tcp() .tcp()
}) })
.await; .await;
@ -319,7 +320,7 @@ async fn test_http1_keepalive_disabled() {
let srv = test_server(|| { let srv = test_server(|| {
HttpService::build() HttpService::build()
.keep_alive(KeepAlive::Disabled) .keep_alive(KeepAlive::Disabled)
.h1(|_| future::ok::<_, ()>(Response::Ok().finish())) .h1(|_| ok::<_, ()>(Response::Ok().finish()))
.tcp() .tcp()
}) })
.await; .await;
@ -354,7 +355,7 @@ async fn test_content_length() {
StatusCode::OK, StatusCode::OK,
StatusCode::NOT_FOUND, StatusCode::NOT_FOUND,
]; ];
future::ok::<_, ()>(Response::new(statuses[indx])) ok::<_, ()>(Response::new(statuses[indx]))
}) })
.tcp() .tcp()
}) })
@ -409,7 +410,7 @@ async fn test_h1_headers() {
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
)); ));
} }
future::ok::<_, ()>(builder.body(data.clone())) ok::<_, ()>(builder.body(data.clone()))
}).tcp() }).tcp()
}).await; }).await;
@ -645,7 +646,7 @@ async fn test_h1_response_http_error_handling() {
async fn test_h1_service_error() { async fn test_h1_service_error() {
let mut srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| future::err::<Response, Error>(error::ErrorBadRequest("error"))) .h1(|_| err::<Response, _>(error::ErrorBadRequest("error")))
.tcp() .tcp()
}) })
.await; .await;
@ -667,7 +668,7 @@ async fn test_h1_on_connect() {
}) })
.h1(|req: Request| { .h1(|req: Request| {
assert!(req.extensions().contains::<isize>()); assert!(req.extensions().contains::<isize>());
future::ok::<_, ()>(Response::Ok().finish()) ok::<_, ()>(Response::Ok().finish())
}) })
.tcp() .tcp()
}) })

View File

@ -3,17 +3,18 @@ use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::{body, h1, ws, Error, HttpService, Request, Response}; use actix_http::{body, h1, ws, Error, HttpService, Request, Response};
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::{fn_factory, Service}; use actix_service::{fn_factory, Service};
use actix_utils::dispatcher::Dispatcher; use actix_utils::future;
use bytes::Bytes; use bytes::Bytes;
use futures_util::future;
use futures_util::task::{Context, Poll};
use futures_util::{SinkExt as _, StreamExt as _}; use futures_util::{SinkExt as _, StreamExt as _};
use crate::ws::Dispatcher;
struct WsService<T>(Arc<Mutex<(PhantomData<T>, Cell<bool>)>>); struct WsService<T>(Arc<Mutex<(PhantomData<T>, Cell<bool>)>>);
impl<T> WsService<T> { impl<T> WsService<T> {
@ -58,7 +59,7 @@ where
.await .await
.unwrap(); .unwrap();
Dispatcher::new(framed.replace_codec(ws::Codec::new()), service) Dispatcher::with(framed.replace_codec(ws::Codec::new()), service)
.await .await
.map_err(|_| panic!()) .map_err(|_| panic!())
}; };

View File

@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 0.4.0-beta.4 - 2021-04-02
* No notable changes.
## 0.4.0-beta.3 - 2021-03-09 ## 0.4.0-beta.3 - 2021-03-09
* No notable changes. * No notable changes.

View File

@ -1,13 +1,13 @@
[package] [package]
name = "actix-multipart" name = "actix-multipart"
version = "0.4.0-beta.3" version = "0.4.0-beta.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Multipart form support for Actix Web" description = "Multipart form support for Actix Web"
readme = "README.md" readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-multipart/" documentation = "https://docs.rs/actix-multipart"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
@ -16,19 +16,21 @@ name = "actix_multipart"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = { version = "4.0.0-beta.4", default-features = false } actix-web = { version = "4.0.0-beta.5", default-features = false }
actix-utils = "3.0.0-beta.2" actix-utils = "3.0.0-beta.4"
bytes = "1" bytes = "1"
derive_more = "0.99.5" derive_more = "0.99.5"
httparse = "1.3" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
httparse = "1.3"
local-waker = "0.1"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
twoway = "0.2" twoway = "0.2"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.1" actix-rt = "2.2"
actix-http = "3.0.0-beta.4" actix-http = "3.0.0-beta.5"
tokio = { version = "1", features = ["sync"] } tokio = { version = "1", features = ["sync"] }
tokio-stream = "0.1" tokio-stream = "0.1"

View File

@ -3,11 +3,11 @@
> Multipart form support for Actix Web. > Multipart form support for Actix Web.
[![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart)
[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.3)](https://docs.rs/actix-multipart/0.4.0-beta.3) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.4)](https://docs.rs/actix-multipart/0.4.0-beta.4)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![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) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.3/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.3) [![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.4/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.4)
[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart)
## Documentation & Resources ## Documentation & Resources

View File

@ -1,21 +1,22 @@
//! Multipart payload support //! Multipart payload support
use actix_utils::future::{ready, Ready};
use actix_web::{dev::Payload, Error, FromRequest, HttpRequest}; use actix_web::{dev::Payload, Error, FromRequest, HttpRequest};
use futures_util::future::{ok, Ready};
use crate::server::Multipart; use crate::server::Multipart;
/// Get request's payload as multipart stream /// Get request's payload as multipart stream.
/// ///
/// Content-type: multipart/form-data; /// Content-type: multipart/form-data;
/// ///
/// ## Server example /// ## Server example
/// ///
/// ``` /// ```
/// use futures_util::stream::{Stream, StreamExt};
/// use actix_web::{web, HttpResponse, Error}; /// use actix_web::{web, HttpResponse, Error};
/// use actix_multipart as mp; /// use actix_multipart::Multipart;
/// use futures_util::stream::StreamExt as _;
/// ///
/// async fn index(mut payload: mp::Multipart) -> Result<HttpResponse, Error> { /// async fn index(mut payload: Multipart) -> Result<HttpResponse, Error> {
/// // iterate over multipart stream /// // iterate over multipart stream
/// while let Some(item) = payload.next().await { /// while let Some(item) = payload.next().await {
/// let mut field = item?; /// let mut field = item?;
@ -25,9 +26,9 @@ use crate::server::Multipart;
/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk?)); /// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk?));
/// } /// }
/// } /// }
///
/// Ok(HttpResponse::Ok().into()) /// Ok(HttpResponse::Ok().into())
/// } /// }
/// # fn main() {}
/// ``` /// ```
impl FromRequest for Multipart { impl FromRequest for Multipart {
type Error = Error; type Error = Error;
@ -36,9 +37,9 @@ impl FromRequest for Multipart {
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
ok(match Multipart::boundary(req.headers()) { ready(Ok(match Multipart::boundary(req.headers()) {
Ok(boundary) => Multipart::from_boundary(boundary, payload.take()), Ok(boundary) => Multipart::from_boundary(boundary, payload.take()),
Err(err) => Multipart::from_error(err), Err(err) => Multipart::from_error(err),
}) }))
} }
} }

View File

@ -1,4 +1,4 @@
//! Multipart payload support //! Multipart response payload support.
use std::cell::{Cell, RefCell, RefMut}; use std::cell::{Cell, RefCell, RefMut};
use std::convert::TryFrom; use std::convert::TryFrom;
@ -8,12 +8,12 @@ use std::rc::Rc;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::{cmp, fmt}; use std::{cmp, fmt};
use bytes::{Bytes, BytesMut};
use futures_util::stream::{LocalBoxStream, Stream, StreamExt};
use actix_utils::task::LocalWaker;
use actix_web::error::{ParseError, PayloadError}; 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 bytes::{Bytes, BytesMut};
use futures_core::stream::{LocalBoxStream, Stream};
use futures_util::stream::StreamExt as _;
use local_waker::LocalWaker;
use crate::error::MultipartError; use crate::error::MultipartError;

9
actix-test/CHANGES.md Normal file
View File

@ -0,0 +1,9 @@
# Changes
## Unreleased - 2021-xx-xx
## 0.1.0-beta.1 - 2021-04-02
* Move integration testing structs from `actix-web`. [#2112]
[#2112]: https://github.com/actix/actix-web/pull/2112

38
actix-test/Cargo.toml Normal file
View File

@ -0,0 +1,38 @@
[package]
name = "actix-test"
version = "0.1.0-beta.1"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
]
edition = "2018"
description = "Integration testing tools for Actix Web applications"
license = "MIT OR Apache-2.0"
[features]
default = []
# rustls
rustls = ["tls-rustls", "actix-http/rustls"]
# openssl
openssl = ["tls-openssl", "actix-http/openssl"]
[dependencies]
actix-codec = "0.4.0-beta.1"
actix-http = { version = "3.0.0-beta.5", features = ["cookies"] }
actix-http-test = { version = "3.0.0-beta.4", features = [] }
actix-service = "2.0.0-beta.4"
actix-utils = "3.0.0-beta.2"
actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] }
actix-rt = "2.1"
awc = { version = "3.0.0-beta.4", default-features = false, features = ["cookies"] }
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
futures-util = { version = "0.3.7", default-features = false, features = [] }
log = "0.4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_urlencoded = "0.7"
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
tls-rustls = { package = "rustls", version = "0.19.0", optional = true }

1
actix-test/LICENSE-APACHE Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-APACHE

1
actix-test/LICENSE-MIT Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-MIT

472
actix-test/src/lib.rs Normal file
View File

@ -0,0 +1,472 @@
//! Integration testing tools for Actix Web applications.
//!
//! The main integration testing tool is [`TestServer`]. It spawns a real HTTP server on an
//! unused port and provides methods that use a real HTTP client. Therefore, it is much closer to
//! real-world cases than using `init_service`, which skips HTTP encoding and decoding.
//!
//! # Examples
//! ```
//! use actix_web::{get, web, test, App, HttpResponse, Error, Responder};
//!
//! #[get("/")]
//! async fn my_handler() -> Result<impl Responder, Error> {
//! Ok(HttpResponse::Ok())
//! }
//!
//! #[actix_rt::test]
//! async fn test_example() {
//! let srv = actix_test::start(||
//! App::new().service(my_handler)
//! );
//!
//! let req = srv.get("/");
//! let res = req.send().await.unwrap();
//!
//! assert!(res.status().is_success());
//! }
//! ```
#[cfg(feature = "openssl")]
extern crate tls_openssl as openssl;
#[cfg(feature = "rustls")]
extern crate tls_rustls as rustls;
use std::{fmt, net, sync::mpsc, thread, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
pub use actix_http::test::TestBuffer;
use actix_http::{
http::{HeaderMap, Method},
ws, HttpService, Request,
};
use actix_service::{map_config, IntoServiceFactory, ServiceFactory};
use actix_web::{
dev::{AppConfig, MessageBody, Server, Service},
rt, web, Error, HttpResponse,
};
use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
use futures_core::Stream;
pub use actix_http_test::unused_addr;
pub use actix_web::test::{
call_service, default_service, init_service, load_stream, ok_service, read_body,
read_body_json, read_response, read_response_json, TestRequest,
};
/// Start default [`TestServer`].
///
/// # Examples
/// ```
/// use actix_web::{get, web, test, App, HttpResponse, Error, Responder};
///
/// #[get("/")]
/// async fn my_handler() -> Result<impl Responder, Error> {
/// Ok(HttpResponse::Ok())
/// }
///
/// #[actix_rt::test]
/// async fn test_example() {
/// let srv = actix_test::start(||
/// App::new().service(my_handler)
/// );
///
/// let req = srv.get("/");
/// let res = req.send().await.unwrap();
///
/// assert!(res.status().is_success());
/// }
/// ```
pub fn start<F, I, S, B>(factory: F) -> TestServer
where
F: Fn() -> I + Send + Clone + 'static,
I: IntoServiceFactory<S, Request>,
S: ServiceFactory<Request, Config = AppConfig> + 'static,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<HttpResponse<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
start_with(TestServerConfig::default(), factory)
}
/// Start test server with custom configuration
///
/// Check [`TestServerConfig`] docs for configuration options.
///
/// # Examples
/// ```
/// use actix_web::{get, web, test, App, HttpResponse, Error, Responder};
///
/// #[get("/")]
/// async fn my_handler() -> Result<impl Responder, Error> {
/// Ok(HttpResponse::Ok())
/// }
///
/// #[actix_rt::test]
/// async fn test_example() {
/// let srv = actix_test::start_with(actix_test::config().h1(), ||
/// App::new().service(my_handler)
/// );
///
/// let req = srv.get("/");
/// let res = req.send().await.unwrap();
///
/// assert!(res.status().is_success());
/// }
/// ```
pub fn start_with<F, I, S, B>(cfg: TestServerConfig, factory: F) -> TestServer
where
F: Fn() -> I + Send + Clone + 'static,
I: IntoServiceFactory<S, Request>,
S: ServiceFactory<Request, Config = AppConfig> + 'static,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<HttpResponse<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
let (tx, rx) = mpsc::channel();
let tls = match cfg.stream {
StreamType::Tcp => false,
#[cfg(feature = "openssl")]
StreamType::Openssl(_) => true,
#[cfg(feature = "rustls")]
StreamType::Rustls(_) => true,
};
// run server in separate thread
thread::spawn(move || {
let sys = rt::System::new();
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
let local_addr = tcp.local_addr().unwrap();
let factory = factory.clone();
let srv_cfg = cfg.clone();
let timeout = cfg.client_timeout;
let builder = Server::build().workers(1).disable_signals();
let srv = match srv_cfg.stream {
StreamType::Tcp => match srv_cfg.tp {
HttpVer::Http1 => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
HttpService::build()
.client_timeout(timeout)
.h1(map_config(factory(), move |_| app_cfg.clone()))
.tcp()
}),
HttpVer::Http2 => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
HttpService::build()
.client_timeout(timeout)
.h2(map_config(factory(), move |_| app_cfg.clone()))
.tcp()
}),
HttpVer::Both => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
HttpService::build()
.client_timeout(timeout)
.finish(map_config(factory(), move |_| app_cfg.clone()))
.tcp()
}),
},
#[cfg(feature = "openssl")]
StreamType::Openssl(acceptor) => match cfg.tp {
HttpVer::Http1 => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
HttpService::build()
.client_timeout(timeout)
.h1(map_config(factory(), move |_| app_cfg.clone()))
.openssl(acceptor.clone())
}),
HttpVer::Http2 => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
HttpService::build()
.client_timeout(timeout)
.h2(map_config(factory(), move |_| app_cfg.clone()))
.openssl(acceptor.clone())
}),
HttpVer::Both => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
HttpService::build()
.client_timeout(timeout)
.finish(map_config(factory(), move |_| app_cfg.clone()))
.openssl(acceptor.clone())
}),
},
#[cfg(feature = "rustls")]
StreamType::Rustls(config) => match cfg.tp {
HttpVer::Http1 => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
HttpService::build()
.client_timeout(timeout)
.h1(map_config(factory(), move |_| app_cfg.clone()))
.rustls(config.clone())
}),
HttpVer::Http2 => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
HttpService::build()
.client_timeout(timeout)
.h2(map_config(factory(), move |_| app_cfg.clone()))
.rustls(config.clone())
}),
HttpVer::Both => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
HttpService::build()
.client_timeout(timeout)
.finish(map_config(factory(), move |_| app_cfg.clone()))
.rustls(config.clone())
}),
},
}
.unwrap();
sys.block_on(async {
let srv = srv.run();
tx.send((rt::System::current(), srv, local_addr)).unwrap();
});
sys.run()
});
let (system, server, addr) = rx.recv().unwrap();
let client = {
let connector = {
#[cfg(feature = "openssl")]
{
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_verify(SslVerifyMode::NONE);
let _ = builder
.set_alpn_protos(b"\x02h2\x08http/1.1")
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
Connector::new()
.conn_lifetime(time::Duration::from_secs(0))
.timeout(time::Duration::from_millis(30000))
.ssl(builder.build())
}
#[cfg(not(feature = "openssl"))]
{
Connector::new()
.conn_lifetime(time::Duration::from_secs(0))
.timeout(time::Duration::from_millis(30000))
}
};
Client::builder().connector(connector).finish()
};
TestServer {
addr,
client,
system,
tls,
server,
}
}
#[derive(Debug, Clone)]
enum HttpVer {
Http1,
Http2,
Both,
}
#[derive(Clone)]
enum StreamType {
Tcp,
#[cfg(feature = "openssl")]
Openssl(openssl::ssl::SslAcceptor),
#[cfg(feature = "rustls")]
Rustls(rustls::ServerConfig),
}
/// Create default test server config.
pub fn config() -> TestServerConfig {
TestServerConfig::default()
}
#[derive(Clone)]
pub struct TestServerConfig {
tp: HttpVer,
stream: StreamType,
client_timeout: u64,
}
impl Default for TestServerConfig {
fn default() -> Self {
TestServerConfig::new()
}
}
impl TestServerConfig {
/// Create default server configuration
pub(crate) fn new() -> TestServerConfig {
TestServerConfig {
tp: HttpVer::Both,
stream: StreamType::Tcp,
client_timeout: 5000,
}
}
/// Accept HTTP/1.1 only.
pub fn h1(mut self) -> Self {
self.tp = HttpVer::Http1;
self
}
/// Accept HTTP/2 only.
pub fn h2(mut self) -> Self {
self.tp = HttpVer::Http2;
self
}
/// Accept secure connections via OpenSSL.
#[cfg(feature = "openssl")]
pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self {
self.stream = StreamType::Openssl(acceptor);
self
}
/// Accept secure connections via Rustls.
#[cfg(feature = "rustls")]
pub fn rustls(mut self, config: rustls::ServerConfig) -> Self {
self.stream = StreamType::Rustls(config);
self
}
/// Set client timeout in milliseconds for first request.
pub fn client_timeout(mut self, val: u64) -> Self {
self.client_timeout = val;
self
}
}
/// A basic HTTP server controller that simplifies the process of writing integration tests for
/// Actix Web applications.
///
/// See [`start`] for usage example.
pub struct TestServer {
addr: net::SocketAddr,
client: awc::Client,
system: rt::System,
tls: bool,
server: Server,
}
impl TestServer {
/// Construct test server url
pub fn addr(&self) -> net::SocketAddr {
self.addr
}
/// Construct test server url
pub fn url(&self, uri: &str) -> String {
let scheme = if self.tls { "https" } else { "http" };
if uri.starts_with('/') {
format!("{}://localhost:{}{}", scheme, self.addr.port(), uri)
} else {
format!("{}://localhost:{}/{}", scheme, self.addr.port(), uri)
}
}
/// Create `GET` request.
pub fn get(&self, path: impl AsRef<str>) -> ClientRequest {
self.client.get(self.url(path.as_ref()).as_str())
}
/// Create `POST` request.
pub fn post(&self, path: impl AsRef<str>) -> ClientRequest {
self.client.post(self.url(path.as_ref()).as_str())
}
/// Create `HEAD` request.
pub fn head(&self, path: impl AsRef<str>) -> ClientRequest {
self.client.head(self.url(path.as_ref()).as_str())
}
/// Create `PUT` request.
pub fn put(&self, path: impl AsRef<str>) -> ClientRequest {
self.client.put(self.url(path.as_ref()).as_str())
}
/// Create `PATCH` request.
pub fn patch(&self, path: impl AsRef<str>) -> ClientRequest {
self.client.patch(self.url(path.as_ref()).as_str())
}
/// Create `DELETE` request.
pub fn delete(&self, path: impl AsRef<str>) -> ClientRequest {
self.client.delete(self.url(path.as_ref()).as_str())
}
/// Create `OPTIONS` request.
pub fn options(&self, path: impl AsRef<str>) -> ClientRequest {
self.client.options(self.url(path.as_ref()).as_str())
}
/// Connect request with given method and path.
pub fn request(&self, method: Method, path: impl AsRef<str>) -> ClientRequest {
self.client.request(method, path.as_ref())
}
pub async fn load_body<S>(
&mut self,
mut response: ClientResponse<S>,
) -> Result<web::Bytes, PayloadError>
where
S: Stream<Item = Result<web::Bytes, PayloadError>> + Unpin + 'static,
{
response.body().limit(10_485_760).await
}
/// 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> {
let url = self.url(path);
let connect = self.client.ws(url).connect();
connect.await.map(|(_, framed)| framed)
}
/// Connect to a WebSocket server.
pub async fn ws(
&mut self,
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError> {
self.ws_at("/").await
}
/// Get default HeaderMap of Client.
///
/// Returns Some(&mut HeaderMap) when Client object is unique
/// (No other clone of client exists at the same time).
pub fn client_headers(&mut self) -> Option<&mut HeaderMap> {
self.client.headers()
}
/// Gracefully stop HTTP server.
pub async fn stop(self) {
self.server.stop(true).await;
self.system.stop();
rt::time::sleep(time::Duration::from_millis(100)).await;
}
}
impl Drop for TestServer {
fn drop(&mut self) {
self.system.stop()
}
}

View File

@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 4.0.0-beta.4 - 2021-04-02
* No notable changes.
## 4.0.0-beta.3 - 2021-03-09 ## 4.0.0-beta.3 - 2021-03-09
* No notable changes. * No notable changes.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-actors" name = "actix-web-actors"
version = "4.0.0-beta.3" version = "4.0.0-beta.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for Actix Web" description = "Actix actors support for Actix Web"
readme = "README.md" readme = "README.md"
@ -18,8 +18,8 @@ path = "src/lib.rs"
[dependencies] [dependencies]
actix = { version = "0.11.0-beta.3", default-features = false } actix = { version = "0.11.0-beta.3", default-features = false }
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-http = "3.0.0-beta.4" actix-http = "3.0.0-beta.5"
actix-web = { version = "4.0.0-beta.4", default-features = false } actix-web = { version = "4.0.0-beta.5", default-features = false }
bytes = "1" bytes = "1"
bytestring = "1" bytestring = "1"
@ -28,6 +28,9 @@ pin-project = "1.0.0"
tokio = { version = "1", features = ["sync"] } tokio = { version = "1", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
actix-rt = "2.1" actix-rt = "2.2"
actix-test = "0.1.0-beta.1"
awc = { version = "3.0.0-beta.4", default-features = false }
env_logger = "0.8" env_logger = "0.8"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }

View File

@ -3,11 +3,11 @@
> Actix actors support for Actix Web. > Actix actors support for Actix Web.
[![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.3)](https://docs.rs/actix-web-actors/4.0.0-beta.3) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.4)](https://docs.rs/actix-web-actors/4.0.0-beta.4)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
![License](https://img.shields.io/crates/l/actix-web-actors.svg) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.3/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.3) [![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.4)
[![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors)
[![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) [![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)

View File

@ -1,8 +1,11 @@
use actix::prelude::*; use actix::prelude::*;
use actix_web::{test, web, App, HttpRequest}; use actix_web::{
http::{header, StatusCode},
web, App, HttpRequest, HttpResponse,
};
use actix_web_actors::*; use actix_web_actors::*;
use bytes::Bytes; use bytes::Bytes;
use futures_util::{SinkExt, StreamExt}; use futures_util::{SinkExt as _, StreamExt as _};
struct Ws; struct Ws;
@ -24,7 +27,7 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Ws {
#[actix_rt::test] #[actix_rt::test]
async fn test_simple() { async fn test_simple() {
let mut srv = test::start(|| { let mut srv = actix_test::start(|| {
App::new().service(web::resource("/").to( 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) },
)) ))
@ -56,3 +59,51 @@ async fn test_simple() {
let item = framed.next().await.unwrap().unwrap(); let item = framed.next().await.unwrap().unwrap();
assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into()))); assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into())));
} }
#[actix_rt::test]
async fn test_with_credentials() {
let mut srv = actix_test::start(|| {
App::new().service(web::resource("/").to(
|req: HttpRequest, stream: web::Payload| async move {
if req.headers().contains_key("Authorization") {
ws::start(Ws, &req, stream)
} else {
Ok(HttpResponse::new(StatusCode::UNAUTHORIZED))
}
},
))
});
// client service without credentials
match srv.ws().await {
Ok(_) => panic!("WebSocket client without credentials should panic"),
Err(awc::error::WsClientError::InvalidResponseStatus(status)) => {
assert_eq!(status, StatusCode::UNAUTHORIZED)
}
Err(e) => panic!("Invalid error from WebSocket client: {}", e),
}
let headers = srv.client_headers().unwrap();
headers.insert(
header::AUTHORIZATION,
header::HeaderValue::from_static("Bearer Something"),
);
// client service with credentials
let client = srv.ws();
let mut framed = client.await.unwrap();
framed.send(ws::Message::Text("text".into())).await.unwrap();
let item = framed.next().await.unwrap().unwrap();
assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text")));
framed
.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
.await
.unwrap();
let item = framed.next().await.unwrap().unwrap();
assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into())));
}

View File

@ -19,8 +19,11 @@ syn = { version = "1", features = ["full", "parsing"] }
proc-macro2 = "1" proc-macro2 = "1"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.1" actix-rt = "2.2"
actix-web = "4.0.0-beta.4" actix-test = "0.1.0-beta.1"
futures-util = { version = "0.3.7", default-features = false } actix-utils = "3.0.0-beta.4"
actix-web = "4.0.0-beta.5"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
trybuild = "1" trybuild = "1"
rustversion = "1" rustversion = "1"

View File

@ -1,11 +1,12 @@
use std::future::Future; use std::future::Future;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_utils::future;
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::http::header::{HeaderName, HeaderValue}; use actix_web::http::header::{HeaderName, HeaderValue};
use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder}; use actix_web::{http, 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}; use futures_core::future::LocalBoxFuture;
// Make sure that we can name function as 'config' // Make sure that we can name function as 'config'
#[get("/config")] #[get("/config")]
@ -148,7 +149,7 @@ async fn get_wrap(_: Path<String>) -> impl Responder {
#[actix_rt::test] #[actix_rt::test]
async fn test_params() { async fn test_params() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new() App::new()
.service(get_param_test) .service(get_param_test)
.service(put_param_test) .service(put_param_test)
@ -170,7 +171,7 @@ async fn test_params() {
#[actix_rt::test] #[actix_rt::test]
async fn test_body() { async fn test_body() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new() App::new()
.service(post_test) .service(post_test)
.service(put_test) .service(put_test)
@ -244,7 +245,7 @@ async fn test_body() {
#[actix_rt::test] #[actix_rt::test]
async fn test_auto_async() { async fn test_auto_async() {
let srv = test::start(|| App::new().service(auto_async)); let srv = actix_test::start(|| App::new().service(auto_async));
let request = srv.request(http::Method::GET, srv.url("/test")); let request = srv.request(http::Method::GET, srv.url("/test"));
let response = request.send().await.unwrap(); let response = request.send().await.unwrap();
@ -253,7 +254,7 @@ async fn test_auto_async() {
#[actix_rt::test] #[actix_rt::test]
async fn test_wrap() { async fn test_wrap() {
let srv = test::start(|| App::new().service(get_wrap)); let srv = actix_test::start(|| App::new().service(get_wrap));
let request = srv.request(http::Method::GET, srv.url("/test/wrap")); let request = srv.request(http::Method::GET, srv.url("/test/wrap"));
let response = request.send().await.unwrap(); let response = request.send().await.unwrap();

View File

@ -1,7 +1,7 @@
use actix_web::{Responder, HttpResponse, App, test}; use actix_web::{Responder, HttpResponse, App};
use actix_web_codegen::*; use actix_web_codegen::*;
/// Docstrings shouldn't break anything. /// doc comments shouldn't break anything
#[get("/")] #[get("/")]
async fn index() -> impl Responder { async fn index() -> impl Responder {
HttpResponse::Ok() HttpResponse::Ok()
@ -9,7 +9,7 @@ async fn index() -> impl Responder {
#[actix_web::main] #[actix_web::main]
async fn main() { async fn main() {
let srv = test::start(|| App::new().service(index)); let srv = actix_test::start(|| App::new().service(index));
let request = srv.get("/"); let request = srv.get("/");
let response = request.send().await.unwrap(); let response = request.send().await.unwrap();

View File

@ -7,9 +7,9 @@ async fn index() -> String {
#[actix_web::main] #[actix_web::main]
async fn main() { async fn main() {
use actix_web::{App, test}; use actix_web::App;
let srv = test::start(|| App::new().service(index)); let srv = actix_test::start(|| App::new().service(index));
let request = srv.get("/"); let request = srv.get("/");
let response = request.send().await.unwrap(); let response = request.send().await.unwrap();

View File

@ -5,7 +5,7 @@ error: HTTP method defined more than once: `GET`
| ^^^^^ | ^^^^^
error[E0425]: cannot find value `index` in this scope error[E0425]: cannot find value `index` in this scope
--> $DIR/route-duplicate-method-fail.rs:12:49 --> $DIR/route-duplicate-method-fail.rs:12:55
| |
12 | let srv = test::start(|| App::new().service(index)); 12 | let srv = actix_test::start(|| App::new().service(index));
| ^^^^^ not found in this scope | ^^^^^ not found in this scope

View File

@ -7,9 +7,9 @@ async fn index() -> String {
#[actix_web::main] #[actix_web::main]
async fn main() { async fn main() {
use actix_web::{App, test}; use actix_web::App;
let srv = test::start(|| App::new().service(index)); let srv = actix_test::start(|| App::new().service(index));
let request = srv.get("/"); let request = srv.get("/");
let response = request.send().await.unwrap(); let response = request.send().await.unwrap();

View File

@ -7,7 +7,7 @@ error: The #[route(..)] macro requires at least one `method` attribute
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0425]: cannot find value `index` in this scope error[E0425]: cannot find value `index` in this scope
--> $DIR/route-missing-method-fail.rs:12:49 --> $DIR/route-missing-method-fail.rs:12:55
| |
12 | let srv = test::start(|| App::new().service(index)); 12 | let srv = actix_test::start(|| App::new().service(index));
| ^^^^^ not found in this scope | ^^^^^ not found in this scope

View File

@ -7,9 +7,9 @@ async fn index() -> String {
#[actix_web::main] #[actix_web::main]
async fn main() { async fn main() {
use actix_web::{App, test}; use actix_web::App;
let srv = test::start(|| App::new().service(index)); let srv = actix_test::start(|| App::new().service(index));
let request = srv.get("/"); let request = srv.get("/");
let response = request.send().await.unwrap(); let response = request.send().await.unwrap();

View File

@ -7,9 +7,9 @@ async fn index() -> String {
#[actix_web::main] #[actix_web::main]
async fn main() { async fn main() {
use actix_web::{App, test}; use actix_web::App;
let srv = test::start(|| App::new().service(index)); let srv = actix_test::start(|| App::new().service(index));
let request = srv.get("/"); let request = srv.get("/");
let response = request.send().await.unwrap(); let response = request.send().await.unwrap();

View File

@ -5,7 +5,7 @@ error: Unexpected HTTP method: `UNEXPECTED`
| ^^^^^^^^^^^^ | ^^^^^^^^^^^^
error[E0425]: cannot find value `index` in this scope error[E0425]: cannot find value `index` in this scope
--> $DIR/route-unexpected-method-fail.rs:12:49 --> $DIR/route-unexpected-method-fail.rs:12:55
| |
12 | let srv = test::start(|| App::new().service(index)); 12 | let srv = actix_test::start(|| App::new().service(index));
| ^^^^^ not found in this scope | ^^^^^ not found in this scope

View File

@ -1,4 +1,4 @@
use actix_web::{Responder, HttpResponse, App, test}; use actix_web::{Responder, HttpResponse, App};
use actix_web_codegen::*; use actix_web_codegen::*;
#[get("/config")] #[get("/config")]
@ -8,7 +8,7 @@ async fn config() -> impl Responder {
#[actix_web::main] #[actix_web::main]
async fn main() { async fn main() {
let srv = test::start(|| App::new().service(config)); let srv = actix_test::start(|| App::new().service(config));
let request = srv.get("/config"); let request = srv.get("/config");
let response = request.send().await.unwrap(); let response = request.send().await.unwrap();

View File

@ -1,10 +1,22 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 3.0.0-beta.4 - 2021-04-02
### Added
* Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114]
### Changed ### Changed
* `ConnectorService` type is renamed to `BoxConnectorService` [#2081] * `ConnectorService` type is renamed to `BoxConnectorService`. [#2081]
* Fix http/https encoding when enabling `compress` feature. [#2116]
* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header
methods now take `IntoHeaderPair` tuples. [#2094]
[#2081]: https://github.com/actix/actix-web/pull/2081 [#2081]: https://github.com/actix/actix-web/pull/2081
[#2094]: https://github.com/actix/actix-web/pull/2094
[#2114]: https://github.com/actix/actix-web/pull/2114
[#2116]: https://github.com/actix/actix-web/pull/2116
## 3.0.0-beta.3 - 2021-03-08 ## 3.0.0-beta.3 - 2021-03-08

View File

@ -1,7 +1,10 @@
[package] [package]
name = "awc" name = "awc"
version = "3.0.0-beta.3" version = "3.0.0-beta.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>",
]
description = "Async HTTP and WebSocket client library built on the Actix ecosystem" description = "Async HTTP and WebSocket client library built on the Actix ecosystem"
readme = "README.md" readme = "README.md"
keywords = ["actix", "http", "framework", "async", "web"] keywords = ["actix", "http", "framework", "async", "web"]
@ -46,12 +49,11 @@ trust-dns = ["actix-http/trust-dns"]
[dependencies] [dependencies]
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-service = "2.0.0-beta.4" actix-service = "2.0.0-beta.4"
actix-http = "3.0.0-beta.4" actix-http = "3.0.0-beta.5"
actix-rt = { version = "2.1", default-features = false } actix-rt = { version = "2.1", default-features = false }
base64 = "0.13" base64 = "0.13"
bytes = "1" bytes = "1"
cfg-if = "1.0"
derive_more = "0.99.5" derive_more = "0.99.5"
futures-core = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.7", default-features = false }
itoa = "0.4" itoa = "0.4"
@ -67,12 +69,13 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
[dev-dependencies] [dev-dependencies]
actix-web = { version = "4.0.0-beta.4", features = ["openssl"] } actix-web = { version = "4.0.0-beta.5", features = ["openssl"] }
actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } actix-http = { version = "3.0.0-beta.5", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] }
actix-utils = "3.0.0-beta.1" actix-utils = "3.0.0-beta.4"
actix-server = "2.0.0-beta.3" actix-server = "2.0.0-beta.3"
actix-tls = { version = "3.0.0-beta.4", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] }
actix-test = { version = "0.1.0-beta.1", features = ["openssl", "rustls"] }
brotli2 = "0.3.2" brotli2 = "0.3.2"
env_logger = "0.8" env_logger = "0.8"
@ -80,3 +83,7 @@ flate2 = "1.0.13"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
rcgen = "0.8" rcgen = "0.8"
webpki = "0.21" webpki = "0.21"
[[example]]
name = "client"
required-features = ["rustls"]

View File

@ -3,9 +3,9 @@
> Async HTTP and WebSocket client library. > Async HTTP and WebSocket client library.
[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc)
[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.3)](https://docs.rs/awc/3.0.0-beta.3) [![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.4)](https://docs.rs/awc/3.0.0-beta.4)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc)
[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.3/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.3) [![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.4/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.4)
[![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) [![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 ## Documentation & Resources

View File

@ -4,12 +4,11 @@ use std::net::IpAddr;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use actix_codec::{AsyncRead, AsyncWrite};
use actix_http::{ use actix_http::{
client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection}, client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection},
http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}, http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri},
}; };
use actix_rt::net::TcpStream; use actix_rt::net::{ActixStream, TcpStream};
use actix_service::{boxed, Service}; use actix_service::{boxed, Service};
use crate::connect::DefaultConnector; use crate::connect::DefaultConnector;
@ -64,7 +63,7 @@ where
S: Service<TcpConnect<Uri>, Response = TcpConnection<Uri, Io>, Error = TcpConnectError> S: Service<TcpConnect<Uri>, Response = TcpConnection<Uri, Io>, Error = TcpConnectError>
+ Clone + Clone
+ 'static, + 'static,
Io: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, Io: ActixStream + fmt::Debug + 'static,
{ {
/// Use custom connector service. /// Use custom connector service.
pub fn connector<S1, Io1>(self, connector: Connector<S1>) -> ClientBuilder<S1, M> pub fn connector<S1, Io1>(self, connector: Connector<S1>) -> ClientBuilder<S1, M>
@ -75,7 +74,7 @@ where
Error = TcpConnectError, Error = TcpConnectError,
> + Clone > + Clone
+ 'static, + 'static,
Io1: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, Io1: ActixStream + fmt::Debug + 'static,
{ {
ClientBuilder { ClientBuilder {
middleware: self.middleware, middleware: self.middleware,

View File

@ -286,4 +286,12 @@ impl Client {
} }
req req
} }
/// Get default HeaderMap of Client.
///
/// Returns Some(&mut HeaderMap) when Client object is unique
/// (No other clone of client exists at the same time).
pub fn headers(&mut self) -> Option<&mut HeaderMap> {
Rc::get_mut(&mut self.0.headers)
}
} }

View File

@ -283,10 +283,9 @@ fn rebuild_uri(res: &ClientResponse, org_uri: Uri) -> Result<Uri, SendRequestErr
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_web::{test::start, web, App, Error, HttpResponse}; use actix_web::{web, App, Error, HttpResponse};
use super::*; use super::*;
use crate::ClientBuilder; use crate::ClientBuilder;
#[actix_rt::test] #[actix_rt::test]
@ -296,7 +295,7 @@ mod tests {
.wrap(Redirect::new().max_redirect_times(10)) .wrap(Redirect::new().max_redirect_times(10))
.finish(); .finish();
let srv = start(|| { let srv = actix_test::start(|| {
App::new() App::new()
.service(web::resource("/test").route(web::to(|| async { .service(web::resource("/test").route(web::to(|| async {
Ok::<_, Error>(HttpResponse::BadRequest()) Ok::<_, Error>(HttpResponse::BadRequest())
@ -323,7 +322,7 @@ mod tests {
.connector(crate::Connector::new()) .connector(crate::Connector::new())
.finish(); .finish();
let srv = start(|| { let srv = actix_test::start(|| {
App::new() App::new()
.service(web::resource("/").route(web::to(|| async { .service(web::resource("/").route(web::to(|| async {
Ok::<_, Error>( Ok::<_, Error>(

View File

@ -21,15 +21,10 @@ use crate::frozen::FrozenClientRequest;
use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest}; use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest};
use crate::ClientConfig; use crate::ClientConfig;
cfg_if::cfg_if! { #[cfg(feature = "compress")]
if #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] { const HTTPS_ENCODING: &str = "br, gzip, deflate";
const HTTPS_ENCODING: &str = "br, gzip, deflate"; #[cfg(not(feature = "compress"))]
} else if #[cfg(feature = "compress")] { const HTTPS_ENCODING: &str = "br";
const HTTPS_ENCODING: &str = "br";
} else {
const HTTPS_ENCODING: &str = "identity";
}
}
/// An HTTP Client request builder /// An HTTP Client request builder
/// ///
@ -194,12 +189,12 @@ impl ClientRequest {
/// # #[actix_rt::main] /// # #[actix_rt::main]
/// # async fn main() { /// # async fn main() {
/// # use awc::Client; /// # use awc::Client;
/// use awc::http::header::ContentType; /// use awc::http::header::CONTENT_TYPE;
/// ///
/// Client::new() /// Client::new()
/// .get("http://www.rust-lang.org") /// .get("http://www.rust-lang.org")
/// .insert_header(("X-TEST", "value")) /// .insert_header(("X-TEST", "value"))
/// .insert_header(ContentType(mime::APPLICATION_JSON)); /// .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON));
/// # } /// # }
/// ``` /// ```
pub fn append_header<H>(mut self, header: H) -> Self pub fn append_header<H>(mut self, header: H) -> Self
@ -521,11 +516,11 @@ impl ClientRequest {
.unwrap_or(true); .unwrap_or(true);
if https { 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 { } else {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[cfg(feature = "compress")]
{ {
slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, "gzip, deflate")) slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, "gzip, deflate"));
} }
}; };
} }
@ -553,6 +548,8 @@ impl fmt::Debug for ClientRequest {
mod tests { mod tests {
use std::time::SystemTime; use std::time::SystemTime;
use actix_http::http::header::HttpDate;
use super::*; use super::*;
use crate::Client; use crate::Client;
@ -569,7 +566,7 @@ mod tests {
let req = Client::new() let req = Client::new()
.put("/") .put("/")
.version(Version::HTTP_2) .version(Version::HTTP_2)
.insert_header(header::Date(SystemTime::now().into())) .insert_header((header::DATE, HttpDate::from(SystemTime::now())))
.content_type("plain/text") .content_type("plain/text")
.append_header((header::SERVER, "awc")); .append_header((header::SERVER, "awc"));

View File

@ -449,13 +449,13 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_body() { async fn test_body() {
let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); let mut req = TestResponse::with_header((header::CONTENT_LENGTH, "xxxx")).finish();
match req.body().await.err().unwrap() { match req.body().await.err().unwrap() {
PayloadError::UnknownLength => {} PayloadError::UnknownLength => {}
_ => unreachable!("error"), _ => unreachable!("error"),
} }
let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "10000000").finish(); let mut req = TestResponse::with_header((header::CONTENT_LENGTH, "10000000")).finish();
match req.body().await.err().unwrap() { match req.body().await.err().unwrap() {
PayloadError::Overflow => {} PayloadError::Overflow => {}
_ => unreachable!("error"), _ => unreachable!("error"),
@ -497,23 +497,23 @@ mod tests {
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let mut req = TestResponse::default() let mut req = TestResponse::default()
.header( .insert_header((
header::CONTENT_TYPE, header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"), header::HeaderValue::from_static("application/text"),
) ))
.finish(); .finish();
let json = JsonBody::<_, MyObject>::new(&mut req).await; let json = JsonBody::<_, MyObject>::new(&mut req).await;
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let mut req = TestResponse::default() let mut req = TestResponse::default()
.header( .insert_header((
header::CONTENT_TYPE, header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"), header::HeaderValue::from_static("application/json"),
) ))
.header( .insert_header((
header::CONTENT_LENGTH, header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000"), header::HeaderValue::from_static("10000"),
) ))
.finish(); .finish();
let json = JsonBody::<_, MyObject>::new(&mut req).limit(100).await; let json = JsonBody::<_, MyObject>::new(&mut req).limit(100).await;
@ -523,14 +523,14 @@ mod tests {
)); ));
let mut req = TestResponse::default() let mut req = TestResponse::default()
.header( .insert_header((
header::CONTENT_TYPE, header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"), header::HeaderValue::from_static("application/json"),
) ))
.header( .insert_header((
header::CONTENT_LENGTH, header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"), header::HeaderValue::from_static("16"),
) ))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish(); .finish();

View File

@ -1,8 +1,6 @@
//! Test helpers for actix http client to use during testing. //! Test helpers for actix http client to use during testing.
use std::convert::TryFrom; use actix_http::http::header::IntoHeaderPair;
use actix_http::http::{StatusCode, Version};
use actix_http::http::header::{Header, IntoHeaderValue};
use actix_http::http::{Error as HttpError, HeaderName, StatusCode, Version};
#[cfg(feature = "cookies")] #[cfg(feature = "cookies")]
use actix_http::{ use actix_http::{
cookie::{Cookie, CookieJar}, cookie::{Cookie, CookieJar},
@ -34,13 +32,11 @@ impl Default for TestResponse {
impl TestResponse { impl TestResponse {
/// Create TestResponse and set header /// Create TestResponse and set header
pub fn with_header<K, V>(key: K, value: V) -> Self pub fn with_header<H>(header: H) -> Self
where where
HeaderName: TryFrom<K>, H: IntoHeaderPair,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
{ {
Self::default().header(key, value) Self::default().insert_header(header)
} }
/// Set HTTP version of this response /// Set HTTP version of this response
@ -49,28 +45,27 @@ impl TestResponse {
self self
} }
/// Set a header /// Insert a header
pub fn set<H: Header>(mut self, hdr: H) -> Self { pub fn insert_header<H>(mut self, header: H) -> Self
if let Ok(value) = hdr.try_into_value() { where
self.head.headers.append(H::name(), value); H: IntoHeaderPair,
{
if let Ok((key, value)) = header.try_into_header_pair() {
self.head.headers.insert(key, value);
return self; return self;
} }
panic!("Can not set header"); panic!("Can not set header");
} }
/// Append a header /// Append a header
pub fn header<K, V>(mut self, key: K, value: V) -> Self pub fn append_header<H>(mut self, header: H) -> Self
where where
HeaderName: TryFrom<K>, H: IntoHeaderPair,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
{ {
if let Ok(key) = HeaderName::try_from(key) { if let Ok((key, value)) = header.try_into_header_pair() {
if let Ok(value) = value.try_into_value() {
self.head.headers.append(key, value); self.head.headers.append(key, value);
return self; return self;
} }
}
panic!("Can not create header"); panic!("Can not create header");
} }
@ -115,6 +110,8 @@ impl TestResponse {
mod tests { mod tests {
use std::time::SystemTime; use std::time::SystemTime;
use actix_http::http::header::HttpDate;
use super::*; use super::*;
use crate::{cookie, http::header}; use crate::{cookie, http::header};
@ -122,7 +119,7 @@ mod tests {
fn test_basics() { fn test_basics() {
let res = TestResponse::default() let res = TestResponse::default()
.version(Version::HTTP_2) .version(Version::HTTP_2)
.set(header::Date(SystemTime::now().into())) .insert_header((header::DATE, HttpDate::from(SystemTime::now())))
.cookie(cookie::Cookie::build("name", "value").finish()) .cookie(cookie::Cookie::build("name", "value").finish())
.finish(); .finish();
assert!(res.headers().contains_key(header::SET_COOKIE)); assert!(res.headers().contains_key(header::SET_COOKIE));

View File

@ -6,7 +6,7 @@
//! //!
//! ```no_run //! ```no_run
//! use awc::{Client, ws}; //! use awc::{Client, ws};
//! use futures_util::{sink::SinkExt, stream::StreamExt}; //! use futures_util::{sink::SinkExt as _, stream::StreamExt as _};
//! //!
//! #[actix_rt::main] //! #[actix_rt::main]
//! async fn main() { //! async fn main() {

View File

@ -5,12 +5,13 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use actix_utils::future::ok;
use brotli2::write::BrotliEncoder; use brotli2::write::BrotliEncoder;
use bytes::Bytes; use bytes::Bytes;
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use flate2::write::GzEncoder; use flate2::write::GzEncoder;
use flate2::Compression; use flate2::Compression;
use futures_util::{future::ok, stream}; use futures_util::stream;
use rand::Rng; use rand::Rng;
use actix_http::{ use actix_http::{
@ -23,7 +24,7 @@ use actix_web::{
dev::{AppConfig, BodyEncoding}, dev::{AppConfig, BodyEncoding},
http::{header, Cookie}, http::{header, Cookie},
middleware::Compress, middleware::Compress,
test, web, App, Error, HttpMessage, HttpRequest, HttpResponse, web, App, Error, HttpMessage, HttpRequest, HttpResponse,
}; };
use awc::error::{JsonPayloadError, PayloadError, SendRequestError}; use awc::error::{JsonPayloadError, PayloadError, SendRequestError};
@ -51,7 +52,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
#[actix_rt::test] #[actix_rt::test]
async fn test_simple() { async fn test_simple() {
let srv = test::start(|| { let srv = actix_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))))
}); });
@ -77,7 +78,7 @@ async fn test_simple() {
#[actix_rt::test] #[actix_rt::test]
async fn test_json() { async fn test_json() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().service( App::new().service(
web::resource("/").route(web::to(|_: web::Json<String>| HttpResponse::Ok())), web::resource("/").route(web::to(|_: web::Json<String>| HttpResponse::Ok())),
) )
@ -93,7 +94,7 @@ async fn test_json() {
#[actix_rt::test] #[actix_rt::test]
async fn test_form() { async fn test_form() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to( App::new().service(web::resource("/").route(web::to(
|_: web::Form<HashMap<String, String>>| HttpResponse::Ok(), |_: web::Form<HashMap<String, String>>| HttpResponse::Ok(),
))) )))
@ -112,7 +113,7 @@ async fn test_form() {
#[actix_rt::test] #[actix_rt::test]
async fn test_timeout() { async fn test_timeout() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|| async { App::new().service(web::resource("/").route(web::to(|| async {
actix_rt::time::sleep(Duration::from_millis(200)).await; actix_rt::time::sleep(Duration::from_millis(200)).await;
Ok::<_, Error>(HttpResponse::Ok().body(STR)) Ok::<_, Error>(HttpResponse::Ok().body(STR))
@ -137,7 +138,7 @@ async fn test_timeout() {
#[actix_rt::test] #[actix_rt::test]
async fn test_timeout_override() { async fn test_timeout_override() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|| async { App::new().service(web::resource("/").route(web::to(|| async {
actix_rt::time::sleep(Duration::from_millis(200)).await; actix_rt::time::sleep(Duration::from_millis(200)).await;
Ok::<_, Error>(HttpResponse::Ok().body(STR)) Ok::<_, Error>(HttpResponse::Ok().body(STR))
@ -159,9 +160,9 @@ async fn test_timeout_override() {
#[actix_rt::test] #[actix_rt::test]
async fn test_response_timeout() { async fn test_response_timeout() {
use futures_util::stream::{once, StreamExt}; use futures_util::stream::{once, StreamExt as _};
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|| async { App::new().service(web::resource("/").route(web::to(|| async {
Ok::<_, Error>( Ok::<_, Error>(
HttpResponse::Ok() HttpResponse::Ok()
@ -443,7 +444,7 @@ async fn test_connection_wait_queue_force_close() {
#[actix_rt::test] #[actix_rt::test]
async fn test_with_query_parameter() { async fn test_with_query_parameter() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").to(|req: HttpRequest| { App::new().service(web::resource("/").to(|req: HttpRequest| {
if req.query_string().contains("qp") { if req.query_string().contains("qp") {
HttpResponse::Ok() HttpResponse::Ok()
@ -463,7 +464,7 @@ async fn test_with_query_parameter() {
#[actix_rt::test] #[actix_rt::test]
async fn test_no_decompress() { async fn test_no_decompress() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new() App::new()
.wrap(Compress::default()) .wrap(Compress::default())
.service(web::resource("/").route(web::to(|| { .service(web::resource("/").route(web::to(|| {
@ -507,7 +508,7 @@ async fn test_no_decompress() {
#[actix_rt::test] #[actix_rt::test]
async fn test_client_gzip_encoding() { async fn test_client_gzip_encoding() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|| { App::new().service(web::resource("/").route(web::to(|| {
let mut e = GzEncoder::new(Vec::new(), Compression::default()); let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap(); e.write_all(STR.as_ref()).unwrap();
@ -530,7 +531,7 @@ async fn test_client_gzip_encoding() {
#[actix_rt::test] #[actix_rt::test]
async fn test_client_gzip_encoding_large() { async fn test_client_gzip_encoding_large() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|| { App::new().service(web::resource("/").route(web::to(|| {
let mut e = GzEncoder::new(Vec::new(), Compression::default()); let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.repeat(10).as_ref()).unwrap(); e.write_all(STR.repeat(10).as_ref()).unwrap();
@ -559,7 +560,7 @@ async fn test_client_gzip_encoding_large_random() {
.map(char::from) .map(char::from)
.collect::<String>(); .collect::<String>();
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|data: Bytes| { App::new().service(web::resource("/").route(web::to(|data: Bytes| {
let mut e = GzEncoder::new(Vec::new(), Compression::default()); let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(&data).unwrap(); e.write_all(&data).unwrap();
@ -581,7 +582,7 @@ async fn test_client_gzip_encoding_large_random() {
#[actix_rt::test] #[actix_rt::test]
async fn test_client_brotli_encoding() { async fn test_client_brotli_encoding() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|data: Bytes| { App::new().service(web::resource("/").route(web::to(|data: Bytes| {
let mut e = BrotliEncoder::new(Vec::new(), 5); let mut e = BrotliEncoder::new(Vec::new(), 5);
e.write_all(&data).unwrap(); e.write_all(&data).unwrap();
@ -609,7 +610,7 @@ async fn test_client_brotli_encoding_large_random() {
.map(char::from) .map(char::from)
.collect::<String>(); .collect::<String>();
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|data: Bytes| { App::new().service(web::resource("/").route(web::to(|data: Bytes| {
let mut e = BrotliEncoder::new(Vec::new(), 5); let mut e = BrotliEncoder::new(Vec::new(), 5);
e.write_all(&data).unwrap(); e.write_all(&data).unwrap();
@ -632,7 +633,7 @@ async fn test_client_brotli_encoding_large_random() {
#[actix_rt::test] #[actix_rt::test]
async fn test_client_deflate_encoding() { async fn test_client_deflate_encoding() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().default_service(web::to(|body: Bytes| { App::new().default_service(web::to(|body: Bytes| {
HttpResponse::Ok() HttpResponse::Ok()
.encoding(http::ContentEncoding::Br) .encoding(http::ContentEncoding::Br)
@ -657,7 +658,7 @@ async fn test_client_deflate_encoding_large_random() {
.take(70_000) .take(70_000)
.collect::<String>(); .collect::<String>();
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().default_service(web::to(|body: Bytes| { App::new().default_service(web::to(|body: Bytes| {
HttpResponse::Ok() HttpResponse::Ok()
.encoding(http::ContentEncoding::Br) .encoding(http::ContentEncoding::Br)
@ -676,7 +677,7 @@ async fn test_client_deflate_encoding_large_random() {
#[actix_rt::test] #[actix_rt::test]
async fn test_client_streaming_explicit() { async fn test_client_streaming_explicit() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().default_service(web::to(|body: web::Payload| { App::new().default_service(web::to(|body: web::Payload| {
HttpResponse::Ok() HttpResponse::Ok()
.encoding(http::ContentEncoding::Identity) .encoding(http::ContentEncoding::Identity)
@ -697,7 +698,7 @@ async fn test_client_streaming_explicit() {
#[actix_rt::test] #[actix_rt::test]
async fn test_body_streaming_implicit() { async fn test_body_streaming_implicit() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().default_service(web::to(|| { App::new().default_service(web::to(|| {
let body = stream::once(async { let body = stream::once(async {
Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes()))
@ -733,7 +734,7 @@ async fn test_client_cookie_handling() {
let cookie1b = cookie1.clone(); let cookie1b = cookie1.clone();
let cookie2b = cookie2.clone(); let cookie2b = cookie2.clone();
let srv = test::start(move || { let srv = actix_test::start(move || {
let cookie1 = cookie1b.clone(); let cookie1 = cookie1b.clone();
let cookie2 = cookie2b.clone(); let cookie2 = cookie2b.clone();
@ -789,8 +790,7 @@ async fn test_client_cookie_handling() {
#[actix_rt::test] #[actix_rt::test]
async fn client_unread_response() { async fn client_unread_response() {
let addr = test::unused_addr(); let addr = actix_test::unused_addr();
let lst = std::net::TcpListener::bind(addr).unwrap(); let lst = std::net::TcpListener::bind(addr).unwrap();
std::thread::spawn(move || { std::thread::spawn(move || {
@ -819,7 +819,7 @@ async fn client_unread_response() {
#[actix_rt::test] #[actix_rt::test]
async fn client_basic_auth() { async fn client_basic_auth() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().route( App::new().route(
"/", "/",
web::to(|req: HttpRequest| { web::to(|req: HttpRequest| {
@ -847,7 +847,7 @@ async fn client_basic_auth() {
#[actix_rt::test] #[actix_rt::test]
async fn client_bearer_auth() { async fn client_bearer_auth() {
let srv = test::start(|| { let srv = actix_test::start(|| {
App::new().route( App::new().route(
"/", "/",
web::to(|req: HttpRequest| { web::to(|req: HttpRequest| {
@ -877,7 +877,7 @@ async fn client_bearer_auth() {
async fn test_local_address() { async fn test_local_address() {
let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
let srv = test::start(move || { let srv = actix_test::start(move || {
App::new().service(web::resource("/").route(web::to( App::new().service(web::resource("/").route(web::to(
move |req: HttpRequest| async move { move |req: HttpRequest| async move {
assert_eq!(req.peer_addr().unwrap().ip(), ip); assert_eq!(req.peer_addr().unwrap().ip(), ip);

View File

@ -13,8 +13,8 @@ use std::{
use actix_http::HttpService; use actix_http::HttpService;
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; use actix_service::{map_config, pipeline_factory, ServiceFactoryExt};
use actix_utils::future::ok;
use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse};
use futures_util::future::ok;
use rustls::internal::pemfile::{certs, pkcs8_private_keys}; use rustls::internal::pemfile::{certs, pkcs8_private_keys};
use rustls::{ClientConfig, NoClientAuth, ServerConfig}; use rustls::{ClientConfig, NoClientAuth, ServerConfig};

View File

@ -8,9 +8,9 @@ use std::sync::Arc;
use actix_http::HttpService; use actix_http::HttpService;
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; use actix_service::{map_config, pipeline_factory, ServiceFactoryExt};
use actix_utils::future::ok;
use actix_web::http::Version; use actix_web::http::Version;
use actix_web::{dev::AppConfig, web, App, HttpResponse}; use actix_web::{dev::AppConfig, web, App, HttpResponse};
use futures_util::future::ok;
use openssl::{ use openssl::{
pkey::PKey, pkey::PKey,
ssl::{SslAcceptor, SslConnector, SslMethod, SslVerifyMode}, ssl::{SslAcceptor, SslConnector, SslMethod, SslVerifyMode},

View File

@ -3,9 +3,9 @@ use std::io;
use actix_codec::Framed; use actix_codec::Framed;
use actix_http::{body::BodySize, h1, ws, Error, HttpService, Request, Response}; use actix_http::{body::BodySize, h1, ws, Error, HttpService, Request, Response};
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_utils::future::ok;
use bytes::Bytes; use bytes::Bytes;
use futures_util::future::ok; use futures_util::{SinkExt as _, StreamExt as _};
use futures_util::{SinkExt, StreamExt};
async fn ws_service(req: ws::Frame) -> Result<ws::Message, io::Error> { async fn ws_service(req: ws::Frame) -> Result<ws::Message, io::Error> {
match req { match req {

View File

@ -1,12 +1,12 @@
use std::future::Future; use std::{future::Future, time::Instant};
use std::time::Instant;
use actix_http::Response; use actix_http::Response;
use actix_utils::future::{ready, Ready};
use actix_web::http::StatusCode; use actix_web::http::StatusCode;
use actix_web::test::TestRequest; use actix_web::test::TestRequest;
use actix_web::{error, Error, HttpRequest, HttpResponse, Responder}; use actix_web::{error, Error, HttpRequest, HttpResponse, Responder};
use criterion::{criterion_group, criterion_main, Criterion}; use criterion::{criterion_group, criterion_main, Criterion};
use futures_util::future::{ready, Either, Ready}; use futures_util::future::{join_all, Either};
// responder simulate the old responder trait. // responder simulate the old responder trait.
trait FutureResponder { trait FutureResponder {
@ -79,7 +79,7 @@ fn future_responder(c: &mut Criterion) {
.await .await
}); });
let futs = futures_util::future::join_all(futs); let futs = join_all(futs);
let start = Instant::now(); let start = Instant::now();

View File

@ -32,7 +32,7 @@ fn bench_async_burst(c: &mut Criterion) {
let rt = actix_rt::System::new(); let rt = actix_rt::System::new();
let srv = rt.block_on(async { let srv = rt.block_on(async {
test::start(|| { actix_test::start(|| {
App::new() App::new()
.service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
}) })

View File

@ -11,6 +11,5 @@ coverage:
ignore: # ignore code coverage on following paths ignore: # ignore code coverage on following paths
- "**/tests" - "**/tests"
- "test-server"
- "**/benches" - "**/benches"
- "**/examples" - "**/examples"

View File

@ -1,21 +1,37 @@
digraph { digraph {
rankdir=TB
subgraph cluster_net { subgraph cluster_net {
label="actix/actix-net"; label="actix-net"
"actix-codec" "actix-codec" "actix-macros" "actix-rt" "actix-server" "actix-service"
"actix-macros" "actix-tls" "actix-tracing" "actix-utils" "actix-router"
"actix-rt"
"actix-server"
"actix-service"
"actix-threadpool"
"actix-tls"
"actix-tracing"
"actix-utils"
"actix-router"
} }
"actix-utils" -> { "actix-service" "actix-rt" "actix-codec" } subgraph cluster_other {
label="other actix owned crates"
{ rank=same; "local-channel" "local-waker" "bytestring" }
}
subgraph cluster_tokio {
label="tokio"
"tokio" "tokio-util"
}
"actix-codec" -> { "tokio" }
"actix-codec" -> { "tokio-util" }[color=red]
"actix-utils" -> { "local-waker" }
"actix-tracing" -> { "actix-service" } "actix-tracing" -> { "actix-service" }
"actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" }
"actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } "actix-tls" -> { "tokio-util" }[color="#009900"]
"actix-rt" -> { "actix-macros" "actix-threadpool" } "actix-server" -> { "actix-service" "actix-rt" "actix-utils" "tokio" }
"actix-rt" -> { "actix-macros" "tokio" }
"actix-router" -> { "bytestring" }
"local-channel" -> { "local-waker" }
// invisible edges to force nicer layout
edge [style=invis]
"actix-macros" -> "tokio"
"actix-service" -> "bytestring"
"actix-macros" -> "bytestring"
} }

View File

@ -9,12 +9,14 @@ digraph {
"actix-web-actors" "actix-web-actors"
"actix-web-codegen" "actix-web-codegen"
"actix-http-test" "actix-http-test"
"actix-test"
} }
"actix-web" -> { "actix-web-codegen" "actix-http" "awc" } "actix-web" -> { "actix-web-codegen" "actix-http" }
"awc" -> { "actix-http" } "awc" -> { "actix-http" }
"actix-web-actors" -> { "actix" "actix-web" "actix-http" } "actix-web-actors" -> { "actix" "actix-web" "actix-http" }
"actix-multipart" -> { "actix-web" } "actix-multipart" -> { "actix-web" }
"actix-files" -> { "actix-web" } "actix-files" -> { "actix-web" }
"actix-http-test" -> { "awc" } "actix-http-test" -> { "awc" }
"actix-test" -> { "actix-web" "awc" "actix-http-test" }
} }

View File

@ -10,7 +10,7 @@ use actix_service::boxed::{self, BoxServiceFactory};
use actix_service::{ use actix_service::{
apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform, apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform,
}; };
use futures_util::future::FutureExt; use futures_util::future::FutureExt as _;
use crate::app_service::{AppEntry, AppInit, AppRoutingFactory}; use crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
@ -465,8 +465,8 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_service::Service; use actix_service::Service;
use actix_utils::future::{err, ok};
use bytes::Bytes; use bytes::Bytes;
use futures_util::future::{err, ok};
use super::*; use super::*;
use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::http::{header, HeaderValue, Method, StatusCode};

View File

@ -103,8 +103,8 @@ impl AppService {
} }
} }
/// Application connection config /// Application connection config.
#[derive(Clone)] #[derive(Debug, Clone)]
pub struct AppConfig { pub struct AppConfig {
secure: bool, secure: bool,
host: String, host: String,
@ -112,10 +112,15 @@ pub struct AppConfig {
} }
impl AppConfig { impl AppConfig {
pub(crate) fn new(secure: bool, addr: SocketAddr, host: String) -> Self { pub(crate) fn new(secure: bool, host: String, addr: SocketAddr) -> Self {
AppConfig { secure, host, addr } AppConfig { secure, host, addr }
} }
#[doc(hidden)]
pub fn __priv_test_new(secure: bool, host: String, addr: SocketAddr) -> Self {
AppConfig::new(secure, host, addr)
}
/// Server host name. /// Server host name.
/// ///
/// Host name is used by application router as a hostname for url generation. /// Host name is used by application router as a hostname for url generation.
@ -142,8 +147,8 @@ impl Default for AppConfig {
fn default() -> Self { fn default() -> Self {
AppConfig::new( AppConfig::new(
false, false,
"127.0.0.1:8080".parse().unwrap(),
"localhost:8080".to_owned(), "localhost:8080".to_owned(),
"127.0.0.1:8080".parse().unwrap(),
) )
} }
} }

View File

@ -4,7 +4,8 @@ use std::sync::Arc;
use actix_http::error::{Error, ErrorInternalServerError}; use actix_http::error::{Error, ErrorInternalServerError};
use actix_http::Extensions; use actix_http::Extensions;
use futures_util::future::{err, ok, LocalBoxFuture, Ready}; use actix_utils::future::{err, ok, Ready};
use futures_core::future::LocalBoxFuture;
use serde::Serialize; use serde::Serialize;
use crate::dev::Payload; use crate::dev::Payload;
@ -147,13 +148,13 @@ impl<T: ?Sized + 'static> DataFactory for Data<T> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_service::Service;
use std::sync::atomic::{AtomicUsize, Ordering};
use super::*; use super::*;
use crate::http::StatusCode; use crate::{
use crate::test::{self, init_service, TestRequest}; dev::Service,
use crate::{web, App, HttpResponse}; http::StatusCode,
test::{init_service, TestRequest},
web, App, HttpResponse,
};
#[actix_rt::test] #[actix_rt::test]
async fn test_data_extractor() { async fn test_data_extractor() {
@ -269,49 +270,6 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[actix_rt::test]
async fn test_data_drop() {
struct TestData(Arc<AtomicUsize>);
impl TestData {
fn new(inner: Arc<AtomicUsize>) -> Self {
let _ = inner.fetch_add(1, Ordering::SeqCst);
Self(inner)
}
}
impl Clone for TestData {
fn clone(&self) -> Self {
let inner = self.0.clone();
let _ = inner.fetch_add(1, Ordering::SeqCst);
Self(inner)
}
}
impl Drop for TestData {
fn drop(&mut self) {
let _ = self.0.fetch_sub(1, Ordering::SeqCst);
}
}
let num = Arc::new(AtomicUsize::new(0));
let data = TestData::new(num.clone());
assert_eq!(num.load(Ordering::SeqCst), 1);
let srv = test::start(move || {
let data = data.clone();
App::new()
.data(data)
.service(web::resource("/").to(|_data: Data<TestData>| async { "ok" }))
});
assert!(srv.get("/").send().await.unwrap().status().is_success());
srv.stop().await;
assert_eq!(num.load(Ordering::SeqCst), 0);
}
#[actix_rt::test] #[actix_rt::test]
async fn test_data_from_arc() { async fn test_data_from_arc() {
let data_new = Data::new(String::from("test-123")); let data_new = Data::new(String::from("test-123"));

View File

@ -6,10 +6,8 @@ use std::{
task::{Context, Poll}, task::{Context, Poll},
}; };
use futures_util::{ use actix_utils::future::{ready, Ready};
future::{ready, Ready}, use futures_core::ready;
ready,
};
use crate::{dev::Payload, Error, HttpRequest}; use crate::{dev::Payload, Error, HttpRequest};

View File

@ -5,8 +5,8 @@ use std::task::{Context, Poll};
use actix_http::{Error, Response}; use actix_http::{Error, Response};
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use futures_util::future::{ready, Ready}; use actix_utils::future::{ready, Ready};
use futures_util::ready; use futures_core::ready;
use pin_project::pin_project; use pin_project::pin_project;
use crate::extract::FromRequest; use crate::extract::FromRequest;

View File

@ -2,10 +2,10 @@ use std::cmp::Ordering;
use mime::Mime; use mime::Mime;
use crate::header::{qitem, QualityItem}; use super::{qitem, QualityItem};
use crate::http::header; use crate::http::header;
header! { crate::header! {
/// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2)
/// ///
/// The `Accept` header field can be used by user agents to specify /// The `Accept` header field can be used by user agents to specify
@ -33,10 +33,10 @@ header! {
/// ///
/// # Examples /// # Examples
/// ``` /// ```
/// use actix_http::Response; /// use actix_web::HttpResponse;
/// use actix_http::http::header::{Accept, qitem}; /// use actix_web::http::header::{Accept, qitem};
/// ///
/// let mut builder = Response::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header( /// builder.insert_header(
/// Accept(vec![ /// Accept(vec![
/// qitem(mime::TEXT_HTML), /// qitem(mime::TEXT_HTML),
@ -45,10 +45,10 @@ header! {
/// ``` /// ```
/// ///
/// ``` /// ```
/// use actix_http::Response; /// use actix_web::HttpResponse;
/// use actix_http::http::header::{Accept, qitem}; /// use actix_web::http::header::{Accept, qitem};
/// ///
/// let mut builder = Response::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header( /// builder.insert_header(
/// Accept(vec![ /// Accept(vec![
/// qitem(mime::APPLICATION_JSON), /// qitem(mime::APPLICATION_JSON),
@ -57,10 +57,10 @@ header! {
/// ``` /// ```
/// ///
/// ``` /// ```
/// use actix_http::Response; /// use actix_web::HttpResponse;
/// use actix_http::http::header::{Accept, QualityItem, q, qitem}; /// use actix_web::http::header::{Accept, QualityItem, q, qitem};
/// ///
/// let mut builder = Response::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header( /// builder.insert_header(
/// Accept(vec![ /// Accept(vec![
/// qitem(mime::TEXT_HTML), /// qitem(mime::TEXT_HTML),
@ -116,8 +116,8 @@ header! {
#[test] #[test]
fn test_fuzzing1() { fn test_fuzzing1() {
use crate::test::TestRequest; use actix_http::test::TestRequest;
let req = TestRequest::default().insert_header((crate::header::ACCEPT, "chunk#;e")).finish(); let req = TestRequest::default().insert_header((crate::http::header::ACCEPT, "chunk#;e")).finish();
let header = Accept::parse(&req); let header = Accept::parse(&req);
assert!(header.is_ok()); assert!(header.is_ok());
} }
@ -213,7 +213,7 @@ impl Accept {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::header::q; use crate::http::header::q;
#[test] #[test]
fn test_mime_precedence() { fn test_mime_precedence() {

View File

@ -1,6 +1,6 @@
use crate::header::{Charset, QualityItem, ACCEPT_CHARSET}; use super::{Charset, QualityItem, ACCEPT_CHARSET};
header! { crate::header! {
/// `Accept-Charset` header, defined in /// `Accept-Charset` header, defined in
/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3)
/// ///
@ -22,20 +22,20 @@ header! {
/// ///
/// # Examples /// # Examples
/// ``` /// ```
/// use actix_http::Response; /// use actix_web::HttpResponse;
/// use actix_http::http::header::{AcceptCharset, Charset, qitem}; /// use actix_web::http::header::{AcceptCharset, Charset, qitem};
/// ///
/// let mut builder = Response::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header( /// builder.insert_header(
/// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) /// AcceptCharset(vec![qitem(Charset::Us_Ascii)])
/// ); /// );
/// ``` /// ```
/// ///
/// ``` /// ```
/// use actix_http::Response; /// use actix_web::HttpResponse;
/// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; /// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem};
/// ///
/// let mut builder = Response::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header( /// builder.insert_header(
/// AcceptCharset(vec![ /// AcceptCharset(vec![
/// QualityItem::new(Charset::Us_Ascii, q(900)), /// QualityItem::new(Charset::Us_Ascii, q(900)),
@ -45,10 +45,10 @@ header! {
/// ``` /// ```
/// ///
/// ``` /// ```
/// use actix_http::Response; /// use actix_web::HttpResponse;
/// use actix_http::http::header::{AcceptCharset, Charset, qitem}; /// use actix_web::http::header::{AcceptCharset, Charset, qitem};
/// ///
/// let mut builder = Response::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header( /// builder.insert_header(
/// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))])
/// ); /// );

View File

@ -26,18 +26,20 @@ header! {
/// ///
/// # Examples /// # Examples
/// ``` /// ```
/// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; /// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptEncoding, Encoding, qitem};
/// ///
/// let mut headers = Headers::new(); /// let mut builder = HttpResponse::new();
/// headers.set( /// builder.insert_header(
/// AcceptEncoding(vec![qitem(Encoding::Chunked)]) /// AcceptEncoding(vec![qitem(Encoding::Chunked)])
/// ); /// );
/// ``` /// ```
/// ``` /// ```
/// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; /// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptEncoding, Encoding, qitem};
/// ///
/// let mut headers = Headers::new(); /// let mut builder = HttpResponse::new();
/// headers.set( /// builder.insert_header(
/// AcceptEncoding(vec![ /// AcceptEncoding(vec![
/// qitem(Encoding::Chunked), /// qitem(Encoding::Chunked),
/// qitem(Encoding::Gzip), /// qitem(Encoding::Gzip),
@ -46,10 +48,11 @@ header! {
/// ); /// );
/// ``` /// ```
/// ``` /// ```
/// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; /// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem, q, qitem};
/// ///
/// let mut headers = Headers::new(); /// let mut builder = HttpResponse::new();
/// headers.set( /// builder.insert_header(
/// AcceptEncoding(vec![ /// AcceptEncoding(vec![
/// qitem(Encoding::Chunked), /// qitem(Encoding::Chunked),
/// QualityItem::new(Encoding::Gzip, q(600)), /// QualityItem::new(Encoding::Gzip, q(600)),

View File

@ -1,7 +1,7 @@
use crate::header::{QualityItem, ACCEPT_LANGUAGE}; use super::{QualityItem, ACCEPT_LANGUAGE};
use language_tags::LanguageTag; use language_tags::LanguageTag;
header! { crate::header! {
/// `Accept-Language` header, defined in /// `Accept-Language` header, defined in
/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5)
/// ///
@ -24,10 +24,10 @@ header! {
/// ///
/// ``` /// ```
/// use language_tags::langtag; /// use language_tags::langtag;
/// use actix_http::Response; /// use actix_web::HttpResponse;
/// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; /// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem};
/// ///
/// let mut builder = Response::Ok(); /// let mut builder = HttpResponse::Ok();
/// let mut langtag: LanguageTag = Default::default(); /// let mut langtag: LanguageTag = Default::default();
/// langtag.language = Some("en".to_owned()); /// langtag.language = Some("en".to_owned());
/// langtag.region = Some("US".to_owned()); /// langtag.region = Some("US".to_owned());
@ -40,10 +40,10 @@ header! {
/// ///
/// ``` /// ```
/// use language_tags::langtag; /// use language_tags::langtag;
/// use actix_http::Response; /// use actix_web::HttpResponse;
/// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; /// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem};
/// ///
/// let mut builder = Response::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header( /// builder.insert_header(
/// AcceptLanguage(vec![ /// AcceptLanguage(vec![
/// qitem(langtag!(da)), /// qitem(langtag!(da)),

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