diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..40fe3e573 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[alias] +chk = "hack check --workspace --all-features --tests --examples" +lint = "hack --clean-per-run clippy --workspace --tests --examples" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc0ff0c19..3aac6efa8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,9 @@ jobs: name: ${{ matrix.target.name }} / ${{ matrix.version }} runs-on: ${{ matrix.target.os }} + env: + VCPKGRS_DYNAMIC: 1 + steps: - uses: actions/checkout@v2 @@ -65,7 +68,13 @@ jobs: uses: actions-rs/cargo@v1 with: 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 uses: actions-rs/cargo@v1 diff --git a/CHANGES.md b/CHANGES.md index 5df0b6d6d..a55005c7a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,19 +1,31 @@ # Changes ## 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 * Double ampersand in Logger format is escaped correctly. [#2067] ### Changed -* `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed instead of skipping. - (Only the first error is kept when multiple error occur) [#2093] +* `CustomResponder` would return error as `HttpResponse` when `CustomResponder::with_header` failed + instead of skipping. (Only the first error is kept when multiple error occur) [#2093] ### Removed * The `client` mod was removed. Clients should now use `awc` directly. [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 [#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 diff --git a/Cargo.toml b/Cargo.toml index f3a6271ee..52db6c335 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.4" +version = "4.0.0-beta.5" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" @@ -36,25 +36,26 @@ members = [ "actix-web-actors", "actix-web-codegen", "actix-http-test", + "actix-test", ] [features] default = ["compress", "cookies"] # content-encoding support -compress = ["actix-http/compress", "awc/compress"] +compress = ["actix-http/compress"] # support for cookies -cookies = ["actix-http/cookies", "awc/cookies"] +cookies = ["actix-http/cookies"] # secure cookies feature secure-cookies = ["actix-http/secure-cookies"] # openssl -openssl = ["tls-openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"] +openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"] # rustls -rustls = ["tls-rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"] +rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"] [[example]] name = "basic" @@ -72,23 +73,18 @@ required-features = ["compress", "cookies"] name = "on_connect" required-features = [] -[[example]] -name = "client" -required-features = ["rustls"] - [dependencies] actix-codec = "0.4.0-beta.1" actix-macros = "0.2.0" actix-router = "0.2.7" -actix-rt = "2.1" +actix-rt = "2.2" actix-server = "2.0.0-beta.3" actix-service = "2.0.0-beta.4" -actix-utils = "3.0.0-beta.2" -actix-tls = { version = "3.0.0-beta.4", default-features = false, optional = true } +actix-utils = "3.0.0-beta.4" +actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.2" -actix-http = "3.0.0-beta.4" -awc = { version = "3.0.0-beta.3", default-features = false } +actix-http = "3.0.0-beta.5" ahash = "0.7" bytes = "1" @@ -97,6 +93,8 @@ either = "1.5.3" encoding_rs = "0.8" futures-core = { 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" mime = "0.3" pin-project = "1.0.0" @@ -107,11 +105,12 @@ serde_urlencoded = "0.7" smallvec = "1.6" socket2 = "0.4.0" 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" [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" criterion = "0.3" env_logger = "0.8" @@ -119,6 +118,8 @@ flate2 = "1.0.13" rand = "0.8" rcgen = "0.8" serde_derive = "1.0" +tls-openssl = { package = "openssl", version = "0.10.9" } +tls-rustls = { package = "rustls", version = "0.19.0" } [profile.release] lto = true @@ -126,13 +127,14 @@ opt-level = 3 codegen-units = 1 [patch.crates-io] -actix-web = { path = "." } +actix-files = { path = "actix-files" } actix-http = { path = "actix-http" } 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-codegen = { path = "actix-web-codegen" } -actix-multipart = { path = "actix-multipart" } -actix-files = { path = "actix-files" } awc = { path = "awc" } [[bench]] diff --git a/README.md b/README.md index 64fd7d08d..508736279 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![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) ![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)
[![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) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index c035d5afe..eb66e0e07 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.6.0-beta.4 - 2021-04-02 +* No notable changes. + + ## 0.6.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 49cd6966c..84d8dd958 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.3" +version = "0.6.0-beta.4" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" readme = "README.md" @@ -17,14 +17,14 @@ name = "actix_files" path = "src/lib.rs" [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-utils = "3.0.0-beta.4" askama_escape = "0.10" bitflags = "1" bytes = "1" -futures-core = { version = "0.3.7", default-features = false } -futures-util = { version = "0.3.7", default-features = false } +futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } http-range = "0.1.4" derive_more = "0.99.5" log = "0.4" @@ -33,5 +33,6 @@ mime_guess = "2.0.1" percent-encoding = "2.1" [dev-dependencies] -actix-rt = "2.1" -actix-web = "4.0.0-beta.4" +actix-rt = "2.2" +actix-web = "4.0.0-beta.5" +actix-test = "0.1.0-beta.1" diff --git a/actix-files/README.md b/actix-files/README.md index c7b7424ec..895d5e687 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![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) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![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) [![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) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 292e3fdf3..ff4241340 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -1,6 +1,7 @@ use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc}; use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt}; +use actix_utils::future::ok; use actix_web::{ dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse}, error::Error, @@ -8,7 +9,7 @@ use actix_web::{ http::header::DispositionType, HttpRequest, }; -use futures_util::future::{ok, FutureExt, LocalBoxFuture}; +use futures_core::future::LocalBoxFuture; use crate::{ directory_listing, named, Directory, DirectoryRenderer, FilesService, HttpNewService, @@ -263,18 +264,18 @@ impl ServiceFactory for Files { }; if let Some(ref default) = *self.default.borrow() { - default - .new_service(()) - .map(move |result| match result { + let fut = default.new_service(()); + Box::pin(async { + match fut.await { Ok(default) => { srv.default = Some(default); Ok(srv) } Err(_) => Err(()), - }) - .boxed_local() + } + }) } else { - ok(srv).boxed_local() + Box::pin(ok(srv)) } } } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 018079b21..24b903c04 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -65,6 +65,7 @@ mod tests { }; use actix_service::ServiceFactory; + use actix_utils::future::ok; use actix_web::{ guard, http::{ @@ -76,7 +77,6 @@ mod tests { web::{self, Bytes}, App, HttpResponse, Responder, }; - use futures_util::future::ok; use super::*; @@ -413,7 +413,7 @@ mod tests { #[actix_rt::test] 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 let response = srv @@ -438,7 +438,7 @@ mod tests { #[actix_rt::test] 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 let response = srv @@ -477,7 +477,7 @@ mod tests { #[actix_rt::test] 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(); diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index dd8e5b503..8a87acd5d 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -3,8 +3,8 @@ use std::{ str::FromStr, }; +use actix_utils::future::{ready, Ready}; use actix_web::{dev::Payload, FromRequest, HttpRequest}; -use futures_util::future::{ready, Ready}; use crate::error::UriSegmentError; diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 3214963ed..d2db8503f 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -1,6 +1,7 @@ use std::{fmt, io, path::PathBuf, rc::Rc}; use actix_service::Service; +use actix_utils::future::ok; use actix_web::{ dev::{ServiceRequest, ServiceResponse}, error::Error, @@ -8,7 +9,7 @@ use actix_web::{ http::{header, Method}, HttpResponse, }; -use futures_util::future::{ok, Either, LocalBoxFuture, Ready}; +use futures_core::future::LocalBoxFuture; use crate::{ named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, NamedFile, @@ -29,19 +30,18 @@ pub struct FilesService { pub(crate) hidden_files: bool, } -type FilesServiceFuture = Either< - Ready>, - LocalBoxFuture<'static, Result>, ->; - impl FilesService { - fn handle_err(&self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture { - log::debug!("Failed to handle {}: {}", req.path(), e); + fn handle_err( + &self, + err: io::Error, + req: ServiceRequest, + ) -> LocalBoxFuture<'static, Result> { + log::debug!("error handling {}: {}", req.path(), err); if let Some(ref default) = self.default { - Either::Right(default.call(req)) + Box::pin(default.call(req)) } 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 for FilesService { type Response = ServiceResponse; type Error = Error; - type Future = FilesServiceFuture; + type Future = LocalBoxFuture<'static, Result>; actix_service::always_ready!(); @@ -69,7 +69,7 @@ impl Service for FilesService { }; if !is_method_valid { - return Either::Left(ok(req.into_response( + return Box::pin(ok(req.into_response( actix_web::HttpResponse::MethodNotAllowed() .insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8)) .body("Request did not meet this resource's requirements."), @@ -79,13 +79,13 @@ impl Service for FilesService { let real_path = match PathBufWrap::parse_path(req.match_info().path(), self.hidden_files) { 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 let path = match self.directory.join(&real_path).canonicalize() { 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() { @@ -93,7 +93,7 @@ impl Service for FilesService { if self.redirect_to_slash && !req.path().ends_with('/') { let redirect_to = format!("{}/", req.path()); - return Either::Left(ok(req.into_response( + return Box::pin(ok(req.into_response( HttpResponse::Found() .insert_header((header::LOCATION, redirect_to)) .body("") @@ -114,9 +114,9 @@ impl Service for FilesService { let (req, _) = req.into_parts(); 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 { let dir = Directory::new(self.directory.clone(), path); @@ -124,12 +124,12 @@ impl Service for FilesService { let (req, _) = req.into_parts(); let x = (self.renderer)(&dir, &req); - match x { - Ok(resp) => Either::Left(ok(resp)), - Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))), - } + Box::pin(match x { + Ok(resp) => ok(resp), + Err(err) => ok(ServiceResponse::from_err(err, req)), + }) } else { - Either::Left(ok(ServiceResponse::from_err( + Box::pin(ok(ServiceResponse::from_err( FilesError::IsDirectory, req.into_parts().0, ))) @@ -145,9 +145,9 @@ impl Service for FilesService { let (req, _) = req.into_parts(); 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), } } } diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index d6a2cdd9b..1dbd9a15b 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -3,6 +3,12 @@ ## 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 * No notable changes. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index a7efc5310..e1dd4c4e8 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.3" +version = "3.0.0-beta.4" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" readme = "README.md" @@ -31,11 +31,11 @@ openssl = ["tls-openssl", "awc/openssl"] [dependencies] actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" -actix-tls = "3.0.0-beta.4" -actix-utils = "3.0.0-beta.2" -actix-rt = "2.1" +actix-tls = "3.0.0-beta.5" +actix-utils = "3.0.0-beta.4" +actix-rt = "2.2" 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" 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 } [dev-dependencies] -actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] } -actix-http = "3.0.0-beta.4" +actix-web = { version = "4.0.0-beta.5", default-features = false, features = ["cookies"] } +actix-http = "3.0.0-beta.5" diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 8cec94808..b8cf450d4 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,9 +3,9 @@ > 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) -[![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) -[![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) ## Documentation & Resources diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 3749b78ca..0f126c99a 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -13,7 +13,9 @@ use std::{net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::{net::TcpStream, System}; 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 futures_core::stream::Stream; use http::Method; @@ -115,16 +117,6 @@ pub async fn test_server_with_addr>( } } -/// 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 pub struct TestServer { addr: net::SocketAddr, @@ -258,6 +250,14 @@ impl TestServer { 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 fn stop(&mut self) { self.system.stop(); @@ -269,3 +269,13 @@ impl Drop for TestServer { 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() +} diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c4e0aec89..9765be3a6 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,16 +1,25 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.5 - 2021-04-02 ### 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::ConnectionIo` trait alias [#2081] -### Chaged +### Changed * `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 [#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 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e1aebb76b..e77d1139c 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.4" +version = "3.0.0-beta.5" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" readme = "README.md" @@ -46,16 +46,15 @@ trust-dns = ["trust-dns-resolver"] [dependencies] actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" -actix-utils = "3.0.0-beta.2" -actix-rt = "2.1" -actix-tls = "3.0.0-beta.4" +actix-utils = "3.0.0-beta.4" +actix-rt = "2.2" +actix-tls = { version = "3.0.0-beta.5", features = ["accept", "connect"] } ahash = "0.7" base64 = "0.13" bitflags = "1.2" bytes = "1" bytestring = "1" -cfg-if = "1" cookie = { version = "0.14.1", features = ["percent-encode"], optional = true } derive_more = "0.99.5" encoding_rs = "0.8" @@ -66,11 +65,13 @@ http = "0.2.2" httparse = "1.3" itoa = "0.4" language-tags = "0.2" +local-channel = "0.1" once_cell = "1.5" log = "0.4" mime = "0.3" percent-encoding = "2.1" pin-project = "1.0.0" +pin-project-lite = "0.2" rand = "0.8" regex = "1.3" serde = "1.0" @@ -89,8 +90,8 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.3" -actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } -actix-tls = { version = "3.0.0-beta.4", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } +actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" rcgen = "0.8" diff --git a/actix-http/README.md b/actix-http/README.md index 53fedd40e..50a17a02f 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,11 +3,11 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=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) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![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) [![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) diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index a84e9aac6..a99ddae46 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -2,7 +2,7 @@ use std::{env, io}; use actix_http::{HttpService, Response}; use actix_server::Server; -use futures_util::future; +use actix_utils::future; use http::header::HeaderValue; use log::info; diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index a4d6ba2b6..fa43e1b03 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -20,8 +20,9 @@ mod tests { use std::pin::Pin; use actix_rt::pin; + use actix_utils::future::poll_fn; use bytes::{Bytes, BytesMut}; - use futures_util::{future::poll_fn, stream}; + use futures_util::stream; use super::*; diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index fa430c4fe..623bfdda2 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -63,11 +63,9 @@ where X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, - )>>::Future: 'static, { /// Set server keep-alive setting. /// @@ -127,7 +125,6 @@ where X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, - >::Future: 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, @@ -152,7 +149,6 @@ where U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, - )>>::Future: 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, @@ -211,7 +207,6 @@ where S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - >::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, @@ -233,7 +228,6 @@ where S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - >::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 78101397d..0e3e97f3f 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -174,7 +174,13 @@ impl H2ConnectionInner { /// Cancel spawned connection task on drop. impl Drop for H2ConnectionInner { fn drop(&mut self) { - self.handle.abort(); + if self + .sender + .send_request(http::Request::new(()), true) + .is_err() + { + self.handle.abort(); + } } } @@ -398,9 +404,18 @@ where #[cfg(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::*; @@ -424,9 +439,36 @@ mod test { drop(conn); - match sender.ready().await { - Ok(_) => panic!("connection should be gone and can not be ready"), - Err(e) => assert!(e.is_io()), - }; + struct DropCheck { + sender: h2::client::SendRequest, + interval: Interval, + start_from: Instant, + } + + impl Future for DropCheck { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + 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; } } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 6996677d2..508fe748b 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -9,7 +9,7 @@ use std::{ }; use actix_rt::{ - net::TcpStream, + net::{ActixStream, TcpStream}, time::{sleep, Sleep}, }; use actix_service::Service; @@ -119,7 +119,7 @@ impl Connector { /// Use custom connector. pub fn connector(self, connector: S1) -> Connector where - Io1: ConnectionIo + fmt::Debug, + Io1: ActixStream + fmt::Debug + 'static, S1: Service< TcpConnect, Response = TcpConnection, @@ -136,7 +136,14 @@ impl Connector { impl Connector 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 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< TcpConnect, Response = TcpConnection, @@ -407,16 +414,14 @@ struct TlsConnectorService { timeout: Duration, } -impl Service for TlsConnectorService +impl Service for TlsConnectorService where S: Service, Error = ConnectError> + Clone + 'static, - St: Service, Response = Res, Error = std::io::Error> - + Clone - + 'static, + St: Service, Error = std::io::Error> + Clone + 'static, Io: ConnectionIo, - Res: IntoConnectionIo, + St::Response: IntoConnectionIo, { type Response = (Box, Protocol); type Error = ConnectError; @@ -471,10 +476,10 @@ where Error = std::io::Error, Future = Fut2, >, + S::Response: IntoConnectionIo, Fut1: Future, ConnectError>>, Fut2: Future>, Io: ConnectionIo, - Res: IntoConnectionIo, { type Output = Result<(Box, Protocol), ConnectError>; diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 01a6e1edf..fa4469d35 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -5,10 +5,11 @@ use std::{ }; use actix_codec::Framed; +use actix_utils::future::poll_fn; use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; -use futures_core::Stream; -use futures_util::{future::poll_fn, SinkExt as _}; +use futures_core::{ready, Stream}; +use futures_util::SinkExt as _; use crate::error::PayloadError; use crate::h1; @@ -17,7 +18,7 @@ use crate::http::{ StatusCode, }; use crate::message::{RequestHeadType, ResponseHead}; -use crate::payload::{Payload, PayloadStream}; +use crate::payload::Payload; use super::connection::{ConnectionIo, H1Connection}; use super::error::{ConnectError, SendRequestError}; @@ -122,10 +123,7 @@ where Ok((head, Payload::None)) } - _ => { - let pl: PayloadStream = Box::pin(PlStream::new(framed)); - Ok((head, pl.into())) - } + _ => Ok((head, Payload::Stream(Box::pin(PlStream::new(framed))))), } } @@ -194,21 +192,16 @@ where } #[pin_project::pin_project] -pub(crate) struct PlStream -where - Io: ConnectionIo, -{ +pub(crate) struct PlStream { #[pin] - framed: Option, h1::ClientPayloadCodec>>, + framed: Framed, h1::ClientPayloadCodec>, } impl PlStream { fn new(framed: Framed, h1::ClientCodec>) -> Self { let framed = framed.into_map_codec(|codec| codec.into_payload_codec()); - PlStream { - framed: Some(framed), - } + PlStream { framed } } } @@ -219,20 +212,16 @@ impl Stream for PlStream { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - let mut framed = self.project().framed.as_pin_mut().unwrap(); + let mut this = self.project(); - match framed.as_mut().next_item(cx)? { - Poll::Pending => Poll::Pending, - Poll::Ready(Some(chunk)) => { - if let Some(chunk) = chunk { - Poll::Ready(Some(Ok(chunk))) - } else { - let keep_alive = framed.codec_ref().keepalive(); - framed.io_mut().on_release(keep_alive); - Poll::Ready(None) - } + match ready!(this.framed.as_mut().next_item(cx)?) { + Some(Some(chunk)) => Poll::Ready(Some(Ok(chunk))), + Some(None) => { + let keep_alive = this.framed.codec_ref().keepalive(); + this.framed.io_mut().on_release(keep_alive); + Poll::Ready(None) } - Poll::Ready(None) => Poll::Ready(None), + None => Poll::Ready(None), } } } diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 437b9ae76..8cb2e2522 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -1,7 +1,7 @@ use std::future::Future; +use actix_utils::future::poll_fn; use bytes::Bytes; -use futures_util::future::poll_fn; use h2::{ client::{Builder, Connection, SendRequest}, SendStream, diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 1354e998e..0178be80c 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -6,9 +6,6 @@ use std::str::Utf8Error; use std::string::FromUtf8Error; 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 derive_more::{Display, From}; use http::uri::InvalidUri; @@ -148,19 +145,6 @@ impl From 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 ResponseError for TimeoutError { - fn status_code(&self) -> StatusCode { - match self { - TimeoutError::Service(e) => e.status_code(), - TimeoutError::Timeout => StatusCode::GATEWAY_TIMEOUT, - } - } -} - #[derive(Debug, Display)] #[display(fmt = "UnknownError")] struct UnitError; @@ -469,14 +453,6 @@ impl ResponseError for ContentTypeError { } } -impl + Decoder, I> ResponseError for FramedDispatcherError -where - E: fmt::Debug + fmt::Display, - >::Error: fmt::Debug, - ::Error: fmt::Debug, -{ -} - /// Helper type that can wrap any error and generate custom response. /// /// In following example any `io::Error` will be converted into "BAD REQUEST" diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index e5989e5ee..bf0365693 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -951,7 +951,8 @@ mod tests { use std::str; 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 crate::{ diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index 5015069bb..bb8e28e95 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,5 +1,5 @@ use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ready, Ready}; +use actix_utils::future::{ready, Ready}; use crate::error::Error; use crate::request::Request; diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 32275ac6b..e72493fa2 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -263,7 +263,7 @@ impl Inner { #[cfg(test)] mod tests { use super::*; - use futures_util::future::poll_fn; + use actix_utils::future::poll_fn; #[actix_rt::test] async fn test_unread_data() { diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 51303886b..a98f6bd0a 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -1,6 +1,4 @@ -use std::future::Future; use std::marker::PhantomData; -use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; use std::{fmt, net}; @@ -8,15 +6,15 @@ use std::{fmt, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; -use futures_core::ready; -use futures_util::future::ready; +use actix_utils::future::ready; +use futures_core::future::LocalBoxFuture; use crate::body::MessageBody; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::request::Request; use crate::response::Response; -use crate::service::HttpFlow; +use crate::service::HttpServiceHandler; use crate::{ConnectCallback, OnConnectData}; use super::codec::Codec; @@ -60,14 +58,17 @@ where impl H1Service where S: ServiceFactory, + S::Future: 'static, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -94,17 +95,21 @@ mod openssl { use super::*; use actix_service::ServiceFactoryExt; - use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream}; - use actix_tls::accept::TlsError; + use actix_tls::accept::{ + openssl::{Acceptor, SslAcceptor, SslError, TlsStream}, + TlsError, + }; impl H1Service, S, B, X, U> where S: ServiceFactory, + S::Future: 'static, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory< @@ -112,6 +117,7 @@ mod openssl { Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -143,19 +149,25 @@ mod openssl { #[cfg(feature = "rustls")] mod rustls { use super::*; + + use std::io; + use actix_service::ServiceFactoryExt; - use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream}; - use actix_tls::accept::TlsError; - use std::{fmt, io}; + use actix_tls::accept::{ + rustls::{Acceptor, ServerConfig, TlsStream}, + TlsError, + }; impl H1Service, S, B, X, U> where S: ServiceFactory, + S::Future: 'static, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory< @@ -163,6 +175,7 @@ mod rustls { Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -241,16 +254,19 @@ where impl ServiceFactory<(T, Option)> for H1Service where - T: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, S: ServiceFactory, + S::Future: 'static, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, B: MessageBody, X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -259,148 +275,50 @@ where type Config = (); type Service = H1ServiceHandler; type InitError = (); - type Future = H1ServiceResponse; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - H1ServiceResponse { - fut: self.srv.new_service(()), - fut_ex: Some(self.expect.new_service(())), - fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), - expect: None, - upgrade: None, - on_connect_ext: self.on_connect_ext.clone(), - cfg: Some(self.cfg.clone()), - _phantom: PhantomData, - } - } -} + let service = self.srv.new_service(()); + let expect = self.expect.new_service(()); + let upgrade = self.upgrade.as_ref().map(|s| s.new_service(())); + let on_connect_ext = self.on_connect_ext.clone(); + let cfg = self.cfg.clone(); -#[doc(hidden)] -#[pin_project::pin_project] -pub struct H1ServiceResponse -where - S: ServiceFactory, - S::Error: Into, - S::InitError: fmt::Debug, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - U: ServiceFactory<(Request, Framed), Response = ()>, - U::Error: fmt::Display, - U::InitError: fmt::Debug, -{ - #[pin] - fut: S::Future, - #[pin] - fut_ex: Option, - #[pin] - fut_upg: Option, - expect: Option, - upgrade: Option, - on_connect_ext: Option>>, - cfg: Option, - _phantom: PhantomData, -} + Box::pin(async move { + let expect = expect + .await + .map_err(|e| log::error!("Init http expect service error: {:?}", e))?; -impl Future for H1ServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into, - S::Response: Into>, - S::InitError: fmt::Debug, - B: MessageBody, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - U: ServiceFactory<(Request, Framed), Response = ()>, - U::Error: fmt::Display, - U::InitError: fmt::Debug, -{ - type Output = Result, ()>; + 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, + }; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); + let service = service + .await + .map_err(|e| log::error!("Init http service error: {:?}", e))?; - 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(), + Ok(H1ServiceHandler::new( + cfg, service, - this.expect.take().unwrap(), - this.upgrade.take(), - this.on_connect_ext.clone(), - ) - })) + expect, + upgrade, + on_connect_ext, + )) + }) } } /// `Service` implementation for HTTP/1 transport -pub struct H1ServiceHandler -where - S: Service, - X: Service, - U: Service<(Request, Framed)>, -{ - flow: Rc>, - on_connect_ext: Option>>, - cfg: ServiceConfig, - _phantom: PhantomData, -} - -impl H1ServiceHandler -where - S: Service, - S::Error: Into, - S::Response: Into>, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display, -{ - fn new( - cfg: ServiceConfig, - service: S, - expect: X, - upgrade: Option, - on_connect_ext: Option>>, - ) -> H1ServiceHandler { - H1ServiceHandler { - flow: HttpFlow::new(service, expect, upgrade), - cfg, - on_connect_ext, - _phantom: PhantomData, - } - } -} +pub type H1ServiceHandler = HttpServiceHandler; impl Service<(T, Option)> - for H1ServiceHandler + for HttpServiceHandler where T: AsyncRead + AsyncWrite + Unpin, S: Service, @@ -417,47 +335,10 @@ where type Future = Dispatcher; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - let ready = self - .flow - .expect - .poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", 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 - } + self._poll_ready(cx).map_err(|e| { + log::error!("HTTP/1 service readiness error: {:?}", e); + DispatchError::Service(e) + }) } fn call(&self, (io, addr): (T, Option)) -> Self::Future { diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index c64139564..8f202e752 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -10,9 +10,9 @@ use actix_service::{ fn_factory, fn_service, pipeline_factory, IntoServiceFactory, Service, ServiceFactory, }; +use actix_utils::future::ready; use bytes::Bytes; -use futures_core::ready; -use futures_util::future::ok; +use futures_core::{future::LocalBoxFuture, ready}; use h2::server::{handshake, Handshake}; use log::error; @@ -65,6 +65,7 @@ where impl H2Service where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, @@ -80,11 +81,11 @@ where Error = DispatchError, InitError = S::InitError, > { - pipeline_factory(fn_factory(|| async { - Ok::<_, S::InitError>(fn_service(|io: TcpStream| { + pipeline_factory(fn_factory(|| { + ready(Ok::<_, S::InitError>(fn_service(|io: TcpStream| { let peer_addr = io.peer_addr().ok(); - ok::<_, DispatchError>((io, peer_addr)) - })) + ready(Ok::<_, DispatchError>((io, peer_addr))) + }))) })) .and_then(self) } @@ -101,6 +102,7 @@ mod openssl { impl H2Service, S, B> where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, @@ -123,10 +125,12 @@ mod openssl { .map_init_err(|_| panic!()), ) .and_then(fn_factory(|| { - ok::<_, S::InitError>(fn_service(|io: TlsStream| { - let peer_addr = io.get_ref().peer_addr().ok(); - ok((io, peer_addr)) - })) + ready(Ok::<_, S::InitError>(fn_service( + |io: TlsStream| { + let peer_addr = io.get_ref().peer_addr().ok(); + ready(Ok((io, peer_addr))) + }, + ))) })) .and_then(self.map_err(TlsError::Service)) } @@ -144,6 +148,7 @@ mod rustls { impl H2Service, S, B> where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, @@ -169,10 +174,12 @@ mod rustls { .map_init_err(|_| panic!()), ) .and_then(fn_factory(|| { - ok::<_, S::InitError>(fn_service(|io: TlsStream| { - let peer_addr = io.get_ref().0.peer_addr().ok(); - ok((io, peer_addr)) - })) + ready(Ok::<_, S::InitError>(fn_service( + |io: TlsStream| { + let peer_addr = io.get_ref().0.peer_addr().ok(); + ready(Ok((io, peer_addr))) + }, + ))) })) .and_then(self.map_err(TlsError::Service)) } @@ -181,8 +188,9 @@ mod rustls { impl ServiceFactory<(T, Option)> for H2Service where - T: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::Response: Into> + 'static, >::Future: 'static, @@ -193,52 +201,16 @@ where type Config = (); type Service = H2ServiceHandler; type InitError = S::InitError; - type Future = H2ServiceResponse; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - H2ServiceResponse { - fut: self.srv.new_service(()), - cfg: Some(self.cfg.clone()), - on_connect_ext: self.on_connect_ext.clone(), - _phantom: PhantomData, - } - } -} + let service = self.srv.new_service(()); + let cfg = self.cfg.clone(); + let on_connect_ext = self.on_connect_ext.clone(); -#[doc(hidden)] -#[pin_project::pin_project] -pub struct H2ServiceResponse -where - S: ServiceFactory, -{ - #[pin] - fut: S::Future, - cfg: Option, - on_connect_ext: Option>>, - _phantom: PhantomData, -} - -impl Future for H2ServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into + 'static, - S::Response: Into> + 'static, - >::Future: 'static, - B: MessageBody + 'static, -{ - type Output = Result, S::InitError>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - 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, - ) + Box::pin(async move { + let service = service.await?; + Ok(H2ServiceHandler::new(cfg, on_connect_ext, service)) }) } } diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 1100a959d..a6056ace4 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -1,9 +1,6 @@ //! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing and conversion, and other //! header utility methods. -use std::fmt; - -use bytes::{Bytes, BytesMut}; use percent_encoding::{AsciiSet, CONTROLS}; pub use http::header::*; @@ -16,11 +13,9 @@ mod into_pair; mod into_value; mod utils; -mod common; pub(crate) mod map; mod shared; -pub use self::common::*; #[doc(hidden)] pub use self::shared::*; @@ -41,34 +36,6 @@ pub trait Header: IntoHeaderValue { fn parse(msg: &T) -> Result; } -#[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`. impl From for HeaderMap { fn from(mut map: http::HeaderMap) -> HeaderMap { diff --git a/actix-http/src/header/common/content_encoding.rs b/actix-http/src/header/shared/content_encoding.rs similarity index 100% rename from actix-http/src/header/common/content_encoding.rs rename to actix-http/src/header/shared/content_encoding.rs diff --git a/actix-http/src/header/shared/httpdate.rs b/actix-http/src/header/shared/httpdate.rs index 72a225589..18278a6d8 100644 --- a/actix-http/src/header/shared/httpdate.rs +++ b/actix-http/src/header/shared/httpdate.rs @@ -1,18 +1,20 @@ -use std::fmt::{self, Display}; -use std::io::Write; -use std::str::FromStr; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::{ + fmt, + io::Write, + str::FromStr, + time::{SystemTime, UNIX_EPOCH}, +}; use bytes::buf::BufMut; use bytes::BytesMut; use http::header::{HeaderValue, InvalidHeaderValue}; -use time::{offset, OffsetDateTime, PrimitiveDateTime}; +use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset}; use crate::error::ParseError; use crate::header::IntoHeaderValue; 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)] 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 { fmt::Display::fmt(&self.0.format("%a, %d %b %Y %H:%M:%S GMT"), f) } } -impl From for HttpDate { - fn from(dt: OffsetDateTime) -> HttpDate { - HttpDate(dt) - } -} - impl From for HttpDate { fn from(sys: SystemTime) -> HttpDate { HttpDate(PrimitiveDateTime::from(sys).assume_utc()) @@ -54,7 +50,7 @@ impl IntoHeaderValue for HttpDate { wrt, "{}", self.0 - .to_offset(offset!(UTC)) + .to_offset(UtcOffset::UTC) .format("%a, %d %b %Y %H:%M:%S GMT") ) .unwrap(); diff --git a/actix-http/src/header/shared/mod.rs b/actix-http/src/header/shared/mod.rs index 72161e46b..b8f9173f9 100644 --- a/actix-http/src/header/shared/mod.rs +++ b/actix-http/src/header/shared/mod.rs @@ -1,15 +1,13 @@ //! Originally taken from `hyper::header::shared`. mod charset; -mod encoding; -mod entity; +mod content_encoding; mod extended; mod httpdate; mod quality_item; pub use self::charset::Charset; -pub use self::encoding::Encoding; -pub use self::entity::EntityTag; +pub use self::content_encoding::ContentEncoding; pub use self::extended::{parse_extended_value, ExtendedValue}; pub use self::httpdate::HttpDate; pub use self::quality_item::{q, qitem, Quality, QualityItem}; diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index 01a3b988a..240a0afa2 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -193,21 +193,69 @@ where #[cfg(test)] mod tests { - use super::super::encoding::*; 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 { + 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] fn test_quality_item_fmt_q_1() { + use Encoding::*; let x = qitem(Chunked); assert_eq!(format!("{}", x), "chunked"); } #[test] fn test_quality_item_fmt_q_0001() { + use Encoding::*; let x = QualityItem::new(Chunked, Quality(1)); assert_eq!(format!("{}", x), "chunked; q=0.001"); } #[test] fn test_quality_item_fmt_q_05() { + use Encoding::*; // Custom value let x = QualityItem { item: EncodingExt("identity".to_owned()), @@ -218,6 +266,7 @@ mod tests { #[test] fn test_quality_item_fmt_q_0() { + use Encoding::*; // Custom value let x = QualityItem { item: EncodingExt("identity".to_owned()), @@ -228,6 +277,7 @@ mod tests { #[test] fn test_quality_item_from_str1() { + use Encoding::*; let x: Result, _> = "chunked".parse(); assert_eq!( x.unwrap(), @@ -237,8 +287,10 @@ mod tests { } ); } + #[test] fn test_quality_item_from_str2() { + use Encoding::*; let x: Result, _> = "chunked; q=1".parse(); assert_eq!( x.unwrap(), @@ -248,8 +300,10 @@ mod tests { } ); } + #[test] fn test_quality_item_from_str3() { + use Encoding::*; let x: Result, _> = "gzip; q=0.5".parse(); assert_eq!( x.unwrap(), @@ -259,8 +313,10 @@ mod tests { } ); } + #[test] fn test_quality_item_from_str4() { + use Encoding::*; let x: Result, _> = "gzip; q=0.273".parse(); assert_eq!( x.unwrap(), @@ -270,16 +326,19 @@ mod tests { } ); } + #[test] fn test_quality_item_from_str5() { let x: Result, _> = "gzip; q=0.2739999".parse(); assert!(x.is_err()); } + #[test] fn test_quality_item_from_str6() { let x: Result, _> = "gzip; q=2".parse(); assert!(x.is_err()); } + #[test] fn test_quality_item_ordering() { let x: QualityItem = "gzip; q=0.5".parse().ok().unwrap(); diff --git a/actix-http/src/header/utils.rs b/actix-http/src/header/utils.rs index e232d462f..5e9652380 100644 --- a/actix-http/src/header/utils.rs +++ b/actix-http/src/header/utils.rs @@ -1,7 +1,6 @@ use std::{fmt, str::FromStr}; -use http::HeaderValue; - +use super::HeaderValue; use crate::{error::ParseError, header::HTTP_VALUE}; /// Reads a comma-delimited raw header into a Vec. diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 94f12bcc3..b27f477c9 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -359,10 +359,10 @@ impl ResponseBuilder { /// /// ``` /// # use actix_http::Response; - /// use actix_http::http::header::ContentType; + /// use actix_http::http::header; /// /// Response::Ok() - /// .insert_header(ContentType(mime::APPLICATION_JSON)) + /// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) /// .insert_header(("X-TEST", "value")) /// .finish(); /// ``` @@ -386,10 +386,10 @@ impl ResponseBuilder { /// /// ``` /// # use actix_http::Response; - /// use actix_http::http::header::ContentType; + /// use actix_http::http::header; /// /// 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", "value2")) /// .finish(); @@ -682,7 +682,7 @@ impl ResponseBuilder { }; if !contains { - self.insert_header(header::ContentType(mime::APPLICATION_JSON)); + self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); } self.body(Body::from(body)) @@ -1133,7 +1133,7 @@ mod tests { #[test] fn response_builder_header_insert_typed() { 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(); assert_eq!( @@ -1158,8 +1158,8 @@ mod tests { #[test] fn response_builder_header_append_typed() { let mut res = Response::Ok(); - res.append_header(header::ContentType(mime::APPLICATION_OCTET_STREAM)); - res.append_header(header::ContentType(mime::APPLICATION_JSON)); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); + res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); let res = res.finish(); let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 1a06cec3d..67a3ec42e 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -12,7 +12,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; use bytes::Bytes; -use futures_core::ready; +use futures_core::{future::LocalBoxFuture, ready}; use h2::server::{handshake, Handshake}; use pin_project::pin_project; @@ -107,7 +107,6 @@ where X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, - >::Future: 'static, { HttpService { expect, @@ -128,7 +127,6 @@ where U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, - )>>::Future: 'static, { HttpService { upgrade, @@ -150,23 +148,27 @@ where impl HttpService where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, + U: ServiceFactory< (Request, Framed), Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - )>>::Future: 'static, { /// Create simple tcp stream service pub fn tcp( @@ -188,31 +190,36 @@ where #[cfg(feature = "openssl")] mod openssl { - use super::*; use actix_service::ServiceFactoryExt; use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, TlsStream}; use actix_tls::accept::TlsError; + use super::*; + impl HttpService, S, B, X, U> where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, + U: ServiceFactory< (Request, Framed, h1::Codec>), Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - , h1::Codec>)>>::Future: 'static, { /// Create openssl based service pub fn openssl( @@ -261,25 +268,29 @@ mod rustls { impl HttpService, S, B, X, U> where S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, + U: ServiceFactory< (Request, Framed, h1::Codec>), Config = (), Response = (), >, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - , h1::Codec>)>>::Future: 'static, { - /// Create openssl based service + /// Create rustls based service pub fn rustls( self, mut config: ServerConfig, @@ -319,137 +330,121 @@ mod rustls { impl ServiceFactory<(T, Protocol, Option)> for HttpService where - T: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, + S: ServiceFactory, + S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, + B: MessageBody + 'static, + X: ServiceFactory, + X::Future: 'static, X::Error: Into, X::InitError: fmt::Debug, - >::Future: 'static, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, + U::Future: 'static, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - )>>::Future: 'static, { type Response = (); type Error = DispatchError; type Config = (); type Service = HttpServiceHandler; type InitError = (); - type Future = HttpServiceResponse; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - HttpServiceResponse { - fut: self.srv.new_service(()), - fut_ex: Some(self.expect.new_service(())), - fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), - expect: None, - upgrade: None, - on_connect_ext: self.on_connect_ext.clone(), - cfg: self.cfg.clone(), - _phantom: PhantomData, - } - } -} + let service = self.srv.new_service(()); + let expect = self.expect.new_service(()); + let upgrade = self.upgrade.as_ref().map(|s| s.new_service(())); + let on_connect_ext = self.on_connect_ext.clone(); + let cfg = self.cfg.clone(); -#[doc(hidden)] -#[pin_project] -pub struct HttpServiceResponse -where - S: ServiceFactory, - X: ServiceFactory, - U: ServiceFactory<(Request, Framed)>, -{ - #[pin] - fut: S::Future, - #[pin] - fut_ex: Option, - #[pin] - fut_upg: Option, - expect: Option, - upgrade: Option, - on_connect_ext: Option>>, - cfg: ServiceConfig, - _phantom: PhantomData, -} + Box::pin(async move { + let expect = expect + .await + .map_err(|e| log::error!("Init http expect service error: {:?}", e))?; -impl Future for HttpServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - >::Future: 'static, - B: MessageBody + 'static, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - >::Future: 'static, - U: ServiceFactory<(Request, Framed), Response = ()>, - U::Error: fmt::Display, - U::InitError: fmt::Debug, - )>>::Future: 'static, -{ - type Output = - Result, ()>; + 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, + }; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); + let service = service + .await + .map_err(|e| log::error!("Init http service error: {:?}", e))?; - 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(), + Ok(HttpServiceHandler::new( + cfg, service, - this.expect.take().unwrap(), - this.upgrade.take(), - this.on_connect_ext.clone(), - ) - })) + expect, + upgrade, + on_connect_ext, + )) + }) } } -/// `Service` implementation for HTTP transport +/// `Service` implementation for HTTP/1 and HTTP/2 transport pub struct HttpServiceHandler where S: Service, X: Service, U: Service<(Request, Framed)>, { - flow: Rc>, - cfg: ServiceConfig, - on_connect_ext: Option>>, + pub(super) flow: Rc>, + pub(super) cfg: ServiceConfig, + pub(super) on_connect_ext: Option>>, _phantom: PhantomData, } +impl HttpServiceHandler +where + S: Service, + S::Error: Into, + X: Service, + X::Error: Into, + U: Service<(Request, Framed)>, + U::Error: Into, +{ + pub(super) fn new( + cfg: ServiceConfig, + service: S, + expect: X, + upgrade: Option, + on_connect_ext: Option>>, + ) -> HttpServiceHandler { + HttpServiceHandler { + cfg, + on_connect_ext, + flow: HttpFlow::new(service, expect, upgrade), + _phantom: PhantomData, + } + } + + pub(super) fn _poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + 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. pub(super) struct HttpFlow { pub(super) service: S, @@ -467,34 +462,6 @@ impl HttpFlow { } } -impl HttpServiceHandler -where - S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, - X: Service, - X::Error: Into, - U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display, -{ - fn new( - cfg: ServiceConfig, - service: S, - expect: X, - upgrade: Option, - on_connect_ext: Option>>, - ) -> HttpServiceHandler { - HttpServiceHandler { - cfg, - on_connect_ext, - flow: HttpFlow::new(service, expect, upgrade), - _phantom: PhantomData, - } - } -} - impl Service<(T, Protocol, Option)> for HttpServiceHandler where @@ -514,47 +481,10 @@ where type Future = HttpServiceHandlerResponse; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - let ready = self - .flow - .expect - .poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", 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 - } + self._poll_ready(cx).map_err(|e| { + log::error!("HTTP service readiness error: {:?}", e); + DispatchError::Service(e) + }) } fn call( diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index 7be7cf637..576851139 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -4,7 +4,6 @@ use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoService, Service}; -use actix_utils::dispatcher::{Dispatcher as InnerDispatcher, DispatcherError}; use super::{Codec, Frame, Message}; @@ -15,7 +14,7 @@ where T: AsyncRead + AsyncWrite, { #[pin] - inner: InnerDispatcher, + inner: inner::Dispatcher, } impl Dispatcher @@ -27,13 +26,13 @@ where { pub fn new>(io: T, service: F) -> Self { Dispatcher { - inner: InnerDispatcher::new(Framed::new(io, Codec::new()), service), + inner: inner::Dispatcher::new(Framed::new(io, Codec::new()), service), } } pub fn with>(framed: Framed, service: F) -> Self { Dispatcher { - inner: InnerDispatcher::new(framed, service), + inner: inner::Dispatcher::new(framed, service), } } } @@ -45,9 +44,393 @@ where S::Future: 'static, S::Error: 'static, { - type Output = Result<(), DispatcherError>; + type Output = Result<(), inner::DispatcherError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 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 + where + U: Encoder + Decoder, + { + /// Inner service error. + Service(E), + + /// Frame encoding error. + Encoder(>::Error), + + /// Frame decoding error. + Decoder(::Error), + } + + impl From for DispatcherError + where + U: Encoder + Decoder, + { + fn from(err: E) -> Self { + DispatcherError::Service(err) + } + } + + impl fmt::Debug for DispatcherError + where + E: fmt::Debug, + U: Encoder + Decoder, + >::Error: fmt::Debug, + ::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 fmt::Display for DispatcherError + where + E: fmt::Display, + U: Encoder + Decoder, + >::Error: fmt::Debug, + ::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 ResponseError for DispatcherError + where + E: fmt::Debug + fmt::Display, + U: Encoder + Decoder, + >::Error: fmt::Debug, + ::Error: fmt::Debug, + { + } + + /// Message type wrapper for signalling end of message stream. + pub enum Message { + /// 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 + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead, + T: AsyncWrite, + U: Encoder, + U: Decoder, + I: 'static, + >::Error: fmt::Debug, + { + service: S, + state: State, + #[pin] + framed: Framed, + rx: mpsc::Receiver, S::Error>>, + tx: mpsc::Sender, S::Error>>, + } + } + + enum State + where + S: Service<::Item>, + U: Encoder + Decoder, + { + Processing, + Error(DispatcherError), + FramedError(DispatcherError), + FlushAndStop, + Stopping, + } + + impl State + where + S: Service<::Item>, + U: Encoder + Decoder, + { + fn take_error(&mut self) -> DispatcherError { + match mem::replace(self, State::Processing) { + State::Error(err) => err, + _ => panic!(), + } + } + + fn take_framed_error(&mut self) -> DispatcherError { + match mem::replace(self, State::Processing) { + State::FramedError(err) => err, + _ => panic!(), + } + } + } + + impl Dispatcher + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, + I: 'static, + ::Error: fmt::Debug, + >::Error: fmt::Debug, + { + /// Create new `Dispatcher`. + pub fn new(framed: Framed, service: F) -> Self + where + F: IntoService::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( + framed: Framed, + service: F, + rx: mpsc::Receiver, S::Error>>, + ) -> Self + where + F: IntoService::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, 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 { + &self.framed + } + + /// Get mutable reference to a framed instance wrapped by `Dispatcher` instance. + pub fn framed_mut(&mut self) -> &mut Framed { + &mut self.framed + } + + /// Read from framed object. + fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, + I: 'static, + >::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<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, + I: 'static, + >::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 Future for Dispatcher + where + S: Service<::Item, Response = I>, + S::Error: 'static, + S::Future: 'static, + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, + I: 'static, + >::Error: fmt::Debug, + ::Error: fmt::Debug, + { + type Output = Result<(), DispatcherError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + 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(())), + }; + } + } + } +} diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 758e39745..b5f8d54b9 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -3,11 +3,9 @@ use actix_http::{ }; use actix_http_test::test_server; use actix_service::ServiceFactoryExt; +use actix_utils::future; use bytes::Bytes; -use futures_util::{ - future::{self, ok}, - StreamExt as _, -}; +use futures_util::StreamExt as _; const STR: &str = "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() { let srv = test_server(move || { HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .tcp() .map(|_| ()) }) @@ -79,9 +77,9 @@ async fn test_with_query_parameter() { HttpService::build() .finish(|req: Request| { if req.uri().query().unwrap().contains("qp=") { - ok::<_, ()>(Response::Ok().finish()) + future::ok::<_, ()>(Response::Ok().finish()) } else { - ok::<_, ()>(Response::BadRequest().finish()) + future::ok::<_, ()>(Response::BadRequest().finish()) } }) .tcp() diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 49a68a60d..c9cfa7d18 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -11,12 +11,10 @@ use actix_http::HttpMessage; use actix_http::{body, Error, HttpService, Request, Response}; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; +use actix_utils::future::{err, ok, ready}; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::{ - future::{err, ok, ready}, - stream::{once, StreamExt as _}, -}; +use futures_util::stream::{once, StreamExt as _}; use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslMethod}, diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 7a3cb1473..5b8ba6582 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -8,10 +8,10 @@ use actix_http::http::{Method, StatusCode, Version}; use actix_http::{body, error, Error, HttpService, Request, Response}; use actix_http_test::test_server; use actix_service::{fn_factory_with_config, fn_service}; +use actix_utils::future::{err, ok}; use bytes::{Bytes, BytesMut}; use futures_core::Stream; -use futures_util::future::{self, err, ok}; use futures_util::stream::{once, StreamExt as _}; use rustls::{ internal::pemfile::{certs, pkcs8_private_keys}, @@ -51,7 +51,7 @@ fn tls_config() -> RustlsServerConfig { async fn test_h1() -> io::Result<()> { let srv = test_server(move || { HttpService::build() - .h1(|_| future::ok::<_, Error>(Response::Ok().finish())) + .h1(|_| ok::<_, Error>(Response::Ok().finish())) .rustls(tls_config()) }) .await; @@ -65,7 +65,7 @@ async fn test_h1() -> io::Result<()> { async fn test_h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() - .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) + .h2(|_| ok::<_, Error>(Response::Ok().finish())) .rustls(tls_config()) }) .await; @@ -82,7 +82,7 @@ async fn test_h1_1() -> io::Result<()> { .h1(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), Version::HTTP_11); - future::ok::<_, Error>(Response::Ok().finish()) + ok::<_, Error>(Response::Ok().finish()) }) .rustls(tls_config()) }) @@ -100,7 +100,7 @@ async fn test_h2_1() -> io::Result<()> { .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), Version::HTTP_2); - future::ok::<_, Error>(Response::Ok().finish()) + ok::<_, Error>(Response::Ok().finish()) }) .rustls(tls_config()) }) @@ -144,7 +144,7 @@ async fn test_h2_content_length() { StatusCode::OK, StatusCode::NOT_FOUND, ]; - future::ok::<_, ()>(Response::new(statuses[indx])) + ok::<_, ()>(Response::new(statuses[indx])) }) .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 ", )); } - future::ok::<_, ()>(config.body(data.clone())) + ok::<_, ()>(config.body(data.clone())) }) .rustls(tls_config()) }).await; @@ -252,7 +252,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ async fn test_h2_body2() { let mut srv = test_server(move || { HttpService::build() - .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) .rustls(tls_config()) }) .await; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 6d145400c..9084a597f 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -5,9 +5,10 @@ use std::{net, thread}; use actix_http_test::test_server; use actix_rt::time::sleep; use actix_service::fn_service; +use actix_utils::future::{err, ok, ready}; use bytes::Bytes; -use futures_util::future::{self, err, ok, ready, FutureExt}; use futures_util::stream::{once, StreamExt as _}; +use futures_util::FutureExt as _; use regex::Regex; use actix_http::HttpMessage; @@ -24,7 +25,7 @@ async fn test_h1() { .client_disconnect(1000) .h1(|req: Request| { assert!(req.peer_addr().is_some()); - future::ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::Ok().finish()) }) .tcp() }) @@ -44,7 +45,7 @@ async fn test_h1_2() { .finish(|req: Request| { assert!(req.peer_addr().is_some()); assert_eq!(req.version(), http::Version::HTTP_11); - future::ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::Ok().finish()) }) .tcp() }) @@ -65,7 +66,7 @@ async fn test_expect_continue() { err(error::ErrorPreconditionFailed("error")) } })) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .finish(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .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() }) .await; @@ -175,7 +176,7 @@ async fn test_slow_request() { let srv = test_server(|| { HttpService::build() .client_timeout(100) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .finish(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -191,7 +192,7 @@ async fn test_slow_request() { async fn test_http1_malformed_request() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -207,7 +208,7 @@ async fn test_http1_malformed_request() { async fn test_http1_keepalive() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -229,7 +230,7 @@ async fn test_http1_keepalive_timeout() { let srv = test_server(|| { HttpService::build() .keep_alive(1) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -250,7 +251,7 @@ async fn test_http1_keepalive_timeout() { async fn test_http1_keepalive_close() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -271,7 +272,7 @@ async fn test_http1_keepalive_close() { async fn test_http10_keepalive_default_close() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -291,7 +292,7 @@ async fn test_http10_keepalive_default_close() { async fn test_http10_keepalive() { let srv = test_server(|| { HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -319,7 +320,7 @@ async fn test_http1_keepalive_disabled() { let srv = test_server(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|_| ok::<_, ()>(Response::Ok().finish())) .tcp() }) .await; @@ -354,7 +355,7 @@ async fn test_content_length() { StatusCode::OK, StatusCode::NOT_FOUND, ]; - future::ok::<_, ()>(Response::new(statuses[indx])) + ok::<_, ()>(Response::new(statuses[indx])) }) .tcp() }) @@ -409,7 +410,7 @@ async fn test_h1_headers() { 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() }).await; @@ -645,7 +646,7 @@ async fn test_h1_response_http_error_handling() { async fn test_h1_service_error() { let mut srv = test_server(|| { HttpService::build() - .h1(|_| future::err::(error::ErrorBadRequest("error"))) + .h1(|_| err::(error::ErrorBadRequest("error"))) .tcp() }) .await; @@ -667,7 +668,7 @@ async fn test_h1_on_connect() { }) .h1(|req: Request| { assert!(req.extensions().contains::()); - future::ok::<_, ()>(Response::Ok().finish()) + ok::<_, ()>(Response::Ok().finish()) }) .tcp() }) diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 3b90b4e54..9a2e57711 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -3,17 +3,18 @@ use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::{body, h1, ws, Error, HttpService, Request, Response}; use actix_http_test::test_server; use actix_service::{fn_factory, Service}; -use actix_utils::dispatcher::Dispatcher; +use actix_utils::future; use bytes::Bytes; -use futures_util::future; -use futures_util::task::{Context, Poll}; use futures_util::{SinkExt as _, StreamExt as _}; +use crate::ws::Dispatcher; + struct WsService(Arc, Cell)>>); impl WsService { @@ -58,7 +59,7 @@ where .await .unwrap(); - Dispatcher::new(framed.replace_codec(ws::Codec::new()), service) + Dispatcher::with(framed.replace_codec(ws::Codec::new()), service) .await .map_err(|_| panic!()) }; diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index ce5bd57d7..cd50305cb 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.4.0-beta.4 - 2021-04-02 +* No notable changes. + + ## 0.4.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 9a3ea7bb5..77558fcc5 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.3" +version = "0.4.0-beta.4" authors = ["Nikolay Kim "] description = "Multipart form support for Actix Web" readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" 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" edition = "2018" @@ -16,19 +16,21 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.4", default-features = false } -actix-utils = "3.0.0-beta.2" +actix-web = { version = "4.0.0-beta.5", default-features = false } +actix-utils = "3.0.0-beta.4" bytes = "1" 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"] } +httparse = "1.3" +local-waker = "0.1" log = "0.4" mime = "0.3" twoway = "0.2" [dev-dependencies] -actix-rt = "2.1" -actix-http = "3.0.0-beta.4" +actix-rt = "2.2" +actix-http = "3.0.0-beta.5" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-multipart/README.md b/actix-multipart/README.md index e4a54ac47..8a4279a62 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![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) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![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) ## Documentation & Resources diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index bffbe8a1b..c87f8cc2d 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -1,21 +1,22 @@ //! Multipart payload support + +use actix_utils::future::{ready, Ready}; use actix_web::{dev::Payload, Error, FromRequest, HttpRequest}; -use futures_util::future::{ok, Ready}; use crate::server::Multipart; -/// Get request's payload as multipart stream +/// Get request's payload as multipart stream. /// /// Content-type: multipart/form-data; /// /// ## Server example /// /// ``` -/// use futures_util::stream::{Stream, StreamExt}; /// 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 { +/// async fn index(mut payload: Multipart) -> Result { /// // iterate over multipart stream /// while let Some(item) = payload.next().await { /// let mut field = item?; @@ -25,9 +26,9 @@ use crate::server::Multipart; /// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk?)); /// } /// } +/// /// Ok(HttpResponse::Ok().into()) /// } -/// # fn main() {} /// ``` impl FromRequest for Multipart { type Error = Error; @@ -36,9 +37,9 @@ impl FromRequest for Multipart { #[inline] 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()), Err(err) => Multipart::from_error(err), - }) + })) } } diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index d9ff3d574..b7d251537 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -1,4 +1,4 @@ -//! Multipart payload support +//! Multipart response payload support. use std::cell::{Cell, RefCell, RefMut}; use std::convert::TryFrom; @@ -8,12 +8,12 @@ use std::rc::Rc; use std::task::{Context, Poll}; 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::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; diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md new file mode 100644 index 000000000..70e643ba7 --- /dev/null +++ b/actix-test/CHANGES.md @@ -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 diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml new file mode 100644 index 000000000..aa82fd046 --- /dev/null +++ b/actix-test/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "actix-test" +version = "0.1.0-beta.1" +authors = [ + "Nikolay Kim ", + "Rob Ede ", +] +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 } diff --git a/actix-test/LICENSE-APACHE b/actix-test/LICENSE-APACHE new file mode 120000 index 000000000..965b606f3 --- /dev/null +++ b/actix-test/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/actix-test/LICENSE-MIT b/actix-test/LICENSE-MIT new file mode 120000 index 000000000..76219eb72 --- /dev/null +++ b/actix-test/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs new file mode 100644 index 000000000..bd86c27ad --- /dev/null +++ b/actix-test/src/lib.rs @@ -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 { +//! 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 { +/// 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(factory: F) -> TestServer +where + F: Fn() -> I + Send + Clone + 'static, + I: IntoServiceFactory, + S: ServiceFactory + 'static, + S::Error: Into + 'static, + S::InitError: fmt::Debug, + S::Response: Into> + 'static, + >::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 { +/// 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(cfg: TestServerConfig, factory: F) -> TestServer +where + F: Fn() -> I + Send + Clone + 'static, + I: IntoServiceFactory, + S: ServiceFactory + 'static, + S::Error: Into + 'static, + S::InitError: fmt::Debug, + S::Response: Into> + 'static, + >::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) -> ClientRequest { + self.client.get(self.url(path.as_ref()).as_str()) + } + + /// Create `POST` request. + pub fn post(&self, path: impl AsRef) -> ClientRequest { + self.client.post(self.url(path.as_ref()).as_str()) + } + + /// Create `HEAD` request. + pub fn head(&self, path: impl AsRef) -> ClientRequest { + self.client.head(self.url(path.as_ref()).as_str()) + } + + /// Create `PUT` request. + pub fn put(&self, path: impl AsRef) -> ClientRequest { + self.client.put(self.url(path.as_ref()).as_str()) + } + + /// Create `PATCH` request. + pub fn patch(&self, path: impl AsRef) -> ClientRequest { + self.client.patch(self.url(path.as_ref()).as_str()) + } + + /// Create `DELETE` request. + pub fn delete(&self, path: impl AsRef) -> ClientRequest { + self.client.delete(self.url(path.as_ref()).as_str()) + } + + /// Create `OPTIONS` request. + pub fn options(&self, path: impl AsRef) -> 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) -> ClientRequest { + self.client.request(method, path.as_ref()) + } + + pub async fn load_body( + &mut self, + mut response: ClientResponse, + ) -> Result + where + S: Stream> + 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, 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, 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() + } +} diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 80eef08c1..cd76b201e 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 4.0.0-beta.4 - 2021-04-02 +* No notable changes. + + ## 4.0.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 77663540c..a539227e4 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.3" +version = "4.0.0-beta.4" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" readme = "README.md" @@ -18,8 +18,8 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.3", default-features = false } actix-codec = "0.4.0-beta.1" -actix-http = "3.0.0-beta.4" -actix-web = { version = "4.0.0-beta.4", default-features = false } +actix-http = "3.0.0-beta.5" +actix-web = { version = "4.0.0-beta.5", default-features = false } bytes = "1" bytestring = "1" @@ -28,6 +28,9 @@ pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } [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" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 6d9d573d7..2dc779f10 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,11 +3,11 @@ > 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) -[![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) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![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) [![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) diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index 912480ae4..0a8e50b3e 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -1,8 +1,11 @@ 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 bytes::Bytes; -use futures_util::{SinkExt, StreamExt}; +use futures_util::{SinkExt as _, StreamExt as _}; struct Ws; @@ -24,7 +27,7 @@ impl StreamHandler> for Ws { #[actix_rt::test] async fn test_simple() { - let mut srv = test::start(|| { + let mut srv = actix_test::start(|| { App::new().service(web::resource("/").to( |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(); 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()))); +} diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index d8a189565..04c988392 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -19,8 +19,11 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-rt = "2.1" -actix-web = "4.0.0-beta.4" -futures-util = { version = "0.3.7", default-features = false } +actix-rt = "2.2" +actix-test = "0.1.0-beta.1" +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" rustversion = "1" diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 0cbb64ba5..7b95edba2 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,11 +1,12 @@ use std::future::Future; use std::task::{Context, Poll}; +use actix_utils::future; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::http::header::{HeaderName, HeaderValue}; -use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder}; +use actix_web::{http, web::Path, App, Error, HttpResponse, Responder}; 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' #[get("/config")] @@ -148,7 +149,7 @@ async fn get_wrap(_: Path) -> impl Responder { #[actix_rt::test] async fn test_params() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new() .service(get_param_test) .service(put_param_test) @@ -170,7 +171,7 @@ async fn test_params() { #[actix_rt::test] async fn test_body() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new() .service(post_test) .service(put_test) @@ -244,7 +245,7 @@ async fn test_body() { #[actix_rt::test] 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 response = request.send().await.unwrap(); @@ -253,7 +254,7 @@ async fn test_auto_async() { #[actix_rt::test] 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 response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/docstring-ok.rs b/actix-web-codegen/tests/trybuild/docstring-ok.rs index 2910976c7..4cf310be5 100644 --- a/actix-web-codegen/tests/trybuild/docstring-ok.rs +++ b/actix-web-codegen/tests/trybuild/docstring-ok.rs @@ -1,7 +1,7 @@ -use actix_web::{Responder, HttpResponse, App, test}; +use actix_web::{Responder, HttpResponse, App}; use actix_web_codegen::*; -/// Docstrings shouldn't break anything. +/// doc comments shouldn't break anything #[get("/")] async fn index() -> impl Responder { HttpResponse::Ok() @@ -9,7 +9,7 @@ async fn index() -> impl Responder { #[actix_web::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 response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.rs b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.rs index 9a38050f7..9322b4895 100644 --- a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.rs +++ b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.rs @@ -7,9 +7,9 @@ async fn index() -> String { #[actix_web::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 response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr index f3eda68af..abdc895d7 100644 --- a/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr @@ -5,7 +5,7 @@ error: HTTP method defined more than once: `GET` | ^^^^^ 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)); - | ^^^^^ not found in this scope +12 | let srv = actix_test::start(|| App::new().service(index)); + | ^^^^^ not found in this scope diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail.rs b/actix-web-codegen/tests/trybuild/route-missing-method-fail.rs index ce87a55a4..cd43c0669 100644 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail.rs +++ b/actix-web-codegen/tests/trybuild/route-missing-method-fail.rs @@ -7,9 +7,9 @@ async fn index() -> String { #[actix_web::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 response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr index 0518a61ed..0e16b5e27 100644 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr @@ -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) 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)); - | ^^^^^ not found in this scope +12 | let srv = actix_test::start(|| App::new().service(index)); + | ^^^^^ not found in this scope diff --git a/actix-web-codegen/tests/trybuild/route-ok.rs b/actix-web-codegen/tests/trybuild/route-ok.rs index c4f679604..e1082e88e 100644 --- a/actix-web-codegen/tests/trybuild/route-ok.rs +++ b/actix-web-codegen/tests/trybuild/route-ok.rs @@ -7,9 +7,9 @@ async fn index() -> String { #[actix_web::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 response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.rs b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.rs index 28cd1344c..1a50e01bc 100644 --- a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.rs +++ b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.rs @@ -7,9 +7,9 @@ async fn index() -> String { #[actix_web::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 response = request.send().await.unwrap(); diff --git a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr index 9d87f310b..a638a96a6 100644 --- a/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr +++ b/actix-web-codegen/tests/trybuild/route-unexpected-method-fail.stderr @@ -5,7 +5,7 @@ error: Unexpected HTTP method: `UNEXPECTED` | ^^^^^^^^^^^^ 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)); - | ^^^^^ not found in this scope +12 | let srv = actix_test::start(|| App::new().service(index)); + | ^^^^^ not found in this scope diff --git a/actix-web-codegen/tests/trybuild/simple.rs b/actix-web-codegen/tests/trybuild/simple.rs index 761b04905..8170edbfa 100644 --- a/actix-web-codegen/tests/trybuild/simple.rs +++ b/actix-web-codegen/tests/trybuild/simple.rs @@ -1,4 +1,4 @@ -use actix_web::{Responder, HttpResponse, App, test}; +use actix_web::{Responder, HttpResponse, App}; use actix_web_codegen::*; #[get("/config")] @@ -8,7 +8,7 @@ async fn config() -> impl Responder { #[actix_web::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 response = request.send().await.unwrap(); diff --git a/awc/CHANGES.md b/awc/CHANGES.md index d2cb7c009..eb008ff98 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,10 +1,22 @@ # Changes ## 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 -* `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 +[#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 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ec2e03a96..aebf73ef5 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "awc" -version = "3.0.0-beta.3" -authors = ["Nikolay Kim "] +version = "3.0.0-beta.4" +authors = [ + "Nikolay Kim ", + "fakeshadow <24548779@qq.com>", +] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" keywords = ["actix", "http", "framework", "async", "web"] @@ -46,12 +49,11 @@ trust-dns = ["actix-http/trust-dns"] [dependencies] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" -actix-http = "3.0.0-beta.4" +actix-http = "3.0.0-beta.5" actix-rt = { version = "2.1", default-features = false } base64 = "0.13" bytes = "1" -cfg-if = "1.0" derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false } 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"] } [dev-dependencies] -actix-web = { version = "4.0.0-beta.4", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.4", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] } -actix-utils = "3.0.0-beta.1" +actix-web = { version = "4.0.0-beta.5", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.5", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } +actix-utils = "3.0.0-beta.4" 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" env_logger = "0.8" @@ -80,3 +83,7 @@ flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } rcgen = "0.8" webpki = "0.21" + +[[example]] +name = "client" +required-features = ["rustls"] diff --git a/awc/README.md b/awc/README.md index 2e7dad320..a836a2497 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=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) -[![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) ## Documentation & Resources diff --git a/examples/client.rs b/awc/examples/client.rs similarity index 100% rename from examples/client.rs rename to awc/examples/client.rs diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 925d9ae2a..c594b4836 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -4,12 +4,11 @@ use std::net::IpAddr; use std::rc::Rc; use std::time::Duration; -use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::{ client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection}, 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 crate::connect::DefaultConnector; @@ -64,7 +63,7 @@ where S: Service, Response = TcpConnection, Error = TcpConnectError> + Clone + 'static, - Io: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, + Io: ActixStream + fmt::Debug + 'static, { /// Use custom connector service. pub fn connector(self, connector: Connector) -> ClientBuilder @@ -75,7 +74,7 @@ where Error = TcpConnectError, > + Clone + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, + Io1: ActixStream + fmt::Debug + 'static, { ClientBuilder { middleware: self.middleware, diff --git a/awc/src/lib.rs b/awc/src/lib.rs index c7bb68a8f..f1aecbd37 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -286,4 +286,12 @@ impl Client { } 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) + } } diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index 62ea1d0ac..ae09edf9c 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -283,10 +283,9 @@ fn rebuild_uri(res: &ClientResponse, org_uri: Uri) -> Result(HttpResponse::BadRequest()) @@ -323,7 +322,7 @@ mod tests { .connector(crate::Connector::new()) .finish(); - let srv = start(|| { + let srv = actix_test::start(|| { App::new() .service(web::resource("/").route(web::to(|| async { Ok::<_, Error>( diff --git a/awc/src/request.rs b/awc/src/request.rs index a847b09a3..6ecb64f81 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -21,15 +21,10 @@ use crate::frozen::FrozenClientRequest; use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest}; use crate::ClientConfig; -cfg_if::cfg_if! { - if #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] { - const HTTPS_ENCODING: &str = "br, gzip, deflate"; - } else if #[cfg(feature = "compress")] { - const HTTPS_ENCODING: &str = "br"; - } else { - const HTTPS_ENCODING: &str = "identity"; - } -} +#[cfg(feature = "compress")] +const HTTPS_ENCODING: &str = "br, gzip, deflate"; +#[cfg(not(feature = "compress"))] +const HTTPS_ENCODING: &str = "br"; /// An HTTP Client request builder /// @@ -194,12 +189,12 @@ impl ClientRequest { /// # #[actix_rt::main] /// # async fn main() { /// # use awc::Client; - /// use awc::http::header::ContentType; + /// use awc::http::header::CONTENT_TYPE; /// /// Client::new() /// .get("http://www.rust-lang.org") /// .insert_header(("X-TEST", "value")) - /// .insert_header(ContentType(mime::APPLICATION_JSON)); + /// .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON)); /// # } /// ``` pub fn append_header(mut self, header: H) -> Self @@ -521,11 +516,11 @@ impl ClientRequest { .unwrap_or(true); if https { - slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING)) + slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING)); } else { - #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] + #[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 { use std::time::SystemTime; + use actix_http::http::header::HttpDate; + use super::*; use crate::Client; @@ -569,7 +566,7 @@ mod tests { let req = Client::new() .put("/") .version(Version::HTTP_2) - .insert_header(header::Date(SystemTime::now().into())) + .insert_header((header::DATE, HttpDate::from(SystemTime::now()))) .content_type("plain/text") .append_header((header::SERVER, "awc")); diff --git a/awc/src/response.rs b/awc/src/response.rs index 994ddb761..27ba83af7 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -449,13 +449,13 @@ mod tests { #[actix_rt::test] 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() { PayloadError::UnknownLength => {} _ => 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() { PayloadError::Overflow => {} _ => unreachable!("error"), @@ -497,23 +497,23 @@ mod tests { assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let mut req = TestResponse::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/text"), - ) + )) .finish(); let json = JsonBody::<_, MyObject>::new(&mut req).await; assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let mut req = TestResponse::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + )) + .insert_header(( header::CONTENT_LENGTH, header::HeaderValue::from_static("10000"), - ) + )) .finish(); let json = JsonBody::<_, MyObject>::new(&mut req).limit(100).await; @@ -523,14 +523,14 @@ mod tests { )); let mut req = TestResponse::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + )) + .insert_header(( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), - ) + )) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .finish(); diff --git a/awc/src/test.rs b/awc/src/test.rs index 97bbb9c3d..8e95396b3 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,8 +1,6 @@ //! Test helpers for actix http client to use during testing. -use std::convert::TryFrom; - -use actix_http::http::header::{Header, IntoHeaderValue}; -use actix_http::http::{Error as HttpError, HeaderName, StatusCode, Version}; +use actix_http::http::header::IntoHeaderPair; +use actix_http::http::{StatusCode, Version}; #[cfg(feature = "cookies")] use actix_http::{ cookie::{Cookie, CookieJar}, @@ -34,13 +32,11 @@ impl Default for TestResponse { impl TestResponse { /// Create TestResponse and set header - pub fn with_header(key: K, value: V) -> Self + pub fn with_header(header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - Self::default().header(key, value) + Self::default().insert_header(header) } /// Set HTTP version of this response @@ -49,27 +45,26 @@ impl TestResponse { self } - /// Set a header - pub fn set(mut self, hdr: H) -> Self { - if let Ok(value) = hdr.try_into_value() { - self.head.headers.append(H::name(), value); + /// Insert a header + pub fn insert_header(mut self, header: H) -> Self + where + H: IntoHeaderPair, + { + if let Ok((key, value)) = header.try_into_header_pair() { + self.head.headers.insert(key, value); return self; } panic!("Can not set header"); } /// Append a header - pub fn header(mut self, key: K, value: V) -> Self + pub fn append_header(mut self, header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - if let Ok(key) = HeaderName::try_from(key) { - if let Ok(value) = value.try_into_value() { - self.head.headers.append(key, value); - return self; - } + if let Ok((key, value)) = header.try_into_header_pair() { + self.head.headers.append(key, value); + return self; } panic!("Can not create header"); } @@ -115,6 +110,8 @@ impl TestResponse { mod tests { use std::time::SystemTime; + use actix_http::http::header::HttpDate; + use super::*; use crate::{cookie, http::header}; @@ -122,7 +119,7 @@ mod tests { fn test_basics() { let res = TestResponse::default() .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()) .finish(); assert!(res.headers().contains_key(header::SET_COOKIE)); diff --git a/awc/src/ws.rs b/awc/src/ws.rs index df25b7289..8458d3e31 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -6,7 +6,7 @@ //! //! ```no_run //! use awc::{Client, ws}; -//! use futures_util::{sink::SinkExt, stream::StreamExt}; +//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _}; //! //! #[actix_rt::main] //! async fn main() { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 50d2b5eac..a393a6415 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -5,12 +5,13 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; +use actix_utils::future::ok; use brotli2::write::BrotliEncoder; use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use futures_util::{future::ok, stream}; +use futures_util::stream; use rand::Rng; use actix_http::{ @@ -23,7 +24,7 @@ use actix_web::{ dev::{AppConfig, BodyEncoding}, http::{header, Cookie}, middleware::Compress, - test, web, App, Error, HttpMessage, HttpRequest, HttpResponse, + web, App, Error, HttpMessage, HttpRequest, HttpResponse, }; 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] 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)))) }); @@ -77,7 +78,7 @@ async fn test_simple() { #[actix_rt::test] async fn test_json() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service( web::resource("/").route(web::to(|_: web::Json| HttpResponse::Ok())), ) @@ -93,7 +94,7 @@ async fn test_json() { #[actix_rt::test] async fn test_form() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to( |_: web::Form>| HttpResponse::Ok(), ))) @@ -112,7 +113,7 @@ async fn test_form() { #[actix_rt::test] async fn test_timeout() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { actix_rt::time::sleep(Duration::from_millis(200)).await; Ok::<_, Error>(HttpResponse::Ok().body(STR)) @@ -137,7 +138,7 @@ async fn test_timeout() { #[actix_rt::test] async fn test_timeout_override() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { actix_rt::time::sleep(Duration::from_millis(200)).await; Ok::<_, Error>(HttpResponse::Ok().body(STR)) @@ -159,9 +160,9 @@ async fn test_timeout_override() { #[actix_rt::test] 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 { Ok::<_, Error>( HttpResponse::Ok() @@ -443,7 +444,7 @@ async fn test_connection_wait_queue_force_close() { #[actix_rt::test] async fn test_with_query_parameter() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").to(|req: HttpRequest| { if req.query_string().contains("qp") { HttpResponse::Ok() @@ -463,7 +464,7 @@ async fn test_with_query_parameter() { #[actix_rt::test] async fn test_no_decompress() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new() .wrap(Compress::default()) .service(web::resource("/").route(web::to(|| { @@ -507,7 +508,7 @@ async fn test_no_decompress() { #[actix_rt::test] async fn test_client_gzip_encoding() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| { let mut e = GzEncoder::new(Vec::new(), Compression::default()); e.write_all(STR.as_ref()).unwrap(); @@ -530,7 +531,7 @@ async fn test_client_gzip_encoding() { #[actix_rt::test] async fn test_client_gzip_encoding_large() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| { let mut e = GzEncoder::new(Vec::new(), Compression::default()); e.write_all(STR.repeat(10).as_ref()).unwrap(); @@ -559,7 +560,7 @@ async fn test_client_gzip_encoding_large_random() { .map(char::from) .collect::(); - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|data: Bytes| { let mut e = GzEncoder::new(Vec::new(), Compression::default()); e.write_all(&data).unwrap(); @@ -581,7 +582,7 @@ async fn test_client_gzip_encoding_large_random() { #[actix_rt::test] 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| { let mut e = BrotliEncoder::new(Vec::new(), 5); e.write_all(&data).unwrap(); @@ -609,7 +610,7 @@ async fn test_client_brotli_encoding_large_random() { .map(char::from) .collect::(); - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|data: Bytes| { let mut e = BrotliEncoder::new(Vec::new(), 5); e.write_all(&data).unwrap(); @@ -632,7 +633,7 @@ async fn test_client_brotli_encoding_large_random() { #[actix_rt::test] async fn test_client_deflate_encoding() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|body: Bytes| { HttpResponse::Ok() .encoding(http::ContentEncoding::Br) @@ -657,7 +658,7 @@ async fn test_client_deflate_encoding_large_random() { .take(70_000) .collect::(); - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|body: Bytes| { HttpResponse::Ok() .encoding(http::ContentEncoding::Br) @@ -676,7 +677,7 @@ async fn test_client_deflate_encoding_large_random() { #[actix_rt::test] async fn test_client_streaming_explicit() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|body: web::Payload| { HttpResponse::Ok() .encoding(http::ContentEncoding::Identity) @@ -697,7 +698,7 @@ async fn test_client_streaming_explicit() { #[actix_rt::test] async fn test_body_streaming_implicit() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|| { let body = stream::once(async { 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 cookie2b = cookie2.clone(); - let srv = test::start(move || { + let srv = actix_test::start(move || { let cookie1 = cookie1b.clone(); let cookie2 = cookie2b.clone(); @@ -789,8 +790,7 @@ async fn test_client_cookie_handling() { #[actix_rt::test] async fn client_unread_response() { - let addr = test::unused_addr(); - + let addr = actix_test::unused_addr(); let lst = std::net::TcpListener::bind(addr).unwrap(); std::thread::spawn(move || { @@ -819,7 +819,7 @@ async fn client_unread_response() { #[actix_rt::test] async fn client_basic_auth() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().route( "/", web::to(|req: HttpRequest| { @@ -847,7 +847,7 @@ async fn client_basic_auth() { #[actix_rt::test] async fn client_bearer_auth() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().route( "/", web::to(|req: HttpRequest| { @@ -877,7 +877,7 @@ async fn client_bearer_auth() { async fn test_local_address() { 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( move |req: HttpRequest| async move { assert_eq!(req.peer_addr().unwrap().ip(), ip); diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 464edfe89..080eaf792 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -13,8 +13,8 @@ use std::{ use actix_http::HttpService; use actix_http_test::test_server; use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; +use actix_utils::future::ok; 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::{ClientConfig, NoClientAuth, ServerConfig}; diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index 3079aaf5e..502223401 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -8,9 +8,9 @@ use std::sync::Arc; use actix_http::HttpService; use actix_http_test::test_server; use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; +use actix_utils::future::ok; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; -use futures_util::future::ok; use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslConnector, SslMethod, SslVerifyMode}, diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 1b3f780dc..3f19ac4e8 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -3,9 +3,9 @@ use std::io; use actix_codec::Framed; use actix_http::{body::BodySize, h1, ws, Error, HttpService, Request, Response}; use actix_http_test::test_server; +use actix_utils::future::ok; use bytes::Bytes; -use futures_util::future::ok; -use futures_util::{SinkExt, StreamExt}; +use futures_util::{SinkExt as _, StreamExt as _}; async fn ws_service(req: ws::Frame) -> Result { match req { diff --git a/benches/responder.rs b/benches/responder.rs index 8cfdbd3ea..0dfc8cd18 100644 --- a/benches/responder.rs +++ b/benches/responder.rs @@ -1,12 +1,12 @@ -use std::future::Future; -use std::time::Instant; +use std::{future::Future, time::Instant}; use actix_http::Response; +use actix_utils::future::{ready, Ready}; use actix_web::http::StatusCode; use actix_web::test::TestRequest; use actix_web::{error, Error, HttpRequest, HttpResponse, Responder}; 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. trait FutureResponder { @@ -79,7 +79,7 @@ fn future_responder(c: &mut Criterion) { .await }); - let futs = futures_util::future::join_all(futs); + let futs = join_all(futs); let start = Instant::now(); diff --git a/benches/server.rs b/benches/server.rs index 9dd540a73..c6695817f 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -32,7 +32,7 @@ fn bench_async_burst(c: &mut Criterion) { let rt = actix_rt::System::new(); let srv = rt.block_on(async { - test::start(|| { + actix_test::start(|| { App::new() .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }) diff --git a/codecov.yml b/codecov.yml index e45672bfc..d80835c7f 100644 --- a/codecov.yml +++ b/codecov.yml @@ -11,6 +11,5 @@ coverage: ignore: # ignore code coverage on following paths - "**/tests" - - "test-server" - "**/benches" - "**/examples" diff --git a/docs/graphs/net-only.dot b/docs/graphs/net-only.dot index 9488f3fe7..bee0185ab 100644 --- a/docs/graphs/net-only.dot +++ b/docs/graphs/net-only.dot @@ -1,21 +1,37 @@ digraph { + rankdir=TB + subgraph cluster_net { - label="actix/actix-net"; - "actix-codec" - "actix-macros" - "actix-rt" - "actix-server" - "actix-service" - "actix-threadpool" - "actix-tls" - "actix-tracing" - "actix-utils" - "actix-router" + label="actix-net" + "actix-codec" "actix-macros" "actix-rt" "actix-server" "actix-service" + "actix-tls" "actix-tracing" "actix-utils" "actix-router" + } + + subgraph cluster_other { + label="other actix owned crates" + { rank=same; "local-channel" "local-waker" "bytestring" } } - "actix-utils" -> { "actix-service" "actix-rt" "actix-codec" } + 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-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } - "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } - "actix-rt" -> { "actix-macros" "actix-threadpool" } + "actix-tls" -> { "tokio-util" }[color="#009900"] + "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" } diff --git a/docs/graphs/web-only.dot b/docs/graphs/web-only.dot index 6f8292a3a..b0decd818 100644 --- a/docs/graphs/web-only.dot +++ b/docs/graphs/web-only.dot @@ -9,12 +9,14 @@ digraph { "actix-web-actors" "actix-web-codegen" "actix-http-test" + "actix-test" } - "actix-web" -> { "actix-web-codegen" "actix-http" "awc" } + "actix-web" -> { "actix-web-codegen" "actix-http" } "awc" -> { "actix-http" } "actix-web-actors" -> { "actix" "actix-web" "actix-http" } "actix-multipart" -> { "actix-web" } "actix-files" -> { "actix-web" } "actix-http-test" -> { "awc" } + "actix-test" -> { "actix-web" "awc" "actix-http-test" } } diff --git a/src/app.rs b/src/app.rs index f2c6bce8a..357d45eeb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -10,7 +10,7 @@ use actix_service::boxed::{self, BoxServiceFactory}; use actix_service::{ 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::config::ServiceConfig; @@ -465,8 +465,8 @@ where #[cfg(test)] mod tests { use actix_service::Service; + use actix_utils::future::{err, ok}; use bytes::Bytes; - use futures_util::future::{err, ok}; use super::*; use crate::http::{header, HeaderValue, Method, StatusCode}; diff --git a/src/config.rs b/src/config.rs index cd14eb4cc..4bd76f2b7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -103,8 +103,8 @@ impl AppService { } } -/// Application connection config -#[derive(Clone)] +/// Application connection config. +#[derive(Debug, Clone)] pub struct AppConfig { secure: bool, host: String, @@ -112,10 +112,15 @@ pub struct 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 } } + #[doc(hidden)] + pub fn __priv_test_new(secure: bool, host: String, addr: SocketAddr) -> Self { + AppConfig::new(secure, host, addr) + } + /// Server host name. /// /// Host name is used by application router as a hostname for url generation. @@ -142,8 +147,8 @@ impl Default for AppConfig { fn default() -> Self { AppConfig::new( false, - "127.0.0.1:8080".parse().unwrap(), "localhost:8080".to_owned(), + "127.0.0.1:8080".parse().unwrap(), ) } } diff --git a/src/data.rs b/src/data.rs index 56ecdb8ae..bd9b88301 100644 --- a/src/data.rs +++ b/src/data.rs @@ -4,7 +4,8 @@ use std::sync::Arc; use actix_http::error::{Error, ErrorInternalServerError}; 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 crate::dev::Payload; @@ -147,13 +148,13 @@ impl DataFactory for Data { #[cfg(test)] mod tests { - use actix_service::Service; - use std::sync::atomic::{AtomicUsize, Ordering}; - use super::*; - use crate::http::StatusCode; - use crate::test::{self, init_service, TestRequest}; - use crate::{web, App, HttpResponse}; + use crate::{ + dev::Service, + http::StatusCode, + test::{init_service, TestRequest}, + web, App, HttpResponse, + }; #[actix_rt::test] async fn test_data_extractor() { @@ -269,49 +270,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - #[actix_rt::test] - async fn test_data_drop() { - struct TestData(Arc); - - impl TestData { - fn new(inner: Arc) -> 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| async { "ok" })) - }); - - assert!(srv.get("/").send().await.unwrap().status().is_success()); - srv.stop().await; - - assert_eq!(num.load(Ordering::SeqCst), 0); - } - #[actix_rt::test] async fn test_data_from_arc() { let data_new = Data::new(String::from("test-123")); diff --git a/src/extract.rs b/src/extract.rs index 8851481e3..80f2384a0 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -6,10 +6,8 @@ use std::{ task::{Context, Poll}, }; -use futures_util::{ - future::{ready, Ready}, - ready, -}; +use actix_utils::future::{ready, Ready}; +use futures_core::ready; use crate::{dev::Payload, Error, HttpRequest}; diff --git a/src/handler.rs b/src/handler.rs index 7e3c5f47e..e005a96a6 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -5,8 +5,8 @@ use std::task::{Context, Poll}; use actix_http::{Error, Response}; use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ready, Ready}; -use futures_util::ready; +use actix_utils::future::{ready, Ready}; +use futures_core::ready; use pin_project::pin_project; use crate::extract::FromRequest; diff --git a/actix-http/src/header/common/accept.rs b/src/http/header/accept.rs similarity index 93% rename from actix-http/src/header/common/accept.rs rename to src/http/header/accept.rs index 775da3394..1ec94e353 100644 --- a/actix-http/src/header/common/accept.rs +++ b/src/http/header/accept.rs @@ -2,10 +2,10 @@ use std::cmp::Ordering; use mime::Mime; -use crate::header::{qitem, QualityItem}; +use super::{qitem, QualityItem}; use crate::http::header; -header! { +crate::header! { /// `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 @@ -33,10 +33,10 @@ header! { /// /// # Examples /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{Accept, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{Accept, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ /// qitem(mime::TEXT_HTML), @@ -45,10 +45,10 @@ header! { /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{Accept, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{Accept, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ /// qitem(mime::APPLICATION_JSON), @@ -57,10 +57,10 @@ header! { /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{Accept, QualityItem, q, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{Accept, QualityItem, q, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Accept(vec![ /// qitem(mime::TEXT_HTML), @@ -116,8 +116,8 @@ header! { #[test] fn test_fuzzing1() { - use crate::test::TestRequest; - let req = TestRequest::default().insert_header((crate::header::ACCEPT, "chunk#;e")).finish(); + use actix_http::test::TestRequest; + let req = TestRequest::default().insert_header((crate::http::header::ACCEPT, "chunk#;e")).finish(); let header = Accept::parse(&req); assert!(header.is_ok()); } @@ -213,7 +213,7 @@ impl Accept { #[cfg(test)] mod tests { use super::*; - use crate::header::q; + use crate::http::header::q; #[test] fn test_mime_precedence() { diff --git a/actix-http/src/header/common/accept_charset.rs b/src/http/header/accept_charset.rs similarity index 73% rename from actix-http/src/header/common/accept_charset.rs rename to src/http/header/accept_charset.rs index db530a8bc..9932ac57a 100644 --- a/actix-http/src/header/common/accept_charset.rs +++ b/src/http/header/accept_charset.rs @@ -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 /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) /// @@ -22,20 +22,20 @@ header! { /// /// # Examples /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) /// ); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptCharset(vec![ /// QualityItem::new(Charset::Us_Ascii, q(900)), @@ -45,10 +45,10 @@ header! { /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) /// ); diff --git a/actix-http/src/header/common/accept_encoding.rs b/src/http/header/accept_encoding.rs similarity index 76% rename from actix-http/src/header/common/accept_encoding.rs rename to src/http/header/accept_encoding.rs index c90f529bc..e59351708 100644 --- a/actix-http/src/header/common/accept_encoding.rs +++ b/src/http/header/accept_encoding.rs @@ -26,18 +26,20 @@ header! { /// /// # 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(); - /// headers.set( + /// let mut builder = HttpResponse::new(); + /// builder.insert_header( /// 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(); - /// headers.set( + /// let mut builder = HttpResponse::new(); + /// builder.insert_header( /// AcceptEncoding(vec![ /// qitem(Encoding::Chunked), /// 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(); - /// headers.set( + /// let mut builder = HttpResponse::new(); + /// builder.insert_header( /// AcceptEncoding(vec![ /// qitem(Encoding::Chunked), /// QualityItem::new(Encoding::Gzip, q(600)), diff --git a/actix-http/src/header/common/accept_language.rs b/src/http/header/accept_language.rs similarity index 82% rename from actix-http/src/header/common/accept_language.rs rename to src/http/header/accept_language.rs index a7ad00863..2963844af 100644 --- a/actix-http/src/header/common/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -1,7 +1,7 @@ -use crate::header::{QualityItem, ACCEPT_LANGUAGE}; +use super::{QualityItem, ACCEPT_LANGUAGE}; use language_tags::LanguageTag; -header! { +crate::header! { /// `Accept-Language` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) /// @@ -24,10 +24,10 @@ header! { /// /// ``` /// use language_tags::langtag; - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let mut langtag: LanguageTag = Default::default(); /// langtag.language = Some("en".to_owned()); /// langtag.region = Some("US".to_owned()); @@ -40,10 +40,10 @@ header! { /// /// ``` /// use language_tags::langtag; - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// AcceptLanguage(vec![ /// qitem(langtag!(da)), diff --git a/actix-http/src/header/common/allow.rs b/src/http/header/allow.rs similarity index 83% rename from actix-http/src/header/common/allow.rs rename to src/http/header/allow.rs index 06b1efedc..e1f2bb4b6 100644 --- a/actix-http/src/header/common/allow.rs +++ b/src/http/header/allow.rs @@ -1,7 +1,7 @@ -use http::header; -use http::Method; +use actix_http::http::Method; +use crate::http::header; -header! { +crate::header! { /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) /// /// The `Allow` header field lists the set of methods advertised as @@ -23,20 +23,20 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::{header::Allow, Method}; + /// use actix_web::HttpResponse; + /// use actix_web::http::{header::Allow, Method}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Allow(vec![Method::GET]) /// ); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::{header::Allow, Method}; + /// use actix_web::HttpResponse; + /// use actix_web::http::{header::Allow, Method}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Allow(vec![ /// Method::GET, diff --git a/actix-http/src/header/common/cache_control.rs b/src/http/header/cache_control.rs similarity index 95% rename from actix-http/src/header/common/cache_control.rs rename to src/http/header/cache_control.rs index b19823d22..6f020a931 100644 --- a/actix-http/src/header/common/cache_control.rs +++ b/src/http/header/cache_control.rs @@ -1,12 +1,12 @@ use std::fmt::{self, Write}; use std::str::FromStr; -use http::header; - -use crate::header::{ +use super::{ fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer, }; +use crate::http::header; + /// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) /// /// The `Cache-Control` header field is used to specify directives for @@ -29,18 +29,18 @@ use crate::header::{ /// /// # Examples /// ``` -/// use actix_http::Response; -/// use actix_http::http::header::{CacheControl, CacheDirective}; +/// use actix_web::HttpResponse; +/// use actix_web::http::header::{CacheControl, CacheDirective}; /// -/// let mut builder = Response::Ok(); +/// let mut builder = HttpResponse::Ok(); /// builder.insert_header(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); /// ``` /// /// ``` -/// use actix_http::Response; -/// use actix_http::http::header::{CacheControl, CacheDirective}; +/// use actix_web::HttpResponse; +/// use actix_web::http::header::{CacheControl, CacheDirective}; /// -/// let mut builder = Response::Ok(); +/// let mut builder = HttpResponse::Ok(); /// builder.insert_header(CacheControl(vec![ /// CacheDirective::NoCache, /// CacheDirective::Private, @@ -191,8 +191,8 @@ impl FromStr for CacheDirective { #[cfg(test)] mod tests { use super::*; - use crate::header::Header; - use crate::test::TestRequest; + use crate::http::header::Header; + use actix_http::test::TestRequest; #[test] fn test_parse_multiple_headers() { diff --git a/actix-http/src/header/common/content_disposition.rs b/src/http/header/content_disposition.rs similarity index 99% rename from actix-http/src/header/common/content_disposition.rs rename to src/http/header/content_disposition.rs index 6076d033c..3dea0997b 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/src/http/header/content_disposition.rs @@ -10,7 +10,8 @@ use once_cell::sync::Lazy; use regex::Regex; use std::fmt::{self, Write}; -use crate::header::{self, ExtendedValue, Header, IntoHeaderValue, Writer}; +use crate::http::header; +use super::{ExtendedValue, Header, IntoHeaderValue, Writer}; /// Split at the index of the first `needle` if it exists or at the end. fn split_once(haystack: &str, needle: char) -> (&str, &str) { @@ -63,7 +64,7 @@ impl<'a> From<&'a str> for DispositionType { /// /// # Examples /// ``` -/// use actix_http::http::header::DispositionParam; +/// use actix_web::http::header::DispositionParam; /// /// let param = DispositionParam::Filename(String::from("sample.txt")); /// assert!(param.is_filename()); @@ -240,7 +241,7 @@ impl DispositionParam { /// # Example /// /// ``` -/// use actix_http::http::header::{ +/// use actix_web::http::header::{ /// Charset, ContentDisposition, DispositionParam, DispositionType, /// ExtendedValue, /// }; @@ -554,8 +555,8 @@ impl fmt::Display for ContentDisposition { #[cfg(test)] mod tests { use super::{ContentDisposition, DispositionParam, DispositionType}; - use crate::header::shared::Charset; - use crate::header::{ExtendedValue, HeaderValue}; + use crate::http::header::Charset; + use crate::http::header::{ExtendedValue, HeaderValue}; #[test] fn test_from_raw_basic() { diff --git a/actix-http/src/header/common/content_language.rs b/src/http/header/content_language.rs similarity index 77% rename from actix-http/src/header/common/content_language.rs rename to src/http/header/content_language.rs index e9be67a1b..5dd8f72a5 100644 --- a/actix-http/src/header/common/content_language.rs +++ b/src/http/header/content_language.rs @@ -1,7 +1,7 @@ -use crate::header::{QualityItem, CONTENT_LANGUAGE}; +use super::{QualityItem, CONTENT_LANGUAGE}; use language_tags::LanguageTag; -header! { +crate::header! { /// `Content-Language` header, defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) /// @@ -25,10 +25,10 @@ header! { /// /// ``` /// use language_tags::langtag; - /// use actix_http::Response; - /// use actix_http::http::header::{ContentLanguage, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{ContentLanguage, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentLanguage(vec![ /// qitem(langtag!(en)), @@ -38,10 +38,10 @@ header! { /// /// ``` /// use language_tags::langtag; - /// use actix_http::Response; - /// use actix_http::http::header::{ContentLanguage, qitem}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{ContentLanguage, qitem}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentLanguage(vec![ /// qitem(langtag!(da)), diff --git a/actix-http/src/header/common/content_range.rs b/src/http/header/content_range.rs similarity index 99% rename from actix-http/src/header/common/content_range.rs rename to src/http/header/content_range.rs index 8b7552377..dfcf24251 100644 --- a/actix-http/src/header/common/content_range.rs +++ b/src/http/header/content_range.rs @@ -2,11 +2,11 @@ use std::fmt::{self, Display, Write}; use std::str::FromStr; use crate::error::ParseError; -use crate::header::{ +use super::{ HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE, }; -header! { +crate::header! { /// `Content-Range` header, defined in /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] diff --git a/actix-http/src/header/common/content_type.rs b/src/http/header/content_type.rs similarity index 91% rename from actix-http/src/header/common/content_type.rs rename to src/http/header/content_type.rs index ac5c7e5b8..a85e64ba9 100644 --- a/actix-http/src/header/common/content_type.rs +++ b/src/http/header/content_type.rs @@ -1,7 +1,7 @@ -use crate::header::CONTENT_TYPE; +use super::CONTENT_TYPE; use mime::Mime; -header! { +crate::header! { /// `Content-Type` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) /// @@ -31,20 +31,20 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::ContentType; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::ContentType; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentType::json() /// ); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::ContentType; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::ContentType; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ContentType(mime::TEXT_HTML) /// ); diff --git a/actix-http/src/header/common/date.rs b/src/http/header/date.rs similarity index 83% rename from actix-http/src/header/common/date.rs rename to src/http/header/date.rs index e5ace95e6..faceefb4f 100644 --- a/actix-http/src/header/common/date.rs +++ b/src/http/header/date.rs @@ -1,7 +1,7 @@ -use crate::header::{HttpDate, DATE}; +use super::{HttpDate, DATE}; use std::time::SystemTime; -header! { +crate::header! { /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) /// /// The `Date` header field represents the date and time at which the @@ -21,10 +21,10 @@ header! { /// /// ``` /// use std::time::SystemTime; - /// use actix_http::Response; - /// use actix_http::http::header::Date; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::Date; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// Date(SystemTime::now().into()) /// ); diff --git a/actix-http/src/header/shared/encoding.rs b/src/http/header/encoding.rs similarity index 100% rename from actix-http/src/header/shared/encoding.rs rename to src/http/header/encoding.rs diff --git a/actix-http/src/header/shared/entity.rs b/src/http/header/entity.rs similarity index 99% rename from actix-http/src/header/shared/entity.rs rename to src/http/header/entity.rs index 2505216f2..5073ed692 100644 --- a/actix-http/src/header/shared/entity.rs +++ b/src/http/header/entity.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Display, Write}; use std::str::FromStr; -use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; +use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; /// check that each char in the slice is either: /// 1. `%x21`, or diff --git a/actix-http/src/header/common/etag.rs b/src/http/header/etag.rs similarity index 90% rename from actix-http/src/header/common/etag.rs rename to src/http/header/etag.rs index 4c1e8d262..8972564d0 100644 --- a/actix-http/src/header/common/etag.rs +++ b/src/http/header/etag.rs @@ -1,6 +1,6 @@ -use crate::header::{EntityTag, ETAG}; +use super::{EntityTag, ETAG}; -header! { +crate::header! { /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) /// /// The `ETag` header field in a response provides the current entity-tag @@ -28,20 +28,20 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{ETag, EntityTag}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{ETag, EntityTag}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ETag(EntityTag::new(false, "xyzzy".to_owned())) /// ); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{ETag, EntityTag}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{ETag, EntityTag}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// ETag(EntityTag::new(true, "xyzzy".to_owned())) /// ); diff --git a/actix-http/src/header/common/expires.rs b/src/http/header/expires.rs similarity index 84% rename from actix-http/src/header/common/expires.rs rename to src/http/header/expires.rs index 79563955d..1c306cae0 100644 --- a/actix-http/src/header/common/expires.rs +++ b/src/http/header/expires.rs @@ -1,6 +1,6 @@ -use crate::header::{HttpDate, EXPIRES}; +use super::{HttpDate, EXPIRES}; -header! { +crate::header! { /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) /// /// The `Expires` header field gives the date/time after which the @@ -23,10 +23,10 @@ header! { /// /// ``` /// use std::time::{SystemTime, Duration}; - /// use actix_http::Response; - /// use actix_http::http::header::Expires; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::Expires; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// Expires(expiration.into()) diff --git a/actix-http/src/header/common/if_match.rs b/src/http/header/if_match.rs similarity index 86% rename from actix-http/src/header/common/if_match.rs rename to src/http/header/if_match.rs index db255e91a..80699e39c 100644 --- a/actix-http/src/header/common/if_match.rs +++ b/src/http/header/if_match.rs @@ -1,6 +1,6 @@ -use crate::header::{EntityTag, IF_MATCH}; +use super::{EntityTag, IF_MATCH}; -header! { +crate::header! { /// `If-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) /// @@ -30,18 +30,18 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::IfMatch; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::IfMatch; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header(IfMatch::Any); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{IfMatch, EntityTag}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{IfMatch, EntityTag}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// IfMatch::Items(vec![ /// EntityTag::new(false, "xyzzy".to_owned()), diff --git a/actix-http/src/header/common/if_modified_since.rs b/src/http/header/if_modified_since.rs similarity index 84% rename from actix-http/src/header/common/if_modified_since.rs rename to src/http/header/if_modified_since.rs index 99c7e441d..d777e0c5c 100644 --- a/actix-http/src/header/common/if_modified_since.rs +++ b/src/http/header/if_modified_since.rs @@ -1,6 +1,6 @@ -use crate::header::{HttpDate, IF_MODIFIED_SINCE}; +use super::{HttpDate, IF_MODIFIED_SINCE}; -header! { +crate::header! { /// `If-Modified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) /// @@ -23,10 +23,10 @@ header! { /// /// ``` /// use std::time::{SystemTime, Duration}; - /// use actix_http::Response; - /// use actix_http::http::header::IfModifiedSince; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::IfModifiedSince; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// IfModifiedSince(modified.into()) diff --git a/actix-http/src/header/common/if_none_match.rs b/src/http/header/if_none_match.rs similarity index 86% rename from actix-http/src/header/common/if_none_match.rs rename to src/http/header/if_none_match.rs index 464caf1ae..a5c06b374 100644 --- a/actix-http/src/header/common/if_none_match.rs +++ b/src/http/header/if_none_match.rs @@ -1,6 +1,6 @@ -use crate::header::{EntityTag, IF_NONE_MATCH}; +use super::{EntityTag, IF_NONE_MATCH}; -header! { +crate::header! { /// `If-None-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) /// @@ -32,18 +32,18 @@ header! { /// # Examples /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::IfNoneMatch; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::IfNoneMatch; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header(IfNoneMatch::Any); /// ``` /// /// ``` - /// use actix_http::Response; - /// use actix_http::http::header::{IfNoneMatch, EntityTag}; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::{IfNoneMatch, EntityTag}; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// IfNoneMatch::Items(vec![ /// EntityTag::new(false, "xyzzy".to_owned()), @@ -66,8 +66,8 @@ header! { #[cfg(test)] mod tests { use super::IfNoneMatch; - use crate::header::{EntityTag, Header, IF_NONE_MATCH}; - use crate::test::TestRequest; + use crate::http::header::{EntityTag, Header, IF_NONE_MATCH}; + use actix_http::test::TestRequest; #[test] fn test_if_none_match() { diff --git a/actix-http/src/header/common/if_range.rs b/src/http/header/if_range.rs similarity index 89% rename from actix-http/src/header/common/if_range.rs rename to src/http/header/if_range.rs index 0a5749505..f34332f22 100644 --- a/actix-http/src/header/common/if_range.rs +++ b/src/http/header/if_range.rs @@ -1,8 +1,9 @@ use std::fmt::{self, Display, Write}; +use crate::http::header; use crate::error::ParseError; -use crate::header::{ - self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, +use super::{ + from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, InvalidHeaderValue, Writer, }; use crate::HttpMessage; @@ -36,10 +37,10 @@ use crate::HttpMessage; /// # Examples /// /// ``` -/// use actix_http::Response; -/// use actix_http::http::header::{EntityTag, IfRange}; +/// use actix_web::HttpResponse; +/// use actix_web::http::header::{EntityTag, IfRange}; /// -/// let mut builder = Response::Ok(); +/// let mut builder = HttpResponse::Ok(); /// builder.insert_header( /// IfRange::EntityTag( /// EntityTag::new(false, "abc".to_owned()) @@ -49,9 +50,9 @@ use crate::HttpMessage; /// /// ``` /// use std::time::{Duration, SystemTime}; -/// use actix_http::{http::header::IfRange, Response}; +/// use actix_web::{http::header::IfRange, HttpResponse}; /// -/// let mut builder = Response::Ok(); +/// let mut builder = HttpResponse::Ok(); /// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// IfRange::Date(fetched.into()) @@ -111,7 +112,7 @@ impl IntoHeaderValue for IfRange { #[cfg(test)] mod test_if_range { use super::IfRange as HeaderField; - use crate::header::*; + use crate::http::header::*; use std::str; test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); diff --git a/actix-http/src/header/common/if_unmodified_since.rs b/src/http/header/if_unmodified_since.rs similarity index 85% rename from actix-http/src/header/common/if_unmodified_since.rs rename to src/http/header/if_unmodified_since.rs index 1c2b4af78..8887982aa 100644 --- a/actix-http/src/header/common/if_unmodified_since.rs +++ b/src/http/header/if_unmodified_since.rs @@ -1,6 +1,6 @@ -use crate::header::{HttpDate, IF_UNMODIFIED_SINCE}; +use super::{HttpDate, IF_UNMODIFIED_SINCE}; -header! { +crate::header! { /// `If-Unmodified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) /// @@ -24,10 +24,10 @@ header! { /// /// ``` /// use std::time::{SystemTime, Duration}; - /// use actix_http::Response; - /// use actix_http::http::header::IfUnmodifiedSince; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::IfUnmodifiedSince; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// IfUnmodifiedSince(modified.into()) diff --git a/actix-http/src/header/common/last_modified.rs b/src/http/header/last_modified.rs similarity index 84% rename from actix-http/src/header/common/last_modified.rs rename to src/http/header/last_modified.rs index 65608d846..9ed6fcf69 100644 --- a/actix-http/src/header/common/last_modified.rs +++ b/src/http/header/last_modified.rs @@ -1,6 +1,6 @@ -use crate::header::{HttpDate, LAST_MODIFIED}; +use super::{HttpDate, LAST_MODIFIED}; -header! { +crate::header! { /// `Last-Modified` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) /// @@ -23,10 +23,10 @@ header! { /// /// ``` /// use std::time::{SystemTime, Duration}; - /// use actix_http::Response; - /// use actix_http::http::header::LastModified; + /// use actix_web::HttpResponse; + /// use actix_web::http::header::LastModified; /// - /// let mut builder = Response::Ok(); + /// let mut builder = HttpResponse::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// builder.insert_header( /// LastModified(modified.into()) diff --git a/actix-http/src/header/common/mod.rs b/src/http/header/mod.rs similarity index 92% rename from actix-http/src/header/common/mod.rs rename to src/http/header/mod.rs index 90e0a855e..a1c405344 100644 --- a/actix-http/src/header/common/mod.rs +++ b/src/http/header/mod.rs @@ -7,6 +7,10 @@ //! is used, such as `ContentType(pub Mime)`. #![cfg_attr(rustfmt, rustfmt_skip)] +use std::fmt; +use bytes::{BytesMut, Bytes}; + +pub use actix_http::http::header::*; pub use self::accept_charset::AcceptCharset; //pub use self::accept_encoding::AcceptEncoding; pub use self::accept::Accept; @@ -18,7 +22,6 @@ pub use self::content_disposition::{ }; pub use self::content_language::ContentLanguage; pub use self::content_range::{ContentRange, ContentRangeSpec}; -pub use self::content_encoding::{ContentEncoding}; pub use self::content_type::ContentType; pub use self::date::Date; pub use self::etag::ETag; @@ -29,7 +32,39 @@ pub use self::if_none_match::IfNoneMatch; pub use self::if_range::IfRange; pub use self::if_unmodified_since::IfUnmodifiedSince; pub use self::last_modified::LastModified; +pub use self::encoding::Encoding; +pub use self::entity::EntityTag; //pub use self::range::{Range, ByteRangeSpec}; +pub(crate) use actix_http::http::header::{fmt_comma_delimited, from_comma_delimited, from_one_raw_str}; + +#[derive(Debug, Default)] +struct Writer { + buf: BytesMut, +} + +impl Writer { + pub fn new() -> Writer { + Writer::default() + } + + pub 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) + } +} + #[doc(hidden)] #[macro_export] @@ -61,9 +96,9 @@ macro_rules! __hyper__tm { #[cfg(test)] mod $tm{ use std::str; - use http::Method; + use actix_http::http::Method; use mime::*; - use $crate::header::*; + use $crate::http::header::*; use super::$id as HeaderField; $($tf)* } @@ -77,8 +112,7 @@ macro_rules! test_header { ($id:ident, $raw:expr) => { #[test] fn $id() { - use super::*; - use $crate::test; + use actix_http::test; let raw = $raw; let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); @@ -106,7 +140,7 @@ macro_rules! test_header { ($id:ident, $raw:expr, $typed:expr) => { #[test] fn $id() { - use $crate::test; + use actix_http::test; let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); let mut req = test::TestRequest::default(); @@ -134,6 +168,7 @@ macro_rules! test_header { }; } +#[doc(hidden)] #[macro_export] macro_rules! header { // $a:meta: Attributes associated with the header item (usually docs) @@ -341,7 +376,6 @@ mod allow; mod cache_control; mod content_disposition; mod content_language; -mod content_encoding; mod content_range; mod content_type; mod date; @@ -353,3 +387,5 @@ mod if_none_match; mod if_range; mod if_unmodified_since; mod last_modified; +mod encoding; +mod entity; diff --git a/actix-http/src/header/common/range.rs b/src/http/header/range.rs similarity index 99% rename from actix-http/src/header/common/range.rs rename to src/http/header/range.rs index f9e203bb2..a9b40b403 100644 --- a/actix-http/src/header/common/range.rs +++ b/src/http/header/range.rs @@ -1,8 +1,8 @@ use std::fmt::{self, Display}; use std::str::FromStr; -use header::parsing::from_one_raw_str; -use header::{Header, Raw}; +use super::parsing::from_one_raw_str; +use super::{Header, Raw}; /// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) /// diff --git a/src/http/mod.rs b/src/http/mod.rs new file mode 100644 index 000000000..fa28a5fa9 --- /dev/null +++ b/src/http/mod.rs @@ -0,0 +1,2 @@ +pub mod header; +pub use actix_http::http::*; diff --git a/src/lib.rs b/src/lib.rs index 7a6498546..1a11921a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,11 +71,6 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -#[cfg(feature = "openssl")] -extern crate tls_openssl as openssl; -#[cfg(feature = "rustls")] -extern crate tls_rustls as rustls; - mod app; mod app_service; mod config; @@ -84,6 +79,7 @@ pub mod error; mod extract; pub mod guard; mod handler; +pub mod http; mod info; pub mod middleware; mod request; @@ -102,7 +98,7 @@ pub mod web; #[cfg(feature = "cookies")] pub use actix_http::cookie; pub use actix_http::Response as HttpResponse; -pub use actix_http::{body, http, Error, HttpMessage, ResponseError, Result}; +pub use actix_http::{body, Error, HttpMessage, ResponseError, Result}; pub use actix_rt as rt; pub use actix_web_codegen::*; diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 71193a5c5..0d197ba80 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -137,7 +137,7 @@ mod tests { use crate::{web, App, HttpResponse}; #[actix_rt::test] - #[cfg(feature = "cookies")] + #[cfg(all(feature = "cookies", feature = "compress"))] async fn test_scope_middleware() { use crate::middleware::Compress; @@ -160,7 +160,7 @@ mod tests { } #[actix_rt::test] - #[cfg(feature = "cookies")] + #[cfg(all(feature = "cookies", feature = "compress"))] async fn test_resource_scope_middleware() { use crate::middleware::Compress; diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index a397bccd6..6a56e6de0 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -16,8 +16,8 @@ use actix_http::{ Error, }; use actix_service::{Service, Transform}; +use actix_utils::future::{ok, Ready}; use futures_core::ready; -use futures_util::future::{ok, Ready}; use pin_project::pin_project; use crate::{ diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index dd599a0cb..d1ba7ee4d 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -3,7 +3,9 @@ use std::task::{Context, Poll}; use actix_service::{Service, Transform}; -use futures_util::future::{Either, FutureExt, LocalBoxFuture}; +use actix_utils::future::Either; +use futures_core::future::LocalBoxFuture; +use futures_util::future::FutureExt as _; /// Middleware for conditionally enabling other middleware. /// @@ -85,8 +87,8 @@ where fn call(&self, req: Req) -> Self::Future { match self { - ConditionMiddleware::Enable(service) => Either::Left(service.call(req)), - ConditionMiddleware::Disable(service) => Either::Right(service.call(req)), + ConditionMiddleware::Enable(service) => Either::left(service.call(req)), + ConditionMiddleware::Disable(service) => Either::right(service.call(req)), } } } @@ -94,7 +96,7 @@ where #[cfg(test)] mod tests { use actix_service::IntoService; - use futures_util::future::ok; + use actix_utils::future::ok; use super::*; use crate::{ diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index 12d70ab2c..d8a947aab 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -9,10 +9,8 @@ use std::{ task::{Context, Poll}, }; -use futures_util::{ - future::{ready, Ready}, - ready, -}; +use actix_utils::future::{ready, Ready}; +use futures_core::ready; use crate::{ dev::{Service, Transform}, @@ -188,7 +186,7 @@ where #[cfg(test)] mod tests { use actix_service::IntoService; - use futures_util::future::ok; + use actix_utils::future::ok; use super::*; use crate::{ diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index fddd87a99..88834f8ce 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -175,7 +175,8 @@ where #[cfg(test)] mod tests { use actix_service::IntoService; - use futures_util::future::{ok, FutureExt}; + use actix_utils::future::ok; + use futures_util::future::FutureExt as _; use super::*; use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 8f5391757..3fd372117 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -13,8 +13,9 @@ use std::{ }; use actix_service::{Service, Transform}; +use actix_utils::future::{ok, Ready}; use bytes::Bytes; -use futures_util::future::{ok, Ready}; +use futures_core::ready; use log::{debug, warn}; use regex::{Regex, RegexSet}; use time::OffsetDateTime; @@ -269,7 +270,7 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); - let res = match futures_util::ready!(this.fut.poll(cx)) { + let res = match ready!(this.fut.poll(cx)) { Ok(res) => res, Err(e) => return Poll::Ready(Err(e)), }; @@ -588,7 +589,7 @@ impl<'a> fmt::Display for FormatDisplay<'a> { #[cfg(test)] mod tests { use actix_service::{IntoService, Service, Transform}; - use futures_util::future::ok; + use actix_utils::future::ok; use super::*; use crate::http::{header, StatusCode}; diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 2a97a047b..ec6c2a344 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -2,8 +2,8 @@ use actix_http::http::{PathAndQuery, Uri}; use actix_service::{Service, Transform}; +use actix_utils::future::{ready, Ready}; use bytes::Bytes; -use futures_util::future::{ready, Ready}; use regex::Regex; use crate::{ diff --git a/src/request.rs b/src/request.rs index 15c97345c..f3cbc07b8 100644 --- a/src/request.rs +++ b/src/request.rs @@ -5,7 +5,7 @@ use std::{fmt, net}; use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_router::{Path, Url}; -use futures_util::future::{ok, Ready}; +use actix_utils::future::{ok, Ready}; use smallvec::SmallVec; use crate::app_service::AppInitServiceState; @@ -52,6 +52,18 @@ impl HttpRequest { }), } } + + #[doc(hidden)] + pub fn __priv_test_new( + path: Path, + head: Message, + rmap: Rc, + config: AppConfig, + app_data: Rc, + ) -> HttpRequest { + let app_state = AppInitServiceState::new(rmap, config); + Self::new(path, head, app_state, app_data) + } } impl HttpRequest { diff --git a/src/request_data.rs b/src/request_data.rs index fc711d011..60471cbf9 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -1,7 +1,7 @@ use std::{any::type_name, ops::Deref}; use actix_http::error::{Error, ErrorInternalServerError}; -use futures_util::future; +use actix_utils::future::{err, ok, Ready}; use crate::{dev::Payload, FromRequest, HttpRequest}; @@ -67,11 +67,11 @@ impl Deref for ReqData { impl FromRequest for ReqData { type Config = (); type Error = Error; - type Future = future::Ready>; + type Future = Ready>; fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { if let Some(st) = req.extensions().get::() { - future::ok(ReqData(st.clone())) + ok(ReqData(st.clone())) } else { log::debug!( "Failed to construct App-level ReqData extractor. \ @@ -79,7 +79,7 @@ impl FromRequest for ReqData { req.path(), type_name::(), ); - future::err(ErrorInternalServerError( + err(ErrorInternalServerError( "Missing expected request extension data", )) } diff --git a/src/resource.rs b/src/resource.rs index 8f356c76d..e868bb547 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -519,7 +519,7 @@ mod tests { use actix_rt::time::sleep; use actix_service::Service; - use futures_util::future::ok; + use actix_utils::future::ok; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::middleware::DefaultHeaders; diff --git a/src/scope.rs b/src/scope.rs index 693d6860f..3be6adb0c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -575,8 +575,8 @@ impl ServiceFactory for ScopeEndpoint { #[cfg(test)] mod tests { use actix_service::Service; + use actix_utils::future::ok; use bytes::Bytes; - use futures_util::future::ok; use crate::dev::{Body, ResponseBody}; use crate::http::{header, HeaderValue, Method, StatusCode}; diff --git a/src/server.rs b/src/server.rs index 97c4fdaa2..e3a9f6e01 100644 --- a/src/server.rs +++ b/src/server.rs @@ -71,12 +71,15 @@ impl HttpServer where F: Fn() -> I + Send + Clone + 'static, I: IntoServiceFactory, + S: ServiceFactory + 'static, + // S::Future: 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, >::Future: 'static, S::Service: 'static, + // S::Service: 'static, B: MessageBody + 'static, { /// Create new HTTP server with application factory @@ -288,7 +291,7 @@ where }; svc.finish(map_config(factory(), move |_| { - AppConfig::new(false, addr, host.clone()) + AppConfig::new(false, host.clone(), addr) })) .tcp() })?; @@ -343,10 +346,11 @@ where }; svc.finish(map_config(factory(), move |_| { - AppConfig::new(true, addr, host.clone()) + AppConfig::new(true, host.clone(), addr) })) .openssl(acceptor.clone()) })?; + Ok(self) } @@ -396,10 +400,11 @@ where }; svc.finish(map_config(factory(), move |_| { - AppConfig::new(true, addr, host.clone()) + AppConfig::new(true, host.clone(), addr) })) .rustls(config.clone()) })?; + Ok(self) } @@ -502,8 +507,8 @@ where let c = cfg.lock().unwrap(); let config = AppConfig::new( false, - socket_addr, c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), + socket_addr, ); pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }) @@ -552,8 +557,8 @@ where let c = cfg.lock().unwrap(); let config = AppConfig::new( false, - socket_addr, c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), + socket_addr, ); pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }) .and_then( diff --git a/src/service.rs b/src/service.rs index 32f152f7d..c2ecc0033 100644 --- a/src/service.rs +++ b/src/service.rs @@ -69,6 +69,12 @@ impl ServiceRequest { Self { req, payload } } + /// Construct service request. + #[doc(hidden)] + pub fn __priv_test_new(req: HttpRequest, payload: Payload) -> Self { + Self::new(req, payload) + } + /// Deconstruct request into parts #[inline] pub fn into_parts(self) -> (HttpRequest, Payload) { @@ -602,7 +608,7 @@ mod tests { use crate::test::{init_service, TestRequest}; use crate::{guard, http, web, App, HttpResponse}; use actix_service::Service; - use futures_util::future::ok; + use actix_utils::future::ok; #[actix_rt::test] async fn test_service() { diff --git a/src/test.rs b/src/test.rs index 7e8bf1069..37fb96e0e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,47 +1,41 @@ //! Various helpers for Actix applications to use during testing. -use std::net::SocketAddr; -use std::rc::Rc; -use std::sync::mpsc; -use std::{fmt, net, thread, time}; +use std::{net::SocketAddr, rc::Rc}; -use actix_codec::{AsyncRead, AsyncWrite, Framed}; #[cfg(feature = "cookies")] use actix_http::cookie::Cookie; -use actix_http::http::header::{ContentType, IntoHeaderPair}; -use actix_http::http::{Method, StatusCode, Uri, Version}; -use actix_http::test::TestRequest as HttpTestRequest; -use actix_http::{ws, Extensions, HttpService, Request}; -use actix_router::{Path, ResourceDef, Url}; -use actix_rt::{time::sleep, System}; -use actix_service::{map_config, IntoService, IntoServiceFactory, Service, ServiceFactory}; -use awc::error::PayloadError; -use awc::{Client, ClientRequest, ClientResponse, Connector}; -use bytes::{Bytes, BytesMut}; -use futures_core::Stream; -use futures_util::future::ok; -use futures_util::StreamExt; -use serde::de::DeserializeOwned; -use serde::Serialize; -use socket2::{Domain, Protocol, Socket, Type}; - pub use actix_http::test::TestBuffer; +use actix_http::{ + http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version}, + test::TestRequest as HttpTestRequest, + Extensions, Request, +}; +use actix_router::{Path, ResourceDef, Url}; +use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory}; +use actix_utils::future::ok; +use futures_core::Stream; +use futures_util::StreamExt as _; +use serde::{de::DeserializeOwned, Serialize}; -use crate::app_service::AppInitServiceState; -use crate::config::AppConfig; -use crate::data::Data; -use crate::dev::{Body, MessageBody, Payload, Server}; -use crate::rmap::ResourceMap; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::{Error, HttpRequest, HttpResponse}; +use crate::{ + app_service::AppInitServiceState, + config::AppConfig, + data::Data, + dev::{Body, MessageBody, Payload}, + http::header::ContentType, + rmap::ResourceMap, + service::{ServiceRequest, ServiceResponse}, + web::{Bytes, BytesMut}, + Error, HttpRequest, HttpResponse, +}; -/// Create service that always responds with `HttpResponse::Ok()` +/// Create service that always responds with `HttpResponse::Ok()` and no body. pub fn ok_service( ) -> impl Service, Error = Error> { default_service(StatusCode::OK) } -/// Create service that responds with response with specified status code +/// Create service that always responds with given status code and no body. pub fn default_service( status_code: StatusCode, ) -> impl Service, Error = Error> { @@ -51,8 +45,7 @@ pub fn default_service( .into_service() } -/// This method accepts application builder instance, and constructs -/// service. +/// Initialize service from application builder instance. /// /// ``` /// use actix_service::Service; @@ -83,10 +76,10 @@ where { try_init_service(app) .await - .expect("service initilization failed") + .expect("service initialization failed") } -/// Fallible version of init_service that allows testing data factory errors. +/// Fallible version of [`init_service`] that allows testing initialization errors. pub(crate) async fn try_init_service( app: R, ) -> Result, Error = E>, S::InitError> @@ -162,13 +155,15 @@ where let mut resp = app .call(req) .await - .unwrap_or_else(|_| panic!("read_response failed at application call")); + .unwrap_or_else(|e| panic!("read_response failed at application call: {}", e)); let mut body = resp.take_body(); let mut bytes = BytesMut::new(); + while let Some(item) = body.next().await { bytes.extend_from_slice(&item.unwrap()); } + bytes.freeze() } @@ -254,8 +249,12 @@ where { let body = read_body(res).await; - serde_json::from_slice(&body) - .unwrap_or_else(|e| panic!("read_response_json failed during deserialization: {}", e)) + serde_json::from_slice(&body).unwrap_or_else(|e| { + panic!( + "read_response_json failed during deserialization of body: {:?}, {}", + body, e + ) + }) } pub async fn load_stream(mut stream: S) -> Result @@ -311,8 +310,12 @@ where { let body = read_response(app, req).await; - serde_json::from_slice(&body) - .unwrap_or_else(|_| panic!("read_response_json failed during deserialization")) + serde_json::from_slice(&body).unwrap_or_else(|_| { + panic!( + "read_response_json failed during deserialization of body: {:?}", + body + ) + }) } /// Test `Request` builder. @@ -565,422 +568,12 @@ impl TestRequest { } } -/// Start test server with default configuration -/// -/// Test server is very simple server that simplify process of writing -/// integration tests cases for actix web applications. -/// -/// # Examples -/// -/// ``` -/// use actix_web::{web, test, App, HttpResponse, Error}; -/// -/// async fn my_handler() -> Result { -/// Ok(HttpResponse::Ok().into()) -/// } -/// -/// #[actix_rt::test] -/// async fn test_example() { -/// let srv = test::start( -/// || App::new().service( -/// web::resource("/").to(my_handler)) -/// ); -/// -/// let req = srv.get("/"); -/// let response = req.send().await.unwrap(); -/// assert!(response.status().is_success()); -/// } -/// ``` -pub fn start(factory: F) -> TestServer -where - F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory + 'static, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - >::Future: 'static, - B: MessageBody + 'static, -{ - start_with(TestServerConfig::default(), factory) -} - -/// Start test server with custom configuration -/// -/// Test server could be configured in different ways, for details check -/// `TestServerConfig` docs. -/// -/// # Examples -/// -/// ``` -/// use actix_web::{web, test, App, HttpResponse, Error}; -/// -/// async fn my_handler() -> Result { -/// Ok(HttpResponse::Ok().into()) -/// } -/// -/// #[actix_rt::test] -/// async fn test_example() { -/// let srv = test::start_with(test::config().h1(), || -/// App::new().service(web::resource("/").to(my_handler)) -/// ); -/// -/// let req = srv.get("/"); -/// let response = req.send().await.unwrap(); -/// assert!(response.status().is_success()); -/// } -/// ``` -pub fn start_with(cfg: TestServerConfig, factory: F) -> TestServer -where - F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory + 'static, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - >::Future: 'static, - B: MessageBody + 'static, -{ - let (tx, rx) = mpsc::channel(); - - let ssl = 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 = 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 cfg = cfg.clone(); - let ctimeout = cfg.client_timeout; - let builder = Server::build().workers(1).disable_signals(); - - let srv = match cfg.stream { - StreamType::Tcp => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h1(map_config(factory(), move |_| cfg.clone())) - .tcp() - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h2(map_config(factory(), move |_| cfg.clone())) - .tcp() - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .finish(map_config(factory(), move |_| cfg.clone())) - .tcp() - }), - }, - #[cfg(feature = "openssl")] - StreamType::Openssl(acceptor) => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h1(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h2(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .finish(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) - }), - }, - #[cfg(feature = "rustls")] - StreamType::Rustls(config) => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h1(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h2(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .finish(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) - }), - }, - } - .unwrap(); - - sys.block_on(async { - let srv = srv.run(); - tx.send((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, - ssl, - server, - } -} - -#[derive(Clone)] -pub struct TestServerConfig { - tp: HttpVer, - stream: StreamType, - client_timeout: u64, -} - -#[derive(Clone)] -enum HttpVer { - Http1, - Http2, - Both, -} - -#[derive(Clone)] -enum StreamType { - Tcp, - #[cfg(feature = "openssl")] - Openssl(openssl::ssl::SslAcceptor), - #[cfg(feature = "rustls")] - Rustls(rustls::ServerConfig), -} - -impl Default for TestServerConfig { - fn default() -> Self { - TestServerConfig::new() - } -} - -/// Create default test server config -pub fn config() -> TestServerConfig { - TestServerConfig::new() -} - -impl TestServerConfig { - /// Create default server configuration - pub(crate) fn new() -> TestServerConfig { - TestServerConfig { - tp: HttpVer::Both, - stream: StreamType::Tcp, - client_timeout: 5000, - } - } - - /// Start HTTP/1.1 server only - pub fn h1(mut self) -> Self { - self.tp = HttpVer::Http1; - self - } - - /// Start HTTP/2 server only - pub fn h2(mut self) -> Self { - self.tp = HttpVer::Http2; - self - } - - /// Start openssl server - #[cfg(feature = "openssl")] - pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self { - self.stream = StreamType::Openssl(acceptor); - self - } - - /// Start rustls server - #[cfg(feature = "rustls")] - pub fn rustls(mut self, config: rustls::ServerConfig) -> Self { - self.stream = StreamType::Rustls(config); - self - } - - /// Set server client timeout in milliseconds for first request. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } -} - -/// 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 -pub struct TestServer { - addr: net::SocketAddr, - client: awc::Client, - system: actix_rt::System, - ssl: 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.ssl { "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: S) -> ClientRequest { - self.client.get(self.url(path.as_ref()).as_str()) - } - - /// Create `POST` request - pub fn post>(&self, path: S) -> ClientRequest { - self.client.post(self.url(path.as_ref()).as_str()) - } - - /// Create `HEAD` request - pub fn head>(&self, path: S) -> ClientRequest { - self.client.head(self.url(path.as_ref()).as_str()) - } - - /// Create `PUT` request - pub fn put>(&self, path: S) -> ClientRequest { - self.client.put(self.url(path.as_ref()).as_str()) - } - - /// Create `PATCH` request - pub fn patch>(&self, path: S) -> ClientRequest { - self.client.patch(self.url(path.as_ref()).as_str()) - } - - /// Create `DELETE` request - pub fn delete>(&self, path: S) -> ClientRequest { - self.client.delete(self.url(path.as_ref()).as_str()) - } - - /// Create `OPTIONS` request - pub fn options>(&self, path: S) -> ClientRequest { - self.client.options(self.url(path.as_ref()).as_str()) - } - - /// Connect to test HTTP server - pub fn request>(&self, method: Method, path: S) -> ClientRequest { - self.client.request(method, path.as_ref()) - } - - pub async fn load_body( - &mut self, - mut response: ClientResponse, - ) -> Result - where - S: Stream> + 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, 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, awc::error::WsClientError> { - self.ws_at("/").await - } - - /// Gracefully stop HTTP server - pub async fn stop(self) { - self.server.stop(true).await; - self.system.stop(); - sleep(time::Duration::from_millis(100)).await; - } -} - -impl Drop for TestServer { - fn drop(&mut self) { - self.system.stop() - } -} - #[cfg(test)] mod tests { + use std::time::SystemTime; + use actix_http::HttpMessage; use serde::{Deserialize, Serialize}; - use std::time::SystemTime; use super::*; use crate::{http::header, web, App, HttpResponse, Responder}; diff --git a/src/types/either.rs b/src/types/either.rs index bbab48dec..210495e47 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -1,7 +1,8 @@ //! For either helper, see [`Either`]. use bytes::Bytes; -use futures_util::{future::LocalBoxFuture, FutureExt, TryFutureExt}; +use futures_core::future::LocalBoxFuture; +use futures_util::{FutureExt as _, TryFutureExt as _}; use crate::{ dev, diff --git a/src/types/form.rs b/src/types/form.rs index 57a742e38..0985bd945 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -12,10 +12,8 @@ use std::{ use actix_http::Payload; use bytes::BytesMut; use encoding_rs::{Encoding, UTF_8}; -use futures_util::{ - future::{FutureExt, LocalBoxFuture}, - StreamExt, -}; +use futures_core::future::LocalBoxFuture; +use futures_util::{FutureExt as _, StreamExt as _}; use serde::{de::DeserializeOwned, Serialize}; #[cfg(feature = "compress")] diff --git a/src/types/header.rs b/src/types/header.rs new file mode 100644 index 000000000..1f8be707a --- /dev/null +++ b/src/types/header.rs @@ -0,0 +1,112 @@ +//! For header extractor helper documentation, see [`Header`](crate::types::Header). + +use std::{fmt, ops}; + +use actix_utils::future::{err, ok, Ready}; + +use crate::{ + dev::Payload, error::ParseError, extract::FromRequest, http::header::Header as ParseHeader, + HttpRequest, +}; + +/// Extract typed headers from the request. +/// +/// To extract a header, the inner type `T` must implement the +/// [`Header`](crate::http::header::Header) trait. +/// +/// # Examples +/// ``` +/// use actix_web::{get, web, http::header}; +/// +/// #[get("/")] +/// async fn index(date: web::Header) -> String { +/// format!("Request was sent at {}", date.to_string()) +/// } +/// ``` +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Header(pub T); + +impl Header { + /// Unwrap into the inner `T` value. + pub fn into_inner(self) -> T { + self.0 + } +} + +impl ops::Deref for Header { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl ops::DerefMut for Header { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl fmt::Debug for Header +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Header: {:?}", self.0) + } +} + +impl fmt::Display for Header +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl FromRequest for Header +where + T: ParseHeader, +{ + type Error = ParseError; + type Future = Ready>; + type Config = (); + + #[inline] + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + match ParseHeader::parse(req) { + Ok(header) => ok(Header(header)), + Err(e) => err(e), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::{header, Method}; + use crate::test::TestRequest; + + #[actix_rt::test] + async fn test_header_extract() { + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) + .insert_header((header::ALLOW, header::Allow(vec![Method::GET]))) + .to_http_parts(); + + let s = Header::::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!(s.into_inner().0, mime::APPLICATION_JSON); + + let s = Header::::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!(s.into_inner().0, vec![Method::GET]); + + assert!(Header::::from_request(&req, &mut pl) + .await + .is_err()); + } +} diff --git a/src/types/json.rs b/src/types/json.rs index d8ce3cb71..068dfeb2c 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -11,7 +11,7 @@ use std::{ }; use bytes::BytesMut; -use futures_util::{ready, stream::Stream}; +use futures_core::{ready, stream::Stream as _}; use serde::{de::DeserializeOwned, Serialize}; use actix_http::Payload; diff --git a/src/types/mod.rs b/src/types/mod.rs index a062c351e..461d771eb 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -3,6 +3,7 @@ // TODO: review visibility mod either; pub(crate) mod form; +mod header; pub(crate) mod json; mod path; pub(crate) mod payload; @@ -11,6 +12,7 @@ pub(crate) mod readlines; pub use self::either::{Either, EitherExtractError}; pub use self::form::{Form, FormConfig}; +pub use self::header::Header; pub use self::json::{Json, JsonConfig}; pub use self::path::{Path, PathConfig}; pub use self::payload::{Payload, PayloadConfig}; diff --git a/src/types/path.rs b/src/types/path.rs index 294df6cf2..90ee5296b 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -4,7 +4,7 @@ use std::{fmt, ops, sync::Arc}; use actix_http::error::{Error, ErrorNotFound}; use actix_router::PathDeserializer; -use futures_util::future::{ready, Ready}; +use actix_utils::future::{ready, Ready}; use serde::de; use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest}; diff --git a/src/types/payload.rs b/src/types/payload.rs index 781347b84..f88800855 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -8,13 +8,10 @@ use std::{ }; use actix_http::error::{ErrorBadRequest, PayloadError}; +use actix_utils::future::{ready, Either, Ready}; use bytes::{Bytes, BytesMut}; use encoding_rs::{Encoding, UTF_8}; -use futures_core::stream::Stream; -use futures_util::{ - future::{ready, Either, ErrInto, Ready, TryFutureExt as _}, - ready, -}; +use futures_core::{ready, stream::Stream}; use mime::Mime; use crate::{dev, http::header, web, Error, FromRequest, HttpMessage, HttpRequest}; @@ -26,7 +23,7 @@ use crate::{dev, http::header, web, Error, FromRequest, HttpMessage, HttpRequest /// # Examples /// ``` /// use std::future::Future; -/// use futures_util::stream::{Stream, StreamExt}; +/// use futures_util::stream::StreamExt as _; /// use actix_web::{post, web}; /// /// // `body: web::Payload` parameter extracts raw payload stream from request @@ -91,7 +88,7 @@ impl FromRequest for Payload { impl FromRequest for Bytes { type Config = PayloadConfig; type Error = Error; - type Future = Either, Ready>>; + type Future = Either>>; #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { @@ -99,12 +96,25 @@ impl FromRequest for Bytes { let cfg = PayloadConfig::from_req(req); if let Err(err) = cfg.check_mimetype(req) { - return Either::Right(ready(Err(err))); + return Either::right(ready(Err(err))); } - let limit = cfg.limit; - let fut = HttpMessageBody::new(req, payload).limit(limit); - Either::Left(fut.err_into()) + Either::left(BytesExtractFut { + body_fut: HttpMessageBody::new(req, payload).limit(cfg.limit), + }) + } +} + +/// Future for `Bytes` extractor. +pub struct BytesExtractFut { + body_fut: HttpMessageBody, +} + +impl<'a> Future for BytesExtractFut { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.body_fut).poll(cx).map_err(Into::into) } } @@ -135,21 +145,22 @@ impl FromRequest for String { // check content-type if let Err(err) = cfg.check_mimetype(req) { - return Either::Right(ready(Err(err))); + return Either::right(ready(Err(err))); } // check charset let encoding = match req.encoding() { Ok(enc) => enc, - Err(err) => return Either::Right(ready(Err(err.into()))), + Err(err) => return Either::right(ready(Err(err.into()))), }; let limit = cfg.limit; let body_fut = HttpMessageBody::new(req, payload).limit(limit); - Either::Left(StringExtractFut { body_fut, encoding }) + Either::left(StringExtractFut { body_fut, encoding }) } } +/// Future for `String` extractor. pub struct StringExtractFut { body_fut: HttpMessageBody, encoding: &'static Encoding, diff --git a/src/types/query.rs b/src/types/query.rs index 79af32581..b6c025ef3 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -2,7 +2,7 @@ use std::{fmt, ops, sync::Arc}; -use futures_util::future::{err, ok, Ready}; +use actix_utils::future::{err, ok, Ready}; use serde::de; use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequest}; diff --git a/src/types/readlines.rs b/src/types/readlines.rs index b8bdcc504..6c456e21c 100644 --- a/src/types/readlines.rs +++ b/src/types/readlines.rs @@ -177,7 +177,7 @@ where #[cfg(test)] mod tests { - use futures_util::stream::StreamExt; + use futures_util::stream::StreamExt as _; use super::*; use crate::test::TestRequest; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 12225b7e5..881c6ce94 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -3,14 +3,14 @@ extern crate tls_openssl as openssl; #[cfg(any(unix, feature = "openssl"))] use { - actix_web::{test, web, App, HttpResponse, HttpServer}, + actix_web::{web, App, HttpResponse, HttpServer}, std::{sync::mpsc, thread, time::Duration}, }; #[cfg(unix)] #[actix_rt::test] async fn test_start() { - let addr = test::unused_addr(); + let addr = actix_test::unused_addr(); let (tx, rx) = mpsc::channel(); thread::spawn(move || { @@ -93,7 +93,7 @@ fn ssl_acceptor() -> openssl::ssl::SslAcceptorBuilder { async fn test_start_ssl() { use actix_web::HttpRequest; - let addr = test::unused_addr(); + let addr = actix_test::unused_addr(); let (tx, rx) = mpsc::channel(); thread::spawn(move || { diff --git a/tests/test_server.rs b/tests/test_server.rs index b35af657d..d114b022d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -20,7 +20,7 @@ use flate2::{ write::{GzEncoder, ZlibDecoder, ZlibEncoder}, Compression, }; -use futures_util::ready; +use futures_core::ready; #[cfg(feature = "openssl")] use openssl::{ pkey::PKey, @@ -31,7 +31,7 @@ use rand::{distributions::Alphanumeric, Rng}; use actix_web::dev::BodyEncoding; use actix_web::middleware::{Compress, NormalizePath, TrailingSlash}; -use actix_web::{dev, test, web, App, Error, HttpResponse}; +use actix_web::{dev, web, App, Error, HttpResponse}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -115,7 +115,7 @@ impl futures_core::stream::Stream for TestBody { #[actix_rt::test] async fn test_body() { - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); @@ -129,7 +129,7 @@ async fn test_body() { #[actix_rt::test] async fn test_body_gzip() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) @@ -156,7 +156,7 @@ async fn test_body_gzip() { #[actix_rt::test] async fn test_body_gzip2() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::to(|| { @@ -185,7 +185,7 @@ async fn test_body_gzip2() { #[actix_rt::test] async fn test_body_encoding_override() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::to(|| { @@ -248,7 +248,7 @@ async fn test_body_gzip_large() { let data = STR.repeat(10); let srv_data = data.clone(); - let srv = test::start_with(test::config().h1(), move || { + let srv = actix_test::start_with(actix_test::config().h1(), move || { let data = srv_data.clone(); App::new() .wrap(Compress::new(ContentEncoding::Gzip)) @@ -286,7 +286,7 @@ async fn test_body_gzip_large_random() { .collect::(); let srv_data = data.clone(); - let srv = test::start_with(test::config().h1(), move || { + let srv = actix_test::start_with(actix_test::config().h1(), move || { let data = srv_data.clone(); App::new() .wrap(Compress::new(ContentEncoding::Gzip)) @@ -318,7 +318,7 @@ async fn test_body_gzip_large_random() { #[actix_rt::test] async fn test_body_chunked_implicit() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::get().to(move || { @@ -352,7 +352,7 @@ async fn test_body_chunked_implicit() { #[actix_rt::test] async fn test_body_br_streaming() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Br)) .service(web::resource("/").route(web::to(move || { @@ -384,7 +384,7 @@ async fn test_body_br_streaming() { #[actix_rt::test] async fn test_head_binary() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::head().to(move || HttpResponse::Ok().body(STR))), ) @@ -405,7 +405,7 @@ async fn test_head_binary() { #[actix_rt::test] async fn test_no_chunking() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service(web::resource("/").route(web::to(move || { HttpResponse::Ok() .no_chunking(STR.len() as u64) @@ -424,7 +424,7 @@ async fn test_no_chunking() { #[actix_rt::test] async fn test_body_deflate() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Deflate)) .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) @@ -451,7 +451,7 @@ async fn test_body_deflate() { #[actix_rt::test] async fn test_body_brotli() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Br)) .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) @@ -479,7 +479,7 @@ async fn test_body_brotli() { #[actix_rt::test] async fn test_encoding() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().wrap(Compress::default()).service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -504,7 +504,7 @@ async fn test_encoding() { #[actix_rt::test] async fn test_gzip_encoding() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -530,7 +530,7 @@ async fn test_gzip_encoding() { #[actix_rt::test] async fn test_gzip_encoding_large() { let data = STR.repeat(10); - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -561,7 +561,7 @@ async fn test_reading_gzip_encoding_large_random() { .map(char::from) .collect::(); - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -587,7 +587,7 @@ async fn test_reading_gzip_encoding_large_random() { #[actix_rt::test] async fn test_reading_deflate_encoding() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -613,7 +613,7 @@ async fn test_reading_deflate_encoding() { #[actix_rt::test] async fn test_reading_deflate_encoding_large() { let data = STR.repeat(10); - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -644,7 +644,7 @@ async fn test_reading_deflate_encoding_large_random() { .map(char::from) .collect::(); - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -670,7 +670,7 @@ async fn test_reading_deflate_encoding_large_random() { #[actix_rt::test] async fn test_brotli_encoding() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) @@ -701,7 +701,7 @@ async fn test_brotli_encoding_large() { .map(char::from) .collect::(); - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new().service( web::resource("/") .app_data(web::PayloadConfig::new(320_000)) @@ -732,13 +732,14 @@ async fn test_brotli_encoding_large() { #[actix_rt::test] async fn test_brotli_encoding_large_openssl() { let data = STR.repeat(10); - let srv = test::start_with(test::config().openssl(openssl_config()), move || { - App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { - HttpResponse::Ok() - .encoding(actix_web::http::ContentEncoding::Identity) - .body(bytes) - }))) - }); + let srv = + actix_test::start_with(actix_test::config().openssl(openssl_config()), move || { + App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + HttpResponse::Ok() + .encoding(actix_web::http::ContentEncoding::Identity) + .body(bytes) + }))) + }); // body let mut e = BrotliEncoder::new(Vec::new(), 3); @@ -794,7 +795,7 @@ mod plus_rustls { .map(char::from) .collect::(); - let srv = test::start_with(test::config().rustls(rustls_config()), || { + let srv = actix_test::start_with(actix_test::config().rustls(rustls_config()), || { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { HttpResponse::Ok() .encoding(actix_web::http::ContentEncoding::Identity) @@ -827,7 +828,7 @@ mod plus_rustls { async fn test_server_cookies() { use actix_web::{http, HttpMessage}; - let srv = test::start(|| { + let srv = actix_test::start(|| { App::new().default_service(web::to(|| { HttpResponse::Ok() .cookie( @@ -880,7 +881,7 @@ async fn test_server_cookies() { async fn test_slow_request() { use std::net; - let srv = test::start_with(test::config().client_timeout(200), || { + let srv = actix_test::start_with(actix_test::config().client_timeout(200), || { App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))) }); @@ -898,7 +899,7 @@ async fn test_slow_request() { #[actix_rt::test] async fn test_normalize() { - let srv = test::start_with(test::config().h1(), || { + let srv = actix_test::start_with(actix_test::config().h1(), || { App::new() .wrap(NormalizePath::new(TrailingSlash::Trim)) .service(web::resource("/one").route(web::to(|| HttpResponse::Ok().finish()))) @@ -907,3 +908,51 @@ async fn test_normalize() { let response = srv.get("/one/").send().await.unwrap(); assert!(response.status().is_success()); } + +#[actix_rt::test] +async fn test_data_drop() { + use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }; + + struct TestData(Arc); + + impl TestData { + fn new(inner: Arc) -> 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) { + 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 = actix_test::start(move || { + let data = data.clone(); + + App::new() + .data(data) + .service(web::resource("/").to(|_data: web::Data| async { "ok" })) + }); + + assert!(srv.get("/").send().await.unwrap().status().is_success()); + srv.stop().await; + + assert_eq!(num.load(Ordering::SeqCst), 0); +}